227 lines
7.3 KiB
Python
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)
|