Merge branch 'main' of gitlab.fdmci.hva.nl:propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59
This commit is contained in:
@@ -16,7 +16,7 @@ void DisplayText::writeText(char* text, int size, int posX, int posY, int screen
|
|||||||
// }
|
// }
|
||||||
tft.setCursor(posX, posY);
|
tft.setCursor(posX, posY);
|
||||||
tft.setTextSize(size);
|
tft.setTextSize(size);
|
||||||
printWordsFull(text);
|
printWordsFull(text, bottom);
|
||||||
delay(screenTime);
|
delay(screenTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,38 +39,68 @@ int DisplayText::centerText(char* text) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
//attempt to write the text out in full (wip)
|
//attempt to write the text out in full (wip)
|
||||||
void DisplayText::printWordsFull(char* text) {
|
void DisplayText::printWordsFull(char* text, bool bottom) {
|
||||||
const int screenWidth = 320; // replace with your TFT display width
|
const int screenWidth = 480; // replace with your TFT display width
|
||||||
const int lineHeight = 22; // replace with your text line height
|
const int lineHeight = 30; // replace with your text line height
|
||||||
|
//the double copy is needed so it doesnt alter the original text
|
||||||
char* word = strtok(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] = "";
|
char line[100] = "";
|
||||||
int lineCount = 0;
|
int lineCount = 0;
|
||||||
|
int lineWidth = 0;
|
||||||
|
|
||||||
while (word != NULL) {
|
// Calculate total number of lines
|
||||||
char tempLine[100];
|
int totalLines = 0;
|
||||||
strcpy(tempLine, line);
|
char* tempWord = strtok_r(newtext2, " ", &saveptr2);
|
||||||
strcat(tempLine, word);
|
while (tempWord != NULL) {
|
||||||
strcat(tempLine, " ");
|
|
||||||
|
|
||||||
int16_t x1, y1;
|
int16_t x1, y1;
|
||||||
uint16_t w, h;
|
uint16_t w, h;
|
||||||
tft.getTextBounds(tempLine, 0, 0, &x1, &y1, &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
|
||||||
|
|
||||||
if (w > screenWidth && strlen(line) > 0) {
|
// Reset variables for actual printing
|
||||||
tft.setCursor(0, lineHeight * lineCount);
|
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);
|
tft.println(line);
|
||||||
lineCount++;
|
lineCount++;
|
||||||
strcpy(line, word);
|
strcpy(line, word);
|
||||||
strcat(line, " ");
|
strcat(line, " ");
|
||||||
|
lineWidth = w;
|
||||||
} else {
|
} else {
|
||||||
strcpy(line, tempLine);
|
strcat(line, word);
|
||||||
|
strcat(line, " ");
|
||||||
|
lineWidth += w;
|
||||||
}
|
}
|
||||||
|
|
||||||
word = strtok(NULL, " ");
|
word = strtok_r(NULL, " ", &saveptr1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// print the last line
|
// print the last line
|
||||||
tft.setCursor(0, lineHeight * lineCount);
|
int y = bottom ? tft.height() - lineHeight * (totalLines - lineCount) : lineHeight * lineCount;
|
||||||
|
tft.setCursor(0, y);
|
||||||
tft.println(line);
|
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;
|
Adafruit_ST7796S_kbv& tft;
|
||||||
int centerText(char* text);
|
int centerText(char* text);
|
||||||
// int bottomText(char* text);
|
// int bottomText(char* text);
|
||||||
void printWordsFull(char* text);
|
void printWordsFull(char* text, bool bottom);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DisplayText(Adafruit_ST7796S_kbv& tftDisplay);
|
DisplayText(Adafruit_ST7796S_kbv& tftDisplay);
|
||||||
|
@@ -28,6 +28,7 @@ nav:
|
|||||||
- Infrastructure: brainstorm/UML-infrastructure
|
- Infrastructure: brainstorm/UML-infrastructure
|
||||||
- Taskflow: brainstorm/Taskflow
|
- Taskflow: brainstorm/Taskflow
|
||||||
- Design: Sp1SchetsProject/FirstDesign
|
- Design: Sp1SchetsProject/FirstDesign
|
||||||
|
- Interview facility manager: brainstorm/bebouwBeheer.md
|
||||||
- 🖨️ Software:
|
- 🖨️ Software:
|
||||||
- Dev page: brainstorm/SoftwareDocumentatie/Dev_page
|
- Dev page: brainstorm/SoftwareDocumentatie/Dev_page
|
||||||
|
|
@@ -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
BIN
docs/LearningProcessBram/ArduinoExperience/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
BIN
docs/LearningProcessBram/documentatie/assets/2Covers.jpg
Normal file
BIN
docs/LearningProcessBram/documentatie/assets/2Covers.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 492 KiB |
@@ -43,34 +43,52 @@ 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.
|
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.
|
To do this i took the front of the plastic design, and the front of the wooden design.
|
||||||
They lloked like this:
|
They looked like this:
|
||||||
|

|
||||||
1. which design is more likable
|
1. which design is more likable
|
||||||
|
|
||||||
/Sebas : plastic looks better and goes straight to the point.
|
/Sebas : plastic looks better and goes straight to the point.
|
||||||
|
|
||||||
/Skip : plastic looks more clean.
|
/Skip : plastic looks more clean.
|
||||||
|
|
||||||
/Dano : plastic is more smooth.
|
/Dano : plastic is more smooth.
|
||||||
|
|
||||||
/Sietse : plastic is better.
|
/Sietse : plastic is better.
|
||||||
|
|
||||||
/Ishak : Wood is more ecofriendly.
|
/Ishak : Wood is more ecofriendly.
|
||||||
|
|
||||||
/Nailah : PLastic looks cooler
|
/Nailah : PLastic looks cooler
|
||||||
|
|
||||||
|
|
||||||
2. which design is more distracting.
|
2. which design is more distracting.
|
||||||
|
|
||||||
/Sebas : wood is more distracting and more obvious.
|
/Sebas : wood is more distracting and more obvious.
|
||||||
|
|
||||||
/Skip : wood, because the school walls are more darker themed.
|
/Skip : wood, because the school walls are more darker themed.
|
||||||
|
|
||||||
/Dano : wood looks off.
|
/Dano : wood looks off.
|
||||||
|
|
||||||
/Sietse : wood would be out of place.
|
/Sietse : wood would be out of place.
|
||||||
|
|
||||||
/Ishak : Wood, but in a good way.
|
/Ishak : Wood, but in a good way.
|
||||||
|
|
||||||
/Nailah : plastic looks more interesting.
|
/Nailah : plastic looks more interesting.
|
||||||
|
|
||||||
|
|
||||||
3. Any further comments about the design?
|
3. Any further comments about the design?
|
||||||
|
|
||||||
/Sebas : Don't do wood, plastic is more sleek.
|
/Sebas : Don't do wood, plastic is more sleek.
|
||||||
|
|
||||||
/Skip : plastic looks more professional.
|
/Skip : plastic looks more professional.
|
||||||
|
|
||||||
/Dano : no.
|
/Dano : no.
|
||||||
|
|
||||||
/Sietse: no.
|
/Sietse: no.
|
||||||
|
|
||||||
/Ishak : in the wood you can burn in your logo.
|
/Ishak : in the wood you can burn in your logo.
|
||||||
|
|
||||||
/Nailah : The wood can look better inside the school.
|
/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.
|
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.
|
So from this information we will continue to make plastic cases for the nodes.
|
@@ -1,12 +0,0 @@
|
|||||||
# Talk with building management
|
|
||||||
|
|
||||||
## 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)
|
|
||||||
|
|
||||||
## 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. 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 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 technically active, to put it that way. 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 it vandalizing students proof.
|
|
24
docs/brainstorm/gebouwBeheer.md
Normal file
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.
|
@@ -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
|
## Sources
|
||||||
* https://www.tinytronics.nl/en/displays/tft/4-inch-tft-display-320*480-pixels-with-touchscreen-spi-st7796s Source for Driver
|
* 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
|
* https://github.com/prenticedavid/Adafruit_ST7796S_kbv Download link for the library
|
||||||
|
@@ -1,8 +1,12 @@
|
|||||||
|
# Original Version (by bram)
|
||||||
|
|
||||||
## Python code + explaination
|
## Python code + explaination
|
||||||
|
|
||||||
We wanted to make a working connection between our websocket wich runs all the data gatherd by our nodes and a live feed to our database.
|
We wanted to make a working connection between our websocket wich runs all the data gatherd by our nodes and a live feed to our database.
|
||||||
So we set out to make this connection using python.
|
So we set out to make this connection using python.
|
||||||
|
|
||||||
At first we needed to import the folowing librarys:
|
At first we needed to import the folowing librarys:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// everything is running async so the code can run together and not interfere with eachother.
|
// everything is running async so the code can run together and not interfere with eachother.
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -11,9 +15,11 @@ import websockets
|
|||||||
import mysql.connector
|
import mysql.connector
|
||||||
import json
|
import json
|
||||||
```
|
```
|
||||||
|
|
||||||
Then we began the process of connecting with both the websocket and the database.
|
Then we began the process of connecting with both the websocket and the database.
|
||||||
|
|
||||||
First-off, we began making a connection to the database by using a mysql library in wich we gave the log in in order to connect to our ow database.
|
First-off, we began making a connection to the database by using a mysql library in wich we gave the log in in order to connect to our ow database.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//the data that has to be pushed needs to be
|
//the data that has to be pushed needs to be
|
||||||
async def process_data(data):
|
async def process_data(data):
|
||||||
@@ -25,8 +31,10 @@ async def process_data(data):
|
|||||||
database="*******"
|
database="*******"
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Then after this we code in the infromation we want to put inside of the database.
|
Then after this we code in the infromation we want to put inside of the database.
|
||||||
The data collected from the websocket is json data, so this has to be changed.
|
The data collected from the websocket is json data, so this has to be changed.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//Making a variable for the database connection
|
//Making a variable for the database connection
|
||||||
cursor = mydb.cursor()
|
cursor = mydb.cursor()
|
||||||
@@ -70,9 +78,11 @@ The data collected from the websocket is json data, so this has to be changed.
|
|||||||
cursor.close()
|
cursor.close()
|
||||||
mydb.close()
|
mydb.close()
|
||||||
```
|
```
|
||||||
|
|
||||||
After fully connecting t othe database, making statements of what to put there and telling the code what to do, we ofcourse need to write the code to connect to the weebsocket.
|
After fully connecting t othe database, making statements of what to put there and telling the code what to do, we ofcourse need to write the code to connect to the weebsocket.
|
||||||
We begin by telling our websocket id and what type of port we are using.
|
We begin by telling our websocket id and what type of port we are using.
|
||||||
Then we will collect live data from the conected websocket, store it in a variable, and then in the previous code
|
Then we will collect live data from the conected websocket, store it in a variable, and then in the previous code
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//here the connection to the websocked is made
|
//here the connection to the websocked is made
|
||||||
async def receive_data():
|
async def receive_data():
|
||||||
@@ -95,3 +105,164 @@ async def main():
|
|||||||
|
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# 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\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.
|
@@ -12,7 +12,7 @@ async def send_data(uri):
|
|||||||
data = {
|
data = {
|
||||||
"node": "69:42:08:F5:00:00",
|
"node": "69:42:08:F5:00:00",
|
||||||
"Response": str(round(random.uniform(0, 2))),
|
"Response": str(round(random.uniform(0, 2))),
|
||||||
"QuestionID": str(round(random.uniform(0, 90))),
|
"QuestionID": str(round(random.uniform(1, 2))),
|
||||||
}
|
}
|
||||||
await websocket.send(json.dumps(data))
|
await websocket.send(json.dumps(data))
|
||||||
print("Data sent")
|
print("Data sent")
|
||||||
|
47
web/newWebsite/GaugGroup.js
Normal file
47
web/newWebsite/GaugGroup.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
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">
|
||||||
|
${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>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 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/gauge.png
Normal file
BIN
web/newWebsite/gauge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
33
web/newWebsite/graphs.html
Normal file
33
web/newWebsite/graphs.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<!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>
|
||||||
|
<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="settings.html" class="nav-link">Settings</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script src="graph-main.js"></script>
|
||||||
|
<script src="graph-classes.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
33
web/newWebsite/index.html
Normal file
33
web/newWebsite/index.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<!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>
|
||||||
|
<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="settings.html" class="nav-link">Settings</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script src="main.js"></script>
|
||||||
|
<script src="GaugGroup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
145
web/newWebsite/main.js
Normal file
145
web/newWebsite/main.js
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// Description: Main JavaScript file for the web application.
|
||||||
|
// arrays and stuff
|
||||||
|
const sensorData = {};
|
||||||
|
let liveGraphs = [];
|
||||||
|
let nodeArray = [];
|
||||||
|
let nodeDict = {};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createGauge(node, dataType) {
|
||||||
|
// Create a new gauge here
|
||||||
|
let gauge = new GaugeGroup(node, dataType); // Assuming Gauge is the name of the class
|
||||||
|
sensorData[node][dataType] = gauge; // Store the gauge in the sensorData object for later use
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//function for making the html elements for the following html code
|
||||||
|
function nodeData(data) {
|
||||||
|
let nodeData = document.createElement("div");
|
||||||
|
nodeData.innerHTML = data;
|
||||||
|
// nodeData.setAttribute("id", "node" + node);
|
||||||
|
document.body.appendChild(nodeData);
|
||||||
|
// console.log("Hello World");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateGauge(nodeNumber, dataType, value) {
|
||||||
|
// Update the gauge here
|
||||||
|
let gauge = sensorData[nodeNumber][dataType]; // Get the gauge from the sensorData object
|
||||||
|
gauge.update(value); // Assuming the Gauge class has an update method
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeInfo(node){
|
||||||
|
return fetch("http://145.92.8.114/getNodeInfo?macAdress=" + node)
|
||||||
|
.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
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
33
web/newWebsite/settings.html
Normal file
33
web/newWebsite/settings.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<!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/settings-styles.css">
|
||||||
|
<title>Gauges</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">
|
||||||
|
</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="settings.html" class="nav-link">Settings</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script src="main.js"></script>
|
||||||
|
<script src="GaugGroup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
172
web/newWebsite/styles/dashboard-styles.css
Normal file
172
web/newWebsite/styles/dashboard-styles.css
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding-top: 5vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #afafaf;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.gaugeGroup {
|
||||||
|
width: 98vw;
|
||||||
|
height: 20vh;
|
||||||
|
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;
|
||||||
|
float: top;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 */
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
168
web/newWebsite/styles/graph-styles.css
Normal file
168
web/newWebsite/styles/graph-styles.css
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding-top: 5vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #afafaf;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.gaugeGroup {
|
||||||
|
width: 98vw;
|
||||||
|
height: 20vh;
|
||||||
|
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;
|
||||||
|
float: top;
|
||||||
|
}
|
||||||
|
.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-bottom: 6vh; /* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.gaugeImage {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 200%;
|
||||||
|
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;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
bottom: -3.2vw; /* Adjust this value to move the text further down */
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.valueContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#valueText {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.needle {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -40%; /* Lower the needle to the bottom */
|
||||||
|
left: 50%;
|
||||||
|
width: 2px;
|
||||||
|
height: 110%;
|
||||||
|
background-color: black;
|
||||||
|
transform-origin: bottom;
|
||||||
|
z-index: 3; /* Make the needle display above the image */
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
0
web/newWebsite/styles/settings-styles.css
Normal file
0
web/newWebsite/styles/settings-styles.css
Normal file
Reference in New Issue
Block a user