diff --git a/arduino/Screen code/Screen-code-final/.theia/launch.json b/arduino/Screen code/Screen-code-final/.theia/launch.json new file mode 100644 index 0000000..8b8a0d4 --- /dev/null +++ b/arduino/Screen code/Screen-code-final/.theia/launch.json @@ -0,0 +1,7 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + "version": "0.2.0", + "configurations": [ + ] +} diff --git a/arduino/Screen code/Screen-code-final/Screen-code-final.ino b/arduino/Screen code/Screen-code-final/Screen-code-final.ino new file mode 100644 index 0000000..0f965ba --- /dev/null +++ b/arduino/Screen code/Screen-code-final/Screen-code-final.ino @@ -0,0 +1,115 @@ +#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", "vbK9gbDBIB"); + 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); + } + + 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"); +} + diff --git a/arduino/Screen code/Screen-code-full/displayText.cpp b/arduino/Screen code/Screen-code-final/displayText.cpp similarity index 100% rename from arduino/Screen code/Screen-code-full/displayText.cpp rename to arduino/Screen code/Screen-code-final/displayText.cpp diff --git a/arduino/Screen code/Screen-code-full/displayText.h b/arduino/Screen code/Screen-code-final/displayText.h similarity index 100% rename from arduino/Screen code/Screen-code-full/displayText.h rename to arduino/Screen code/Screen-code-final/displayText.h diff --git a/arduino/Screen code/Screen-code-final/headerFile.h b/arduino/Screen code/Screen-code-final/headerFile.h new file mode 100644 index 0000000..4f1152e --- /dev/null +++ b/arduino/Screen code/Screen-code-final/headerFile.h @@ -0,0 +1,22 @@ +//screen stuff +#include +#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 +#include +#include +#define USE_SERIAL Serial + +WiFiMulti WiFiMulti; +WebSocketsClient webSocket; +bool initialized = true; \ No newline at end of file diff --git a/arduino/Screen code/Adafruit_ST7796S_kbv-master.zip b/arduino/Screen code/screen-code-class/Adafruit_ST7796S_kbv-master.zip similarity index 100% rename from arduino/Screen code/Adafruit_ST7796S_kbv-master.zip rename to arduino/Screen code/screen-code-class/Adafruit_ST7796S_kbv-master.zip diff --git a/arduino/Screen code/screen-code-class/displayText.cpp b/arduino/Screen code/screen-code-class/displayText.cpp new file mode 100644 index 0000000..88dfd70 --- /dev/null +++ b/arduino/Screen code/screen-code-class/displayText.cpp @@ -0,0 +1,106 @@ +#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 +} \ No newline at end of file diff --git a/arduino/Screen code/screen-code-class/displayText.h b/arduino/Screen code/screen-code-class/displayText.h new file mode 100644 index 0000000..9888068 --- /dev/null +++ b/arduino/Screen code/screen-code-class/displayText.h @@ -0,0 +1,19 @@ +#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 \ No newline at end of file diff --git a/arduino/Screen code/Screen-code-full/Screen-code-full.ino b/arduino/Screen code/screen-code-class/example.ino similarity index 100% rename from arduino/Screen code/Screen-code-full/Screen-code-full.ino rename to arduino/Screen code/screen-code-class/example.ino diff --git a/arduino/Screen code/Screen-code-full/headerFile.h b/arduino/Screen code/screen-code-class/headerFile.h similarity index 100% rename from arduino/Screen code/Screen-code-full/headerFile.h rename to arduino/Screen code/screen-code-class/headerFile.h diff --git a/docs/brainstorm/Questions Enquete.md b/docs/brainstorm/Questions Enquete.md index 5fc8bc8..e32bd27 100644 --- a/docs/brainstorm/Questions Enquete.md +++ b/docs/brainstorm/Questions Enquete.md @@ -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? (no, decently, yes). +- Is there enough help available on the 5th floor? (no, decently, yes). ### Feedback questions diff --git a/docs/node-documentation/node-design.md b/docs/node-documentation/node-design.md index 01fba8e..2f2cd56 100644 --- a/docs/node-documentation/node-design.md +++ b/docs/node-documentation/node-design.md @@ -61,4 +61,29 @@ graph LR A[Design Sketch] -->|Usertest| B(Fusion360 design) B -->|Not fitting| C[New fusion360 design] C -->|assembly| D[Finished product] -``` \ No newline at end of file +``` + +## 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: \ No newline at end of file diff --git a/server/Flask/main.py b/server/Flask/main.py index c185937..9fb35a1 100644 --- a/server/Flask/main.py +++ b/server/Flask/main.py @@ -3,12 +3,14 @@ import mysql.connector from queries import * app = Flask(__name__) -@app.route('/') +@app.route('/getMeasurements') def index(): node = request.args.get('node', default = None) dataType = request.args.get('dataType', default = None) MAC = request.args.get('MAC', default = None) - return getData(node, dataType, MAC) + dateStart = request.args.get('dateStart', default = None) + dateEnd = request.args.get('dateEnd', default = None) + return getData(node, dataType, MAC, dateStart, dateEnd) @app.route('/updateData') def updateDataIndex(): @@ -22,9 +24,13 @@ 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) + query = update_query(node, name, location, False, False) cursor = mydb.cursor(dictionary=True) # Enable dictionary output cursor.execute(query) mydb.commit() @@ -43,9 +49,9 @@ def loginDB(): ) return mydb -def getData(node, dataType, MAC): +def getData(node, dataTypes, MAC, dateStart, dateEnd): mydb = loginDB() - query = get_query(node, dataType, MAC) + query = get_query(node, dataTypes, MAC, False, False, dateStart, dateEnd) cursor = mydb.cursor(dictionary=True) # Enable dictionary output cursor.execute(query) result = cursor.fetchall() # Fetch the results @@ -56,7 +62,7 @@ def getData(node, dataType, MAC): def getNodeInfo(macAdress): mydb = loginDB() - query = get_query(False, False, macAdress) + 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 @@ -65,5 +71,16 @@ def getNodeInfo(macAdress): 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') diff --git a/server/Flask/queries.py b/server/Flask/queries.py index e2da308..c423711 100644 --- a/server/Flask/queries.py +++ b/server/Flask/queries.py @@ -1,18 +1,38 @@ -def get_query(node, dataType, MAC): - if node and dataType: +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: 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""" diff --git a/server/data.py b/server/web-data-connection/data.py similarity index 52% rename from server/data.py rename to server/web-data-connection/data.py index 3157c8a..d5865f9 100644 --- a/server/data.py +++ b/server/web-data-connection/data.py @@ -1,64 +1,19 @@ import asyncio import websockets -import mysql.connector import json +from class_SensorNode import SensorNode +from class_enqueteNode import EnqueteNode +from classes_data import dbLogin sensorNodeArray = [] enqueteNodeArray = [] -async def processSensorNodeData(data, nodeID): - try: - mydb = dbLogin() - cursor = mydb.cursor() - - 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']) - - pushingDataArray = [(nodeID, "Temp", processedTemp), (nodeID, "Humi", processedHumi), (nodeID, "eCO2", processedeCO2), (nodeID, "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 processEnqueteNodeData(data, nodeID): - try: - mydb = dbLogin() - cursor = mydb.cursor() - - query = "INSERT INTO `Reply` (Result, Node_NodeID, Question_QuestionID) VALUES (%s, %s, %s)" - processedData = json.loads(data) - - processedQuestionID = (processedData['QuestionID']) - processedResponse = (processedData['Response']) - - pushingDataArray = [(processedResponse, nodeID, processedQuestionID)] - - for i in pushingDataArray: - 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: + print("true") data = await websocket.recv() print(f"Received data: {data}") @@ -73,14 +28,13 @@ async def receive_data(): await getNodeInfo('sensor') await getNodeInfo('enquete') - print(sensorNodeArray) if macAdress in sensorNodeArray: nodeID = await getNodeID(macAdress) - await processSensorNodeData(data, nodeID) + await SensorNode.processSensorNodeData(data, nodeID) elif macAdress in enqueteNodeArray: nodeID = await getNodeID(macAdress) - await processEnqueteNodeData(data, nodeID) + await EnqueteNode.processEnqueteNodeData(data, nodeID) else: await newNode(macAdress, type) except websockets.ConnectionClosedError as e: @@ -89,17 +43,8 @@ async def receive_data(): async def main(): await receive_data() -def dbLogin(): - mydb = mysql.connector.connect( - host="localhost", - user="root", - password="Dingleberries69!", - database="NodeData" - ) - - return mydb - async def getNodeInfo(type): + print("getNodeINfo") global sensorNodeArray global enqueteNodeArray @@ -114,14 +59,12 @@ async def getNodeInfo(type): 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': @@ -143,7 +86,7 @@ async def getNodeID(macAdress): return nodeID async def newNode(mac, type): - id = (mac, type,) + id = (mac, type) mydb = dbLogin() cursor = mydb.cursor() diff --git a/server/web-data-connection/databaseGeneralClass.py b/server/web-data-connection/databaseGeneralClass.py new file mode 100644 index 0000000..e3c6407 --- /dev/null +++ b/server/web-data-connection/databaseGeneralClass.py @@ -0,0 +1,26 @@ +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 \ No newline at end of file diff --git a/server/web-data-connection/enqueteNodeClass.py b/server/web-data-connection/enqueteNodeClass.py new file mode 100644 index 0000000..f1243bf --- /dev/null +++ b/server/web-data-connection/enqueteNodeClass.py @@ -0,0 +1,35 @@ +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() \ No newline at end of file diff --git a/server/web-data-connection/sensorNodeClass.py b/server/web-data-connection/sensorNodeClass.py new file mode 100644 index 0000000..c3cb90d --- /dev/null +++ b/server/web-data-connection/sensorNodeClass.py @@ -0,0 +1,41 @@ +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() \ No newline at end of file diff --git a/web/newWebsite/GaugGroup.js b/web/newWebsite/GaugGroup.js index fc214fc..cf3c1f4 100644 --- a/web/newWebsite/GaugGroup.js +++ b/web/newWebsite/GaugGroup.js @@ -13,6 +13,7 @@ class GaugeGroup { this.element.innerHTML = `

${this.nodeId} - ${this.location}

+ ${Array(this.gaugesCount).fill().map((_, i) => `
@@ -25,6 +26,9 @@ class GaugeGroup {
`).join('')}
+
+
+
`; // Append the new div to the body diff --git a/web/newWebsite/graph-classes.js b/web/newWebsite/graph-classes.js new file mode 100644 index 0000000..9700ab0 --- /dev/null +++ b/web/newWebsite/graph-classes.js @@ -0,0 +1,157 @@ +class Graph { + constructor(id) { + this.id = "graph" + id; + this.timeArray = []; + this.tempArray = []; + this.humiArray = []; + this.eco2Array = []; + this.tvocArray = []; + } + + // Function to create a graph + makeGraph(line, lineColor, name) { + let div = document.createElement("div"); + div.setAttribute("id", this.id); + document.body.appendChild(div); + 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.makeGraph("temp", "red", "Temperature"); + this.graph.makeGraph("humi", "blue", "Humidity"); + this.graph.makeGraph("eco2", "green", "eCO2"); + this.graph.makeGraph("tvoc", "#F5G644", "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(); + } +} diff --git a/web/newWebsite/graph-main.js b/web/newWebsite/graph-main.js new file mode 100644 index 0000000..cd9760a --- /dev/null +++ b/web/newWebsite/graph-main.js @@ -0,0 +1,145 @@ +// Sample data - you can replace this with your actual dataset +const data = []; +processor = new DataProcessor(); +let link; + +// 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 }; +} + +// 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("input"); +nodeInput.setAttribute("type", "text"); +nodeInput.setAttribute("placeholder", "Enter Node Name (* for all)"); +nodeInput.setAttribute("id", "node-input"); +nodeInput.setAttribute("class", "input-field"); + +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); + }); +} \ No newline at end of file diff --git a/web/newWebsite/graphs.html b/web/newWebsite/graphs.html index 975f041..d4b62cc 100644 --- a/web/newWebsite/graphs.html +++ b/web/newWebsite/graphs.html @@ -6,6 +6,7 @@ Graphs + @@ -22,12 +23,13 @@ + -
- -
-
- - - - -

Status updating

-
- + + diff --git a/web/newWebsite/liveGraph.js b/web/newWebsite/liveGraph.js index e2aac1d..6668620 100644 --- a/web/newWebsite/liveGraph.js +++ b/web/newWebsite/liveGraph.js @@ -54,15 +54,16 @@ class liveGraph { } // Function to update the graph with new values got from updateData function - updateGraph() { + 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 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 = { @@ -75,11 +76,4 @@ class liveGraph { 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); - } } \ No newline at end of file diff --git a/web/newWebsite/main.js b/web/newWebsite/main.js index 25fbf2f..c5e1486 100644 --- a/web/newWebsite/main.js +++ b/web/newWebsite/main.js @@ -4,7 +4,7 @@ const sensorData = {}; let liveGraphs = []; let nodeArray = []; let nodeDict = {}; - +let graphArray = []; // letiables let intervalDelay = 5000; let amountOfNodes = 3; @@ -66,6 +66,7 @@ async function handleIncomingData(data) { 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); } @@ -98,31 +99,14 @@ async function nodeAdressHandler(node, dataTypes) { 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 createGauge(node, dataType) { - // Create a new gauge here - let gauge = new GaugeGroup(node, dataType); // Assuming Gauge is the name of the class - sensorData[node][dataType] = gauge; // Store the gauge in the sensorData object for later use -} - -//function for making the html elements for the following html code -function nodeData(data) { - let nodeData = document.createElement("div"); - nodeData.innerHTML = data; - // nodeData.setAttribute("id", "node" + node); - document.body.appendChild(nodeData); - // console.log("Hello World"); -} - - -function updateGauge(nodeNumber, dataType, value) { - // Update the gauge here - let gauge = sensorData[nodeNumber][dataType]; // Get the gauge from the sensorData object - gauge.update(value); // Assuming the Gauge class has an update method - } function getNodeInfo(node){ return fetch("http://145.92.8.114/getNodeInfo?macAdress=" + node) @@ -142,4 +126,19 @@ function getNodeInfo(node){ }; }) +} + +// 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); + } } \ No newline at end of file diff --git a/web/newWebsite/questions-chart-class.js b/web/newWebsite/questions-chart-class.js new file mode 100644 index 0000000..42467d9 --- /dev/null +++ b/web/newWebsite/questions-chart-class.js @@ -0,0 +1,27 @@ +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 + } + } + } + } +} \ No newline at end of file diff --git a/web/newWebsite/questions-creation-class.js b/web/newWebsite/questions-creation-class.js new file mode 100644 index 0000000..356d3e0 --- /dev/null +++ b/web/newWebsite/questions-creation-class.js @@ -0,0 +1,27 @@ +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(255, 99, 132, 0.2)', + 'rgba(54, 162, 235, 0.2)', + 'rgba(255, 206, 86, 0.2)' + ], + borderColor: [ + 'rgba(255, 99, 132, 1)', + 'rgba(54, 162, 235, 1)', + 'rgba(255, 206, 86, 1)' + ], + borderWidth: 1 + }] + }; + } +} \ No newline at end of file diff --git a/web/newWebsite/questions-dashboard.html b/web/newWebsite/questions-dashboard.html new file mode 100644 index 0000000..5ce5f66 --- /dev/null +++ b/web/newWebsite/questions-dashboard.html @@ -0,0 +1,59 @@ + + + + + + + + Graphs + + + + + + + + + +
+
+

Question 1: How clean are the toilets?

+ +
+
+

Question 2: How clean is the study area?

+ +
+
+

Question 3: What do you think of the temperature in the study area?

+ +
+
+

Question 4: How crowded would you say the study area is?

+ +
+
+

Question 5: Is there enough help available on the 5th floor?

+ +
+ +
+ + + + + \ No newline at end of file diff --git a/web/newWebsite/questions-main.js b/web/newWebsite/questions-main.js new file mode 100644 index 0000000..06bf47e --- /dev/null +++ b/web/newWebsite/questions-main.js @@ -0,0 +1,54 @@ +//For now create dummy data to show on the website. +let dummydata1 = [40, 30, 20]; +let questionOptionsDummy1 = ['disgusting','clean', 'fine']; + +let dummydata2 = [25, 35, 40]; +let questionOptionsDummy2 = ['disgusting', 'clean', 'normal']; + +let dummydata3 = [30, 20, 20]; +let questionOptionsDummy3 = ['cold', 'perfect', 'hot']; + +let dummydata4 = [30, 20, 20]; +let questionOptionsDummy4 = ['really crowded','not at all', 'its fine', ]; + +let dummydata5 = [30, 20, 20]; +let questionOptionsDummy5 = ['no','yes', 'decently']; + +//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); diff --git a/web/newWebsite/settings.html b/web/newWebsite/settings.html index 6c33bd2..383842a 100644 --- a/web/newWebsite/settings.html +++ b/web/newWebsite/settings.html @@ -22,6 +22,8 @@ +