Merge branch 'main' of gitlab.fdmci.hva.nl:propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59

This commit is contained in:
Dano van den Bosch
2024-04-03 10:12:29 +02:00
16 changed files with 552 additions and 30 deletions

View File

@@ -0,0 +1,71 @@
```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
+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()
}
}
```

View File

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

View File

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

View File

@@ -0,0 +1,78 @@
## Questionaire class
This File and class are dedicated to storing/using data that is related to the questionaire. This class is primairly used as a gateway for pushing data and loading data.
By doing this a lot of space is saved on the main file and the readability wil increase.
By doing this, it also solves the issues with the very precise naming and the often similar types of names.
This way it ensures 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
import json
#Importing different classes.
from classes_data import Node
from classes_data import dbLogin
```
Here a Class is made as a child-class of the parent-class: "Node".
This clas first makes a private variable, this being: "__query".
This is done so no other outside sources can interfere with this query and potentially cause problems down the line.
After this the "__init__"function is called.
(In javascript this is known as the "constructor".
For further information visit https://www.w3schools.com/python/gloss_python_class_init.asp)
Because this class is a child class, we want to use some functionality from the parent class. That is where the "super" comes in. This makes it so the class uses the values and propperties of the parent class. (for more information visit https://www.w3schools.com/python/python_inheritance.asp)
The rest of the class contains a function in which the gatherd data
gets put into the database using the query and the gatherd data.
```py
#Node is between brackets to show that this class is a child class from the parent class "Node"
class EnqueteNode(Node):
#A private query to use later in a function.
__query = "INSERT INTO `Reply` (Result, Node_NodeID, Question_QuestionID) VALUES (%s, %s, %s)"
#use a super to get info from the parent class.
def __init__(self, macAdress, response, questionID):
super().__init__(macAdress)
self.response = response
self.questionID = questionID
```
The following function is meant to make a database connection, get the data from the websocket (this data will only be from the questionaire-node)
and send the gotten data to the database.
The function starts with the database connection and calls uppon the websocket data.
It then creates variables with the data to put it in an array.
This array then gets sorted and pushed thogether with the query to correctly sort it and push it to the database.
In case of an error, it also asks for 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'])
#An array with the data to push.
pushingDataArray = [(EnqueteNode.questionID, nodeID, EnqueteNode.response)]
#Push the data according to the query to the database.
for i in pushingDataArray:
cursor.execute(EnqueteNode.__query, i)
mydb.commit()
#print an error.
except mysql.connector.Error as err:
print("MySQL Error:", err)
finally:
cursor.close()
mydb.close()
```

View File

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

View File

@@ -0,0 +1,68 @@
## General node file.
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",
password="**********",
database="NodeData"
)
return mydb
```
After the function, a central class is made to act as a parent class for every other class that has something to do with the nodes and the data.
The class has some interchangable variables and primairly asks the incomming data for its mac adress and looks for any similarities.
It primairly gives the main MAC-address over to the child-classes so these can be used.
This class might seem small, but is not to be underestimated.
At the start of every class the "__init__" function is called.
(In javascript this is known as the "constructor".
For further information visit https://www.w3schools.com/python/gloss_python_class_init.asp)
The parent node is able to be further evolved, but for now is able do always provide the node ID to all child-classes.
```py
# 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
```
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()
#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
```

View File

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

View File

@@ -0,0 +1,56 @@
## 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
```
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.
__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()
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -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.body.appendChild(div);
let lineArray;
switch (line) {
case "temp":
@@ -132,10 +137,11 @@ 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");
this.graph.makeGraph("tvoc", "#F5G644", "TVOC");
this.graph.makeGraph("tvoc", "black", "TVOC");
}
updateGraph() {

View File

@@ -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);

View File

@@ -25,6 +25,9 @@
<li class="nav-item">
<a href="settings.html" class="nav-link">Settings</a>
</li>
<li class="nav-item">
<a href="questions-dashboard.html" class="nav-link">Questions</a>
</li>
</ul>
</nav>

View File

@@ -1,17 +1,54 @@
//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);
// Initialize an array to hold the counts for each question
let questionCounts = [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]];
// Iterate over the data
for (let item of data) {
// Increment the count for the appropriate question and result
questionCounts[item.Question_QuestionID - 1][item.Result]++;
}
// Log the counts for each question
for (let i = 0; i < questionCounts.length; i++) {
console.log(`Question ${i + 1} counts: ${questionCounts[i]}`);
}
// Update the dummydata arrays
dummydata1 = questionCounts[0];
dummydata2 = questionCounts[1];
dummydata3 = questionCounts[2];
dummydata4 = questionCounts[3];
dummydata5 = questionCounts[4];
graph();
})
}
// for each(Result == 0) in
async function graph() {
let questionOptionsDummy1 = ['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.
@@ -52,3 +89,4 @@ const myChart4 = new Chart(ctx4, chartConfigArray[3].chartConfig);
const ctx5 = document.getElementById('chart5').getContext('2d');
const myChart5 = new Chart(ctx5, chartConfigArray[4].chartConfig);
}

View File

@@ -55,9 +55,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;
}
@@ -68,11 +67,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;
@@ -108,7 +107,6 @@ body {
text-align: center;
position: relative;
padding-top: 3vh; /* Increase bottom padding */
}
.gaugeContainer {
@@ -128,7 +126,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;
@@ -196,7 +194,8 @@ body {
}
.navbar {
background-color: #333;
background-color: #bfbfbf;
border-bottom: 2px solid #CC2936;
height: 60px;
display: flex;
align-items: center;

View File

@@ -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,9 +109,20 @@ body {
}
.js-plotly-plot {
align-self: center;
width: 95%;
width: 100%;
height: 100%;
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;
}

View File

@@ -65,6 +65,7 @@
border: 3px solid #000;
border-radius: 30px;
}
canvas {
margin-bottom: 20px;
}