Compare commits
241 Commits
Websockets
...
Graph_Page
Author | SHA1 | Date | |
---|---|---|---|
|
013ff4cffa | ||
|
e5d2f23d59 | ||
|
8ae69a6fbe | ||
|
dcc99c1258 | ||
|
7104331e4c | ||
|
11f9f53f6b | ||
|
4708c9c71e | ||
|
cb1dbe8b11 | ||
|
61c8d09760 | ||
|
f2907117be | ||
|
0ecc2457e7 | ||
|
fde715d7ed | ||
|
e2ffcc4fa1 | ||
|
adce47db3a | ||
|
3d85220adc | ||
54a2eef7c5 | |||
09522218f3 | |||
4cf78ddb0b | |||
6ce7ac8c97 | |||
|
cd56c45c47 | ||
|
5bf19a82ff | ||
|
38d34cce91 | ||
|
c0034acf65 | ||
|
d52eabbb61 | ||
5b327942be | |||
89956bc4c8 | |||
15504eaea6 | |||
|
a82acc526c | ||
|
5fa07f3a82 | ||
|
54fc6f2306 | ||
|
a707c4f9bb | ||
|
b8fbf0dfaf | ||
|
b4dcdf7609 | ||
1c7fd2ab68 | |||
6ea17a63c7 | |||
|
a2ff97d7fb | ||
|
6c60a184b8 | ||
|
28ff778ce8 | ||
|
d20e6db9fe | ||
ef8821fe48 | |||
|
072ff99624 | ||
035a7f4c72 | |||
23bfbdada5 | |||
47f1f393da | |||
|
10eee12189 | ||
|
c4880ce0e2 | ||
|
3801b074f6 | ||
|
25de52c54b | ||
7b6f8463cd | |||
b8f8f5ea7e | |||
6efac7b179 | |||
023a1b0781 | |||
|
6c3b523208 | ||
|
7f5db74732 | ||
|
ddb7df5679 | ||
|
d7a762f30c | ||
|
85a49151ac | ||
|
0ab0b433ab | ||
|
8b0be0d6bd | ||
|
5d2223e5d7 | ||
|
dd6b5caa33 | ||
8da5b9f215 | |||
4fd0badd3c | |||
15f897f4d9 | |||
d542da7db9 | |||
|
8aeca410fc | ||
|
57ee918b64 | ||
|
57a556ab6c | ||
1bafce0015 | |||
|
fd260d9c56 | ||
|
f8489bca01 | ||
|
cc92eff4f8 | ||
|
4d0bd8aa43 | ||
dec48091f4 | |||
|
3fe74603ca | ||
|
c90fda2977 | ||
|
b27f9f8ed6 | ||
|
d21776cfc4 | ||
|
3bdc81b320 | ||
|
856775c2a8 | ||
|
649ea5e1a6 | ||
|
60d86bc0d5 | ||
|
77636f2a60 | ||
|
9acd2b44a8 | ||
|
3886292110 | ||
|
3a8998c00a | ||
|
5df0bfa0dc | ||
|
08c9bedb44 | ||
|
28bfd3a300 | ||
|
56abd5cbde | ||
|
3b375266ee | ||
|
2351c515f3 | ||
|
aa6c35044e | ||
|
67aaaca588 | ||
|
54f52bce48 | ||
|
083858f9cf | ||
|
866af9253e | ||
|
185a972f1b | ||
|
fb304af3a4 | ||
|
c7ee23afd7 | ||
|
598dd54521 | ||
|
9566148fa6 | ||
|
878fb9f491 | ||
|
1472c17125 | ||
|
d1c962f71c | ||
|
f2964ebce8 | ||
|
0b8ae0ef34 | ||
|
bc8cc500cb | ||
|
2e394f3d45 | ||
|
6070ba7480 | ||
|
656cd83aee | ||
|
bd66f21082 | ||
|
36277a7e5d | ||
|
2c0b7df0a1 | ||
|
55f94387cc | ||
|
24164841a9 | ||
|
c14c18deb0 | ||
|
f4840775ed | ||
|
63be84e287 | ||
|
199283d3e0 | ||
|
27742cfa89 | ||
|
5a8e13c0d6 | ||
|
284071b4ee | ||
|
8ed556d856 | ||
|
951fc64e6d | ||
|
65783ac3b6 | ||
|
9b01523f0e | ||
|
b971fe0b54 | ||
|
a2219fa03a | ||
|
67d77d59d1 | ||
|
db9c984446 | ||
|
026bd92d65 | ||
|
698b3055a4 | ||
|
2b31f80655 | ||
|
d3952306b2 | ||
|
de1ae975a4 | ||
|
f99d7b5c3d | ||
|
21ef8604c2 | ||
|
e09651a90c | ||
|
0886352a1c | ||
|
307257eefc | ||
|
fba7ada367 | ||
|
1596f0c641 | ||
|
3ebe661de0 | ||
|
fed0adb299 | ||
|
7476f98590 | ||
|
db9380816c | ||
|
d127a6001d | ||
|
bcbb8080c2 | ||
|
072752cec8 | ||
|
9193a753ce | ||
|
902031cfd5 | ||
|
d24744823b | ||
|
5b26d6b844 | ||
|
ce24a782ed | ||
|
5d9d377010 | ||
|
a70575d8b8 | ||
|
4db11e3035 | ||
|
20a5446c8c | ||
|
5c59dc033c | ||
|
ff296eefb0 | ||
|
2389c068bf | ||
|
f533e1a9c1 | ||
|
7b48a4a04e | ||
|
d1714fabec | ||
|
eee106a402 | ||
|
5f88a8f495 | ||
|
0d1da4503e | ||
|
14b07da103 | ||
|
9238d9841e | ||
|
734dca30bc | ||
|
503ef68e67 | ||
|
1d76c5ff7a | ||
|
3afc7797ab | ||
|
ddb21adc52 | ||
|
b0731f739b | ||
|
982a275aa0 | ||
|
3e82990c97 | ||
|
1b594b503a | ||
|
dcb2f37694 | ||
|
5558289ba2 | ||
|
dc3566f5e5 | ||
|
63ad49675b | ||
|
984e5e748d | ||
|
17588c38b3 | ||
|
3f60a08695 | ||
|
53af1d5858 | ||
|
766d03282b | ||
|
540d80a545 | ||
|
a084960f0f | ||
|
9adbde7e5c | ||
|
79ec11a7a2 | ||
|
cf6aaa2f33 | ||
|
71981d193a | ||
|
9255b8ab97 | ||
|
44b1317e33 | ||
|
16105122dd | ||
|
a0f8378cc9 | ||
|
270cff0435 | ||
|
955dab9dd4 | ||
|
515792b03f | ||
|
6ddb09112e | ||
|
aa31eeaaef | ||
|
26f075ee78 | ||
|
22e30063c4 | ||
|
35117663b1 | ||
|
613a01924d | ||
|
ea8cbe7c88 | ||
|
e6b3e07a71 | ||
|
154b10b66c | ||
|
885d2978a2 | ||
|
b622902221 | ||
|
ccfa8c75d0 | ||
|
faa49c7a21 | ||
|
02c92a9a70 | ||
|
c0af3e3cd4 | ||
|
d3f810ecf7 | ||
|
ce88a119c6 | ||
|
23a2f623a6 | ||
|
1ba6b305bb | ||
|
8ee0774602 | ||
|
41b678bf9c | ||
|
55fbb73a89 | ||
|
816f25757f | ||
|
85fc96cb63 | ||
|
ceb52b70fa | ||
|
fc0b96ec0a | ||
|
5791b373e9 | ||
|
bfeb8169eb | ||
|
e8ab30fbb1 | ||
|
0d00f4a874 | ||
|
b276ef1199 | ||
|
183f49f8c6 | ||
|
762dbee696 | ||
|
39250f33d8 | ||
|
ef90e4000a | ||
|
8a664f39ae | ||
|
26d88ce281 | ||
|
d253fd8394 | ||
|
23a3bc2cb2 | ||
|
ccd94c260e |
40
arduino/Button code/simple-buttons.ino
Normal file
@@ -0,0 +1,40 @@
|
||||
//connect the 3v3 directly to the button and the other end of the button to a pin.
|
||||
//this script adds a digital resistor so you dont have to add one yourself
|
||||
|
||||
const int buttonPin = 21; // the number of the pushbutton pin
|
||||
const int buttonPin1 = 1; // the number of the pushbutton pin
|
||||
const int buttonPin2 = 37; // the number of the pushbutton pin
|
||||
|
||||
|
||||
// variables will change:
|
||||
int buttonState = 0; // variable for reading the pushbutton status
|
||||
int buttonState1 = 0; // variable for reading the pushbutton status
|
||||
int buttonState2 = 0; // variable for reading the pushbutton status
|
||||
|
||||
void setup() {
|
||||
// initialize the pushbutton pin as an input:
|
||||
pinMode(buttonPin, INPUT_PULLDOWN);
|
||||
Serial.begin(9600);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// read the state of the pushbutton value:
|
||||
buttonState = digitalRead(buttonPin);
|
||||
buttonState1 = digitalRead(buttonPin1);
|
||||
buttonState2 = digitalRead(buttonPin2);
|
||||
|
||||
|
||||
// check if the pushbutton is pressed. If it is, the buttonState is HIGH:
|
||||
if (buttonState == HIGH) {
|
||||
Serial.println("ik ben high");
|
||||
}
|
||||
else if (buttonState1 == HIGH){
|
||||
Serial.println("ik ben higher");
|
||||
}
|
||||
else if (buttonState2 == HIGH){
|
||||
Serial.println("ik ben highest");
|
||||
}
|
||||
else {
|
||||
// turn LED off:
|
||||
}
|
||||
}
|
41
arduino/Mic/Mic.ino
Normal file
@@ -0,0 +1,41 @@
|
||||
const int sampleWindow = 50; // Sample window width in mS (50 mS = 20Hz)
|
||||
int const AMP_PIN = 7; // Preamp output pin connected to A0
|
||||
unsigned int sample;
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(9600);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
unsigned long startMillis = millis(); // Start of sample window
|
||||
unsigned int peakToPeak = 0; // peak-to-peak level
|
||||
|
||||
unsigned int signalMax = 0;
|
||||
unsigned int signalMin = 1024;
|
||||
|
||||
// collect data for 50 mS and then plot data
|
||||
while (millis() - startMillis < sampleWindow)
|
||||
{
|
||||
sample = analogRead(AMP_PIN);
|
||||
if (sample < 1024) // toss out spurious readings
|
||||
{
|
||||
if (sample > signalMax)
|
||||
{
|
||||
signalMax = sample; // save just the max levels
|
||||
}
|
||||
else if (sample < signalMin)
|
||||
{
|
||||
signalMin = sample; // save just the min levels
|
||||
}
|
||||
}
|
||||
}
|
||||
peakToPeak = signalMax - signalMin; // max - min = peak-peak amplitude
|
||||
if (peakToPeak == 4294966272){
|
||||
peakToPeak = 0;
|
||||
}
|
||||
Serial.println(peakToPeak);
|
||||
//double volts = (peakToPeak * 5.0) / 1024; // convert to volts
|
||||
//Serial.println(volts);
|
||||
}
|
BIN
arduino/Screen code/Adafruit_ST7796S_kbv-master.zip
Normal file
43
arduino/Screen code/Screen-code-full/Screen-code-full.ino
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "headerFile.h"
|
||||
|
||||
int i = 0;
|
||||
|
||||
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"
|
||||
};
|
||||
|
||||
//simplify the name of the library
|
||||
Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, MOSI, SCK, TFT_RST, MISO);
|
||||
DisplayText displayText(tft);
|
||||
|
||||
|
||||
void setup() {
|
||||
tft.begin(); // Initialize the display
|
||||
tft.setRotation(3); // Set the rotation to horizontal
|
||||
}
|
||||
|
||||
void loop() {
|
||||
i++;
|
||||
if (i == 5) {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK); // Fill the screen with black color
|
||||
// writeText(Question[0], 2, 50, 0, 0);
|
||||
// writeText(Answer[0], 2, 50, 300, 4000);
|
||||
displayText.writeText(Question[i], 3, 0, 0, 0, true, false);
|
||||
displayText.writeText(Answer[i], 3, 0, 200, 0, true, true);
|
||||
delay(2000);
|
||||
}
|
76
arduino/Screen code/Screen-code-full/displayText.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "displayText.h"
|
||||
#include "Arduino.h"
|
||||
|
||||
//constructor
|
||||
DisplayText::DisplayText(Adafruit_ST7796S_kbv& tftDisplay) : tft(tftDisplay) {
|
||||
tft.setCursor(0,0);
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
}
|
||||
//display text public function
|
||||
void DisplayText::writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom) {
|
||||
if (center) {
|
||||
posX = centerText(text);
|
||||
}
|
||||
// if (bottom) {
|
||||
// posY = bottomText(text);
|
||||
// }
|
||||
tft.setCursor(posX, posY);
|
||||
tft.setTextSize(size);
|
||||
printWordsFull(text);
|
||||
delay(screenTime);
|
||||
}
|
||||
|
||||
//to center the text when enabled in the public function
|
||||
int DisplayText::centerText(char* text) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
|
||||
int x = (tft.width() - w) / 2;
|
||||
return x;
|
||||
}
|
||||
|
||||
// //to display the text at the bottom when enabled in the public function
|
||||
// int DisplayText::bottomText(char* text) {
|
||||
// int16_t x1, y1;
|
||||
// uint16_t w, h;
|
||||
// tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
|
||||
// int y = (tft.height() - h);
|
||||
// return y;
|
||||
// }
|
||||
|
||||
//attempt to write the text out in full (wip)
|
||||
void DisplayText::printWordsFull(char* text) {
|
||||
const int screenWidth = 320; // replace with your TFT display width
|
||||
const int lineHeight = 22; // replace with your text line height
|
||||
|
||||
char* word = strtok(text, " ");
|
||||
char line[100] = "";
|
||||
int lineCount = 0;
|
||||
|
||||
while (word != NULL) {
|
||||
char tempLine[100];
|
||||
strcpy(tempLine, line);
|
||||
strcat(tempLine, word);
|
||||
strcat(tempLine, " ");
|
||||
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
tft.getTextBounds(tempLine, 0, 0, &x1, &y1, &w, &h);
|
||||
|
||||
if (w > screenWidth && strlen(line) > 0) {
|
||||
tft.setCursor(0, lineHeight * lineCount);
|
||||
tft.println(line);
|
||||
lineCount++;
|
||||
strcpy(line, word);
|
||||
strcat(line, " ");
|
||||
} else {
|
||||
strcpy(line, tempLine);
|
||||
}
|
||||
|
||||
word = strtok(NULL, " ");
|
||||
}
|
||||
|
||||
// print the last line
|
||||
tft.setCursor(0, lineHeight * lineCount);
|
||||
tft.println(line);
|
||||
}
|
19
arduino/Screen code/Screen-code-full/displayText.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef DISPLAYTEXT_H
|
||||
#define DISPLAYTEXT_H
|
||||
|
||||
#include "Adafruit_GFX.h"
|
||||
#include "Adafruit_ST7796S_kbv.h"
|
||||
|
||||
class DisplayText {
|
||||
private:
|
||||
Adafruit_ST7796S_kbv& tft;
|
||||
int centerText(char* text);
|
||||
// int bottomText(char* text);
|
||||
void printWordsFull(char* text);
|
||||
|
||||
public:
|
||||
DisplayText(Adafruit_ST7796S_kbv& tftDisplay);
|
||||
void writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom);
|
||||
};
|
||||
|
||||
#endif
|
11
arduino/Screen code/Screen-code-full/headerFile.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#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
|
40
arduino/Screen code/Screen-example-simplified.ino
Normal file
@@ -0,0 +1,40 @@
|
||||
#include <SPI.h>
|
||||
#include "Adafruit_GFX.h"
|
||||
#include "Adafruit_ST7796S_kbv.h"
|
||||
|
||||
#define TFT_CS 14
|
||||
#define TFT_DC 13
|
||||
#define TFT_RST 12 // RST can be set to -1 if you tie it to Arduino's reset
|
||||
#define MOSI 11
|
||||
#define SCK 10
|
||||
#define MISO 9
|
||||
|
||||
//simplify the name of the library
|
||||
|
||||
Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, MOSI, SCK, TFT_RST, MISO);
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
tft.begin(); // Initialize the display
|
||||
tft.setRotation(3); // Set the rotation
|
||||
tft.fillScreen(ST7796S_BLACK); // Fill the screen with black color
|
||||
tft.setTextColor(ST7796S_WHITE); // Set the text color to white
|
||||
tft.setTextSize(2); // Set the text size to 2
|
||||
}
|
||||
|
||||
void loop() {
|
||||
tft.setTextSize(5); // Set textsize
|
||||
tft.setCursor(0, 0); // Set the cursor to the top left corner
|
||||
//the cursor is where it types the text or shape on the screen, 0,0 is the topleft
|
||||
tft.println("Hello, world!"); // Print the text
|
||||
delay(2000); // Wait for 2 seconds
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK); // Clear the screen
|
||||
|
||||
tft.setCursor(0, 0); // Set the cursor to the top left corner
|
||||
tft.println("New text!"); // Print the new text
|
||||
delay(2000); // Wait for 2 seconds
|
||||
tft.fillScreen(ST7796S_BLACK); // Clear the screen
|
||||
|
||||
}
|
327
arduino/Screen code/Screen-example.ino
Normal file
@@ -0,0 +1,327 @@
|
||||
/***************************************************
|
||||
This is our library for the Adafruit HX8357D Breakout
|
||||
----> http://www.adafruit.com/products/2050
|
||||
|
||||
Check out the links above for our tutorials and wiring diagrams
|
||||
These displays use SPI to communicate, 4 or 5 pins are required to
|
||||
interface (RST is optional)
|
||||
Adafruit invests time and resources providing this open source code,
|
||||
please support Adafruit and open-source hardware by purchasing
|
||||
products from Adafruit!
|
||||
|
||||
Written by Limor Fried/Ladyada for Adafruit Industries.
|
||||
MIT license, all text above must be included in any redistribution
|
||||
****************************************************/
|
||||
|
||||
#include <SPI.h>
|
||||
#include "Adafruit_GFX.h"
|
||||
#include "Adafruit_ST7796S_kbv.h"
|
||||
|
||||
// These are 'flexible' lines that can be changed
|
||||
#define TFT_CS 14
|
||||
#define TFT_DC 13
|
||||
#define TFT_RST 12 // RST can be set to -1 if you tie it to Arduino's reset
|
||||
#define MOSI 11
|
||||
#define SCK 10
|
||||
#define MISO 9
|
||||
|
||||
// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
|
||||
//Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, TFT_RST);
|
||||
|
||||
// SoftSPI - note that on some processors this might be *faster* than hardware SPI!
|
||||
Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, MOSI, SCK, TFT_RST, MISO);
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
Serial.println("ST7796S_kbv Test!");
|
||||
|
||||
tft.begin();
|
||||
|
||||
// read diagnostics (optional but can help debug problems)
|
||||
uint8_t x = tft.readcommand8(ST7796S_RDMODE);
|
||||
Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
|
||||
x = tft.readcommand8(ST7796S_RDMADCTL);
|
||||
Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
|
||||
x = tft.readcommand8(ST7796S_RDPIXFMT);
|
||||
Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
|
||||
x = tft.readcommand8(ST7796S_RDIMGFMT);
|
||||
Serial.print("Image Format: 0x"); Serial.println(x, HEX);
|
||||
x = tft.readcommand8(ST7796S_RDSELFDIAG);
|
||||
Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX);
|
||||
|
||||
Serial.println(F("Benchmark Time (microseconds)"));
|
||||
|
||||
tft.setRotation(1);
|
||||
|
||||
Serial.print(F("Text "));
|
||||
Serial.println(testText());
|
||||
delay(500);
|
||||
|
||||
Serial.print(F("Lines "));
|
||||
Serial.println(testLines(ST7796S_CYAN));
|
||||
delay(500);
|
||||
|
||||
Serial.print(F("Rectangles (outline) "));
|
||||
Serial.println(testRects(ST7796S_GREEN));
|
||||
delay(500);
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
Serial.print(F("Circles (outline) "));
|
||||
Serial.println(testCircles(10, ST7796S_RED));
|
||||
delay(500);
|
||||
|
||||
|
||||
Serial.print(F("Triangles (outline) "));
|
||||
Serial.println(testTriangles());
|
||||
delay(500);
|
||||
|
||||
Serial.print(F("Triangles (filled) "));
|
||||
Serial.println(testFilledTriangles());
|
||||
delay(500);
|
||||
|
||||
|
||||
Serial.print(F("Rounded rects (outline) "));
|
||||
Serial.println(testRoundRects());
|
||||
delay(500);
|
||||
|
||||
Serial.print(F("Rounded rects (filled) "));
|
||||
Serial.println(testFilledRoundRects());
|
||||
delay(500);
|
||||
|
||||
Serial.println(F("Done!"));
|
||||
}
|
||||
|
||||
|
||||
void loop(void) {
|
||||
for(uint8_t rotation=0; rotation<4; rotation++) {
|
||||
tft.setRotation(rotation);
|
||||
testText();
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long testFillScreen() {
|
||||
unsigned long start = micros();
|
||||
tft.fillScreen(ST7796S_RED);
|
||||
tft.fillScreen(ST7796S_GREEN);
|
||||
tft.fillScreen(ST7796S_BLUE);
|
||||
tft.fillScreen(ST7796S_WHITE);
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
|
||||
unsigned long testText() {
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
unsigned long start = micros();
|
||||
tft.setCursor(0, 0);
|
||||
tft.setTextColor(ST7796S_WHITE); tft.setTextSize(1);
|
||||
tft.println("Hello World!");
|
||||
tft.setTextColor(ST7796S_YELLOW); tft.setTextSize(2);
|
||||
tft.println(1234.56);
|
||||
tft.setTextColor(ST7796S_RED); tft.setTextSize(3);
|
||||
tft.println(0xDEADBEEF, HEX);
|
||||
tft.println();
|
||||
tft.setTextColor(ST7796S_GREEN);
|
||||
tft.setTextSize(5);
|
||||
tft.println("Groop");
|
||||
tft.setTextSize(2);
|
||||
tft.println("I implore thee,");
|
||||
tft.setTextSize(1);
|
||||
tft.println("my foonting turlingdromes.");
|
||||
tft.println("And hooptiously drangle me");
|
||||
tft.println("with crinkly bindlewurdles,");
|
||||
tft.println("Or I will rend thee");
|
||||
tft.println("in the gobberwarts");
|
||||
tft.println("with my blurglecruncheon,");
|
||||
tft.println("see if I don't!");
|
||||
|
||||
tft.setTextColor(ST7796S_WHITE);
|
||||
tft.println(F("Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice 'without pictures or conversations?'"));
|
||||
|
||||
tft.println(F("So she was considering in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid), whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her."));
|
||||
|
||||
tft.println(F("There was nothing so very remarkable in that; nor did Alice think it so very much out of the way to hear the Rabbit say to itself, 'Oh dear! Oh dear! I shall be late!' (when she thought it over afterwards, it occurred to her that she ought to have wondered at this, but at the time it all seemed quite natural); but when the Rabbit actually took a watch out of its waistcoat-pocket, and looked at it, and then hurried on, Alice started to her feet, for it flashed across her mind that she had never before seen a rabbit with either a waistcoat-pocket, or a watch to take out of it, and burning with curiosity, she ran across the field after it, and fortunately was just in time to see it pop down a large rabbit-hole under the hedge."));
|
||||
|
||||
tft.println(F("In another moment down went Alice after it, never once considering how in the world she was to get out again."));
|
||||
|
||||
tft.println(F("The rabbit-hole went straight on like a tunnel for some way, and then dipped suddenly down, so suddenly that Alice had not a moment to think about stopping herself before she found herself falling down a very deep well."));
|
||||
|
||||
tft.println(F("Either the well was very deep, or she fell very slowly, for she had plenty of time as she went down to look about her and to wonder what was going to happen next. First, she tried to look down and make out what she was coming to, but it was too dark to see anything; then she looked at the sides of the well, and noticed that they were filled with cupboards and book-shelves; here and there she saw maps and pictures hung upon pegs. She took down a jar from one of the shelves as she passed; it was labelled 'ORANGE MARMALADE', but to her great disappointment it was empty: she did not like to drop the jar for fear of killing somebody, so managed to put it into one of the cupboards as she fell past it."));
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testLines(uint16_t color) {
|
||||
unsigned long start, t;
|
||||
int x1, y1, x2, y2,
|
||||
w = tft.width(),
|
||||
h = tft.height();
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
|
||||
x1 = y1 = 0;
|
||||
y2 = h - 1;
|
||||
start = micros();
|
||||
for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color);
|
||||
x2 = w - 1;
|
||||
for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color);
|
||||
t = micros() - start; // fillScreen doesn't count against timing
|
||||
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testFastLines(uint16_t color1, uint16_t color2) {
|
||||
unsigned long start;
|
||||
int x, y, w = tft.width(), h = tft.height();
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
start = micros();
|
||||
for(y=0; y<h; y+=5) tft.drawFastHLine(0, y, w, color1);
|
||||
for(x=0; x<w; x+=5) tft.drawFastVLine(x, 0, h, color2);
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testRects(uint16_t color) {
|
||||
unsigned long start;
|
||||
int n, i, i2,
|
||||
cx = tft.width() / 2,
|
||||
cy = tft.height() / 2;
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
n = min(tft.width(), tft.height());
|
||||
start = micros();
|
||||
for(i=2; i<n; i+=6) {
|
||||
i2 = i / 2;
|
||||
tft.drawRect(cx-i2, cy-i2, i, i, color);
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testFilledRects(uint16_t color1, uint16_t color2) {
|
||||
unsigned long start, t = 0;
|
||||
int n, i, i2,
|
||||
cx = tft.width() / 2 - 1,
|
||||
cy = tft.height() / 2 - 1;
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
n = min(tft.width(), tft.height());
|
||||
for(i=n; i>0; i-=6) {
|
||||
i2 = i / 2;
|
||||
start = micros();
|
||||
tft.fillRect(cx-i2, cy-i2, i, i, color1);
|
||||
t += micros() - start;
|
||||
// Outlines are not included in timing results
|
||||
tft.drawRect(cx-i2, cy-i2, i, i, color2);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
unsigned long testFilledCircles(uint8_t radius, uint16_t color) {
|
||||
unsigned long start;
|
||||
int x, y, w = tft.width(), h = tft.height(), r2 = radius * 2;
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
start = micros();
|
||||
for(x=radius; x<w; x+=r2) {
|
||||
for(y=radius; y<h; y+=r2) {
|
||||
tft.fillCircle(x, y, radius, color);
|
||||
}
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testCircles(uint8_t radius, uint16_t color) {
|
||||
unsigned long start;
|
||||
int x, y, r2 = radius * 2,
|
||||
w = tft.width() + radius,
|
||||
h = tft.height() + radius;
|
||||
|
||||
// Screen is not cleared for this one -- this is
|
||||
// intentional and does not affect the reported time.
|
||||
start = micros();
|
||||
for(x=0; x<w; x+=r2) {
|
||||
for(y=0; y<h; y+=r2) {
|
||||
tft.drawCircle(x, y, radius, color);
|
||||
}
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testTriangles() {
|
||||
unsigned long start;
|
||||
int n, i, cx = tft.width() / 2 - 1,
|
||||
cy = tft.height() / 2 - 1;
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
n = min(cx, cy);
|
||||
start = micros();
|
||||
for(i=0; i<n; i+=5) {
|
||||
tft.drawTriangle(
|
||||
cx , cy - i, // peak
|
||||
cx - i, cy + i, // bottom left
|
||||
cx + i, cy + i, // bottom right
|
||||
tft.color565(200, 20, i));
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testFilledTriangles() {
|
||||
unsigned long start, t = 0;
|
||||
int i, cx = tft.width() / 2 - 1,
|
||||
cy = tft.height() / 2 - 1;
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
start = micros();
|
||||
for(i=min(cx,cy); i>10; i-=5) {
|
||||
start = micros();
|
||||
tft.fillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i,
|
||||
tft.color565(0, i, i));
|
||||
t += micros() - start;
|
||||
tft.drawTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i,
|
||||
tft.color565(i, i, 0));
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
unsigned long testRoundRects() {
|
||||
unsigned long start;
|
||||
int w, i, i2,
|
||||
cx = tft.width() / 2 ,
|
||||
cy = tft.height() / 2 ;
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
w = min(tft.width(), tft.height());
|
||||
start = micros();
|
||||
for(i=0; i<w; i+=8) {
|
||||
i2 = i / 2 - 2;
|
||||
tft.drawRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(i, 100, 100));
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testFilledRoundRects() {
|
||||
unsigned long start;
|
||||
int i, i2,
|
||||
cx = tft.width() / 2 + 10,
|
||||
cy = tft.height() / 2 + 10;
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
start = micros();
|
||||
for(i=min(tft.width(), tft.height()) - 20; i>25; i-=6) {
|
||||
i2 = i / 2;
|
||||
tft.fillRoundRect(cx-i2, cy-i2, i-20, i-20, i/8, tft.color565(100, i/2, 100));
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
53
arduino/Screen code/screen-with-questions-nostyling.ino
Normal file
@@ -0,0 +1,53 @@
|
||||
//styling still needs to be done.
|
||||
//this code is to display the questions on the screen.
|
||||
#include <SPI.h>
|
||||
#include "Adafruit_GFX.h"
|
||||
#include "Adafruit_ST7796S_kbv.h"
|
||||
|
||||
#define TFT_CS 14
|
||||
#define TFT_DC 13
|
||||
#define TFT_RST 12
|
||||
#define MOSI 11
|
||||
#define SCK 10
|
||||
#define MISO 9
|
||||
|
||||
char* Question[] = {
|
||||
"How clean are the toilets? (clean, normal, disgusting).",
|
||||
"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)"
|
||||
};
|
||||
|
||||
//simplify the name of the library
|
||||
|
||||
Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, MOSI, SCK, TFT_RST, MISO);
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
tft.begin(); // Initialize the display
|
||||
tft.setRotation(3); // Set the rotation to horizontal
|
||||
tft.fillScreen(ST7796S_BLACK); // Fill the screen with black color
|
||||
tft.setTextColor(ST7796S_WHITE); // Set the text color to white
|
||||
tft.setTextSize(2); // Set the text size to 2
|
||||
Serial.begin(9600);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
tft.setTextSize(2); // Set textsize
|
||||
tft.setCursor(0, 0); // Set the cursor to the top left corner
|
||||
//the cursor is where it types the text or shape on the screen, 0,0 is the topleft
|
||||
tft.println("Hello, world!"); // Print the text
|
||||
delay(2000); // Wait for 2 seconds
|
||||
|
||||
tft.fillScreen(ST7796S_BLACK); // Clear the screen
|
||||
|
||||
tft.setCursor(0, 0); // Set the cursor to the top left corner
|
||||
tft.println("New text!"); // Print the new text
|
||||
delay(2000); // Wait for 2 seconds
|
||||
tft.fillScreen(ST7796S_BLACK); // Clear the screen
|
||||
tft.setCursor(0, 0); // Set the cursor to the top left corner
|
||||
tft.println(Question[1]);
|
||||
delay(10000); //delay set a longer delay so the long text can write out
|
||||
}
|
40
arduino/Screen code/writetext function.ino
Normal file
@@ -0,0 +1,40 @@
|
||||
#include <SPI.h>
|
||||
#include "Adafruit_GFX.h"
|
||||
#include "Adafruit_ST7796S_kbv.h"
|
||||
#include "headerFile.h"
|
||||
|
||||
char* Question[] = {
|
||||
"How clean are the toilets? (clean, normal, disgusting).",
|
||||
"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)"
|
||||
};
|
||||
|
||||
//simplify the name of the library
|
||||
Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, MOSI, SCK, TFT_RST, MISO);
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
tft.begin(); // Initialize the display
|
||||
tft.setRotation(3); // Set the rotation to horizontal
|
||||
tft.fillScreen(ST7796S_BLACK);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
writeText(Question[0], 2, 0, 0, 4000);
|
||||
writeText(Question[1], 2, 0, 0, 4000);
|
||||
writeText(Question[2], 2, 0, 0, 4000);
|
||||
}
|
||||
|
||||
|
||||
void writeText(char* text, int size, int posX, int posY, int screenTime){
|
||||
tft.fillScreen(ST7796S_BLACK); // Fill the screen with black color
|
||||
|
||||
tft.setCursor(posX, posY); // Set the cursor to the top left corner
|
||||
tft.setTextSize(size); // Set textsize
|
||||
tft.println(text);
|
||||
delay(screenTime);
|
||||
}
|
13
arduino/mac_adress.ino
Normal file
@@ -0,0 +1,13 @@
|
||||
// Complete Instructions to Get and Change ESP MAC Address: https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/
|
||||
#include <WiFi.h>
|
||||
|
||||
void setup(){
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
Serial.print("ESP Board MAC Address: ");
|
||||
Serial.println(WiFi.macAddress());
|
||||
}
|
||||
|
||||
void loop(){
|
||||
|
||||
}
|
@@ -1,3 +1,6 @@
|
||||
// Dano Bosch
|
||||
// 14/2/2024
|
||||
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_GFX.h>
|
||||
|
42
arduino/node-code/node-code-final/headerFile.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// include these libraries
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_SH110X.h>
|
||||
#include <Adafruit_SGP30.h>
|
||||
#include <DHT.h>
|
||||
#include <WiFiMulti.h>
|
||||
#include <WiFi.h>
|
||||
#include <WebSocketsClient.h>
|
||||
#include <nodeCodeHeader.h>
|
||||
|
||||
// define pins on esp32
|
||||
#define MICPIN 6
|
||||
#define DHTPIN 7
|
||||
#define SCL 9
|
||||
#define SDA 8
|
||||
#define DHTTYPE DHT11
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 64
|
||||
#define i2c_adress 0x3c
|
||||
#define OLED_RESET -1 // QT-PY / XIAO
|
||||
#define USE_SERIAL Serial
|
||||
|
||||
// make new objects
|
||||
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
||||
DHT dht(DHTPIN, DHTTYPE);
|
||||
WiFiMulti WiFiMulti;
|
||||
Adafruit_SGP30 sgp;
|
||||
WebSocketsClient webSocket;
|
||||
|
||||
// define variables
|
||||
uint16_t TVOC_base, eCO2_base;
|
||||
uint16_t counter = 0;
|
||||
uint16_t eCO2 = 0;
|
||||
uint16_t TVOC = 0;
|
||||
uint16_t interval = 5000;
|
||||
float temperature = 0;
|
||||
float humidity = 0;
|
||||
unsigned long currentMillis;
|
||||
unsigned long lastMillis;
|
||||
bool errorSGP30 = false;
|
||||
bool errorDHT11 = false;
|
||||
bool noise = false;
|
177
arduino/node-code/node-code-final/node-code-final.ino
Normal file
@@ -0,0 +1,177 @@
|
||||
// include header file into code
|
||||
#include <headerFile.h>
|
||||
|
||||
// setup function
|
||||
void setup() {
|
||||
// make serial connection at 115200 baud
|
||||
Serial.begin(115200);
|
||||
|
||||
// tell display what settings to use
|
||||
display.begin(i2c_adress, true);
|
||||
display.clearDisplay();
|
||||
|
||||
// tell sensors to start reading
|
||||
dht.begin();
|
||||
sgp.begin();
|
||||
|
||||
pinMode(MICPIN, INPUT);
|
||||
pinMode(DHTPIN, INPUT);
|
||||
|
||||
websocketSetup();
|
||||
resetValues();
|
||||
}
|
||||
|
||||
// loop function
|
||||
void loop() {
|
||||
// loop the websocket connection so it stays alive
|
||||
webSocket.loop();
|
||||
|
||||
// update when interval is met
|
||||
if (currentMillis - lastMillis >= interval){
|
||||
lastMillis = millis();
|
||||
update();
|
||||
}
|
||||
|
||||
// update the counter
|
||||
currentMillis = millis();
|
||||
}
|
||||
|
||||
// hexdump function for websockets binary handler
|
||||
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");
|
||||
}
|
||||
|
||||
// handler for websocket events
|
||||
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
|
||||
switch(type) {
|
||||
case WStype_DISCONNECTED:
|
||||
USE_SERIAL.printf("[WSc] Disconnected!\n");
|
||||
break;
|
||||
case WStype_CONNECTED:
|
||||
USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
|
||||
|
||||
// send message to server when Connected
|
||||
webSocket.sendTXT("{\"message\": \"Connected\"}");
|
||||
break;
|
||||
case WStype_TEXT:
|
||||
// USE_SERIAL.printf("[WSc] get text: %s\n", payload);
|
||||
|
||||
// send message to server
|
||||
// webSocket.sendTXT("message here");
|
||||
break;
|
||||
case WStype_BIN:
|
||||
// USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
|
||||
hexdump(payload, length);
|
||||
|
||||
// send data to server
|
||||
// webSocket.sendBIN(payload, length);
|
||||
break;
|
||||
case WStype_ERROR:
|
||||
case WStype_FRAGMENT_TEXT_START:
|
||||
case WStype_FRAGMENT_BIN_START:
|
||||
case WStype_FRAGMENT:
|
||||
case WStype_FRAGMENT_FIN:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// special function to setup websocket
|
||||
void websocketSetup(){
|
||||
WiFiMulti.addAP("iotroam", "vbK9gbDBIB");
|
||||
WiFiMulti.addAP("LansanKPN-boven", "19sander71vlieland14");
|
||||
|
||||
while(WiFiMulti.run() != WL_CONNECTED) {
|
||||
delay(100);
|
||||
}
|
||||
|
||||
// server address, port and URL
|
||||
webSocket.begin("145.92.8.114", 80, "/ws");
|
||||
|
||||
// event handler
|
||||
webSocket.onEvent(webSocketEvent);
|
||||
|
||||
// try ever 500 again if connection has failed
|
||||
webSocket.setReconnectInterval(500);
|
||||
}
|
||||
|
||||
// function to reset the values if needed
|
||||
void resetValues() {
|
||||
TVOC_base;
|
||||
eCO2_base;
|
||||
counter = 0;
|
||||
temperature = 0;
|
||||
humidity = 0;
|
||||
eCO2 = 0;
|
||||
TVOC = 0;
|
||||
noise = false;
|
||||
lastMillis = 0;
|
||||
currentMillis = 0;
|
||||
}
|
||||
|
||||
// function to check for errors in sensors
|
||||
bool checkForError(){
|
||||
if (!sgp.IAQmeasure()) {
|
||||
Serial.println("SGP30: BAD");
|
||||
errorSGP30 = true;
|
||||
} else {
|
||||
Serial.println("SGP30: OK");
|
||||
errorSGP30 = false;
|
||||
}
|
||||
|
||||
if (isnan(temperature) || isnan(humidity)){
|
||||
Serial.println("DHT11: BAD");
|
||||
errorDHT11 = true;
|
||||
} else {
|
||||
Serial.println("DHT11: OK");
|
||||
errorDHT11 = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// function to update when interval is met (see intervalCounter variable)
|
||||
void update(){
|
||||
// display sensordata on oled screen
|
||||
displayData();
|
||||
|
||||
// webSocket.sendTXT("{\"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp.eCO2) + "\",\"TVOC\":\"" + String(sgp.TVOC) + "\"}");
|
||||
webSocket.sendTXT("{\"node\": \"" + String(WiFi.macAddress()) + "\", \"Temp\":\"" + String(temperature) + "\",\"Humi\":\"" + String(humidity) + "\",\"eCO2\":\"" + String(sgp.eCO2) + "\",\"TVOC\":\"" + String(sgp.TVOC) + "\"}");
|
||||
|
||||
sgp.getIAQBaseline(&eCO2_base, &TVOC_base);
|
||||
|
||||
// read dht11 sensor
|
||||
temperature = float(dht.readTemperature());
|
||||
humidity = float(dht.readHumidity());
|
||||
|
||||
// check if any errors occured when reading sensors
|
||||
checkForError();
|
||||
}
|
||||
|
||||
// function to display data on oled screen
|
||||
void displayData() {
|
||||
// clear display for new info
|
||||
display.clearDisplay();
|
||||
|
||||
// set custom text properties
|
||||
display.setTextSize(2);
|
||||
display.setTextColor(SH110X_WHITE);
|
||||
display.setCursor(0, 0);
|
||||
|
||||
// display info on oled screen
|
||||
display.println((String) "Temp: " + int(temperature) + "C" + '\n'
|
||||
+ "Humi: " + int(humidity) + "%" + '\n'
|
||||
+ "eCO2: " + int(sgp.eCO2) + '\n'
|
||||
+ "TVOC: " + int(sgp.TVOC));
|
||||
|
||||
// display the screen
|
||||
display.display();
|
||||
}
|
18
arduino/node-code/node-code-final/nodeCode.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "arduino.h"
|
||||
#include "nodeCodeHeader.h"
|
||||
|
||||
nodeReadings::nodeReadings() {
|
||||
}
|
||||
|
||||
void nodeReadings::resetValues() {
|
||||
counter = 0;
|
||||
eCO2 = 0;
|
||||
TVOC = 0;
|
||||
temperature = 0;
|
||||
humidity = 0;
|
||||
currentMillis = 0;
|
||||
lastMillis = 0;
|
||||
errorSGP30 = false;
|
||||
errorDHT11 = false;
|
||||
noise = false;
|
||||
}
|
17
arduino/node-code/node-code-final/nodeCodeFinal.ino
Normal file
@@ -0,0 +1,17 @@
|
||||
#include <nodeCodeHeader.h>
|
||||
|
||||
nodeReadings esp32Node();
|
||||
|
||||
void setup()
|
||||
{
|
||||
// put your setup code here, to run once:
|
||||
esp32Node.setup();
|
||||
esp32Node.websocketSetup();
|
||||
esp32Node.resetValues();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
// put your main code here, to run repeatedly:
|
||||
esp32Node.loop();
|
||||
}
|
63
arduino/node-code/node-code-final/nodeCodeHeader.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "arduino.h"
|
||||
#include "nodeCodeHeader.h"
|
||||
|
||||
nodeReadings::nodeReadings() {
|
||||
}
|
||||
|
||||
void nodeReadings::setup(){
|
||||
// make serial connection at 115200 baud
|
||||
Serial.begin(115200);
|
||||
|
||||
// tell display what settings to use
|
||||
display.begin(i2c_adress, true);
|
||||
display.clearDisplay();
|
||||
|
||||
// tell sensors to start reading
|
||||
dht.begin();
|
||||
sgp.begin();
|
||||
|
||||
pinMode(MICPIN, INPUT);
|
||||
pinMode(DHTPIN, INPUT);
|
||||
|
||||
}
|
||||
|
||||
void nodeReadings::loop() {
|
||||
// loop the websocket connection so it stays alive
|
||||
webSocket.loop();
|
||||
|
||||
// update when interval is met
|
||||
if (currentMillis - lastMillis >= interval){
|
||||
lastMillis = millis();
|
||||
update();
|
||||
}
|
||||
|
||||
// update the counter
|
||||
currentMillis = millis();
|
||||
}
|
||||
|
||||
void nodeReadings::resetValues() {
|
||||
counter = 0;
|
||||
eCO2 = 0;
|
||||
TVOC = 0;
|
||||
temperature = 0;
|
||||
humidity = 0;
|
||||
currentMillis = 0;
|
||||
lastMillis = 0;
|
||||
errorSGP30 = false;
|
||||
errorDHT11 = false;
|
||||
noise = false;
|
||||
}
|
||||
|
||||
// hexdump function for websockets binary handler
|
||||
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");
|
||||
}
|
18
arduino/node-code/node-code-final/nodeCodeHeader.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef nodeReading_h
|
||||
#define nodeReading_h
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "headerFile.h"
|
||||
|
||||
|
||||
class nodeReadings {
|
||||
|
||||
public:
|
||||
nodeReadings();
|
||||
void setup();
|
||||
void loop();
|
||||
void resetValues();
|
||||
private:
|
||||
};
|
||||
|
||||
#endif
|
112
arduino/websockets/websocket.ino
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* WebSocketClient.ino
|
||||
*
|
||||
* Created on: 24.05.2015
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <WiFiMulti.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
|
||||
#include <WebSocketsClient.h>
|
||||
|
||||
|
||||
WiFiMulti WiFiMulti;
|
||||
WebSocketsClient webSocket;
|
||||
|
||||
#define USE_SERIAL Serial1
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
|
||||
|
||||
switch(type) {
|
||||
case WStype_DISCONNECTED:
|
||||
USE_SERIAL.printf("[WSc] Disconnected!\n");
|
||||
break;
|
||||
case WStype_CONNECTED:
|
||||
USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
|
||||
|
||||
// send message to server when Connected
|
||||
webSocket.sendTXT("Connected");
|
||||
break;
|
||||
case WStype_TEXT:
|
||||
USE_SERIAL.printf("[WSc] get text: %s\n", payload);
|
||||
|
||||
// send message to server
|
||||
// webSocket.sendTXT("message here");
|
||||
break;
|
||||
case WStype_BIN:
|
||||
USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
|
||||
hexdump(payload, length);
|
||||
|
||||
// send data to server
|
||||
// webSocket.sendBIN(payload, length);
|
||||
break;
|
||||
case WStype_ERROR:
|
||||
case WStype_FRAGMENT_TEXT_START:
|
||||
case WStype_FRAGMENT_BIN_START:
|
||||
case WStype_FRAGMENT:
|
||||
case WStype_FRAGMENT_FIN:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// USE_SERIAL.begin(921600);
|
||||
USE_SERIAL.begin(115200);
|
||||
|
||||
//Serial.setDebugOutput(true);
|
||||
USE_SERIAL.setDebugOutput(true);
|
||||
|
||||
USE_SERIAL.println();
|
||||
USE_SERIAL.println();
|
||||
USE_SERIAL.println();
|
||||
|
||||
for(uint8_t t = 4; t > 0; t--) {
|
||||
USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
|
||||
USE_SERIAL.flush();
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
WiFiMulti.addAP("iotroam", "vbK9gbDBIB");
|
||||
|
||||
//WiFi.disconnect();
|
||||
while(WiFiMulti.run() != WL_CONNECTED) {
|
||||
delay(100);
|
||||
}
|
||||
|
||||
// server address, port and URL
|
||||
webSocket.begin("145.92.8.114", 80, "/ws");
|
||||
|
||||
// event handler
|
||||
webSocket.onEvent(webSocketEvent);
|
||||
|
||||
// use HTTP Basic Authorization this is optional remove if not needed
|
||||
|
||||
|
||||
// try ever 5000 again if connection has failed
|
||||
webSocket.setReconnectInterval(5000);
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
webSocket.loop();
|
||||
|
||||
webSocket.sendTXT("test websocket 45050");
|
||||
}
|
13
docs/.pages
@@ -8,11 +8,24 @@ nav:
|
||||
- Links: opdracht/links
|
||||
- 🎮 Arduino documentation:
|
||||
- I2C: arduino-documentation/i2c-ESP32
|
||||
- TFT screen : node-documentation/TFT-screen
|
||||
- Classes : arduino-documentation/classes
|
||||
- Node Documentation: node-documentation/node
|
||||
- 🍓 RPi Documentation:
|
||||
- Raspberry pi: Sp1SchetsProject/InfrastructuurDocumentatie/raspberryPi
|
||||
- MariaDB: rpi-documentation/mariadb-installation
|
||||
- phpMyAdmin: rpi-documentation/phpmyadmin-installation
|
||||
- Websockets: rpi-documentation/websockets
|
||||
- Reverse Proxy: rpi-documentation/Reverse-Proxy
|
||||
- Db - Ws connection: rpi-documentation/Databaseconnection
|
||||
- 🧠 Brainstorm:
|
||||
- Ideeën: brainstorm/ideeën
|
||||
- Database design: brainstorm/Database
|
||||
- Feedback: brainstorm/Feedback
|
||||
- Problem: brainstorm/Problem
|
||||
- Infrastructure: brainstorm/UML-infrastructure
|
||||
- Taskflow: brainstorm/Taskflow
|
||||
- Design: Sp1SchetsProject/FirstDesign
|
||||
- 🖨️ Software:
|
||||
- Dev page: brainstorm/SoftwareDocumentatie/Dev_page
|
||||
|
@@ -1,9 +0,0 @@
|
||||
## Bram's Learning Curve
|
||||
Here i post my progress on learning and mastering arduino. I originally did the Gamedevelopment study but decided to switch to "Technische Informatica". That is why i need to learn everything from scratch, and everything is new to me.
|
||||
This is the reason that i made these documents, in order to track my progression in this new study.
|
||||
|
||||
|
||||
### Buzzer
|
||||
For one of my first projects I wanted to learn how to use a buzzer with different notes.
|
||||
To achieve this, i went on a search accross sites to see how i can use frequenties to make different sounds.
|
||||
Then i came accross a teaching site (https://www.codingkids.nl/arduino-buzzer.html) that taught me the language to communicate to the buzzer what frequenties to use. This way i can make any sound come out of this simple buzzer.
|
3
docs/LearningProcessBram/documentatie/FeedbackRetro.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Personal Learning
|
||||
Keep documenting my personal progression, but ad my personal way of learning, like video's and such.
|
||||
Do update the Team document, because the current version has a bit too much generic points.
|
271
docs/LearningProcessBram/documentatie/FirstDocumentation.md
Normal file
@@ -0,0 +1,271 @@
|
||||
## Bram's Learning Curve
|
||||
Here I post my progress on learning and mastering Arduino. I originally did the Game development study but decided to switch to "Technische Informatica". That is why I need to learn everything from scratch, and everything is new to me.
|
||||
This is the reason that I made these documents, in order to track my progression in this new study.
|
||||
|
||||
### Buzzer
|
||||
For one of my first projects, I wanted to learn how to use a buzzer with different notes.
|
||||
To achieve this, I went on a search across sites to see how I can use frequencies to make different sounds.
|
||||
Then I came across a teaching site (https://www.codingkids.nl/arduino-buzzer.html) that taught me the language to communicate to the buzzer what frequencies to use. This way I can make any sound come out of this simple buzzer.
|
||||
|
||||
### Arduino Video
|
||||
I watched a video about using Arduino and took a lot of op notes along the way.
|
||||
The link is: (https://www.youtube.com/watch?v=BLrHTHUjPuw).
|
||||
|
||||
\/\/Arduino information:
|
||||
|
||||
\/pinnumber(locatie, in-/output)
|
||||
|
||||
\/digitalwrite(locatie, high/low)
|
||||
|
||||
\/delay(time in millisec)
|
||||
|
||||
\/int... <- variable
|
||||
|
||||
\/with a decimal its a double -> 1,2
|
||||
|
||||
\/a character is a char -> "a"
|
||||
|
||||
\/\/serial communications:
|
||||
|
||||
\/setup:
|
||||
Serial.begin(9600) -> the text speed
|
||||
Serial.sprintLn(text)
|
||||
|
||||
\/Loop:
|
||||
Serial.print("text")
|
||||
Serial.printLn(....) -> variable because no "
|
||||
|
||||
\/Ctrl + shift + M = serial monitor.
|
||||
The text speed needs to be the same as given.
|
||||
|
||||
\/\/If Statements:
|
||||
|
||||
if(condition){
|
||||
|
||||
action
|
||||
|
||||
}else{
|
||||
|
||||
action
|
||||
|
||||
}
|
||||
|
||||
\/&& = "and"
|
||||
|
||||
\/|| = "or"
|
||||
|
||||
\/\/For loops:
|
||||
|
||||
For(int*i; i <= 10 ; i++){
|
||||
|
||||
serial.print(i)
|
||||
|
||||
}
|
||||
|
||||
\/The fading of led's:
|
||||
|
||||
examples, basics, fade
|
||||
|
||||
\/ servo's
|
||||
|
||||
examples, servo, sweep
|
||||
|
||||
### Linux and raspberry PI.
|
||||
To gain more knowledge about Linux, I first asked my classmates if they could get me started.
|
||||
|
||||
They showed me how to gain access to a server and told me how to navigate through files.
|
||||
By doing this I got taught the following commands:
|
||||
|
||||
~ $ 'ls -la' = show file / folders
|
||||
|
||||
~ $ 'top' = see currently running programs
|
||||
|
||||
~ $ 'cd ' = change directory
|
||||
|
||||
~ $ 'mkdir name' = make directory
|
||||
|
||||
~ $ 'touch name' = make file
|
||||
|
||||
~ $ 'ping ip addres'
|
||||
|
||||
~ $ 'ssh username@ip address' = open ssh connection.
|
||||
|
||||
### Air, temperature, and all sort of stuff.
|
||||
|
||||
After the Linux coding I decided to take a step back and began gaining experience with sensors.
|
||||
I began trying to make our group project's "node" for myself.
|
||||
I did this by using one of the main sensors and tried programing it myself.
|
||||
I used this website for the information and for the right library:(https://randomnerdtutorials.com/esp32-dht11-dht22-temperature-humidity-sensor-arduino-ide/).
|
||||
Aside from the website I used my teammates for help where it was needed.
|
||||
I wanted to make my own spin on the original design by including a button to activate the sensor and an LED to show if its on.
|
||||
The rest of the tutorial was clear and worked like a charm.
|
||||
the code used looks like this:
|
||||
|
||||
```
|
||||
#include "DHT.h"
|
||||
#define DHTPIN 4
|
||||
#define DHTTYPE DHT11
|
||||
DHT dht(DHTPIN, DHTTYPE);
|
||||
|
||||
void setup() {
|
||||
//the serial port:
|
||||
Serial.begin(9600);
|
||||
|
||||
//the initial test if the code works
|
||||
Serial.println(F("DHTxx test!"));
|
||||
//the library start
|
||||
dht.begin();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void loop() {
|
||||
delay(2000);
|
||||
//a float has decimal numbers and the library reads the measurements.
|
||||
float h = dht.readHumidity();
|
||||
float t = dht.readTemperature();
|
||||
float f = dht.readTemperature(true);
|
||||
|
||||
//isnan = there is no reading , the || is "or"
|
||||
if (isnan(h) || isnan(t) || isnan(f)) {
|
||||
|
||||
Serial.println(F("Failed to read from DHT sensor!"));
|
||||
|
||||
return;
|
||||
}
|
||||
float hif = dht.computeHeatIndex(f, h);
|
||||
float hic = dht.computeHeatIndex(t, h, false);
|
||||
|
||||
//all serial.ptint's send stuff to the serial board to showcase.
|
||||
Serial.print(F("Humidity: "));
|
||||
Serial.print(h);
|
||||
Serial.print(F("% Temperature: "));
|
||||
Serial.print(t);
|
||||
Serial.print(F("°C "));
|
||||
Serial.print(f);
|
||||
Serial.print(F("°F Heat index: "));
|
||||
Serial.print(hic);
|
||||
Serial.print(F("°C "));
|
||||
Serial.print(hif);
|
||||
Serial.println(F("°F"));
|
||||
}
|
||||
```
|
||||
And the physical board looks like this:
|
||||

|
||||
And here it looks in action:
|
||||

|
||||
|
||||
Later on, I could expand this code and the physical product to include the rest of the sensors.
|
||||
|
||||
The wiring is shown here:
|
||||

|
||||
The red cables are 3v, the black cables are the ground and the green cable is the echo for data.
|
||||
This version of a DHT11 has a built-in resistor otherwise this would have to be included.
|
||||
|
||||
### Buzzers .pt 2
|
||||
I found out how to make multiple buzzers go off with the press of one button and increase as Mutch as there are pins.
|
||||
I tried to not look up anything for this one but did ask questions from time to time.
|
||||
I designed it to work with scanning if there is any input and then output this signal to activate the buzzers.
|
||||
This output signal can activate over multiple pins so this one button can set off all sorts of stuff.
|
||||
The code is short and simple:
|
||||
|
||||
```
|
||||
//set up some variables
|
||||
int button = 20;
|
||||
int buzzerone = 12;
|
||||
int buzzertwo = 11;
|
||||
|
||||
void setup() {
|
||||
//put down some pins that will output , and some that input.
|
||||
pinMode(button, INPUT);
|
||||
pinMode(buzzerone, OUTPUT);
|
||||
pinMode(buzzertwo, OUTPUT);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void loop() {
|
||||
//read is there is input on the button pin, if so send output to the other pins, otherwise keep them off.
|
||||
if(digitalRead(button) == HIGH){
|
||||
digitalWrite(buzzerone, HIGH);
|
||||
digitalWrite(buzzertwo, HIGH);
|
||||
}else{
|
||||
digitalWrite(buzzerone, LOW);
|
||||
digitalWrite(buzzertwo, LOW);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here I made the physical design but instead of buzzers I used lights in order to show it working.
|
||||

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

|
||||
|
||||
### Python For Dummies.
|
||||
My job was to make a connection between the WebSocket and the database we had set up, and to do this we wanted to use python.
|
||||
To give an easy picture, here is where i would come:
|
||||

|
||||
It looks easy, but for someone who never worked wit hpython and linux, this will prove to be a challenge.
|
||||
|
||||
Because I was totally new to all of python I began asking friends for advice and started asking chatgpt for some examples.
|
||||
and worked in reverse in order to understand python fully.
|
||||
I went and looked up fitting tutorials on W3SChools.
|
||||
The following were used:
|
||||
|
||||
(https://www.w3schools.com/python/python_mysql_getstarted.asp)
|
||||
|
||||
(https://websockets.readthedocs.io/en/stable/)
|
||||
|
||||
(https://www.w3schools.com/python/python_mysql_insert.asp)
|
||||
|
||||
(https://www.w3schools.com/python/python_json.asp)
|
||||
|
||||
After still not getting it very well i asked Dano (a teammate of mine) if he could teach me some basics, and so we did.
|
||||
He taught me how to make a simple calculator and told me why my code worked the way it did.
|
||||
(This was the code:
|
||||
|
||||
// this prints a cheatsheet to know if the user wants plus or minus.
|
||||
print("1 = + 2 = -")
|
||||
c = int(input())
|
||||
|
||||
// a input is given for the first number.
|
||||
print("first number")
|
||||
a = input()
|
||||
|
||||
//a input is given for the second number.
|
||||
print("second number")
|
||||
b= input()
|
||||
|
||||
//a if-statement to see if it is plus or minus.
|
||||
if c == 1:
|
||||
print(int(a) + int(b))
|
||||
elif c == 2:
|
||||
print(int(a) - int(b))
|
||||
)
|
||||
|
||||
Even if it looked simple, this was the ignition I needed to understand python better and continue my own research.
|
||||
|
||||
### Python for dummmies pt2
|
||||
After some intense trail and error, I managed to connect to the websocket.
|
||||
After wich I also managed to send infromation to the database by including details like pasword and such.
|
||||
I began investigating deeper and asked for other people's vision on my code.
|
||||
|
||||
It wasa hard to notice what problems there were, but eventualy me and a classmate found out that the problem was inside of the database itself instead of the code. So after fixing some issues with the primary keys and some tesing with the code, I managed to fix the issues that popped up.
|
||||
|
||||
Now the code is able to send websocket data to the database under the name of node "1".
|
||||
This will have to be updated so all names could be sent to the database without causing issues.
|
||||
But the current code does what we expect from sprint two but I will surely continue working on making it perfect.
|
||||
|
||||
### python for dummies pt3
|
||||
After the sprint review for sprint two, we as a team decided that it was time to make progress with connecting more nodes, and so I did.
|
||||
|
||||
I began searching for leads on how to grab information from a database, and used this website to teach me:
|
||||
https://www.w3schools.com/python/python_mysql_select.asp
|
||||
|
||||
Once I figured out how to grab information, I wanted to put it in an array and look if the connected node(wich I put in a varriable) was already known in the array. The webpage for this was: https://stackabuse.com/python-check-if-array-or-list-contains-element-or-value/
|
||||
|
||||
This originaly didn't work, and by printing all my variables and the array I initially didn't see anything that would prevent it from working.
|
||||
But for some reason the given node wouldn't compare itself to the nodes in the array.
|
||||
That is when I found out the data from the array was in a tuple. So when I changed the variable to turn into a tuple, it worked like a charm.
|
||||
|
||||
Then after all of this trouble, I finished by putting the new node MAC (in string format) into the correct collum in the database so this will MAC will be saved in the future.
|
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/LearningProcessBram/documentatie/assets/Buzzer board on.jpg
Normal file
After Width: | Height: | Size: 1.0 MiB |
BIN
docs/LearningProcessBram/documentatie/assets/DHT11 state 1.jpg
Normal file
After Width: | Height: | Size: 572 KiB |
BIN
docs/LearningProcessBram/documentatie/assets/DHT11 state 2.jpg
Normal file
After Width: | Height: | Size: 596 KiB |
BIN
docs/LearningProcessBram/documentatie/assets/DHT11 wires.png
Normal file
After Width: | Height: | Size: 298 KiB |
BIN
docs/LearningProcessBram/documentatie/assets/myconnection.png
Normal file
After Width: | Height: | Size: 18 KiB |
41
docs/Sp1SchetsProject/FirstDesign.md
Normal file
@@ -0,0 +1,41 @@
|
||||
## The First Designs(led By Bram)
|
||||
The first designs are made using a pencil and paper, because we want a clear view of the direction we want to go in. The design of the nodes that are being put around school, wil have a compact and simple look.
|
||||

|
||||
On the top of this, page the front of the node is visable.
|
||||
This front shows a screen , a bar with led's ( wich is used to show how much sound is picked up on the node.), and a hexagon grid where the sensors are located behind.
|
||||
This way we can have enough airflow and more sensor pickups.
|
||||
|
||||
On top of the node there are two lights. These indicate if the air is too warm for comfort, so the cleaners are aware of the the current studying climate.
|
||||
|
||||
The nodes will be powerd by batteries and wil be mounted on walls around eye height. They will latch on a mount that is stuck using dubble sidded tape. this way we can mount and dismount the nodes when the batteries need to be replaced.
|
||||
|
||||
(The mount and the sode profile are shown on the bottom of the page.)
|
||||
|
||||
Next up is the central node that has a questionaire built into it.
|
||||
|
||||

|
||||
|
||||
On the top Of the page The front of the node is drawn. The node has two screens to show both the statistics and the questionaire. On the node, three Buttons are show wich the user can press in order to take the questionaire.
|
||||
|
||||
This node still has all the sensors like all of the others, except this one has a built in questionaire.
|
||||
|
||||
The locations for the nodes are still unsure but what is certain is that the questionaire node will be placed besides the water fountain and cofemachines.
|
||||
|
||||
This location is frequently visited and is a wel known spot.
|
||||
The people going for a cup of coffee often have to wait for it to be finished, so we can take advantage of that time by giving them a short questionaire that they can comforably fil in while their cofee is being made.
|
||||
|
||||
### Feedback
|
||||
|
||||
As for feedback, we got a lot of positives, but also a few critic points.
|
||||
`the LED's could be seen as distracting.
|
||||
|
||||
That is why we decided to remove them intirely. THis wat the lights can't be distracting and the focus can remain on the statistics.
|
||||
|
||||
`The whole ventilation look is a bit too crowded and messy.
|
||||
|
||||
That is why we will shrink down the size of the hexagons and make them less obvious.
|
||||
|
||||
We also redesignd the shell shape. it now curves inward in order to lock the statistics screen in place.
|
||||
|
||||
Here is an updated version ready to be printed.
|
||||

|
@@ -1,6 +1,6 @@
|
||||
#Rasberry Pi
|
||||
# Rasberry Pi
|
||||
|
||||
##Table of contents
|
||||
## Table of contents
|
||||
1. [Introduction](##introduction)
|
||||
2. [Rasberry Pi](##Rasberry-Pi)
|
||||
3. [Dev-Page](###Dev-Page)
|
||||
@@ -8,20 +8,24 @@
|
||||
5. [Database software](###Database-software)
|
||||
6. [Esp to database](###Esp-to-database)
|
||||
|
||||
##Introduction
|
||||
## Introduction
|
||||
|
||||
The backend of this project there is a raspberry pi 3 for handeling the dev page (more on this later) and the database. The Esp nodes are going to send a api request to the dev page wich is going to make a POST request to the database.
|
||||
In the backend of this project, there is a Raspberry Pi 3 for handling the dev page (more on this later) and the database. The ESP nodes are going to send an API request to the dev page, which is going to make a POST request to the database.
|
||||
|
||||
##Rasberry Pi
|
||||
## Rasberry Pi
|
||||
|
||||
The Raspberry 3 can be descriped as a "little" computer running a quad-code 64-bit prossesor, the previus Raspberry witch is 50% slower than this one
|
||||
The Raspberry 3 can be descriped as a "little" computer running a quad-code 64-bit prossesor, the previus Raspberry witch is the pi 2 and is 50% slower. The operaiting system is linux, it is a open source os system, we are using it for database and website hosting purposes, but it can do a wide range of things. The GPIO pins make it usefull for even more things, like home automation with sensors.
|
||||
|
||||
The Raspberry Pi 3 is equipped with a quad-core 64-bit Broadcom BCM2837 ARM Cortex-A53 SoC processor running at 1.2 GHz, making it about 50% more powerful than the Pi 2.
|
||||
### Dev Page
|
||||
|
||||
###Dev Page
|
||||
For monitoring porpoises we are going to implement a dev page, this means that there basicey is a debug page. On this page we will be displaying the current readings that are comming in in to graphs and also the data form the last 10 redings from all the nodes, the readings are sorted in there respective graphs like temp, humidity and noice redings for example. This is handy for quick access to the data ouwr nodes are sending out. So it will be easy to see if one is malvuntion and monetoring the state off the battery to check if its nessesery to replace it.
|
||||
|
||||
##Database
|
||||
### Database
|
||||
|
||||
###Database software
|
||||
## Database
|
||||
|
||||
###Esp to database
|
||||
The database is set up on the raspberry so that we dont always have to have a computewr
|
||||
|
||||
### Database software
|
||||
|
||||
### Esp to database
|
34
docs/Sprint1/sprint1Revieuw.md
Normal file
@@ -0,0 +1,34 @@
|
||||
### Begin met een introductie van het team (jullie namen + teamnaam)
|
||||
|
||||
Welkom bij de sprint review, wij zijn *insert namen*.
|
||||
|
||||
### Leg uit wat jullie van plan zijn om op te leveren; wat is de opdracht, wat gaat er waarschijnlijk uitkomen.
|
||||
|
||||
Wij hebben de opdracht gekregen om de studieruimte te optimaliseren. Hiervoor moeten we data verzamelen over de studieomgeving en de meningen van onze medeleerlingen. We hebben feedback gevraagd bij studenten en daar is uit gebleken dat mensen moeite hebben met een rustige omgeving te vinden. Daarom gaan we een systeem bouwen waardoor je makkelijk rustige studieplekken kan vinden.
|
||||
|
||||
Dit gaan we doen door het plaatsen van een centrale hub node en een aantal losse hub nodes. Deze nodes zullen verschillende datapunten verzamelen met behulp van sensoren, namelijk: temperatuur, luchtvochtigheid, ppm van koolstof en TVOC. Daarnaast hebben we een display waarop de huidige metingen worden getoond. In de centrale hub is ook een meetstation en een tweede scherm met enkele enquêteachtige vragen, waarbij gebruikers hun mening kunnen geven met behulp van 3 knoppen. We moeten nog testen of dit toegankelijk genoeg is. We hebben ook al een basisidee van hoe het design van de nodes eruit zal zien. *insert bram funny pic*
|
||||
|
||||
### Bespreek welke User Stories jullie hebben gemaakt voor de Product Backlog.
|
||||
|
||||
*laat belangrijke user stories zien*
|
||||
|
||||
*leg uit waarom*
|
||||
|
||||
### Bespreek welke User Stories jullie hebben opgepakt in de Sprint Backlog.
|
||||
|
||||
- [Luchtmeten](https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/issues/36)
|
||||
- [Temperatuur meten](https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/issues/18)
|
||||
- [Schermpje op node](https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-3/qaajeeqiinii59/-/issues/39)
|
||||
|
||||
In de eerste sprint werken we vooral aan het algehele plan en de basis van het project, met name de Raspberry Pi die tot onze beschikking is gesteld voor de backend. Deze moeten we zelf ontwerpen. Daarnaast moeten we zaken zoals de wireflows, de infrastructuur: , en het [uml diagram](../brainstorm/UML%20infrastructure.md) regelen.
|
||||
|
||||
### Geef een demonstratie van het product.
|
||||
|
||||
*show node*
|
||||
|
||||
### Geef een overzicht van jullie portfoliowebsite (highlights).
|
||||
|
||||
We hebben op onze portfoliopagina verschillende documentaties geplaatst, met name:
|
||||
- [Het algehele idee](https://opti.smikkelbakje.nl/brainstorm/idee%C3%ABn/)
|
||||
- [Databaseontwerp](https://opti.smikkelbakje.nl/brainstorm/Database/)
|
||||
- [I2C-communicatie](https://opti.smikkelbakje.nl/arduino-documentation/i2c-ESP32/)
|
73
docs/arduino-documentation/classes.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Arduino classes
|
||||
|
||||
|
||||
## How do I make a class?
|
||||
|
||||
First you need to start out with a header file. This file will contain the class definition. And which functions of the class are public and which are private.
|
||||
|
||||
```cpp
|
||||
#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);
|
||||
|
||||
public:
|
||||
DisplayText(Adafruit_ST7796S_kbv& tftDisplay);
|
||||
void writeText(char* text, int size, int posX, int posY, int screenTime, bool center, bool bottom);
|
||||
};
|
||||
|
||||
#endif
|
||||
```
|
||||
```#ifndef``` DISPLAYTEXT_H is a preprocessor directive that checks if DISPLAYTEXT_H is defined. If it is not defined, the code between #ifndef and #endif will be included in the compilation. If it is defined, the code will be excluded from the compilation. This is to prevent the same code from being included multiple times in the same file.
|
||||
|
||||
|
||||
To start out with a class you need a constructor. This is a function that is called when the class is created. This function is used to initialize the class.
|
||||
```cpp
|
||||
DisplayText::DisplayText(Adafruit_ST7796S_kbv& tftDisplay) : tft(tftDisplay) {
|
||||
tft.fillScreen(0x0000);
|
||||
}
|
||||
```
|
||||
The library gets passed to tftdisplay. And the tft gets initialized with the tftdisplay.
|
||||
|
||||
|
||||
|
||||
## How do I create a function in a class?
|
||||
```cpp
|
||||
void DisplayText::centerText() {
|
||||
//insert code here
|
||||
}
|
||||
```
|
||||
This is a example of a function in a class. The function is called centerText and it is a private function. This means that it can only be called from within the class.
|
||||
|
||||
|
||||
When creating a class you also need to reference it in the header file like this
|
||||
|
||||
```cpp
|
||||
#include "DisplayText.h"
|
||||
|
||||
class DisplayText {
|
||||
private:
|
||||
int centerText();
|
||||
//other functions
|
||||
|
||||
public:
|
||||
//other functions
|
||||
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
# Sources
|
||||
* https://www.circuitbasics.com/programming-with-classes-and-objects-on-the-arduino/
|
||||
|
||||
|
||||
|
BIN
docs/assets/ImagesSp1/Sp13DDesignNode.png
Normal file
After Width: | Height: | Size: 656 KiB |
BIN
docs/assets/ImagesSp1/Sp13DDesignUpdated.png
Normal file
After Width: | Height: | Size: 718 KiB |
BIN
docs/assets/ImagesSp1/Sp1NodeBottomSide.png
Normal file
After Width: | Height: | Size: 338 KiB |
BIN
docs/assets/ImagesSp1/Sp1NodeUpperSide.png
Normal file
After Width: | Height: | Size: 347 KiB |
BIN
docs/assets/ImagesSp1/Sp1NodeUpperSideNew.png
Normal file
After Width: | Height: | Size: 500 KiB |
BIN
docs/assets/ImagesSp1/Sp1questionBoxNode.jpg
Normal file
After Width: | Height: | Size: 470 KiB |
BIN
docs/assets/ImagesSp1/assemblyOfNodeSideview.jpg
Normal file
After Width: | Height: | Size: 465 KiB |
BIN
docs/assets/ImagesSp1/assemblyOfNodeTopview.jpg
Normal file
After Width: | Height: | Size: 1.5 MiB |
BIN
docs/assets/ImagesSp1/sp1NodeSketch.jpg
Normal file
After Width: | Height: | Size: 445 KiB |
BIN
docs/assets/Technical/Enquete box.f3d
Normal file
BIN
docs/assets/Technical/node casing v3.f3d
Normal file
BIN
docs/assets/Technical/node lower body.stl
Normal file
BIN
docs/assets/Technical/node upper body.stl
Normal file
BIN
docs/assets/imagesSp2/IMG_1131.jpg
Normal file
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/assets/imagesSp2/fritzingscreen.png
Normal file
After Width: | Height: | Size: 151 KiB |
BIN
docs/assets/imagesSp3/wiringDiagramNode.png
Normal file
After Width: | Height: | Size: 130 KiB |
36
docs/brainstorm/Feedback.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Feedback
|
||||
|
||||
### Studieomgeving(Led by Sam)
|
||||
|
||||
* "Betere zitplekken"
|
||||
* "Mensen minder praten"
|
||||
* "Geluidsgehalte lager in studieomgeving"
|
||||
* "Zachtere lachten"
|
||||
* "Minder domme mensen"
|
||||
|
||||
### What are we going to do to improve the study area?
|
||||
We are going to build multiple sensor nodes and hang them around study areas to measure the sound level. These nodes are going to send the data to a server, which is going to process the data and send it to the main node that is located at the coffee machine. This node is going to show the data so the student can quickly find a quiet spot to work.
|
||||
|
||||
|
||||
### Feedback about physical node design(Led By Sam)
|
||||
|
||||
* "Cool"
|
||||
* "Goed"
|
||||
* "Prima"
|
||||
* "Is wel Nice"
|
||||
* "Vet idee"
|
||||
* "Duidelijker maken wat lampjes rechts doen"
|
||||
* "Beetje te druk"
|
||||
* "Display in midden"
|
||||
* "Ventilatie niet in het midden focus niet op scherm"
|
||||
* "Lampjes mischien afleidend"
|
||||
|
||||
|
||||
### Conclusion(led by Bram and Sam)
|
||||
We got a lot of positive feedback like "Cool" and "Vet idee". But we also got some feedback about the design of the node.
|
||||
|
||||
We Wil use this Feedback and see what we can do to change the design in a way that is more accepted by our target audience.
|
||||
The design will be updated to include:
|
||||
`smaller holes for less distraction.
|
||||
`ventilation at the bottom for better airflow.
|
||||
`Remove the lamps on top of the node for less distraction.
|
@@ -1,5 +1,31 @@
|
||||
# Questions
|
||||
|
||||
How clean are the toilets (clean, normal, disgusting)
|
||||
How clean is the study area (clean, normal, disgusting)
|
||||
What do you think of the temparature in the study area (hot, perfect, cold)
|
||||
## Criteria
|
||||
Questions shouldn't be able to measure using sensors. 3 possible answers.
|
||||
|
||||
## Questions
|
||||
|
||||
- How clean are the toilets? (clean, normal, disgusting).
|
||||
- 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).
|
||||
|
||||
### Feedback questions
|
||||
|
||||
- Zijn de vragen duidelijk en snel te beantwoorden hierboven?
|
||||
|
||||
- Hebben jullie nog andere feedback voor gebouwbeheer, die niet gemeten kan worden door sensoren?
|
||||
|
||||
## Feedback
|
||||
|
||||
1. vraag 5 is onduidelijk. Wat bedoel je met genoeg hulp? Docenten, schoonmakers? Andere manier formuleren over welke hulp. Andere vragen zijn duidelijk. Er kan een vraag worden toegevoegd over of het goed gaat met de studie of over de dag. Dit is niet heel hulpvol voor gebouwbeheer maar voor de studenten zelf wel.
|
||||
|
||||
|
||||
2. andere variant van antwoord geven op de vragen. Bijvoorbeeld een schaal van 1 tot 5. Of een schaal van 1 tot 10. Dit is makkelijker te verwerken en te analyseren. Voor de rest zijn de vragen duidelijk. Deze persoon heeft geen andere feedback voor gebouwbeheer want deze vragen zijn genoeg
|
||||
|
||||
3. De vragen zijn duidelijk en snel te beantwoorden. behalve vraag 5 die onduidleijk is. geen andere vragen vragen als feedback.
|
||||
|
||||
## Conclusion
|
||||
|
||||
The questions are clear and easy to answer, except for question 5 which is unclear.
|
76
docs/brainstorm/SoftwareDocumentatie/Dev_page.md
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
|
||||
### parsing JSON
|
||||
|
||||
The data that is send by the nodes is send in json data. We chose this becouse we can easily use the data within javascript, this is called parsing. We use the parced data and send that in to the next function to make the data more acceseble to use in further instences.
|
||||
|
||||
```js
|
||||
const jsonData = JSON.parse(event.data);
|
||||
// Use the parsed JSON data as needed
|
||||
handleIncomingData(jsonData);
|
||||
```
|
||||
|
||||
Here is the function that receves the parced JSON data and set it into variables. So that it can be more easlily used in the next function with is the processNodeData, witch is setting the data in to the right array.
|
||||
|
||||
```js
|
||||
function handleIncomingData(data) {
|
||||
nodeNumber = data.node;
|
||||
temperature = data.Temp;
|
||||
humidity = data.Humi;
|
||||
CO2 = data.eCO2;
|
||||
TVOC = data.TVOC;
|
||||
|
||||
processNodeData(nodeNumber, temperature, humidity, CO2, TVOC);
|
||||
}
|
||||
```
|
||||
|
||||
In the function processNodeData we first check if there is a array for the node that is sending the data, this is done becouse if we want to seperate the data in to show witch node is sending what data. So if the nodeNumber plus sensorData (the name of the array) not already there the array is made.
|
||||
|
||||
```js
|
||||
// Initialize the array for this node if it doesn't exist yet
|
||||
if (!sensorData[nodeNumber]) {
|
||||
sensorData[nodeNumber] = [];
|
||||
}
|
||||
```
|
||||
|
||||
For the actual data put in to array function is there a simple array.push for the data that is send allong from when the function is called.
|
||||
|
||||
```js
|
||||
// Push the new data onto the array for this node
|
||||
sensorData[nodeNumber].push({
|
||||
'node': nodeNumber,
|
||||
'temp': temperature,
|
||||
'humi': humidity,
|
||||
'CO2': CO2,
|
||||
'TVOC': TVOC,
|
||||
});
|
||||
```
|
||||
|
||||
We want only use the last 10 readings from the nodes so there is a check for if the array is longer than 10 the first (or the oldest reading), if that is so there is a .shift executed. This is done to be later used in the graph function. Than there is a call for the next function, that is the updateNodeData and that will acctually find the right html id coresponding with the right reading to update that.
|
||||
|
||||
```js
|
||||
// If the array for this node has more than 10 elements, remove the oldest one
|
||||
if (sensorData[nodeNumber].length >= 10) {
|
||||
sensorData[nodeNumber].shift();
|
||||
}
|
||||
|
||||
updateNodeData(nodeNumber, temperature, humidity, CO2, TVOC);
|
||||
```
|
||||
|
||||
In the last function there are 2 updates the first is to actually update the text to the right value that we are getting from the node, and the connection checker.
|
||||
|
||||
```js
|
||||
function updateNodeData(node, temperature, humidity, eCO2, TVOC) {
|
||||
// Update the temperature, humidity and light intensity values
|
||||
document.getElementById("temperature" + node).textContent = temperature;
|
||||
document.getElementById("humidity" + node).textContent = humidity;
|
||||
document.getElementById("CO2" + node).textContent = eCO2;
|
||||
document.getElementById("TVOC" + node).textContent = TVOC;
|
||||
|
||||
// Update the status text
|
||||
document.getElementById("tempStatus").textContent = "Connected";
|
||||
document.getElementById("humidStatus").textContent = "Connected";
|
||||
document.getElementById("CO2Status").textContent = "Connected";
|
||||
document.getElementById("TVOCStatus").textContent = "Connected";
|
||||
}
|
||||
```
|
117
docs/brainstorm/SoftwareDocumentatie/classes.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Nodes
|
||||
|
||||
## Introduction
|
||||
|
||||
The nodes are the devices that are placed in the rooms. The nodes are used to collect the data from the sensors. Every node is connected to the websocket, and sends their data with their mac address in json format. The websocket broadcasts the node data back to all clients, and since our website functions as a client it also receives the data. Every node will, depending on what node, be made into a class.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Sensornode
|
||||
|
||||
- Every node has to have a unique nodeID
|
||||
- Every node has to have their corresponding sensorsvalues in form of arrays
|
||||
|
||||
### Feedbacknodes
|
||||
|
||||
- Every node has to have a unique nodeID
|
||||
- Every node has to have their corresponding feedback in form of a 2D array
|
||||
|
||||
## Class diagrams
|
||||
|
||||
### Node
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Node {
|
||||
+nodeID
|
||||
+processNodeData()
|
||||
+updateNodeData()
|
||||
}
|
||||
```
|
||||
|
||||
#### Sensornode
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class SensorNode extends Node {
|
||||
+tempArray
|
||||
+humiArray
|
||||
+eco2Array
|
||||
+tvocArray
|
||||
}
|
||||
```
|
||||
|
||||
#### Feedbacknode
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class FeedbackNode extends Node {
|
||||
+feedbackArray
|
||||
}
|
||||
```
|
||||
|
||||
# Graphs
|
||||
|
||||
## Introduction
|
||||
|
||||
The graphs are used to display the data from the sensors. The data is collected by the raspberry pi and then displayed on the graphs. The graphs are made using the [plotly library](https://plotly.com/javascript/) .
|
||||
|
||||
## Requirements
|
||||
|
||||
### Live graphs
|
||||
- Every node has to have a live graph
|
||||
- The graphs has to be updated every 5 seconds
|
||||
- All the data from one node has to fit in one graph
|
||||
|
||||
|
||||
## Class diagrams
|
||||
|
||||
### Graphs
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class graph {
|
||||
+nodeId
|
||||
makeGraph()
|
||||
}
|
||||
```
|
||||
|
||||
### Live graphs
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class liveGraph extends graph {
|
||||
+cnt
|
||||
+timeArray
|
||||
+tempArray
|
||||
+humiArray
|
||||
+eco2Array
|
||||
+tvocArray
|
||||
makeGraph()
|
||||
updateGraph()
|
||||
updateData()
|
||||
}
|
||||
```
|
||||
|
||||
## Order of operations
|
||||
|
||||
### Live graphs
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Node
|
||||
participant Raspberry pi
|
||||
participant Website
|
||||
|
||||
Node->>Raspberry pi: sensordata via websocket every 5 seconds
|
||||
Raspberry pi->>Website: Node data via websocket if new data is received from the node
|
||||
Website->>Website: updateGraph()
|
||||
Website->>Website: updateData()
|
||||
```
|
||||
|
||||
1. Every node sends its data to the raspberry pi via websocket every 5 seconds
|
||||
2. The raspberry pi sends the data to the website via websocket if new data is received from the node
|
||||
3. The website updates the data coming from the raspberry pi on its own variables and arrays
|
||||
4. The website updates the live graphs every time new data is received from the websocket
|
||||
|
||||
|
@@ -11,6 +11,9 @@ Raspberry pi <--> EnqueteNode : Websocket
|
||||
|
||||
namespace Server {
|
||||
class Raspberry pi {
|
||||
+MariaDB
|
||||
+Apache2
|
||||
+Python
|
||||
Database()
|
||||
Webserver()
|
||||
Websocket()
|
||||
@@ -27,7 +30,6 @@ namespace Server {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace User {
|
||||
class Website {
|
||||
+Co2
|
BIN
docs/brainstorm/samenwerkingTijdlijn.jpg
Normal file
After Width: | Height: | Size: 1.2 MiB |
@@ -1,8 +1,10 @@
|
||||
# TI Portfolio Website
|
||||
|
||||
## Team deurMat
|
||||
Welkom op jullie portfolio website! Hier kunnen jullie documentatie kwijt
|
||||
die jullie gaan schrijven voor jullie project.
|
||||
|
||||
Maak je pagina's vooral leuk door [plaatjes](https://knowledgebase.hbo-ict-hva.nl/1_beroepstaken/software/realiseren/talen/declaratief/markdown/1_markdown_basics/#afbeeldingen), [video's](https://knowledgebase.hbo-ict-hva.nl/1_beroepstaken/software/realiseren/talen/declaratief/markdown/videos/) en [diagrammen](https://knowledgebase.hbo-ict-hva.nl/1_beroepstaken/software/realiseren/talen/declaratief/markdown/mermaid_diagrammen/) toe te voegen; dit kan allemaal in [markdown](https://knowledgebase.hbo-ict-hva.nl/1_beroepstaken/software/realiseren/talen/declaratief/markdown/0_markdown/)!
|
||||
|
||||
|
||||
|
||||
{{ mdocotion_header('https://images.unsplash.com/photo-1498050108023-c5249f4df085?q=80&w=2072&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D') }}
|
||||
|
105
docs/node-documentation/TFT-screen.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# TFT screen
|
||||
|
||||
|
||||
## Prequesites
|
||||
|
||||
To use the TFT screen, you need to install the following libraries:
|
||||
|
||||
``` SPI.h , Adafruit_GFX.h, Adafruit_ST7796S_kbv.h ```
|
||||
|
||||
|
||||
## Programming the screen
|
||||
To program the screen we need to first declare all the pins and give it to the Adafruit_ST7796S_kbv.h library. Then we need to initialize the screen and we can start using it.
|
||||
|
||||
```cpp
|
||||
#define TFT_CS 14
|
||||
#define TFT_DC 13
|
||||
#define TFT_RST 12
|
||||
#define MOSI 11
|
||||
#define SCK 10
|
||||
#define MISO 9
|
||||
|
||||
//simplify the name of the library and feed all the correct pins to it
|
||||
|
||||
Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, MOSI, SCK, TFT_RST, MISO);
|
||||
```
|
||||
|
||||
## Drawing on the screen
|
||||
|
||||
To draw on the screen we can use the following functions:
|
||||
|
||||
```cpp
|
||||
tft.fillScreen(0x0000); //clear the screen
|
||||
tft.fillRect(0, 0, 240, 240, 0xFFFF); //draw a white rectangle
|
||||
tft.fillCircle(120, 120, 50, 0xF800); //draw a red circle
|
||||
tft.fillTriangle(0, 0, 240, 240, 0, 240, 0x1F); //draw a blue triangle
|
||||
tft.fillRoundRect(0, 0, 240, 240, 10, 0xF81F); //draw a pink rounded rectangle
|
||||
tft.drawChar(120, 120, 'A', 0xFFFF, 0x0000, 2); //draw a character
|
||||
tft.setTextSize(2); //set the text size
|
||||
tft.setTextColor(0xFFFF); //set the text color
|
||||
tft.setCursor(0, 0); //set the cursor
|
||||
tft.println("Hello World!"); //print a string
|
||||
```
|
||||
|
||||
To write Hello world on the screen we can use the following code:
|
||||
|
||||
```cpp
|
||||
tft.fillScreen(0x0000); //clear the screen
|
||||
tft.setTextSize(2); //set the text size
|
||||
tft.setTextColor(0xFFFF); //set the text color
|
||||
tft.setCursor(0, 0); //set the cursor so it starts at the top right, you can also make this other numbers to change the position
|
||||
tft.println("Hello World!"); //print a string
|
||||
```
|
||||
|
||||
To clear the screen for new text you can use the following code:
|
||||
|
||||
```cpp
|
||||
tft.fillScreen(0x0000); //clear the screen
|
||||
```
|
||||
## Wiring
|
||||
You can connect the wires to any pin except the vcc and the ground. The vcc and ground need to be connected to the 3.3v and ground of the esp32. The other pins can be connected to any pin of the esp32.
|
||||
|
||||

|
||||
|
||||
We aren't using the cs2 and the pen pin because we arent utilizing the touch function of the screen.
|
||||
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
|
||||
displayText <|-- Adafruit_ST7796S_kbv
|
||||
|
||||
|
||||
class displayText{
|
||||
+text
|
||||
+color
|
||||
+size
|
||||
+posx
|
||||
+posy
|
||||
+void writeText()
|
||||
-int bottomText(char* text)
|
||||
-void printWordsFull(char* text)
|
||||
-int centerText(char* text)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
class Adafruit_ST7796S_kbv{
|
||||
+fillScreen()
|
||||
+fillRect()
|
||||
+fillCircle()
|
||||
+fillTriangle()
|
||||
+fillRoundRect()
|
||||
+drawChar()
|
||||
+setTextSize()
|
||||
+setTextColor()
|
||||
+setCursor()
|
||||
+println()
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
|
64
docs/node-documentation/node-design.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Node Design
|
||||
|
||||
## Design Process (led by sietse)
|
||||
### Thoughts
|
||||
|
||||
Before making the design i was thinking about the following things:
|
||||
|
||||
- The node should be compact and simple
|
||||
- The node should have a screen
|
||||
- The node should have a bar with led's to show how much sound is picked up on the node
|
||||
- The node should have a hexagon grid where the sensors are located behind
|
||||
- The node should have two lights on top
|
||||
- The nodes should be powered by batteries
|
||||
|
||||
I also thought about the following things on how to make the design. I have decided to make the design using fusion360, because i have some experience with it and i think it is a good tool to make the design. I wanted it to be really easy to assemble so i split the design in two parts. The top and the bottom. The top part will be the part with the screen and the sensors, and the bottom part will be the part with the whole circuitboard.
|
||||
|
||||
### Bottom design
|
||||
I made the final design of sprint 1 using fusion360. I used the original drawing from Bram and modified it using the feedback we've got from the usertest. I begun making the bottom side of the node, because this could serve me as a template to make the rest. The bottom side was pretty easy to make because i only had to sketch and measure a few things. It eventually looked like this:
|
||||
|
||||

|
||||
|
||||
### Top design
|
||||
When i went to work on the upper side it took some more time than the bottom one. since i had to make cutouts for the screen and led bar and the hexagon grid. The hexagon grid was the hardest because i used the geometric pattern fucntion on my hexagon object and i had to make sure that there would still be room between the hexagons. I made it so the screens have like a indent on the top part. Otherwise the battery, sensors and esp32 wouldnt be able to fit underneath the grid. The final product looks like this:
|
||||
|
||||

|
||||
|
||||
### Assembly
|
||||
|
||||
#### First assembly
|
||||
The part was finished on 15/2 at 11am. I first screwed the pcb in the bottom part which was really easy to do. When trying to put the top part on the bottom part i noticed that the holes were not aligned, and the batteryholder was fatter than i thought. I tried to cut some material away from the top part but it was still not fitting.
|
||||
|
||||
It looked like this from the top:
|
||||
|
||||

|
||||
|
||||
You can see that the oled screen cutouts and the led bar cutouts are not aligned with the components on the pcb.
|
||||
|
||||
---
|
||||
|
||||
And like this from the side view:
|
||||
|
||||

|
||||
|
||||
Here you can see that the wifi antenna of the esp32 is not fitting in the top part, because the fillet is too small.
|
||||
|
||||
#### Second assembly
|
||||
In fusion360 i made the cutouts for the screen and the led bar lower on the part. I also made the fillet on the top part a bit bigger, and adjusted the fillet on top too. We also decided we'd take this oppurtunitty to make vent holes in the bottom aswell. This is because hot air rises and with this there would be a little bit of air coming through the node for more accurate readings. I then printed the new design and it took around 7 hours. The new design looks like this:
|
||||
|
||||
|
||||

|
||||
|
||||
The new design fits perfectly and looks like this:
|
||||
|
||||
...
|
||||
|
||||
|
||||
### Conclusion
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Design Sketch] -->|Usertest| B(Fusion360 design)
|
||||
B -->|Not fitting| C[New fusion360 design]
|
||||
C -->|assembly| D[Finished product]
|
||||
```
|
72
docs/node-documentation/node.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Node
|
||||
|
||||
Since the node is what gathers all the data from the sensors, it is the most important part of the system. The node is responsible for reading the data from the sensors, processing it, and sending it to the server.
|
||||
|
||||
## Hardware
|
||||
|
||||
The node is composed of the following hardware components:
|
||||
|
||||
- [ESP32 S3 DevkitC](https://www.espressif.com/en/products/socs/esp32-s3)
|
||||
- [SGP30](https://www.sensirion.com/en/environmental-sensors/gas-sensors/sgp30/)
|
||||
- [DHT11](https://www.sparkfun.com/datasheets/Sensors/Temperature/DHT11.pdf)
|
||||
- [OLED Display](https://www.tinytronics.nl/nl/displays/oled/1.3-inch-oled-display-128*64-pixels-wit-i2c)
|
||||
|
||||
## Software
|
||||
|
||||
The node is programmed using the Arduino IDE. The code is written in C++ and is responsible for reading the data from the sensors, processing it, and sending it to the server. The first couple versions of the code were written by Sietse, and because we all had to use Object Oriented Programming, Dano rewrote the code to be object-oriented. Which is now final version
|
||||
|
||||
### Libraries
|
||||
|
||||
The following libraries are used in the node code:
|
||||
|
||||
- <Wire.h>
|
||||
- <Adafruit_SH110X.h
|
||||
- <Adafruit_SGP30.h>
|
||||
- <DHT.h>
|
||||
- <WiFiMulti.h>
|
||||
- <WiFi.h>
|
||||
- <WebSocketsClient.>
|
||||
- <nodeCodeHeader.h>
|
||||
|
||||
### Code
|
||||
|
||||
The code is divided into the following classes:
|
||||
|
||||
uitleg dano
|
||||
|
||||
### Communication
|
||||
|
||||
The node communicates with the server using WebSockets. The node sends the data to our website which uses a reverse proxy to route it to the websocket. The server then processes the data and stores it in the database / puts it on the website. We use the WebSocketsClient library to communicate with the server. Because [websocket connections](docs\rpi-documentation\websockets.md) are stateful, we need to keep the connection alive. So we do not use any delays in the code, but instead use the millis() function to keep track of time.
|
||||
|
||||
Flow of the data:
|
||||
|
||||
```mermaid
|
||||
|
||||
flowchart LR
|
||||
A(Sensors)
|
||||
B(Node)
|
||||
D(Websocket)
|
||||
E(Website)
|
||||
F(DB-Client)
|
||||
G(Database)
|
||||
|
||||
A-->|sensordata| B
|
||||
B -->|JSONData| D
|
||||
D -->|JSONData| E
|
||||
D -->|JSONData| F
|
||||
F -->|Data| G
|
||||
```
|
||||
|
||||
### Wiring Diagram
|
||||
|
||||
The wiring diagram for the node is as follows:
|
||||
|
||||

|
||||
|
||||
## Future Improvements
|
||||
|
||||
The node is currently working as intended, but there are some improvements that could be made:
|
||||
|
||||
- The node could be made more energy efficient by putting the ESP32 in deep sleep mode when it is not sending data.
|
||||
- The node could be made more robust by adding error handling to the code.
|
||||
- The node could be made more secure by adding encryption to the data that is sent to the server.
|
97
docs/rpi-documentation/Databaseconnection.md
Normal file
@@ -0,0 +1,97 @@
|
||||
## 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.
|
||||
So we set out to make this connection using python.
|
||||
|
||||
At first we needed to import the folowing librarys:
|
||||
```js
|
||||
// everything is running async so the code can run together and not interfere with eachother.
|
||||
import asyncio
|
||||
import websockets
|
||||
// a library to connect to the database.
|
||||
import mysql.connector
|
||||
import json
|
||||
```
|
||||
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.
|
||||
```js
|
||||
//the data that has to be pushed needs to be
|
||||
async def process_data(data):
|
||||
try:
|
||||
mydb = mysql.connector.connect(
|
||||
host="localhost",
|
||||
user="*****",
|
||||
password="*********",
|
||||
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.
|
||||
```js
|
||||
//Making a variable for the database connection
|
||||
cursor = mydb.cursor()
|
||||
//Making a variable for the database connection
|
||||
MACDataReading = mydb.cursor()
|
||||
//find the correct section and interact with it.
|
||||
MACDataReading.execute("SELECT MAC FROM Node")
|
||||
//the query for what needs to be inserted into the database ( atleast where it has to go).
|
||||
query = "INSERT INTO `Measurement` (NodeID, Type, Value) VALUES (%s, %s, %s)"
|
||||
//the recieved data from the websocket is json data so needs to be changed.
|
||||
processedData = json.loads(data)
|
||||
//variables about the recieved data points.
|
||||
processedTemp = (processedData['Temp'])
|
||||
processedHumi = (processedData['Humi'])
|
||||
processedECo = (processedData['eCO2'])
|
||||
processedTvoc = (processedData['TVOC'])
|
||||
processedMAC = (processedData['node'])
|
||||
//change the recieved macadress to a tuple.
|
||||
MACTuple = (processedMAC,)
|
||||
//fetch the data from the database an[d put it in an array.
|
||||
MACDataFetching = MACDataReading.fetchall()
|
||||
MACArray = list(MACDataFetching)
|
||||
|
||||
//see if the fetched data is not in the gotten array.
|
||||
//otehrwise insert it into the database directly.
|
||||
if MACTuple not in MACArray:
|
||||
addingNode = "INSERT INTO `Node` (MAC) VALUES (%s)"
|
||||
cursor.execute(addingNode, MACTuple)
|
||||
mydb.commit()
|
||||
//the websocket data that needs to be sent to the database.
|
||||
pushingDataArray = [(1, "Temp", processedTemp), (1, "Humi", processedHumi), (1, "eCO2", processedECo), (1, "TVOC", processedTvoc)]
|
||||
// forloop, go allong the array.
|
||||
for i in pushingDataArray:
|
||||
print(query ,i)
|
||||
cursor.execute(query, i)
|
||||
mydb.commit()
|
||||
// show me the error it there is one.
|
||||
except mysql.connector.Error as err:
|
||||
print("MySQL Error:", err)
|
||||
finally:
|
||||
cursor.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.
|
||||
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
|
||||
```js
|
||||
//here the connection to the websocked is made
|
||||
async def receive_data():
|
||||
uri = "****************"
|
||||
|
||||
try:
|
||||
async with websockets.connect(uri) as websocket:
|
||||
while True:
|
||||
// the data collected from the websocket is.
|
||||
data = await websocket.recv()
|
||||
// data recieved: conformation.
|
||||
print(f"Received data: {data}")
|
||||
await process_data(data)
|
||||
// error sowing.
|
||||
except websockets.ConnectionClosedError as e:
|
||||
print("WebSocket connection closed:", e)
|
||||
|
||||
async def main():
|
||||
await receive_data()
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
16
docs/rpi-documentation/Flask.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Flask
|
||||
|
||||
|
||||
## Introduction
|
||||
Flask is a micro web framework written in Python. It is easy to use and has a lot of documentation. We are going to use Flask to serve our REST api.
|
||||
|
||||
## Cheatsheet
|
||||
```python
|
||||
print(f"Hello world have a nice {args}!")
|
||||
```
|
||||
This way you can put variables in a string. This is called f-strings.
|
||||
|
||||
|
||||
|
||||
## Rules for python
|
||||
* You can only use one return statement in a function.
|
71
docs/rpi-documentation/Put-Request.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# To edit data in the database we wanna use PUT request.
|
||||
|
||||
### What is a put request?
|
||||
To edit data in the database we wanna use PUT request. A PUT request is used to update an existing resource. If the resource does not exist, it will create a new one. The PUT request requires the client to send the entire updated resource, not just the changes. This means that the client must send all the data, even if only one field has changed.
|
||||
|
||||
A put request is a json its for example looks like this:
|
||||
```json
|
||||
{
|
||||
"NodeID": 1,
|
||||
"Location": "testlocation",
|
||||
"Name": "testname",
|
||||
}
|
||||
```
|
||||
|
||||
### How to use a put request
|
||||
We have been trying to use a PUT request with flask. But that didn't work because the reverse proxy would reform the request to a GET request. So we tried to let apache 2 run it with wsgi.
|
||||
|
||||
### What is wsgi?
|
||||
WSGI stands for Web Server Gateway Interface. It is a specification that describes how a web server communicates with web applications, and how web applications can be chained together to process one request. WSGI is a Python standard, and it is implemented by most Python web frameworks.
|
||||
|
||||
We couldnt get it to work even though we worked trough all the errors apache 2 gave us.
|
||||
|
||||
We first had some paths misconfigured. Then we had Forbidden error because we didn't give permission for apache2 to use that path.
|
||||
|
||||
And now we ended on not found and we verified that all configuration is in the correct syntrax with ```apachectl configtest```
|
||||
|
||||
|
||||
### How to use wsgi
|
||||
To use wsgi you need to install mod_wsgi with the following command:
|
||||
```bash
|
||||
sudo apt-get install libapache2-mod-wsgi
|
||||
```
|
||||
|
||||
Then you need to enable the module with the following command:
|
||||
```bash
|
||||
sudo a2enmod wsgi
|
||||
```
|
||||
|
||||
Then you need to create a wsgi file in the same directory as your python file. The wsgi file should look like this:
|
||||
```python
|
||||
import sys
|
||||
sys.path.insert(0, '/home/pi/webapp')
|
||||
from mainflask import app as application
|
||||
```
|
||||
|
||||
Then you need to configure your apache2 configuration file to use the wsgi file. The configuration file should look like this:
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
DocumentRoot /home/pi/www/html/
|
||||
|
||||
WSGIDaemonProcess webapp python-path=/home/pi/webapp
|
||||
WSGIScriptAlias /flask /home/pi/webapp/main.wsgi
|
||||
|
||||
<Directory /home/pi/webapp>
|
||||
WSGIProcessGroup webapp
|
||||
WSGIApplicationGroup %{GLOBAL}
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
# Logging
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
Then you need to restart apache2 with the following command:
|
||||
```bash
|
||||
sudo systemctl restart apache2
|
||||
```
|
||||
|
||||
|
46
docs/rpi-documentation/Reverse-Proxy.md
Normal file
@@ -0,0 +1,46 @@
|
||||
### Reverse proxy
|
||||
|
||||
|
||||
#### Introduction
|
||||
A reverse proxy is for example used to host 2 services on the same port. This is useful for us because we can use the same port for the websockets and the webserver.
|
||||
|
||||
#### How to use a reverse proxy
|
||||
We are going to use `Apache2` as our reverse proxy. This is because it is easy to use and has a lot of documentation. It also hosts our webserver so it is easy to configure and we don't have to install another program.
|
||||
|
||||
#### Additional modules
|
||||
We need to enable the `proxy` and `proxy_wstunnel` modules. These are used to proxy the websockets.
|
||||
|
||||
You can install them with these commands on a Raspberry Pi. For other linux distributions its may be different.
|
||||
|
||||
```bash
|
||||
$ sudo a2enmod proxy
|
||||
$ sudo a2enmod proxy_wstunnel
|
||||
```
|
||||
|
||||
#### Script
|
||||
The enabled apache2 config scripts are found in `/etc/apache2/conf-enabled/`. You can create a new file with the name of your service. For example `studyarea.conf`.
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
DocumentRoot /home/pi/www/html/
|
||||
|
||||
# Enable proxying WebSockets
|
||||
ProxyPass /ws ws://localhost:8001/
|
||||
|
||||
# Enable proxying HTTP
|
||||
ProxyPass /ws http://localhost:8080/
|
||||
</VirtualHost>
|
||||
```
|
||||
Because VirtualHost is listening on port 80, we can use the same port for the webserver and the websockets. The `ProxyPass` is used to proxy the websocket. The `/ws` is the path where the websockets are hosted. Its hosted on `localhost/ws` so it kinda creates a new subdomain for the websocket. The `localhost:8001` is the address of the websockets and the `localhost:8080` is the address of the webserver.
|
||||
|
||||
|
||||
`DocumentRoot` is the path where the files are for the website.
|
||||
|
||||
|
||||
|
||||
### Sources:
|
||||
|
||||
* https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
|
||||
* https://www.serverlab.ca/tutorials/linux/web-servers-linux/how-to-reverse-proxy-websockets-with-apache-2-4/
|
||||
* https://gist.github.com/mortenege/91ec6fe02dca6f736303a00f8cea2731
|
||||
* https://www.jscape.com/blog/forward-proxy-vs-reverse-proxy#:~:text=While%20a%20forward%20proxy%20proxies,is%20providing%20file%20transfer%20services.
|
||||
* https://webmasters.stackexchange.com/questions/143106/functional-difference-between-proxypass-and-proxypassreverse-in-apache
|
@@ -7,8 +7,83 @@ With websockets you can establish a connection between the client and the server
|
||||
### How to use websockets
|
||||
There are different languages to serve websockets with, but we are going to use Python with the library `websockets`. This library is easy to use and has a lot of documentation.
|
||||
|
||||
## Issues
|
||||
|
||||
Not all ports are open so we need to make a reverse proxy to use websockets.
|
||||
|
||||
### Ports
|
||||
We can only use certain ports like 113, 80, 443. These are common ports and are not blocked by firewalls. Something in the network is blocking all other ports which makes it hard to use websockets.
|
||||
|
||||
A solution for this is to use a reverse proxy. (See [Reverse Proxy](./Reverse-Proxy.md))
|
||||
|
||||
## Classes
|
||||
|
||||
For the websockets we are going to use 2 classes. One for the nodes and one for the websites. The nodes are going to send data to the website and have multiple variables like temperature, humidity, eCO2, TVOC and sound. The website is going to send data to the nodes like names and settings. For this they have to use a userName and password.
|
||||
|
||||
``` mermaid
|
||||
classDiagram
|
||||
|
||||
client --> User : website client
|
||||
client --> Node : esp32 client
|
||||
|
||||
namespace raspberry pi clients {
|
||||
class client {
|
||||
+MacAdress
|
||||
sendData()
|
||||
}
|
||||
|
||||
class Node {
|
||||
+eCO2
|
||||
+Temperature
|
||||
+Humidity
|
||||
+TVOC
|
||||
+Sound
|
||||
+Settings
|
||||
changeNodeName(name)
|
||||
updateData(data)
|
||||
}
|
||||
|
||||
class User {
|
||||
+userName
|
||||
+password
|
||||
changeNodeName(data)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Code of the classes:
|
||||
|
||||
``` python
|
||||
class client:
|
||||
def __init__(self, macAdress):
|
||||
self.macAdress = macAdress
|
||||
|
||||
class node(client):
|
||||
def __init__(self, name, node, temperature, humidity, eCO2, TVOC, sound):
|
||||
super().__init__(macAdress)
|
||||
self.nodeNumber = node
|
||||
self.temperature = temperature
|
||||
self.humidity = humidity
|
||||
self.eCO2 = eCO2
|
||||
self.TVOC = TVOC
|
||||
self.sound = sound
|
||||
self.name = name
|
||||
|
||||
def updateData(self, temperature, humidity, eCO2, TVOC, sound):
|
||||
self.temperature = temperature
|
||||
self.humidity = humidity
|
||||
self.eCO2 = eCO2
|
||||
self.TVOC = TVOC
|
||||
self.sound = sound
|
||||
|
||||
class Website(client):
|
||||
def __init__(self, macAdress, user, password):
|
||||
super().__init__(macAdress)
|
||||
self.passWord = passWord
|
||||
self.userName = userName
|
||||
```
|
||||
|
||||
#### Sources:
|
||||
* https://websockets.readthedocs.io/en/stable/index.html
|
||||
* https://websockets.readthedocs.io/en/stable/index.html
|
||||
|
||||
**Written by Sam & Sietse**
|
@@ -2,7 +2,7 @@ mkdocs ~= 1.4.0
|
||||
Jinja2==3.1.0
|
||||
mkdocs-material ~= 9.1.12
|
||||
mkdocs-video ~= 1.3
|
||||
mkdocs-mermaid2-plugin ~= 0.6.0
|
||||
mkdocs-mermaid2-plugin ~= 1.0.4
|
||||
mkdocs-macros-plugin ~= 0.7.0
|
||||
mkdocs-awesome-pages-plugin ~= 2.8.0
|
||||
mkdocs-autolinks-plugin ~= 0.6.0
|
||||
|
117
server/Database/DatabasewithMAC.sql
Normal file
@@ -0,0 +1,117 @@
|
||||
-- MySQL Script generated by MySQL Workbench
|
||||
-- Wed Mar 13 16:04:58 2024
|
||||
-- Model: New Model Version: 1.0
|
||||
-- MySQL Workbench Forward Engineering
|
||||
-- CHANGE NODEID TO AUTO INCREMENET IN NODE TABLE
|
||||
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
|
||||
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
|
||||
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Schema NodeData
|
||||
-- -----------------------------------------------------
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Schema NodeData
|
||||
-- -----------------------------------------------------
|
||||
CREATE SCHEMA IF NOT EXISTS `NodeData` DEFAULT CHARACTER SET utf8 ;
|
||||
USE `NodeData` ;
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `NodeData`.`Question`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `NodeData`.`Question` (
|
||||
`QuestionID` INT NOT NULL,
|
||||
`Question` VARCHAR(45) NULL,
|
||||
PRIMARY KEY (`QuestionID`))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `NodeData`.`Node`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `NodeData`.`Node` (
|
||||
`NodeID` INT NOT NULL,
|
||||
`Name` VARCHAR(45) NULL,
|
||||
`Location` VARCHAR(45) NULL,
|
||||
`MAC` VARCHAR(45) NULL,
|
||||
PRIMARY KEY (`NodeID`))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `NodeData`.`Measurement`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `NodeData`.`Measurement` (
|
||||
`NodeID` INT NOT NULL,
|
||||
`TimeStamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`Type` VARCHAR(45) NULL,
|
||||
`Value` FLOAT NULL,
|
||||
CONSTRAINT `fk_Measurement_Node1`
|
||||
FOREIGN KEY (`NodeID`)
|
||||
REFERENCES `NodeData`.`Node` (`NodeID`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `NodeData`.`Enquete`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `NodeData`.`Enquete` (
|
||||
`EnqueteID` INT NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (`EnqueteID`))
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `NodeData`.`enqueteQuestion`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `NodeData`.`enqueteQuestion` (
|
||||
`Enquete_EnqueteID` INT NOT NULL,
|
||||
`Question_QuestionID` INT NOT NULL,
|
||||
PRIMARY KEY (`Enquete_EnqueteID`, `Question_QuestionID`),
|
||||
INDEX `fk_Enquete_has_Question_Question1_idx` (`Question_QuestionID` ASC) VISIBLE,
|
||||
INDEX `fk_Enquete_has_Question_Enquete1_idx` (`Enquete_EnqueteID` ASC) VISIBLE,
|
||||
CONSTRAINT `fk_Enquete_has_Question_Enquete1`
|
||||
FOREIGN KEY (`Enquete_EnqueteID`)
|
||||
REFERENCES `NodeData`.`Enquete` (`EnqueteID`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_Enquete_has_Question_Question1`
|
||||
FOREIGN KEY (`Question_QuestionID`)
|
||||
REFERENCES `NodeData`.`Question` (`QuestionID`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `NodeData`.`Reply`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `NodeData`.`Reply` (
|
||||
`Node_NodeID` INT NOT NULL,
|
||||
`enqueteQuestion_Enquete_EnqueteID` INT NOT NULL,
|
||||
`enqueteQuestion_Question_QuestionID` INT NOT NULL,
|
||||
`Result` INT NULL,
|
||||
`Time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`AnswerID` INT NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (`AnswerID`, `Node_NodeID`, `enqueteQuestion_Enquete_EnqueteID`, `enqueteQuestion_Question_QuestionID`),
|
||||
INDEX `fk_Node_has_enqueteQuestion_enqueteQuestion1_idx` (`enqueteQuestion_Enquete_EnqueteID` ASC, `enqueteQuestion_Question_QuestionID` ASC) VISIBLE,
|
||||
INDEX `fk_Node_has_enqueteQuestion_Node1_idx` (`Node_NodeID` ASC) VISIBLE,
|
||||
CONSTRAINT `fk_Node_has_enqueteQuestion_Node1`
|
||||
FOREIGN KEY (`Node_NodeID`)
|
||||
REFERENCES `NodeData`.`Node` (`NodeID`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION,
|
||||
CONSTRAINT `fk_Node_has_enqueteQuestion_enqueteQuestion1`
|
||||
FOREIGN KEY (`enqueteQuestion_Enquete_EnqueteID` , `enqueteQuestion_Question_QuestionID`)
|
||||
REFERENCES `NodeData`.`enqueteQuestion` (`Enquete_EnqueteID` , `Question_QuestionID`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
SET SQL_MODE=@OLD_SQL_MODE;
|
||||
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
|
||||
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
|
54
server/Flask/main.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from flask import Flask, request, jsonify
|
||||
import mysql.connector
|
||||
from queries import *
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
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)
|
||||
|
||||
@app.route('/updateData')
|
||||
def updateDataIndex():
|
||||
node_id = request.args.get('node', None)
|
||||
new_name = request.args.get('name', None)
|
||||
new_location = request.args.get('location', None)
|
||||
return updateData(node_id, new_name, new_location)
|
||||
|
||||
|
||||
def updateData(node, name, location):
|
||||
mydb = loginDB()
|
||||
query = update_query(node, name, location)
|
||||
cursor = mydb.cursor(dictionary=True) # Enable dictionary output
|
||||
cursor.execute(query)
|
||||
mydb.commit()
|
||||
result = cursor.fetchall() # Fetch the results
|
||||
cursor.close()
|
||||
mydb.close()
|
||||
|
||||
return result
|
||||
|
||||
def loginDB():
|
||||
mydb = mysql.connector.connect(
|
||||
host="localhost",
|
||||
user="root",
|
||||
password="Dingleberries69!",
|
||||
database="NodeData"
|
||||
)
|
||||
return mydb
|
||||
|
||||
def getData(node, dataType, MAC):
|
||||
mydb = loginDB()
|
||||
query = get_query(node, dataType, MAC)
|
||||
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')
|
23
server/Flask/queries.py
Normal file
@@ -0,0 +1,23 @@
|
||||
def get_query(node, dataType, MAC):
|
||||
if node and dataType:
|
||||
query = f"SELECT * FROM Measurement WHERE NodeID = {node} AND Type = '{dataType}'"
|
||||
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}'"
|
||||
else:
|
||||
query = "SELECT * FROM `Measurement`"
|
||||
return query
|
||||
|
||||
|
||||
|
||||
def update_query(node, name, location):
|
||||
if node and name and location:
|
||||
query = f"""
|
||||
UPDATE Node
|
||||
SET Name = '{name}', Location = '{location}'
|
||||
WHERE NodeID = {node};
|
||||
"""
|
||||
return query
|
@@ -1,21 +1,34 @@
|
||||
import websockets;
|
||||
import asyncio;
|
||||
import websockets
|
||||
import asyncio
|
||||
|
||||
connected = set()
|
||||
|
||||
async def handler(websocket):
|
||||
while True:
|
||||
#Save the incoming message in variable message.
|
||||
message = await websocket.recv()
|
||||
print(message)
|
||||
# Register.
|
||||
connected.add(websocket)
|
||||
try:
|
||||
while True:
|
||||
message = await websocket.recv()
|
||||
print(message)
|
||||
await broadcast(message)
|
||||
except websockets.ConnectionClosedOK:
|
||||
print("Client disconnected")
|
||||
finally:
|
||||
connected.remove(websocket)
|
||||
|
||||
async def broadcast(message):
|
||||
if connected: # asyncio.wait doesn't accept an empty list
|
||||
await asyncio.wait([ws.send(message) for ws in connected])
|
||||
|
||||
async def sendClientId():
|
||||
if connected: # asyncio.wait doesn't accept an empty list
|
||||
await asyncio.wait([ws.send(message) for ws in connected])
|
||||
|
||||
async def main():
|
||||
async with websockets.serve(handler, "145.92.8.114" , 8001):
|
||||
async with websockets.serve(handler, "0.0.0.0", 8001):
|
||||
await asyncio.Future() # run forever
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
#https://websockets.readthedocs.io/en/stable/reference/sync/server.html#websockets.sync.server.serve
|
33
server/bullshitSender.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import asyncio
|
||||
import websockets
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
|
||||
async def send_data(uri):
|
||||
async with websockets.connect(uri) as websocket:
|
||||
print("Connected to WebSocket server")
|
||||
|
||||
while True:
|
||||
data = {
|
||||
"node": "69:42:08:F5:00:00",
|
||||
"Temp": str(round(random.uniform(0, 25), 2)),
|
||||
"Humi": str(round(random.uniform(0, 90), 2)),
|
||||
"eCO2": str(round(random.uniform(350, 2000), 0)),
|
||||
"TVOC": str(round(random.uniform(100, 1200), 0))
|
||||
}
|
||||
await websocket.send(json.dumps(data))
|
||||
print("Data sent")
|
||||
|
||||
response = await websocket.recv()
|
||||
print("Received message:", response)
|
||||
|
||||
await asyncio.sleep(2) # Wait a bit before sending the next message
|
||||
|
||||
# Start the WebSocket connection
|
||||
while True:
|
||||
try:
|
||||
asyncio.get_event_loop().run_until_complete(send_data("ws://145.92.8.114/ws"))
|
||||
except Exception as e:
|
||||
print("Exception:", e)
|
||||
time.sleep(1) # Wait a bit before trying to reconnect
|
20
server/reverseproxy
Normal file
@@ -0,0 +1,20 @@
|
||||
<VirtualHost *:80>
|
||||
ProxyPreserveHost On
|
||||
DocumentRoot /home/pi/www/html/
|
||||
|
||||
|
||||
# Enable proxying WebSockets
|
||||
ProxyPass /ws ws://localhost:8001/
|
||||
ProxyPassReverse /ws ws://localhost:8001/
|
||||
# Enable proxying HTTP
|
||||
ProxyPass /http http://localhost:8080/
|
||||
|
||||
ProxyPass /putData http://localhost:5000/putData
|
||||
ProxyPassReverse /putData http://localhost:5000/putData
|
||||
ProxyPass /flask http://localhost:5000/
|
||||
ProxyPassReverse /flask http://localhost:5000/
|
||||
ProxyPreserveHost On
|
||||
# Logging
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
83
teamdocumentatie/dailyStandupNotes.md
Normal file
@@ -0,0 +1,83 @@
|
||||
### Sprint 2: The product.
|
||||
28 - 2 - 2024: {
|
||||
At the standup we made soem finishing touches on who is gonna do what and what each role will feature.
|
||||
Dano and Bram are going to start with doing research about extracting information from the PI and wil be setting up a HTML page for the information to display.
|
||||
Sam and Sietse are going to be busy with connecting to the websocket and will do some minor tweaking to the current node moddel that we have.
|
||||
}
|
||||
|
||||
29 - 2 - 2024: {
|
||||
At today's standup we got together to talk about our progression in each of our tasks. Where we found that our tasks for today will be moreso the same but wit hsome minor ajusting.
|
||||
Sam and Sietse will be designing the pcb aswell as making some progress on the questionaire, while Dano and Bram will be making the connection between the websocket and the devpage/ html page aswell as making some user-stories.
|
||||
}
|
||||
|
||||
1 - 3 - 2024: {
|
||||
From yesterday's standup, Sietse made a digital PCD schematic, Dano and Bram made great progress with connecting the PI to the html website and are trying to finish it today. Sam has made progress with documentation and was helping Sietse.
|
||||
Today we are going to continue our tasks and try to finish these, so we can continue with our project.
|
||||
}
|
||||
|
||||
5 - 3 - 2024: {
|
||||
At the standup we discussed our goals for today, Sam is going to get the screen going, Sietse is gonna help Sam, Dano is working on kthe website, and Bram is going to send live info to the database in linux.
|
||||
}
|
||||
|
||||
6 - 3 - 2024 {
|
||||
Sietse is going to make a class wich contains all the graphs, Dano is making documentation and OOP, Sam is also making OOP,
|
||||
and im going to continue with working on the connection between the websocket and the database. }
|
||||
|
||||
7 - 3 - 2024 {
|
||||
Sietse is going to make more progress on updating the graph on the website.
|
||||
Sam is going to finish the code for the big questionaire screen.
|
||||
Dano is going to be writing some documentation and will be working with classes in arduino.
|
||||
Bram will do research on python to finish the connection to the database and the websocket.
|
||||
}
|
||||
|
||||
12 - 3 - 2024 {
|
||||
Sam is going to continue to build his custom library for out custom questionaire screen.
|
||||
Sietse is going to make a class in python.
|
||||
Dano is making classes for the code of the node.
|
||||
Bram is correcting some of his documentation and will continue the coding in python( to still make the connection between the database and the websocket.)
|
||||
}
|
||||
|
||||
13 - 3 - 2024 {
|
||||
Dano: website scaling + css fixing and arduino OOP.
|
||||
Sam: making a rest api to pull stuff from database.
|
||||
Sietse: OOP python.
|
||||
Bram: Finaly finishing connection between database and websocket.
|
||||
}
|
||||
|
||||
14 - 3- 2024 {
|
||||
Sam: rest api building.
|
||||
Dano: OOP arduino.
|
||||
sietse: making the website dynamic.
|
||||
Bram: Documentation updating for review.
|
||||
}
|
||||
|
||||
15 - 3- 2024 {
|
||||
We are all making preperations for the sprint-review.
|
||||
}
|
||||
|
||||
3/19/2024
|
||||
|
||||
Dano, oop arduino
|
||||
Bram, nodes registereen als er een nieuwe binnenkomt
|
||||
Sietse, onderdelen bestellen, verder user story 46: nodes beheren op website.
|
||||
Sam, tft scherm, rest api ombouwen om data in node tabel te douwen
|
||||
|
||||
20/03/2024:
|
||||
Dano: Adding comments for OOP Arduino
|
||||
Bram: Python send node data to database
|
||||
Sam: flask rerouten to use put recuest
|
||||
Sietse: WireFrame
|
||||
|
||||
21/03/2024
|
||||
|
||||
Sietse: fritsing node documentation
|
||||
Bram: personal documetation.
|
||||
Sam: Rest api improvements
|
||||
Dano: prepering for retro and making docu for aruidno
|
||||
|
||||
22/03/2024
|
||||
|
||||
Bram: Fritsing, documentation for code
|
||||
Sietse: Database
|
||||
Dano: Docuemtation for arduino
|
||||
Sam:
|
29
teamdocumentatie/samenwerkingscontract.md
Normal file
@@ -0,0 +1,29 @@
|
||||
Doelstelling Goed functioneerend product leveren
|
||||
Werktijden Wanneer afgesproken en op hen zelf
|
||||
Vergadertijden Tijdens schooltijd en wanneer afgesproken
|
||||
Afspraken communicatie Discord
|
||||
Afspraken aanwezigheid Vermeld wanneer deze afspraak niet nagekomen kan worden of elders kom altijd optijd.
|
||||
Afspraken documenten delen Gitlab, discord
|
||||
Geen spaties in gitlab documenten/mappen
|
||||
Procedure bij niet-nakomen afspraken Communicatie is sleutel
|
||||
|
||||
Definition of done:
|
||||
- Code is geschreven
|
||||
- Code is getest
|
||||
- Code is gereviewd
|
||||
- Code is gedocumenteerd
|
||||
- Code Werkt
|
||||
- Code is geïntegreerd
|
||||
|
||||
|
||||
Taakverdeling
|
||||
Veranderd elke sprint
|
||||
|
||||
|
||||
Akkoord gemaakte afspraken
|
||||
Namen, datum, handtekening
|
||||
1. Bram datum handtekening
|
||||
2. Dano
|
||||
3. Sam Hos
|
||||
5. Sietse jonko
|
||||
|
85
web/classes.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);
|
||||
}
|
||||
}
|
12
web/graphClass.js
Normal file
@@ -0,0 +1,12 @@
|
||||
class graphClass {
|
||||
constructor(dataArray) {
|
||||
this.dataArray = dataArray;
|
||||
}
|
||||
|
||||
createGraph() {
|
||||
// make the graphs
|
||||
liveGraphs.forEach((graph) => {
|
||||
graph.makeGraph();
|
||||
});
|
||||
}
|
||||
}
|
0
web/graphPage.js
Normal file
33
web/index.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script src="https://cdn.plot.ly/plotly-latest.min.js" charset="utf-8"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<title>Node dev page</title>
|
||||
<!-- <link rel="icon" type="image/x-icon" href="favicon.ico"> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Make a new header with a navigation bar -->
|
||||
<header>
|
||||
<nav class="navbar">
|
||||
<ul>
|
||||
<li><a href="climate-monitor.html">Dashboard
|
||||
</a></li>
|
||||
<li><a href="Map.html">Graph</a></li>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div id="nodeDataLocation"></div>
|
||||
|
||||
<!-- Include the js file -->
|
||||
<script src="classes.js"></script>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
|
||||
<html>
|
194
web/main.js
Normal file
@@ -0,0 +1,194 @@
|
||||
// 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();
|
||||
|
||||
function handleIncomingData(data) {
|
||||
nodeAdressHandler(data.Node);
|
||||
|
||||
nodeNumber = nodeDict[data.Node];
|
||||
temperature = data.Temp;
|
||||
humidity = data.Humi;
|
||||
CO2 = data.eCO2;
|
||||
TVOC = data.TVOC;
|
||||
|
||||
updateNodeData(nodeNumber, temperature, humidity, CO2, TVOC);
|
||||
}
|
||||
|
||||
function nodeAdressHandler(node) {
|
||||
if (!nodeArray.includes(node)) {
|
||||
nodeArray.push(node);
|
||||
nodeDict[node] = nodeArray.length;
|
||||
}
|
||||
}
|
||||
|
||||
//function for making the html elements for the following html code
|
||||
function nodeData(data, node) {
|
||||
let nodeData = document.createElement("div");
|
||||
nodeData.innerHTML = data;
|
||||
// nodeData.setAttribute("id", "node" + node);
|
||||
document.body.appendChild(nodeData);
|
||||
// console.log("Hello World");
|
||||
}
|
||||
|
||||
function createNodeData(node) {
|
||||
// Create main div
|
||||
let nodeData = document.createElement("div");
|
||||
nodeData.className = "nodeData";
|
||||
|
||||
// Create flex-NodeData div
|
||||
let flexNodeData = document.createElement("div");
|
||||
flexNodeData.className = "flex-NodeData";
|
||||
|
||||
// Create p element
|
||||
let pNode = document.createElement("p");
|
||||
pNode.textContent = "Node " + node;
|
||||
|
||||
// Append p to flex-NodeData
|
||||
flexNodeData.appendChild(pNode);
|
||||
|
||||
// Create flex-LiveData div
|
||||
let flexLiveData = document.createElement("div");
|
||||
flexLiveData.className = "flex-LiveData";
|
||||
|
||||
// Create data divs (Temperature, Humidity, Light Intensity)
|
||||
let dataTypes = ["Temperatuur", "Luchtvochtigheid", "CO2", "TVOC"];
|
||||
let ids = ["temperature", "humidity", "CO2", "TVOC"];
|
||||
let statusIds = ["tempStatus", "humidStatus", "CO2Status", "TVOCStatus"];
|
||||
|
||||
for (let i = 0; i < dataTypes.length; i++) {
|
||||
let dataDiv = document.createElement("div");
|
||||
|
||||
let dataTypeDiv = document.createElement("div");
|
||||
dataTypeDiv.textContent = dataTypes[i] + ": ";
|
||||
|
||||
let pElement = document.createElement("p");
|
||||
pElement.id = ids[i] + node;
|
||||
pElement.textContent = "Not connected";
|
||||
|
||||
dataTypeDiv.appendChild(pElement);
|
||||
dataDiv.appendChild(dataTypeDiv);
|
||||
|
||||
let statusElement = document.createElement("div");
|
||||
statusElement.className = "statusElement";
|
||||
|
||||
let statusText = document.createElement("p");
|
||||
statusText.className = "statusText";
|
||||
statusText.id = statusIds[i];
|
||||
statusText.textContent = "Not connected";
|
||||
|
||||
statusElement.appendChild(statusText);
|
||||
dataDiv.appendChild(statusElement);
|
||||
|
||||
flexLiveData.appendChild(dataDiv);
|
||||
}
|
||||
|
||||
// Append flex-LiveData to flex-NodeData
|
||||
flexNodeData.appendChild(flexLiveData);
|
||||
|
||||
// Create flex-graph div
|
||||
let flexGraph = document.createElement("div");
|
||||
flexGraph.className = "flex-graph";
|
||||
|
||||
let graphDiv = document.createElement("div");
|
||||
|
||||
let graphP = document.createElement("p");
|
||||
graphP.textContent = "Live graph:";
|
||||
|
||||
let liveGraph = document.createElement("div");
|
||||
liveGraph.id = "liveGraph" + node;
|
||||
|
||||
graphDiv.appendChild(graphP);
|
||||
graphDiv.appendChild(liveGraph);
|
||||
flexGraph.appendChild(graphDiv);
|
||||
|
||||
// Append flex-graph to flex-NodeData
|
||||
flexNodeData.appendChild(flexGraph);
|
||||
|
||||
// Append flex-NodeData to main div
|
||||
nodeData.appendChild(flexNodeData);
|
||||
|
||||
// Check if nodeDataLocation element exists
|
||||
let nodeDataLocation = document.getElementById("nodeDataLocation");
|
||||
if (nodeDataLocation) {
|
||||
// Append main div to nodeDataLocation
|
||||
nodeDataLocation.appendChild(nodeData);
|
||||
} else {
|
||||
console.error("Element with ID 'nodeDataLocation' does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
function updateNodeData(node, temperature, humidity, eCO2, TVOC) {
|
||||
// Update the temperature, humidity and light intensity values
|
||||
document.getElementById("temperature" + node).textContent = temperature;
|
||||
document.getElementById("humidity" + node).textContent = humidity;
|
||||
document.getElementById("CO2" + node).textContent = eCO2;
|
||||
document.getElementById("TVOC" + node).textContent = TVOC;
|
||||
|
||||
// Update the status text
|
||||
document.getElementById("tempStatus").textContent = "Connected";
|
||||
document.getElementById("humidStatus").textContent = "Connected";
|
||||
document.getElementById("CO2Status").textContent = "Connected";
|
||||
document.getElementById("TVOCStatus").textContent = "Connected";
|
||||
|
||||
// Update the graph
|
||||
liveGraphs[0].updateData(temperature, humidity, eCO2, TVOC);
|
||||
liveGraphs[0].updateGraph();
|
||||
|
||||
console.log(nodeDict[node]);
|
||||
console.log(nodeArray);
|
||||
}
|
||||
|
||||
// Call the function to create the HTML structure
|
||||
for (let i = 1; i <= amountOfNodes; i++) {
|
||||
createNodeData(i);
|
||||
liveGraphs.push(new liveGraph(i));
|
||||
}
|
||||
|
||||
// make the graphs
|
||||
liveGraphs.forEach((graph) => {
|
||||
graph.makeGraph();
|
||||
});
|
166
web/styles.css
Normal file
@@ -0,0 +1,166 @@
|
||||
body {
|
||||
background-color: white;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
font-family: "inter", sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
opacity: 0.8;
|
||||
color: green;
|
||||
margin: auto;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
color: black;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
p1 {
|
||||
color: solid white;
|
||||
}
|
||||
|
||||
.liveGraph{
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
|
||||
.divCnt{
|
||||
text-align: begin;
|
||||
}
|
||||
|
||||
.statusElement{
|
||||
display:inline-block;
|
||||
border: solid #1f82d3 2px;
|
||||
border-radius: 10px;
|
||||
width: 90%;
|
||||
|
||||
}
|
||||
|
||||
.statusText{
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.flex-NodeData {
|
||||
display: flex;
|
||||
margin-left: 1%;
|
||||
margin-right: 1%;
|
||||
justify-content: space-evenly;
|
||||
/* height: 40%; */
|
||||
flex-direction: column;
|
||||
border: 3px solid #1f82d3;
|
||||
border-radius: 20px;
|
||||
/* width: 90%; */
|
||||
/* border: 2px solid red; */
|
||||
/* margin-right: 90%; */
|
||||
/* padding-right: 40%; */
|
||||
/* padding-left: 40%; */
|
||||
}
|
||||
|
||||
#nodeDataLocation{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
border: 4px solid rgb(0, 0, 255);
|
||||
/* padding-bottom: 50%; */
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.nodeData {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
flex-direction: row;
|
||||
margin-bottom: 2%;
|
||||
margin-top: 1%;
|
||||
/* border: 2px solid red; */
|
||||
}
|
||||
|
||||
.flex-LiveData {
|
||||
display: flex;
|
||||
align-content: begin;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
gap: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
.flex-LiveData > div {
|
||||
|
||||
border: solid #1f82d3 2px;
|
||||
padding: 15px 0;
|
||||
font-size: 30px;
|
||||
border-radius: 10px;
|
||||
height: fit-content;
|
||||
width: 30%;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.flex-graph {
|
||||
display: flex;
|
||||
align-content: begin;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
gap: 5px;
|
||||
padding: 10px;
|
||||
|
||||
}
|
||||
|
||||
.flex-graph > div {
|
||||
position: relative;
|
||||
border: solid #1f82d3 2px;
|
||||
padding: 15px 0;
|
||||
font-size: 30px;
|
||||
border-radius: 10px;
|
||||
flex-basis: 95%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 850px) {
|
||||
.flex-LiveData>div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
text-align: center;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
a{
|
||||
padding: 10px;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
li{
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
margin: 0 5px;
|
||||
border-radius: 5px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
.navbar a:hover {
|
||||
background-color: #196bad;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|