Merge branch 'main' of https://git.miaig.dev/mia/abschlussarbeit
2
.gitignore
vendored
|
@ -1 +1 @@
|
|||
/webserver/plots/
|
||||
/webserver/static/plots/
|
||||
|
|
22
README.md
|
@ -81,17 +81,33 @@ Data should be sent and received via MQTT to an ESP32. The Pi acts as a sensor g
|
|||
- `main.py`
|
||||
The main file for the project
|
||||
|
||||
- `The Graphs`
|
||||
https://ron.sh/creating-real-time-charts-with-fastapi/
|
||||
|
||||
### Git Help
|
||||
|
||||

|
||||

|
||||
|
||||
### MarkDown Help
|
||||
|
||||

|
||||

|
||||
|
||||
### Html Help
|
||||
|
||||

|
||||

|
||||
|
||||
### Css Help
|
||||
|
||||

|
||||
|
||||
### JavaScript Help
|
||||
|
||||

|
||||
|
||||
### Jinja Help
|
||||
|
||||

|
||||
|
||||
|
||||
## Arbeitsaufträge / Aufgabeneinteilung
|
||||
|
||||
|
|
BIN
images/cssHelp.png
Normal file
After Width: | Height: | Size: 430 KiB |
Before Width: | Height: | Size: 521 KiB After Width: | Height: | Size: 521 KiB |
Before Width: | Height: | Size: 701 KiB After Width: | Height: | Size: 701 KiB |
BIN
images/javascriptHelp.png
Normal file
After Width: | Height: | Size: 544 KiB |
BIN
images/jinjaHelp.png
Normal file
After Width: | Height: | Size: 248 KiB |
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 215 KiB |
12
main.py
|
@ -0,0 +1,12 @@
|
|||
# Copyright 2025 Kieler, Chiara
|
||||
#
|
||||
# Licensed under the AGPLv3.0 (the "License");
|
||||
# You may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.gnu.org/licenses/
|
||||
|
||||
# What this file should do? It should:
|
||||
# 1. Start all the Sensors
|
||||
# 2. Start the Webserver
|
||||
# 3. Somehow transfer data from the Sensors to the Webserver
|
|
@ -1,12 +0,0 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
(pkgs.buildFHSUserEnv {
|
||||
name = "venv";
|
||||
targetPkgs = pkgs: (with pkgs; [
|
||||
python312
|
||||
python312Packages.pip
|
||||
python312Packages.virtualenv
|
||||
python312Packages.matplotlib
|
||||
]);
|
||||
runScript = "bash --init-file /etc/profile";
|
||||
# runScript = "zsh";
|
||||
}).env
|
100
webserver/data_big.json
Normal file
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"location": "Building B - Research Wing",
|
||||
"sensors": [
|
||||
{
|
||||
"id": "temp-A12",
|
||||
"type": "temperature",
|
||||
"unit": "°C",
|
||||
"readings": [
|
||||
{ "ts": 1747814400, "value": 21.0 },
|
||||
{ "ts": 1747818000, "value": 21.4 },
|
||||
{ "ts": 1747821600, "value": 22.1 },
|
||||
{ "ts": 1747825200, "value": 22.6 },
|
||||
{ "ts": 1747828800, "value": 22.8 },
|
||||
{ "ts": 1747832400, "value": 23.0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hum_B2",
|
||||
"type": "humidity",
|
||||
"unit": "%",
|
||||
"readings": [
|
||||
{ "ts": 1747814400, "value": 50.1 },
|
||||
{ "ts": 1747818000, "value": 51.7 },
|
||||
{ "ts": 1747821600, "value": 49.3 },
|
||||
{ "ts": 1747825200, "value": 48.8 },
|
||||
{ "ts": 1747828800, "value": 47.6 },
|
||||
{ "ts": 1747832400, "value": 46.9 },
|
||||
{ "ts": 1747836000, "value": 45.2 },
|
||||
{ "ts": 1747839600, "value": 44.7 },
|
||||
{ "ts": 1747843200, "value": 43.5 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PRES-009",
|
||||
"type": "pressure",
|
||||
"unit": "hPa",
|
||||
"readings": [
|
||||
{ "ts": 1747814400, "value": 1011.0 },
|
||||
{ "ts": 1747818000, "value": 1010.8 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sensorX04",
|
||||
"type": "CO2",
|
||||
"unit": "ppm",
|
||||
"readings": [
|
||||
{ "ts": 1747814400, "value": 415 },
|
||||
{ "ts": 1747818000, "value": 420 },
|
||||
{ "ts": 1747821600, "value": 432 },
|
||||
{ "ts": 1747825200, "value": 440 },
|
||||
{ "ts": 1747828800, "value": 437 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "luxSensor-1",
|
||||
"type": "light",
|
||||
"unit": "lux",
|
||||
"readings": [
|
||||
{ "ts": 1747814400, "value": 300 },
|
||||
{ "ts": 1747818000, "value": 450 },
|
||||
{ "ts": 1747821600, "value": 600 },
|
||||
{ "ts": 1747825200, "value": 550 },
|
||||
{ "ts": 1747828800, "value": 500 },
|
||||
{ "ts": 1747832400, "value": 480 },
|
||||
{ "ts": 1747836000, "value": 470 },
|
||||
{ "ts": 1747839600, "value": 460 },
|
||||
{ "ts": 1747843200, "value": 455 },
|
||||
{ "ts": 1747846800, "value": 440 },
|
||||
{ "ts": 1747850400, "value": 430 },
|
||||
{ "ts": 1747854000, "value": 420 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "vib_07",
|
||||
"type": "vibration",
|
||||
"unit": "mm/s",
|
||||
"readings": [
|
||||
{ "ts": 1747814400, "value": 0.05 },
|
||||
{ "ts": 1747818000, "value": 0.06 },
|
||||
{ "ts": 1747821600, "value": 0.08 },
|
||||
{ "ts": 1747825200, "value": 0.07 },
|
||||
{ "ts": 1747828800, "value": 0.09 },
|
||||
{ "ts": 1747832400, "value": 0.1 },
|
||||
{ "ts": 1747836000, "value": 0.12 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "motion-Z3",
|
||||
"type": "motion",
|
||||
"unit": "count",
|
||||
"readings": [
|
||||
{ "ts": 1747814400, "value": 3 },
|
||||
{ "ts": 1747818000, "value": 1 },
|
||||
{ "ts": 1747821600, "value": 0 },
|
||||
{ "ts": 1747825200, "value": 2 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
{
|
||||
"location": "Building A - Lab 3",
|
||||
"sensors": [
|
|
@ -1,28 +1,40 @@
|
|||
# Copyright 2025 Kieler, Chiara
|
||||
#
|
||||
# Licensed under the AGPLv3.0 (the "License");
|
||||
# You may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.gnu.org/licenses/
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
import re
|
||||
from flask import Flask, send_from_directory, render_template
|
||||
from flask import Flask, render_template
|
||||
import json
|
||||
import matplotlib
|
||||
import hashlib
|
||||
matplotlib.use('Agg')
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Global Variables:
|
||||
jsonpath = "data.json"
|
||||
htmldata = ""
|
||||
jsonpath = "data_big.json"
|
||||
sensors: dict = {}
|
||||
|
||||
|
||||
# class Entries:
|
||||
# def __init__(self, id: str, ts: list, value: list):
|
||||
# self.id = id
|
||||
# templist: list = []
|
||||
# for i in range(len(ts)):
|
||||
# templist.append([ts[i], value[i]])
|
||||
# self.entries: list = templist
|
||||
#
|
||||
# def printEntries(self):
|
||||
# print(self.entries)
|
||||
#
|
||||
#
|
||||
# tese = Entries("sensor0", [1, 2, 3, 4], [10, 20, 30, 40])
|
||||
# tese.printEntries()
|
||||
def readJson():
|
||||
f = open(jsonpath)
|
||||
jsondata = json.load(f) # returns JSON object as a dictionary
|
||||
for i in jsondata["sensors"]: # Iterating through the json list
|
||||
timestamps: list = []
|
||||
values: list = []
|
||||
# print(len(i["readings"]))
|
||||
for j in range(len(i["readings"])):
|
||||
timestamps.append(i["readings"][j]["ts"])
|
||||
values.append(i["readings"][j]["value"])
|
||||
# print(i)
|
||||
# print(i["readings"][1])
|
||||
# print("\n")
|
||||
sensors[i["id"]] = Sensor(i["id"], i["unit"], i["type"], timestamps, values)
|
||||
f.close()
|
||||
|
||||
|
||||
class Sensor:
|
||||
|
@ -34,6 +46,7 @@ class Sensor:
|
|||
self.values: list = value
|
||||
self.timeonly: list = []
|
||||
self.timedate: list = []
|
||||
self.hash = hashlib.sha256(repr(ts).encode()).hexdigest()
|
||||
for i in ts:
|
||||
# print(i)
|
||||
self.timeonly.append(datetime.fromtimestamp(int(i)).strftime("%H:%M.%S"))
|
||||
|
@ -73,6 +86,29 @@ class Sensor:
|
|||
def getReadingsTime(self):
|
||||
return self.getReadings(timetype="time")
|
||||
|
||||
def getChartJSData(self, limit=5, reversed=False, timetype="ts"):
|
||||
datalist: list = [[],[]]
|
||||
match timetype:
|
||||
case "ts":
|
||||
for i in range(limit if limit < len(self.ts) else len(self.ts)):
|
||||
datalist[0].append(self.ts[i])
|
||||
datalist[1].append(self.values[i])
|
||||
case "time":
|
||||
for i in range(limit if limit < len(self.ts) else len(self.ts)):
|
||||
datalist[0].append(self.timeonly[i])
|
||||
datalist[1].append(self.values[i])
|
||||
case "timedate":
|
||||
for i in range(limit if limit < len(self.ts) else len(self.ts)):
|
||||
datalist[0].append(self.timedate[i])
|
||||
datalist[1].append(self.values[i])
|
||||
case _:
|
||||
return "ERROR: timetype must be one of 'ts', 'time', 'timedate'"
|
||||
if reversed:
|
||||
datalist[0].reverse()
|
||||
datalist[1].reverse()
|
||||
return datalist
|
||||
|
||||
|
||||
def getValueByTimestamp(self, ts: int):
|
||||
c = 0
|
||||
for i in self.ts:
|
||||
|
@ -97,113 +133,16 @@ class Sensor:
|
|||
plt.close()
|
||||
return path
|
||||
|
||||
# def formatLine(self, ts: int):
|
||||
# for i in self.ts:
|
||||
# if i == ts:
|
||||
# return f"{self.timedate}> {self.values[]}"
|
||||
def getHash(self):
|
||||
return self.hash
|
||||
|
||||
|
||||
# Json
|
||||
f = open(jsonpath)
|
||||
jsondata = json.load(f) # returns JSON object as a dictionary
|
||||
# sensordict = dict()
|
||||
sensors: dict = {}
|
||||
for i in jsondata["sensors"]: # Iterating through the json list
|
||||
timestamps: list = []
|
||||
values: list = []
|
||||
# print(len(i["readings"]))
|
||||
for j in range(len(i["readings"])):
|
||||
timestamps.append(i["readings"][j]["ts"])
|
||||
values.append(i["readings"][j]["value"])
|
||||
# print(i)
|
||||
# print(i["readings"][1])
|
||||
readJson()
|
||||
|
||||
# print("\n")
|
||||
# sensordict[i["id"]] = [i["type"], i["unit"], i["readings"]]
|
||||
# print(sensordict[i["id"]])
|
||||
sensors[i["id"]] = Sensor(i["id"], i["unit"], i["type"], timestamps, values)
|
||||
# print(sensors)
|
||||
|
||||
|
||||
f.close()
|
||||
print("\n")
|
||||
# print(sensordict)
|
||||
print(sensors)
|
||||
# print(sensors["sensor_001"].getReadings(limit=2, reversed=True, timetype="time"))
|
||||
|
||||
# for i in sensordict:
|
||||
# type = sensordict[i][0]
|
||||
# unit = sensordict[i][1]
|
||||
# readings = ""
|
||||
# datapoints: list = []
|
||||
# sensorrange = len(sensordict[i][2]) if len(sensordict[i][2]) < 5 else 5
|
||||
# for j in range(sensorrange):
|
||||
# datapoints.append([])
|
||||
# ts = sensordict[i][2][j]["ts"]
|
||||
# value = sensordict[i][2][j]["value"]
|
||||
# datapoints[j].append(ts)
|
||||
# datapoints[j].append(value)
|
||||
# datapoints[j].append(unit)
|
||||
# datapoints.sort(reverse=True)
|
||||
#
|
||||
# graphdata: list = [[], []]
|
||||
# for j in datapoints:
|
||||
# ts = j[0]
|
||||
# time = datetime.fromtimestamp(ts).strftime("%d.%m.%Y %H:%M.%S")
|
||||
# timeNoDate = datetime.fromtimestamp(ts).strftime("%H:%M.%S")
|
||||
# value = j[1]
|
||||
# unit = j[2]
|
||||
# # print(f"{time} {j}")
|
||||
# readings += f"{time}> {value}{unit}<br>"
|
||||
# graphdata[0].append(timeNoDate)
|
||||
# graphdata[1].append(value)
|
||||
#
|
||||
# # create_plot(i, unit, graphdata[0], graphdata[1])
|
||||
#
|
||||
# htmldata += f"""
|
||||
# <p>Id: {i}</p>
|
||||
# <p>Type: {type}</p>
|
||||
# <p>Readings:</p>
|
||||
# <div style="margin-left: 40px;">
|
||||
# {readings}
|
||||
# <img src="/plots/{i}.png" alt="Graph">
|
||||
# </div>
|
||||
# <hr>"""
|
||||
#
|
||||
# html = f"""
|
||||
# <!DOCTYPE html>
|
||||
# <html lang="en">
|
||||
# <head>
|
||||
# <meta charset="UTF-8">
|
||||
# <title>Sensor Data</title>
|
||||
# <style>
|
||||
# * {{
|
||||
# font-family: 'Caskaydia Cove NF';
|
||||
# font-size: 18px;
|
||||
# color: white;
|
||||
# background: #1C1C1C;
|
||||
# }}
|
||||
# </style>
|
||||
# </head>
|
||||
# <body>
|
||||
# {htmldata}
|
||||
# </body>
|
||||
# </html>
|
||||
# """
|
||||
|
||||
|
||||
# class MyHandler(BaseHTTPRequestHandler):
|
||||
# def do_GET(self):
|
||||
# self.send_response(200)
|
||||
# # self.send_header("Content-type", "text/html")
|
||||
# self.end_headers()
|
||||
# self.wfile.write(html.encode("utf-8"))
|
||||
#
|
||||
#
|
||||
# if __name__ == "__main__":
|
||||
# server_address = ("", 8000)
|
||||
# httpd = HTTPServer(server_address, MyHandler)
|
||||
# print("Serving on http://localhost:8000")
|
||||
# httpd.serve_forever()
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
|
@ -211,13 +150,10 @@ app = Flask(__name__)
|
|||
def index():
|
||||
return render_template("index.html", data=sensors)
|
||||
|
||||
|
||||
# @app.before_request
|
||||
# def before_request():
|
||||
# for sensor in sensors.values():
|
||||
# print(sensor)
|
||||
# sensor.renderPlot()
|
||||
|
||||
@app.before_request
|
||||
def reload_data():
|
||||
readJson()
|
||||
print("hello")
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True)
|
||||
|
|
2034
webserver/static/CaskaydiaCove-Regular.ttf
Normal file
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 16 KiB |
|
@ -1,3 +1,11 @@
|
|||
<!-- Copyright 2025 Kieler, Chiara -->
|
||||
<!---->
|
||||
<!-- Licensed under the AGPLv3.0 (the "License"); -->
|
||||
<!-- You may not use this file except in compliance with the License. -->
|
||||
<!-- You may obtain a copy of the License at -->
|
||||
<!---->
|
||||
<!-- https://www.gnu.org/licenses/ -->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
@ -7,28 +15,96 @@
|
|||
href="{{ url_for('static', filename='favicon.ico') }}"
|
||||
/>
|
||||
<title>Sensor Data</title>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js">
|
||||
</script>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'CaskaydiaCove Nerd Font';
|
||||
src: url('/static/CaskaydiaCove-Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
* {
|
||||
font-family: "Caskaydia Cove NF";
|
||||
font-family: "CaskaydiaCove Nerd Font";
|
||||
font-size: 18px;
|
||||
color: white;
|
||||
background: #1c1c1c;
|
||||
<!-- margin: 0; -->
|
||||
<!-- padding: 0; -->
|
||||
<!-- box-sizing: border-box; -->
|
||||
}
|
||||
.container {
|
||||
display: flex; /* Enable flexbox */
|
||||
justify-content: space-between; /* Optional: space between items */
|
||||
}
|
||||
.imgbox {
|
||||
<!-- background-color: #4CAF50; /* Background color for the boxes */ -->
|
||||
display: flex; /* Enable flexbox for inner content */
|
||||
justify-content: right; /* Center text horizontally */
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
}
|
||||
.txtbox {
|
||||
<!-- background-color: #C4FA05; /* Background color for the boxes */ -->
|
||||
justify-content: left; /* Center text horizontally */
|
||||
}
|
||||
.txtbox h1 {
|
||||
margin: 0;
|
||||
padding: 0.5em 0; /* optional: add some vertical padding */
|
||||
}
|
||||
.txtbox h2 {
|
||||
margin: 0;
|
||||
padding: 0em 0; /* optional: add some vertical padding */
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class = "container">
|
||||
<div class = "txtbox" style="text-align: center; margin: auto;">
|
||||
<h1>Sensor Readings</h1>
|
||||
</div>
|
||||
</div>
|
||||
<h2>{% for sensor in data.items() %}</h2>
|
||||
<hr>
|
||||
<h2>{{ sensor[1].getId() }}[{{ sensor[1].getType() }}]</h2>
|
||||
<div class = "container">
|
||||
<div class = "txtbox">
|
||||
<ul>
|
||||
{% for key,value in sensor[1].getReadingsTimeDate().items() %}
|
||||
<li>{{ key }}» {{ value }}{{ sensor[1].getUnit() }}</li>
|
||||
{% endfor %}
|
||||
<img
|
||||
src="{{ url_for('static', filename='plots/' + sensor[1].getId() + '.png') }}"
|
||||
alt="Graph"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
<canvas id={{ sensor[1].getId() }} class = "imgbox"></canvas>
|
||||
</div>
|
||||
<script>
|
||||
var sensordata = {{ sensor[1].getChartJSData(timetype="timedate")|tojson }};
|
||||
console.log(sensordata);
|
||||
new Chart('{{ sensor[1].getId() }}', {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: sensordata[0],
|
||||
datasets: [{
|
||||
label: '{{ sensor[1].getId() }}',
|
||||
data: sensordata[1],
|
||||
borderColor: 'rgba(245, 40, 145, 1)',
|
||||
backgroundColor: 'rgba(245, 40, 145, 0.2)',
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
||||
|
|