diff --git a/README.md b/README.md index 2356642..e0d5570 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,12 @@ The Central Console dashboard will automatically read this configuration and dyn ### 🤖 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. +### 🔌 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 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. @@ -95,6 +101,12 @@ La dashboard centrale leggerà questa configurazione e genererà dinamicamente u ### 🤖 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. +### 🔌 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 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. diff --git a/install.txt b/install.txt index 9116a7a..032fb82 100644 --- a/install.txt +++ b/install.txt @@ -53,6 +53,13 @@ E) MULTIPLE PROFILES (Dynamic Profiles): If you don't need this feature, simply leave it empty: "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) ------------------------------------------------------------ @@ -117,6 +124,13 @@ E) PROFILI MULTIPLI (Dynamic Profiles): Se non ti serve questa funzione, lascialo vuoto: "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) ------------------------------------------------------------ diff --git a/node_config.json.example b/node_config.json.example index 3062254..69b2259 100644 --- a/node_config.json.example +++ b/node_config.json.example @@ -13,7 +13,9 @@ }, "settings": { "auto_healing": true, - "update_interval": 60 + "update_interval": 60, + "usb_reset_id": "", + "gpio_reset_pin": 21 }, "telegram": { "enabled": false, diff --git a/system_monitor.py b/system_monitor.py index a32b50b..c04ef3e 100644 --- a/system_monitor.py +++ b/system_monitor.py @@ -199,25 +199,36 @@ def check_auto_healing(client, status): client.publish(f"devices/{CLIENT_ID}/logs", msg) send_telegram_message(msg) - # --- START MODIFICATION: SPECIFIC HARDWARE RESET FOR MMDVMHOST --- - if proc_name.lower() == "mmdvmhost" and GPIO_AVAILABLE: - logger.info("Executing automatic HAT RESET before restarting MMDVMHost...") - try: - RESET_PIN = 21 # Ensure the PIN is correct for your nodes - GPIO.setwarnings(False) - GPIO.setmode(GPIO.BCM) - GPIO.setup(RESET_PIN, GPIO.OUT) - # LOW pulse to reset - GPIO.output(RESET_PIN, GPIO.LOW) - time.sleep(0.5) - GPIO.output(RESET_PIN, GPIO.HIGH) - GPIO.cleanup(RESET_PIN) - # Give the microcontroller time to restart - time.sleep(1.5) - client.publish(f"devices/{CLIENT_ID}/logs", "🔌 GPIO Pulse (MMDVM Reset) sent!") - except Exception as e: - logger.error(f"GPIO error in auto-healing: {e}") - # --- END MODIFICATION --- + # --- INIZIO LOGICA RESET HARDWARE PER MMDVMHOST --- + if proc_name.lower() == "mmdvmhost": + usb_id = cfg.get('settings', {}).get('usb_reset_id', "").strip() + gpio_pin = cfg.get('settings', {}).get('gpio_reset_pin', 21) + + if usb_id: + logger.info(f"Esecuzione reset USB per il dispositivo {usb_id} prima del riavvio...") + try: + subprocess.run(["sudo", "usbreset", usb_id], check=False) + time.sleep(1.5) + client.publish(f"devices/{CLIENT_ID}/logs", f"🔌 USB Reset ({usb_id}) inviato!") + except Exception as e: + logger.error(f"Errore durante il reset USB in auto-healing: {e}") + + elif GPIO_AVAILABLE and gpio_pin: + logger.info(f"Esecuzione reset GPIO (HAT) sul pin {gpio_pin} prima del riavvio...") + try: + RESET_PIN = int(gpio_pin) + 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]) elif attempts == 3: @@ -355,10 +366,27 @@ def on_message(client, userdata, msg): logger.info("REBOOT command received. Rebooting system...") time.sleep(1) subprocess.run(["sudo", "reboot"], check=True) + elif cmd == 'RESET_HAT': - RESET_PIN = 21 - if GPIO_AVAILABLE: + usb_id = cfg.get('settings', {}).get('usb_reset_id', "").strip() + gpio_pin = cfg.get('settings', {}).get('gpio_reset_pin', 21) + + if usb_id: 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.setmode(GPIO.BCM) GPIO.setup(RESET_PIN, GPIO.OUT) @@ -366,15 +394,19 @@ def on_message(client, userdata, msg): time.sleep(0.5) GPIO.output(RESET_PIN, GPIO.HIGH) 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) logger.info("Restarting MMDVMHost...") subprocess.run(["sudo", "systemctl", "restart", "mmdvmhost"], check=False) 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: - 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}") + 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"]: new_state = (cmd == "TG:ON")