Merge pull request #1 from picchiosat/websocket-upgrade

Aggiunti WebSockets al frontend
This commit is contained in:
2026-04-21 22:35:29 +02:00
committed by GitHub
3 changed files with 39 additions and 10 deletions
+8 -6
View File
@@ -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`
+10 -2
View File
@@ -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)
+20 -1
View File
@@ -363,6 +363,8 @@
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js"></script>
<script>
// --- 1. TRANSLATION SYSTEM (i18n) ---
const i18n = {
@@ -655,7 +657,7 @@
}).join('');
refreshStates(); refreshLogs();
setInterval(() => { refreshStates(); refreshLogs(); }, 3000);
// setInterval rimosso! Da oggi si va in tempo reale!
} catch (e) { console.error(e); }
}
@@ -1075,6 +1077,23 @@
}
initUI();
// --- MOTORE WEBSOCKET REAL-TIME ---
const socket = io();
socket.on('connect', () => {
console.log("🟢 Connesso al server via WebSocket in tempo reale!");
// Facciamo un aggiornamento di sicurezza appena il tunnel si apre
refreshStates();
refreshLogs();
});
socket.on('dati_aggiornati', function() {
console.log("⚡ Rilevato nuovo traffico! Scatto istantaneo dell'interfaccia...");
// Il server ha appena urlato che ci sono novità: ricarichiamo le card e i log!
refreshStates();
refreshLogs();
});
</script>
<script>
if ('serviceWorker' in navigator) {