diff --git a/.gitignore b/.gitignore index 8489ff1..63fc34e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,4 @@ doc/ src/constants.h # Other -INFO.md -old/ \ No newline at end of file +INFO.md \ No newline at end of file diff --git a/ESP32-asyncServer-data_screenshot.png b/ESP32-asyncServer-data_screenshot.png index ab909c6..c0fb95a 100644 Binary files a/ESP32-asyncServer-data_screenshot.png and b/ESP32-asyncServer-data_screenshot.png differ diff --git a/README.md b/README.md index d5ab563..c3c2402 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,7 @@ # ESP32-asyncServer-data -This repository contains an implementation of an asynchronous HTTP webserver for an ESP32 microcontroller. The ESP32, connected to a WiFi network, is used to control a LED and read and process the values from a thermistor and a photoresistor. The control and values are sent to the clients connected to the webserver using websockets. +This repository contains an implementation of an asynchronous HTTP webserver for an ESP32 microcontroller. The ESP32, connected to a WiFi network, is used to control a LED as well as read and process the values from a thermistor and a photoresistor. -Apart from the Async server and [websockets](https://en.wikipedia.org/wiki/WebSocket), the following technologies are used: -- [JSON](https://www.json.org/json-en.html) data format. -- [charts.js](https://www.chartjs.org/) for data presentation. -- [Bootstrap](https://getbootstrap.com/) CSS framework. +The communication between the clients and the async webserver is done through [websockets](https://en.wikipedia.org/wiki/WebSocket). [JSON](https://www.json.org/json-en.html) is used as data format and [charts.js](https://www.chartjs.org/) for data visualization. ## Website appearance Website appearance @@ -19,7 +16,8 @@ The following parts have been used in the electronic circuit. Nevertheless, they Electrical schematic ESP32 pins in the schematic are referred as 3.3V, GND and D*. -Note: ESP32 ADC2 pins cannot be used when Wi-Fi is used. + +*Note: ESP32 ADC2 pins cannot be used when Wi-Fi is used.* ## Requirements ESP32 libraries: diff --git a/html/main.html b/html/main.html index b4d5341..7f455be 100644 --- a/html/main.html +++ b/html/main.html @@ -5,196 +5,33 @@ ESP32-asyncServer-data - - - - - + + + + -

ESP32 async server with websockets

-

- LED:   - Sensors: Temperature: 0°C. Illuminance: 0 lx

- -
+

ESP32 async server with websockets

+

+ + + SENSORS: Temperature: 0°C. Illuminance: 0 lx. +

+ +

-
+
- - - - - - - - - - + \ No newline at end of file diff --git a/html/main.js b/html/main.js new file mode 100644 index 0000000..a65bc17 --- /dev/null +++ b/html/main.js @@ -0,0 +1,192 @@ +var counter = 0; +var HTMLbutton = document.getElementById("ledButton"); +var led = false; + +// Draw LED +var contextLED = document.getElementById("led").getContext("2d"); +contextLED.arc(25, 25, 15, 0, Math.PI * 2, false); +contextLED.lineWidth = 3; +contextLED.strokeStyle = "black"; +contextLED.fillStyle = "black"; +contextLED.stroke(); +contextLED.fill(); + +var ctxTemp = document.getElementById('temperatureChart').getContext('2d'); +var temperatureChart = new Chart(ctxTemp, { + type: 'line', + data: { + //labels: [1, 2, 3], + datasets: [{ + label: 'Temperature', + borderColor: 'red', + backgroundColor: 'red', + borderWidth: 2, + pointRadius: 1, + fill: false + }] + }, + options: { + legend: { + display: false + }, + responsive: true, + scales: { + xAxes: [{ + display: true, + ticks: { + display: true + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Temperature (ºC)' + }, + ticks: { + min: 0, + max: 40 + } + }] + }, + showLines: true, + elements: { + line: { + tension: 0 // disables bezier curves + } + }, + animation: { + duration: 0 // general animation time + }, + hover: { + animationDuration: 0 // duration of animations when hovering an item + }, + responsiveAnimationDuration: 0 // animation duration after a resize + } +}); + +var ctxIllum = document.getElementById('illuminanceChart').getContext('2d'); +var illuminanceChart = new Chart(ctxIllum, { + type: 'line', + data: { + datasets: [{ + label: 'Illuminance', + borderColor: 'gold', + backgroundColor: 'gold', + borderWidth: 2, + pointRadius: 1, + fill: false + }] + }, + options: { + legend: { + display: false + }, + responsive: true, + scales: { + xAxes: [{ + display: true, + ticks: { + display: true + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Illuminance (lux)' + }, + ticks: { + min: 0, + max: 10000 + } + }] + }, + showLines: true, + animation: { + duration: 0 // general animation time + }, + hover: { + animationDuration: 0 // duration of animations when hovering an item + }, + responsiveAnimationDuration: 0 // animation duration after a resize + } +}); + +var webSocket = new WebSocket("ws://10.42.0.190/ws"); +webSocket.onopen = function (event) { + HTMLbutton.disabled = false; +} +webSocket.onclose = function (event) { + HTMLbuton.disabled = true; +} +webSocket.onmessage = function (event) { + let jsonObj = JSON.parse(event.data); + // LED change + if ((jsonObj["ledStatus"] != undefined) && (jsonObj["ledStatus"] != led)) { + led = jsonObj["ledStatus"]; + updateLed(); + } + // Sensors update + else { + let temp = jsonObj["temperature"]; + let illum = jsonObj["illuminance"]; + updateValues(temp, illum); + updateCharts(temp, illum); + } +} + +function changeLed() { + webSocket.send("C"); +} + +function updateLed() { + if (led) { + contextLED.fillStyle = "red"; + contextLED.fill(); + } + else { + contextLED.fillStyle = "black"; + contextLED.fill(); + } +} + +//window.setInterval(updateValues, 10); // Update values used when testing + +function updateValues(temperature, illuminance) { + //temperature = Math.floor(Math.random() * 100); // Testing + //illuminance = Math.floor(Math.random() * 100); // Testing + document.getElementById("temperature").innerHTML = temperature; + document.getElementById("illuminance").innerHTML = illuminance; +} + +function updateCharts(temperature, illuminance) { + let date = new Date(); + let timeDislpayed = date.getMinutes().toString().padStart(2, '0') + ":" + date.getSeconds().toString().padStart(2, '0'); + addData(temperatureChart, timeDislpayed, [temperature]); + addData(illuminanceChart, timeDislpayed, [illuminance]); + // Remove values from chart after 100 data + if (counter < 100) { + counter++; + } + else { + removeData(temperatureChart); + removeData(illuminanceChart); + } +} + +function addData(chart, label, data) { + chart.data.labels.push(label); + chart.data.datasets.forEach((dataset) => { + dataset.data.push(data); + }); + chart.update(); +} + +function removeData(chart) { + chart.data.labels.shift(); + chart.data.datasets.forEach((dataset) => { + dataset.data.shift(); + }); + chart.update(); +} \ No newline at end of file diff --git a/html/styles.css b/html/styles.css new file mode 100644 index 0000000..319e641 --- /dev/null +++ b/html/styles.css @@ -0,0 +1,10 @@ +html { + text-align: center; + font-family: Arial, Helvetica, sans-serif; +} +#title { + margin: 0px auto; + text-decoration: underline; +} +#data { margin: 5px auto;} +#led { margin-bottom: -20px;} \ No newline at end of file diff --git a/src/constants.h.TEMPLATE b/src/constants.h.TEMPLATE index d4ce4ff..6f88f98 100644 --- a/src/constants.h.TEMPLATE +++ b/src/constants.h.TEMPLATE @@ -18,8 +18,8 @@ namespace Constants { // ESP32 constants - const uint16_t resolution(4095); // Max analog value - const float maxVoltage(3.3); // Max voltage + const uint16_t resolution(4095); // Max analog value + const float maxVoltage(3.3); // Max voltage // WiFI parameters (TO BE MODIFIED) const char ssid[] = "SSID"; @@ -32,15 +32,15 @@ namespace Constants const uint8_t ledPin(13); // Thermistor MF52A - const uint8_t thermistorPin(32); // Connected through a voltage divider + const uint8_t thermistorPin(32); // Connected through a voltage divider const uint16_t beta(3950); - const uint16_t resitanceTherm(1000); // 1 kohm - const float parameterB(3950); // Simplified B parameter equation from Steinhart-Hart equation + const uint16_t resitanceTherm(1000); // 1 kohm + const float parameterB(3950); // Simplified B parameter equation from Steinhart-Hart equation const float parameterR0(23000); // Photoresistor - const uint8_t photoresistorPin(34); // Connected through a voltage divider - const uint16_t resistancePhoto(10000); // 10 kohm + const uint8_t photoresistorPin(34); // Connected through a voltage divider + const uint16_t resistancePhoto(10000); // 10 kohm const float minResistance(1); // 100 kohm @ 1 lx and 30 kohm @ 10 lx (See graph) const float parameter1(-1.91248928939); @@ -48,7 +48,9 @@ namespace Constants // Values to smooth the readings const uint8_t iterations(5); - const uint8_t numReadings(3); // Smoothing https://www.arduino.cc/en/tutorial/smoothing -} + const uint8_t numReadings(3); // Smoothing https://www.arduino.cc/en/tutorial/smoothing +} // namespace Constants + +#endif #endif \ No newline at end of file diff --git a/src/index.h b/src/index.h index 30e96d6..cd97986 100644 --- a/src/index.h +++ b/src/index.h @@ -2,8 +2,8 @@ * @file index.h * @author José Ángel Sánchez (https://github.com/gelanchez) * @brief HTML with the main page. - * @version 0.0.1 - * @date 2020-09-29 + * @version 0.0.2 + * @date 2020-10-17 * @copyright GPL-3.0 */ @@ -11,55 +11,66 @@ #define INDEX_H /** - * @brief Main html page. + * @brief main.html, main.js and styles.css in one char array. */ const char MAIN_page[] PROGMEM = R"=====( - ESP32-asyncServer-data - - - - - + + + + - -

ESP32 async server with websockets

-

- LED:   - Sensors: Temperature: 0°C. Illuminance: 0 lx

- -
+

ESP32 async server with websockets

+

+ + + SENSORS: Temperature: 0°C. Illuminance: 0 lx. +

+ +

-
+
- - - - - - - - - - - )====="; diff --git a/src/mysensors.cpp b/src/mysensors.cpp index fb9de36..5eb9194 100644 --- a/src/mysensors.cpp +++ b/src/mysensors.cpp @@ -11,7 +11,7 @@ #include "constants.h" #include "mysensors.h" -Sensor::Sensor(uint8_t pin): m_pin(pin), m_readings{}, m_readIndex(0), m_total(0), m_value(0) +Sensor::Sensor(uint8_t pin) : m_pin(pin), m_readings{}, m_readIndex(0), m_total(0), m_value(0) { pinMode(pin, INPUT); } @@ -70,11 +70,11 @@ float Sensor::read() m_readIndex = m_readIndex + 1; if (m_readIndex >= Constants::numReadings) - m_readIndex = 0; // Restart intex array + m_readIndex = 0; // Restart intex array // Calculate the average m_value = m_total / Constants::numReadings; - return m_value; + return m_value; } float Sensor::getLastValue() @@ -82,7 +82,7 @@ float Sensor::getLastValue() return m_value; } -Photoresistor::Photoresistor(uint8_t pin): Sensor(pin) +Photoresistor::Photoresistor(uint8_t pin) : Sensor(pin) { } @@ -102,7 +102,7 @@ float Photoresistor::_rawToValue(int rawValue) float Photoresistor::read() { - return round(Sensor::read()); // lux values only as integers + return round(Sensor::read()); // lux values only as integers } void Photoresistor::printLastValue() @@ -112,7 +112,7 @@ void Photoresistor::printLastValue() Serial.println(" lux"); } -Thermistor::Thermistor(uint8_t pin): Sensor(pin) +Thermistor::Thermistor(uint8_t pin) : Sensor(pin) { } @@ -124,13 +124,13 @@ float Thermistor::_rawToValue(int rawValue) { float voltage = (static_cast(rawValue) / Constants::resolution) * Constants::maxVoltage; float resistance = Constants::resitanceTherm * (static_cast(Constants::maxVoltage) / voltage - 1); - float temperature = 1 / (1/298.15 + (1/Constants::parameterB)*log(resistance/Constants::parameterR0)) - 273.15; // 26000 @ 22 degC + float temperature = 1 / (1 / 298.15 + (1 / Constants::parameterB) * log(resistance / Constants::parameterR0)) - 273.15; // 26000 @ 22 degC return temperature; } float Thermistor::read() { - return round(Sensor::read()*10)/10; // degC values only with one decimal + return round(Sensor::read() * 10) / 10; // degC values only with one decimal } void Thermistor::printLastValue() diff --git a/src/webserver.ino b/src/webserver.ino index 684c4d8..aeb0c62 100644 --- a/src/webserver.ino +++ b/src/webserver.ino @@ -1,10 +1,10 @@ /** * @file webserver.ino * @author José Ángel Sánchez (https://github.com/gelanchez) - * @brief Main implementation of the async webserver to control a LED and present - * data from the sensors to the clients using websockets. - * @version 0.0.1 - * @date 2020-09-29 + * @brief Main implementation of the async webserver and communications using + * websockets to control a LED and present sensors data. + * @version 0.1.0 + * @date 2020-10-17 * @copyright GPL-3.0 */ @@ -14,21 +14,23 @@ #include #include "constants.h" #include -#include "index.h" // HTML webpage contents with javascripts +#include "index.h" // HTML webpage contents with javascripts #include "mysensors.h" #include +bool g_ledStatus(LOW); Photoresistor g_photoresistor(Constants::photoresistorPin); Thermistor g_thermistor(Constants::thermistorPin); -StaticJsonDocument<150> g_doc; +StaticJsonDocument<100> g_doc; AsyncWebServer g_server(80); AsyncWebSocket g_ws("/ws"); static unsigned long g_lastUpdate = millis(); -void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); +void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); +void changeLed(); void setup() { @@ -36,7 +38,6 @@ void setup() delay(1000); pinMode(Constants::ledPin, OUTPUT); - pinMode(Constants::photoresistorPin, INPUT); /** * @brief Connect to WiFi. @@ -53,20 +54,18 @@ void setup() /** * @brief Server setup. */ - - g_server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + g_server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->send_P(200, "text/html", MAIN_page); - }); - g_server.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request){ + }); + g_server.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request) { request->send(200, "text/plain", "Hello world"); - }); - g_server.onNotFound([](AsyncWebServerRequest *request){ + }); + g_server.onNotFound([](AsyncWebServerRequest *request) { request->send(404, "text/plain", "Not found"); }); g_ws.onEvent(onWsEvent); g_server.addHandler(&g_ws); - g_server.begin(); } @@ -79,41 +78,58 @@ void loop() g_doc["illuminance"] = g_photoresistor.read(); g_doc["temperature"] = g_thermistor.read(); - /** - * @brief Print values. - */ + // Print values. //serializeJson(g_doc, Serial); //Serial.println(); String output; serializeJson(g_doc, output); - - //char charJson[output.length() + 1]; - //output.toCharArray(charJson, output.length() + 1); - g_ws.textAll(output); - g_ws.cleanupClients(); } } -void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) +/** + * @brief Toggle the LED and send JSON to clients + */ +void changeLed() +{ + g_ledStatus = !g_ledStatus; + if (g_ledStatus) + digitalWrite(Constants::ledPin, HIGH); + else + digitalWrite(Constants::ledPin, LOW); + + g_doc["ledStatus"] = g_ledStatus; + String output; + serializeJson(g_doc, output); + g_ws.textAll(output); +} + +void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { - if(type == WS_EVT_CONNECT) + if (type == WS_EVT_CONNECT) { Serial.println("Websocket client connection received"); } - else if(type == WS_EVT_DISCONNECT) + else if (type == WS_EVT_DISCONNECT) { Serial.println("Client disconnected"); } - else if(type == WS_EVT_DATA) + else if (type == WS_EVT_DATA) { - Serial.println("Data received: "); - for(int i=0; i < len; i++) + if ((char)data[0] == 'C') { - Serial.print((char) data[i]); + changeLed(); } - Serial.println(); - } + else + { + Serial.print("Data received: "); + for (int i = 0; i < len; i++) + { + Serial.print((char)data[i]); + } + Serial.println(); + } + } }