From 93492db324f3ec08c1b2c505d4eeb750b4966279 Mon Sep 17 00:00:00 2001 From: Bram Barbieri Date: Tue, 2 Apr 2024 13:25:39 +0200 Subject: [PATCH 01/18] documentation updated. --- docs/rpi-documentation/Databaseconnection.md | 2 +- server/web-data-connection/datatransfer.md | 128 ++++++++++++++++++ .../generalDatabaseFile.md | 0 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 server/web-data-connection/datatransfer.md create mode 100644 server/web-data-connection/generalDatabaseFile.md diff --git a/docs/rpi-documentation/Databaseconnection.md b/docs/rpi-documentation/Databaseconnection.md index 8f6dd7f..86cf47b 100644 --- a/docs/rpi-documentation/Databaseconnection.md +++ b/docs/rpi-documentation/Databaseconnection.md @@ -237,7 +237,7 @@ async def getNodeID(macAdress): 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\data.py) +[link to code](../../server/web-data-connection/data.py) ## The function to commit the data from the enqueteNodes diff --git a/server/web-data-connection/datatransfer.md b/server/web-data-connection/datatransfer.md new file mode 100644 index 0000000..d1f9547 --- /dev/null +++ b/server/web-data-connection/datatransfer.md @@ -0,0 +1,128 @@ +## The websocket -> database connection classes. +I have made several classes to make the database connection more clear and easyer to overlook. +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 variables need to be global to serve for several locations in the code. + +Here the code makes connection with the websocket. +```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() +``` +Here we have a case of python's scoping, it couldn't read the variables correctly and by making them global the variables were now available for all functions. +```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 = [] + + id = (type,) + mydb = dbLogin() + cursor = mydb.cursor() + cursor.execute("""SELECT MAC FROM Node WHERE Type = %s""", id) + 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 +``` +Here the database gets hinted to gain info of the existing nodes and find their macadress. +```py +async def getNodeID(macAdress): + id = (macAdress,) + #the db login is on a different page. + 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 +``` +See if the node is existing, if not push it to the database. +```py +async def newNode(mac, type): + id = (mac, type) + #Same thing as before. + 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()) +``` diff --git a/server/web-data-connection/generalDatabaseFile.md b/server/web-data-connection/generalDatabaseFile.md new file mode 100644 index 0000000..e69de29 From 276ec2ed120c5657bd8ba41e41fd35bcac77aeb7 Mon Sep 17 00:00:00 2001 From: Sam Hos Date: Tue, 2 Apr 2024 13:32:35 +0200 Subject: [PATCH 02/18] Add UML diagram and classes for infrastructure V2 --- docs/brainstorm/UML-infrastrucuteV2.md | 72 ++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/brainstorm/UML-infrastrucuteV2.md diff --git a/docs/brainstorm/UML-infrastrucuteV2.md b/docs/brainstorm/UML-infrastrucuteV2.md new file mode 100644 index 0000000..059a1e5 --- /dev/null +++ b/docs/brainstorm/UML-infrastrucuteV2.md @@ -0,0 +1,72 @@ +```mermaid +classDiagram + setup --> websocketSetup + loop --> screenButtonHandler + screenButtonHandler --> DisplayText + screenButtonHandler --> sendData + sendData --> Server + setup --> loop + python --> Server + Server --> website + + +namespace ESP32Questionbox { + class setup { + +int questionID + +char*[] Question + +char*[] Answer + +Adafruit_ST7796S_kbv tft + +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 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() + + } +} + +``` From 62206a6ce3db48bcb5539742e7339948e22f23d5 Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Tue, 2 Apr 2024 13:47:04 +0200 Subject: [PATCH 03/18] Add fetch request to get node information and populate select dropdown --- web/newWebsite/graph-main.js | 40 +++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/web/newWebsite/graph-main.js b/web/newWebsite/graph-main.js index cd9760a..6caadde 100644 --- a/web/newWebsite/graph-main.js +++ b/web/newWebsite/graph-main.js @@ -2,6 +2,7 @@ const data = []; processor = new DataProcessor(); let link; +nodeDataArray = {}; // Function to create checkbox with label function createCheckBox(id, label) { @@ -16,7 +17,41 @@ function createCheckBox(id, 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 => { + handleData(data); + }) + +function handleData(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"); @@ -102,11 +137,10 @@ container.appendChild(dateFilter); const nodeFilter = document.createElement("div"); nodeFilter.setAttribute("class", "node-filter"); -const nodeInput = document.createElement("input"); -nodeInput.setAttribute("type", "text"); +const nodeInput = document.createElement("select"); +nodeInput.setAttribute("type", "select"); nodeInput.setAttribute("placeholder", "Enter Node Name (* for all)"); nodeInput.setAttribute("id", "node-input"); -nodeInput.setAttribute("class", "input-field"); nodeFilter.appendChild(nodeInput); container.appendChild(nodeFilter); From 8bb1a1c69fa64ef9145cc1881cbfdecb2c8ea541 Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Tue, 2 Apr 2024 13:47:07 +0200 Subject: [PATCH 04/18] Add Questions link to navigation --- web/newWebsite/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/newWebsite/index.html b/web/newWebsite/index.html index 6346d81..e31a4ba 100644 --- a/web/newWebsite/index.html +++ b/web/newWebsite/index.html @@ -25,6 +25,9 @@ + From 5761e0a028abb17a0771296e3bcc2a6c0891bc2b Mon Sep 17 00:00:00 2001 From: Bram Barbieri Date: Tue, 2 Apr 2024 13:51:37 +0200 Subject: [PATCH 05/18] new documentations made. --- .../web-data-connection/enqueteClassFile.md | 40 +++++++++++++++++++ .../generalDatabaseFile.md | 33 +++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 server/web-data-connection/enqueteClassFile.md diff --git a/server/web-data-connection/enqueteClassFile.md b/server/web-data-connection/enqueteClassFile.md new file mode 100644 index 0000000..7c0f67c --- /dev/null +++ b/server/web-data-connection/enqueteClassFile.md @@ -0,0 +1,40 @@ +## Questionaire class +This file prmairly is dedicated to the class which gets its data form the questionare and pushes it to the database. + +```py +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/generalDatabaseFile.md b/server/web-data-connection/generalDatabaseFile.md index e69de29..81e7c49 100644 --- a/server/web-data-connection/generalDatabaseFile.md +++ b/server/web-data-connection/generalDatabaseFile.md @@ -0,0 +1,33 @@ +## General node file. +The general node file includes the database connecting and a node connection class. +This is done so the files don't go back and forth with the db connection, this otherwise would have caused problems. +```py +#importing a database library to connect to the database. +import mysql.connector + +def dbLogin(): + mydb = mysql.connector.connect( + host="localhost", + user="root", + password="**********", + database="NodeData" + ) + return mydb +# A general class which acts as a database connector and a node identifyer +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() + #again go along all tuple items. and asign them to the ID + for tuples in data: + for item in tuples: + self.id = item +``` \ No newline at end of file From 87246917161ab8f0dbca24ff5f16e67dcf728aeb Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Tue, 2 Apr 2024 14:15:31 +0200 Subject: [PATCH 06/18] Update graph creation and styling --- web/newWebsite/graph-classes.js | 4 ++-- web/newWebsite/graphs.html | 2 ++ web/newWebsite/styles/graph-styles.css | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/web/newWebsite/graph-classes.js b/web/newWebsite/graph-classes.js index 9700ab0..491ecae 100644 --- a/web/newWebsite/graph-classes.js +++ b/web/newWebsite/graph-classes.js @@ -12,7 +12,7 @@ class Graph { makeGraph(line, lineColor, name) { let div = document.createElement("div"); div.setAttribute("id", this.id); - document.body.appendChild(div); + document.graphBody.appendChild(div); let lineArray; switch (line) { case "temp": @@ -135,7 +135,7 @@ class DataProcessor { this.graph.makeGraph("temp", "red", "Temperature"); this.graph.makeGraph("humi", "blue", "Humidity"); this.graph.makeGraph("eco2", "green", "eCO2"); - this.graph.makeGraph("tvoc", "#F5G644", "TVOC"); + this.graph.makeGraph("tvoc", "black", "TVOC"); } updateGraph() { diff --git a/web/newWebsite/graphs.html b/web/newWebsite/graphs.html index d4b62cc..18f4f4e 100644 --- a/web/newWebsite/graphs.html +++ b/web/newWebsite/graphs.html @@ -31,5 +31,7 @@ + +
\ No newline at end of file diff --git a/web/newWebsite/styles/graph-styles.css b/web/newWebsite/styles/graph-styles.css index 370617f..5d88644 100644 --- a/web/newWebsite/styles/graph-styles.css +++ b/web/newWebsite/styles/graph-styles.css @@ -111,6 +111,8 @@ body { align-self: center; width: 95%; height: 100%; + border-radius: 20px; + border: 2px solid #000; margin: 10px; } /* Additional styling as needed */ From b6c2bdb4a4f2b3c6fb2ed6160cc1a0d95062b267 Mon Sep 17 00:00:00 2001 From: Bram Barbieri Date: Tue, 2 Apr 2024 15:32:45 +0200 Subject: [PATCH 07/18] More documentation about classes. --- .../web-data-connection/enqueteClassFile.md | 25 +++++++++++++------ .../web-data-connection/enqueteNodeClass.py | 7 +++--- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/server/web-data-connection/enqueteClassFile.md b/server/web-data-connection/enqueteClassFile.md index 7c0f67c..7ab0c54 100644 --- a/server/web-data-connection/enqueteClassFile.md +++ b/server/web-data-connection/enqueteClassFile.md @@ -1,21 +1,27 @@ ## Questionaire class This file prmairly is dedicated to the class which gets its data form the questionare and pushes it to the database. - +This is done this way to make the code more visable and more clear. ```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 to again provide more clear viewing and more reusability. +The +```py +#Node is between brackets to show that this class is a child class from the parent class "Node" class EnqueteNode(Node): - query = "INSERT INTO `Reply` (Result, Node_NodeID, Question_QuestionID) VALUES (%s, %s, %s)" - + #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 - + #making a database connection to then load in the processed data. async def processEnqueteNodeData(data, nodeID): try: mydb = dbLogin() @@ -26,12 +32,15 @@ class EnqueteNode(Node): 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: - print(EnqueteNode.query, i) - cursor.execute(EnqueteNode.query, i) + print(EnqueteNode.__query, i) + cursor.execute(EnqueteNode.__query, i) mydb.commit() + #print an error. except mysql.connector.Error as err: print("MySQL Error:", err) finally: diff --git a/server/web-data-connection/enqueteNodeClass.py b/server/web-data-connection/enqueteNodeClass.py index f1243bf..6abf7aa 100644 --- a/server/web-data-connection/enqueteNodeClass.py +++ b/server/web-data-connection/enqueteNodeClass.py @@ -5,7 +5,8 @@ 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)" + __query = "INSERT INTO `Reply` (Result, Node_NodeID, Question_QuestionID) VALUES (%s, %s, %s)" + def __init__(self, macAdress, response, questionID): super().__init__(macAdress) @@ -25,8 +26,8 @@ class EnqueteNode(Node): pushingDataArray = [(EnqueteNode.questionID, nodeID, EnqueteNode.response)] for i in pushingDataArray: - print(EnqueteNode.query, i) - cursor.execute(EnqueteNode.query, i) + print(EnqueteNode.__query, i) + cursor.execute(EnqueteNode.__query, i) mydb.commit() except mysql.connector.Error as err: print("MySQL Error:", err) From adab457fd9d9cdbd5e6b4d9a1c087cc744be70b9 Mon Sep 17 00:00:00 2001 From: Bram Barbieri Date: Tue, 2 Apr 2024 15:41:45 +0200 Subject: [PATCH 08/18] sensornode updated docs. --- .../sensorNodeClassFile.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 server/web-data-connection/sensorNodeClassFile.md diff --git a/server/web-data-connection/sensorNodeClassFile.md b/server/web-data-connection/sensorNodeClassFile.md new file mode 100644 index 0000000..f4d7fc9 --- /dev/null +++ b/server/web-data-connection/sensorNodeClassFile.md @@ -0,0 +1,51 @@ +## Sensor Node class +This class is made to get the info of the sensor-nodes and send this to the database. +This is done this way to make the code more clear and readable. + +```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 +#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 + + #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() +``` \ No newline at end of file From badea73c4d2cf0080dcd3b66226402b4b0e5c2c1 Mon Sep 17 00:00:00 2001 From: Bram Barbieri Date: Tue, 2 Apr 2024 15:55:10 +0200 Subject: [PATCH 09/18] Uml added, documetnation added --- server/web-data-connection/sensorNodeClass.py | 4 ++-- server/web-data-connection/umlpython.png | Bin 0 -> 19133 bytes 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 server/web-data-connection/umlpython.png diff --git a/server/web-data-connection/sensorNodeClass.py b/server/web-data-connection/sensorNodeClass.py index c3cb90d..0e22bb9 100644 --- a/server/web-data-connection/sensorNodeClass.py +++ b/server/web-data-connection/sensorNodeClass.py @@ -5,7 +5,7 @@ from classes_data import Node from classes_data import dbLogin class SensorNode(Node): - query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)" + __query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)" def __init__(self, macAdress, temp, humi, eCO2, TVOC): super().__init__(macAdress) @@ -30,7 +30,7 @@ class SensorNode(Node): 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) + cursor.execute(SensorNode.__query, i) mydb.commit() diff --git a/server/web-data-connection/umlpython.png b/server/web-data-connection/umlpython.png new file mode 100644 index 0000000000000000000000000000000000000000..721adcad35be409052186741cd39acb06044b7e5 GIT binary patch literal 19133 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 literal 0 HcmV?d00001 From c61cdaad1f329737e283c2d137818d092c457736 Mon Sep 17 00:00:00 2001 From: Bram Barbieri Date: Tue, 2 Apr 2024 16:07:53 +0200 Subject: [PATCH 10/18] Hoofdletters update --- server/web-data-connection/datatransfer.md | 20 +++++++++---------- .../web-data-connection/enqueteClassFile.md | 6 +++--- .../generalDatabaseFile.md | 6 +++--- .../sensorNodeClassFile.md | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/server/web-data-connection/datatransfer.md b/server/web-data-connection/datatransfer.md index d1f9547..0304d65 100644 --- a/server/web-data-connection/datatransfer.md +++ b/server/web-data-connection/datatransfer.md @@ -43,7 +43,7 @@ async def receive_data(): await getNodeInfo('sensor') await getNodeInfo('enquete') - #get the node id and use it in functions seperate from this file. + #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) @@ -52,23 +52,23 @@ async def receive_data(): await EnqueteNode.processEnqueteNodeData(data, nodeID) else: await newNode(macAdress, type) - # error message if smth went wrong + # Error message if smth went wrong except websockets.ConnectionClosedError as e: print("WebSocket connection closed:", e) -#wait for data to come in. +#Wait for data to come in. async def main(): await receive_data() ``` Here we have a case of python's scoping, it couldn't read the variables correctly and by making them global the variables were now available for all functions. ```py -#by python's scuffed we had to use global variables. +#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. + #New array which is needed. nodeInfoArray = [] id = (type,) @@ -77,7 +77,7 @@ async def getNodeInfo(type): cursor.execute("""SELECT MAC FROM Node WHERE Type = %s""", id) nodeInfo = cursor.fetchall() - #go along each tuple in nodeinfo and each item in tuple, append(item) + #Go along each tuple in nodeinfo and each item in tuple, append(item) for tuples in nodeInfo: for item in tuples: nodeInfoArray.append(item) @@ -85,12 +85,12 @@ async def getNodeInfo(type): cursor.close() mydb.close() - #if the type is a sensor do this, + #If the type is a sensor do this, if type == 'sensor': sensorNodeArray = nodeInfoArray return sensorNodeArray - #else, this if statement + #Else, this if statement elif type == 'enquete': enqueteNodeArray = nodeInfoArray return enqueteNodeArray @@ -99,13 +99,13 @@ Here the database gets hinted to gain info of the existing nodes and find their ```py async def getNodeID(macAdress): id = (macAdress,) - #the db login is on a different page. + #The db login is on a different page. 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 + #Again, all tuples in data, all items for all tuples, nodeID for tuples in data: for item in tuples: nodeID = item diff --git a/server/web-data-connection/enqueteClassFile.md b/server/web-data-connection/enqueteClassFile.md index 7ab0c54..a89eb58 100644 --- a/server/web-data-connection/enqueteClassFile.md +++ b/server/web-data-connection/enqueteClassFile.md @@ -5,7 +5,7 @@ This is done this way to make the code more visable and more clear. #Importing different librarys. import mysql.connector import json -#importing different classes. +#Importing different classes. from classes_data import Node from classes_data import dbLogin ``` @@ -32,10 +32,10 @@ class EnqueteNode(Node): EnqueteNode.questionID = (processedData['QuestionID']) EnqueteNode.response = (processedData['Response']) - #an array with the data to push. + #An array with the data to push. pushingDataArray = [(EnqueteNode.questionID, nodeID, EnqueteNode.response)] - #push the data according to the query to the database. + #Push the data according to the query to the database. for i in pushingDataArray: print(EnqueteNode.__query, i) cursor.execute(EnqueteNode.__query, i) diff --git a/server/web-data-connection/generalDatabaseFile.md b/server/web-data-connection/generalDatabaseFile.md index 81e7c49..c82813d 100644 --- a/server/web-data-connection/generalDatabaseFile.md +++ b/server/web-data-connection/generalDatabaseFile.md @@ -2,7 +2,7 @@ The general node file includes the database connecting and a node connection class. This is done so the files don't go back and forth with the db connection, this otherwise would have caused problems. ```py -#importing a database library to connect to the database. +#Importing a database library to connect to the database. import mysql.connector def dbLogin(): @@ -15,7 +15,7 @@ def dbLogin(): return mydb # A general class which acts as a database connector and a node identifyer class Node(): - + def __init__(self, macAdress): self.macAdress = macAdress self.id = None @@ -26,7 +26,7 @@ class Node(): cursor = mydb.cursor() cursor.execute("""SELECT nodeID FROM Node WHERE MAC = %s""", id) data = cursor.fetchall() - #again go along all tuple items. and asign them to the ID + #Again go along all tuple items. and asign them to the ID for tuples in data: for item in tuples: self.id = item diff --git a/server/web-data-connection/sensorNodeClassFile.md b/server/web-data-connection/sensorNodeClassFile.md index f4d7fc9..db8f197 100644 --- a/server/web-data-connection/sensorNodeClassFile.md +++ b/server/web-data-connection/sensorNodeClassFile.md @@ -14,7 +14,7 @@ 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. + #All variables to be used. def __init__(self, macAdress, temp, humi, eCO2, TVOC): super().__init__(macAdress) self.temperature = temp @@ -22,7 +22,7 @@ class SensorNode(Node): self.eCO2 = eCO2 self.TVOC = TVOC - #a function to connect to the database, grab the info which is given, and push this to the database. + #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() From b69b0296281852e8a41e3b3841203706aeb612b0 Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Tue, 2 Apr 2024 16:39:32 +0200 Subject: [PATCH 11/18] Add createDiv() method to Graph class and update graph-classes.js, graphs.html, and graph-styles.css --- web/newWebsite/graph-classes.js | 12 +++++++--- web/newWebsite/graphs.html | 2 -- web/newWebsite/styles/graph-styles.css | 24 +++++++++++++------ .../styles/questions-dashboard-styles.css | 1 + 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/web/newWebsite/graph-classes.js b/web/newWebsite/graph-classes.js index 491ecae..a432a36 100644 --- a/web/newWebsite/graph-classes.js +++ b/web/newWebsite/graph-classes.js @@ -8,11 +8,16 @@ class Graph { 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 div = document.createElement("div"); - div.setAttribute("id", this.id); - document.graphBody.appendChild(div); let lineArray; switch (line) { case "temp": @@ -132,6 +137,7 @@ class DataProcessor { 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"); diff --git a/web/newWebsite/graphs.html b/web/newWebsite/graphs.html index 18f4f4e..d4b62cc 100644 --- a/web/newWebsite/graphs.html +++ b/web/newWebsite/graphs.html @@ -31,7 +31,5 @@ - -
\ No newline at end of file diff --git a/web/newWebsite/styles/graph-styles.css b/web/newWebsite/styles/graph-styles.css index 5d88644..2d8a800 100644 --- a/web/newWebsite/styles/graph-styles.css +++ b/web/newWebsite/styles/graph-styles.css @@ -46,11 +46,12 @@ body { display: flex; flex-direction: row; align-items: center; - border: 2px solid #ccc; + align-self: center; + border: 3px solid #ccc; border-radius: 10px; margin: 20px; padding: 20px; - width: 90; + width: 95%; box-sizing: border-box; } @@ -108,11 +109,20 @@ body { } .js-plotly-plot { - align-self: center; - width: 95%; + width: 100%; height: 100%; - border-radius: 20px; - border: 2px solid #000; - margin: 10px; + 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; +} diff --git a/web/newWebsite/styles/questions-dashboard-styles.css b/web/newWebsite/styles/questions-dashboard-styles.css index b336bc0..0dcc484 100644 --- a/web/newWebsite/styles/questions-dashboard-styles.css +++ b/web/newWebsite/styles/questions-dashboard-styles.css @@ -65,6 +65,7 @@ border: 3px solid #000; border-radius: 30px; } + canvas { margin-bottom: 20px; } \ No newline at end of file From 24aaf29bce5180a0097237e415897bbea3c8643a Mon Sep 17 00:00:00 2001 From: Bram Barbieri Date: Tue, 2 Apr 2024 17:05:55 +0200 Subject: [PATCH 12/18] beginning with getting data --- web/newWebsite/questions-main.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/web/newWebsite/questions-main.js b/web/newWebsite/questions-main.js index 06bf47e..8703471 100644 --- a/web/newWebsite/questions-main.js +++ b/web/newWebsite/questions-main.js @@ -1,5 +1,30 @@ //For now create dummy data to show on the website. -let dummydata1 = [40, 30, 20]; +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); + let Q1Array = data.filter(item => item.Question_QuestionID == 1); + console.log(Q1Array); + let R1Array = Q1Array.filter(item => item.Result == 0); + awa = R1Array.length; + graph() +}) + +} +// for each(Result == 0) in +async function graph() { +let dummydata1 = [awa, 3, 5]; let questionOptionsDummy1 = ['disgusting','clean', 'fine']; let dummydata2 = [25, 35, 40]; @@ -52,3 +77,4 @@ const myChart4 = new Chart(ctx4, chartConfigArray[3].chartConfig); const ctx5 = document.getElementById('chart5').getContext('2d'); const myChart5 = new Chart(ctx5, chartConfigArray[4].chartConfig); +} \ No newline at end of file From 70c95e676ec47181227706d7d1e5ee6f4b88ee11 Mon Sep 17 00:00:00 2001 From: Sam Hos Date: Tue, 2 Apr 2024 17:10:26 +0200 Subject: [PATCH 13/18] for each loop for data graphs --- web/newWebsite/questions-main.js | 56 +++++++++++++++++++------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/web/newWebsite/questions-main.js b/web/newWebsite/questions-main.js index 8703471..4326b92 100644 --- a/web/newWebsite/questions-main.js +++ b/web/newWebsite/questions-main.js @@ -4,39 +4,51 @@ 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); - let Q1Array = data.filter(item => item.Question_QuestionID == 1); - console.log(Q1Array); - let R1Array = Q1Array.filter(item => item.Result == 0); - awa = R1Array.length; - graph() -}) +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 dummydata1 = [awa, 3, 5]; 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. From 46f9ca12927276d0f59e3a14738759bcc20e07f2 Mon Sep 17 00:00:00 2001 From: Sam Hos Date: Tue, 2 Apr 2024 17:10:33 +0200 Subject: [PATCH 14/18] removed thingy --- docs/brainstorm/UML-infrastrucuteV2.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/brainstorm/UML-infrastrucuteV2.md b/docs/brainstorm/UML-infrastrucuteV2.md index 059a1e5..87ea245 100644 --- a/docs/brainstorm/UML-infrastrucuteV2.md +++ b/docs/brainstorm/UML-infrastrucuteV2.md @@ -15,7 +15,6 @@ namespace ESP32Questionbox { +int questionID +char*[] Question +char*[] Answer - +Adafruit_ST7796S_kbv tft +DisplayText displayText +void websocketSetup() } From 86193d35938f8e285bb336d34cc5faaf75255a65 Mon Sep 17 00:00:00 2001 From: Sietse Jonker Date: Tue, 2 Apr 2024 17:13:02 +0200 Subject: [PATCH 15/18] Update dashboard styles in CSS file --- web/newWebsite/styles/dashboard-styles.css | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/web/newWebsite/styles/dashboard-styles.css b/web/newWebsite/styles/dashboard-styles.css index 35fa91c..c0fbab1 100644 --- a/web/newWebsite/styles/dashboard-styles.css +++ b/web/newWebsite/styles/dashboard-styles.css @@ -46,9 +46,8 @@ body { justify-content: center; margin: 0; margin-top: 8vh; - background-color: #f0f0f0; flex-direction: column; - background-color: #afafaf; + background-color: #bfbfbf; align-items: center; } @@ -59,11 +58,11 @@ body { display: flex; flex-direction: column; /* Keep as column */ justify-content: flex-start; - background-color: #333; + background-color: #9f9f9f; color: #fff; padding: 10px; border-radius: 50px; - border: 2px solid #333; + border: 2px solid #CC2936; clear: both; margin-bottom: 10px; position: relative; @@ -99,7 +98,6 @@ body { text-align: center; position: relative; padding-top: 3vh; /* Increase bottom padding */ - } .gaugeContainer { @@ -119,7 +117,7 @@ body { .gaugeImage { width: 100%; height: auto; - max-height: 120%; + max-height: 140%; object-fit: contain; position: absolute; /* Make the image position absolute */ top: 0; @@ -187,7 +185,8 @@ body { } .navbar { - background-color: #333; + background-color: #bfbfbf; + border-bottom: 2px solid #CC2936; height: 60px; display: flex; align-items: center; From 5f426df0ed74726be37fb61b6089bed5b7038225 Mon Sep 17 00:00:00 2001 From: Bram Barbieri Date: Tue, 2 Apr 2024 20:22:33 +0200 Subject: [PATCH 16/18] Main data file documentation done. --- server/web-data-connection/datatransfer.md | 41 ++++++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/server/web-data-connection/datatransfer.md b/server/web-data-connection/datatransfer.md index 0304d65..cb39ab2 100644 --- a/server/web-data-connection/datatransfer.md +++ b/server/web-data-connection/datatransfer.md @@ -1,5 +1,7 @@ ## 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.). @@ -17,9 +19,17 @@ from classes_data import dbLogin sensorNodeArray = [] enqueteNodeArray = [] ``` -These variables need to be global to serve for several locations in the code. +These array's need to be global because of the several uses later in the code. These cannot be bound to a singular function. -Here the code makes connection with the websocket. +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(): @@ -60,7 +70,18 @@ async def receive_data(): async def main(): await receive_data() ``` -Here we have a case of python's scoping, it couldn't read the variables correctly and by making them global the variables were now available for all functions. +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 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 ```py #By python's scuffed we had to use global variables. async def getNodeInfo(type): @@ -71,10 +92,12 @@ async def getNodeInfo(type): #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) @@ -95,11 +118,12 @@ async def getNodeInfo(type): enqueteNodeArray = nodeInfoArray return enqueteNodeArray ``` -Here the database gets hinted to gain info of the existing nodes and find their macadress. +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 this is not yet existent. ```py async def getNodeID(macAdress): id = (macAdress,) - #The db login is on a different page. mydb = dbLogin() cursor = mydb.cursor() cursor.execute("""SELECT nodeID FROM Node WHERE MAC = %s""", id) @@ -112,15 +136,18 @@ async def getNodeID(macAdress): return nodeID ``` -See if the node is existing, if not push it to the database. +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) - #Same thing as before. 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() From 5d8bec404b5be6642c309fda3ed6b77858c0ca41 Mon Sep 17 00:00:00 2001 From: Bram Barbieri Date: Tue, 2 Apr 2024 21:13:27 +0200 Subject: [PATCH 17/18] enquete class documentation + a bit of the others --- .../web-data-connection/enqueteClassFile.md | 47 +++++++++++++++---- .../generalDatabaseFile.md | 5 ++ .../sensorNodeClassFile.md | 5 ++ 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/server/web-data-connection/enqueteClassFile.md b/server/web-data-connection/enqueteClassFile.md index a89eb58..21821d3 100644 --- a/server/web-data-connection/enqueteClassFile.md +++ b/server/web-data-connection/enqueteClassFile.md @@ -1,6 +1,14 @@ ## Questionaire class -This file prmairly is dedicated to the class which gets its data form the questionare and pushes it to the database. -This is done this way to make the code more visable and more clear. +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 no confusion on what the purpous of each segement is. + +First up this page imports different types of information, like the library's and the needed files and/or Node. + + ```py #Importing different librarys. import mysql.connector @@ -9,8 +17,19 @@ import json from classes_data import Node from classes_data import dbLogin ``` -Here a class is made to again provide more clear viewing and more reusability. -The +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): @@ -21,14 +40,25 @@ class EnqueteNode(Node): 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 errors and prints it. +```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']) @@ -37,13 +67,12 @@ class EnqueteNode(Node): #Push the data according to the query to the database. for i in pushingDataArray: - print(EnqueteNode.__query, i) cursor.execute(EnqueteNode.__query, i) mydb.commit() - #print an error. + #print an error. 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/generalDatabaseFile.md b/server/web-data-connection/generalDatabaseFile.md index c82813d..d733fce 100644 --- a/server/web-data-connection/generalDatabaseFile.md +++ b/server/web-data-connection/generalDatabaseFile.md @@ -13,6 +13,11 @@ def dbLogin(): database="NodeData" ) return mydb +``` +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) +```py # A general class which acts as a database connector and a node identifyer class Node(): diff --git a/server/web-data-connection/sensorNodeClassFile.md b/server/web-data-connection/sensorNodeClassFile.md index db8f197..4c7413c 100644 --- a/server/web-data-connection/sensorNodeClassFile.md +++ b/server/web-data-connection/sensorNodeClassFile.md @@ -9,6 +9,11 @@ import json #Import classes and functions from different files. from classes_data import Node from classes_data import dbLogin +``` +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) +```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. From 355a2b4f75a8632c806c26b4f9c39f6fa0e09dc5 Mon Sep 17 00:00:00 2001 From: Bram Barbieri Date: Tue, 2 Apr 2024 22:50:28 +0200 Subject: [PATCH 18/18] documentation general data. --- .../generalDatabaseFile.md | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/server/web-data-connection/generalDatabaseFile.md b/server/web-data-connection/generalDatabaseFile.md index d733fce..5d1b24a 100644 --- a/server/web-data-connection/generalDatabaseFile.md +++ b/server/web-data-connection/generalDatabaseFile.md @@ -1,11 +1,20 @@ ## General node file. -The general node file includes the database connecting and a node connection class. -This is done so the files don't go back and forth with the db connection, this otherwise would have caused problems. +This File includes several main (verry important) components: +The Node parent class and the database log- 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", @@ -14,9 +23,19 @@ def dbLogin(): ) return mydb ``` -The "__init__"function is called. +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. ```py # A general class which acts as a database connector and a node identifyer class Node(): @@ -24,14 +43,25 @@ 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 frist 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() - #Again go along all tuple items. and asign them to the ID + #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