Merge branch 'main' of https://git.miaig.dev/mia/abschlussarbeit
This commit is contained in:
commit
42ca11f965
8 changed files with 2279 additions and 18 deletions
19
.direnv/bin/nix-direnv-reload
Executable file
19
.direnv/bin/nix-direnv-reload
Executable file
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
if [[ ! -d "/home/mia/git/abschlussarbeit" ]]; then
|
||||||
|
echo "Cannot find source directory; Did you move it?"
|
||||||
|
echo "(Looking for "/home/mia/git/abschlussarbeit")"
|
||||||
|
echo 'Cannot force reload with this script - use "direnv reload" manually and then try again'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# rebuild the cache forcefully
|
||||||
|
_nix_direnv_force_reload=1 direnv exec "/home/mia/git/abschlussarbeit" true
|
||||||
|
|
||||||
|
# Update the mtime for .envrc.
|
||||||
|
# This will cause direnv to reload again - but without re-building.
|
||||||
|
touch "/home/mia/git/abschlussarbeit/.envrc"
|
||||||
|
|
||||||
|
# Also update the timestamp of whatever profile_rc we have.
|
||||||
|
# This makes sure that we know we are up to date.
|
||||||
|
touch -r "/home/mia/git/abschlussarbeit/.envrc" "/home/mia/git/abschlussarbeit/.direnv"/*.rc
|
1
.direnv/nix-profile-25.05-vj980b72z6zb0yg6
Symbolic link
1
.direnv/nix-profile-25.05-vj980b72z6zb0yg6
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
/nix/store/j5gmbcmy5gzjaksw6b6ksbfka49azfc6-nix-shell-env
|
2089
.direnv/nix-profile-25.05-vj980b72z6zb0yg6.rc
Normal file
2089
.direnv/nix-profile-25.05-vj980b72z6zb0yg6.rc
Normal file
File diff suppressed because it is too large
Load diff
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use nix
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/webserver/static/plots/
|
/webserver/static/plots/
|
||||||
*_data.json
|
*_data.json
|
||||||
|
/.direnv
|
||||||
|
|
64
sensors/sender.py
Normal file
64
sensors/sender.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import socket
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
SERVER_HOST = "127.0.0.1"
|
||||||
|
SERVER_PORT = 9999
|
||||||
|
|
||||||
|
def send_data(data):
|
||||||
|
with socket.create_connection((SERVER_HOST, SERVER_PORT)) as sock:
|
||||||
|
print(f"[+] Connected to server at {SERVER_HOST}:{SERVER_PORT}")
|
||||||
|
message = json.dumps(data) + '\n'
|
||||||
|
sock.sendall(message.encode('utf-8'))
|
||||||
|
print("[x] Data sent and connection closed.")
|
||||||
|
|
||||||
|
# Test behavior if run directly
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_data = {
|
||||||
|
"location": "Building A - Lab 3",
|
||||||
|
"sensors": [
|
||||||
|
{
|
||||||
|
"id": "sensor_001",
|
||||||
|
"type": "temperature",
|
||||||
|
"unit": "°C",
|
||||||
|
"readings": [
|
||||||
|
{ "ts": int(time.time()) - 60, "value": 22.3 },
|
||||||
|
{ "ts": int(time.time()), "value": 22.8 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sensor_002",
|
||||||
|
"type": "humidity",
|
||||||
|
"unit": "%",
|
||||||
|
"readings": [
|
||||||
|
{ "ts": int(time.time()) - 60, "value": 45.2 },
|
||||||
|
{ "ts": int(time.time()), "value": 46.1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
send_data(test_data)
|
||||||
|
|
||||||
|
# Live Updates
|
||||||
|
for i in range(15):
|
||||||
|
live_update = {
|
||||||
|
"location": "Building A - Lab 3",
|
||||||
|
"sensors": [
|
||||||
|
{
|
||||||
|
"id": "sensor_001",
|
||||||
|
"readings": [
|
||||||
|
{ "ts": int(time.time()), "value": i }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sensor_002",
|
||||||
|
"readings": [
|
||||||
|
{ "ts": int(time.time()), "value": 23.0 + i }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
send_data(live_update)
|
||||||
|
time.sleep(2)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright 2025 Kieler, Chiara
|
# Copyright 2025 Mia, Chiara
|
||||||
#
|
#
|
||||||
# Licensed under the AGPLv3.0 (the "License");
|
# Licensed under the AGPLv3.0 (the "License");
|
||||||
# You may not use this file except in compliance with the License.
|
# You may not use this file except in compliance with the License.
|
||||||
|
@ -12,6 +12,9 @@ from flask import Flask, render_template
|
||||||
import json
|
import json
|
||||||
import matplotlib
|
import matplotlib
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import os
|
||||||
matplotlib.use('Agg')
|
matplotlib.use('Agg')
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
@ -28,15 +31,63 @@ def readJson():
|
||||||
values: list = []
|
values: list = []
|
||||||
# print(len(i["readings"]))
|
# print(len(i["readings"]))
|
||||||
for j in range(len(i["readings"])):
|
for j in range(len(i["readings"])):
|
||||||
timestamps.append(i["readings"][j]["ts"])
|
timestamps.insert(0, i["readings"][j]["ts"])
|
||||||
values.append(i["readings"][j]["value"])
|
values.insert(0, i["readings"][j]["value"])
|
||||||
# print(i)
|
# print(i)
|
||||||
# print(i["readings"][1])
|
# print(i["readings"][1])
|
||||||
# print("\n")
|
# print("\n")
|
||||||
sensors[i["id"]] = Sensor(i["id"], i["unit"], i["type"], timestamps, values)
|
sensors[(jsondata["location"], i["id"])] = Sensor(i["id"], i["unit"], i["type"], timestamps, values)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_connection(conn, addr):
|
||||||
|
print(f"[+] Connected from {addr}")
|
||||||
|
buffer = ""
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
data = conn.recv(4096)
|
||||||
|
if not data:
|
||||||
|
break # client closed connection
|
||||||
|
buffer += data.decode("utf-8")
|
||||||
|
|
||||||
|
while '\n' in buffer:
|
||||||
|
line, buffer = buffer.split('\n', 1)
|
||||||
|
if line.strip():
|
||||||
|
try:
|
||||||
|
message = json.loads(line)
|
||||||
|
process_message(message)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print("[!] Invalid JSON")
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
print(f"[-] Disconnected from {addr}")
|
||||||
|
|
||||||
|
def process_message(message):
|
||||||
|
location = message.get("location", "unknown_location")
|
||||||
|
for s in message.get("sensors", []):
|
||||||
|
sensor_id = s["id"]
|
||||||
|
key = (location, sensor_id)
|
||||||
|
|
||||||
|
# Check if this is a new sensor (history), or just live data
|
||||||
|
if "type" in s and "unit" in s:
|
||||||
|
# Full metadata available
|
||||||
|
timestamps = [r["ts"] for r in s["readings"]]
|
||||||
|
values = [r["value"] for r in s["readings"]]
|
||||||
|
sensors[key] = Sensor(sensor_id, s["unit"], s["type"], timestamps, values)
|
||||||
|
else:
|
||||||
|
# Live update, assume sensor already exists
|
||||||
|
if key not in sensors:
|
||||||
|
print(f"[!] Live data received for unknown sensor {key}, ignoring.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
sensor = sensors[key]
|
||||||
|
for reading in s.get("readings", []):
|
||||||
|
sensor.ts.insert(0, reading["ts"])
|
||||||
|
sensor.timeonly.insert(0, datetime.fromtimestamp(int(reading["ts"])).strftime("%H:%M.%S"))
|
||||||
|
sensor.timedate.insert(0, datetime.fromtimestamp(int(reading["ts"])).strftime("%d.%m.%Y %H:%M.%S"))
|
||||||
|
sensor.values.insert(0, reading["value"])
|
||||||
|
|
||||||
|
|
||||||
class Sensor:
|
class Sensor:
|
||||||
def __init__(self, id: str, unit: str, type: str, ts: list, value: list):
|
def __init__(self, id: str, unit: str, type: str, ts: list, value: list):
|
||||||
self.id = id
|
self.id = id
|
||||||
|
@ -49,10 +100,8 @@ class Sensor:
|
||||||
self.hash = hashlib.sha256(repr(ts).encode()).hexdigest()
|
self.hash = hashlib.sha256(repr(ts).encode()).hexdigest()
|
||||||
for i in ts:
|
for i in ts:
|
||||||
# print(i)
|
# print(i)
|
||||||
self.timeonly.append(datetime.fromtimestamp(int(i)).strftime("%H:%M.%S"))
|
self.timeonly.insert(0, datetime.fromtimestamp(int(i)).strftime("%H:%M.%S"))
|
||||||
self.timedate.append(
|
self.timedate.insert(0, datetime.fromtimestamp(i).strftime("%d.%m.%Y %H:%M.%S"))
|
||||||
datetime.fromtimestamp(i).strftime("%d.%m.%Y %H:%M.%S")
|
|
||||||
)
|
|
||||||
|
|
||||||
def getId(self):
|
def getId(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
@ -63,7 +112,8 @@ class Sensor:
|
||||||
def getType(self):
|
def getType(self):
|
||||||
return self.type
|
return self.type
|
||||||
|
|
||||||
def getReadings(self, limit=5, reversed=True, timetype="ts"):
|
def getReadings(self, limit=10, reversed=True, timetype="ts"):
|
||||||
|
limit += 1
|
||||||
datadic: dict = {}
|
datadic: dict = {}
|
||||||
match timetype:
|
match timetype:
|
||||||
case "ts":
|
case "ts":
|
||||||
|
@ -74,6 +124,8 @@ class Sensor:
|
||||||
datadic[self.timeonly[i]] = self.values[i]
|
datadic[self.timeonly[i]] = self.values[i]
|
||||||
case "timedate":
|
case "timedate":
|
||||||
for i in range(limit if limit < len(self.ts) else len(self.ts)):
|
for i in range(limit if limit < len(self.ts) else len(self.ts)):
|
||||||
|
# print(i)
|
||||||
|
# print(self.values[i])
|
||||||
datadic[self.timedate[i]] = self.values[i]
|
datadic[self.timedate[i]] = self.values[i]
|
||||||
case _:
|
case _:
|
||||||
return "ERROR: timetype must be one of 'ts', 'time', 'timedate'"
|
return "ERROR: timetype must be one of 'ts', 'time', 'timedate'"
|
||||||
|
@ -86,7 +138,8 @@ class Sensor:
|
||||||
def getReadingsTime(self):
|
def getReadingsTime(self):
|
||||||
return self.getReadings(timetype="time")
|
return self.getReadings(timetype="time")
|
||||||
|
|
||||||
def getChartJSData(self, limit=5, reversed=False, timetype="ts"):
|
def getChartJSData(self, limit=10, reversed=False, timetype="ts"):
|
||||||
|
limit += 1
|
||||||
datalist: list = [[],[]]
|
datalist: list = [[],[]]
|
||||||
match timetype:
|
match timetype:
|
||||||
case "ts":
|
case "ts":
|
||||||
|
@ -103,7 +156,7 @@ class Sensor:
|
||||||
datalist[1].append(self.values[i])
|
datalist[1].append(self.values[i])
|
||||||
case _:
|
case _:
|
||||||
return "ERROR: timetype must be one of 'ts', 'time', 'timedate'"
|
return "ERROR: timetype must be one of 'ts', 'time', 'timedate'"
|
||||||
if reversed:
|
if not reversed:
|
||||||
datalist[0].reverse()
|
datalist[0].reverse()
|
||||||
datalist[1].reverse()
|
datalist[1].reverse()
|
||||||
return datalist
|
return datalist
|
||||||
|
@ -137,7 +190,20 @@ class Sensor:
|
||||||
return self.hash
|
return self.hash
|
||||||
|
|
||||||
|
|
||||||
readJson()
|
def start_socket_server(host='0.0.0.0', port=9999):
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
|
s.bind((host, port))
|
||||||
|
s.listen()
|
||||||
|
print(f"[*] Server listening on {host}:{port}")
|
||||||
|
while True:
|
||||||
|
conn, addr = s.accept()
|
||||||
|
threading.Thread(target=handle_connection, args=(conn, addr), daemon=True).start()
|
||||||
|
|
||||||
|
# if __name__ == "__main__":
|
||||||
|
# start_server()
|
||||||
|
|
||||||
|
|
||||||
|
# readJson()
|
||||||
|
|
||||||
# print("\n")
|
# print("\n")
|
||||||
# print(sensors)
|
# print(sensors)
|
||||||
|
@ -150,10 +216,12 @@ app = Flask(__name__)
|
||||||
def index():
|
def index():
|
||||||
return render_template("index.html", data=sensors)
|
return render_template("index.html", data=sensors)
|
||||||
|
|
||||||
@app.before_request
|
# @app.before_request
|
||||||
def reload_data():
|
# def reload_data():
|
||||||
readJson()
|
# readJson()
|
||||||
print("hello")
|
# print("hello")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
||||||
|
threading.Thread(target=start_socket_server, daemon=True).start()
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<!-- Copyright 2025 Kieler, Chiara -->
|
<!-- Copyright 2025 Mia, Chiara -->
|
||||||
<!---->
|
<!---->
|
||||||
<!-- Licensed under the AGPLv3.0 (the "License"); -->
|
<!-- Licensed under the AGPLv3.0 (the "License"); -->
|
||||||
<!-- You may not use this file except in compliance with the License. -->
|
<!-- You may not use this file except in compliance with the License. -->
|
||||||
|
@ -37,6 +37,17 @@
|
||||||
.container {
|
.container {
|
||||||
display: flex; /* Enable flexbox */
|
display: flex; /* Enable flexbox */
|
||||||
justify-content: space-between; /* Optional: space between items */
|
justify-content: space-between; /* Optional: space between items */
|
||||||
|
}
|
||||||
|
.namecontainer {
|
||||||
|
display: flex; /* Enable flexbox */
|
||||||
|
justify-content: space-between; /* Optional: space between items */
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.left {
|
||||||
|
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
|
||||||
}
|
}
|
||||||
.imgbox {
|
.imgbox {
|
||||||
<!-- background-color: #4CAF50; /* Background color for the boxes */ -->
|
<!-- background-color: #4CAF50; /* Background color for the boxes */ -->
|
||||||
|
@ -68,7 +79,14 @@
|
||||||
</div>
|
</div>
|
||||||
<h2>{% for sensor in data.items() %}</h2>
|
<h2>{% for sensor in data.items() %}</h2>
|
||||||
<hr>
|
<hr>
|
||||||
<h2>{{ sensor[1].getId() }}[{{ sensor[1].getType() }}]</h2>
|
<div class = "namecontainer">
|
||||||
|
<div class = "right">
|
||||||
|
<h2>{{ sensor[1].getId() }}[{{ sensor[1].getType() }}]</h2>
|
||||||
|
</div>
|
||||||
|
<div class = "left">
|
||||||
|
<h3>{{ sensor[0][0] }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class = "container">
|
<div class = "container">
|
||||||
<div class = "txtbox">
|
<div class = "txtbox">
|
||||||
<ul>
|
<ul>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue