Merge branch 'main' into '44-als-gebruiker-wil-ik-dat-de-website-automatisch-het-aantal-nodes-dat-ik-heb-aangesloten-op-de'

# Conflicts:
#   docs/.pages
This commit is contained in:
Sietse Jonker
2024-04-04 22:20:09 +02:00
81 changed files with 4015 additions and 144 deletions

View File

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

View File

@@ -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", "BcgrFpX3kl");
WiFiMulti.addAP("ObsidianAmstelveen", "drijversstraatmaastricht");
while(WiFiMulti.run() != WL_CONNECTED) {
delay(100);
}
// server address, port and URL
webSocket.begin("145.92.8.114", 80, "/ws");
// try ever 500 again if connection has failed
webSocket.setReconnectInterval(500);
}
void screenButtonHandler(){
int redButton = digitalRead(16);
int whiteButton = digitalRead(17);
int greenButton = digitalRead(18);
if (initialized) {
initialized = false;
tft.fillScreen(ST7796S_BLACK);
displayText.writeText(Question[i], 3, 0, 0, 0, true, false);
displayText.writeText(Answer[i], 3, 0, 200, 0, true, true);
}
// 0 is best 2 is worst
if (redButton == HIGH){
sendData(questionID, "0");
}
if (whiteButton == HIGH){
sendData(questionID, "1");
}
if (greenButton == HIGH){
sendData(questionID, "2");
}
if (redButton || whiteButton || greenButton) {
i++;
questionID++;
if (questionID == 6){
questionID = 1;
}
if (i == 5) {
i = 0;
}
tft.fillScreen(ST7796S_BLACK);
displayText.writeText(Question[i], 3, 0, 0, 0, true, false);
displayText.writeText(Answer[i], 3, 0, 200, 0, true, true);
}
}
void sendData(int question, String answer){
webSocket.sendTXT("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Response\":\"" + String(answer) + "\",\"QuestionID\":\"" + String(question) + "\"}");
}
void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) {
const uint8_t* src = (const uint8_t*) mem;
USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
for(uint32_t i = 0; i < len; i++) {
if(i % cols == 0) {
USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
}
USE_SERIAL.printf("%02X ", *src);
src++;
}
USE_SERIAL.printf("\n");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -1,5 +1,4 @@
## Bram's Learning Curve
Here I post my progress on learning and mastering Arduino. I originally did the Game development study but decided to switch to "Technische Informatica". That is why I need to learn everything from scratch, and everything is new to me.
This is the reason that I made these documents, in order to track my progression in this new study.
@@ -11,35 +10,35 @@ Then I came across a teaching site (https://www.codingkids.nl/arduino-buzzer.htm
### Arduino Video
I watched a video about using Arduino and took a lot of op notes along the way.
The link is: (https://www.youtube.com/watch?v=BLrHTHUjPuw).
```
// $Arduino information:
\/\/Arduino information:
//pinnumber(locatie, in-/output)
\/pinnumber(locatie, in-/output)
//digitalwrite(locatie, high/low)
\/digitalwrite(locatie, high/low)
//delay(time in millisec)
\/delay(time in millisec)
//int... <- variable
\/int... <- variable
//with a decimal its a double -> 1,2
\/with a decimal its a double -> 1,2
//a character is a char -> "a"
\/a character is a char -> "a"
// $serial communications:
\/\/serial communications:
\/setup:
setup:
Serial.begin(9600) -> the text speed
Serial.sprintLn(text)
\/Loop:
Loop:
Serial.print("text")
Serial.printLn(....) -> variable because no "
\/Ctrl + shift + M = serial monitor.
//Ctrl + shift + M = serial monitor.
The text speed needs to be the same as given.
\/\/If Statements:
// $If Statements:
if(condition){
@@ -51,9 +50,9 @@ if(condition){
}
\/&& = "and"
// && = "and"
\/|| = "or"
// || = "or"
\/\/For loops:
@@ -63,22 +62,20 @@ For(int*i; i <= 10 ; i++){
}
\/The fading of led's:
// The fading of led's:
examples, basics, fade
\/ servo's
// servo's
examples, servo, sweep
```
### Linux and raspberry PI.
To gain more knowledge about Linux, I first asked my classmates if they could get me started.
They showed me how to gain access to a server and told me how to navigate through files.
By doing this I got taught the following commands:
```
~ $ 'ls -la' = show file / folders
~ $ 'top' = see currently running programs
@@ -92,7 +89,7 @@ By doing this I got taught the following commands:
~ $ 'ping ip addres'
~ $ 'ssh username@ip address' = open ssh connection.
```
### Air, temperature, and all sort of stuff.
After the Linux coding I decided to take a step back and began gaining experience with sensors.
@@ -104,12 +101,16 @@ I wanted to make my own spin on the original design by including a button to act
The rest of the tutorial was clear and worked like a charm.
the code used looks like this:
Begin by including a specific library for the DHT11.
```
#include "DHT.h"
#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
```
using This perticulair serial port, means that you do have to switch to it when you want to see results coming in.
The dht.begin command starts the process.
```
void setup() {
//the serial port:
Serial.begin(9600);
@@ -119,9 +120,11 @@ Serial.println(F("DHTxx test!"));
//the library start
dht.begin();
}
```
It starts by making float variables, to give over the information.
It also includes a error message in case of no feedback.
```
void loop() {
delay(2000);
//a float has decimal numbers and the library reads the measurements.
@@ -139,7 +142,7 @@ void loop() {
float hif = dht.computeHeatIndex(f, h);
float hic = dht.computeHeatIndex(t, h, false);
//all serial.ptint's send stuff to the serial board to showcase.
//all serial.print's send stuff to the serial board to showcase.
Serial.print(F("Humidity: "));
Serial.print(h);
Serial.print(F("% Temperature: "));
@@ -160,7 +163,14 @@ And here it looks in action:
Later on, I could expand this code and the physical product to include the rest of the sensors.
The wiring is shown here:
![my fritzing diagram](assets/DHT11_by_button_fritzing.png)
The red cables are 3v, the black cables are the ground and the green cable is the echo for data.
This version is using a DHT22, this is a updated version of a DHT11 but still provides the same information and still uses the same pins. So i decided to compromise for it, using it in my fritzing diagram.
The wire-frame is shown below:
![wireframe DHT11](assets/DHT11_by_button_wireframe.png)
This helps visualise all connections and shows what parts were used for reproductional .
### Buzzers .pt 2
I found out how to make multiple buzzers go off with the press of one button and increase as Mutch as there are pins.
@@ -174,18 +184,20 @@ The code is short and simple:
int button = 20;
int buzzerone = 12;
int buzzertwo = 11;
```
Now we set the pins up to either input or output a signal.
```
void setup() {
//put down some pins that will output , and some that input.
pinMode(button, INPUT);
pinMode(buzzerone, OUTPUT);
pinMode(buzzertwo, OUTPUT);
}
```
Here the button pin will seek a signal, when it is given it will send signals to the other given pins in the if-statement.
```
void loop() {
//read is there is input on the button pin, if so send output to the other pins., otherwise keep them off.
//read is there is input on the button pin, if so send output to the other pins, otherwise keep them off.
if(digitalRead(button) == HIGH){
digitalWrite(buzzerone, HIGH);
digitalWrite(buzzertwo, HIGH);
@@ -196,16 +208,26 @@ void loop() {
}
```
Here I made the physical design but instead of buzzers i used lights in order to show it working.
Here I made the physical design but instead of buzzers I used lights in order to show it working.
![board turned off.](<assets/Buzzer board off.jpg>)
And here is the the board working:
![board working and turned on.](<assets/Buzzer board on.jpg>)
To Show my wiring more clearly, here is my fritzing board:
![fritzing_buttons pt2](assets/Buzzer_board_fritzing.png)
the red wires are 3v, the black wires are the ground connections.
Here the wireframe is shown for parts specification and possible reproduction:
![wireframe _ buzzers pt2](assets/Buzzer_board_wireframe.png)
### Python For Dummies.
My job was to make a connection between the WebSocket and the database we had set up, and to do this we wanted to use python.
I had never used python before and was totally new to the entire code structure and wording.
Because I was totally new to all of python, I asked ChatGPT for and example and some concrete code.
I asked it my question which was the assignment I was given and tried to reverse learn this way.
To give an easy picture, here is where i would come:
![my datqa-websocket connection](assets/myconnection.png)
It looks easy, but for someone who never worked wit hpython and linux, this will prove to be a challenge.
Because I was totally new to all of python I began asking friends for advice and started asking chatgpt for some examples.
and worked in reverse in order to understand python fully.
I went and looked up fitting tutorials on W3SChools.
The following were used:
@@ -243,4 +265,26 @@ elif c == 2:
Even if it looked simple, this was the ignition I needed to understand python better and continue my own research.
### Python for dummmies pt2
after some intense trail and error, I managed to connect to the websocket.
After some intense trail and error, I managed to connect to the websocket.
After wich I also managed to send infromation to the database by including details like pasword and such.
I began investigating deeper and asked for other people's vision on my code.
It wasa hard to notice what problems there were, but eventualy me and a classmate found out that the problem was inside of the database itself instead of the code. So after fixing some issues with the primary keys and some tesing with the code, I managed to fix the issues that popped up.
Now the code is able to send websocket data to the database under the name of node "1".
This will have to be updated so all names could be sent to the database without causing issues.
But the current code does what we expect from sprint two but I will surely continue working on making it perfect.
### python for dummies pt3
After the sprint review for sprint two, we as a team decided that it was time to make progress with connecting more nodes, and so I did.
I began searching for leads on how to grab information from a database, and used this website to teach me:
https://www.w3schools.com/python/python_mysql_select.asp
Once I figured out how to grab information, I wanted to put it in an array and look if the connected node(wich I put in a varriable) was already known in the array. The webpage for this was: https://stackabuse.com/python-check-if-array-or-list-contains-element-or-value/
This originaly didn't work, and by printing all my variables and the array I initially didn't see anything that would prevent it from working.
But for some reason the given node wouldn't compare itself to the nodes in the array.
That is when I found out the data from the array was in a tuple. So when I changed the variable to turn into a tuple, it worked like a charm.
Then after all of this trouble, I finished by putting the new node MAC (in string format) into the correct collum in the database so this will MAC will be saved in the future.

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

Before

Width:  |  Height:  |  Size: 572 KiB

After

Width:  |  Height:  |  Size: 572 KiB

View File

Before

Width:  |  Height:  |  Size: 596 KiB

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/assets/Node.fzz Normal file

Binary file not shown.

BIN
docs/assets/Node.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

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

View File

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

View File

@@ -0,0 +1,117 @@
# Nodes
## Introduction
The nodes are the devices that are placed in the rooms. The nodes are used to collect the data from the sensors. Every node is connected to the websocket, and sends their data with their mac address in json format. The websocket broadcasts the node data back to all clients, and since our website functions as a client it also receives the data. Every node will, depending on what node, be made into a class.
## Requirements
### Sensornode
- Every node has to have a unique nodeID
- Every node has to have their corresponding sensorsvalues in form of arrays
### Feedbacknodes
- Every node has to have a unique nodeID
- Every node has to have their corresponding feedback in form of a 2D array
## Class diagrams
### Node
```mermaid
classDiagram
class Node {
+nodeID
+processNodeData()
+updateNodeData()
}
```
#### Sensornode
```mermaid
classDiagram
class SensorNode extends Node {
+tempArray
+humiArray
+eco2Array
+tvocArray
}
```
#### Feedbacknode
```mermaid
classDiagram
class FeedbackNode extends Node {
+feedbackArray
}
```
# Graphs
## Introduction
The graphs are used to display the data from the sensors. The data is collected by the raspberry pi and then displayed on the graphs. The graphs are made using the [plotly library](https://plotly.com/javascript/) .
## Requirements
### Live graphs
- Every node has to have a live graph
- The graphs has to be updated every 5 seconds
- All the data from one node has to fit in one graph
## Class diagrams
### Graphs
```mermaid
classDiagram
class graph {
+nodeId
makeGraph()
}
```
### Live graphs
```mermaid
classDiagram
class liveGraph extends graph {
+cnt
+timeArray
+tempArray
+humiArray
+eco2Array
+tvocArray
makeGraph()
updateGraph()
updateData()
}
```
## Order of operations
### Live graphs
```mermaid
sequenceDiagram
participant Node
participant Raspberry pi
participant Website
Node->>Raspberry pi: sensordata via websocket every 5 seconds
Raspberry pi->>Website: Node data via websocket if new data is received from the node
Website->>Website: updateGraph()
Website->>Website: updateData()
```
1. Every node sends its data to the raspberry pi via websocket every 5 seconds
2. The raspberry pi sends the data to the website via websocket if new data is received from the node
3. The website updates the data coming from the raspberry pi on its own variables and arrays
4. The website updates the live graphs every time new data is received from the websocket

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
# OOP within Arduino
Object-Oriented Programming (OOP) is a way of programing that provides a means of structuring your code so the code is modular and can be used more often without making huge changes or having to copy past the same code.
## Abstraction
Abstraction in OOP is the process of exposing only the required essential variables and functions. This means hiding the complexity and only showing the essential features of the object. In Arduino, this could mean creating a class like `Sensor node` with methods such as `setUp()`, `displayData()`, and `checkForError()`.
## Encapsulation
Encapsulation is the technique used to hide the data and methods within an object and prevent outside access. In Arduino, this could mean having private variables and methods in a class that can only be accessed and modified through public methods.
## Which classes did we use
In this Arduino project, we used several classes to organize our code and manage the complexity of the project. Some of these classes include:
- `websockets`: This class is responsible for managing the WebSocket connections.
- `nodeReadings`: This class is responsible for managing the sensor readings.
Each of these classes encapsulates the data and methods related to a specific part of the project, making the code easier to understand and maintain.

View File

@@ -0,0 +1,43 @@
### Uml diagram:
``` mermaid
classDiagram
namespace Esp {
class Websockets{
+webSocket
+_WiFiMulti
hexdump()
websocketSetup()
loop()
webSocketEvent()
sendMyText()
}
class NodeReadings{
+TVOC_base, eCO2_base
+counter
+eCO2
+TVOC
+interval
+temperature
+humidity
+currentMillis
+lastMillis
+errorSGP30
+errorDHT11
+noise
setup()
loop()
resetValues()
update()
checkForError()
displayData()
}
}
```

View File

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

View File

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

View File

@@ -0,0 +1,80 @@
# Node
Since the node is what gathers all the data from the sensors, it is the most important part of the system. The node is responsible for reading the data from the sensors, processing it, and sending it to the server.
## Hardware
The node is composed of the following hardware components:
- [ESP32 S3 DevkitC](https://www.espressif.com/en/products/socs/esp32-s3)
- [SGP30](https://www.sensirion.com/en/environmental-sensors/gas-sensors/sgp30/)
- [DHT11](https://www.sparkfun.com/datasheets/Sensors/Temperature/DHT11.pdf)
- [OLED Display](https://www.tinytronics.nl/nl/displays/oled/1.3-inch-oled-display-128*64-pixels-wit-i2c)
## Software
The node is programmed using the Arduino IDE. The code is written in C++ and is responsible for reading the data from the sensors, processing it, and sending it to the server. The first couple versions of the code were written by Sietse, and because we all had to use Object Oriented Programming, Dano rewrote the code to be object-oriented. Which is now final version
### Libraries
The following libraries are used in the node code:
- Wire.h
- Adafruit_SH110X.h
- Adafruit_SGP30.h
- DHT.h
- WiFiMulti.h
- WiFi.h
- WebSocketsClient.h
- nodeCodeHeader.h
### Code
The code is divided into the following classes:
- Node readings
- Websockets
The two classes that are used are split into the 2 becouse the node readings handels everything about reading information from the sensors and displaying them on the screen, all the local stuff is handeled here so to speak. And into Websockets this handels every thing from connecting to the wifi to sending the data that is recorded from the sensors into json format and sending that data to the websockets so that the data can be prossed over there.
### Communication
The node communicates with the server using WebSockets. The node sends the data to our website which uses a reverse proxy to route it to the websocket. The server then processes the data and stores it in the database / puts it on the website. We use the WebSocketsClient library to communicate with the server. Because [websocket connections](docs\rpi-documentation\websockets.md) are stateful, we need to keep the connection alive. So we do not use any delays in the code, but instead use the millis() function to keep track of time.
Flow of the data:
```mermaid
flowchart LR
A(Sensors)
B(Node)
D(Websocket)
E(Website)
F(DB-Client)
G(Database)
A-->|sensordata| B
B -->|JSONData| D
D -->|JSONData| E
D -->|JSONData| F
F -->|Data| G
```
### Wiring Diagram
The wiring diagram for the node is as follows:
![Wiring diagram of node](..\assets\imagesSp3\wiringDiagramNode.png)
### Fritsing Diagram
![fritsing diagram of node](..\assets\Node.png)
## Future Improvements
The node is currently working as intended, but there are some improvements that could be made:
- The node could be made more energy efficient by putting the ESP32 in deep sleep mode when it is not sending data.
- The node could be made more robust by adding error handling to the code.
- The node could be made more secure by adding encryption to the data that is sent to the server.

View File

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

View File

@@ -0,0 +1,71 @@
# To edit data in the database we wanna use PUT request.
### What is a put request?
To edit data in the database we wanna use PUT request. A PUT request is used to update an existing resource. If the resource does not exist, it will create a new one. The PUT request requires the client to send the entire updated resource, not just the changes. This means that the client must send all the data, even if only one field has changed.
A put request is a json its for example looks like this:
```json
{
"NodeID": 1,
"Location": "testlocation",
"Name": "testname",
}
```
### How to use a put request
We have been trying to use a PUT request with flask. But that didn't work because the reverse proxy would reform the request to a GET request. So we tried to let apache 2 run it with wsgi.
### What is wsgi?
WSGI stands for Web Server Gateway Interface. It is a specification that describes how a web server communicates with web applications, and how web applications can be chained together to process one request. WSGI is a Python standard, and it is implemented by most Python web frameworks.
We couldnt get it to work even though we worked trough all the errors apache 2 gave us.
We first had some paths misconfigured. Then we had Forbidden error because we didn't give permission for apache2 to use that path.
And now we ended on not found and we verified that all configuration is in the correct syntrax with ```apachectl configtest```
### How to use wsgi
To use wsgi you need to install mod_wsgi with the following command:
```bash
sudo apt-get install libapache2-mod-wsgi
```
Then you need to enable the module with the following command:
```bash
sudo a2enmod wsgi
```
Then you need to create a wsgi file in the same directory as your python file. The wsgi file should look like this:
```python
import sys
sys.path.insert(0, '/home/pi/webapp')
from mainflask import app as application
```
Then you need to configure your apache2 configuration file to use the wsgi file. The configuration file should look like this:
```apache
<VirtualHost *:80>
DocumentRoot /home/pi/www/html/
WSGIDaemonProcess webapp python-path=/home/pi/webapp
WSGIScriptAlias /flask /home/pi/webapp/main.wsgi
<Directory /home/pi/webapp>
WSGIProcessGroup webapp
WSGIApplicationGroup %{GLOBAL}
Require all granted
</Directory>
# Logging
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
```
Then you need to restart apache2 with the following command:
```bash
sudo systemctl restart apache2
```

View File

@@ -2,7 +2,7 @@
-- Wed Mar 13 16:04:58 2024
-- Model: New Model Version: 1.0
-- MySQL Workbench Forward Engineering
-- CHANGE NODEID TO AUTO INCREMENET IN NODE TABLE
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';

View File

@@ -1,43 +1,86 @@
from flask import Flask, request
from flask import Flask, request, jsonify
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)
return getData(node, dataType)
MAC = request.args.get('MAC', default = None)
dateStart = request.args.get('dateStart', default = None)
dateEnd = request.args.get('dateEnd', default = None)
return getData(node, dataType, MAC, dateStart, dateEnd)
def getData(node, dataType):
try:
@app.route('/updateData')
def updateDataIndex():
node_id = request.args.get('node', None)
new_name = request.args.get('name', None)
new_location = request.args.get('location', None)
return updateData(node_id, new_name, new_location)
@app.route('/getNodeInfo')
def getNodeInfoIndex():
macAdress = request.args.get('macAdress', None)
return getNodeInfo(macAdress)
@app.route('/getQuestionData')
def getQuestionDataIndex():
return getQuestionData()
def updateData(node, name, location):
mydb = loginDB()
query = update_query(node, name, location, False, False)
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
cursor.execute(query)
mydb.commit()
result = cursor.fetchall() # Fetch the results
cursor.close()
mydb.close()
return result
def loginDB():
mydb = mysql.connector.connect(
host="localhost",
user="root",
password="Dingleberries69!",
database="NodeData"
)
#turn this into a switch statement
cursor = mydb.cursor()
if node:
query = f"SELECT * FROM Measurement WHERE NodeID = {node}"
elif dataType:
query = f"SELECT * FROM Measurement WHERE Type = '{dataType}'"
else:
query = "SELECT * FROM `Measurement`"
return mydb
def getData(node, dataTypes, MAC, dateStart, dateEnd):
mydb = loginDB()
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
# Convert the results to a string for display
result_str = ', '.join([str(row) for row in result])
cursor.close()
mydb.close()
return result
except mysql.connector.Error as err:
print("MySQL Error:", err)
return "MySQL Error: " + str(err)
def getNodeInfo(macAdress):
mydb = loginDB()
query = get_query(False, False, macAdress, False, False, False, False)
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
cursor.execute(query)
result = cursor.fetchall() # Fetch the results
cursor.close()
mydb.close()
return result
def getQuestionData(questionID, replies):
mydb = loginDB()
query = get_query(False, False, False, questionID, replies)
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
cursor.execute(query)
result = cursor.fetchall() # Fetch the results
cursor.close()
mydb.close()
return result
if __name__ == '__main__':
app.run(debug=True, host='localhost')
app.run(debug=True, host='localhost')

43
server/Flask/queries.py Normal file
View File

@@ -0,0 +1,43 @@
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"""
UPDATE Node
SET Name = '{name}', Location = '{location}'
WHERE NodeID = {node};
"""
return query

65
server/brams-script.py Normal file
View File

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

View File

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

View File

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

20
server/reverseproxy Normal file
View File

@@ -0,0 +1,20 @@
<VirtualHost *:80>
ProxyPreserveHost On
DocumentRoot /home/pi/www/html/
# Enable proxying WebSockets
ProxyPass /ws ws://localhost:8001/
ProxyPassReverse /ws ws://localhost:8001/
# Enable proxying HTTP
ProxyPass /http http://localhost:8080/
ProxyPass /putData http://localhost:5000/putData
ProxyPassReverse /putData http://localhost:5000/putData
ProxyPass /flask http://localhost:5000/
ProxyPassReverse /flask http://localhost:5000/
ProxyPreserveHost On
# Logging
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

View File

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

View File

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

View File

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

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

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

View File

@@ -0,0 +1,69 @@
## 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.
and a variable with the MAC-addresses.
```py
# A general class which acts as a database connector and a node identifier
class Node():
def __init__(self, macAdress):
self.macAdress = macAdress
self.id = None
```
The function below uses the infromation from the database to find the corresponding node ID from the gotten MAc-address.
It searches inside of the database and finds a match with the given MAC-address.
It first makes a connection with the database by calling the previous function, then it executes the given querry, and searches for the node ID corresponding with the inserted MAC-address.
After this search, it inserts that given id into the ID variable.
```py
def getNodeId(self):
id = (self.macAdress,)
#loging in
mydb = dbLogin()
#make a cursor.
cursor = mydb.cursor()
cursor.execute("""SELECT nodeID FROM Node WHERE MAC = %s""", id)
#get all the data
data = cursor.fetchall()
#make a for-loop to go along all the tuples in the data, and then all items in the tupe and those items get put into the ID
for tuples in data:
for item in tuples:
self.id = item
```

View File

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

View File

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

View File

@@ -42,4 +42,56 @@ and im going to continue with working on the connection between the websocket an
Sam: making a rest api to pull stuff from database.
Sietse: OOP python.
Bram: Finaly finishing connection between database and websocket.
}
}
14 - 3- 2024 {
Sam: rest api building.
Dano: OOP arduino.
sietse: making the website dynamic.
Bram: Documentation updating for review.
}
15 - 3- 2024 {
We are all making preperations for the sprint-review.
}
3/19/2024
Dano, oop arduino
Bram, nodes registereen als er een nieuwe binnenkomt
Sietse, onderdelen bestellen, verder user story 46: nodes beheren op website.
Sam, tft scherm, rest api ombouwen om data in node tabel te douwen
20/03/2024:
Dano: Adding comments for OOP Arduino
Bram: Python send node data to database
Sam: flask rerouten to use put recuest
Sietse: WireFrame
21/03/2024
Sietse: fritsing node documentation
Bram: personal documetation.
Sam: Rest api improvements
Dano: prepering for retro and making docu for aruidno
22/03/2024
Bram: Fritsing, documentation for code
Sietse: Database
Dano: Docuemtation for arduino
Sam:
26/03/2024
Sam: gebouw beheer vragen, Wall mounted enquete doos
Sietse: rest api encete vragen
Bram: expert voorbereigen, node data updaten
Dano: Documentatie schreiven,
27/03/2024
Bram: OOP python
Sam: new website
Sietse: enquete screen data sending to database and database cript better
Dano: documentation about oop arduino

View File

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

BIN
web/newWebsite/arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
web/newWebsite/gauge.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

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

View File

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

View File

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

53
web/newWebsite/index.html Normal file
View File

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

View File

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

144
web/newWebsite/main.js Normal file
View File

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

View File

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

View File

@@ -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(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(255, 99, 132, 0.2)'
],
borderColor: [
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(255, 99, 132, 1)'
],
borderWidth: 1
}]
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

25
web/newWebsite/text.html Normal file
View File

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

96
web/newWebsite/text.js Normal file
View File

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

View File

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