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.setTextSize(size);
|
||||
printWordsFull(text);
|
||||
printWordsFull(text, bottom);
|
||||
delay(screenTime);
|
||||
}
|
||||
|
||||
@@ -39,38 +39,68 @@ int DisplayText::centerText(char* text) {
|
||||
// }
|
||||
|
||||
//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, " ");
|
||||
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;
|
||||
|
||||
while (word != NULL) {
|
||||
char tempLine[100];
|
||||
strcpy(tempLine, line);
|
||||
strcat(tempLine, word);
|
||||
strcat(tempLine, " ");
|
||||
|
||||
// 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(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) {
|
||||
tft.setCursor(0, lineHeight * lineCount);
|
||||
// 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 {
|
||||
strcpy(line, tempLine);
|
||||
strcat(line, word);
|
||||
strcat(line, " ");
|
||||
lineWidth += w;
|
||||
}
|
||||
|
||||
word = strtok(NULL, " ");
|
||||
word = strtok_r(NULL, " ", &saveptr1);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
|
@@ -28,6 +28,7 @@ nav:
|
||||
- Infrastructure: brainstorm/UML-infrastructure
|
||||
- Taskflow: brainstorm/Taskflow
|
||||
- Design: Sp1SchetsProject/FirstDesign
|
||||
- Interview facility manager: brainstorm/bebouwBeheer.md
|
||||
- 🖨️ Software:
|
||||
- Dev page: brainstorm/SoftwareDocumentatie/Dev_page
|
||||
|
BIN
docs/assets/Node.fzz
Normal file
BIN
docs/assets/Node.fzz
Normal file
Binary file not shown.
BIN
docs/assets/Node.png
Normal file
BIN
docs/assets/Node.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 216 KiB |
@@ -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
|
||||
* 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
|
||||
|
@@ -19,20 +19,23 @@ The node is programmed using the Arduino IDE. The code is written in C++ and is
|
||||
|
||||
The following libraries are used in the node code:
|
||||
|
||||
- <Wire.h>
|
||||
- <Adafruit_SH110X.h
|
||||
- <Adafruit_SGP30.h>
|
||||
- <DHT.h>
|
||||
- <WiFiMulti.h>
|
||||
- <WiFi.h>
|
||||
- <WebSocketsClient.>
|
||||
- <nodeCodeHeader.h>
|
||||
- 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:
|
||||
|
||||
uitleg dano
|
||||
- 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
|
||||
|
||||
@@ -63,6 +66,13 @@ 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:
|
||||
|
@@ -1,22 +0,0 @@
|
||||
// JavaScript
|
||||
function updateGauge(value) {
|
||||
const gaugeValue = document.getElementById('gaugeValue');
|
||||
const gaugeText = document.getElementById('gaugeText');
|
||||
const maxGaugeValue = 100; // Maximum value the gauge can display
|
||||
const rotationDegree = ((value / maxGaugeValue) * 180) - 90; // Convert value to degree (-90 to 90)
|
||||
|
||||
gaugeValue.style.transform = `rotate(${rotationDegree}deg)`;
|
||||
gaugeText.textContent = value; // Update the text
|
||||
|
||||
// Change color based on value
|
||||
if (value <= 40) {
|
||||
gaugeValue.style.backgroundColor = 'green';
|
||||
} else if (value <= 80) {
|
||||
gaugeValue.style.backgroundColor = 'orange';
|
||||
} else {
|
||||
gaugeValue.style.backgroundColor = 'red';
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
updateGauge(50); // Rotates the gauge to 0 degrees (50 out of 100), changes color to orange, and updates the text to "50"
|
@@ -1,60 +0,0 @@
|
||||
<!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="style.css">
|
||||
<title>bruh</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="gaugeGroup">
|
||||
<h2 class="groupTitle">Gauge Group</h2> <!-- Title above the gauges -->
|
||||
|
||||
<div class="Node">
|
||||
<div class="Sensorvalues">
|
||||
<div id="gaugeContainer" class="gaugeContainer">
|
||||
<img src="gauge.png" class="gaugeImage">
|
||||
|
||||
<div id="gaugeValue" class="gaugeValue"></div>
|
||||
<div id="gaugeText" class="gaugeText">0</div>
|
||||
<div class="gaugeCover"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Sensorvalues">
|
||||
<div id="gaugeContainer" class="gaugeContainer">
|
||||
<img src="gauge.png" class="gaugeImage">
|
||||
|
||||
<div id="gaugeValue" class="gaugeValue"></div>
|
||||
<div id="gaugeText" class="gaugeText">0</div>
|
||||
<div class="gaugeCover"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Sensorvalues">
|
||||
<div id="gaugeContainer" class="gaugeContainer">
|
||||
<img src="gauge.png" class="gaugeImage">
|
||||
|
||||
<div id="gaugeValue" class="gaugeValue"></div>
|
||||
<div id="gaugeText" class="gaugeText">0</div>
|
||||
<div class="gaugeCover"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Sensorvalues">
|
||||
<div id="gaugeContainer" class="gaugeContainer">
|
||||
<img src="gauge.png" class="gaugeImage">
|
||||
|
||||
<div id="gaugeValue" class="gaugeValue"></div>
|
||||
<div id="gaugeText" class="gaugeText">0</div>
|
||||
<div class="gaugeCover"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
<script src="gauge.js"></script>
|
@@ -1,88 +0,0 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.speedometer-scale {
|
||||
width: 8px;
|
||||
height: 280px;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
/* CSS */
|
||||
.gaugeContainer {
|
||||
width: 30vw;
|
||||
height: 300pxx;
|
||||
border: 3px solid black;
|
||||
fill: grey;
|
||||
margin: 50px auto;
|
||||
position: relative;
|
||||
overflow: hidden; /* Hide the first half of the gauge */
|
||||
}
|
||||
|
||||
.gaugeValue {
|
||||
width: 10px;
|
||||
height: 150px;
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: 50%;
|
||||
transform-origin: bottom;
|
||||
transition: transform 0.3s ease, background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.gaugeText {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
z-index: 2; /* Bring the value to the front */
|
||||
}
|
||||
|
||||
.gaugeImage {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: auto;
|
||||
z-index: 2; /* Bring the value to the front */
|
||||
}
|
||||
|
||||
.gaugeContainer {
|
||||
width: 80%; /* Adjust this value as necessary */
|
||||
height: 80%; /* Adjust this value as necessary */
|
||||
border: 3px solid black;
|
||||
fill: grey;
|
||||
margin: 10% auto;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.Node {
|
||||
display: flex; /* Use Flexbox */
|
||||
justify-content: space-between; /* Distribute the items evenly along the horizontal line */
|
||||
flex-wrap: wrap; /* Allow the items to wrap onto multiple lines if necessary */
|
||||
}
|
||||
|
||||
.Sensorvalues {
|
||||
flex: 1 0 20%; /* Allow the items to grow and shrink as necessary, and set the base width to 20% */
|
||||
margin: 10px; /* Add some margin around the items */
|
||||
background-color: #333; /* Dark background color */
|
||||
color: #fff; /* Light text color */
|
||||
padding: 20px; /* Some padding around the gauges */
|
||||
border-radius: 10px; /* Round borders */
|
||||
}
|
||||
|
||||
.gaugeGroup {
|
||||
background-color: #333; /* Dark background color */
|
||||
color: #fff; /* Light text color */
|
||||
padding: 20px; /* Some padding around the gauges */
|
||||
margin-bottom: 20px; /* Some margin below the group */
|
||||
}
|
||||
|
||||
.groupTitle {
|
||||
text-align: center; /* Center the title */
|
||||
margin-bottom: 20px; /* Some margin below the title */
|
||||
}
|
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/arrow.png
Normal file
BIN
web/newWebsite/arrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 8.7 KiB 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>
|
35
web/newWebsite/index.html
Normal file
35
web/newWebsite/index.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/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="GaugGroup.js"></script>
|
||||
<script src="main.js"></script>
|
||||
<script src="liveGraph.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
85
web/newWebsite/liveGraph.js
Normal file
85
web/newWebsite/liveGraph.js
Normal file
@@ -0,0 +1,85 @@
|
||||
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() {
|
||||
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.nodeId, 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);
|
||||
}
|
||||
}
|
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>
|
182
web/newWebsite/styles/dashboard-styles.css
Normal file
182
web/newWebsite/styles/dashboard-styles.css
Normal file
@@ -0,0 +1,182 @@
|
||||
* {
|
||||
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 */
|
||||
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
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