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
7
arduino/Screen code/Screen-code-final/.theia/launch.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
]
|
||||
}
|
115
arduino/Screen code/Screen-code-final/Screen-code-final.ino
Normal 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");
|
||||
}
|
||||
|
106
arduino/Screen code/Screen-code-final/displayText.cpp
Normal 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
|
||||
}
|
@@ -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);
|
22
arduino/Screen code/Screen-code-final/headerFile.h
Normal 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;
|
@@ -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);
|
||||
}
|
106
arduino/Screen code/screen-code-class/displayText.cpp
Normal 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
|
||||
}
|
19
arduino/Screen code/screen-code-class/displayText.h
Normal 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
|
@@ -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
|
||||
|
@@ -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) + "\"}");
|
||||
}
|
@@ -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:
|
||||

|
||||
It also turns on:
|
||||

|
||||
|
||||
Here are my fritzing and wireframe, the components used are shown but The DHT11 is replaced by a DHT22.
|
||||

|
||||

|
||||
|
||||
### 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
|
BIN
docs/LearningProcessBram/ArduinoExperience/image.png
Normal file
After Width: | Height: | Size: 96 KiB |
@@ -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:
|
||||

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

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

|
||||
And here is the the board working:
|
||||

|
||||
|
||||
To Show my wiring more clearly, here is my fritzing board:
|
||||

|
||||
the red wires are 3v, the black wires are the ground connections.
|
||||
|
||||
Here the wireframe is shown for parts specification and possible reproduction:
|
||||

|
||||
|
||||
### 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:
|
||||

|
||||
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.
|
BIN
docs/LearningProcessBram/documentatie/assets/2Covers.jpg
Normal file
After Width: | Height: | Size: 492 KiB |
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
After Width: | Height: | Size: 215 KiB |
After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 572 KiB After Width: | Height: | Size: 572 KiB |
Before Width: | Height: | Size: 596 KiB After Width: | Height: | Size: 596 KiB |
BIN
docs/LearningProcessBram/documentatie/assets/DHT11 wires.png
Normal file
After Width: | Height: | Size: 298 KiB |
After Width: | Height: | Size: 198 KiB |
After Width: | Height: | Size: 89 KiB |
BIN
docs/LearningProcessBram/documentatie/assets/myconnection.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
docs/assets/Node.fzz
Normal file
BIN
docs/assets/Node.png
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
docs/assets/imagesSp3/retrosprint3.jpg
Normal file
After Width: | Height: | Size: 1.5 MiB |
BIN
docs/assets/imagesSp3/wiringDiagramNode.png
Normal file
After Width: | Height: | Size: 130 KiB |
@@ -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:
|
||||

|
||||
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.
|
@@ -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
|
||||
|
117
docs/brainstorm/SoftwareDocumentatie/classes.md
Normal 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
|
||||
|
||||
|
77
docs/brainstorm/UML-infrastructureV2.md
Normal 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()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
```
|
24
docs/brainstorm/gebouwBeheer.md
Normal 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.
|
20
docs/node-documentation/NodeClassDocumentation.md
Normal 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.
|
43
docs/node-documentation/NodeClassUml.md
Normal 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()
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
```
|
@@ -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
|
||||
|
@@ -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:
|
80
docs/node-documentation/node.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
|
||||
### Fritsing Diagram
|
||||
|
||||

|
||||
|
||||
## 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.
|
296
docs/rpi-documentation/Databaseconnection.md
Normal 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.
|
71
docs/rpi-documentation/Put-Request.md
Normal 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
|
||||
```
|
||||
|
||||
|
@@ -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';
|
||||
|
@@ -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
@@ -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
@@ -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())
|
||||
|
31
server/bullshitSenderENQUETE.py
Normal 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
|
33
server/bullshitSenderSENSOR.py
Normal 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
@@ -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>
|
22
server/web-data-connection/PythonMermaid.md
Normal 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()
|
||||
}
|
||||
```
|
97
server/web-data-connection/data.py
Normal 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())
|
26
server/web-data-connection/databaseGeneralClass.py
Normal 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
|
155
server/web-data-connection/datatransfer.md
Normal 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())
|
||||
```
|
78
server/web-data-connection/enqueteClassFile.md
Normal 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()
|
||||
```
|
36
server/web-data-connection/enqueteNodeClass.py
Normal 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()
|
69
server/web-data-connection/generalDatabaseFile.md
Normal 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
|
||||
```
|
41
server/web-data-connection/sensorNodeClass.py
Normal 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()
|
77
server/web-data-connection/sensorNodeClassFile.md
Normal 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()
|
||||
```
|
@@ -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
|
51
web/newWebsite/GaugGroup.js
Normal 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
After Width: | Height: | Size: 22 KiB |
BIN
web/newWebsite/gauge.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
163
web/newWebsite/graph-classes.js
Normal 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();
|
||||
}
|
||||
}
|
179
web/newWebsite/graph-main.js
Normal 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);
|
||||
});
|
||||
}
|
35
web/newWebsite/graphs.html
Normal 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
@@ -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>
|
79
web/newWebsite/liveGraph.js
Normal 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
@@ -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);
|
||||
}
|
||||
}
|
27
web/newWebsite/questions-chart-class.js
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
web/newWebsite/questions-creation-class.js
Normal 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
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
56
web/newWebsite/questions-dashboard.html
Normal 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>
|
92
web/newWebsite/questions-main.js
Normal 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);
|
||||
}
|
249
web/newWebsite/styles/dashboard-styles.css
Normal 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;
|
||||
}
|
128
web/newWebsite/styles/graph-styles.css
Normal 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;
|
||||
}
|
71
web/newWebsite/styles/questions-dashboard-styles.css
Normal 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;
|
||||
}
|
202
web/newWebsite/styles/settings-styles.css
Normal 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
@@ -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
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@@ -72,8 +72,6 @@ p1 {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.nodeData {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
|