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

This commit is contained in:
Dano van den Bosch
2024-04-02 13:09:16 +02:00
34 changed files with 1361 additions and 283 deletions

View File

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

View File

@@ -0,0 +1,115 @@
#include "headerFile.h"
int i = 0;
int questionID = 1;
char* Question[] = {
"How clean are the toilets?",
"How clean is the study area?",
"What do you think of the temperature in the study area?",
"How crowded would you say the study area is?",
"Is there enough help available?"
};
char* Answer[] = {
"clean, normal, disgusting",
"clean, normal, disgusting",
"hot, perfect, cold",
"not at all, its fine, really crowded",
"no, decently, yes"
};
Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, MOSI, SCK, TFT_RST, MISO);
DisplayText displayText(tft);
void setup() {
//buttonpins
pinMode(16, INPUT_PULLDOWN);
pinMode(17, INPUT_PULLDOWN);
pinMode(18, INPUT_PULLDOWN);
Serial.begin(9600);
tft.begin(); // Initialize the display
tft.setRotation(3); // Set the rotation to horizontal
tft.fillScreen(ST7796S_BLACK);
displayText.writeText("Loading...", 3, 0, 200, 0, true, true);
websocketSetup();
}
void loop() {
webSocket.loop();
screenButtonHandler();
}
void websocketSetup(){
WiFiMulti.addAP("iotroam", "vbK9gbDBIB");
WiFiMulti.addAP("ObsidianAmstelveen", "drijversstraatmaastricht");
while(WiFiMulti.run() != WL_CONNECTED) {
delay(100);
}
// server address, port and URL
webSocket.begin("145.92.8.114", 80, "/ws");
// try ever 500 again if connection has failed
webSocket.setReconnectInterval(500);
}
void screenButtonHandler(){
int redButton = digitalRead(16);
int whiteButton = digitalRead(17);
int greenButton = digitalRead(18);
if (initialized) {
initialized = false;
tft.fillScreen(ST7796S_BLACK);
displayText.writeText(Question[i], 3, 0, 0, 0, true, false);
displayText.writeText(Answer[i], 3, 0, 200, 0, true, true);
}
if (redButton == HIGH){
sendData(questionID, "0");
}
if (whiteButton == HIGH){
sendData(questionID, "1");
}
if (greenButton == HIGH){
sendData(questionID, "2");
}
if (redButton || whiteButton || greenButton) {
i++;
questionID++;
if (questionID == 6){
questionID = 1;
}
if (i == 5) {
i = 0;
}
tft.fillScreen(ST7796S_BLACK);
displayText.writeText(Question[i], 3, 0, 0, 0, true, false);
displayText.writeText(Answer[i], 3, 0, 200, 0, true, true);
}
}
void sendData(int question, String answer){
webSocket.sendTXT("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Response\":\"" + String(answer) + "\",\"QuestionID\":\"" + String(question) + "\"}");
}
void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) {
const uint8_t* src = (const uint8_t*) mem;
USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
for(uint32_t i = 0; i < len; i++) {
if(i % cols == 0) {
USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
}
USE_SERIAL.printf("%02X ", *src);
src++;
}
USE_SERIAL.printf("\n");
}

View File

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

View File

@@ -0,0 +1,106 @@
#include "displayText.h"
#include "Arduino.h"
//constructor
DisplayText::DisplayText(Adafruit_ST7796S_kbv& tftDisplay) : tft(tftDisplay) {
tft.setCursor(0,0);
tft.fillScreen(ST7796S_BLACK);
}
//display text public function
void DisplayText::writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom) {
if (center) {
posX = centerText(text);
}
// if (bottom) {
// posY = bottomText(text);
// }
tft.setCursor(posX, posY);
tft.setTextSize(size);
printWordsFull(text, bottom);
delay(screenTime);
}
//to center the text when enabled in the public function
int DisplayText::centerText(char* text) {
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
int x = (tft.width() - w) / 2;
return x;
}
// //to display the text at the bottom when enabled in the public function
// int DisplayText::bottomText(char* text) {
// int16_t x1, y1;
// uint16_t w, h;
// tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
// int y = (tft.height() - h);
// return y;
// }
//attempt to write the text out in full (wip)
void DisplayText::printWordsFull(char* text, bool bottom) {
const int screenWidth = 480; // replace with your TFT display width
const int lineHeight = 30; // replace with your text line height
//the double copy is needed so it doesnt alter the original text
char* newtext1 = strdup(text); // Create a copy of the string for the first strtok_r
char* newtext2 = strdup(text); // Create a second copy for the second strtok_r
char* saveptr1, *saveptr2;
char* word = strtok_r(newtext1, " ", &saveptr1);
char line[100] = "";
int lineCount = 0;
int lineWidth = 0;
// Calculate total number of lines
int totalLines = 0;
char* tempWord = strtok_r(newtext2, " ", &saveptr2);
while (tempWord != NULL) {
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(tempWord, 0, 0, &x1, &y1, &w, &h);
if (lineWidth + w > screenWidth && strlen(line) > 0) {
//if the line width is greater than the screen width, then we need to add a new line
totalLines++;
lineWidth = w;
//otherwise we just add the width of the word to the line width
} else {
lineWidth += w;
}
tempWord = strtok_r(NULL, " ", &saveptr2);
}
totalLines++; // Add one for the last line
// Reset variables for actual printing
strcpy(line, "");
lineWidth = 0;
while (word != NULL) {
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(word, 0, 0, &x1, &y1, &w, &h);
if (lineWidth + w > screenWidth && strlen(line) > 0) {
int y = bottom ? tft.height() - lineHeight * (totalLines - lineCount) : lineHeight * lineCount;
tft.setCursor(0, y);
tft.println(line);
lineCount++;
strcpy(line, word);
strcat(line, " ");
lineWidth = w;
} else {
strcat(line, word);
strcat(line, " ");
lineWidth += w;
}
word = strtok_r(NULL, " ", &saveptr1);
}
// print the last line
int y = bottom ? tft.height() - lineHeight * (totalLines - lineCount) : lineHeight * lineCount;
tft.setCursor(0, y);
tft.println(line);
free(newtext1); // Free the memory allocated by strdup
free(newtext2); // Free the memory allocated by strdup
}

View File

@@ -0,0 +1,19 @@
#ifndef DISPLAYTEXT_H
#define DISPLAYTEXT_H
#include "Adafruit_GFX.h"
#include "Adafruit_ST7796S_kbv.h"
class DisplayText {
private:
Adafruit_ST7796S_kbv& tft;
int centerText(char* text);
// int bottomText(char* text);
void printWordsFull(char* text, bool bottom);
public:
DisplayText(Adafruit_ST7796S_kbv& tftDisplay);
void writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom);
};
#endif

View File

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

View File

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

View File

@@ -3,12 +3,14 @@ import mysql.connector
from queries import *
app = Flask(__name__)
@app.route('/')
@app.route('/getMeasurements')
def index():
node = request.args.get('node', default = None)
dataType = request.args.get('dataType', default = None)
MAC = request.args.get('MAC', default = None)
return getData(node, dataType, MAC)
dateStart = request.args.get('dateStart', default = None)
dateEnd = request.args.get('dateEnd', default = None)
return getData(node, dataType, MAC, dateStart, dateEnd)
@app.route('/updateData')
def updateDataIndex():
@@ -22,9 +24,13 @@ def getNodeInfoIndex():
macAdress = request.args.get('macAdress', None)
return getNodeInfo(macAdress)
@app.route('/getQuestionData')
def getQuestionDataIndex():
return getQuestionData()
def updateData(node, name, location):
mydb = loginDB()
query = update_query(node, name, location)
query = update_query(node, name, location, False, False)
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
cursor.execute(query)
mydb.commit()
@@ -43,9 +49,9 @@ def loginDB():
)
return mydb
def getData(node, dataType, MAC):
def getData(node, dataTypes, MAC, dateStart, dateEnd):
mydb = loginDB()
query = get_query(node, dataType, MAC)
query = get_query(node, dataTypes, MAC, False, False, dateStart, dateEnd)
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
cursor.execute(query)
result = cursor.fetchall() # Fetch the results
@@ -56,7 +62,7 @@ def getData(node, dataType, MAC):
def getNodeInfo(macAdress):
mydb = loginDB()
query = get_query(False, False, macAdress)
query = get_query(False, False, macAdress, False, False, False, False)
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
cursor.execute(query)
result = cursor.fetchall() # Fetch the results
@@ -65,5 +71,16 @@ def getNodeInfo(macAdress):
return result
def getQuestionData(questionID, replies):
mydb = loginDB()
query = get_query(False, False, False, questionID, replies)
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
cursor.execute(query)
result = cursor.fetchall() # Fetch the results
cursor.close()
mydb.close()
return result
if __name__ == '__main__':
app.run(debug=True, host='localhost')

View File

@@ -1,18 +1,38 @@
def get_query(node, dataType, MAC):
if node and dataType:
def get_query(node, dataType, MAC, questionID, replies, dateStart, dateEnd):
if dateStart and dateEnd and node and dataType:
query = f'''SELECT *
FROM Measurement
WHERE TimeStamp BETWEEN '{dateStart}' AND '{dateEnd}' AND NodeID = {node} AND Type IN ('{dataType}');'''
elif dateStart and dateEnd and node:
query = f'''SELECT *
FROM Measurement
WHERE TimeStamp BETWEEN '{dateStart}' AND '{dateEnd}' AND NodeID = {node};'''
elif dateStart and dateEnd:
query = f'''SELECT *
FROM Measurement
WHERE TimeStamp BETWEEN '{dateStart}' AND '{dateEnd}';'''
elif node and dataType:
query = f"SELECT * FROM Measurement WHERE NodeID = {node} AND Type = '{dataType}'"
elif MAC == "*":
query = "SELECT * FROM Node"
elif node:
query = f"SELECT * FROM Measurement WHERE NodeID = {node}"
elif dataType:
query = f"SELECT * FROM Measurement WHERE Type = '{dataType}'"
elif MAC:
query = f"SELECT * FROM Node WHERE MAC = '{MAC}'"
elif replies and questionID:
query = f"SELECT * FROM Reply WHERE replies = '{replies}' AND QuestionID = '{questionID}'"
elif questionID:
query = f"SELECT * FROM Question"
elif replies:
query = f"SELECT * FROM Reply"
else:
query = "SELECT * FROM `Measurement`"
return query
def update_query(node, name, location):
if node and name and location:
query = f"""

View File

@@ -1,64 +1,19 @@
import asyncio
import websockets
import mysql.connector
import json
from class_SensorNode import SensorNode
from class_enqueteNode import EnqueteNode
from classes_data import dbLogin
sensorNodeArray = []
enqueteNodeArray = []
async def processSensorNodeData(data, nodeID):
try:
mydb = dbLogin()
cursor = mydb.cursor()
query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)"
processedData = json.loads(data)
processedTemp = (processedData['Temp'])
processedHumi = (processedData['Humi'])
processedeCO2 = (processedData['eCO2'])
processedTVOC = (processedData['TVOC'])
processedMAC = (processedData['node'])
pushingDataArray = [(nodeID, "Temp", processedTemp), (nodeID, "Humi", processedHumi), (nodeID, "eCO2", processedeCO2), (nodeID, "TVOC", processedTVOC)]
for i in pushingDataArray:
print(query ,i)
cursor.execute(query, i)
mydb.commit()
except mysql.connector.Error as err:
print("MySQL Error:", err)
finally:
cursor.close()
mydb.close()
async def processEnqueteNodeData(data, nodeID):
try:
mydb = dbLogin()
cursor = mydb.cursor()
query = "INSERT INTO `Reply` (Result, Node_NodeID, Question_QuestionID) VALUES (%s, %s, %s)"
processedData = json.loads(data)
processedQuestionID = (processedData['QuestionID'])
processedResponse = (processedData['Response'])
pushingDataArray = [(processedResponse, nodeID, processedQuestionID)]
for i in pushingDataArray:
cursor.execute(query, i)
mydb.commit()
except mysql.connector.Error as err:
print("MySQL Error:", err)
finally:
cursor.close()
mydb.close()
async def receive_data():
uri = "ws://145.92.8.114/ws"
try:
async with websockets.connect(uri) as websocket:
while True:
print("true")
data = await websocket.recv()
print(f"Received data: {data}")
@@ -73,14 +28,13 @@ async def receive_data():
await getNodeInfo('sensor')
await getNodeInfo('enquete')
print(sensorNodeArray)
if macAdress in sensorNodeArray:
nodeID = await getNodeID(macAdress)
await processSensorNodeData(data, nodeID)
await SensorNode.processSensorNodeData(data, nodeID)
elif macAdress in enqueteNodeArray:
nodeID = await getNodeID(macAdress)
await processEnqueteNodeData(data, nodeID)
await EnqueteNode.processEnqueteNodeData(data, nodeID)
else:
await newNode(macAdress, type)
except websockets.ConnectionClosedError as e:
@@ -89,17 +43,8 @@ async def receive_data():
async def main():
await receive_data()
def dbLogin():
mydb = mysql.connector.connect(
host="localhost",
user="root",
password="Dingleberries69!",
database="NodeData"
)
return mydb
async def getNodeInfo(type):
print("getNodeINfo")
global sensorNodeArray
global enqueteNodeArray
@@ -114,14 +59,12 @@ async def getNodeInfo(type):
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':
@@ -143,7 +86,7 @@ async def getNodeID(macAdress):
return nodeID
async def newNode(mac, type):
id = (mac, type,)
id = (mac, type)
mydb = dbLogin()
cursor = mydb.cursor()

View File

@@ -0,0 +1,26 @@
import mysql.connector
def dbLogin():
mydb = mysql.connector.connect(
host="localhost",
user="root",
password="Dingleberries69!",
database="NodeData"
)
return mydb
class Node():
def __init__(self, macAdress):
self.macAdress = macAdress
self.id = None
def getNodeId(self):
id = (self.macAdress,)
mydb = dbLogin()
cursor = mydb.cursor()
cursor.execute("""SELECT nodeID FROM Node WHERE MAC = %s""", id)
data = cursor.fetchall()
for tuples in data:
for item in tuples:
self.id = item

View File

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

View File

@@ -0,0 +1,41 @@
import mysql.connector
import json
from classes_data import Node
from classes_data import dbLogin
class SensorNode(Node):
query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)"
def __init__(self, macAdress, temp, humi, eCO2, TVOC):
super().__init__(macAdress)
self.temperature = temp
self.humidity = humi
self.eCO2 = eCO2
self.TVOC = TVOC
async def processSensorNodeData(data, nodeID):
try:
mydb = dbLogin()
cursor = mydb.cursor()
processedData = json.loads(data)
SensorNode.temperature = (processedData['Temp'])
SensorNode.humidity = (processedData['Humi'])
SensorNode.eCO2 = (processedData['eCO2'])
SensorNode.TVOC = (processedData['TVOC'])
pushingDataArray = [(nodeID, "Temp", SensorNode.temperature), (nodeID, "Humi", SensorNode.humidity), (nodeID, "eCO2", SensorNode.eCO2), (nodeID, "TVOC", SensorNode.TVOC)]
for i in pushingDataArray:
cursor.execute(SensorNode.query, i)
mydb.commit()
except mysql.connector.Error as err:
print("MySQL Error:", err)
finally:
cursor.close()
mydb.close()

View File

@@ -13,6 +13,7 @@ class GaugeGroup {
this.element.innerHTML = `
<h2 class="groupTitle">${this.nodeId} - ${this.location}</h2>
<div class="Node">
<img src="arrow.png" class="arrowimg" onclick="toggleGraph('${this.nodeId}')">
${Array(this.gaugesCount).fill().map((_, i) => `
<div class="Sensorvalues">
<div id="gaugeContainer${this.nodeId}_${i+1}" class="gaugeContainer">
@@ -25,6 +26,9 @@ class GaugeGroup {
</div>
`).join('')}
</div>
<div class="plotly-container">
<div id="liveGraph${this.nodeId}" class="disabled" ></div>
</div>
`;
// Append the new div to the body

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles/dashboard-styles.css">
<title>Gauges</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js" charset="utf-8"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
@@ -24,22 +25,13 @@
<li class="nav-item">
<a href="settings.html" class="nav-link">Settings</a>
</li>
<div id="editNodeInformation">
<button id="settings" onclick="settings()">Settings</button>
</div>
<div id="editNode">
<textarea id="inputName"></textarea>
<textarea id="inputLocation"></textarea>
<select id="mySelect"></select>
<button id="button" onclick="changeText()">Change Information</button>
<p id="text">Status updating</p>
</div>
</ul>
</nav>
<body>
<body>
<script src="GaugGroup.js"></script>
<script src="graph-classes.js"></script>
<script src="main.js"></script>
<script src="text.js"></script>
<script src="liveGraph.js"></script>

View File

@@ -54,15 +54,16 @@ class liveGraph {
}
// Function to update the graph with new values got from updateData function
updateGraph() {
updateGraph(temperature, humidity, eCO2, TVOC) {
// Update the graph
this.tempArray.push(temperature);
this.humiArray.push(humidity);
this.eco2Array.push(eCO2 / 10);
this.tvocArray.push(TVOC / 10);
let time = new Date();
this.timeArray.push(new Date());
let 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 = {
@@ -75,11 +76,4 @@ class liveGraph {
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);
}
}

View File

@@ -4,7 +4,7 @@ const sensorData = {};
let liveGraphs = [];
let nodeArray = [];
let nodeDict = {};
let graphArray = [];
// letiables
let intervalDelay = 5000;
let amountOfNodes = 3;
@@ -66,6 +66,7 @@ async function handleIncomingData(data) {
sensorData[data.node].updateGauge(2, humidity);
sensorData[data.node].updateGauge(3, CO2);
sensorData[data.node].updateGauge(4, TVOC);
sensorData[data.node].graph.updateGraph(temperature, humidity, CO2, TVOC);
} else {
console.error('No sensor data for node:', nodeName);
}
@@ -98,31 +99,14 @@ async function nodeAdressHandler(node, dataTypes) {
let gaugeGroup = new GaugeGroup(nodeName, nodeLocation, dataTypes.length, maxGaugeValues, dataTypes);
sensorData[node] = gaugeGroup;
gaugeGroup.graph = new liveGraph(nodeName);
sensorData[node] = gaugeGroup;
gaugeGroup.graph.makeGraph();
sensorData[node] = gaugeGroup;
}
}
function 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)
@@ -142,4 +126,19 @@ function getNodeInfo(node){
};
})
}
// create a function to enable and disable the graph using .disabled using the id of the graph
function toggleGraph(nodeId) {
let graph = document.querySelector('#liveGraph' + nodeId);
if (graph) {
if (graph.classList.contains('disabled')) {
graph.classList.remove('disabled');
} else {
graph.classList.add('disabled');
}
} else {
console.error('No element found with id: liveGraph' + nodeId);
}
}

View File

@@ -0,0 +1,27 @@
class ChartConfigClass{
constructor(data, text){
this.data = data
this.text = text
}
get chartConfig() {
return{
type: 'pie',
data: this.data,
options: {
responsive: true,
legend: {
position: 'top',
},
title: {
display: true,
text: this.text
},
animation: {
animateScale: true,
animateRotate: true
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
class QuestionCreationClass {
constructor(data, label) {
this.data = data;
this.label = label;
}
get questionData() {
return {
labels: this.label,
datasets: [{
label: 'Responses',
data: this.data,
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)'
],
borderWidth: 1
}]
};
}
}

View File

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

View File

@@ -0,0 +1,54 @@
//For now create dummy data to show on the website.
let dummydata1 = [40, 30, 20];
let questionOptionsDummy1 = ['disgusting','clean', 'fine'];
let dummydata2 = [25, 35, 40];
let questionOptionsDummy2 = ['disgusting', 'clean', 'normal'];
let dummydata3 = [30, 20, 20];
let questionOptionsDummy3 = ['cold', 'perfect', 'hot'];
let dummydata4 = [30, 20, 20];
let questionOptionsDummy4 = ['really crowded','not at all', 'its fine', ];
let dummydata5 = [30, 20, 20];
let questionOptionsDummy5 = ['no','yes', 'decently'];
//make arrays to store data.
let chartConfigArray = [];
let textArray = [];
let questionArray = [];
let questionOptionsDummy = [];
let dummydata = [];
//Go along the array's to fetch data, and push this in a class.
for (let i = 0; i < 5; i++) {
dummydata.push(dummydata1, dummydata2, dummydata3, dummydata4, dummydata5);
questionOptionsDummy.push(questionOptionsDummy1, questionOptionsDummy2, questionOptionsDummy3, questionOptionsDummy4, questionOptionsDummy5);
questionArray.push(new QuestionCreationClass(dummydata[i], questionOptionsDummy[i]));
}
//Go allong another array to also give the class that creates the charts the data collected by the previous array.
for (let i = 0; i < 5; i++){
textArray.push('Question 1 Responses', 'Question 2 Responses', 'Question 3 Responses', 'Question 4 Responses', 'Question 5 Responses');
chartConfigArray.push(new ChartConfigClass(questionArray[i].questionData, textArray[i]));
}
// Create the charts
const ctx1 = document.getElementById('chart1').getContext('2d');
const myChart1 = new Chart(ctx1, chartConfigArray[0].chartConfig);
const ctx2 = document.getElementById('chart2').getContext('2d');
const myChart2 = new Chart(ctx2, chartConfigArray[1].chartConfig);
const ctx3 = document.getElementById('chart3').getContext('2d');
const myChart3 = new Chart(ctx3, chartConfigArray[2].chartConfig);
const ctx4 = document.getElementById('chart4').getContext('2d');
const myChart4 = new Chart(ctx4, chartConfigArray[3].chartConfig);
const ctx5 = document.getElementById('chart5').getContext('2d');
const myChart5 = new Chart(ctx5, chartConfigArray[4].chartConfig);

View File

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

View File

@@ -42,21 +42,20 @@
} */
body {
padding-top: 5vw;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
margin-top: 8vh;
background-color: #f0f0f0;
flex-direction: column;
background-color: #afafaf;
align-items: center;
}
.gaugeGroup {
width: 98vw;
height: 20vh;
height: auto;
display: flex;
flex-direction: column; /* Keep as column */
justify-content: flex-start;
@@ -68,8 +67,8 @@ body {
clear: both;
margin-bottom: 10px;
position: relative;
float: top;
}
.groupTitle {
width: 100%;
text-align: center;
@@ -112,7 +111,9 @@ body {
height: 80%; /* Increase the height from 70% to 80% */
position: relative;
overflow: visible;
height: 5vh;
margin-bottom: 1vh;
}
.gaugeImage {
@@ -125,7 +126,6 @@ body {
left: 0;
z-index: 1; /* Make the image display below the needle */
bottom: 0;
margin-bottom: 10px;
}
.gaugeValue, .gaugeText {
@@ -215,4 +215,24 @@ body {
color: #fff;
text-decoration: none;
font-size: 18px;
}
.plotly-container {
width: 100%;
float: bottom;
padding: 1vw;
align-items: center;
justify-content: center;
display: flex;
}
.js-plotly-plot {
width: 90%;
height: 100%;
}
.disabled {
opacity: 0;
height: 0;
}

View File

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

View File

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

View File

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