From 8a40e9b066cd2065185c18f85f6bb6b31581b8c0 Mon Sep 17 00:00:00 2001 From: Roby Date: Tue, 21 Apr 2026 22:28:32 +0200 Subject: [PATCH] Aggiunti WebSockets al frontend --- README.md | 14 ++++++++------ app.py | 12 ++++++++++-- templates/index.html | 23 +++++++++++++++++++++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 968bf41..ee05ca7 100644 --- a/README.md +++ b/README.md @@ -13,24 +13,25 @@ ### 🏗️ System Architecture The ecosystem consists of three main parts: -1. **The Central Dashboard (`app.py`):** A Flask web server that handles the UI (with modern Glassmorphism design), user permissions (SQLite), and logic. +1. **The Central Dashboard (`app.py`):** A Flask web server that handles the UI (with modern Glassmorphism design), user permissions (SQLite), and **WebSocket connections for zero-latency updates**. 2. **The Remote Agent (`system_monitor.py`):** A lightweight script running on each repeater (Raspberry Pi/Linux) that monitors hardware and executes commands. 3. **The MQTT Broker:** The communication backbone. All data and commands flow through MQTT for near-instant responsiveness. ### ✨ Features +* **Zero-Latency Real-Time UI:** Powered by WebSockets (Socket.IO), the dashboard updates instantly upon radio traffic or telemetry changes, completely eliminating heavy HTTP polling overhead. * **Centralized Telemetry:** Real-time CPU, RAM, Temperature, and Disk usage for all nodes. * **Service Management:** Start, Stop, or Restart system daemons (MMDVMHost, DMRGateway, etc.) remotely. * **Smart Auto-Healing:** The agent automatically detects crashed services and attempts to revive them before raising critical alerts (includes Telegram notifications). * **Hardware Reset (GPIO):** Physically reboot the MMDVM radio HAT via GPIO pins directly from the dashboard, automatically restarting the host daemon for a fail-proof serial connection recovery. * **Remote Configuration:** Built-in web editor for `.ini` files—no SSH required. -* **Live Heard Log:** Unified view of radio traffic across the entire network. +* **Live Heard Log:** Unified, real-time view of radio traffic across the entire network. * **Global Operations:** Switch profiles (e.g., Profile A/B) or force network-wide configuration updates instantly on all repeaters with one click. * **PWA Ready (Mobile App):** Fully responsive design that can be installed directly on Android/iOS as a Progressive Web App for a native, full-screen mobile experience. ### 🚀 Installation & Setup #### 1. Server Setup (Central Hub) -* Install dependencies: `pip install flask paho-mqtt psutil werkzeug` +* Install dependencies: `pip install flask flask-socketio paho-mqtt psutil werkzeug` * Configure `config.json` (use `config.example.json` as template) with your MQTT credentials. * Define your repeaters in `clients.json`. * Run: `python3 app.py` @@ -53,24 +54,25 @@ The `system_monitor.py` must be installed on every node you wish to monitor. ### 🏗️ Architettura del Sistema L'ecosistema si compone di tre parti principali: -1. **Dashboard Centrale (`app.py`):** Un server web Flask che gestisce l'interfaccia (con design moderno in stile Glassmorphism), i permessi utenti (SQLite) e la logica di controllo. +1. **Dashboard Centrale (`app.py`):** Un server web Flask che gestisce l'interfaccia (con design moderno in stile Glassmorphism), i permessi utenti (SQLite) e **connessioni WebSocket per aggiornamenti a latenza zero**. 2. **Agente Remoto (`system_monitor.py`):** Uno script leggero in esecuzione su ogni ripetitore (Raspberry Pi/Linux) che raccoglie i dati hardware ed esegue i comandi. 3. **Broker MQTT:** Il centro nevralgico della comunicazione. Tutti i dati e i comandi viaggiano su MQTT per una reattività istantanea. ### ✨ Funzionalità +* **Interfaccia Real-Time a Latenza Zero:** Grazie all'integrazione di WebSockets (Socket.IO), la dashboard scatta all'istante al passaggio di traffico radio o ai cambi di telemetria, eliminando totalmente il carico del polling HTTP continuo. * **Telemetria Centralizzata:** Stato in tempo reale di CPU, RAM, Temperatura e Disco di tutti i nodi. * **Gestione Servizi:** Avvio, arresto o riavvio dei demoni di sistema (MMDVMHost, DMRGateway, ecc.) da remoto. * **Auto-Healing Intelligente:** L'agente rileva automaticamente i servizi andati in blocco e tenta di rianimarli prima di inviare allarmi critici (include notifiche Telegram). * **Reset Hardware (GPIO):** Invia un impulso di reset fisico alla scheda radio MMDVM tramite i pin GPIO direttamente dalla dashboard, riavviando automaticamente il demone host per ripristinare la comunicazione seriale. * **Configurazione Remota:** Editor web integrato per i file `.ini`: modifica i parametri senza accedere in SSH. -* **Log Ascolti Live:** Vista unificata del traffico radio di tutta la rete. +* **Log Ascolti Live:** Vista unificata e in tempo reale del traffico radio di tutta la rete. * **Operazioni Globali:** Commuta tra diversi assetti (es. Profilo A/B) o forza l'aggiornamento dei dati contemporaneamente su tutta la rete con un solo clic. * **PWA Ready (App Mobile):** Design completamente responsivo, installabile su smartphone Android e iOS come Progressive Web App per un'esperienza fluida e nativa a schermo intero. ### 🚀 Installazione e Configurazione #### 1. Setup del Server (Hub Centrale) -* Installa le dipendenze: `pip install flask paho-mqtt psutil werkzeug` +* Installa le dipendenze: `pip install flask flask-socketio paho-mqtt psutil werkzeug` * Configura `config.json` (usa `config.example.json` come base) con le credenziali MQTT. * Definisci i tuoi ripetitori nel file `clients.json`. * Avvia: `python3 app.py` diff --git a/app.py b/app.py index 5051b57..03c5f45 100644 --- a/app.py +++ b/app.py @@ -9,6 +9,7 @@ import threading import time import logging from logging.handlers import RotatingFileHandler +from flask_socketio import SocketIO, emit # --- CONFIGURAZIONE LOGGING --- logging.basicConfig( @@ -99,6 +100,7 @@ def get_call(id, proto="DMR"): def save_cache(data): with open(CACHE_FILE, 'w') as f: json.dump(data, f) + socketio.emit('dati_aggiornati') def save_to_sqlite(client_id, data, protocol="DMR"): conn = sqlite3.connect(DB_PATH) @@ -107,8 +109,10 @@ def save_to_sqlite(client_id, data, protocol="DMR"): (client_id, protocol, str(data.get('source_id', '---')), str(data.get('destination_id', '---')), data.get('slot', 0), round(data.get('duration', 0), 1), round(data.get('ber', 0), 2))) conn.commit() conn.close() + socketio.emit('dati_aggiornati') app = Flask(__name__) +socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading') app.secret_key = 'ari_fvg_secret_ultra_secure' client_states = {} device_configs = {} @@ -169,6 +173,7 @@ def on_message(client, userdata, msg): elif parts[0] == 'servizi': client_states[cid] = payload + socketio.emit('dati_aggiornati') # <--- WEBSOCKET if payload.upper() not in ['OFF', 'OFFLINE', '']: tel = client_telemetry.get(cid, {}) if isinstance(tel, dict) and '🔄' in str(tel.get('ts1', '')): @@ -187,9 +192,9 @@ def on_message(client, userdata, msg): "files": data.get("files", data.get("config_files", [])), "profiles": data.get("profiles", {"A": "PROFILO A", "B": "PROFILO B"}) } + socketio.emit('dati_aggiornati') # <--- WEBSOCKET except Exception as e: logger.error(f"Errore parsing health: {e}") - # NUOVO BLOCCO: Intercettazione configurazione DMRGateway elif len(parts) >= 4 and parts[0] == 'data' and parts[2].lower() == 'dmrgateway' and (parts[3].upper().startswith('NETWORK') or parts[3].upper().startswith('DMR NETWORK')): try: cid = parts[1].lower() @@ -211,6 +216,7 @@ def on_message(client, userdata, msg): if is_ts1: network_mapping[cid]["ts1"] = net_name if is_ts2: network_mapping[cid]["ts2"] = net_name + socketio.emit('dati_aggiornati') # <--- WEBSOCKET except Exception as e: logger.error(f"Errore parsing DMRGateway per {cid}: {e}") @@ -259,6 +265,7 @@ def on_message(client, userdata, msg): active_calls[cid][sk] = {'src': src, 'dst': dst} client_telemetry[cid]["alt"] = "" client_telemetry[cid][sk] = f"🎙️ {src} ➔ TG {dst}" + socketio.emit('dati_aggiornati') # <--- WEBSOCKET elif act in ['end', 'lost']: info = active_calls[cid].get(sk, {'src': '---', 'dst': '---'}) d['source_id'], d['destination_id'] = info['src'], info['dst'] @@ -288,6 +295,7 @@ def on_message(client, userdata, msg): active_calls[cid][k] = {'src': src, 'dst': target} client_telemetry[cid].update({"ts1":"","ts2":"","alt": f"{ico} {name}: {src} ➔ {target}"}) + socketio.emit('dati_aggiornati') # <--- WEBSOCKET elif act in ['end', 'lost']: info = active_calls[cid].get(k, {'src': '---', 'dst': '---'}) @@ -619,4 +627,4 @@ def serve_icon(): if __name__ == '__main__': threading.Thread(target=auto_update_ids, daemon=True).start() - app.run(host='0.0.0.0', port=5000) + socketio.run(app, host='0.0.0.0', port=9000) diff --git a/templates/index.html b/templates/index.html index 5fc35b2..2db854f 100644 --- a/templates/index.html +++ b/templates/index.html @@ -363,6 +363,8 @@ + +