1 Commits

Author SHA1 Message Date
Dano van den Bosch
013ff4cffa Set up for making the class for the graph page 2024-03-23 11:46:18 +01:00
100 changed files with 379 additions and 4323 deletions

View File

@@ -1,7 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
"version": "0.2.0",
"configurations": [
]
}

View File

@@ -1,115 +0,0 @@
#include "headerFile.h"
int i = 0;
int questionID = 1;
char* Question[] = {
"How clean are the toilets?",
"How clean is the study area?",
"What do you think of the temperature in the study area?",
"How crowded would you say the study area is?",
"Is there enough help available?"
};
char* Answer[] = {
"clean, normal, disgusting",
"clean, normal, disgusting",
"hot, perfect, cold",
"not at all, its fine, really crowded",
"no, decently, yes"
};
Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, MOSI, SCK, TFT_RST, MISO);
DisplayText displayText(tft);
void setup() {
//buttonpins
pinMode(16, INPUT_PULLDOWN);
pinMode(17, INPUT_PULLDOWN);
pinMode(18, INPUT_PULLDOWN);
Serial.begin(9600);
tft.begin(); // Initialize the display
tft.setRotation(3); // Set the rotation to horizontal
tft.fillScreen(ST7796S_BLACK);
displayText.writeText("Loading...", 3, 0, 200, 0, true, true);
websocketSetup();
}
void loop() {
webSocket.loop();
screenButtonHandler();
}
void websocketSetup(){
WiFiMulti.addAP("iotroam", "BcgrFpX3kl");
WiFiMulti.addAP("ObsidianAmstelveen", "drijversstraatmaastricht");
while(WiFiMulti.run() != WL_CONNECTED) {
delay(100);
}
// server address, port and URL
webSocket.begin("145.92.8.114", 80, "/ws");
// try ever 500 again if connection has failed
webSocket.setReconnectInterval(500);
}
void screenButtonHandler(){
int redButton = digitalRead(16);
int whiteButton = digitalRead(17);
int greenButton = digitalRead(18);
if (initialized) {
initialized = false;
tft.fillScreen(ST7796S_BLACK);
displayText.writeText(Question[i], 3, 0, 0, 0, true, false);
displayText.writeText(Answer[i], 3, 0, 200, 0, true, true);
}
// 0 is best 2 is worst
if (redButton == HIGH){
sendData(questionID, "0");
}
if (whiteButton == HIGH){
sendData(questionID, "1");
}
if (greenButton == HIGH){
sendData(questionID, "2");
}
if (redButton || whiteButton || greenButton) {
i++;
questionID++;
if (questionID == 6){
questionID = 1;
}
if (i == 5) {
i = 0;
}
tft.fillScreen(ST7796S_BLACK);
displayText.writeText(Question[i], 3, 0, 0, 0, true, false);
displayText.writeText(Answer[i], 3, 0, 200, 0, true, true);
}
}
void sendData(int question, String answer){
webSocket.sendTXT("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Response\":\"" + String(answer) + "\",\"QuestionID\":\"" + String(question) + "\"}");
}
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");
}

View File

@@ -1,106 +0,0 @@
#include "displayText.h"
#include "Arduino.h"
//constructor
DisplayText::DisplayText(Adafruit_ST7796S_kbv& tftDisplay) : tft(tftDisplay) {
tft.setCursor(0,0);
tft.fillScreen(ST7796S_BLACK);
}
//display text public function
void DisplayText::writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom) {
if (center) {
posX = centerText(text);
}
// if (bottom) {
// posY = bottomText(text);
// }
tft.setCursor(posX, posY);
tft.setTextSize(size);
printWordsFull(text, bottom);
delay(screenTime);
}
//to center the text when enabled in the public function
int DisplayText::centerText(char* text) {
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
int x = (tft.width() - w) / 2;
return x;
}
// //to display the text at the bottom when enabled in the public function
// int DisplayText::bottomText(char* text) {
// int16_t x1, y1;
// uint16_t w, h;
// tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
// int y = (tft.height() - h);
// return y;
// }
//attempt to write the text out in full (wip)
void DisplayText::printWordsFull(char* text, bool bottom) {
const int screenWidth = 480; // replace with your TFT display width
const int lineHeight = 30; // replace with your text line height
//the double copy is needed so it doesnt alter the original text
char* newtext1 = strdup(text); // Create a copy of the string for the first strtok_r
char* newtext2 = strdup(text); // Create a second copy for the second strtok_r
char* saveptr1, *saveptr2;
char* word = strtok_r(newtext1, " ", &saveptr1);
char line[100] = "";
int lineCount = 0;
int lineWidth = 0;
// Calculate total number of lines
int totalLines = 0;
char* tempWord = strtok_r(newtext2, " ", &saveptr2);
while (tempWord != NULL) {
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(tempWord, 0, 0, &x1, &y1, &w, &h);
if (lineWidth + w > screenWidth && strlen(line) > 0) {
//if the line width is greater than the screen width, then we need to add a new line
totalLines++;
lineWidth = w;
//otherwise we just add the width of the word to the line width
} else {
lineWidth += w;
}
tempWord = strtok_r(NULL, " ", &saveptr2);
}
totalLines++; // Add one for the last line
// Reset variables for actual printing
strcpy(line, "");
lineWidth = 0;
while (word != NULL) {
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(word, 0, 0, &x1, &y1, &w, &h);
if (lineWidth + w > screenWidth && strlen(line) > 0) {
int y = bottom ? tft.height() - lineHeight * (totalLines - lineCount) : lineHeight * lineCount;
tft.setCursor(0, y);
tft.println(line);
lineCount++;
strcpy(line, word);
strcat(line, " ");
lineWidth = w;
} else {
strcat(line, word);
strcat(line, " ");
lineWidth += w;
}
word = strtok_r(NULL, " ", &saveptr1);
}
// print the last line
int y = bottom ? tft.height() - lineHeight * (totalLines - lineCount) : lineHeight * lineCount;
tft.setCursor(0, y);
tft.println(line);
free(newtext1); // Free the memory allocated by strdup
free(newtext2); // Free the memory allocated by strdup
}

View File

@@ -1,19 +0,0 @@
#ifndef DISPLAYTEXT_H
#define DISPLAYTEXT_H
#include "Adafruit_GFX.h"
#include "Adafruit_ST7796S_kbv.h"
class DisplayText {
private:
Adafruit_ST7796S_kbv& tft;
int centerText(char* text);
// int bottomText(char* text);
void printWordsFull(char* text, bool bottom);
public:
DisplayText(Adafruit_ST7796S_kbv& tftDisplay);
void writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom);
};
#endif

View File

@@ -1,22 +0,0 @@
//screen stuff
#include <SPI.h>
#include "Adafruit_GFX.h"
#include "Adafruit_ST7796S_kbv.h"
#include "displayText.h"
#define TFT_CS 14
#define TFT_DC 13
#define TFT_RST 12
#define MOSI 11
#define SCK 10
#define MISO 9
//websocket stuff
#include <WiFiMulti.h>
#include <WiFi.h>
#include <WebSocketsClient.h>
#define USE_SERIAL Serial
WiFiMulti WiFiMulti;
WebSocketsClient webSocket;
bool initialized = true;

View File

@@ -0,0 +1,76 @@
#include "displayText.h"
#include "Arduino.h"
//constructor
DisplayText::DisplayText(Adafruit_ST7796S_kbv& tftDisplay) : tft(tftDisplay) {
tft.setCursor(0,0);
tft.fillScreen(ST7796S_BLACK);
}
//display text public function
void DisplayText::writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom) {
if (center) {
posX = centerText(text);
}
// if (bottom) {
// posY = bottomText(text);
// }
tft.setCursor(posX, posY);
tft.setTextSize(size);
printWordsFull(text);
delay(screenTime);
}
//to center the text when enabled in the public function
int DisplayText::centerText(char* text) {
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
int x = (tft.width() - w) / 2;
return x;
}
// //to display the text at the bottom when enabled in the public function
// int DisplayText::bottomText(char* text) {
// int16_t x1, y1;
// uint16_t w, h;
// tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
// int y = (tft.height() - h);
// return y;
// }
//attempt to write the text out in full (wip)
void DisplayText::printWordsFull(char* text) {
const int screenWidth = 320; // replace with your TFT display width
const int lineHeight = 22; // replace with your text line height
char* word = strtok(text, " ");
char line[100] = "";
int lineCount = 0;
while (word != NULL) {
char tempLine[100];
strcpy(tempLine, line);
strcat(tempLine, word);
strcat(tempLine, " ");
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(tempLine, 0, 0, &x1, &y1, &w, &h);
if (w > screenWidth && strlen(line) > 0) {
tft.setCursor(0, lineHeight * lineCount);
tft.println(line);
lineCount++;
strcpy(line, word);
strcat(line, " ");
} else {
strcpy(line, tempLine);
}
word = strtok(NULL, " ");
}
// print the last line
tft.setCursor(0, lineHeight * lineCount);
tft.println(line);
}

View File

@@ -9,7 +9,7 @@ class DisplayText {
Adafruit_ST7796S_kbv& tft;
int centerText(char* text);
// int bottomText(char* text);
void printWordsFull(char* text, bool bottom);
void printWordsFull(char* text);
public:
DisplayText(Adafruit_ST7796S_kbv& tftDisplay);

View File

@@ -1,106 +0,0 @@
#include "displayText.h"
#include "Arduino.h"
//constructor
DisplayText::DisplayText(Adafruit_ST7796S_kbv& tftDisplay) : tft(tftDisplay) {
tft.setCursor(0,0);
tft.fillScreen(ST7796S_BLACK);
}
//display text public function
void DisplayText::writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom) {
if (center) {
posX = centerText(text);
}
// if (bottom) {
// posY = bottomText(text);
// }
tft.setCursor(posX, posY);
tft.setTextSize(size);
printWordsFull(text, bottom);
delay(screenTime);
}
//to center the text when enabled in the public function
int DisplayText::centerText(char* text) {
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
int x = (tft.width() - w) / 2;
return x;
}
// //to display the text at the bottom when enabled in the public function
// int DisplayText::bottomText(char* text) {
// int16_t x1, y1;
// uint16_t w, h;
// tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
// int y = (tft.height() - h);
// return y;
// }
//attempt to write the text out in full (wip)
void DisplayText::printWordsFull(char* text, bool bottom) {
const int screenWidth = 480; // replace with your TFT display width
const int lineHeight = 30; // replace with your text line height
//the double copy is needed so it doesnt alter the original text
char* newtext1 = strdup(text); // Create a copy of the string for the first strtok_r
char* newtext2 = strdup(text); // Create a second copy for the second strtok_r
char* saveptr1, *saveptr2;
char* word = strtok_r(newtext1, " ", &saveptr1);
char line[100] = "";
int lineCount = 0;
int lineWidth = 0;
// Calculate total number of lines
int totalLines = 0;
char* tempWord = strtok_r(newtext2, " ", &saveptr2);
while (tempWord != NULL) {
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(tempWord, 0, 0, &x1, &y1, &w, &h);
if (lineWidth + w > screenWidth && strlen(line) > 0) {
//if the line width is greater than the screen width, then we need to add a new line
totalLines++;
lineWidth = w;
//otherwise we just add the width of the word to the line width
} else {
lineWidth += w;
}
tempWord = strtok_r(NULL, " ", &saveptr2);
}
totalLines++; // Add one for the last line
// Reset variables for actual printing
strcpy(line, "");
lineWidth = 0;
while (word != NULL) {
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(word, 0, 0, &x1, &y1, &w, &h);
if (lineWidth + w > screenWidth && strlen(line) > 0) {
int y = bottom ? tft.height() - lineHeight * (totalLines - lineCount) : lineHeight * lineCount;
tft.setCursor(0, y);
tft.println(line);
lineCount++;
strcpy(line, word);
strcat(line, " ");
lineWidth = w;
} else {
strcat(line, word);
strcat(line, " ");
lineWidth += w;
}
word = strtok_r(NULL, " ", &saveptr1);
}
// print the last line
int y = bottom ? tft.height() - lineHeight * (totalLines - lineCount) : lineHeight * lineCount;
tft.setCursor(0, y);
tft.println(line);
free(newtext1); // Free the memory allocated by strdup
free(newtext2); // Free the memory allocated by strdup
}

View File

@@ -1,54 +0,0 @@
#ifndef headerFile_h
#define headerFile_h
// include these libraries
#include <Wire.h>
// #include <Adafruit_SH110X.h>
// #include <Adafruit_SGP30.h>
// #include "DHT.h"
#include <WiFiMulti.h>
#include <WiFi.h>
// #include <WebSocketsClient.h>
// #include <nodeCodeHeader.h>
// #include <websockets.h>
// 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
#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;
// Adafruit_SGP30 sgp;
// WebSocketsClient webSocket;
// define variables
uint16_t TVOC_base, eCO2_base;
uint16_t counter = 0;
uint16_t eCO2 = 0;
uint16_t TVOC = 0;
uint16_t interval = 5000;
float temperature = 0;
float humidity = 0;
unsigned long currentMillis;
unsigned long lastMillis;
bool errorSGP30 = false;
bool errorDHT11 = false;
bool noise = false;
#endif

View File

@@ -0,0 +1,42 @@
// include these libraries
#include <Wire.h>
#include <Adafruit_SH110X.h>
#include <Adafruit_SGP30.h>
#include <DHT.h>
#include <WiFiMulti.h>
#include <WiFi.h>
#include <WebSocketsClient.h>
#include <nodeCodeHeader.h>
// 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
#define USE_SERIAL Serial
// make new objects
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
DHT dht(DHTPIN, DHTTYPE);
WiFiMulti WiFiMulti;
Adafruit_SGP30 sgp;
WebSocketsClient webSocket;
// define variables
uint16_t TVOC_base, eCO2_base;
uint16_t counter = 0;
uint16_t eCO2 = 0;
uint16_t TVOC = 0;
uint16_t interval = 5000;
float temperature = 0;
float humidity = 0;
unsigned long currentMillis;
unsigned long lastMillis;
bool errorSGP30 = false;
bool errorDHT11 = false;
bool noise = false;

View File

@@ -0,0 +1,17 @@
#include <nodeCodeHeader.h>
nodeReadings esp32Node();
void setup()
{
// put your setup code here, to run once:
esp32Node.setup();
esp32Node.websocketSetup();
esp32Node.resetValues();
}
void loop()
{
// put your main code here, to run repeatedly:
esp32Node.loop();
}

View File

@@ -0,0 +1,63 @@
#include "arduino.h"
#include "nodeCodeHeader.h"
nodeReadings::nodeReadings() {
}
void nodeReadings::setup(){
// 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::resetValues() {
counter = 0;
eCO2 = 0;
TVOC = 0;
temperature = 0;
humidity = 0;
currentMillis = 0;
lastMillis = 0;
errorSGP30 = false;
errorDHT11 = false;
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");
}

View File

@@ -0,0 +1,18 @@
#ifndef nodeReading_h
#define nodeReading_h
#include "Arduino.h"
#include "headerFile.h"
class nodeReadings {
public:
nodeReadings();
void setup();
void loop();
void resetValues();
private:
};
#endif

View File

@@ -1,13 +0,0 @@
#include <nodeCodeHeader.h>
#include "websockets.h"
nodeReadings esp32Node;
void setup() {
esp32Node.setup();
esp32Node.resetValues();
}
void loop() {
esp32Node.loop();
}

View File

@@ -1,115 +0,0 @@
#include "nodeCodeHeader.h"
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();
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;
TVOC = 0;
temperature = 0;
humidity = 0;
currentMillis = 0;
lastMillis = 0;
errorSGP30 = false;
errorDHT11 = false;
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);
// 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);
webSocket->websocketSetup();
}
void nodeReadings::loop() {
// update when interval is met
if (currentMillis - lastMillis >= interval){
lastMillis = millis();
update();
}
// update the counter
currentMillis = millis();
// loop the websocket connection so it stays alive
webSocket->loop();
}
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);
// read dht11 sensor
temperature = float(dht->readTemperature());
humidity = float(dht->readHumidity());
// 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(2);
display->setTextColor(SH110X_WHITE);
display->setCursor(0,0);
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");
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;
}
}

View File

@@ -1,55 +0,0 @@
#ifndef nodeReading_h
#define nodeReading_h
#include <Wire.h>
#include <Adafruit_SH110X.h>
#include <DHT.h>
#include <Adafruit_SGP30.h>
#include "websockets.h"
// define pins on esp32
#define MICPIN 6
#define DHTPIN 7
#define SCL 9
#define SDA 8
#define DHTTYPE DHT22
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define i2c_adress 0x3c
#define OLED_RESET -1 // QT-PY / XIAO
#define USE_SERIAL Serial
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;
};
#endif

View File

@@ -1,81 +0,0 @@
#include "websockets.h"
websockets::websockets(){
webSocket = new WebSocketsClient();
_WiFiMulti = new WiFiMulti();
}
// hexdump function for websockets binary handler
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++) {
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");
// 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:
// 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;
}
}
void websockets::sendMyText(String message) {
webSocket->sendTXT(message);
}

View File

@@ -1,25 +0,0 @@
#ifndef websockets_h
#define websockets_h
#include <WiFiMulti.h>
#include <WiFi.h>
#include <WebSocketsClient.h>
#include <Arduino.h>
#define USE_SERIAL Serial
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;
};
#endif

View File

@@ -11,8 +11,6 @@ nav:
- TFT screen : node-documentation/TFT-screen
- Classes : arduino-documentation/classes
- Node Documentation: node-documentation/node
- Node Class: node-documentation/NodeClassDocumentation
- Node Uml Diagram: node-documentation/NodeClassUml
- 🍓 RPi Documentation:
- Raspberry pi: Sp1SchetsProject/InfrastructuurDocumentatie/raspberryPi
- MariaDB: rpi-documentation/mariadb-installation
@@ -20,19 +18,14 @@ nav:
- Websockets: rpi-documentation/websockets
- Reverse Proxy: rpi-documentation/Reverse-Proxy
- Db - Ws connection: rpi-documentation/Databaseconnection
- Put-request: rpi-documentation/Put-Request
- 🧠 Brainstorm:
- Ideeën: brainstorm/ideeën
- Database design: brainstorm/Database
- Feedback: brainstorm/Feedback
- Problem: brainstorm/Problem
- InfrastructureUml: brainstorm/UML-infrastructure
- Infrascructure: brainstorm/infrasturcture
- Infrastructure: brainstorm/UML-infrastructure
- Taskflow: brainstorm/Taskflow
- Design: Sp1SchetsProject/FirstDesign
- Interview facility manager: brainstorm/gebouwBeheer
- Questions enquete: brainstorm/QuestionsEnquete
- 🖨️ Software:
- Dev page: brainstorm/SoftwareDocumentatie/Dev_page
- Graph classes: brainstorm/SoftwareDocumentatie/classes

View File

@@ -1,86 +0,0 @@
#include "DHT.h"
#include <WiFi.h>
#include <WiFiMulti.h>
#include <WebSocketsClient.h>
#define DHTPIN 4
#define DHTTYPE DHT11
uint8_t h;
uint8_t t;
uint16_t interval = 5000;
unsigned long currentMillis;
unsigned long lastMillis;
WiFiMulti wifiMulti;
WebSocketsClient webSocket;
DHT dht(DHTPIN, DHTTYPE);
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
// Handle WebSocket events here if needed
}
void setup() {
Serial.begin(9600);
Serial.println(F("DHTxx test!"));
dht.begin();
wifiMulti.addAP("iotroam", "sGMINiJDcU");
wifiMulti.addAP("Ziggo6565749", "Ziggobroek1@");
Serial.println("Connecting Wifi...");
if (wifiMulti.run() == WL_CONNECTED) {
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Connect to WebSocket server
webSocket.begin("145.92.8.114", 80, "/ws"); // Replace with your Raspberry Pi's IP address and port
webSocket.onEvent(webSocketEvent);
webSocket.setReconnectInterval(500);
}
}
void loop() {
// update when interval is met
if (currentMillis - lastMillis >= interval){
lastMillis = millis();
update();
}
// update the counter
currentMillis = millis();
float h = dht.readHumidity();
// Read temperature as Celsius (the default)
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
Serial.println(F("Failed to read from DHT sensor!"));
return;
}
// Compute heat index in Celsius (isFahrenheit = false)
float hic = dht.computeHeatIndex(t, h, false);
Serial.print(F("Humidity: "));
Serial.print(h);
Serial.print(F("% Temperature: "));
Serial.print(t);
Serial.print(F("°C "));
Serial.print(hic);
Serial.print(F("°C "));
// Ensure WebSocket communication
webSocket.loop();
}
void update(){
webSocket.sendTXT("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Temp\":\"" + String(t) + "\",\"Humi\":\"" + String(h) + "\"}");
}

View File

@@ -1,53 +0,0 @@
### Connecting With The websocket.
#### The reason.
During our project we needed more esp's to connect to the websocket, so i was given the task to make a connection to wifi, connect to the websocket, and then send information to it.
#### Before the setup.
I used library's for: connecting to multiple wifi's, connecting to the websocket and being able to read infromation form a DHT11.
These were called uppon in the first few lines.
Together with this a few variables are named to be used later in the code.
#### The Setup
Firstoff, start with identifying which serial port will be used and work from there.
It Then tells the DHT to send a test message and start it up.
Then the several wifi connections get told here and it looks for the one that connects.
(This is made bij giving the wifi name and pasword)
In this sequence a print is made, showing that there is an atempt to connect to a wifi.
*This was used because the node should be able to connect to several wifi's and not be stuck t one in perticulair.
After the wifi is connected it sends a print to indicate a connection and pings the ip adress of the used wifi.
The websocket connection which was then made is made using the uri, port, and type of connection.
In case of websocket events it gets sent.
In case of connection failiure, try this.
#### The loop
We start with setting a timer because a "delay" function would break the connection with the websocket resulting in an error.
This relates back to some some variables that were made in the beginning.
We make variables for the different results we are gathering, so these can be used later.
Then the variables get printed for overview.
To ensure the websocket connection , the process gets looped.
#### Update File.
Here the text thathas te be sent to the websocket gets sent.
### The fysical product.
it is shown here:
![fysical1](<../documentatie/assets/DHT11 state 1.jpg>)
It also turns on:
![fysical2](<../documentatie/assets/DHT11 state 2.jpg>)
Here are my fritzing and wireframe, the components used are shown but The DHT11 is replaced by a DHT22.
![alt text](../documentatie/assets/DHT11_by_button_fritzing.png)
![alt text](../documentatie/assets/DHT11_by_button_wireframe.png)
### The code.
Here the c++ code is shown:
https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/blob/main/docs/LearningProcessBram/ArduinoExperience/NodeWithWebConnection.ino?ref_type=heads

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 KiB

View File

@@ -1,58 +0,0 @@
### Buzzer class
To provide a demonstration of how classes work in C++/Arduino, I decided to code something simple and repeatable.
This class is intended to assign an input and output source for a button to activate a buzzer, and for a buzzer to be activated by a button. This is achieved by using private variables and public functions that access these variables.
This class code allows the user to easily add new buzzers by simply creating an object and calling the appropriate function.
##### Constructor
Inside the class, a constructor is defined. In C++, this constructor shares the same name as the class itself. Every time the class is called, the constructor is activated with the given data.
##### Class functions
Within a class, functions can be defined. These functions can interact with the variables made inside the class. These functions can then be called outside the class to execute their action.
In this case, the "activation" function is defined within the class and is used in the loop function. This function contains the if-statement that determines if the button is pressed and activates the buzzer pin accordingly.
##### Object Creation
After defining the class, an object needs to be made. This object will immediately trigger the constructor and ask for input variables. These given data points will be inserted into the private variables.
In this instance, the buzzer pin is set to 12 and the button pin to 20.
##### Using the class
In the loop function, the new object is placed along with the function defined in the class to activate it. This function executes with the variable data provided in the constructor. Here, it checks if the specified pin is receiving a signal and then only outputs a signal to the buzzer when there is one.
##### Link to code
(https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/blob/main/docs/LearningProcessBram/ArduinoExperience/classesArduino/buzzerclasses.ino?ref_type=heads)
### The board
The fysical board should look something like this:
![Fritzing of board](<Schermafbeelding 2024-04-05 110142.png>)
The board is simple and easy to recreate and has lots of space left for other functions.
By making this class, the addition of more buzzers is very easy. As long as there are pins, there can be a buzzer.
For this example, I have only used one, but adding more would be no problem. Just make a object, give the pin numbers and call the function.
The wireframe for this board would looke something like this:
![wireframe](<Schermafbeelding 2024-04-05 110219.png>)
I tried making it as clear as possible by moving some parts around and clearing some intertwining wires.
###### My Fysical Recreation.
To properly demonstrate this code in action, I recreated the board. In the image, you can see that I plugged in three different LEDs.
![fysical board](buttonClassOff.jpg)
In the image, you can see that I plugged in three different LEDs.
This was done to show off that the board/code works, as it's not possible to see if a buzzer is working on camera.
For the time being, pretend that the LED and resistor combinations are separate buzzers.
To make this board turn all the buzzers/LEDs on, I needed to create three different objects. These objects looked like this:
![threeObjects](threeObjects.png)
The first variable is where I want the buzzer to connect, and the second variable is where I want the button to be connected.
After this, I simply placed the objects in the loop and called the function we made in the class:
![function calling](threeObjectsAndFunctions.png)
Now, we simply push the code to The Esp and try it out.
![The classes work](buttonClassOn.jpg)
Now, we have effectively made the LEDs/buzzers turn on with the push of a single button.

View File

@@ -1,30 +0,0 @@
class BuzzerPin{
private:
int buzzerPin;
int button;
public:
BuzzerPin(int bp, int but){
buzzerPin = bp;
button = but;
pinMode(button, INPUT);
pinMode(buzzerPin, OUTPUT);
}
void activate(){
if(digitalRead(button) == HIGH){
digitalWrite(buzzerPin, HIGH);
}else{
digitalWrite(buzzerPin, LOW);
}
}
};
BuzzerPin buzzerOne(12, 20);
void setup(){
}
void loop() {
buzzerOne.activate();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -10,35 +10,35 @@ Then I came across a teaching site (https://www.codingkids.nl/arduino-buzzer.htm
### Arduino Video
I watched a video about using Arduino and took a lot of op notes along the way.
The link is: (https://www.youtube.com/watch?v=BLrHTHUjPuw).
```
// $Arduino information:
//pinnumber(locatie, in-/output)
\/\/Arduino information:
//digitalwrite(locatie, high/low)
\/pinnumber(locatie, in-/output)
//delay(time in millisec)
\/digitalwrite(locatie, high/low)
//int... <- variable
\/delay(time in millisec)
//with a decimal its a double -> 1,2
\/int... <- variable
//a character is a char -> "a"
\/with a decimal its a double -> 1,2
// $serial communications:
\/a character is a char -> "a"
setup:
\/\/serial communications:
\/setup:
Serial.begin(9600) -> the text speed
Serial.sprintLn(text)
Loop:
\/Loop:
Serial.print("text")
Serial.printLn(....) -> variable because no "
//Ctrl + shift + M = serial monitor.
\/Ctrl + shift + M = serial monitor.
The text speed needs to be the same as given.
// $If Statements:
\/\/If Statements:
if(condition){
@@ -50,9 +50,9 @@ if(condition){
}
// && = "and"
\/&& = "and"
// || = "or"
\/|| = "or"
\/\/For loops:
@@ -62,20 +62,20 @@ For(int*i; i <= 10 ; i++){
}
// The fading of led's:
\/The fading of led's:
examples, basics, fade
// servo's
\/ servo's
examples, servo, sweep
```
### Linux and raspberry PI.
To gain more knowledge about Linux, I first asked my classmates if they could get me started.
They showed me how to gain access to a server and told me how to navigate through files.
By doing this I got taught the following commands:
```
~ $ 'ls -la' = show file / folders
~ $ 'top' = see currently running programs
@@ -89,7 +89,7 @@ By doing this I got taught the following commands:
~ $ 'ping ip addres'
~ $ 'ssh username@ip address' = open ssh connection.
```
### Air, temperature, and all sort of stuff.
After the Linux coding I decided to take a step back and began gaining experience with sensors.
@@ -101,16 +101,12 @@ I wanted to make my own spin on the original design by including a button to act
The rest of the tutorial was clear and worked like a charm.
the code used looks like this:
Begin by including a specific library for the DHT11.
```
#include "DHT.h"
#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
```
using This perticulair serial port, means that you do have to switch to it when you want to see results coming in.
The dht.begin command starts the process.
```
void setup() {
//the serial port:
Serial.begin(9600);
@@ -120,11 +116,9 @@ Serial.println(F("DHTxx test!"));
//the library start
dht.begin();
}
```
It starts by making float variables, to give over the information.
It also includes a error message in case of no feedback.
```
void loop() {
delay(2000);
//a float has decimal numbers and the library reads the measurements.
@@ -142,7 +136,7 @@ void loop() {
float hif = dht.computeHeatIndex(f, h);
float hic = dht.computeHeatIndex(t, h, false);
//all serial.print's send stuff to the serial board to showcase.
//all serial.ptint's send stuff to the serial board to showcase.
Serial.print(F("Humidity: "));
Serial.print(h);
Serial.print(F("% Temperature: "));
@@ -164,13 +158,9 @@ And here it looks in action:
Later on, I could expand this code and the physical product to include the rest of the sensors.
The wiring is shown here:
![my fritzing diagram](assets/DHT11_by_button_fritzing.png)
![the wiring](<assets/DHT11 wires.png>)
The red cables are 3v, the black cables are the ground and the green cable is the echo for data.
This version is using a DHT22, this is a updated version of a DHT11 but still provides the same information and still uses the same pins. So i decided to compromise for it, using it in my fritzing diagram.
The wire-frame is shown below:
![wireframe DHT11](assets/DHT11_by_button_wireframe.png)
This helps visualise all connections and shows what parts were used for reproductional .
This version of a DHT11 has a built-in resistor otherwise this would have to be included.
### Buzzers .pt 2
I found out how to make multiple buzzers go off with the press of one button and increase as Mutch as there are pins.
@@ -184,18 +174,16 @@ The code is short and simple:
int button = 20;
int buzzerone = 12;
int buzzertwo = 11;
```
Now we set the pins up to either input or output a signal.
```
void setup() {
//put down some pins that will output , and some that input.
pinMode(button, INPUT);
pinMode(buzzerone, OUTPUT);
pinMode(buzzertwo, OUTPUT);
}
```
Here the button pin will seek a signal, when it is given it will send signals to the other given pins in the if-statement.
```
void loop() {
//read is there is input on the button pin, if so send output to the other pins, otherwise keep them off.
if(digitalRead(button) == HIGH){
@@ -213,13 +201,6 @@ Here I made the physical design but instead of buzzers I used lights in order to
And here is the the board working:
![board working and turned on.](<assets/Buzzer board on.jpg>)
To Show my wiring more clearly, here is my fritzing board:
![fritzing_buttons pt2](assets/Buzzer_board_fritzing.png)
the red wires are 3v, the black wires are the ground connections.
Here the wireframe is shown for parts specification and possible reproduction:
![wireframe _ buzzers pt2](assets/Buzzer_board_wireframe.png)
### Python For Dummies.
My job was to make a connection between the WebSocket and the database we had set up, and to do this we wanted to use python.
To give an easy picture, here is where i would come:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

View File

@@ -1,8 +0,0 @@
## 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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -33,62 +33,4 @@ We Wil use this Feedback and see what we can do to change the design in a way th
The design will be updated to include:
`smaller holes for less distraction.
`ventilation at the bottom for better airflow.
`Remove the lamps on top of the node for less distraction.
### Design questions led by bram
For the nodes we designed to be placed around the 5th floor we were stuck between two designs:
The wooden design: takes less time to create,is more fragile, and more eco-friendly.
The Plastic: less eco-friendly, takes a longer time, looks objectively better.
since we can't decide on what we should use, I (Bram) went out to asks the public for their opinion.
To do this i took the front of the plastic design, and the front of the wooden design.
They looked like this:
![2Covers](../LearningProcessBram/documentatie/assets/2Covers.jpg)
1. which design is more likable
/Sebas : plastic looks better and goes straight to the point.
/Skip : plastic looks more clean.
/Dano : plastic is more smooth.
/Sietse : plastic is better.
/Ishak : Wood is more ecofriendly.
/Nailah : PLastic looks cooler
2. which design is more distracting.
/Sebas : wood is more distracting and more obvious.
/Skip : wood, because the school walls are more darker themed.
/Dano : wood looks off.
/Sietse : wood would be out of place.
/Ishak : Wood, but in a good way.
/Nailah : plastic looks more interesting.
3. Any further comments about the design?
/Sebas : Don't do wood, plastic is more sleek.
/Skip : plastic looks more professional.
/Dano : no.
/Sietse: no.
/Ishak : in the wood you can burn in your logo.
/Nailah : The wood can look better inside the school.
In conclusion, Even though the wood would atract more attention, the plastic looks better according to the students.
So from this information we will continue to make plastic cases for the nodes.
`Remove the lamps on top of the node for less distraction.

View File

@@ -9,7 +9,7 @@ Questions shouldn't be able to measure using sensors. 3 possible answers.
- How clean is the study area? (clean, normal, disgusting).
- What do you think of the temperature in the study area? (hot, perfect, cold).
- How crowded would you say the study area is?(not at all, its fine, really crowded).
- Is there enough help available on the 5th floor? (no, decently, yes).
- Is there enough help available? (no, decently, yes).
### Feedback questions

View File

@@ -1,120 +0,0 @@
# 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
Node <-- SensorNode : extends
Node <-- FeedbackNode : extends
class Node {
+nodeID
+processNodeData()
+updateNodeData()
}
class SensorNode {
+tempArray
+humiArray
+eco2Array
+tvocArray
}
class FeedbackNode {
+feedbackArray
}
```
# Graphs
## Introduction
The graphs are used to display the data from the sensors. The data is collected by the raspberry pi and then displayed on the graphs. The graphs are made using the [plotly library](https://plotly.com/javascript/) .
## Requirements
### Live graphs
- Every node has to have a live graph
- The graphs has to be updated every 5 seconds
- All the data from one node has to fit in one graph
## Class diagrams
### Graphs
```mermaid
classDiagram
liveGraph --> graph: extends
class graph {
+nodeId
makeGraph()
}
class liveGraph {
+cnt
+timeArray
+tempArray
+humiArray
+eco2Array
+tvocArray
makeGraph()
updateGraph()
updateData()
}
```
## Order of operations
### Live graphs
```mermaid
sequenceDiagram
participant Node
participant Raspberry pi
participant Website
Node->>Raspberry pi: sensordata via websocket every 5 seconds
Raspberry pi->>Website: Node data via websocket if new data is received from the node
Website->>Website: updateGraph()
Website->>Website: updateData()
```
1. Every node sends its data to the raspberry pi via websocket every 5 seconds
2. The raspberry pi sends the data to the website via websocket if new data is received from the node
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()
```

View File

@@ -1,77 +0,0 @@
```mermaid
classDiagram
setup --> websocketSetup
loop --> screenButtonHandler
screenButtonHandler --> DisplayText
screenButtonHandler --> sendData
sendData --> Server : Websocket
setup --> loop
python --> Server
Server --> website : Websocket
namespace ESP32Questionbox {
class setup {
+int questionID
+char*[] Question
+char*[] Answer
+DisplayText displayText
+void websocketSetup()
}
class loop {
+void screenButtonHandler()
+void sendData(int question, String answer)
+void hexdump(const void* mem, uint32_t len, uint8_t cols)
}
class websocketClient {
+loop()
+begin()
+sendTXT()
}
class websocketSetup {
+connectWifi()
+websocketConnect()
}
class screenButtonHandler {
-bool redButton
-bool greenButton
-bool whiteButton
+displayText.writeText()
+sendData()
}
class DisplayText {
+void writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom)
-int centerText(char* text)
-void printWordsFull(char* text, bool bottom)
}
class sendData{
+webSocket.sendTXT
}
}
namespace server {
class python {
+databaseScript()
+websocketScript()
+flaskScript()
}
class Server {
+websocket()
+flask()
+mariaDB()
}
}
namespace user {
class website {
+ getLiveData()
+ getHistoricalData()
+ showData()
}
}
```

View File

@@ -1,24 +0,0 @@
# Talk with building management
## Introduction
We interviewed building management so we can clarify what they expect from the project and what they think of the state the project is in right now and if there need to be some adjustments. We also asked them some questions about the website and the node itself and the website. We primaly wanted clarity on what they thought on the website design because they are the ones that are going to use it the most.
## Questions for building management
1. Design of page? (current page, new design or own idea)
2. What do they expect of a page made for building management?
3. Do they think they can work with the incomming feedback from the enquete?
4. Design of the node? (plastic or wood)
## Answers
1. The current page is a bit too techincal and a bit unorganized. you couldnt tell that there is a new node added to the page. They also said that we needed to point out that there is another node connected for example start with half the ui of that specific node at the bottom of the screen.
2. They expect a page that is easy to use and dummy proof.
3. They think they can work with the incomming feedback from the enquete. And they thought it was a good idea to measure things that sensors cant measure for example peoples opinions.
4. plastic is better because it is easier to clean and it is more durable. It also looks nicer on the wall, it blends in better.
## Feedback:
Building management had some good feedback points about the page itself, about the idea and some good pointers for the execution. They would also look into making the data they are already measuring accessible for us so we can compare it to our own sensors. The things they had to say about the website are: they found the first design a bit unorganized and said that they would rather see the second design we had in figma where you can select the node you want to see, maybe also a search function to specify the node that is displayed. What also was said was to try to make it idiot-proof because not all of the building management people are very technical. They had some things to say about the node design, like maybe make the color white to make it better with blending into the white walls of the total area. They also asked the question of does it matter at what height the node is placed. One other thing they said was to write something onto the node to make it clear to the people in the area what it was doing, something like ReaderNode™. And the last thing they said was if we thought about how to make sure it doesnt get vandalized.
## Conclusion
Building management thought it was a good and interesting project. They wanna actively help us with the data they are already measuring and they had some good feedback points about the website and the node itself. They also had some good questions about the node itself that we need to look into. For example how are we are going to make sure it doesnt get vandalized. Furthermore they had good feedback on the website and they preffered our figma design over our current design and we needed to make the website dummy proof so everyone can use it even without technical knowledge.

View File

@@ -1,83 +0,0 @@
# The infrastructure
###
## Three layers
###
### 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.
``` mermaid
classDiagram
Node --> Raspberry pi : Websocket
Raspberry pi --> Website : getData
Raspberry pi <--> EnqueteNode : Websocket
namespace Server {
class Raspberry pi {
+MariaDB
+Apache2
+Python
Database()
Webserver()
Websocket()
}
class Node {
+Co2
+Temperature
+Humidity
+Tfok
+Sound
collectData()
}
}
namespace User {
class Website {
+Co2
+Temperature
+Humidity
+Tfok
+Sound
+Graph
+Map
+Settings
GetData()
}
class EnqueteNode {
+Co2
+Temperature
+Humidity
+Tfok
+Sound
+Graph
+Map
+QuestionResponse
EnqueteDisplay()
EnqueteButtons()
GetData()
}
}
```

View File

@@ -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.

View File

@@ -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()
}
}
```

View File

@@ -99,12 +99,6 @@ class Adafruit_ST7796S_kbv{
}
```
## Why did we choose for this screen?
We chose the screen too fast without going over all the functions and researching what we actually needed. For example we aren't using the touchscreen function of the screen so we could've gotten a different screen without touchscreen. We've attempted to use a screen for the raspberry pi with some pin headers on it but we couldn't get it to work with the esp32. We wanted to have a bigger screen than the small oled screens we already have because it isn't nice to read from and you need to get up close to see what it displays. With a bigger screen thats less of a issue. After the purchase we did some more research for screens but the bottomline was this was the best one we could get because there aren't screens that are this big without touchscreen.
## Sources
* https://www.tinytronics.nl/en/displays/tft/4-inch-tft-display-320*480-pixels-with-touchscreen-spi-st7796s Source for Driver
* https://github.com/prenticedavid/Adafruit_ST7796S_kbv Download link for the library

View File

@@ -61,29 +61,4 @@ graph LR
A[Design Sketch] -->|Usertest| B(Fusion360 design)
B -->|Not fitting| C[New fusion360 design]
C -->|assembly| D[Finished product]
```
## The evolved design of the nodes.
During the last sprint we had a lot of feeback about the node and mostly about tis design. hence why we had decided to remake it in several different ways.
We had made a prototype for a wooden version, but that never made it past an first sketch, seeing as almost all fedback came back telling About how the plastic design looked better and was less distreacting.
The user-test document is here:( https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/blob/main/docs/brainstorm/Feedback.md?ref_type=heads#design-questions-led-by-bram )
After the results came back, we decided to make progress with further designing the cover of the node.
The main body shape was certain, because all of the components needed to fit inside of the node and needed some space.
Aswell as the simple shape to save on printing costs and keep the design simple to look at.
The only thing that was able to change was the cover.
The cover was originally made of solid plastic and was molded to fit the shape of the board and the sensors/ screen inside.
From here we decided to brainstorm on an easyer way of covering the circurty.
That is when it hit us, We could use acrylic as a cover!
Acrylic can come in many different colors, is easyer to cut out ( takes less time) and gives a bit of an isight into the wiring.
This this in mind we made a node with this new design and remade one with the old design, this again could be further used for user-tests.
For the time being we are able to show off both of the designs and are able to read data from both, the only difference being the outer shell.
The images:
```

View File

@@ -19,23 +19,20 @@ The node is programmed using the Arduino IDE. The code is written in C++ and is
The following libraries are used in the node code:
- Wire.h
- Adafruit_SH110X.h
- Adafruit_SGP30.h
- DHT.h
- WiFiMulti.h
- WiFi.h
- WebSocketsClient.h
- nodeCodeHeader.h
- <Wire.h>
- <Adafruit_SH110X.h
- <Adafruit_SGP30.h>
- <DHT.h>
- <WiFiMulti.h>
- <WiFi.h>
- <WebSocketsClient.>
- <nodeCodeHeader.h>
### Code
The code is divided into the following classes:
- Node readings
- Websockets
The two classes that are used are split into the 2 becouse the node readings handels everything about reading information from the sensors and displaying them on the screen, all the local stuff is handeled here so to speak. And into Websockets this handels every thing from connecting to the wifi to sending the data that is recorded from the sensors into json format and sending that data to the websockets so that the data can be prossed over there.
uitleg dano
### Communication
@@ -64,12 +61,7 @@ flowchart LR
The wiring diagram for the node is as follows:
![Wiring diagram of node](..\assets\imagesSp3\wiringDiagramNode.png)
### Fritsing Diagram
![fritsing diagram of node](..\assets\Node.png)
![Wiring diagram of node](docs\assets\imagesSp3\wiringDiagramNode.png)
## Future Improvements

View File

@@ -1,298 +1,97 @@
# Original Database - Websocket connection(by bram)
For our project, we needed to establish an efficient and functional connection between a live data collector and a database where this data would be stored.
The data we collected originated from live "nodes" which were small boxes equipped with various types of sensors to gather specific data from their surroundings.
This collected data needed to be transmitted to a database to facilitate its presentation on a website we were developing. The website would retrieve the data from the database and display it in various formats.
This is what the connection would be inserted.
![my connection](MyConnection.png)
Given the high demand of this data connection for our project, it was imprtant that it was reliably. Bram was given the task to designing a connection in Python between the WebSocket (live server) and the database (data storage).
Since we had the WebSocket data on a Raspberry Pi, it made sense to make the connection on the Pi itself. This became an opportunity for Bram to gain knowledge about Python, considering he originaly didn't have experience with this programming language.
## Python code + explaination
In the given Raspberry Pi, a file named "data.py" was created, from which this script could be called when needed.
We wanted to make a working connection between our websocket wich runs all the data gatherd by our nodes and a live feed to our database.
So we set out to make this connection using python.
At the beginning of the file, the code starts with importing the different required libraries.
```py
#this library makes functions run simultaneously
At first we needed to import the folowing librarys:
```js
// everything is running async so the code can run together and not interfere with eachother.
import asyncio
#This library makes the connection to the websocket possible.
import websockets
#This library makes a connection with the database
import mysql.connector
#This library makes json data readable.
// a library to connect to the database.
import mysql.connector
import json
```
Afterward, a function would be called related to the "mysql.connector" library, where you would provide the login credentials of the database you have set up to establish a solid connection. This connection will be utilized multiple times later in the code.
```py
#async is making use of the asyncio library.
Then we began the process of connecting with both the websocket and the database.
First-off, we began making a connection to the database by using a mysql library in wich we gave the log in in order to connect to our ow database.
```js
//the data that has to be pushed needs to be
async def process_data(data):
try:
mydb = mysql.connector.connect(
host="localhost",
user="root",
# pasword hidden for privacy
password="********",
database="NodeData"
user="*****",
password="*********",
database="*******"
)
cursor = mydb.cursor()
```
This next part has a lot of different functions, so it will be split up for clarity.
It begins with creating a variable to retrieve information from a specific part of the database. This information is then stored in an array later on. In this case, it is selecting the existing MAC addresses from the database.
Afterward, a query is made for a different part of the code and acts as a "mold" for the data to be sent to the database. The values are not inserted yet because these will be the data collected from the nodes.
```py
#variable to connect to the DB
MACDataReading = mydb.cursor()
#get data from DB
MACDataReading.execute("SELECT MAC FROM Node")
#make a mold for the data to get to the DB
query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)"
```
In the next part of the code, the data collected from the node (which is done later on) is processed here and made readable for the Python code.
This processed data is then split up into five different types: the temperature, the humidity, the eCO2 and TVOC values, and the MAC address from which this information was sent.
The MAC address is then taken and turned into a tuple. This is done because the database expects a tuple to be inserted instead of a string.
(for more information on tuples I suggest visiting https://www.w3schools.com/python/python_tuples.asp)
```py
#load the json data and make it readable.
processedData = json.loads(data)
#divide the data into types
processedTemp = (processedData['Temp'])
processedHumi = (processedData['Humi'])
processedeCO2 = (processedData['eCO2'])
processedTvoc = (processedData['TVOC'])
processedMAC = (processedData['node'])
#make a tuple of the MAC by placing a comma.
MACTuple = (processedMAC,)
```
Coming back to the previous lines of code, the data which was first asked for is now gathered and put into an array.
This array is then examined, and all the data is compared to the newly obtained MAC address.
If it is not found, then the new MAC address is added to the database. This makes automation much easier and makes the process of adding a new node easy.
```py
#fetching data and adding to an array.
MACDataFetching = MACDataReading.fetchall()
MACArray = list(MACDataFetching)
#see if the given MAC is not in the array.
if MACTuple not in MACArray:
#a query to insert the new MAC in the DB
addingNode = "INSERT INTO `Node` (MAC) VALUES (%s)"
#combine the query and the data and push it.
cursor.execute(addingNode, MACTuple)
mydb.commit()
```
From here the data which was collected from the websocket gets placed in an array together with a few guidlines to propperly place it in the correct files on the database.
After going along all instances of the array, the data gets pushed together with the query to propperly enter the database.
Sadly this version of the code is only able to push the data from the one node because of some errors within the datase.
(This is later fixed in the updated version my teammate made.)
```py
#making an array with the data to sort it and be able to be pushed to the database.
pushingDataArray = [(1, "Temp", processedTemp), (1, "Humi", processedHumi), (1, "eCO2", processedeCO2), (1, "TVOC", processedTvoc)]
#go along all instances in the array, and combine this with the query.
for i in pushingDataArray:
print(query ,i)
cursor.execute(query, i)
mydb.commit()
#in the case of an error, show what and where, and after, close the database connection.
Then after this we code in the infromation we want to put inside of the database.
The data collected from the websocket is json data, so this has to be changed.
```js
//Making a variable for the database connection
cursor = mydb.cursor()
//Making a variable for the database connection
MACDataReading = mydb.cursor()
//find the correct section and interact with it.
MACDataReading.execute("SELECT MAC FROM Node")
//the query for what needs to be inserted into the database ( atleast where it has to go).
query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)"
//the recieved data from the websocket is json data so needs to be changed.
processedData = json.loads(data)
//variables about the recieved data points.
processedTemp = (processedData['Temp'])
processedHumi = (processedData['Humi'])
processedECo = (processedData['eCO2'])
processedTvoc = (processedData['TVOC'])
processedMAC = (processedData['node'])
//change the recieved macadress to a tuple.
MACTuple = (processedMAC,)
//fetch the data from the database an[d put it in an array.
MACDataFetching = MACDataReading.fetchall()
MACArray = list(MACDataFetching)
//see if the fetched data is not in the gotten array.
//otehrwise insert it into the database directly.
if MACTuple not in MACArray:
addingNode = "INSERT INTO `Node` (MAC) VALUES (%s)"
cursor.execute(addingNode, MACTuple)
mydb.commit()
//the websocket data that needs to be sent to the database.
pushingDataArray = [(1, "Temp", processedTemp), (1, "Humi", processedHumi), (1, "eCO2", processedECo), (1, "TVOC", processedTvoc)]
// forloop, go allong the array.
for i in pushingDataArray:
print(query ,i)
cursor.execute(query, i)
mydb.commit()
// show me the error it there is one.
except mysql.connector.Error as err:
print("MySQL Error:", err)
finally:
cursor.close()
mydb.close()
```
In the next function, the connection is established with the WebSocket and collects the data sent by the nodes. This data is then stored in a variable named "data". (This data is the same data that was being processed to make it readable for Python and was split up in differnt types.)
This function also verifies if the WebSocket connection can be established and provides an error message when this is not the case.
```py
After fully connecting t othe database, making statements of what to put there and telling the code what to do, we ofcourse need to write the code to connect to the weebsocket.
We begin by telling our websocket id and what type of port we are using.
Then we will collect live data from the conected websocket, store it in a variable, and then in the previous code
```js
//here the connection to the websocked is made
async def receive_data():
uri = "ws://145.92.8.114/ws"
uri = "****************"
try:
async with websockets.connect(uri) as websocket:
while True:
// the data collected from the websocket is.
data = await websocket.recv()
// data recieved: conformation.
print(f"Received data: {data}")
await process_data(data)
// error sowing.
except websockets.ConnectionClosedError as e:
print("WebSocket connection closed:", e)
```
This is one of the last functions where the file is instructed to wait for a WebSocket connection before executing the code. This is done to prevent false data from entering the database.
```py
async def main():
await receive_data()
asyncio.run(main())
```
As a summary, this code is meant to establish connections both to the database and the WebSocket to enable a data connection between them. When new data arrives, it will be pushed to the database, and if a new MAC address is encountered, it will be added to the list of addresses.
(The link to the code https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/blob/main/server/web-data-connection/data.py?ref_type=heads)
# New Version (by sietse)
## Changes made
The original code was a good start, but it had some issues. The code could only handle the data from the sensorNodes and didn't include the nodeID for measurements.
Since we have 2 kind of nodes (sensorNodes and enqueteNodes) we needed to make another function to commit the enqueteData in the database. I have also made a filter to know which data is from the sensorNodes and which data is from the enqueteNodes. This way we can commit the data to the right table in the database.
I have also added a function to get the nodeID from the MAC address. This way we can commit the data to the right node in the database.
## The new "filter" code
### Function to get a list with macAdresses from the sensorNodes and enqueteNodes
To filter i have made 2 lists, one with all the mac adresses of the sensorNodes and the other with the mac adresses of the enqueteNodes.
The function that handles that and updates the list is the following:
```python
async def getNodeInfo(type):
global sensorNodeArray
global enqueteNodeArray
nodeInfoArray = []
id = (type,)
mydb = dbLogin()
cursor = mydb.cursor()
cursor.execute("""SELECT MAC FROM Node WHERE Type = %s""", id)
nodeInfo = cursor.fetchall()
for tuples in nodeInfo:
for item in tuples:
nodeInfoArray.append(item)
print(nodeInfoArray)
cursor.close()
mydb.close()
if type == 'sensor':
sensorNodeArray = nodeInfoArray
print(sensorNodeArray)
return sensorNodeArray
elif type == 'enquete':
enqueteNodeArray = nodeInfoArray
return enqueteNodeArray
```
As you can it works like this:
1. It gets the MAC adresses from the database with the type of node you want to get the data from. (sensor or enquete)
2. It executes the command and puts the data in a list.
3. It uses a nested for loop to get the data out of the tuples and puts it in the nodeInfoArray.
4. It updates, depending on what type, the sensorNodeArray or the enqueteNodeArray with the new data (NodeInfoArray).
5. It returns the array with the data.
### The filter code
Now that we have the data we can filter the data from the websocket.
```python
data = await websocket.recv()
processedData = json.loads(data)
macAdress = processedData['node']
if "Temp" in processedData:
type = 'sensor'
else:
type = 'enquete'
await getNodeInfo('sensor')
await getNodeInfo('enquete')
if macAdress in sensorNodeArray:
nodeID = await getNodeID(macAdress)
await processSensorNodeData(data, nodeID)
elif macAdress in enqueteNodeArray:
nodeID = await getNodeID(macAdress)
await processEnqueteNodeData(data, nodeID)
else:
await newNode(macAdress, type)
```
As you can see its alot of code to explain. So to make it easier i made a mermaid diagram to show how the code works / what it does.
```mermaid
graph TD
A[Get data from websocket] --> B{Is it sensor data or enquete data?}
B -->|sensor| C[Get sensorNodeArray]
B -->|enquete| D[Get enqueteNodeArray]
B -->|New node| E[Add new node to database]
C -->|data| G[Process sensorNodeData]
D -->|data| H[Process enqueteNodeData]
```
## The function to get the nodeID
This function is used to get the nodeID from the MAC adress. This way we can commit the data with the right id in the database.
The function to get the nodeID from the MAC adress is the following:
```python
async def getNodeID(macAdress):
id = (macAdress,)
mydb = dbLogin()
cursor = mydb.cursor()
cursor.execute("""SELECT nodeID FROM Node WHERE MAC = %s""", id)
data = cursor.fetchall()
for tuples in data:
for item in tuples:
nodeID = item
return nodeID
```
1. It gets the nodeID from the database with the MAC adress.
2. It executes the command and puts the data in a list.
3. It uses a nested for loop to get the data out of the tuples and puts it in the nodeID.
4. It returns the nodeID.
## The function to commit the data from the sensorNodes
This function is alot like the original one, with the only 2 changes being that it now also commits the nodeID and that the code to make a new node is now in a different function.
[link to code](../../server/web-data-connection/data.py)
## The function to commit the data from the enqueteNodes
This function is alot like the sensorNode function. It just commits the data to the enqueteData table in the database. And it has another data.
[Link to code](server\data.py)
## The function to add a new node to the database
This function is used to add a new node to the database. This is used when a new node is connected to the websocket, but not yet in the database.
The function to add a new node to the database is the following:
```python
async def newNode(mac, type):
id = (mac, type)
mydb = dbLogin()
cursor = mydb.cursor()
cursor.execute("INSERT INTO `Node` (MAC, Type) VALUES (%s, %s)", id)
print("new node assigned")
mydb.commit()
```
1. It gets the MAC adress and the type of node from the arguments.
2. It executes the command to add the new node to the database.
3. It prints that a new node is assigned.
4. It commits the data to the database.
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -3,14 +3,12 @@ import mysql.connector
from queries import *
app = Flask(__name__)
@app.route('/getMeasurements')
@app.route('/')
def index():
node = request.args.get('node', default = None)
dataType = request.args.get('dataType', default = None)
MAC = request.args.get('MAC', default = None)
dateStart = request.args.get('dateStart', default = None)
dateEnd = request.args.get('dateEnd', default = None)
return getData(node, dataType, MAC, dateStart, dateEnd)
return getData(node, dataType, MAC)
@app.route('/updateData')
def updateDataIndex():
@@ -18,19 +16,11 @@ def updateDataIndex():
new_name = request.args.get('name', None)
new_location = request.args.get('location', None)
return updateData(node_id, new_name, new_location)
@app.route('/getNodeInfo')
def getNodeInfoIndex():
macAdress = request.args.get('macAdress', None)
return getNodeInfo(macAdress)
@app.route('/getQuestionData')
def getQuestionDataIndex():
return getQuestionData()
def updateData(node, name, location):
mydb = loginDB()
query = update_query(node, name, location, False, False)
query = update_query(node, name, location)
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
cursor.execute(query)
mydb.commit()
@@ -49,9 +39,9 @@ def loginDB():
)
return mydb
def getData(node, dataTypes, MAC, dateStart, dateEnd):
def getData(node, dataType, MAC):
mydb = loginDB()
query = get_query(node, dataTypes, MAC, False, False, dateStart, dateEnd)
query = get_query(node, dataType, MAC)
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
cursor.execute(query)
result = cursor.fetchall() # Fetch the results
@@ -60,27 +50,5 @@ def getData(node, dataTypes, MAC, dateStart, dateEnd):
return result
def getNodeInfo(macAdress):
mydb = loginDB()
query = get_query(False, False, macAdress, False, False, False, False)
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
cursor.execute(query)
result = cursor.fetchall() # Fetch the results
cursor.close()
mydb.close()
return result
def getQuestionData(questionID, replies):
mydb = loginDB()
query = get_query(False, False, False, questionID, replies)
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
cursor.execute(query)
result = cursor.fetchall() # Fetch the results
cursor.close()
mydb.close()
return result
if __name__ == '__main__':
app.run(debug=True, host='localhost')

View File

@@ -1,38 +1,18 @@
def get_query(node, dataType, MAC, questionID, replies, dateStart, dateEnd):
if dateStart and dateEnd and node and dataType:
query = f'''SELECT *
FROM Measurement
WHERE TimeStamp BETWEEN '{dateStart}' AND '{dateEnd}' AND NodeID = {node} AND Type IN ('{dataType}');'''
elif dateStart and dateEnd and node:
query = f'''SELECT *
FROM Measurement
WHERE TimeStamp BETWEEN '{dateStart}' AND '{dateEnd}' AND NodeID = {node};'''
elif dateStart and dateEnd:
query = f'''SELECT *
FROM Measurement
WHERE TimeStamp BETWEEN '{dateStart}' AND '{dateEnd}';'''
elif node and dataType:
def get_query(node, dataType, MAC):
if node and dataType:
query = f"SELECT * FROM Measurement WHERE NodeID = {node} AND Type = '{dataType}'"
elif MAC == "*":
query = "SELECT * FROM Node"
elif node:
query = f"SELECT * FROM Measurement WHERE NodeID = {node}"
elif dataType:
query = f"SELECT * FROM Measurement WHERE Type = '{dataType}'"
elif MAC:
query = f"SELECT * FROM Node WHERE MAC = '{MAC}'"
elif replies and questionID:
query = f"SELECT * FROM Reply WHERE replies = '{replies}' AND QuestionID = '{questionID}'"
elif questionID:
query = f"SELECT * FROM Question"
elif replies:
query = f"SELECT * FROM Reply"
else:
query = "SELECT * FROM `Measurement`"
return query
def update_query(node, name, location):
if node and name and location:
query = f"""

View File

@@ -1,65 +0,0 @@
import asyncio
import websockets
import mysql.connector
import json
async def process_data(data):
try:
mydb = mysql.connector.connect(
host="localhost",
user="root",
password="Dingleberries69!",
database="NodeData"
)
cursor = mydb.cursor()
MACDataReading = mydb.cursor()
MACDataReading.execute("SELECT MAC FROM Node")
print('some_response')
query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)"
processedData = json.loads(data)
processedTemp = (processedData['Temp'])
processedHumi = (processedData['Humi'])
processedeCO2 = (processedData['eCO2'])
processedTvoc = (processedData['TVOC'])
processedMAC = (processedData['node'])
MACTuple = (processedMAC,)
MACDataFetching = MACDataReading.fetchall()
MACArray = list(MACDataFetching)
if MACTuple not in MACArray:
addingNode = "INSERT INTO `Node` (MAC) VALUES (%s)"
cursor.execute(addingNode, MACTuple)
mydb.commit()
pushingDataArray = [(1, "Temp", processedTemp), (1, "Humi", processedHumi), (1, "eCO2", processedeCO2), (1, "TVOC", processedTvoc)]
for i in pushingDataArray:
print(query ,i)
cursor.execute(query, i)
mydb.commit()
except mysql.connector.Error as err:
print("MySQL Error:", err)
finally:
cursor.close()
mydb.close()
async def receive_data():
uri = "ws://145.92.8.114/ws"
try:
async with websockets.connect(uri) as websocket:
while True:
data = await websocket.recv()
print(f"Received data: {data}")
await process_data(data)
except websockets.ConnectionClosedError as e:
print("WebSocket connection closed:", e)
async def main():
await receive_data()
asyncio.run(main())

View File

@@ -1,31 +0,0 @@
import asyncio
import websockets
import json
import random
import time
async def send_data(uri):
async with websockets.connect(uri) as websocket:
print("Connected to WebSocket server")
while True:
data = {
"node": "69:42:08:F5:00:00",
"Response": str(round(random.uniform(0, 2))),
"QuestionID": str(round(random.uniform(1, 2))),
}
await websocket.send(json.dumps(data))
print("Data sent")
response = await websocket.recv()
print("Received message:", response)
await asyncio.sleep(2) # Wait a bit before sending the next message
# Start the WebSocket connection
while True:
try:
asyncio.get_event_loop().run_until_complete(send_data("ws://145.92.8.114/ws"))
except Exception as e:
print("Exception:", e)
time.sleep(1) # Wait a bit before trying to reconnect

View File

@@ -1,22 +0,0 @@
```mermaid
classDiagram
Node <|-- SensorNode
Node <|-- EnqueteNode
Node: + String macAddress
Node: +getNodeID()
class SensorNode{
+ Float temp
+ Float Eco2
+ Float Tvoc
+ Float humi
- String query
+processSensorNodeData()
}
class EnqueteNode{
+ Int result
+ Int questionID
- String query
+ ProcessEnqueteNodeData()
}
```

View File

@@ -1,97 +0,0 @@
import asyncio
import websockets
import json
from class_SensorNode import SensorNode
from class_enqueteNode import EnqueteNode
from classes_data import dbLogin
sensorNodeArray = []
enqueteNodeArray = []
async def receive_data():
uri = "ws://145.92.8.114/ws"
try:
async with websockets.connect(uri) as websocket:
while True:
print("true")
data = await websocket.recv()
print(f"Received data: {data}")
processedData = json.loads(data)
macAdress = processedData['node']
if "Temp" in processedData:
type = 'sensor'
else:
type = 'enquete'
await getNodeInfo('sensor')
await getNodeInfo('enquete')
if macAdress in sensorNodeArray:
nodeID = await getNodeID(macAdress)
await SensorNode.processSensorNodeData(data, nodeID)
elif macAdress in enqueteNodeArray:
nodeID = await getNodeID(macAdress)
await EnqueteNode.processEnqueteNodeData(data, nodeID)
else:
await newNode(macAdress, type)
except websockets.ConnectionClosedError as e:
print("WebSocket connection closed:", e)
async def main():
await receive_data()
async def getNodeInfo(type):
print("getNodeINfo")
global sensorNodeArray
global enqueteNodeArray
nodeInfoArray = []
id = (type,)
mydb = dbLogin()
cursor = mydb.cursor()
cursor.execute("""SELECT MAC FROM Node WHERE Type = %s""", id)
nodeInfo = cursor.fetchall()
for tuples in nodeInfo:
for item in tuples:
nodeInfoArray.append(item)
cursor.close()
mydb.close()
if type == 'sensor':
sensorNodeArray = nodeInfoArray
return sensorNodeArray
elif type == 'enquete':
enqueteNodeArray = nodeInfoArray
return enqueteNodeArray
async def getNodeID(macAdress):
id = (macAdress,)
mydb = dbLogin()
cursor = mydb.cursor()
cursor.execute("""SELECT nodeID FROM Node WHERE MAC = %s""", id)
data = cursor.fetchall()
for tuples in data:
for item in tuples:
nodeID = item
return nodeID
async def newNode(mac, type):
id = (mac, type)
mydb = dbLogin()
cursor = mydb.cursor()
cursor.execute("INSERT INTO `Node` (MAC, Type) VALUES (%s, %s)", id)
print("new node assigned")
mydb.commit()
asyncio.run(main())

View File

@@ -1,26 +0,0 @@
import mysql.connector
def dbLogin():
mydb = mysql.connector.connect(
host="localhost",
user="root",
password="Dingleberries69!",
database="NodeData"
)
return mydb
class Node():
def __init__(self, macAdress):
self.macAdress = macAdress
self.id = None
def getNodeId(self):
id = (self.macAdress,)
mydb = dbLogin()
cursor = mydb.cursor()
cursor.execute("""SELECT nodeID FROM Node WHERE MAC = %s""", id)
data = cursor.fetchall()
for tuples in data:
for item in tuples:
self.id = item

View File

@@ -1,157 +0,0 @@
## The websocket -> database connection classes.
I have made several classes to make the database connection more clear and easyer to overlook.
This here is the main file where the data for each function is collected and ready to be sent to the database.
In the file : "data.py" the primary connections are made to the websocket and the data recieved is split off to see which type of node came back.
These types can be the "sensorNode"(the nodes that are located around the school) and the "enqueteNode"(a questionaire node which also collects data.).
```py
#Importing all different files from all the nodes which are on different pages
import asyncio
import websockets
import json
from class_SensorNode import SensorNode
from class_enqueteNode import EnqueteNode
from classes_data import dbLogin
#Making global variables
sensorNodeArray = []
enqueteNodeArray = []
```
These array's need to be global because of the several uses later in the code. These cannot be bound to a singular function.
The following function is meant to connect to the websocket and after this, process the gained Json data from the socket.
Once this is done get the info if the data comming from the websocket is from a "sensor-node" or a "questionairre-node".
once this information is gained, decide if this new node is a not yet existing connection with the database or if it was already inserted.
In the case that the node didn't connect to the database, a new node is made with this new MAC address.
These functions are put in different classes for a better overview of the whole project and a better understanding of what function is supposed to do what.
```py
#Connection making with the websocket
async def receive_data():
uri = "ws://145.92.8.114/ws"
try:
async with websockets.connect(uri) as websocket:
while True:
print("true")
data = await websocket.recv()
print(f"Received data: {data}")
processedData = json.loads(data)
macAdress = processedData['node']
#A function to see if the node is one of two types.
if "Temp" in processedData:
type = 'sensor'
else:
type = 'enquete'
await getNodeInfo('sensor')
await getNodeInfo('enquete')
#Get the node id and use it in functions seperate from this file.
if macAdress in sensorNodeArray:
nodeID = await getNodeID(macAdress)
await SensorNode.processSensorNodeData(data, nodeID)
elif macAdress in enqueteNodeArray:
nodeID = await getNodeID(macAdress)
await EnqueteNode.processEnqueteNodeData(data, nodeID)
else:
await newNode(macAdress, type)
# Error message if smth went wrong
except websockets.ConnectionClosedError as e:
print("WebSocket connection closed:", e)
#Wait for data to come in.
async def main():
await receive_data()
```
The following function is made to set the different node types appart, this is done by putting down these global variables.
These variables are doen this way because the python-scope could not reach inside some parts of the files.
A bit further a array is made to holde the node info. this is done so the information of the node can be sepperated and held.
After the array, a type tuple is made. (A tuple is a type of info which acts in a way like a array. For more info visit https://www.w3schools.com/python/python_tuples.asp)
Then another connection to the database is made to gather all existing mac-addresses.
Then a for-loop is made to see if the incomming MAC is existing, if this isn't the case, add it to the array.
After, if the given type from the previous function is a sensor- or questionaire-node it sends the infromation to the given function. These are located inside of the classes.
```py
#By python's scuffed we had to use global variables.
async def getNodeInfo(type):
print("getNodeINfo")
global sensorNodeArray
global enqueteNodeArray
#New array which is needed.
nodeInfoArray = []
# make a connection to the databasse, then gather all MAC-adresses from it.
id = (type,)
mydb = dbLogin()
cursor = mydb.cursor()
cursor.execute("""SELECT MAC FROM Node WHERE Type = %s""", id)
#Fetch all info(get all info)
nodeInfo = cursor.fetchall()
#Go along each tuple in nodeinfo and each item in tuple, append(item)
for tuples in nodeInfo:
for item in tuples:
nodeInfoArray.append(item)
cursor.close()
mydb.close()
#If the type is a sensor do this,
if type == 'sensor':
sensorNodeArray = nodeInfoArray
return sensorNodeArray
#Else, this if statement
elif type == 'enquete':
enqueteNodeArray = nodeInfoArray
return enqueteNodeArray
```
The next function acts as a node ID fetcher, it searches the database for information regarding the nodeID's.
Like the previous function, It adds the new ID id if this is not yet existent.
```py
async def getNodeID(macAdress):
id = (macAdress,)
mydb = dbLogin()
cursor = mydb.cursor()
cursor.execute("""SELECT nodeID FROM Node WHERE MAC = %s""", id)
data = cursor.fetchall()
#Again, all tuples in data, all items for all tuples, nodeID
for tuples in data:
for item in tuples:
nodeID = item
return nodeID
```
The following function will take the previous information and process it and push it to the database.
First the connection, then the query, then insert the data into the query and send it off.
```py
async def newNode(mac, type):
id = (mac, type)
mydb = dbLogin()
cursor = mydb.cursor()
#Insert will insert it into the given location in the database.
cursor.execute("INSERT INTO `Node` (MAC, Type) VALUES (%s, %s)", id)
#A simple print to show that the process has been executed succesfully.
print("new node assigned")
mydb.commit()
asyncio.run(main())
```
### code
https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/blob/main/server/web-data-connection/data.py

View File

@@ -1,79 +0,0 @@
## Questionaire class
This File and class are dedicated to storing/using data that is related to the questionaire. This class is primairly used as a gateway for pushing data and loading data.
By doing this a lot of space is saved on the main file and the readability wil increase.
By doing this, it also solves the issues with the very precise naming and the often similar types of names.
This way it ensures that there is no confusion on what the
purpose of each segement is.
First up on this page it imports different types of information, like the library's and the needed files and/or Node.
```py
#Importing different librarys.
import mysql.connector
import json
#Importing different classes.
from classes_data import Node
from classes_data import dbLogin
```
Here a Class is made as a child-class of the parent-class: "Node".
This clas first makes a private variable, this being: "__query".
This is done so no other outside sources can interfere with this query and potentially cause problems down the line.
After this the "__init__"function is called.
(In javascript this is known as the "constructor".
For further information visit https://www.w3schools.com/python/gloss_python_class_init.asp)
Because this class is a child class, we want to use some functionality from the parent class. That is where the "super" comes in. This makes it so the class uses the values and propperties of the parent class. (for more information visit https://www.w3schools.com/python/python_inheritance.asp)
The rest of the class contains a function in which the gatherd data
gets put into the database using the query and the gatherd data.
```py
#Node is between brackets to show that this class is a child class from the parent class "Node"
class EnqueteNode(Node):
#A private query to use later in a function.
__query = "INSERT INTO `Reply` (Result, Node_NodeID, Question_QuestionID) VALUES (%s, %s, %s)"
#use a super to get info from the parent class.
def __init__(self, macAdress, response, questionID):
super().__init__(macAdress)
self.response = response
self.questionID = questionID
```
The following function is meant to make a database connection, get the data from the websocket (this data will only be from the questionaire-node)
and send the gotten data to the database.
The function starts with the database connection and calls uppon the websocket data.
It then creates variables with the data to put it in an array.
This array then gets sorted and pushed thogether with the query to correctly sort it and push it to the database.
In case of an error, it also asks for the error and prints it for more clear fixing.
```py
#making a database connection to then load in the processed data.
async def processEnqueteNodeData(data, nodeID):
try:
mydb = dbLogin()
cursor = mydb.cursor()
#Getting the websocket data.
processedData = json.loads(data)
#Making variables of the different types of data.
EnqueteNode.questionID = (processedData['QuestionID'])
EnqueteNode.response = (processedData['Response'])
#An array with the data to push.
pushingDataArray = [(EnqueteNode.questionID, nodeID, EnqueteNode.response)]
#Push the data according to the query to the database.
for i in pushingDataArray:
cursor.execute(EnqueteNode.__query, i)
mydb.commit()
#print an error.
except mysql.connector.Error as err:
print("MySQL Error:", err)
finally:
cursor.close()
mydb.close()
```
### code
https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/blob/main/server/web-data-connection/enqueteNodeClass.py

View File

@@ -1,36 +0,0 @@
import mysql.connector
import json
from classes_data import Node
from classes_data import dbLogin
class EnqueteNode(Node):
__query = "INSERT INTO `Reply` (Result, Node_NodeID, Question_QuestionID) VALUES (%s, %s, %s)"
def __init__(self, macAdress, response, questionID):
super().__init__(macAdress)
self.response = response
self.questionID = questionID
async def processEnqueteNodeData(data, nodeID):
try:
mydb = dbLogin()
cursor = mydb.cursor()
processedData = json.loads(data)
EnqueteNode.questionID = (processedData['QuestionID'])
EnqueteNode.response = (processedData['Response'])
pushingDataArray = [(EnqueteNode.questionID, nodeID, EnqueteNode.response)]
for i in pushingDataArray:
print(EnqueteNode.__query, i)
cursor.execute(EnqueteNode.__query, i)
mydb.commit()
except mysql.connector.Error as err:
print("MySQL Error:", err)
finally:
cursor.close()
mydb.close()

View File

@@ -1,71 +0,0 @@
## General node file.
This File includes several main (verry important) components:
The Node parent class and the database log-in function.
The database funcion is used in almost every class and almost every function, so I put it here in a centeral location.
The reason it isn't in the main file is because the main file imports all classes and this function, but once the classes are called, this function can't be called up. So it needed to be in a file where all other files take information from and not put information in.
The file begings with importing a library that handles the database connection.
```py
#Importing a database library to connect to the database.
import mysql.connector
```
Here the database log-in function is made, this allows functions to call for it and make a connection.
```py
def dbLogin():
#This variable is used as a latch point to connect to the database with the correct log-in.
mydb = mysql.connector.connect(
host="localhost",
user="root",
password="**********",
database="NodeData"
)
return mydb
```
After the function, a central class is made to act as a parent class for every other class that has something to do with the nodes and the data.
The class has some interchangable variables and primairly asks the incomming data for its mac adress and looks for any similarities.
It primairly gives the main MAC-address over to the child-classes so these can be used.
This class might seem small, but is not to be underestimated.
At the start of every class the "__init__" function is called.
(In javascript this is known as the "constructor".
For further information visit https://www.w3schools.com/python/gloss_python_class_init.asp)
The parent node is able to be further evolved, but for now is able do always provide the node ID to all child-classes.
and a variable with the MAC-addresses.
```py
# A general class which acts as a database connector and a node identifier
class Node():
def __init__(self, macAdress):
self.macAdress = macAdress
self.id = None
```
The function below uses the infromation from the database to find the corresponding node ID from the gotten MAc-address.
It searches inside of the database and finds a match with the given MAC-address.
It first makes a connection with the database by calling the previous function, then it executes the given querry, and searches for the node ID corresponding with the inserted MAC-address.
After this search, it inserts that given id into the ID variable.
```py
def getNodeId(self):
id = (self.macAdress,)
#loging in
mydb = dbLogin()
#make a cursor.
cursor = mydb.cursor()
cursor.execute("""SELECT nodeID FROM Node WHERE MAC = %s""", id)
#get all the data
data = cursor.fetchall()
#make a for-loop to go along all the tuples in the data, and then all items in the tupe and those items get put into the ID
for tuples in data:
for item in tuples:
self.id = item
```
### code
https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/blob/main/server/web-data-connection/databaseGeneralClass.py

View File

@@ -1,41 +0,0 @@
import mysql.connector
import json
from classes_data import Node
from classes_data import dbLogin
class SensorNode(Node):
__query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)"
def __init__(self, macAdress, temp, humi, eCO2, TVOC):
super().__init__(macAdress)
self.temperature = temp
self.humidity = humi
self.eCO2 = eCO2
self.TVOC = TVOC
async def processSensorNodeData(data, nodeID):
try:
mydb = dbLogin()
cursor = mydb.cursor()
processedData = json.loads(data)
SensorNode.temperature = (processedData['Temp'])
SensorNode.humidity = (processedData['Humi'])
SensorNode.eCO2 = (processedData['eCO2'])
SensorNode.TVOC = (processedData['TVOC'])
pushingDataArray = [(nodeID, "Temp", SensorNode.temperature), (nodeID, "Humi", SensorNode.humidity), (nodeID, "eCO2", SensorNode.eCO2), (nodeID, "TVOC", SensorNode.TVOC)]
for i in pushingDataArray:
cursor.execute(SensorNode.__query, i)
mydb.commit()
except mysql.connector.Error as err:
print("MySQL Error:", err)
finally:
cursor.close()
mydb.close()

View File

@@ -1,80 +0,0 @@
## Sensor Node class
This class is made with a intention to store/transfer the data collected from the several node's that are going to be placed around school.
This is done to reduce confusion on what every function is meant to do.
This file imports the needed files/classes to function.
Aswell as the important librarys.I
```py
#Import library's
import mysql.connector
import json
#Import classes and functions from different files.
from classes_data import Node
from classes_data import dbLogin
```
The class then (being a child class) creates a private query.
This is done this way because no other files need this query so it is only logical to private this.
After this the "__init__"function is called.
(In javascript this is known as the "constructor".
For further information visit https://www.w3schools.com/python/gloss_python_class_init.asp)
The "__init__" here makes variables for all the data the node needs to collect and sends to the database.
After this a function to send the gotten data to the databse is made.
The function gets all the data, puts this in an array to then send it to the database according to the given query so it gets placed correctly.
```py
#A class to send data to the database. child class of "Node"
class SensorNode(Node):
#A private query only to be used in this class.
__query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)"
#All variables to be used.
def __init__(self, macAdress, temp, humi, eCO2, TVOC):
super().__init__(macAdress)
self.temperature = temp
self.humidity = humi
self.eCO2 = eCO2
self.TVOC = TVOC
```
The function starts with getting the database connection with the "dblogin" function and gains the the data from the websocket in the variable "processedData".
This data is then split up into different types, these are then inserted into an array.
The newly made array then gets pulled appart and the query is then combined with each segment, thereby succesfully inserting the data into it and then pushing it to the database following its guidline.
In case of a database error, a function is made to show said error.
This helps with identifying problems and potentially fixing it.
```py
#A function to connect to the database, grab the info which is given, and push this to the database.
async def processSensorNodeData(data, nodeID):
try:
mydb = dbLogin()
cursor = mydb.cursor()
processedData = json.loads(data)
#The variables to give to the database.
SensorNode.temperature = (processedData['Temp'])
SensorNode.humidity = (processedData['Humi'])
SensorNode.eCO2 = (processedData['eCO2'])
SensorNode.TVOC = (processedData['TVOC'])
#A array of the info to be given to the database in the correct format.
pushingDataArray = [(nodeID, "Temp", SensorNode.temperature), (nodeID, "Humi", SensorNode.humidity), (nodeID, "eCO2", SensorNode.eCO2), (nodeID, "TVOC", SensorNode.TVOC)]
#Go along all files of the array, and push it out to the database following the query.
for i in pushingDataArray:
cursor.execute(SensorNode.__query, i)
mydb.commit()
except mysql.connector.Error as err:
print("MySQL Error:", err)
finally:
cursor.close()
mydb.close()
```
### code
https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/blob/main/server/web-data-connection/sensorNodeClass.py

View File

@@ -80,18 +80,4 @@ Sam, tft scherm, rest api ombouwen om data in node tabel te douwen
Bram: Fritsing, documentation for code
Sietse: Database
Dano: Docuemtation for arduino
Sam:
26/03/2024
Sam: gebouw beheer vragen, Wall mounted enquete doos
Sietse: rest api encete vragen
Bram: expert voorbereigen, node data updaten
Dano: Documentatie schreiven,
27/03/2024
Bram: OOP python
Sam: new website
Sietse: enquete screen data sending to database and database cript better
Dano: documentation about oop arduino
Sam:

View File

@@ -1,47 +1,7 @@
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() {
if (connectedNodes[this.nodeId]) {
this.connected = true;
} else {
this.connected = false;
}
}
}
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 graph {
class liveGraph {
// Constructor to initialize the graph
constructor(id) {
super(id);
this.timeArray = [];
this.tempArray = [];
this.humiArray = [];
this.eco2Array = [];
@@ -100,12 +60,7 @@ class liveGraph extends graph {
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,46 +82,4 @@ class liveGraph extends graph {
this.eco2Array.push(eCO2 / 10);
this.tvocArray.push(TVOC / 10);
}
}
class graph {
// Constructor to initialize the graph
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",
},
]);
}
}
// Function to update the graph with new values got from updateData function
updateGraph(array1, array2, array3, array4) {
let time = new Date();
this.timeArray.push(new Date());
let update = {
x: [[this.timeArray]],
y: [array1, array2, array3, array4],
};
let olderTime = time.setMinutes(time.getMinutes() - 1);
let futureTime = time.setMinutes(time.getMinutes() + 1);
let minuteView = {
xaxis: {
type: "date",
range: [olderTime, futureTime],
},
};
}
}
}

12
web/graphClass.js Normal file
View File

@@ -0,0 +1,12 @@
class graphClass {
constructor(dataArray) {
this.dataArray = dataArray;
}
createGraph() {
// make the graphs
liveGraphs.forEach((graph) => {
graph.makeGraph();
});
}
}

0
web/graphPage.js Normal file
View File

View File

@@ -5,6 +5,10 @@ let liveGraphs = [];
let nodeArray = [];
let nodeDict = {};
// letiables
let intervalDelay = 5000;
let amountOfNodes = 3;
const socket = new WebSocket("ws://145.92.8.114/ws");
function openConnection() {
// Open connection
@@ -25,7 +29,6 @@ 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);
@@ -44,36 +47,24 @@ function openConnection() {
openConnection();
function handleIncomingData(data) {
if (!data.message) {
nodeEventHandler(data.node);
nodeNumber = nodeDict[data.node];
nodeAdressHandler(data.Node);
nodeNumber = nodeDict[data.Node];
temperature = data.Temp;
humidity = data.Humi;
CO2 = data.eCO2;
TVOC = data.TVOC;
updateNodeData(nodeNumber, temperature, humidity, CO2, TVOC);
}
}
function nodeEventHandler(node) {
function nodeAdressHandler(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));
console.log("Node " + node + " added to the liveGraphs array");
liveGraphs[node - 1].makeGraph();
}
//function for making the html elements for the following html code
function nodeData(data, node) {
let nodeData = document.createElement("div");
@@ -184,6 +175,20 @@ 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].updateGraph();
}
liveGraphs[0].updateData(temperature, humidity, eCO2, TVOC);
liveGraphs[0].updateGraph();
console.log(nodeDict[node]);
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();
});

View File

@@ -1,51 +0,0 @@
class GaugeGroup {
constructor(nodeId, Location, gaugesCount, maxGaugeValues, dataTypes) {
this.nodeId = nodeId;
this.gaugesCount = gaugesCount;
this.maxGaugeValues = maxGaugeValues; // Maximum value the gauge can display
this.dataTypes = dataTypes; // Array of data type names for each gauge
this.location = Location;
// Create a new div element
this.element = document.createElement("div");
this.element.className = "gaugeGroup";
// Set the HTML of the new div
this.element.innerHTML = `
<h2 class="groupTitle">${this.nodeId} - ${this.location}</h2>
<div class="Node">
<img src="arrow.png" class="arrowimg" onclick="toggleGraph('${this.nodeId}')">
${Array(this.gaugesCount).fill().map((_, i) => `
<div class="Sensorvalues">
<div id="gaugeContainer${this.nodeId}_${i+1}" class="gaugeContainer">
<img src="gauge.png" class="gaugeImage">
<div id="gaugeValue${this.nodeId}_${i+1}" class="gaugeValue"></div>
<div id="needle${this.nodeId}_${i+1}" class="needle"></div>
<div id="gaugeText${this.nodeId}_${i+1}" class="gaugeText">0</div>
<div class="gaugeCover"></div>
</div>
</div>
`).join('')}
</div>
<div class="plotly-container">
<div id="liveGraph${this.nodeId}" class="disabled" ></div>
</div>
`;
// Append the new div to the body
document.body.appendChild(this.element);
}
updateGauge(gaugeId, value) {
if (!this.maxGaugeValues || gaugeId - 1 < 0 || gaugeId - 1 >= this.maxGaugeValues.length) {
console.error('Invalid gaugeId or maxGaugeValues:', gaugeId, this.maxGaugeValues);
return;
}
const needle = document.getElementById(`needle${this.nodeId}_${gaugeId}`);
const gaugeText = document.getElementById(`gaugeText${this.nodeId}_${gaugeId}`);
const maxGaugeValue = this.maxGaugeValues[gaugeId - 1]; // Get the maximum value for this gauge
const rotationDegree = ((value / maxGaugeValue) * 180) - 90; // Convert value to degree (-90 to 90)
needle.style.transform = `rotate(${rotationDegree}deg)`;
gaugeText.textContent = `${this.dataTypes[gaugeId - 1]}: ${value}`; // Update the text with data type
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -1,163 +0,0 @@
class Graph {
constructor(id) {
this.id = "graph" + id;
this.timeArray = [];
this.tempArray = [];
this.humiArray = [];
this.eco2Array = [];
this.tvocArray = [];
}
createDiv() {
let div = document.createElement("div");
let graphDiv = document.createElement("div");
div.setAttribute("class", "graphBody");
graphDiv.setAttribute("id", this.id);
div.appendChild(graphDiv);
document.body.appendChild(div);
}
// Function to create a graph
makeGraph(line, lineColor, name) {
let lineArray;
switch (line) {
case "temp":
lineArray = this.tempArray;
break;
case "humi":
lineArray = this.humiArray;
break;
case "eco2":
lineArray = this.eco2Array;
break;
case "tvoc":
lineArray = this.tvocArray;
break;
default:
console.error("Invalid line");
}
Plotly.plot(this.id, [
{
x: this.timeArray,
y: lineArray,
mode: "lines",
line: { color: lineColor },
name: name,
},
]);
}
updateData(type, value, timestamp) {
// this.timeArray.push(timestamp);
switch (type) {
case "Temp":
this.tempArray.push(value);
break;
case "Humi":
this.humiArray.push(value);
break;
case "eCO2":
this.eco2Array.push(value / 10);
break;
case "TVOC":
this.tvocArray.push(value / 10);
break;
default:
console.error("Invalid type");
}
}
updateGraph() {
let update = {
x: [this.timeArray],
y: [this.tempArray, this.humiArray, this.eco2Array, this.tvocArray],
};
console.log(update);
Plotly.update(this.id, update);
}
}
class LiveGraph extends Graph {
// Constructor to initialize the graph
constructor(id) {
super(id);
this.tempArray = [];
this.humiArray = [];
this.eco2Array = [];
this.tvocArray = [];
this.cnt = 0;
}
// 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: [olderTime, futureTime],
},
};
Plotly.relayout(this.id, minuteView);
if (this.cnt === 10) clearInterval(interval);
}
// function to get the new data for graph
updateData(temperature, humidity, eCO2, TVOC) {
// Update the graph
this.tempArray.push(temperature);
this.humiArray.push(humidity);
this.eco2Array.push(eCO2 / 10);
this.tvocArray.push(TVOC / 10);
}
}
class DataProcessor {
constructor(data) {
this.data = data;
this.graph;
}
// You can add more filtering methods based on different criteria if needed
update(data) {
this.data = data;
console.log("Data updated");
console.log(data);
}
makeGraph() {
this.graph = new Graph(1);
this.graph.createDiv();
this.graph.makeGraph("temp", "red", "Temperature");
this.graph.makeGraph("humi", "blue", "Humidity");
this.graph.makeGraph("eco2", "green", "eCO2");
this.graph.makeGraph("tvoc", "black", "TVOC");
}
updateGraph() {
this.graph.timeArray = [];
this.graph.tempArray = [];
this.graph.humiArray = [];
this.graph.eco2Array = [];
this.graph.tvocArray = [];
for (let i = 0; i < this.data.length; i++) {
if (i % 4 == 0){
this.graph.timeArray.push(this.data[i].TimeStamp);
}
this.graph.updateData(this.data[i].Type, this.data[i].Value, this.data[i].TimeStamp);
console.log(this.data[i].Type, this.data[i].Value, this.data[i].TimeStamp);
}
this.graph.updateGraph();
}
}

View File

@@ -1,179 +0,0 @@
// Sample data - you can replace this with your actual dataset
const data = [];
processor = new DataProcessor();
let link;
nodeDataArray = {};
// Function to create checkbox with label
function createCheckBox(id, label) {
const checkbox = document.createElement("input");
checkbox.setAttribute("type", "checkbox");
checkbox.setAttribute("id", id);
checkbox.setAttribute("class", "checkbox");
const checkboxLabel = document.createElement("label");
checkboxLabel.setAttribute("for", id);
checkboxLabel.textContent = label;
return { checkbox, checkboxLabel };
}
fetch("http://145.92.8.114/getNodeInfo?macAdress=*")
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
nodeInputs(data);
})
function nodeInputs(JSONdata) {
select = document.getElementById('node-input');
console.log(JSONdata);
for (var i = 0; i < JSONdata.length; i++) {
console.log(JSONdata[i]);
console.log(option)
var node = JSONdata[i].NodeID;
var name = JSONdata[i].Name;
var location = JSONdata[i].Location;
nodeDataArray[node] = { name: name, location: location };
// Create new option element
var option = document.createElement('option');
// Set the value of the option
option.value = node;
// Set the text of the option
option.text = name;
// Add the option to the select
select.add(option);
}
}
// Create HTML input elements for user input
const container = document.createElement("div");
container.setAttribute("class", "container");
const dataTypesContainer = document.createElement("div");
dataTypesContainer.setAttribute("class", "data-types");
const temperatureCheckbox = createCheckBox("Temp", "Temperature");
const humidityCheckbox = createCheckBox("Humi", "Humidity");
const eco2Checkbox = createCheckBox("eCO2", "eCO2");
const tvocCheckbox = createCheckBox("TVOC", "TVOC");
dataTypesContainer.appendChild(temperatureCheckbox.checkbox);
dataTypesContainer.appendChild(temperatureCheckbox.checkboxLabel);
dataTypesContainer.appendChild(humidityCheckbox.checkbox);
dataTypesContainer.appendChild(humidityCheckbox.checkboxLabel);
dataTypesContainer.appendChild(eco2Checkbox.checkbox);
dataTypesContainer.appendChild(eco2Checkbox.checkboxLabel);
dataTypesContainer.appendChild(tvocCheckbox.checkbox);
dataTypesContainer.appendChild(tvocCheckbox.checkboxLabel);
container.appendChild(dataTypesContainer);
const filterButton = document.createElement("button");
filterButton.textContent = "Filter Data";
filterButton.setAttribute("class", "filter-button");
filterButton.addEventListener("click", () => {
const startDate = document.getElementById("start-date").value
const endDate = document.getElementById("end-date").value
const selectedNodes = document
.getElementById("node-input")
.value.split(",")
.map((node) => node.trim());
const selectedFields = [];
const checkboxes = [
temperatureCheckbox,
humidityCheckbox,
eco2Checkbox,
tvocCheckbox
];
checkboxes.forEach((checkbox) => {
if (checkbox.checkbox.checked) {
selectedFields.push(String(checkbox.checkbox.id));
}
});
let selectedFieldsString = selectedFields.map(String);
let formattedString = '(' + selectedFieldsString.map(item => `'${item}'`).join(', ') + ')';
const filteredData = [
startDate,
endDate,
selectedNodes,
formattedString
];
console.log(filteredData);
console.log(startDate, endDate, selectedNodes);
generateLink(startDate, endDate, selectedNodes, formattedString);
fetchData();
});
const dateFilter = document.createElement("div");
dateFilter.setAttribute("class", "date-filter");
const startDateInput = document.createElement("input");
startDateInput.setAttribute("type", "datetime-local");
startDateInput.setAttribute("id", "start-date");
startDateInput.setAttribute("class", "input-field");
const endDateInput = document.createElement("input");
endDateInput.setAttribute("type", "datetime-local");
endDateInput.setAttribute("id", "end-date");
endDateInput.setAttribute("class", "input-field");
dateFilter.appendChild(startDateInput);
dateFilter.appendChild(endDateInput);
container.appendChild(dateFilter);
const nodeFilter = document.createElement("div");
nodeFilter.setAttribute("class", "node-filter");
const nodeInput = document.createElement("select");
nodeInput.setAttribute("type", "select");
nodeInput.setAttribute("placeholder", "Enter Node Name (* for all)");
nodeInput.setAttribute("id", "node-input");
nodeFilter.appendChild(nodeInput);
container.appendChild(nodeFilter);
container.appendChild(filterButton);
document.body.appendChild(container);
// Function to get the link for the get request
function generateLink(dateStart, dateEnd, node, dataTypes) {
const baseUrl = 'http://145.92.8.114/getMeasurements';
const formattedDateStart = new Date(dateStart).toISOString().replace('T', '%20');
const formattedDateEnd = new Date(dateEnd).toISOString().replace('T', '%20');
link = `${baseUrl}?dateStart=${formattedDateStart}&dateEnd=${formattedDateEnd}&node=${node}&dataType=${dataTypes}`;
console.log(link);
}
processor.makeGraph();
// Get request to fetch data from the server
function fetchData() {
fetch(link)
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((data) => {
processor.update(data);
processor.updateGraph();
})
.catch((error) => {
console.error("Error fetching data:", error);
});
}

View File

@@ -1,35 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles/graph-styles.css">
<title>Graphs</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js" charset="utf-8"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
</head>
<nav class="navbar">
<ul class="navbar-nav">
<li class="nav-item">
<a href="index.html" class="nav-link">Dashboard</a>
</li>
<li class="nav-item">
<a href="graphs.html" class="nav-link">Graphs</a>
</li>
<li class="nav-item">
<a href="questions-dashboard.html" class="nav-link">Questions</a>
</li>
</ul>
</nav>
<body>
<script src="graph-classes.js"></script>
<script src="graph-main.js"></script>
</body>
</html>

View File

@@ -1,53 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles/dashboard-styles.css">
<title>Gauges</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js" charset="utf-8"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
rel="stylesheet">
</head>
<nav class="navbar">
<ul class="navbar-nav">
<li class="nav-item">
<a href="index.html" class="nav-link">Dashboard</a>
</li>
<li class="nav-item">
<a href="graphs.html" class="nav-link">Graphs</a>
</li>
<li class="nav-item">
<a href="questions-dashboard.html" class="nav-link">Questions</a>
</li>
<li class="nav-item">
<button class="nav-button" onclick="settings()">Change node names ⌄</button>
</li>
</ul>
</nav>
<div id="editNode">
<p id="text">Status updating</p>
<select id="mySelect"></select>
<textarea maxlength="44" id="inputName"></textarea>
<textarea maxlength="44" id="inputLocation"></textarea>
<button id="button" onclick="changeText()">Change Information</button>
</div>
<body>
<script src="GaugGroup.js"></script>
<script src="graph-classes.js"></script>
<script src="main.js"></script>
<script src="text.js"></script>
<script src="liveGraph.js"></script>
</body>
</html>

View File

@@ -1,79 +0,0 @@
class liveGraph {
// Constructor to initialize the graph
constructor(id) {
this.timeArray = [];
this.tempArray = [];
this.humiArray = [];
this.eco2Array = [];
this.tvocArray = [];
this.cnt = 0;
this.nodeId = "liveGraph" + id;
}
// Fuction to create a graph
makeGraph() {
// Create a new line for temperature
Plotly.plot(this.nodeId, [
{
x: this.timeArray, // Use timeArray as x values
y: this.tempArray,
mode: "lines",
line: { color: "#FF0000" },
name: "Temperature",
},
]);
// Create a new line for humidity
Plotly.plot(this.nodeId, [
{
x: this.timeArray, // Use timeArray as x values
y: this.humiArray,
mode: "lines",
line: { color: "#80CAF6" },
name: "Humidity",
},
]);
// Create a new line for eCO2
Plotly.plot(this.nodeId, [
{
x: this.timeArray, // Use timeArray as x values
y: this.eco2Array,
mode: "lines",
line: { color: "#FFA500" },
name: "eCO2 / 10",
},
]);
// Create a new line for TVOC
Plotly.plot(this.nodeId, [
{
x: this.timeArray, // Use timeArray as x values
y: this.tvocArray,
mode: "lines",
line: { color: "#000000" },
name: "TVOC / 10",
},
]);
}
// Function to update the graph with new values got from updateData function
updateGraph(temperature, humidity, eCO2, TVOC) {
// Update the graph
this.tempArray.push(temperature);
this.humiArray.push(humidity);
this.eco2Array.push(eCO2 / 10);
this.tvocArray.push(TVOC / 10);
let time = new Date();
this.timeArray.push(new Date());
let olderTime = time.setMinutes(time.getMinutes() - 1);
let futureTime = time.setMinutes(time.getMinutes() + 1);
let minuteView = {
xaxis: {
type: "date",
range: [olderTime, futureTime],
},
};
Plotly.relayout(this.nodeId, minuteView);
if (this.cnt === 10) clearInterval(interval);
}
// function to get the new data for graph
}

View File

@@ -1,144 +0,0 @@
// Description: Main JavaScript file for the web application.
// arrays and stuff
const sensorData = {};
let liveGraphs = [];
let nodeArray = [];
let nodeDict = {};
let graphArray = [];
// letiables
let intervalDelay = 5000;
let amountOfNodes = 3;
const socket = new WebSocket("ws://145.92.8.114/ws");
function openConnection() {
// Open connection
socket.addEventListener("open", (event) => {
console.log("Connected to the WebSocket server");
});
// Error handling
socket.addEventListener('error', (event) => {
console.error('WebSocket error:', event);
// Attempt to reconnect
setTimeout(openConnection, 1000); // Retry after 1 second
});
// Message handling
socket.addEventListener("message", (event) => {
try {
const jsonData = JSON.parse(event.data);
// Use the parsed JSON data as needed
handleIncomingData(jsonData);
} catch (error) {
console.error("Error parsing JSON:", error);
}
});
// Close handling
socket.addEventListener('close', (event) => {
console.log('Connection closed');
// Attempt to reconnect
setTimeout(openConnection, 1000); // Retry after 1 second
});
console.log("Connected to the WebSocket server");
}
openConnection();
async function handleIncomingData(data) {
if (!data.node || !data.Temp || !data.Humi || !data.eCO2 || !data.TVOC) {
console.error('Invalid data received:', data);
return;
}
await nodeAdressHandler(data.node, Object.keys(data).filter(key => key !== 'node'));
let nodeName = nodeDict[data.node].name;
let temperature = data.Temp;
let humidity = data.Humi;
let CO2 = data.eCO2;
let TVOC = data.TVOC;
// Update the gauges with the new data
if (sensorData[data.node]) {
sensorData[data.node].updateGauge(1, temperature);
sensorData[data.node].updateGauge(2, humidity);
sensorData[data.node].updateGauge(3, CO2);
sensorData[data.node].updateGauge(4, TVOC);
sensorData[data.node].graph.updateGraph(temperature, humidity, CO2, TVOC);
} else {
console.error('No sensor data for node:', nodeName);
}
}
async function nodeAdressHandler(node, dataTypes) {
let nodeInfo = await getNodeInfo(node);
if (!nodeInfo) {
console.error('No node info found for node:', node);
return;
}
let nodeName = nodeInfo.name;
let nodeLocation = nodeInfo.location;
if (!nodeArray.includes(node)) {
nodeArray.push(node);
nodeDict[node] = {name: nodeName, location: nodeLocation};
let maxGaugeValues = dataTypes.map(dataType => {
switch (dataType) {
case 'Temp': return 50;
case 'Humi': return 100;
case 'eCO2': return 3000;
case 'TVOC': return 2200;
default: return 100;
}
});
let gaugeGroup = new GaugeGroup(nodeName, nodeLocation, dataTypes.length, maxGaugeValues, dataTypes);
sensorData[node] = gaugeGroup;
gaugeGroup.graph = new liveGraph(nodeName);
sensorData[node] = gaugeGroup;
gaugeGroup.graph.makeGraph();
sensorData[node] = gaugeGroup;
}
}
function getNodeInfo(node){
return fetch("http://145.92.8.114/getNodeInfo?macAdress=" + node)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.length == 0) {
throw new Error('No data returned for node: ' + node);
}
return {
name: data[0].Name,
location: data[0].Location // Assuming the server returns a Location property
};
})
}
// create a function to enable and disable the graph using .disabled using the id of the graph
function toggleGraph(nodeId) {
let graph = document.querySelector('#liveGraph' + nodeId);
if (graph) {
if (graph.classList.contains('disabled')) {
graph.classList.remove('disabled');
} else {
graph.classList.add('disabled');
}
} else {
console.error('No element found with id: liveGraph' + nodeId);
}
}

View File

@@ -1,27 +0,0 @@
class ChartConfigClass{
constructor(data, text){
this.data = data
this.text = text
}
get chartConfig() {
return{
type: 'pie',
data: this.data,
options: {
responsive: true,
legend: {
position: 'top',
},
title: {
display: true,
text: this.text
},
animation: {
animateScale: true,
animateRotate: true
}
}
}
}
}

View File

@@ -1,27 +0,0 @@
class QuestionCreationClass {
constructor(data, label) {
this.data = data;
this.label = label;
}
get questionData() {
return {
labels: this.label,
datasets: [{
label: 'Responses',
data: this.data,
backgroundColor: [
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(255, 99, 132, 0.2)'
],
borderColor: [
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(255, 99, 132, 1)'
],
borderWidth: 1
}]
};
}
}

View File

@@ -1,56 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles/questions-dashboard-styles.css">
<title>Graphs</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<nav class="navbar">
<ul class="navbar-nav">
<li class="nav-item">
<a href="index.html" class="nav-link">Dashboard</a>
</li>
<li class="nav-item">
<a href="graphs.html" class="nav-link">Graphs</a>
</li>
<li class="nav-item">
<a href="questions-dashboard.html" class="nav-link">Questions</a>
</ul>
</nav>
<body>
<div class="chart-container">
<div class="chart">
<h2>Question 1: How clean are the toilets?</h2>
<canvas id="chart1"></canvas>
</div>
<div class="chart">
<h2>Question 2: How clean is the study area?</h2>
<canvas id="chart2"></canvas>
</div>
<div class="chart">
<h2>Question 3: What do you think of the temperature in the study area?</h2>
<canvas id="chart3"></canvas>
</div>
<div class="chart">
<h2>Question 4: How crowded would you say the study area is?</h2>
<canvas id="chart4"></canvas>
</div>
<div class="chart">
<h2>Question 5: Is there enough help available on the 5th floor?</h2>
<canvas id="chart5"></canvas>
</div>
<!-- Add more questions and canvas elements as needed -->
</div>
<script src="questions-chart-class.js"></script>
<script src="questions-creation-class.js"></script>
<script src="questions-main.js"></script>
</body>
</html>

View File

@@ -1,92 +0,0 @@
//For now create dummy data to show on the website.
let awa;
data();
async function data() {
fetch("http://145.92.8.114/getQuestionData")
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log(data);
// Initialize an array to hold the counts for each question
let questionCounts = [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]];
// Iterate over the data
for (let item of data) {
// Increment the count for the appropriate question and result
questionCounts[item.Question_QuestionID - 1][item.Result]++;
}
// Log the counts for each question
for (let i = 0; i < questionCounts.length; i++) {
console.log(`Question ${i + 1} counts: ${questionCounts[i]}`);
}
// Update the dummydata arrays
dummydata1 = questionCounts[0];
dummydata2 = questionCounts[1];
dummydata3 = questionCounts[2];
dummydata4 = questionCounts[3];
dummydata5 = questionCounts[4];
graph();
})
}
// for each(Result == 0) in
async function graph() {
let questionOptionsDummy1 = ['clean','fine', 'disgusting'];
let questionOptionsDummy2 = ['clean', 'normal', 'disgusting'];
let questionOptionsDummy3 = ['hot', 'perfect', 'cold'];
let questionOptionsDummy4 = ['not at all', 'its fine', 'really crowded', ];
let questionOptionsDummy5 = ['no','decently', 'yes'];
//make arrays to store data.
let chartConfigArray = [];
let textArray = [];
let questionArray = [];
let questionOptionsDummy = [];
let dummydata = [];
//Go along the array's to fetch data, and push this in a class.
for (let i = 0; i < 5; i++) {
dummydata.push(dummydata1, dummydata2, dummydata3, dummydata4, dummydata5);
questionOptionsDummy.push(questionOptionsDummy1, questionOptionsDummy2, questionOptionsDummy3, questionOptionsDummy4, questionOptionsDummy5);
questionArray.push(new QuestionCreationClass(dummydata[i], questionOptionsDummy[i]));
}
//Go allong another array to also give the class that creates the charts the data collected by the previous array.
for (let i = 0; i < 5; i++){
textArray.push('Question 1 Responses', 'Question 2 Responses', 'Question 3 Responses', 'Question 4 Responses', 'Question 5 Responses');
chartConfigArray.push(new ChartConfigClass(questionArray[i].questionData, textArray[i]));
}
// Create the charts
const ctx1 = document.getElementById('chart1').getContext('2d');
const myChart1 = new Chart(ctx1, chartConfigArray[0].chartConfig);
const ctx2 = document.getElementById('chart2').getContext('2d');
const myChart2 = new Chart(ctx2, chartConfigArray[1].chartConfig);
const ctx3 = document.getElementById('chart3').getContext('2d');
const myChart3 = new Chart(ctx3, chartConfigArray[2].chartConfig);
const ctx4 = document.getElementById('chart4').getContext('2d');
const myChart4 = new Chart(ctx4, chartConfigArray[3].chartConfig);
const ctx5 = document.getElementById('chart5').getContext('2d');
const myChart5 = new Chart(ctx5, chartConfigArray[4].chartConfig);
}

View File

@@ -1,249 +0,0 @@
* {
box-sizing: border-box;
font-family: "Roboto", sans-serif;
}
/*
.editNodeInformation{
margin-bottom: 60%;
display: flex;
justify-content: left;
} */
.nav-button {
background-color: #333;
color: #fff;
border: none;
cursor: pointer;
font-size: 17px;
}
#editNode {
display: flex;
justify-content: center;
flex-direction: column;
margin-top: 1%;
width: 98vw;
height: 20vh;
justify-content: center;
align-items: center;
background-color: #333;
color: #fff;
padding: 10px;
border-radius: 50px;
border: 2px solid #333;
clear: both;
margin-bottom: 2vh;
/* position: relative; */
/* float: top; */
}
#mySelect {
margin-left: 45vh;
}
#text {
font-size: 20px;
margin-left: 25vh;
}
#inputName {
margin-left: 5vh;
margin-right: 5vh;
}
#inputLocation {
margin-right: 5vh;
}
body {
display: flex;
justify-content: center;
margin: 0;
margin-top: 8vh;
background-color: #f0f0f0;
flex-direction: column;
background-color: #afafaf;
align-items: center;
}
.gaugeGroup {
margin-top: 2vh;
width: 98vw;
height: auto;
display: flex;
flex-direction: column; /* Keep as column */
justify-content: flex-start;
background-color: #333;
color: #fff;
padding: 10px;
border-radius: 50px;
border: 2px solid #333;
clear: both;
margin-bottom: 10px;
position: relative;
}
.groupTitle {
width: 100%;
text-align: center;
font-size: 24px;
}
.Node {
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
height: 100%;
}
.Sensorvalues {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
flex-wrap: wrap;
width: 15%;
height: 110%;
background-color: #ddd;
color: #333;
padding: 10px;
margin: 10px;
border-radius: 10px;
text-align: center;
position: relative;
padding-top: 3vh; /* Increase bottom padding */
}
.gaugeContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 80%; /* Increase the height from 70% to 80% */
position: relative;
overflow: visible;
height: 5vh;
margin-bottom: 1vh;
}
.gaugeImage {
width: 100%;
height: auto;
max-height: 120%;
object-fit: contain;
position: absolute; /* Make the image position absolute */
top: 0;
left: 0;
z-index: 1; /* Make the image display below the needle */
bottom: 0;
}
.gaugeValue,
.gaugeText {
width: 100%;
text-align: center;
font-size: 24px;
z-index: 2;
}
.gaugeText {
width: 100%;
text-align: center;
font-size: 1.4vw;
z-index: 2;
position: absolute;
top: -1.4vw; /* Adjust this value to move the text further down */
}
.arrowimg {
width: 3vh;
height: auto;
max-height: 100%;
object-fit: contain;
position: absolute;
top: 0.5vw;
right: 1.2vw;
z-index: 2;
}
.valueContainer {
display: flex;
justify-content: center;
margin-top: 10px;
}
#valueText {
font-size: 20px;
}
.needle {
position: absolute;
bottom: -10%; /* Lower the needle to the bottom */
left: 50%;
width: 2px;
height: 100%;
background-color: black;
transform-origin: bottom;
z-index: 3; /* Make the needle display above the image */
transition: transform 0.1s ease-out;
}
.contentContainer {
display: flex;
flex-direction: row; /* Layout children side by side */
width: 100%;
height: 100%;
}
.navbar {
background-color: #333;
height: 60px;
display: flex;
align-items: center;
padding: 0 20px;
position: fixed; /* Fix the navbar at the top of the page */
top: 0; /* Position it at the very top */
width: 100%; /* Make it span the full width of the page */
z-index: 1000; /* Make sure it's above all other elements */
}
.navbar-nav {
list-style: none;
display: flex;
align-items: center;
justify-content: center; /* Center the links horizontally */
height: 100%;
width: 100%; /* Make it span the full width of the navbar */
}
.nav-item {
margin-right: 20px;
}
.nav-link {
color: #fff;
text-decoration: none;
font-size: 18px;
}
.plotly-container {
width: 100%;
float: bottom;
padding: 1vw;
align-items: center;
justify-content: center;
display: flex;
}
.js-plotly-plot {
width: 90%;
height: 100%;
}
.disabled {
opacity: 0;
height: 0;
}

View File

@@ -1,128 +0,0 @@
* {
box-sizing: border-box;
font-family: "Roboto", sans-serif;
}
body {
padding-top: 5vw;
display: flex;
flex-direction: column;
background-color: #afafaf;
margin: 0;
}
.navbar {
background-color: #333;
height: 60px;
display: flex;
align-items: center;
padding: 0 20px;
position: fixed; /* Fix the navbar at the top of the page */
top: 0; /* Position it at the very top */
width: 100%; /* Make it span the full width of the page */
z-index: 1000; /* Make sure it's above all other elements */
}
.navbar-nav {
list-style: none;
display: flex;
align-items: center;
justify-content: center; /* Center the links horizontally */
height: 100%;
width: 100%; /* Make it span the full width of the navbar */
}
.nav-item {
margin-right: 20px;
}
.nav-link {
color: #fff;
text-decoration: none;
font-size: 18px;
}
.container {
display: flex;
flex-direction: row;
align-items: center;
align-self: center;
border: 3px solid #ccc;
border-radius: 10px;
margin: 20px;
padding: 20px;
width: 95%;
box-sizing: border-box;
}
.data-types {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
margin-right: 60px;
margin-bottom: 10px;
font-size: 0.8em;
padding: 8px;
border-radius: 5px;
background-color: #f0f0f0;
}
.filters {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin-bottom: 10px;
}
.date-filter {
flex: 1;
text-align: center;
margin-right: 60px;
font-size: 1.2em; /* Increase font size for a bigger appearance */
padding: 8px;
border-radius: 5px; /* Add border radius for a rounded look */
background-color: #f0f0f0; /* Light background color for contrast */
}
.node-filter {
flex: 1;
margin: auto;
text-align: center;
margin-right: 60px;
align-content: center;
font-size: 1.2em; /* Increase font size for a bigger appearance */
padding: 8px;
border-radius: 5px; /* Add border radius for a rounded look */
background-color: #f0f0f0; /* Light background color for contrast */
}
.filter-button {
width: 10%;
background-color: #007bff;
color: white;
padding: 10px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.js-plotly-plot {
width: 100%;
height: 100%;
align-self: center center;
}
/* Additional styling as needed */
.graphBody {
display: flex;
padding: 10px;
border: 3px solid #ccc;
border-radius: 10px;
justify-content: center;
width: 95%;
height: 100%;
align-content: center;
align-self: center;
}

View File

@@ -1,71 +0,0 @@
* {
box-sizing: border-box;
font-family: "Roboto", sans-serif;
}
body {
flex-direction: row;
padding-top: 5vw;
display: flex;
background-color: #dbd7d7;
margin: 0;
}
.navbar {
background-color: #3d898c;
height: 60px;
display: flex;
align-items: center ;
padding: 0 20px;
position: fixed; /* Fix the navbar at the top of the page */
top: 0; /* Position it at the very top */
width: 100%; /* Make it span the full width of the page */
z-index: 1000; /* Make sure it's above all other elements */
}
.navbar-nav {
list-style: none;
display: flex;
align-items: center;
justify-content: center; /* Center the links horizontally */
height: 100%;
width: 100%; /* Make it span the full width of the navbar */
}
.nav-item {
margin-right: 20px;
}
.nav-link {
color: #fff;
text-decoration: none;
font-size: 18px;
}
#data-container {
padding: 20px;
background-color: #f5f5f5;
border: 1px solid #ccc;
}
.chart-container {
justify-content: center;
align-self: start;
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.chart {
width: 30%;
margin: 20px;
padding: 20px;
background-color: #dbd7d7;
border: 3px solid #000;
border-radius: 30px;
}
canvas {
margin-bottom: 20px;
}

View File

@@ -1,202 +0,0 @@
* {
box-sizing: border-box;
font-family: "Roboto", sans-serif;
}
body {
display: flex;
justify-content: center;
margin: 0;
margin-top: 8vh;
background-color: #f0f0f0;
flex-direction: column;
background-color: #afafaf;
align-items: center;
}
.gaugeGroup {
width: 98vw;
height: auto;
display: flex;
flex-direction: column; /* Keep as column */
justify-content: flex-start;
background-color: #333;
color: #fff;
padding: 10px;
border-radius: 50px;
border: 2px solid #333;
clear: both;
margin-bottom: 10px;
position: relative;
}
.groupTitle {
width: 100%;
text-align: center;
font-size: 24px;
}
.Node {
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
height: 100%;
}
.Sensorvalues {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
flex-wrap: wrap;
width: 15%;
height: 110%;
background-color: #ddd;
color: #333;
padding: 10px;
margin: 10px;
border-radius: 10px;
text-align: center;
position: relative;
padding-top: 3vh; /* Increase bottom padding */
}
.gaugeContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 80%; /* Increase the height from 70% to 80% */
position: relative;
overflow: visible;
height: 5vh;
margin-bottom: 1vh;
}
.gaugeImage {
width: 100%;
height: auto;
max-height: 120%;
object-fit: contain;
position: absolute; /* Make the image position absolute */
top: 0;
left: 0;
z-index: 1; /* Make the image display below the needle */
bottom: 0;
}
.gaugeValue, .gaugeText {
width: 100%;
text-align: center;
font-size: 24px;
z-index: 2;
}
.gaugeText {
width: 100%;
text-align: center;
font-size: 1.4vw;
z-index: 2;
position: absolute;
top: -1.4vw; /* Adjust this value to move the text further down */
}
.arrowimg {
width: 3vh;
height: auto;
max-height: 100%;
object-fit: contain;
position: absolute;
top: 0.5vw;
right: 1.2vw;
z-index: 2;
}
.valueContainer {
display: flex;
justify-content: center;
margin-top: 10px;
}
#valueText {
font-size: 20px;
}
.needle {
position: absolute;
bottom: -10%; /* Lower the needle to the bottom */
left: 50%;
width: 2px;
height: 100%;
background-color: black;
transform-origin: bottom;
z-index: 3; /* Make the needle display above the image */
transition: transform 0.1s ease-out;
}
.contentContainer {
display: flex;
flex-direction: row; /* Layout children side by side */
width: 100%;
height: 100%;
}
.navbar {
background-color: #333;
height: 60px;
display: flex;
align-items: center;
padding: 0 20px;
position: fixed; /* Fix the navbar at the top of the page */
top: 0; /* Position it at the very top */
width: 100%; /* Make it span the full width of the page */
z-index: 1000; /* Make sure it's above all other elements */
}
.navbar-nav {
list-style: none;
display: flex;
align-items: center;
justify-content: center; /* Center the links horizontally */
height: 100%;
width: 100%; /* Make it span the full width of the navbar */
}
.nav-item {
margin-right: 20px;
}
.nav-link {
color: #fff;
text-decoration: none;
font-size: 18px;
}
.plotly-container {
width: 100%;
float: bottom;
padding: 1vw;
align-items: center;
justify-content: center;
display: flex;
}
.js-plotly-plot {
width: 90%;
height: 100%;
}
.disabled {
opacity: 0;
height: 0;
}

View File

@@ -1,25 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="settings" onclick="settings()">Settings</button>
<div id="editNode">
<p id="text">Status updating</p>
<select id="mySelect"></select>
<textarea id="inputName"></textarea>
<textarea id="inputLocation"></textarea>
<button id="button" onclick="changeText()">Change Information</button>
</div>
</body>
<header>
<script src="text.js"></script>
</header>
</html>

View File

@@ -1,96 +0,0 @@
apiGetAllNode = "http://145.92.8.114/getNodeInfo?macAdress=*"
nodeDataArray = {};
var updateNode = document.getElementById('editNode');
var locationInput = document.getElementById("inputLocation");
var nameInput = document.getElementById("inputName");
var select = document.getElementById('mySelect');
document.getElementById("inputName").placeholder = "Type new name here..";
document.getElementById("inputLocation").placeholder = "Type new location here..";
updateNode.style.display = "none";
function settings() {
if (updateNode.style.display === "none") {
updateNode.style.display = "block";
locationInput.value = "";
nameInput.value = "";
getInfoNode();
} else {
updateNode.style.display = "none";
}
}
function getInfoNode() {
fetch(apiGetAllNode)
.then(response => {
if (!response.ok) {
document.getElementById('text').innerHTML = "Error: Network response was not ok";
throw new Error('Network response was not ok');
}
document.getElementById('text').innerHTML = "Fetching data";
return response.json();
})
.then(data => {
document.getElementById('text').innerHTML = "Data fetched";
handleData(data);
})
}
function handleData(JSONdata) {
var i, L = select.options.length - 1;
for (i = L; i >= 0; i--) {
select.remove(i);
}
for (var i = 0; i < JSONdata.length; i++) {
var node = JSONdata[i].NodeID;
var name = JSONdata[i].Name;
var location = JSONdata[i].Location;
nodeDataArray[node] = { name: name, location: location };
// Create new option element
var option = document.createElement('option');
// Set the value of the option
option.value = node;
// Set the text of the option
option.text = name;
// Add the option to the select
select.add(option);
}
}
function changeText() {
var nodeName = encodeURIComponent(document.getElementById('inputName').value);
var nodeLocation = encodeURIComponent(document.getElementById('inputLocation').value);
updateNodeInfo(select.value, nodeName, nodeLocation);
var text = document.getElementById('text');
text.innerHTML = "Changes made"
getInfoNode();
}
function updateNodeInfo(node, newNodeName, newNodeLocation) {
apiUrl = "http://145.92.8.114/updateData?node=" + node + "&name=" + newNodeName + "&location=" + newNodeLocation;
fetch(apiUrl)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.catch(error => {
console.error('Error:', error);
});
}

View File

@@ -72,6 +72,8 @@ p1 {
}
.nodeData {
display: flex;
justify-content: left;