Files
smart-mirror/raspi/Dashboard.py

120 lines
4.1 KiB
Python
Raw Permalink Normal View History

2026-05-02 20:54:53 +02:00
"""
Dashboard.py
Flask app factory for the Smart Mirror dashboard.
Routes
------
GET / Jinja2 rendered dashboard (templates/dashboard.html)
GET /api/data JSON snapshot from USBRead (status + ring-buffer)
GET /api/alerts JSON log of dispatched alert events
Start via Gunicorn:
gunicorn -c gunicorn.conf.py "Dashboard:create_app()"
Or for development:
python Dashboard.py
"""
import json
import threading
from collections import deque
from datetime import datetime
from pathlib import Path
from flask import Flask, jsonify, render_template
import Notification
import USBRead
# ── Resolve paths relative to this file ──────────────────────────
BASE_DIR = Path(__file__).parent
SETTINGS_PATH = BASE_DIR / "settings.json"
# ── Settings loader ───────────────────────────────────────────────
def load_settings() -> dict:
with open(SETTINGS_PATH, encoding="utf-8") as fh:
return json.load(fh)
# ── Flask app factory ─────────────────────────────────────────────
def create_app() -> Flask:
settings = load_settings()
dash_cfg = settings["dashboard"]
sensor_cfg = settings.get("sensors", [])
# ── Init subsystems ───────────────────────────────────────────
USBRead.init(settings["usb"])
USBRead.start_reader()
Notification.init(settings)
Notification.attach_to_usb()
# ── In-memory alert log (visible in dashboard) ────────────────
alert_log : deque = deque(maxlen=500)
alert_lock : threading.Lock = threading.Lock()
# Wrap Notification.send_alert so every outbound alert is also
# appended to the dashboard log.
_orig_send = Notification.send_alert
def _logging_send(subject: str, body: str) -> None:
with alert_lock:
alert_log.append({
"ts": datetime.now().isoformat(timespec="milliseconds"),
"subject": subject,
"body": body,
})
_orig_send(subject, body)
Notification.send_alert = _logging_send
# ── Build Flask app ───────────────────────────────────────────
app = Flask(
__name__,
template_folder = str(BASE_DIR / "templates"),
static_folder = str(BASE_DIR / "static"),
)
# Pre-serialise sensor config once (passed to template as JSON
# data-attribute so dashboard.js can read it without an extra request)
sensors_json = json.dumps(sensor_cfg)
# ── Routes ────────────────────────────────────────────────────
@app.route("/")
def index():
return render_template(
"dashboard.html",
title = dash_cfg.get("title", "Smart Mirror"),
sensors = sensor_cfg,
sensors_json = sensors_json,
poll_ms = dash_cfg.get("poll_interval_ms", 800),
)
@app.route("/api/data")
def api_data():
return jsonify(USBRead.get_snapshot())
@app.route("/api/alerts")
def api_alerts():
with alert_lock:
return jsonify(list(alert_log))
return app
# ── Dev entry point ───────────────────────────────────────────────
if __name__ == "__main__":
settings = load_settings()
dash = settings["dashboard"]
app = create_app()
app.run(
host = dash.get("host", "0.0.0.0"),
port = dash.get("port", 80),
debug = False,
)