From b13824afe5902b8c7a7e7cd9517c307a901807cd Mon Sep 17 00:00:00 2001 From: sietse jonker Date: Thu, 14 Mar 2024 15:01:15 +0100 Subject: [PATCH 01/31] adds fucntion to dynamically scale according to amount of nodes --- web/main.js | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/web/main.js b/web/main.js index aa8568b..ac77024 100644 --- a/web/main.js +++ b/web/main.js @@ -4,7 +4,7 @@ const sensorData = {}; let liveGraphs = []; let nodeArray = []; let nodeDict = {}; - +let funny; // letiables let intervalDelay = 5000; let amountOfNodes = 3; @@ -47,9 +47,9 @@ function openConnection() { openConnection(); function handleIncomingData(data) { - nodeAdressHandler(data.Node); - - nodeNumber = nodeDict[data.Node]; + nodeEventHandler(data.node); + funny = data.node; + nodeNumber = nodeDict[data.node]; temperature = data.Temp; humidity = data.Humi; CO2 = data.eCO2; @@ -58,13 +58,23 @@ function handleIncomingData(data) { updateNodeData(nodeNumber, temperature, humidity, CO2, TVOC); } -function nodeAdressHandler(node) { +function nodeEventHandler(node) { if (!nodeArray.includes(node)) { nodeArray.push(node); nodeDict[node] = nodeArray.length; + makeLiveGraph(nodeArray.length); } } +function makeLiveGraph(node) { + createNodeData(node); + liveGraphs.push(new liveGraph(node)); + + liveGraphs.forEach((graph) => { + graph.makeGraph(); + }); +} + //function for making the html elements for the following html code function nodeData(data, node) { let nodeData = document.createElement("div"); @@ -175,19 +185,13 @@ function updateNodeData(node, temperature, humidity, eCO2, TVOC) { document.getElementById("TVOCStatus").textContent = "Connected"; // Update the graph - liveGraphs[0].updateData(temperature, humidity, eCO2, TVOC); - liveGraphs[0].updateGraph(); + liveGraphs[node -1].updateData(temperature, humidity, eCO2, TVOC); + liveGraphs[node - 1].updateGraph(); - console.log(nodeDict[node]); + console.log(nodeDict); console.log(nodeArray); } -// Call the function to create the HTML structure -for (let i = 1; i <= amountOfNodes; i++) { - createNodeData(i); - liveGraphs.push(new liveGraph(i)); -} - // make the graphs liveGraphs.forEach((graph) => { graph.makeGraph(); From 4fd8c3d9582acfa1680b0656adf5a5c1481ca4b1 Mon Sep 17 00:00:00 2001 From: sietse jonker Date: Thu, 14 Mar 2024 15:05:38 +0100 Subject: [PATCH 02/31] Fix node event handling and live graph creation --- web/main.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/main.js b/web/main.js index ac77024..235c834 100644 --- a/web/main.js +++ b/web/main.js @@ -48,7 +48,7 @@ openConnection(); function handleIncomingData(data) { nodeEventHandler(data.node); - funny = data.node; + nodeNumber = nodeDict[data.node]; temperature = data.Temp; humidity = data.Humi; @@ -62,7 +62,8 @@ function nodeEventHandler(node) { if (!nodeArray.includes(node)) { nodeArray.push(node); nodeDict[node] = nodeArray.length; - makeLiveGraph(nodeArray.length); + + makeLiveGraph(nodeArray.length); } } From b3d80c1194a82c8d3c708cf0e7fc018364f392f1 Mon Sep 17 00:00:00 2001 From: sietse jonker Date: Thu, 14 Mar 2024 15:21:51 +0100 Subject: [PATCH 03/31] fixes dynamic node function --- web/main.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web/main.js b/web/main.js index 235c834..8ca88dc 100644 --- a/web/main.js +++ b/web/main.js @@ -29,6 +29,7 @@ function openConnection() { const jsonData = JSON.parse(event.data); // Use the parsed JSON data as needed handleIncomingData(jsonData); + console.log(jsonData); } catch (error) { console.error("Error parsing JSON:", error); @@ -47,8 +48,8 @@ function openConnection() { openConnection(); function handleIncomingData(data) { + if (!data.message) { nodeEventHandler(data.node); - nodeNumber = nodeDict[data.node]; temperature = data.Temp; humidity = data.Humi; @@ -56,6 +57,7 @@ function handleIncomingData(data) { TVOC = data.TVOC; updateNodeData(nodeNumber, temperature, humidity, CO2, TVOC); + } } function nodeEventHandler(node) { @@ -71,9 +73,9 @@ function makeLiveGraph(node) { createNodeData(node); liveGraphs.push(new liveGraph(node)); - liveGraphs.forEach((graph) => { - graph.makeGraph(); - }); + console.log("Node " + node + " added to the liveGraphs array"); + + liveGraphs[node - 1].makeGraph(); } //function for making the html elements for the following html code From 065cb42ef7a605f8d5a9f9d29995f37f3ffa25c5 Mon Sep 17 00:00:00 2001 From: sietse jonker Date: Thu, 14 Mar 2024 17:27:15 +0100 Subject: [PATCH 04/31] Remove unused code and update graph in updateNodeData function --- web/main.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/web/main.js b/web/main.js index 8ca88dc..4721199 100644 --- a/web/main.js +++ b/web/main.js @@ -4,10 +4,6 @@ const sensorData = {}; let liveGraphs = []; let nodeArray = []; let nodeDict = {}; -let funny; -// letiables -let intervalDelay = 5000; -let amountOfNodes = 3; const socket = new WebSocket("ws://145.92.8.114/ws"); function openConnection() { @@ -188,14 +184,6 @@ function updateNodeData(node, temperature, humidity, eCO2, TVOC) { document.getElementById("TVOCStatus").textContent = "Connected"; // Update the graph - liveGraphs[node -1].updateData(temperature, humidity, eCO2, TVOC); + liveGraphs[node - 1].updateData(temperature, humidity, eCO2, TVOC); liveGraphs[node - 1].updateGraph(); - - console.log(nodeDict); - console.log(nodeArray); -} - -// make the graphs -liveGraphs.forEach((graph) => { - graph.makeGraph(); -}); \ No newline at end of file +} \ No newline at end of file From 19034fe7e107c3f581ca6737baee1741c13396a6 Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Fri, 15 Mar 2024 12:40:59 +0100 Subject: [PATCH 05/31] adds sprint2 review documentatie --- docs/Sprint2/sprint2Review.md | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/Sprint2/sprint2Review.md diff --git a/docs/Sprint2/sprint2Review.md b/docs/Sprint2/sprint2Review.md new file mode 100644 index 0000000..8613424 --- /dev/null +++ b/docs/Sprint2/sprint2Review.md @@ -0,0 +1,42 @@ +### Begin met een introductie van het team (jullie namen + teamnaam) + +Welkom bij de sprint review, wij zijn *insert namen*. + +### Leg uit wat wij hebben verbeterd of toegevoegd aan het product ten opzichte van vorige sprint; wat is de opdracht, wat gaat er waarschijnlijk uitkomen. + +Wij hebben ons vooral gefocust op het maken van connecties tussen de database, websocket, nodes en website. Dit zorgt ervoor dat ons MVP nu een ruwe schets is van het uiteindelijke product. We hebben het volgende gedaan: De nodes kunnen succesvol data opsturen in json vorm naar de websocket server. De websocket server kan deze data ontvangen en broadcast het weer naar alle clients. We hebben een python script wat als client meelusiterd op de websocket server en de data in de database zet. We hebben een website gemaakt die de data uit de websocket ontvangt en op de website laat zien. Ook hebben we een rest api waarmee we data van de database kunnen vragen. Het feedback scherm is ook gemaakt en werkt bijna, het is nog een beetje buggy. + +### Bespreek welke User Stories jullie hebben gemaakt voor de Product Backlog. + +*laat belangrijke user stories zien* + +*leg uit waarom* + +### Bespreek welke User Stories jullie hebben opgepakt in de Sprint Backlog. + +- [Luchtmeten](https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/issues/36) +- [Temperatuur meten](https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/issues/18) +- [Schermpje op node](https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/issues/39) + +In de eerste sprint werken we vooral aan het algehele plan en de basis van het project, met name de Raspberry Pi die tot onze beschikking is gesteld voor de backend. Deze moeten we zelf ontwerpen. Daarnaast moeten we zaken zoals de wireflows, de infrastructuur: ![de infrastructuur](../assets/ImagesSp1/digitaleInfrastructuur.png), en het [uml diagram](../brainstorm/UML%20infrastructure.md) regelen. + +### Geef een demonstratie van het product. + +*show node* + +### Geef een overzicht van jullie portfoliowebsite (highlights). + +We hebben op onze portfoliopagina verschillende documentaties geplaatst, met name: +- [Het algehele idee](https://opti.smikkelbakje.nl/brainstorm/idee%C3%ABn/) +- [Databaseontwerp](https://opti.smikkelbakje.nl/brainstorm/Database/) +- [I2C-communicatie](https://opti.smikkelbakje.nl/arduino-documentation/i2c-ESP32/) + + +## Wie doet wat: + +dano laat website zien, ik laat het dynamisch toevoegen van nodes zien, en een kleine uitleg waarom dit zo belangrijk is. Bram laat dan zien dat alle data in de database komt door een python script. Sam laat dan het fysieke model zien van het feedback scherm. Sietse laat de userstories zien. Dan laat dano de volgende documentatie zien: + +- OOP - class grafiek +- Infrastructuur - UML +- Gebruikerstest - feedback scherm vragen +- embedded ontwerpen en maken - tft screen van sam \ No newline at end of file From 7d8f8201002b26cf8b22a8909a68958a94c5e83f Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Tue, 19 Mar 2024 10:45:02 +0100 Subject: [PATCH 06/31] Update sprint2Review.md with sprint review details --- docs/Sprint2/sprint2Review.md | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/docs/Sprint2/sprint2Review.md b/docs/Sprint2/sprint2Review.md index 8613424..4d46494 100644 --- a/docs/Sprint2/sprint2Review.md +++ b/docs/Sprint2/sprint2Review.md @@ -1,37 +1,3 @@ -### Begin met een introductie van het team (jullie namen + teamnaam) - -Welkom bij de sprint review, wij zijn *insert namen*. - -### Leg uit wat wij hebben verbeterd of toegevoegd aan het product ten opzichte van vorige sprint; wat is de opdracht, wat gaat er waarschijnlijk uitkomen. - -Wij hebben ons vooral gefocust op het maken van connecties tussen de database, websocket, nodes en website. Dit zorgt ervoor dat ons MVP nu een ruwe schets is van het uiteindelijke product. We hebben het volgende gedaan: De nodes kunnen succesvol data opsturen in json vorm naar de websocket server. De websocket server kan deze data ontvangen en broadcast het weer naar alle clients. We hebben een python script wat als client meelusiterd op de websocket server en de data in de database zet. We hebben een website gemaakt die de data uit de websocket ontvangt en op de website laat zien. Ook hebben we een rest api waarmee we data van de database kunnen vragen. Het feedback scherm is ook gemaakt en werkt bijna, het is nog een beetje buggy. - -### Bespreek welke User Stories jullie hebben gemaakt voor de Product Backlog. - -*laat belangrijke user stories zien* - -*leg uit waarom* - -### Bespreek welke User Stories jullie hebben opgepakt in de Sprint Backlog. - -- [Luchtmeten](https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/issues/36) -- [Temperatuur meten](https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/issues/18) -- [Schermpje op node](https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/issues/39) - -In de eerste sprint werken we vooral aan het algehele plan en de basis van het project, met name de Raspberry Pi die tot onze beschikking is gesteld voor de backend. Deze moeten we zelf ontwerpen. Daarnaast moeten we zaken zoals de wireflows, de infrastructuur: ![de infrastructuur](../assets/ImagesSp1/digitaleInfrastructuur.png), en het [uml diagram](../brainstorm/UML%20infrastructure.md) regelen. - -### Geef een demonstratie van het product. - -*show node* - -### Geef een overzicht van jullie portfoliowebsite (highlights). - -We hebben op onze portfoliopagina verschillende documentaties geplaatst, met name: -- [Het algehele idee](https://opti.smikkelbakje.nl/brainstorm/idee%C3%ABn/) -- [Databaseontwerp](https://opti.smikkelbakje.nl/brainstorm/Database/) -- [I2C-communicatie](https://opti.smikkelbakje.nl/arduino-documentation/i2c-ESP32/) - - ## Wie doet wat: dano laat website zien, ik laat het dynamisch toevoegen van nodes zien, en een kleine uitleg waarom dit zo belangrijk is. Bram laat dan zien dat alle data in de database komt door een python script. Sam laat dan het fysieke model zien van het feedback scherm. Sietse laat de userstories zien. Dan laat dano de volgende documentatie zien: From b28ae0e4a170abafcfa3625bf7f82486e856bf9c Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Tue, 19 Mar 2024 11:13:47 +0100 Subject: [PATCH 07/31] moved file --- arduino/node-code/{node-code-final => }/node-code-final.ino | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename arduino/node-code/{node-code-final => }/node-code-final.ino (100%) diff --git a/arduino/node-code/node-code-final/node-code-final.ino b/arduino/node-code/node-code-final.ino similarity index 100% rename from arduino/node-code/node-code-final/node-code-final.ino rename to arduino/node-code/node-code-final.ino From a8f6491736c7dd3bd344121b16e9e1f0abb74bef Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Tue, 19 Mar 2024 12:17:39 +0100 Subject: [PATCH 08/31] save state for my oop in aduino --- .../headerFile.h | 10 +++--- .../nodeCode.cpp | 0 .../nodeCodeFinal.ino | 4 ++- .../nodeCodeHeader.cpp | 29 ++++++++------- .../nodeCodeHeader.h | 3 ++ .../node-code/nodeCodeFinal/websockets.cpp | 35 +++++++++++++++++++ arduino/node-code/nodeCodeFinal/websockets.h | 14 ++++++++ .../nodeCodeFinal/websocketsHeader.h | 14 ++++++++ 8 files changed, 92 insertions(+), 17 deletions(-) rename arduino/node-code/{node-code-final => nodeCodeFinal}/headerFile.h (73%) rename arduino/node-code/{node-code-final => nodeCodeFinal}/nodeCode.cpp (100%) rename arduino/node-code/{node-code-final => nodeCodeFinal}/nodeCodeFinal.ino (76%) rename arduino/node-code/{node-code-final => nodeCodeFinal}/nodeCodeHeader.cpp (56%) rename arduino/node-code/{node-code-final => nodeCodeFinal}/nodeCodeHeader.h (90%) create mode 100644 arduino/node-code/nodeCodeFinal/websockets.cpp create mode 100644 arduino/node-code/nodeCodeFinal/websockets.h create mode 100644 arduino/node-code/nodeCodeFinal/websocketsHeader.h diff --git a/arduino/node-code/node-code-final/headerFile.h b/arduino/node-code/nodeCodeFinal/headerFile.h similarity index 73% rename from arduino/node-code/node-code-final/headerFile.h rename to arduino/node-code/nodeCodeFinal/headerFile.h index 1ef0ea7..cdf4357 100644 --- a/arduino/node-code/node-code-final/headerFile.h +++ b/arduino/node-code/nodeCodeFinal/headerFile.h @@ -2,11 +2,12 @@ #include #include #include -#include +#include "DHT.h" #include #include -#include +// #include #include +#include // define pins on esp32 #define MICPIN 6 @@ -21,11 +22,12 @@ #define USE_SERIAL Serial // make new objects -Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +// Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +// Adafruit_SH110X display = Adafruit_SH110X(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); DHT dht(DHTPIN, DHTTYPE); WiFiMulti WiFiMulti; Adafruit_SGP30 sgp; -WebSocketsClient webSocket; +// WebSocketsClient webSocket; // define variables uint16_t TVOC_base, eCO2_base; diff --git a/arduino/node-code/node-code-final/nodeCode.cpp b/arduino/node-code/nodeCodeFinal/nodeCode.cpp similarity index 100% rename from arduino/node-code/node-code-final/nodeCode.cpp rename to arduino/node-code/nodeCodeFinal/nodeCode.cpp diff --git a/arduino/node-code/node-code-final/nodeCodeFinal.ino b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino similarity index 76% rename from arduino/node-code/node-code-final/nodeCodeFinal.ino rename to arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino index eb3c086..d0a60dc 100644 --- a/arduino/node-code/node-code-final/nodeCodeFinal.ino +++ b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino @@ -1,12 +1,14 @@ #include +#include nodeReadings esp32Node(); +websockets webSocket(); void setup() { // put your setup code here, to run once: esp32Node.setup(); - esp32Node.websocketSetup(); + webSocket.websocketSetup(); esp32Node.resetValues(); } diff --git a/arduino/node-code/node-code-final/nodeCodeHeader.cpp b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp similarity index 56% rename from arduino/node-code/node-code-final/nodeCodeHeader.cpp rename to arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp index d2a2ec5..ef05d35 100644 --- a/arduino/node-code/node-code-final/nodeCodeHeader.cpp +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp @@ -48,16 +48,21 @@ void nodeReadings::resetValues() { noise = false; } -// hexdump function for websockets binary handler -void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) { - const uint8_t* src = (const uint8_t*) mem; - USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len); - for(uint32_t i = 0; i < len; i++) { - if(i % cols == 0) { - USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i); - } - USE_SERIAL.printf("%02X ", *src); - src++; - } - USE_SERIAL.printf("\n"); + + +void nodeReadings::update(){ + // display sensordata on oled screen + displayData(); + + // webSocket.sendTXT("{\"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp.eCO2) + "\",\"TVOC\":\"" + String(sgp.TVOC) + "\"}"); + webSocket.sendTXT("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp.eCO2) + "\",\"TVOC\":\"" + String(sgp.TVOC) + "\"}"); + + sgp.getIAQBaseline(&eCO2_base, &TVOC_base); + + // read dht11 sensor + temperature = float(dht.readTemperature()); + humidity = float(dht.readHumidity()); + + // check if any errors occured when reading sensors + checkForError(); } \ No newline at end of file diff --git a/arduino/node-code/node-code-final/nodeCodeHeader.h b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h similarity index 90% rename from arduino/node-code/node-code-final/nodeCodeHeader.h rename to arduino/node-code/nodeCodeFinal/nodeCodeHeader.h index 20ed3e4..0959f11 100644 --- a/arduino/node-code/node-code-final/nodeCodeHeader.h +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h @@ -12,6 +12,9 @@ class nodeReadings { void setup(); void loop(); void resetValues(); + void update(); + + private: }; diff --git a/arduino/node-code/nodeCodeFinal/websockets.cpp b/arduino/node-code/nodeCodeFinal/websockets.cpp new file mode 100644 index 0000000..e1d58be --- /dev/null +++ b/arduino/node-code/nodeCodeFinal/websockets.cpp @@ -0,0 +1,35 @@ +#include "arduino.h" +#include "websockets.h" + +// hexdump function for websockets binary handler +void websockets::hexdump(const void *mem, uint32_t len, uint8_t cols = 16) { + const uint8_t* src = (const uint8_t*) mem; + USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len); + for(uint32_t i = 0; i < len; i++) { + if(i % cols == 0) { + USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i); + } + USE_SERIAL.printf("%02X ", *src); + src++; + } + USE_SERIAL.printf("\n"); +} + +// special function to setup websocket +void websockets::websocketSetup(){ + WiFiMulti.addAP("iotroam", "vbK9gbDBIB"); + WiFiMulti.addAP("LansanKPN-boven", "19sander71vlieland14"); + + while(WiFiMulti.run() != WL_CONNECTED) { + delay(100); + } + + // server address, port and URL + webSocket.begin("145.92.8.114", 80, "/ws"); + + // event handler + webSocket.onEvent(webSocketEvent); + + // try ever 500 again if connection has failed + webSocket.setReconnectInterval(500); +} diff --git a/arduino/node-code/nodeCodeFinal/websockets.h b/arduino/node-code/nodeCodeFinal/websockets.h new file mode 100644 index 0000000..c0a1502 --- /dev/null +++ b/arduino/node-code/nodeCodeFinal/websockets.h @@ -0,0 +1,14 @@ +#ifndef websockerts_h +#define websockerts_h + +#include "Arduino.h" +#include "websocketsHeader.h" + +class websockets { + public: + websockets(); + void hexdump(const void *mem, uint32_t len, uint8_t cols = 16); + private: +}; + +#endif \ No newline at end of file diff --git a/arduino/node-code/nodeCodeFinal/websocketsHeader.h b/arduino/node-code/nodeCodeFinal/websocketsHeader.h new file mode 100644 index 0000000..0bc29a9 --- /dev/null +++ b/arduino/node-code/nodeCodeFinal/websocketsHeader.h @@ -0,0 +1,14 @@ +#include +#include +#include +#include "DHT.h" +#include +#include +#include +#include +#include + + + + +// WebSocketsClient webSocket; From a4b8ae0dca5a4c8a04682d1224e479b9820fc830 Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Tue, 19 Mar 2024 16:15:02 +0100 Subject: [PATCH 09/31] Almost done with the OOP --- .../{nodeCodeFinal => }/nodeCode.cpp | 0 arduino/node-code/nodeCodeFinal/headerFile.h | 8 ++- .../node-code/nodeCodeFinal/nodeCodeFinal.ino | 15 +++--- .../nodeCodeFinal/nodeCodeHeader.cpp | 38 ++++++++++++- .../node-code/nodeCodeFinal/nodeCodeHeader.h | 3 +- .../node-code/nodeCodeFinal/websockets.cpp | 53 ++++++++++++++++++- arduino/node-code/nodeCodeFinal/websockets.h | 10 +++- .../nodeCodeFinal/websocketsHeader.h | 14 ----- 8 files changed, 114 insertions(+), 27 deletions(-) rename arduino/node-code/{nodeCodeFinal => }/nodeCode.cpp (100%) delete mode 100644 arduino/node-code/nodeCodeFinal/websocketsHeader.h diff --git a/arduino/node-code/nodeCodeFinal/nodeCode.cpp b/arduino/node-code/nodeCode.cpp similarity index 100% rename from arduino/node-code/nodeCodeFinal/nodeCode.cpp rename to arduino/node-code/nodeCode.cpp diff --git a/arduino/node-code/nodeCodeFinal/headerFile.h b/arduino/node-code/nodeCodeFinal/headerFile.h index cdf4357..7443590 100644 --- a/arduino/node-code/nodeCodeFinal/headerFile.h +++ b/arduino/node-code/nodeCodeFinal/headerFile.h @@ -19,13 +19,19 @@ #define SCREEN_HEIGHT 64 #define i2c_adress 0x3c #define OLED_RESET -1 // QT-PY / XIAO + + #define USE_SERIAL Serial // make new objects // Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // Adafruit_SH110X display = Adafruit_SH110X(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +// Adafruit_SH110X display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); DHT dht(DHTPIN, DHTTYPE); -WiFiMulti WiFiMulti; + +// WiFiMulti WiFiMulti; + Adafruit_SGP30 sgp; // WebSocketsClient webSocket; diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino index d0a60dc..6def471 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino +++ b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino @@ -1,19 +1,20 @@ #include #include +#include -nodeReadings esp32Node(); -websockets webSocket(); +nodeReadings esp32Node; +websockets webSocketClass; -void setup() -{ +void setup() { // put your setup code here, to run once: esp32Node.setup(); - webSocket.websocketSetup(); + webSocketClass.websocketSetup(); esp32Node.resetValues(); } -void loop() -{ +void loop() { // put your main code here, to run repeatedly: esp32Node.loop(); + webSocketClass.loop(); + } diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp index ef05d35..36dbd8a 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp @@ -1,5 +1,8 @@ #include "arduino.h" #include "nodeCodeHeader.h" +#include + +WebSocketsClient webSocket; nodeReadings::nodeReadings() { } @@ -23,7 +26,7 @@ void nodeReadings::setup(){ void nodeReadings::loop() { // loop the websocket connection so it stays alive - webSocket.loop(); + // webSocket.loop(); // update when interval is met if (currentMillis - lastMillis >= interval){ @@ -65,4 +68,37 @@ void nodeReadings::update(){ // check if any errors occured when reading sensors checkForError(); +} + +void nodeReadings::displayData() { + // clear display for new info + display.clearDisplay(); + + // display the data on the oled screen + display.setTextSize(1); + display.setTextColor(SH110X_WHITE); + display.setCursor(0,0); + display.println("Temperature: " + String(temperature) + " C"); + display.println("Humidity: " + String(humidity) + " %"); + display.println("eCO2: " + String(sgp.eCO2) + " ppm"); + display.println("TVOC: " + String(sgp.TVOC) + " ppb"); + display.display(); +} + +void nodeReadings::checkForError(){ + if (!sgp.IAQmeasure()) { + Serial.println("SGP30: BAD"); + errorSGP30 = true; + } else { + Serial.println("SGP30: OK"); + errorSGP30 = false; + } + + if (isnan(temperature) || isnan(humidity)){ + Serial.println("DHT11: BAD"); + errorDHT11 = true; + } else { + Serial.println("DHT11: OK"); + errorDHT11 = false; + } } \ No newline at end of file diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h index 0959f11..710bf20 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h @@ -13,7 +13,8 @@ class nodeReadings { void loop(); void resetValues(); void update(); - + void checkForError(); + void displayData(); private: }; diff --git a/arduino/node-code/nodeCodeFinal/websockets.cpp b/arduino/node-code/nodeCodeFinal/websockets.cpp index e1d58be..d715a68 100644 --- a/arduino/node-code/nodeCodeFinal/websockets.cpp +++ b/arduino/node-code/nodeCodeFinal/websockets.cpp @@ -1,8 +1,17 @@ #include "arduino.h" +#include +#include +#include #include "websockets.h" +WebSocketsClient webSocket; +WiFiMulti WiFiMulti; + +websockets::websockets(){ +} + // hexdump function for websockets binary handler -void websockets::hexdump(const void *mem, uint32_t len, uint8_t cols = 16) { +void websockets::hexdump(const void *mem, uint32_t len, uint8_t cols) { const uint8_t* src = (const uint8_t*) mem; USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len); for(uint32_t i = 0; i < len; i++) { @@ -28,8 +37,48 @@ void websockets::websocketSetup(){ webSocket.begin("145.92.8.114", 80, "/ws"); // event handler - webSocket.onEvent(webSocketEvent); + // webSocket.onEvent(webSocketEvent); // try ever 500 again if connection has failed webSocket.setReconnectInterval(500); } + +void websockets::loop(){ + webSocket.loop(); +} + +// handler for websocket events +void websockets::webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { + switch(type) { + case WStype_DISCONNECTED: + USE_SERIAL.printf("[WSc] Disconnected!\n"); + break; + case WStype_CONNECTED: + USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); + + // send message to server when Connected + webSocket.sendTXT("{\"message\": \"Connected\"}"); + break; + case WStype_TEXT: + // USE_SERIAL.printf("[WSc] get text: %s\n", payload); + + // send message to server + // webSocket.sendTXT("message here"); + break; + case WStype_BIN: + // USE_SERIAL.printf("[WSc] get binary length: %u\n", length); + hexdump(payload, length); + + // send data to server + // webSocket.sendBIN(payload, length); + break; + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_FRAGMENT_FIN: + break; + } +} + + diff --git a/arduino/node-code/nodeCodeFinal/websockets.h b/arduino/node-code/nodeCodeFinal/websockets.h index c0a1502..2483d88 100644 --- a/arduino/node-code/nodeCodeFinal/websockets.h +++ b/arduino/node-code/nodeCodeFinal/websockets.h @@ -1,13 +1,21 @@ #ifndef websockerts_h #define websockerts_h +#include #include "Arduino.h" -#include "websocketsHeader.h" + +#define USE_SERIAL Serial +// WiFiMulti WiFiMulti; + class websockets { public: websockets(); void hexdump(const void *mem, uint32_t len, uint8_t cols = 16); + void websocketSetup(); + void loop(); + void webSocketEvent(WStype_t type, uint8_t * payload, size_t length); + private: }; diff --git a/arduino/node-code/nodeCodeFinal/websocketsHeader.h b/arduino/node-code/nodeCodeFinal/websocketsHeader.h deleted file mode 100644 index 0bc29a9..0000000 --- a/arduino/node-code/nodeCodeFinal/websocketsHeader.h +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include -#include -#include "DHT.h" -#include -#include -#include -#include -#include - - - - -// WebSocketsClient webSocket; From cf18d4ab4f6a8a11775bc94cefdc261c34ec09dc Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Tue, 19 Mar 2024 22:28:16 +0100 Subject: [PATCH 10/31] =?UTF-8?q?OOP=20made=20in=20arduino=20=F0=9F=91=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{nodeCodeFinal => }/headerFile.h | 22 ++-- .../node-code/nodeCodeFinal/nodeCodeFinal.ino | 6 +- .../nodeCodeFinal/nodeCodeHeader.cpp | 103 ++++++++++-------- .../node-code/nodeCodeFinal/nodeCodeHeader.h | 62 +++++++++-- .../node-code/nodeCodeFinal/websockets.cpp | 33 +++--- arduino/node-code/nodeCodeFinal/websockets.h | 12 +- 6 files changed, 149 insertions(+), 89 deletions(-) rename arduino/node-code/{nodeCodeFinal => }/headerFile.h (74%) diff --git a/arduino/node-code/nodeCodeFinal/headerFile.h b/arduino/node-code/headerFile.h similarity index 74% rename from arduino/node-code/nodeCodeFinal/headerFile.h rename to arduino/node-code/headerFile.h index 7443590..94df6d0 100644 --- a/arduino/node-code/nodeCodeFinal/headerFile.h +++ b/arduino/node-code/headerFile.h @@ -1,13 +1,16 @@ +#ifndef headerFile_h +#define headerFile_h + // include these libraries #include -#include -#include -#include "DHT.h" +// #include +// #include +// #include "DHT.h" #include #include // #include -#include -#include +// #include +// #include // define pins on esp32 #define MICPIN 6 @@ -27,12 +30,12 @@ // Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // Adafruit_SH110X display = Adafruit_SH110X(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // Adafruit_SH110X display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); -Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); -DHT dht(DHTPIN, DHTTYPE); +// Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +// DHT dht(DHTPIN, DHTTYPE); // WiFiMulti WiFiMulti; -Adafruit_SGP30 sgp; +// Adafruit_SGP30 sgp; // WebSocketsClient webSocket; // define variables @@ -47,4 +50,5 @@ unsigned long currentMillis; unsigned long lastMillis; bool errorSGP30 = false; bool errorDHT11 = false; -bool noise = false; \ No newline at end of file +bool noise = false; +#endif \ No newline at end of file diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino index 6def471..ea12094 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino +++ b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino @@ -1,6 +1,8 @@ #include -#include -#include +#include "websockets.h" +// #include + + nodeReadings esp32Node; websockets webSocketClass; diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp index 36dbd8a..c8e6bb3 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp @@ -1,41 +1,19 @@ -#include "arduino.h" +// #include #include "nodeCodeHeader.h" -#include +// #include +// #include -WebSocketsClient webSocket; nodeReadings::nodeReadings() { -} -void nodeReadings::setup(){ - // make serial connection at 115200 baud - Serial.begin(115200); + dht = new DHT(DHTPIN, DHTTYPE); + display = new Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); + + webSocket = new WebSocketsClient(); + sgp = new Adafruit_SGP30(); - // tell display what settings to use - display.begin(i2c_adress, true); - display.clearDisplay(); + resetValues(); - // tell sensors to start reading - dht.begin(); - sgp.begin(); - - pinMode(MICPIN, INPUT); - pinMode(DHTPIN, INPUT); - -} - -void nodeReadings::loop() { - // loop the websocket connection so it stays alive - // webSocket.loop(); - - // update when interval is met - if (currentMillis - lastMillis >= interval){ - lastMillis = millis(); - update(); - } - - // update the counter - currentMillis = millis(); } void nodeReadings::resetValues() { @@ -51,20 +29,53 @@ void nodeReadings::resetValues() { noise = false; } +void nodeReadings::setup(){ + // DHT dht(DHTPIN, DHTTYPE); + // make serial connection at 115200 baud + Serial.begin(115200); + + // tell display what settings to use + display->begin(i2c_adress, true); + display->clearDisplay(); + + // tell sensors to start reading + dht->begin(); + sgp->begin(); + + pinMode(MICPIN, INPUT); + pinMode(DHTPIN, INPUT); + +} + +void nodeReadings::loop() { + // loop the websocket connection so it stays alive + // webSocket->loop(); + + // update when interval is met + if (currentMillis - lastMillis >= interval){ + lastMillis = millis(); + update(); + } + + // update the counter + currentMillis = millis(); +} + void nodeReadings::update(){ + // display sensordata on oled screen displayData(); - // webSocket.sendTXT("{\"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp.eCO2) + "\",\"TVOC\":\"" + String(sgp.TVOC) + "\"}"); - webSocket.sendTXT("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp.eCO2) + "\",\"TVOC\":\"" + String(sgp.TVOC) + "\"}"); + // webSocket->sendTXT("{\"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp->eCO2) + "\",\"TVOC\":\"" + String(sgp->TVOC) + "\"}"); + webSocket->sendTXT("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp->eCO2) + "\",\"TVOC\":\"" + String(sgp->TVOC) + "\"}"); - sgp.getIAQBaseline(&eCO2_base, &TVOC_base); + sgp->getIAQBaseline(&eCO2_base, &TVOC_base); // read dht11 sensor - temperature = float(dht.readTemperature()); - humidity = float(dht.readHumidity()); + temperature = float(dht->readTemperature()); + humidity = float(dht->readHumidity()); // check if any errors occured when reading sensors checkForError(); @@ -72,21 +83,21 @@ void nodeReadings::update(){ void nodeReadings::displayData() { // clear display for new info - display.clearDisplay(); + display->clearDisplay(); // display the data on the oled screen - display.setTextSize(1); - display.setTextColor(SH110X_WHITE); - display.setCursor(0,0); - display.println("Temperature: " + String(temperature) + " C"); - display.println("Humidity: " + String(humidity) + " %"); - display.println("eCO2: " + String(sgp.eCO2) + " ppm"); - display.println("TVOC: " + String(sgp.TVOC) + " ppb"); - display.display(); + display->setTextSize(1); + display->setTextColor(SH110X_WHITE); + display->setCursor(0,0); + display->println("Temperature: " + String(temperature) + " C"); + display->println("Humidity: " + String(humidity) + " %"); + display->println("eCO2: " + String(sgp->eCO2) + " ppm"); + display->println("TVOC: " + String(sgp->TVOC) + " ppb"); + display->display(); } void nodeReadings::checkForError(){ - if (!sgp.IAQmeasure()) { + if (!sgp->IAQmeasure()) { Serial.println("SGP30: BAD"); errorSGP30 = true; } else { diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h index 710bf20..76e9f2e 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h @@ -1,22 +1,60 @@ #ifndef nodeReading_h #define nodeReading_h -#include "Arduino.h" -#include "headerFile.h" +#include +// #include "Arduino.h" +#include +// #include "headerFile.h" +#include +#include +#include -class nodeReadings { +// define pins on esp32 +#define MICPIN 6 +#define DHTPIN 7 +#define SCL 9 +#define SDA 8 +#define DHTTYPE DHT11 +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define i2c_adress 0x3c +#define OLED_RESET -1 // QT-PY / XIAO - public: - nodeReadings(); - void setup(); - void loop(); - void resetValues(); - void update(); - void checkForError(); - void displayData(); +#define USE_SERIAL Serial - private: +// WebSocketsClient webSocket; + +class nodeReadings +{ + +public: + nodeReadings(); + void setup(); + void loop(); + void resetValues(); + void update(); + void checkForError(); + void displayData(); + +private: + DHT *dht; + Adafruit_SH1106G *display; + WebSocketsClient *webSocket; + Adafruit_SGP30 *sgp; + + uint16_t TVOC_base, eCO2_base; + uint16_t counter; + uint16_t eCO2; + uint16_t TVOC; + uint16_t interval; + float temperature; + float humidity; + unsigned long currentMillis; + unsigned long lastMillis; + bool errorSGP30; + bool errorDHT11; + bool noise; }; #endif \ No newline at end of file diff --git a/arduino/node-code/nodeCodeFinal/websockets.cpp b/arduino/node-code/nodeCodeFinal/websockets.cpp index d715a68..f8aee50 100644 --- a/arduino/node-code/nodeCodeFinal/websockets.cpp +++ b/arduino/node-code/nodeCodeFinal/websockets.cpp @@ -1,13 +1,12 @@ -#include "arduino.h" -#include -#include -#include +// #include "arduino.h" +// #include +// #include +// #include #include "websockets.h" -WebSocketsClient webSocket; -WiFiMulti WiFiMulti; - websockets::websockets(){ + webSocket = new WebSocketsClient(); + _WiFiMulti = new WiFiMulti(); } // hexdump function for websockets binary handler @@ -26,25 +25,25 @@ void websockets::hexdump(const void *mem, uint32_t len, uint8_t cols) { // special function to setup websocket void websockets::websocketSetup(){ - WiFiMulti.addAP("iotroam", "vbK9gbDBIB"); - WiFiMulti.addAP("LansanKPN-boven", "19sander71vlieland14"); + _WiFiMulti->addAP("iotroam", "vbK9gbDBIB"); + _WiFiMulti->addAP("LansanKPN-boven", "19sander71vlieland14"); - while(WiFiMulti.run() != WL_CONNECTED) { + while(_WiFiMulti->run() != WL_CONNECTED) { delay(100); } // server address, port and URL - webSocket.begin("145.92.8.114", 80, "/ws"); + webSocket->begin("145.92.8.114", 80, "/ws"); // event handler - // webSocket.onEvent(webSocketEvent); + // webSocket->onEvent(webSocketEvent); // try ever 500 again if connection has failed - webSocket.setReconnectInterval(500); + webSocket->setReconnectInterval(500); } void websockets::loop(){ - webSocket.loop(); + webSocket->loop(); } // handler for websocket events @@ -57,20 +56,20 @@ void websockets::webSocketEvent(WStype_t type, uint8_t * payload, size_t length) USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); // send message to server when Connected - webSocket.sendTXT("{\"message\": \"Connected\"}"); + webSocket->sendTXT("{\"message\": \"Connected\"}"); break; case WStype_TEXT: // USE_SERIAL.printf("[WSc] get text: %s\n", payload); // send message to server - // webSocket.sendTXT("message here"); + // webSocket->sendTXT("message here"); break; case WStype_BIN: // USE_SERIAL.printf("[WSc] get binary length: %u\n", length); hexdump(payload, length); // send data to server - // webSocket.sendBIN(payload, length); + // webSocket->sendBIN(payload, length); break; case WStype_ERROR: case WStype_FRAGMENT_TEXT_START: diff --git a/arduino/node-code/nodeCodeFinal/websockets.h b/arduino/node-code/nodeCodeFinal/websockets.h index 2483d88..f2b1c81 100644 --- a/arduino/node-code/nodeCodeFinal/websockets.h +++ b/arduino/node-code/nodeCodeFinal/websockets.h @@ -1,12 +1,16 @@ -#ifndef websockerts_h -#define websockerts_h +#ifndef websockets_h +#define websockets_h +#include +#include #include -#include "Arduino.h" +#include #define USE_SERIAL Serial // WiFiMulti WiFiMulti; +// WebSocketsClient webSocket; +// WiFiMulti WiFiMulti; class websockets { public: @@ -17,6 +21,8 @@ class websockets { void webSocketEvent(WStype_t type, uint8_t * payload, size_t length); private: + WebSocketsClient *webSocket; + WiFiMulti *_WiFiMulti; }; #endif \ No newline at end of file From 11099a1af9959070de0352d526eadfa39b8c4a59 Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Tue, 19 Mar 2024 22:54:58 +0100 Subject: [PATCH 11/31] moved websocket class to work with esp --- .../node-code/nodeCodeFinal/nodeCodeFinal.ino | 8 ++++---- .../node-code/nodeCodeFinal/nodeCodeHeader.cpp | 16 +++++++++++----- arduino/node-code/nodeCodeFinal/nodeCodeHeader.h | 6 ++++-- arduino/node-code/nodeCodeFinal/websockets.cpp | 7 +++++++ arduino/node-code/nodeCodeFinal/websockets.h | 1 + 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino index ea12094..42193ca 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino +++ b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino @@ -3,20 +3,20 @@ // #include - +// websockets webSocketClass; nodeReadings esp32Node; -websockets webSocketClass; + void setup() { // put your setup code here, to run once: esp32Node.setup(); - webSocketClass.websocketSetup(); + // webSocketClass.websocketSetup(); esp32Node.resetValues(); } void loop() { // put your main code here, to run repeatedly: esp32Node.loop(); - webSocketClass.loop(); + // webSocketClass.loop(); } diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp index c8e6bb3..ac944d8 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp @@ -9,7 +9,8 @@ nodeReadings::nodeReadings() { dht = new DHT(DHTPIN, DHTTYPE); display = new Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); - webSocket = new WebSocketsClient(); + // webSocket = new WebSocketsClient(); + webSocket = new websockets(); //nu naar eigen class sgp = new Adafruit_SGP30(); resetValues(); @@ -46,13 +47,15 @@ void nodeReadings::setup(){ pinMode(MICPIN, INPUT); pinMode(DHTPIN, INPUT); + + webSocket->websocketSetup(); + + // webSocketClass.websocketSetup(); + } void nodeReadings::loop() { - // loop the websocket connection so it stays alive - // webSocket->loop(); - // update when interval is met if (currentMillis - lastMillis >= interval){ lastMillis = millis(); @@ -61,6 +64,9 @@ void nodeReadings::loop() { // update the counter currentMillis = millis(); + + // loop the websocket connection so it stays alive + webSocket->loop(); } void nodeReadings::update(){ @@ -69,7 +75,7 @@ void nodeReadings::update(){ displayData(); // webSocket->sendTXT("{\"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp->eCO2) + "\",\"TVOC\":\"" + String(sgp->TVOC) + "\"}"); - webSocket->sendTXT("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp->eCO2) + "\",\"TVOC\":\"" + String(sgp->TVOC) + "\"}"); + webSocket->sendMyText("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp->eCO2) + "\",\"TVOC\":\"" + String(sgp->TVOC) + "\"}"); sgp->getIAQBaseline(&eCO2_base, &TVOC_base); diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h index 76e9f2e..d103a06 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h @@ -7,8 +7,9 @@ #include // #include "headerFile.h" #include -#include +// #include #include +#include "websockets.h" // define pins on esp32 #define MICPIN 6 @@ -40,7 +41,8 @@ public: private: DHT *dht; Adafruit_SH1106G *display; - WebSocketsClient *webSocket; + // WebSocketsClient *webSocket; + websockets *webSocket; Adafruit_SGP30 *sgp; uint16_t TVOC_base, eCO2_base; diff --git a/arduino/node-code/nodeCodeFinal/websockets.cpp b/arduino/node-code/nodeCodeFinal/websockets.cpp index f8aee50..db4cb55 100644 --- a/arduino/node-code/nodeCodeFinal/websockets.cpp +++ b/arduino/node-code/nodeCodeFinal/websockets.cpp @@ -80,4 +80,11 @@ void websockets::webSocketEvent(WStype_t type, uint8_t * payload, size_t length) } } +void websockets::sendMyText(String message) { + + webSocket->sendTXT(message); + +} + + diff --git a/arduino/node-code/nodeCodeFinal/websockets.h b/arduino/node-code/nodeCodeFinal/websockets.h index f2b1c81..06eb788 100644 --- a/arduino/node-code/nodeCodeFinal/websockets.h +++ b/arduino/node-code/nodeCodeFinal/websockets.h @@ -19,6 +19,7 @@ class websockets { void websocketSetup(); void loop(); void webSocketEvent(WStype_t type, uint8_t * payload, size_t length); + void sendMyText(String message); private: WebSocketsClient *webSocket; From ec336f914a4f27998e05c02a75879b13e15c02e0 Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Wed, 20 Mar 2024 12:40:55 +0100 Subject: [PATCH 12/31] removed unjused code --- .../node-code/nodeCodeFinal/nodeCodeFinal.ino | 7 ------ .../nodeCodeFinal/nodeCodeHeader.cpp | 24 +++++-------------- .../node-code/nodeCodeFinal/nodeCodeHeader.h | 7 ------ .../node-code/nodeCodeFinal/websockets.cpp | 9 ------- arduino/node-code/nodeCodeFinal/websockets.h | 4 ---- 5 files changed, 6 insertions(+), 45 deletions(-) diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino index 42193ca..d984954 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino +++ b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino @@ -1,22 +1,15 @@ #include #include "websockets.h" -// #include - - -// websockets webSocketClass; nodeReadings esp32Node; void setup() { // put your setup code here, to run once: esp32Node.setup(); - // webSocketClass.websocketSetup(); esp32Node.resetValues(); } void loop() { // put your main code here, to run repeatedly: esp32Node.loop(); - // webSocketClass.loop(); - } diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp index ac944d8..cac49f7 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp @@ -1,18 +1,13 @@ -// #include #include "nodeCodeHeader.h" -// #include -// #include - nodeReadings::nodeReadings() { dht = new DHT(DHTPIN, DHTTYPE); display = new Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); - - // webSocket = new WebSocketsClient(); webSocket = new websockets(); //nu naar eigen class sgp = new Adafruit_SGP30(); + interval = 5000; resetValues(); } @@ -31,9 +26,6 @@ void nodeReadings::resetValues() { } void nodeReadings::setup(){ - // DHT dht(DHTPIN, DHTTYPE); - - // make serial connection at 115200 baud Serial.begin(115200); @@ -50,9 +42,6 @@ void nodeReadings::setup(){ webSocket->websocketSetup(); - // webSocketClass.websocketSetup(); - - } void nodeReadings::loop() { @@ -74,7 +63,6 @@ void nodeReadings::update(){ // display sensordata on oled screen displayData(); - // webSocket->sendTXT("{\"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp->eCO2) + "\",\"TVOC\":\"" + String(sgp->TVOC) + "\"}"); webSocket->sendMyText("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp->eCO2) + "\",\"TVOC\":\"" + String(sgp->TVOC) + "\"}"); sgp->getIAQBaseline(&eCO2_base, &TVOC_base); @@ -92,13 +80,13 @@ void nodeReadings::displayData() { display->clearDisplay(); // display the data on the oled screen - display->setTextSize(1); + display->setTextSize(2); display->setTextColor(SH110X_WHITE); display->setCursor(0,0); - display->println("Temperature: " + String(temperature) + " C"); - display->println("Humidity: " + String(humidity) + " %"); - display->println("eCO2: " + String(sgp->eCO2) + " ppm"); - display->println("TVOC: " + String(sgp->TVOC) + " ppb"); + display->println("Temp: " + String(int(temperature)) + " C"); + display->println("Humi: " + String(int(humidity)) + " %"); + display->println("eCO2: " + String(sgp->eCO2));// + " ppm"); + display->println("TVOC: " + String(sgp->TVOC));// + " ppb"); display->display(); } diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h index d103a06..81c8ad6 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h @@ -2,12 +2,8 @@ #define nodeReading_h #include - -// #include "Arduino.h" #include -// #include "headerFile.h" #include -// #include #include #include "websockets.h" @@ -24,8 +20,6 @@ #define USE_SERIAL Serial -// WebSocketsClient webSocket; - class nodeReadings { @@ -41,7 +35,6 @@ public: private: DHT *dht; Adafruit_SH1106G *display; - // WebSocketsClient *webSocket; websockets *webSocket; Adafruit_SGP30 *sgp; diff --git a/arduino/node-code/nodeCodeFinal/websockets.cpp b/arduino/node-code/nodeCodeFinal/websockets.cpp index db4cb55..c606886 100644 --- a/arduino/node-code/nodeCodeFinal/websockets.cpp +++ b/arduino/node-code/nodeCodeFinal/websockets.cpp @@ -1,7 +1,3 @@ -// #include "arduino.h" -// #include -// #include -// #include #include "websockets.h" websockets::websockets(){ @@ -35,9 +31,6 @@ void websockets::websocketSetup(){ // server address, port and URL webSocket->begin("145.92.8.114", 80, "/ws"); - // event handler - // webSocket->onEvent(webSocketEvent); - // try ever 500 again if connection has failed webSocket->setReconnectInterval(500); } @@ -59,8 +52,6 @@ void websockets::webSocketEvent(WStype_t type, uint8_t * payload, size_t length) webSocket->sendTXT("{\"message\": \"Connected\"}"); break; case WStype_TEXT: - // USE_SERIAL.printf("[WSc] get text: %s\n", payload); - // send message to server // webSocket->sendTXT("message here"); break; diff --git a/arduino/node-code/nodeCodeFinal/websockets.h b/arduino/node-code/nodeCodeFinal/websockets.h index 06eb788..aaedb5c 100644 --- a/arduino/node-code/nodeCodeFinal/websockets.h +++ b/arduino/node-code/nodeCodeFinal/websockets.h @@ -7,10 +7,6 @@ #include #define USE_SERIAL Serial -// WiFiMulti WiFiMulti; - -// WebSocketsClient webSocket; -// WiFiMulti WiFiMulti; class websockets { public: From f94c452a19a28fbc7dec4afe78fcad5b9a5b6a0e Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Thu, 21 Mar 2024 11:31:03 +0100 Subject: [PATCH 13/31] added comments --- arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino | 2 -- arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp | 12 +++++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino index d984954..acb3434 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino +++ b/arduino/node-code/nodeCodeFinal/nodeCodeFinal.ino @@ -4,12 +4,10 @@ nodeReadings esp32Node; void setup() { - // put your setup code here, to run once: esp32Node.setup(); esp32Node.resetValues(); } void loop() { - // put your main code here, to run repeatedly: esp32Node.loop(); } diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp index cac49f7..e69dd42 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp @@ -2,16 +2,19 @@ nodeReadings::nodeReadings() { + //Making all the new object as defined in the .h file dht = new DHT(DHTPIN, DHTTYPE); display = new Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); - webSocket = new websockets(); //nu naar eigen class + webSocket = new websockets(); sgp = new Adafruit_SGP30(); + //setting the reding for every 5 sec interval = 5000; resetValues(); } +//Script for simpley reseting every value to 0 void nodeReadings::resetValues() { counter = 0; eCO2 = 0; @@ -25,6 +28,7 @@ void nodeReadings::resetValues() { noise = false; } +//Setup for initilising the dht and sgp sensors. Also the display is set up. void nodeReadings::setup(){ // make serial connection at 115200 baud Serial.begin(115200); @@ -63,6 +67,7 @@ void nodeReadings::update(){ // display sensordata on oled screen displayData(); + //send the data to the websockets webSocket->sendMyText("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp->eCO2) + "\",\"TVOC\":\"" + String(sgp->TVOC) + "\"}"); sgp->getIAQBaseline(&eCO2_base, &TVOC_base); @@ -83,13 +88,14 @@ void nodeReadings::displayData() { display->setTextSize(2); display->setTextColor(SH110X_WHITE); display->setCursor(0,0); - display->println("Temp: " + String(int(temperature)) + " C"); - display->println("Humi: " + String(int(humidity)) + " %"); + display->println("Temp: " + String(int(temperature)) + "C"); + display->println("Humi: " + String(int(humidity)) + "%"); display->println("eCO2: " + String(sgp->eCO2));// + " ppm"); display->println("TVOC: " + String(sgp->TVOC));// + " ppb"); display->display(); } +//function void nodeReadings::checkForError(){ if (!sgp->IAQmeasure()) { Serial.println("SGP30: BAD"); From 1982db8c50f7f4c195cc9980947bb6194b86a661 Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Fri, 22 Mar 2024 11:30:04 +0100 Subject: [PATCH 14/31] Add node class and update liveGraph class --- web/classes.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/web/classes.js b/web/classes.js index 2067bfc..49fae13 100644 --- a/web/classes.js +++ b/web/classes.js @@ -1,3 +1,26 @@ +class node { + // Constructor to initialize the node + constructor(nodeId) { + this.nodeId = nodeId; + this.temperature = 0; + this.humidity = 0; + this.eCO2 = 0; + this.TVOC = 0; + this.connected = false; + } + // Function to update the data + updateData(temperature, humidity, eCO2, TVOC) { + this.temperature = temperature; + this.humidity = humidity; + this.eCO2 = eCO2; + this.TVOC = TVOC; + } + // Function to update the connection status + updateConnection(status) { + this.connected = status; + } +} + class liveGraph { // Constructor to initialize the graph constructor(id) { From 07cc13d573c35096359abae7220ecbab02c2812a Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Fri, 22 Mar 2024 12:31:53 +0100 Subject: [PATCH 15/31] Refactor classes.js to update connection status and add feedbackNode and graph classes --- web/classes.js | 72 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/web/classes.js b/web/classes.js index 49fae13..1837cef 100644 --- a/web/classes.js +++ b/web/classes.js @@ -16,12 +16,30 @@ class node { this.TVOC = TVOC; } // Function to update the connection status - updateConnection(status) { - this.connected = status; + updateConnection() { + if (connectedNodes[this.nodeId]) { + this.connected = true; + } + else { + this.connected = false; + } } } -class liveGraph { +class feedbackNode extends node { + // Constructor to initialize the feedback node + constructor(nodeId) { + super(nodeId); + this.feedback = {}; + this.answers = 0; + } + // Function to update the feedback + updateFeedback(feedback) { + this.feedback = feedback; + } +} + +class liveGraph extends node{ // Constructor to initialize the graph constructor(id) { this.timeArray = []; @@ -105,4 +123,52 @@ class liveGraph { this.eco2Array.push(eCO2 / 10); this.tvocArray.push(TVOC / 10); } +} + +class graph { + // Constructor to initialize the graph + constructor(id) { + this.timeArray = []; + this.tempArray = []; + this.humiArray = []; + this.eco2Array = []; + this.tvocArray = []; + this.nodeId = "graph" + id; + } + // Function to create a graph + makeGraph(amountOfGraphs) { + for (let i = 0; i < amountOfGraphs; i++) { + + // Create a new line for temperature + Plotly.plot(this.nodeId, [ + { + x: this.timeArray, // Use timeArray as x values + y: this.array[i], + mode: "lines", + line: { color: "#FF0000" }, + name: "Temperature", + }, + ]); + } + } + // Function to update the graph with new values got from updateData function + updateGraph() { + let time = new Date(); + this.timeArray.push(new Date()); + + let update = { + x: [[this.timeArray]], + y: [[this.tempArray], [this.humiArray], [this.eco2Array], [this.tvocArray]] + }; + + let olderTime = time.setMinutes(time.getMinutes() - 1); + let futureTime = time.setMinutes(time.getMinutes() + 1); + let minuteView = { + xaxis: { + type: "date", + range: + }, + }; + +} } \ No newline at end of file From ca5d92bd86637a362c26fe19bb0ef4d11297cd2c Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Fri, 22 Mar 2024 12:38:39 +0100 Subject: [PATCH 16/31] Refactor graph class constructor and methods --- web/classes.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/web/classes.js b/web/classes.js index 1837cef..0c9f96a 100644 --- a/web/classes.js +++ b/web/classes.js @@ -42,13 +42,13 @@ class feedbackNode extends node { class liveGraph extends node{ // Constructor to initialize the graph constructor(id) { + super(id, live); this.timeArray = []; this.tempArray = []; this.humiArray = []; this.eco2Array = []; this.tvocArray = []; this.cnt = 0; - this.nodeId = "liveGraph" + id; } // Fuction to create a graph makeGraph() { @@ -127,23 +127,23 @@ class liveGraph extends node{ class graph { // Constructor to initialize the graph - constructor(id) { - this.timeArray = []; - this.tempArray = []; - this.humiArray = []; - this.eco2Array = []; - this.tvocArray = []; + constructor(id, graph) { + if (graph === "live") { + this.nodeId = "liveGraph" + id; + } + else { this.nodeId = "graph" + id; + } } // Function to create a graph - makeGraph(amountOfGraphs) { + makeGraph(amountOfGraphs, array1, array2, array3, array4) { for (let i = 0; i < amountOfGraphs; i++) { // Create a new line for temperature Plotly.plot(this.nodeId, [ { x: this.timeArray, // Use timeArray as x values - y: this.array[i], + y: array + i, mode: "lines", line: { color: "#FF0000" }, name: "Temperature", @@ -152,13 +152,13 @@ class graph { } } // Function to update the graph with new values got from updateData function - updateGraph() { + updateGraph(array1, array2, array3, array4) { let time = new Date(); this.timeArray.push(new Date()); let update = { x: [[this.timeArray]], - y: [[this.tempArray], [this.humiArray], [this.eco2Array], [this.tvocArray]] + y: [array1, array2, array3, array4] }; let olderTime = time.setMinutes(time.getMinutes() - 1); From e5511d192527239aba71b04ada33c1c80f4e20fd Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Fri, 22 Mar 2024 12:45:31 +0100 Subject: [PATCH 17/31] Add node and graph classes for data visualization --- .../SoftwareDocumentatie/graph_classes.md | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/docs/brainstorm/SoftwareDocumentatie/graph_classes.md b/docs/brainstorm/SoftwareDocumentatie/graph_classes.md index 5df185e..0acf0e1 100644 --- a/docs/brainstorm/SoftwareDocumentatie/graph_classes.md +++ b/docs/brainstorm/SoftwareDocumentatie/graph_classes.md @@ -1,3 +1,55 @@ +# Nodes + +## Introduction + +The nodes are the devices that are placed in the rooms. The nodes are used to collect the data from the sensors. Every node is connected to the websocket, and sends their data with their mac address in json format. The websocket broadcasts the node data back to all clients, and since our website functions as a client it also receives the data. Every node will, depending on what node, be made into a class. + +## Requirements + +### Sensornode + +- Every node has to have a unique nodeID +- Every node has to have their corresponding sensorsvalues in form of arrays + +### Feedbacknodes + +- Every node has to have a unique nodeID +- Every node has to have their corresponding feedback in form of a 2D array + +## Class diagrams + +### Node + +```mermaid +classDiagram + class Node { + +nodeID + +processNodeData() + +updateNodeData() + } +``` + +#### Sensornode + +```mermaid +classDiagram + class SensorNode extends Node { + +tempArray + +humiArray + +eco2Array + +tvocArray + } +``` + +#### Feedbacknode + +```mermaid +classDiagram + class FeedbackNode extends Node { + +feedbackArray + } +``` + # Graphs ## Introduction @@ -14,12 +66,21 @@ The graphs are used to display the data from the sensors. The data is collected ## Class diagrams +### Graphs + +```mermaid +classDiagram + class graph { + +nodeId + makeGraph() + } +``` + ### Live graphs ```mermaid classDiagram - class liveGraph { - +nodeId + class liveGraph extends graph { +cnt +timeArray +tempArray From 2221be88bfd81301793f1d949cbe70b81bdb2ea3 Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Fri, 22 Mar 2024 14:44:25 +0100 Subject: [PATCH 18/31] Refactor class constructors and update graph creation --- web/classes.js | 56 ++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/web/classes.js b/web/classes.js index 0c9f96a..50e0e26 100644 --- a/web/classes.js +++ b/web/classes.js @@ -19,8 +19,7 @@ class node { updateConnection() { if (connectedNodes[this.nodeId]) { this.connected = true; - } - else { + } else { this.connected = false; } } @@ -31,7 +30,7 @@ class feedbackNode extends node { constructor(nodeId) { super(nodeId); this.feedback = {}; - this.answers = 0; + this.answers = 0; } // Function to update the feedback updateFeedback(feedback) { @@ -39,16 +38,16 @@ class feedbackNode extends node { } } -class liveGraph extends node{ +class liveGraph extends node { // Constructor to initialize the graph constructor(id) { - super(id, live); - this.timeArray = []; + super(id); this.tempArray = []; this.humiArray = []; this.eco2Array = []; this.tvocArray = []; this.cnt = 0; + this.nodeId = "liveGraph" + id; } // Fuction to create a graph makeGraph() { @@ -101,7 +100,12 @@ class liveGraph extends node{ let update = { x: [[this.timeArray]], - y: [[this.tempArray], [this.humiArray], [this.eco2Array], [this.tvocArray]] + y: [ + [this.tempArray], + [this.humiArray], + [this.eco2Array], + [this.tvocArray], + ], }; let olderTime = time.setMinutes(time.getMinutes() - 1); @@ -127,28 +131,23 @@ class liveGraph extends node{ class graph { // Constructor to initialize the graph - constructor(id, graph) { - if (graph === "live") { - this.nodeId = "liveGraph" + id; - } - else { - this.nodeId = "graph" + id; - } + constructor(id) { + this.nodeId = "graph" + id; + this.timeArray = []; } // Function to create a graph makeGraph(amountOfGraphs, array1, array2, array3, array4) { for (let i = 0; i < amountOfGraphs; i++) { - - // Create a new line for temperature - Plotly.plot(this.nodeId, [ - { - x: this.timeArray, // Use timeArray as x values - y: array + i, - mode: "lines", - line: { color: "#FF0000" }, - name: "Temperature", - }, - ]); + // Create a new line for temperature + Plotly.plot(this.nodeId, [ + { + x: this.timeArray, // Use timeArray as x values + y: array + i, + mode: "lines", + line: { color: "#FF0000" }, + name: "Temperature", + }, + ]); } } // Function to update the graph with new values got from updateData function @@ -158,7 +157,7 @@ class graph { let update = { x: [[this.timeArray]], - y: [array1, array2, array3, array4] + y: [array1, array2, array3, array4], }; let olderTime = time.setMinutes(time.getMinutes() - 1); @@ -166,9 +165,8 @@ class graph { let minuteView = { xaxis: { type: "date", - range: + range: [olderTime, futureTime], }, }; - + } } -} \ No newline at end of file From 1182e89c56d0610853a91f9006bb5c9de353a2df Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Sat, 23 Mar 2024 11:39:25 +0100 Subject: [PATCH 19/31] Node documetnation --- docs/node-documentation/nodeClass.md | 59 ++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/node-documentation/nodeClass.md diff --git a/docs/node-documentation/nodeClass.md b/docs/node-documentation/nodeClass.md new file mode 100644 index 0000000..fb9b4ed --- /dev/null +++ b/docs/node-documentation/nodeClass.md @@ -0,0 +1,59 @@ +# OOP within Arduino + +### Why use classes + +## Whitch classes did we use + +### Uml diagram + +--- +title: Arduino classes +--- +classDiagram + class Websockets + Websockets: + + +Class Websockets: + Public: + websockets(); + void hexdump(const void *mem, uint32_t len, uint8_t cols = 16); + void websocketSetup(); + void loop(); + void webSocketEvent(WStype_t type, uint8_t * payload, size_t length); + void sendMyText(String message); + + private: + WebSocketsClient *webSocket; + WiFiMulti *_WiFiMulti; + +Class NodeReadings: +public: + nodeReadings(); + void setup(); + void loop(); + void resetValues(); + void update(); + void checkForError(); + void displayData(); + +private: + DHT *dht; + Adafruit_SH1106G *display; + websockets *webSocket; + Adafruit_SGP30 *sgp; + + uint16_t TVOC_base, eCO2_base; + uint16_t counter; + uint16_t eCO2; + uint16_t TVOC; + uint16_t interval; + float temperature; + float humidity; + unsigned long currentMillis; + unsigned long lastMillis; + bool errorSGP30; + bool errorDHT11; + bool noise; + +### Small explenation \ No newline at end of file From 12909d365089313c468d2d1bac50464a52a0cf7e Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Tue, 26 Mar 2024 11:01:16 +0100 Subject: [PATCH 20/31] Add Node and Graph classes for website functionality --- .../{graph_classes.md => classes-website.md} | 43 ++++++++++--------- web/classes.js | 2 +- 2 files changed, 24 insertions(+), 21 deletions(-) rename docs/brainstorm/SoftwareDocumentatie/{graph_classes.md => classes-website.md} (82%) diff --git a/docs/brainstorm/SoftwareDocumentatie/graph_classes.md b/docs/brainstorm/SoftwareDocumentatie/classes-website.md similarity index 82% rename from docs/brainstorm/SoftwareDocumentatie/graph_classes.md rename to docs/brainstorm/SoftwareDocumentatie/classes-website.md index 0acf0e1..9da4e19 100644 --- a/docs/brainstorm/SoftwareDocumentatie/graph_classes.md +++ b/docs/brainstorm/SoftwareDocumentatie/classes-website.md @@ -22,30 +22,24 @@ The nodes are the devices that are placed in the rooms. The nodes are used to co ```mermaid classDiagram + + Node <-- SensorNode : extends + Node <-- FeedbackNode : extends + class Node { +nodeID +processNodeData() +updateNodeData() } -``` -#### Sensornode - -```mermaid -classDiagram - class SensorNode extends Node { + class SensorNode { +tempArray +humiArray +eco2Array +tvocArray } -``` -#### Feedbacknode - -```mermaid -classDiagram - class FeedbackNode extends Node { + class FeedbackNode { +feedbackArray } ``` @@ -68,19 +62,16 @@ The graphs are used to display the data from the sensors. The data is collected ### Graphs -```mermaid +```mermaid classDiagram + + liveGraph --> graph: extends class graph { +nodeId makeGraph() } -``` -### Live graphs - -```mermaid -classDiagram - class liveGraph extends graph { + class liveGraph { +cnt +timeArray +tempArray @@ -91,8 +82,8 @@ classDiagram updateGraph() updateData() } -``` +``` ## Order of operations ### Live graphs @@ -114,4 +105,16 @@ sequenceDiagram 3. The website updates the data coming from the raspberry pi on its own variables and arrays 4. The website updates the live graphs every time new data is received from the websocket +### Node +```mermaid +sequenceDiagram + participant Node + participant Raspberry pi + participant Website + + Node->>Raspberry pi: node data via websocket every 5 seconds + Raspberry pi->>Website: Make a new object depending on what node it is + Website->>Website: updateNodeData() + Website->>Website: processNodeData() +``` diff --git a/web/classes.js b/web/classes.js index 50e0e26..9ce53a8 100644 --- a/web/classes.js +++ b/web/classes.js @@ -38,7 +38,7 @@ class feedbackNode extends node { } } -class liveGraph extends node { +class liveGraph extends graph { // Constructor to initialize the graph constructor(id) { super(id); From 62124ec40283052b9718b7e828734f04315e04ec Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Tue, 26 Mar 2024 13:41:16 +0100 Subject: [PATCH 21/31] added kopjes --- docs/node-documentation/nodeClass.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/node-documentation/nodeClass.md b/docs/node-documentation/nodeClass.md index fb9b4ed..02723d4 100644 --- a/docs/node-documentation/nodeClass.md +++ b/docs/node-documentation/nodeClass.md @@ -2,6 +2,16 @@ ### Why use classes + + +### Abstraction + +### Encaptiolation + +### Inheritance + +### Polymorphism + ## Whitch classes did we use ### Uml diagram From 9fc5c5b53b1994dedf525fddeeb51363d559e3df Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Wed, 27 Mar 2024 12:13:59 +0100 Subject: [PATCH 22/31] Arduino OOP documentation --- .../NodeClassDocumentation.md | 20 ++++++ docs/node-documentation/nodeClass.md | 69 ------------------- docs/node-documentation/nodeClassUml.md | 43 ++++++++++++ 3 files changed, 63 insertions(+), 69 deletions(-) create mode 100644 docs/node-documentation/NodeClassDocumentation.md delete mode 100644 docs/node-documentation/nodeClass.md create mode 100644 docs/node-documentation/nodeClassUml.md diff --git a/docs/node-documentation/NodeClassDocumentation.md b/docs/node-documentation/NodeClassDocumentation.md new file mode 100644 index 0000000..d0bc370 --- /dev/null +++ b/docs/node-documentation/NodeClassDocumentation.md @@ -0,0 +1,20 @@ +# OOP within Arduino + +Object-Oriented Programming (OOP) is a way of programing that provides a means of structuring your code so the code is modular and can be used more often without making huge changes or having to copy past the same code. + +## Abstraction + +Abstraction in OOP is the process of exposing only the required essential variables and functions. This means hiding the complexity and only showing the essential features of the object. In Arduino, this could mean creating a class like `Sensor node` with methods such as `setUp()`, `displayData()`, and `checkForError()`. + +## Encapsulation + +Encapsulation is the technique used to hide the data and methods within an object and prevent outside access. In Arduino, this could mean having private variables and methods in a class that can only be accessed and modified through public methods. + +## Which classes did we use + +In this Arduino project, we used several classes to organize our code and manage the complexity of the project. Some of these classes include: + +- `websockets`: This class is responsible for managing the WebSocket connections. +- `nodeReadings`: This class is responsible for managing the sensor readings. + +Each of these classes encapsulates the data and methods related to a specific part of the project, making the code easier to understand and maintain. \ No newline at end of file diff --git a/docs/node-documentation/nodeClass.md b/docs/node-documentation/nodeClass.md deleted file mode 100644 index 02723d4..0000000 --- a/docs/node-documentation/nodeClass.md +++ /dev/null @@ -1,69 +0,0 @@ -# OOP within Arduino - -### Why use classes - - - -### Abstraction - -### Encaptiolation - -### Inheritance - -### Polymorphism - -## Whitch classes did we use - -### Uml diagram - ---- -title: Arduino classes ---- -classDiagram - class Websockets - Websockets: - - -Class Websockets: - Public: - websockets(); - void hexdump(const void *mem, uint32_t len, uint8_t cols = 16); - void websocketSetup(); - void loop(); - void webSocketEvent(WStype_t type, uint8_t * payload, size_t length); - void sendMyText(String message); - - private: - WebSocketsClient *webSocket; - WiFiMulti *_WiFiMulti; - -Class NodeReadings: -public: - nodeReadings(); - void setup(); - void loop(); - void resetValues(); - void update(); - void checkForError(); - void displayData(); - -private: - DHT *dht; - Adafruit_SH1106G *display; - websockets *webSocket; - Adafruit_SGP30 *sgp; - - uint16_t TVOC_base, eCO2_base; - uint16_t counter; - uint16_t eCO2; - uint16_t TVOC; - uint16_t interval; - float temperature; - float humidity; - unsigned long currentMillis; - unsigned long lastMillis; - bool errorSGP30; - bool errorDHT11; - bool noise; - -### Small explenation \ No newline at end of file diff --git a/docs/node-documentation/nodeClassUml.md b/docs/node-documentation/nodeClassUml.md new file mode 100644 index 0000000..2259384 --- /dev/null +++ b/docs/node-documentation/nodeClassUml.md @@ -0,0 +1,43 @@ +### Uml diagram: + +``` mermaid +classDiagram + +namespace Esp { + class Websockets{ + +webSocket + +_WiFiMulti + hexdump() + websocketSetup() + loop() + webSocketEvent() + sendMyText() + } + + class NodeReadings{ + + +TVOC_base, eCO2_base + +counter + +eCO2 + +TVOC + +interval + +temperature + +humidity + +currentMillis + +lastMillis + +errorSGP30 + +errorDHT11 + +noise + + setup() + loop() + resetValues() + update() + checkForError() + displayData() + + } + + +} +``` From 1ac6f0133179279ccceea111e34ea00ca06f7e98 Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Wed, 27 Mar 2024 12:18:55 +0100 Subject: [PATCH 23/31] replact to main --- .../NodeClassDocumentation.md | 20 --------- docs/node-documentation/nodeClassUml.md | 43 ------------------- 2 files changed, 63 deletions(-) delete mode 100644 docs/node-documentation/NodeClassDocumentation.md delete mode 100644 docs/node-documentation/nodeClassUml.md diff --git a/docs/node-documentation/NodeClassDocumentation.md b/docs/node-documentation/NodeClassDocumentation.md deleted file mode 100644 index d0bc370..0000000 --- a/docs/node-documentation/NodeClassDocumentation.md +++ /dev/null @@ -1,20 +0,0 @@ -# OOP within Arduino - -Object-Oriented Programming (OOP) is a way of programing that provides a means of structuring your code so the code is modular and can be used more often without making huge changes or having to copy past the same code. - -## Abstraction - -Abstraction in OOP is the process of exposing only the required essential variables and functions. This means hiding the complexity and only showing the essential features of the object. In Arduino, this could mean creating a class like `Sensor node` with methods such as `setUp()`, `displayData()`, and `checkForError()`. - -## Encapsulation - -Encapsulation is the technique used to hide the data and methods within an object and prevent outside access. In Arduino, this could mean having private variables and methods in a class that can only be accessed and modified through public methods. - -## Which classes did we use - -In this Arduino project, we used several classes to organize our code and manage the complexity of the project. Some of these classes include: - -- `websockets`: This class is responsible for managing the WebSocket connections. -- `nodeReadings`: This class is responsible for managing the sensor readings. - -Each of these classes encapsulates the data and methods related to a specific part of the project, making the code easier to understand and maintain. \ No newline at end of file diff --git a/docs/node-documentation/nodeClassUml.md b/docs/node-documentation/nodeClassUml.md deleted file mode 100644 index 2259384..0000000 --- a/docs/node-documentation/nodeClassUml.md +++ /dev/null @@ -1,43 +0,0 @@ -### Uml diagram: - -``` mermaid -classDiagram - -namespace Esp { - class Websockets{ - +webSocket - +_WiFiMulti - hexdump() - websocketSetup() - loop() - webSocketEvent() - sendMyText() - } - - class NodeReadings{ - - +TVOC_base, eCO2_base - +counter - +eCO2 - +TVOC - +interval - +temperature - +humidity - +currentMillis - +lastMillis - +errorSGP30 - +errorDHT11 - +noise - - setup() - loop() - resetValues() - update() - checkForError() - displayData() - - } - - -} -``` From 4ef167c590bc4ef74069a16a93a0119126557b12 Mon Sep 17 00:00:00 2001 From: sietse jonker Date: Fri, 29 Mar 2024 11:26:37 +0100 Subject: [PATCH 24/31] Update temperature and humidity display format --- arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp | 4 ++-- arduino/node-code/nodeCodeFinal/nodeCodeHeader.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp index cac49f7..9726029 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.cpp @@ -83,8 +83,8 @@ void nodeReadings::displayData() { display->setTextSize(2); display->setTextColor(SH110X_WHITE); display->setCursor(0,0); - display->println("Temp: " + String(int(temperature)) + " C"); - display->println("Humi: " + String(int(humidity)) + " %"); + display->println("Temp: " + String(int(temperature)) + "C"); + display->println("Humi: " + String(int(humidity)) + "%"); display->println("eCO2: " + String(sgp->eCO2));// + " ppm"); display->println("TVOC: " + String(sgp->TVOC));// + " ppb"); display->display(); diff --git a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h index 81c8ad6..da18676 100644 --- a/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h +++ b/arduino/node-code/nodeCodeFinal/nodeCodeHeader.h @@ -12,7 +12,7 @@ #define DHTPIN 7 #define SCL 9 #define SDA 8 -#define DHTTYPE DHT11 +#define DHTTYPE DHT22 #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define i2c_adress 0x3c From 1410ff7989bb512767bf0bee8c2dbb384d5a04ed Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Thu, 4 Apr 2024 21:21:32 +0200 Subject: [PATCH 25/31] Added infrastucture documentation --- docs/assets/funcionalInfra.png | Bin 0 -> 64453 bytes docs/assets/technicalInfra.png | Bin 0 -> 32362 bytes docs/brainstorm/infrasturcture.md | 19 +++++++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 docs/assets/funcionalInfra.png create mode 100644 docs/assets/technicalInfra.png create mode 100644 docs/brainstorm/infrasturcture.md diff --git a/docs/assets/funcionalInfra.png b/docs/assets/funcionalInfra.png new file mode 100644 index 0000000000000000000000000000000000000000..06ab7ebc8403c84626f3f0dafa8c78ace85cea39 GIT binary patch literal 64453 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 JonuJK{{T9bm`eZv literal 0 HcmV?d00001 diff --git a/docs/assets/technicalInfra.png b/docs/assets/technicalInfra.png new file mode 100644 index 0000000000000000000000000000000000000000..d280f31e0ee2258e6028b482dcd49376df368f4b GIT binary patch literal 32362 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 literal 0 HcmV?d00001 diff --git a/docs/brainstorm/infrasturcture.md b/docs/brainstorm/infrasturcture.md new file mode 100644 index 0000000..fd1dce0 --- /dev/null +++ b/docs/brainstorm/infrasturcture.md @@ -0,0 +1,19 @@ +# The infrastructure + +### + +## Three layers + +### + +### functional + + +[functional Infra](../assets/funcionalInfra.png) + +### Technical + +[technical Infra](../assets/technicalInfra.png) + +### UML diagram of software + From b8c16a13c79b64ffef215c065fa817b5fe4c86a7 Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Thu, 4 Apr 2024 21:22:24 +0200 Subject: [PATCH 26/31] updated pages --- docs/.pages | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/.pages b/docs/.pages index 9803d55..b8b2d3c 100644 --- a/docs/.pages +++ b/docs/.pages @@ -21,7 +21,8 @@ nav: - Database design: brainstorm/Database - Feedback: brainstorm/Feedback - Problem: brainstorm/Problem - - Infrastructure: brainstorm/UML-infrastructure + - InfrastructureUml: brainstorm/UML-infrastructure + - Infrascructure: brainstorm/infrasturcture - Taskflow: brainstorm/Taskflow - Design: Sp1SchetsProject/FirstDesign - 🖨️ Software: From 72ebd92c99dac2de5829c0a56debcf79790048a5 Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Thu, 4 Apr 2024 21:42:33 +0200 Subject: [PATCH 27/31] Update infrastructure documentation with functional, technical, and UML diagrams --- docs/brainstorm/infrasturcture.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/brainstorm/infrasturcture.md b/docs/brainstorm/infrasturcture.md index fd1dce0..0a40ab8 100644 --- a/docs/brainstorm/infrasturcture.md +++ b/docs/brainstorm/infrasturcture.md @@ -8,12 +8,16 @@ ### functional +In the top layer of the infrastructure documentation, we are showing the functional flows of the information trafic between the user and the sensor/enquete that are located in the same room. The traffic consists of enviromental sensor values like temperature, humidity etc. Also it is showing the user input at the enquete device to the database that is hosted at the brain of the operation. [functional Infra](../assets/funcionalInfra.png) ### Technical +In the middel layer we are showing the data flow and processing of the data at component level. It consists of 2 area's with 3 parts, namelie the server room and the building as in the common area. Here we send the data in JSON form that is collected at the enquete node (this is the feedback data) and the sensor node (this is the environmental data). This is sent to the database through the websocket that is running on the raspberry pi and than via a rest api sent to the database. If one of the operational manager would log in to the website. They would see the incomming sensor node data that is comming through, but also the data that is already collected and stored in the database. + [technical Infra](../assets/technicalInfra.png) ### UML diagram of software +At the lowest level of our system we are showing how the software is designed. This is shown as a UML diagram. The diagram is made with mermaid. This shows the work flow of the different parts of the software which is running on the main server, for which we are using a Raspberry pi. The pi is located on the 6th floor of the school building in a locked server room. From 0db2e8ab524195f0f766c0e40e2b39afd2396724 Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Thu, 4 Apr 2024 22:06:30 +0200 Subject: [PATCH 28/31] Update file .pages --- docs/.pages | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/.pages b/docs/.pages index b8b2d3c..9f3d0a3 100644 --- a/docs/.pages +++ b/docs/.pages @@ -22,7 +22,6 @@ nav: - Feedback: brainstorm/Feedback - Problem: brainstorm/Problem - InfrastructureUml: brainstorm/UML-infrastructure - - Infrascructure: brainstorm/infrasturcture - Taskflow: brainstorm/Taskflow - Design: Sp1SchetsProject/FirstDesign - 🖨️ Software: From 55314853858936a10d5d2b8b4d0e51380651ae32 Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Thu, 4 Apr 2024 22:09:15 +0200 Subject: [PATCH 29/31] Update file .pages --- docs/.pages | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/.pages b/docs/.pages index 9f3d0a3..8c5f6de 100644 --- a/docs/.pages +++ b/docs/.pages @@ -21,7 +21,6 @@ nav: - Database design: brainstorm/Database - Feedback: brainstorm/Feedback - Problem: brainstorm/Problem - - InfrastructureUml: brainstorm/UML-infrastructure - Taskflow: brainstorm/Taskflow - Design: Sp1SchetsProject/FirstDesign - 🖨️ Software: From beb123c2b954353ed72d4c3af2fb1f7ddce9ece4 Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Thu, 4 Apr 2024 22:22:40 +0200 Subject: [PATCH 30/31] PAGES --- docs/.pages | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/.pages b/docs/.pages index b1b49e0..f5edd59 100644 --- a/docs/.pages +++ b/docs/.pages @@ -26,7 +26,8 @@ nav: - Database design: brainstorm/Database - Feedback: brainstorm/Feedback - Problem: brainstorm/Problem - - Infrastructure: brainstorm/UML-infrastructureV2 + - InfrastructureUml: brainstorm/UML-infrastructure + - Infrascructure: brainstorm/infrasturcture - Taskflow: brainstorm/Taskflow - Design: Sp1SchetsProject/FirstDesign - Interview facility manager: brainstorm/gebouwBeheer From 43242f2080e3f466008682fd9d50d2ca570d2c94 Mon Sep 17 00:00:00 2001 From: Dano van den Bosch Date: Thu, 4 Apr 2024 22:24:21 +0200 Subject: [PATCH 31/31] afbeeldong --- docs/brainstorm/infrasturcture.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/brainstorm/infrasturcture.md b/docs/brainstorm/infrasturcture.md index 0a40ab8..8be7c12 100644 --- a/docs/brainstorm/infrasturcture.md +++ b/docs/brainstorm/infrasturcture.md @@ -10,13 +10,13 @@ In the top layer of the infrastructure documentation, we are showing the functional flows of the information trafic between the user and the sensor/enquete that are located in the same room. The traffic consists of enviromental sensor values like temperature, humidity etc. Also it is showing the user input at the enquete device to the database that is hosted at the brain of the operation. -[functional Infra](../assets/funcionalInfra.png) +![functional Infra](../assets/funcionalInfra.png) ### Technical In the middel layer we are showing the data flow and processing of the data at component level. It consists of 2 area's with 3 parts, namelie the server room and the building as in the common area. Here we send the data in JSON form that is collected at the enquete node (this is the feedback data) and the sensor node (this is the environmental data). This is sent to the database through the websocket that is running on the raspberry pi and than via a rest api sent to the database. If one of the operational manager would log in to the website. They would see the incomming sensor node data that is comming through, but also the data that is already collected and stored in the database. -[technical Infra](../assets/technicalInfra.png) +![technical Infra](../assets/technicalInfra.png) ### UML diagram of software