abschlussarbeit/webserver/server.py

227 lines
7.3 KiB
Python

# Copyright 2025 Mia, 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
from flask import Flask, render_template
import json
import matplotlib
import hashlib
import socket
import threading
import os
matplotlib.use('Agg')
import matplotlib.pyplot as plt
# Global Variables:
jsonpath = "data_big.json"
sensors: dict = {}
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.insert(0, i["readings"][j]["ts"])
values.insert(0, i["readings"][j]["value"])
# print(i)
# print(i["readings"][1])
# print("\n")
sensors[(jsondata["location"], i["id"])] = Sensor(i["id"], i["unit"], i["type"], timestamps, values)
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:
def __init__(self, id: str, unit: str, type: str, ts: list, value: list):
self.id = id
self.unit = unit
self.type = type
self.ts: list = ts
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.insert(0, datetime.fromtimestamp(int(i)).strftime("%H:%M.%S"))
self.timedate.insert(0, datetime.fromtimestamp(i).strftime("%d.%m.%Y %H:%M.%S"))
def getId(self):
return self.id
def getUnit(self):
return self.unit
def getType(self):
return self.type
def getReadings(self, limit=10, reversed=True, timetype="ts"):
limit += 1
datadic: dict = {}
match timetype:
case "ts":
for i in range(limit if limit < len(self.ts) else len(self.ts)):
datadic[self.ts[i]] = self.values[i]
case "time":
for i in range(limit if limit < len(self.ts) else len(self.ts)):
datadic[self.timeonly[i]] = self.values[i]
case "timedate":
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]
case _:
return "ERROR: timetype must be one of 'ts', 'time', 'timedate'"
return dict(sorted(datadic.items(), reverse=reversed))
# This only exist because jinja2 can not accept arguments :(
def getReadingsTimeDate(self):
return self.getReadings(timetype="timedate")
def getReadingsTime(self):
return self.getReadings(timetype="time")
def getChartJSData(self, limit=10, reversed=False, timetype="ts"):
limit += 1
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 not reversed:
datalist[0].reverse()
datalist[1].reverse()
return datalist
def getValueByTimestamp(self, ts: int):
c = 0
for i in self.ts:
c += 1
if i == ts:
return self.values[c]
def getTimestampByValue(self, value: float):
values: list = []
for i in range(len(self.values)):
if self.values[i] == value:
values.append(self.ts[i])
return values
def renderPlot(self):
plt.plot(self.timeonly, self.values)
plt.title(self.id)
plt.xlabel("Time")
plt.ylabel(self.unit)
path = f"plots/{self.id}.png"
plt.savefig(f"static/{path}") # Save to file
plt.close()
return path
def getHash(self):
return self.hash
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(sensors)
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html", data=sensors)
# @app.before_request
# def reload_data():
# readJson()
# print("hello")
if __name__ == "__main__":
if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
threading.Thread(target=start_socket_server, daemon=True).start()
app.run(debug=True)