feat: externalize hardware reset parameters (USB/GPIO) to dynamic JSON config

This commit is contained in:
2026-04-30 22:04:10 +02:00
parent 78a406a4aa
commit 279ca84aa1
4 changed files with 85 additions and 25 deletions
+12
View File
@@ -27,6 +27,12 @@ The Central Console dashboard will automatically read this configuration and dyn
### 🤖 Telegram Bot Integration ### 🤖 Telegram Bot Integration
The agent features built-in support for **Telegram Bot** notifications. This allows system administrators to receive real-time alerts regarding node status, offline services, and critical system events directly on their mobile devices. Notifications are highly manageable: they can be dynamically enabled or muted for each individual node directly from the central NOC Fleet Console, ensuring you only get notified when it matters. The agent features built-in support for **Telegram Bot** notifications. This allows system administrators to receive real-time alerts regarding node status, offline services, and critical system events directly on their mobile devices. Notifications are highly manageable: they can be dynamically enabled or muted for each individual node directly from the central NOC Fleet Console, ensuring you only get notified when it matters.
### 🔌 Dynamic Hardware Resets (USB & GPIO)
The agent can perform surgical hardware resets to stuck modems without rebooting the entire node. You can configure this dynamically in your `node_config.json` under the `"settings"` block:
* **USB Modems (e.g., STM32 Nucleo):** Set `"usb_reset_id"` to your device's ID (e.g., `"0483:374b"`). This requires the `usbutils` package installed on the host.
* **GPIO HATs:** Set `"gpio_reset_pin"` to your specific BCM pin (e.g., `21`).
The agent will automatically prioritize the USB reset. If `"usb_reset_id"` is left empty (`""`), it will gracefully fall back to the GPIO method.
### 🛠️ Core Configuration Files ### 🛠️ Core Configuration Files
To enable full functionality, you must configure these three files: To enable full functionality, you must configure these three files:
* **`node_config.json`**: The main configuration. Here you set your MQTT broker, your unique `client_id` (e.g., `IR3XXX`), and the paths for the lists below. * **`node_config.json`**: The main configuration. Here you set your MQTT broker, your unique `client_id` (e.g., `IR3XXX`), and the paths for the lists below.
@@ -95,6 +101,12 @@ La dashboard centrale leggerà questa configurazione e genererà dinamicamente u
### 🤖 Telegram Bot Integration ### 🤖 Telegram Bot Integration
L'agent dispone del supporto nativo per le notifiche tramite **Bot Telegram**. Questo permette agli amministratori di sistema di ricevere alert in tempo reale sullo stato dei nodi, sui servizi offline e sugli eventi critici direttamente sul proprio smartphone. La gestione degli avvisi è centralizzata: le notifiche possono essere attivate o silenziate dinamicamente per ogni singolo nodo direttamente dalla Fleet Console centrale, evitando spam inutile. L'agent dispone del supporto nativo per le notifiche tramite **Bot Telegram**. Questo permette agli amministratori di sistema di ricevere alert in tempo reale sullo stato dei nodi, sui servizi offline e sugli eventi critici direttamente sul proprio smartphone. La gestione degli avvisi è centralizzata: le notifiche possono essere attivate o silenziate dinamicamente per ogni singolo nodo direttamente dalla Fleet Console centrale, evitando spam inutile.
### 🔌 Reset Hardware Dinamico (USB & GPIO)
L'agente può eseguire reset hardware "chirurgici" ai modem bloccati senza dover riavviare l'intero nodo. Puoi configurare questa funzione dinamicamente nel file `node_config.json` sotto il blocco `"settings"`:
* **Modem USB (es. STM32 Nucleo):** Imposta `"usb_reset_id"` con l'ID del tuo dispositivo (es. `"0483:374b"`). Richiede il pacchetto `usbutils` installato sul nodo.
* **HAT GPIO:** Imposta `"gpio_reset_pin"` con il tuo pin BCM specifico (es. `21`).
L'agente darà sempre priorità al reset USB. Se il campo `"usb_reset_id"` viene lasciato vuoto (`""`), il sistema ripiegherà automaticamente sul metodo GPIO.
### 🛠️ File di Configurazione Chiave ### 🛠️ File di Configurazione Chiave
Per il corretto funzionamento, è necessario definire i parametri in questi tre file: Per il corretto funzionamento, è necessario definire i parametri in questi tre file:
* **`node_config.json`**: La configurazione principale. Qui imposti il broker MQTT, il tuo `client_id` univoco (es. `IR3XXX`) e i percorsi per le liste sottostanti. * **`node_config.json`**: La configurazione principale. Qui imposti il broker MQTT, il tuo `client_id` univoco (es. `IR3XXX`) e i percorsi per le liste sottostanti.
+14
View File
@@ -53,6 +53,13 @@ E) MULTIPLE PROFILES (Dynamic Profiles):
If you don't need this feature, simply leave it empty: If you don't need this feature, simply leave it empty:
"profiles": {} "profiles": {}
F) HARDWARE RESET CONFIGURATION:
In 'node_config.json' under "settings", define your hardware
reset method to recover stuck modems automatically:
- For USB modems: set "usb_reset_id" (e.g., "0483:374b").
- For GPIO HATs: set "gpio_reset_pin" (e.g., 21).
Leave "usb_reset_id" empty ("") to force GPIO usage.
------------------------------------------------------------ ------------------------------------------------------------
3. RUNNING AS A SERVICE (SYSTEMD) 3. RUNNING AS A SERVICE (SYSTEMD)
------------------------------------------------------------ ------------------------------------------------------------
@@ -117,6 +124,13 @@ E) PROFILI MULTIPLI (Dynamic Profiles):
Se non ti serve questa funzione, lascialo vuoto: Se non ti serve questa funzione, lascialo vuoto:
"profiles": {} "profiles": {}
F) CONFIGURAZIONE RESET HARDWARE:
In 'node_config.json' sotto "settings", definisci il metodo
di reset per sbloccare i modem bloccati:
- Per modem USB: imposta "usb_reset_id" (es. "0483:374b").
- Per HAT GPIO: imposta "gpio_reset_pin" (es. 21).
Lascia "usb_reset_id" vuoto ("") per forzare l'uso del GPIO.
------------------------------------------------------------ ------------------------------------------------------------
3. ESECUZIONE COME SERVIZIO (SYSTEMD) 3. ESECUZIONE COME SERVIZIO (SYSTEMD)
------------------------------------------------------------ ------------------------------------------------------------
+3 -1
View File
@@ -13,7 +13,9 @@
}, },
"settings": { "settings": {
"auto_healing": true, "auto_healing": true,
"update_interval": 60 "update_interval": 60,
"usb_reset_id": "",
"gpio_reset_pin": 21
}, },
"telegram": { "telegram": {
"enabled": false, "enabled": false,
+56 -24
View File
@@ -199,25 +199,36 @@ def check_auto_healing(client, status):
client.publish(f"devices/{CLIENT_ID}/logs", msg) client.publish(f"devices/{CLIENT_ID}/logs", msg)
send_telegram_message(msg) send_telegram_message(msg)
# --- START MODIFICATION: SPECIFIC HARDWARE RESET FOR MMDVMHOST --- # --- INIZIO LOGICA RESET HARDWARE PER MMDVMHOST ---
if proc_name.lower() == "mmdvmhost" and GPIO_AVAILABLE: if proc_name.lower() == "mmdvmhost":
logger.info("Executing automatic HAT RESET before restarting MMDVMHost...") usb_id = cfg.get('settings', {}).get('usb_reset_id', "").strip()
try: gpio_pin = cfg.get('settings', {}).get('gpio_reset_pin', 21)
RESET_PIN = 21 # Ensure the PIN is correct for your nodes
GPIO.setwarnings(False) if usb_id:
GPIO.setmode(GPIO.BCM) logger.info(f"Esecuzione reset USB per il dispositivo {usb_id} prima del riavvio...")
GPIO.setup(RESET_PIN, GPIO.OUT) try:
# LOW pulse to reset subprocess.run(["sudo", "usbreset", usb_id], check=False)
GPIO.output(RESET_PIN, GPIO.LOW) time.sleep(1.5)
time.sleep(0.5) client.publish(f"devices/{CLIENT_ID}/logs", f"🔌 USB Reset ({usb_id}) inviato!")
GPIO.output(RESET_PIN, GPIO.HIGH) except Exception as e:
GPIO.cleanup(RESET_PIN) logger.error(f"Errore durante il reset USB in auto-healing: {e}")
# Give the microcontroller time to restart
time.sleep(1.5) elif GPIO_AVAILABLE and gpio_pin:
client.publish(f"devices/{CLIENT_ID}/logs", "🔌 GPIO Pulse (MMDVM Reset) sent!") logger.info(f"Esecuzione reset GPIO (HAT) sul pin {gpio_pin} prima del riavvio...")
except Exception as e: try:
logger.error(f"GPIO error in auto-healing: {e}") RESET_PIN = int(gpio_pin)
# --- END MODIFICATION --- GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(RESET_PIN, GPIO.OUT)
GPIO.output(RESET_PIN, GPIO.LOW)
time.sleep(0.5)
GPIO.output(RESET_PIN, GPIO.HIGH)
GPIO.cleanup(RESET_PIN)
time.sleep(1.5)
client.publish(f"devices/{CLIENT_ID}/logs", f"🔌 GPIO Pulse (Pin {RESET_PIN}) inviato!")
except Exception as e:
logger.error(f"Errore GPIO in auto-healing: {e}")
# --- FINE LOGICA MODIFICA ---
subprocess.run(["sudo", "systemctl", "restart", proc_name]) subprocess.run(["sudo", "systemctl", "restart", proc_name])
elif attempts == 3: elif attempts == 3:
@@ -355,10 +366,27 @@ def on_message(client, userdata, msg):
logger.info("REBOOT command received. Rebooting system...") logger.info("REBOOT command received. Rebooting system...")
time.sleep(1) time.sleep(1)
subprocess.run(["sudo", "reboot"], check=True) subprocess.run(["sudo", "reboot"], check=True)
elif cmd == 'RESET_HAT': elif cmd == 'RESET_HAT':
RESET_PIN = 21 usb_id = cfg.get('settings', {}).get('usb_reset_id', "").strip()
if GPIO_AVAILABLE: gpio_pin = cfg.get('settings', {}).get('gpio_reset_pin', 21)
if usb_id:
try: try:
logger.info(f"RESET USB manuale inviato al dispositivo {usb_id}")
subprocess.run(["sudo", "usbreset", usb_id], check=False)
time.sleep(1.5)
logger.info("Restarting MMDVMHost...")
subprocess.run(["sudo", "systemctl", "restart", "mmdvmhost"], check=False)
client.publish(f"fleet/{CLIENT_ID}/status", "USB RESET + MMDVM RESTART OK")
client.publish(f"devices/{CLIENT_ID}/logs", f"🔌 USB Reset ({usb_id}) + MMDVMHost Restarted")
except Exception as e:
logger.error(f"Errore durante il reset USB/MMDVMHost: {e}")
client.publish(f"fleet/{CLIENT_ID}/status", f"RESET ERROR: {e}")
elif GPIO_AVAILABLE and gpio_pin:
try:
RESET_PIN = int(gpio_pin)
GPIO.setwarnings(False) GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM) GPIO.setmode(GPIO.BCM)
GPIO.setup(RESET_PIN, GPIO.OUT) GPIO.setup(RESET_PIN, GPIO.OUT)
@@ -366,15 +394,19 @@ def on_message(client, userdata, msg):
time.sleep(0.5) time.sleep(0.5)
GPIO.output(RESET_PIN, GPIO.HIGH) GPIO.output(RESET_PIN, GPIO.HIGH)
GPIO.cleanup(RESET_PIN) GPIO.cleanup(RESET_PIN)
logger.info(f"RESET pulse sent to GPIO {RESET_PIN}") logger.info(f"Pulsazione di RESET inviata al GPIO {RESET_PIN}")
time.sleep(1.5) time.sleep(1.5)
logger.info("Restarting MMDVMHost...") logger.info("Restarting MMDVMHost...")
subprocess.run(["sudo", "systemctl", "restart", "mmdvmhost"], check=False) subprocess.run(["sudo", "systemctl", "restart", "mmdvmhost"], check=False)
client.publish(f"fleet/{CLIENT_ID}/status", "HAT RESET + MMDVM RESTART OK") client.publish(f"fleet/{CLIENT_ID}/status", "HAT RESET + MMDVM RESTART OK")
client.publish(f"devices/{CLIENT_ID}/logs", "🔌 HAT Reset + MMDVMHost Restarted") client.publish(f"devices/{CLIENT_ID}/logs", f"🔌 GPIO Reset (Pin {RESET_PIN}) + MMDVMHost Restarted")
except Exception as e: except Exception as e:
logger.error(f"Error during GPIO/MMDVMHost reset: {e}") logger.error(f"Errore durante il reset GPIO/MMDVMHost: {e}")
client.publish(f"fleet/{CLIENT_ID}/status", f"RESET ERROR: {e}") client.publish(f"fleet/{CLIENT_ID}/status", f"RESET ERROR: {e}")
else:
msg = "⚠️ Nessun metodo di reset (USB o GPIO) configurato o disponibile sul nodo."
logger.warning(msg)
client.publish(f"devices/{CLIENT_ID}/logs", msg)
elif cmd in ["TG:OFF", "TG:ON"]: elif cmd in ["TG:OFF", "TG:ON"]:
new_state = (cmd == "TG:ON") new_state = (cmd == "TG:ON")