# Architettura del Codice - Mappa Logica e Flusso di Esecuzione ## Panoramica Generale Il progetto è composto da due componenti principali: 1. **inverter-cli** (C++): Applicazione che comunica con l'inverter via RS232/USB 2. **inverter-mqtt** (Bash): Script che gestiscono l'integrazione MQTT con Home Assistant ``` ┌──────────────────────────────────────────────────────────────┐ │ CONTAINER DOCKER │ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ entrypoint.sh │ │ │ │ 1. Auto-Discovery (prima esecuzione) │ │ │ │ 2. Caricamento configurazione │ │ │ │ 3. Avvio servizi MQTT │ │ │ └──────┬─────────────┬─────────────┬─────────────────────┘ │ │ │ │ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌────▼────────┐ │ │ │mqtt-init│ │mqtt-push│ │mqtt-subscriber│ │ │ │ .sh │ │ .sh │ │ .sh │ │ │ └────┬────┘ └────┬────┘ └────┬────────┘ │ │ │ │ │ │ │ │ ┌────▼─────────────▼────┐ │ │ │ │ inverter_poller │ │ │ │ │ (binario C++) │ │ │ │ └────┬──────────────────┘ │ │ │ │ │ │ └─────────────┴──────┐ │ │ │ │ │ ┌────▼────┐ │ │ │ MQTT │ │ │ │ Broker │ │ │ └─────────┘ │ └──────────────────────────────────────────────────────────────┘ │ ┌─────▼──────┐ │ Home │ │ Assistant │ └────────────┘ ``` --- ## Componente 1: inverter-cli (C++) ### 1.1 Mappa delle Variabili Globali #### File: main.cpp ```cpp // Configurazione string devicename = "/dev/ttyUSB0"; // Device seriale float runinterval = 0; // Intervallo polling (120 = ogni 30s) float ampfactor = 1; // Fattore correzione amperaggio float wattfactor = 1; // Fattore correzione wattaggio int qpiri = 0, qpiws = 0; // Buffer sizes comandi int qmod = 0, qpigs = 0; // Flag di controllo bool debugFlag = false; // Debug mode bool runOnce = false; // Esegui una volta e esci // Flag atomici per sincronizzazione thread atomic_bool ups_status_changed(false); atomic_bool ups_qmod_changed(false); // Mode query completata atomic_bool ups_qpiri_changed(false); // QPIRI query completata atomic_bool ups_qpigs_changed(false); // QPIGS query completata atomic_bool ups_qpiws_changed(false); // QPIWS query completata // Oggetto inverter cInverter *ups = nullptr; ``` ### 1.2 Flusso di Esecuzione: main() ``` START main(argc, argv) │ ├─> Parse argomenti CLI (InputParser) │ ├─> -d/--debug → debugFlag = true │ ├─> -1/--run-once → runOnce = true │ ├─> -r → rawcmd = command │ └─> -a/--auto-discover → modalità auto-discovery │ ├─> Carica configurazione │ ├─> Cerca ./inverter.conf │ └─> Altrimenti /etc/inverter/inverter.conf │ ├─> devicename (es: /dev/ttyUSB1) │ ├─> run_interval │ ├─> amperage_factor, watt_factor │ └─> qpiri, qpiws, qmod, qpigs (buffer sizes) │ ├─> Crea oggetto cInverter │ ups = new cInverter(devicename, qpiri, qpiws, qmod, qpigs) │ ├─> BRANCH: Modalità auto-discovery? │ YES ├─> ups->AutoDiscoverBufferSizes() │ ├─> Stampa DISCOVERY_QMOD=X │ ├─> Stampa DISCOVERY_QPIGS=X │ ├─> Stampa DISCOVERY_QPIRI=X │ ├─> Stampa DISCOVERY_QPIWS=X │ ├─> Stampa DISCOVERY_SUCCESS=true/false │ └─> exit(0) │ ├─> BRANCH: Comando raw specificato? │ YES ├─> ups->ExecuteCmd(rawcmd) │ ├─> Stampa risposta │ └─> exit(0) │ ├─> Modalità polling normale │ ├─> ups->runMultiThread() │ │ └─> Avvia thread: poll() │ │ │ └─> LOOP PRINCIPALE while(true) │ │ │ ├─> WAIT: ups_qmod_changed && ups_qpiri_changed && │ │ ups_qpigs_changed && ups_qpiws_changed │ │ │ ├─> Quando tutti i flag sono true: │ │ ├─> Reset flag a false │ │ ├─> Leggi dati con GetQpigsStatus(), GetQpiriStatus() │ │ ├─> Parse valori con sscanf() │ │ ├─> Calcola PV watts, watthour │ │ ├─> Stampa JSON completo su stdout │ │ └─> IF runOnce: exit(0) │ │ │ └─> sleep(1) e ripeti │ └─> END ``` ### 1.3 Classe cInverter #### Variabili Membro (inverter.h) ```cpp class cInverter { private: // Configurazione int fd; // File descriptor device seriale string device; // Path device (es: /dev/ttyUSB1) int buf_qpiri, buf_qpiws; // Buffer sizes configurati int buf_qmod, buf_qpigs; // Mutex per thread-safety mutex m; // Dati letti dall'inverter char status1[512]; // Risposta QPIGS char status2[512]; // Risposta QPIRI char warnings[512]; // Risposta QPIWS int mode; // Mode corrente (1-6) // Buffer comunicazione seriale uint8_t buf[2048]; public: // Costruttore cInverter(string devicename, int qpiri, int qpiws, int qmod, int qpigs); // Metodi polling void poll(); // Thread principale polling void runMultiThread(); // Avvia poll() in thread separato // Metodi query bool query(const char *cmd, int replysize); int query_auto(const char *cmd, int max_size); // Getter string *GetQpigsStatus(); string *GetQpiriStatus(); string *GetWarnings(); int GetMode(); // Utility void ExecuteCmd(const string cmd); void AutoDiscoverBufferSizes(); }; ``` #### Flusso di Esecuzione: poll() Thread ``` START poll() [Thread separato] │ ├─> fprintf: "Thread started, runOnce=X" │ └─> LOOP while(true) │ ├─> IF !ups_qmod_changed │ ├─> query("QMOD", buf_qmod) │ │ ├─> Costruisci comando con CRC │ │ ├─> write() su device seriale │ │ ├─> read() risposta │ │ ├─> Verifica CRC │ │ └─> return true se OK │ ├─> SetMode(buf[1]) │ └─> ups_qmod_changed = true │ ├─> IF !ups_qpigs_changed │ ├─> query("QPIGS", buf_qpigs) │ ├─> Copia in status1 │ └─> ups_qpigs_changed = true │ ├─> IF !ups_qpiri_changed │ ├─> query("QPIRI", buf_qpiri) │ ├─> Copia in status2 │ └─> ups_qpiri_changed = true │ ├─> IF !ups_qpiws_changed │ ├─> query("QPIWS", buf_qpiws) │ ├─> Copia in warnings │ └─> ups_qpiws_changed = true │ ├─> IF runOnce && tutti_i_flag_true │ ├─> fprintf: "All data collected" │ └─> return (termina thread) │ ├─> fprintf: "Flags: QMOD=X QPIGS=X ..." │ └─> sleep(2) e ripeti ``` #### Flusso di Esecuzione: query() ``` START query(cmd, replysize) │ ├─> Apri device se non già aperto │ ├─> open(device, O_RDWR | O_NOCTTY | O_NDELAY) │ ├─> Configura termios (2400 baud, 8N1) │ └─> tcsetattr() │ ├─> Flush buffer I/O │ ├─> tcflush(fd, TCIOFLUSH) │ └─> usleep(100000) // 100ms delay │ ├─> Costruisci comando con CRC │ ├─> Comando base (es: "QPIGS") │ ├─> Calcola CRC con cal_crc_half() │ ├─> Aggiungi CRC bytes │ └─> Aggiungi CR (\r) │ ├─> Invia comando │ └─> write(fd, comando, lunghezza) │ ├─> Leggi risposta (loop incrementale) │ ├─> FOR i=0 to replysize │ │ ├─> read(fd, buf+i, replysize-i) │ │ ├─> IF byte letto == CR: break │ │ └─> continue │ │ │ ├─> IF debug: stampa hex dump │ │ │ └─> Verifica formato risposta │ ├─> Primo byte deve essere '(' │ ├─> Ultimo byte deve essere CR │ └─> Verifica CRC │ └─> RETURN true se OK, false se errore ``` #### Flusso di Esecuzione: AutoDiscoverBufferSizes() ``` START AutoDiscoverBufferSizes() │ ├─> Stampa intestazione "AUTO-DISCOVERY MODE" │ ├─> Test QMOD (5-100 bytes) │ ├─> query_auto("QMOD", 100) │ │ ├─> Invia comando │ │ ├─> Leggi byte fino a CR │ │ └─> return numero_bytes │ └─> Stampa "✓ QMOD buffer size: X" │ ├─> Test QPIGS (5-200 bytes) │ ├─> query_auto("QPIGS", 200) │ └─> Stampa "✓ QPIGS buffer size: X" │ ├─> Test QPIRI (5-200 bytes) │ ├─> query_auto("QPIRI", 200) │ └─> Stampa "✓ QPIRI buffer size: X" │ ├─> Test QPIWS (5-100 bytes) │ ├─> query_auto("QPIWS", 100) │ └─> Stampa "✓ QPIWS buffer size: X" │ └─> Output risultati (parsabile) ├─> DISCOVERY_QMOD=X ├─> DISCOVERY_QPIGS=X ├─> DISCOVERY_QPIRI=X ├─> DISCOVERY_QPIWS=X └─> DISCOVERY_SUCCESS=true ``` ### 1.4 Funzioni di Utilità #### getSettingsFile() ``` Legge /etc/inverter/inverter.conf ├─> Parse linee formato: chiave=valore ├─> Ignora righe con # (commenti) └─> Popola variabili globali: ├─> devicename ├─> runinterval ├─> amperage_factor, watt_factor └─> qpiri, qpiws, qmod, qpigs ``` #### attemptAddSettingInt() / attemptAddSetting() ``` Parse stringhe in int/float ├─> stoi() per interi (qpiri, qpiws, qmod, qpigs) └─> stof() per float (amperage_factor, watt_factor) ``` #### cal_crc_half() ``` Calcola CRC-16 custom per protocollo inverter ├─> Input: buffer, lunghezza ├─> Algoritmo polinomiale custom └─> Return: uint16_t CRC ``` --- ## Componente 2: inverter-mqtt (Bash Scripts) ### 2.1 entrypoint.sh - Orchestrator Principale ``` START Container │ ├─> Carica variabili d'ambiente │ ├─> INVERTER_DEVICE (default: /dev/ttyUSB0) │ ├─> FORCE_DISCOVERY (default: false) │ └─> SKIP_DISCOVERY (default: false) │ ├─> DECISIONE: Eseguire auto-discovery? │ │ │ ├─> IF FORCE_DISCOVERY=true │ │ ├─> rm .discovery_done │ │ └─> NEED_DISCOVERY=true │ │ │ ├─> ELSE IF SKIP_DISCOVERY=true │ │ ├─> Aggiorna solo device in config │ │ └─> NEED_DISCOVERY=false │ │ │ ├─> ELSE IF .discovery_done non esiste │ │ └─> NEED_DISCOVERY=true │ │ │ └─> ELSE │ ├─> Leggi device salvato │ ├─> IF device cambiato │ │ └─> NEED_DISCOVERY=true │ └─> ELSE │ ├─> Carica parametri salvati │ └─> NEED_DISCOVERY=false │ ├─> IF NEED_DISCOVERY │ └─> run_discovery() │ ├─> Esegue: inverter_poller -d -a │ ├─> Parse output DISCOVERY_* │ ├─> IF SUCCESS=true │ │ ├─> update_config_with_discovery() │ │ │ ├─> sed device, qmod, qpigs, qpiri, qpiws │ │ │ └─> cp inverter.conf.backup │ │ └─> Salva in .discovery_done │ └─> ELSE │ └─> Usa config di default │ ├─> Avvia servizi MQTT │ │ │ ├─> BACKGROUND: watch -n 300 mqtt-init.sh │ │ └─> Ogni 5 minuti: re-init sensori HA │ │ │ ├─> BACKGROUND: mqtt-subscriber.sh │ │ └─> Ascolta comandi da Home Assistant │ │ │ └─> FOREGROUND: watch -n 30 mqtt-push.sh │ └─> Ogni 30s: push dati MQTT │ └─> END (rimane in loop con mqtt-push.sh) ``` ### 2.2 mqtt-push.sh - Pubblicazione Dati ``` START mqtt-push.sh │ ├─> Carica configurazione MQTT │ └─> cat mqtt.json | jq │ ├─> MQTT_SERVER │ ├─> MQTT_PORT │ ├─> MQTT_TOPIC (base) │ ├─> DEVICENAME │ └─> Credenziali │ ├─> Esegui polling inverter │ └─> inverter_poller -1 > output.json │ ├─> Parse JSON e pubblica ogni metrica │ └─> FOR each campo in JSON │ ├─> Estrai valore con jq │ └─> mosquitto_pub │ ├─> Topic: homeassistant/sensor/${DEVICENAME}_${metric} │ ├─> Payload: valore │ └─> Retain: true │ ├─> OPZIONALE: Push su InfluxDB │ └─> IF influx.enabled=true │ └─> curl POST a InfluxDB API │ └─> END ``` ### 2.3 mqtt-subscriber.sh - Ricezione Comandi ``` START mqtt-subscriber.sh (loop infinito) │ ├─> Subscribe a topic comandi │ └─> mosquitto_sub -t homeassistant/sensor/${DEVICENAME}/command │ └─> LOOP: Per ogni messaggio ricevuto │ ├─> Valida comando con regex │ └─> Match contro lista comandi validi │ ├─> POP0[0-2] │ ├─> PCP0[0-3] │ ├─> PBDV*, PBCV*, PBFT*, PCVV* │ └─> PE*/PD* (enable/disable) │ ├─> IF comando valido │ ├─> Esegui: inverter_poller -r COMANDO │ └─> Log risultato │ └─> ELSE └─> Log: comando non valido ``` ### 2.4 mqtt-init.sh - Auto-Discovery Home Assistant ``` START mqtt-init.sh │ ├─> Carica config MQTT │ └─> FOR each sensor (mode, voltage, current, watt, etc) │ ├─> Costruisci JSON config │ ├─> name: "Voltronic Battery Voltage" │ ├─> state_topic: homeassistant/sensor/voltronic_Battery_voltage │ ├─> unit_of_measurement: "V" │ ├─> device_class: "voltage" │ └─> unique_id, icon, ecc. │ └─> Pubblica su config topic └─> mosquitto_pub -t homeassistant/sensor/${DEVICENAME}_${sensor}/config └─> Payload: JSON config └─> Retain: true └─> END (Home Assistant auto-scopre tutti i sensori) ``` --- ## Mappa Concettuale del Sistema ``` ┌─────────────────────────────────────────────────────────────────┐ │ LIVELLO FISICO │ │ ┌──────────────┐ │ │ │ Inverter │ ←──── RS232/USB (2400 baud, 8N1) │ │ │ Hardware │ │ │ └──────┬───────┘ │ └─────────┼─────────────────────────────────────────────────────┬─┘ │ │ ┌─────────▼─────────────────────────────────────────────────────▼─┐ │ LIVELLO COMUNICAZIONE │ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ cInverter::query() / query_auto() │ │ │ │ • Costruzione comandi con CRC │ │ │ │ • Gestione protocollo RS232 │ │ │ │ • Parsing risposte │ │ │ │ • Validazione CRC │ │ │ └────────┬───────────────────────────────────────────────┘ │ └───────────┼──────────────────────────────────────────────────┬─┘ │ │ ┌───────────▼──────────────────────────────────────────────────▼─┐ │ LIVELLO APPLICAZIONE │ │ │ │ ┌──────────────────┐ ┌─────────────────────┐ │ │ │ cInverter::poll │◄───────┤ Thread Polling │ │ │ │ (Thread) │ │ • QMOD │ │ │ │ │ │ • QPIGS │ │ │ │ Ciclo continuo │ │ • QPIRI │ │ │ │ lettura dati │ │ • QPIWS │ │ │ └────────┬─────────┘ └─────────────────────┘ │ │ │ │ │ │ Atomic flags │ │ │ (ups_qmod_changed, ups_qpigs_changed, ...) │ │ │ │ │ ┌────────▼─────────┐ │ │ │ main() loop │ │ │ │ • Attende flag │ │ │ │ • Parse dati │ │ │ │ • Output JSON │ │ │ └────────┬─────────┘ │ └───────────┼──────────────────────────────────────────────────┬─┘ │ │ │ JSON stdout │ │ │ ┌───────────▼──────────────────────────────────────────────────▼─┐ │ LIVELLO INTEGRAZIONE │ │ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │ │ │ mqtt-push.sh │ │mqtt-subscriber.sh│ │ mqtt-init.sh │ │ │ │ • Esegue poller │ │ • Riceve comandi │ │ • Auto- │ │ │ │ • Parse JSON │ │ • Valida │ │ discovery │ │ │ │ • Pubblica MQTT │ │ • Esegue │ │ • Config HA │ │ │ └────────┬─────────┘ └────────┬─────────┘ └──────┬───────┘ │ └───────────┼──────────────────────┼────────────────────┼───────┬─┘ │ │ │ │ │ MQTT Protocol │ │ │ │ │ │ │ ┌───────────▼──────────────────────▼────────────────────▼───────▼─┐ │ LIVELLO PRESENTAZIONE │ │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ MQTT Broker │ │ │ │ Topics: │ │ │ │ • homeassistant/sensor/voltronic_Battery_voltage │ │ │ │ • homeassistant/sensor/voltronic_PV_in_watts │ │ │ │ • homeassistant/sensor/voltronic_Load_watt │ │ │ │ • ... │ │ │ │ • homeassistant/sensor/voltronic/command (subscribe) │ │ │ └─────────────────────────────┬─────────────────────────────┘ │ └────────────────────────────────┼───────────────────────────────┘ │ ┌──────────▼──────────┐ │ Home Assistant │ │ • Sensori │ │ • Grafici │ │ • Automazioni │ │ • Comandi │ └─────────────────────┘ ``` --- ## Sincronizzazione Multi-Thread ### Schema di Comunicazione Thread ``` ┌─────────────────────────────────────────────────────────────┐ │ THREAD PRINCIPALE │ │ main() │ │ │ │ while(true) { │ │ ┌────────────────────────────────────────────────┐ │ │ │ WAIT per flag atomici: │ │ │ │ ups_qmod_changed == true ◄────┐ │ │ │ │ ups_qpigs_changed == true ◄────┤ │ │ │ │ ups_qpiri_changed == true ◄────┤ │ │ │ │ ups_qpiws_changed == true ◄────┤ │ │ │ └────────────────────────────────────┼───────────┘ │ │ │ │ │ Quando TUTTI true: │ │ │ ├─> Reset flag a false │ │ │ ├─> GetQpigsStatus() ────────────────┼──────┐ │ │ ├─> GetQpiriStatus() ────────────────┼──┐ │ │ │ ├─> GetWarnings() ────────────────┼┐ │ │ │ │ ├─> Parse e calcolo ││ │ │ │ │ ├─> Output JSON ││ │ │ │ │ └─> IF runOnce: exit(0) ││ │ │ │ │ ││ │ │ │ │ sleep(1) ││ │ │ │ │ } ││ │ │ │ └──────────────────────────────────────────┼┼─┼───┼──────────┘ ││ │ │ Mutex protected ────────┼┼─┼───┼──────────┐ ││ │ │ │ ┌──────────────────────────────────────────▼▼─▼───▼──────────▼─┐ │ THREAD POLL │ │ cInverter::poll() │ │ │ │ while(true) { │ │ IF !ups_qmod_changed { │ │ query("QMOD") ──> status1[512] │ │ ups_qmod_changed = true ─────────────────┐ │ │ } │ │ │ │ │ │ IF !ups_qpigs_changed { │ │ │ query("QPIGS") ─┬─> LOCK(mutex) │ │ │ └─> status1 = buf │ │ │ ups_qpigs_changed = true ────────────────┤ │ │ } │ │ │ │ │ │ IF !ups_qpiri_changed { │ │ │ query("QPIRI") ─┬─> LOCK(mutex) │ │ │ └─> status2 = buf │ │ │ ups_qpiri_changed = true ────────────────┤ │ │ } │ │ │ │ │ │ IF !ups_qpiws_changed { │ │ │ query("QPIWS") ─┬─> LOCK(mutex) │ │ │ └─> warnings = buf │ │ │ ups_qpiws_changed = true ────────────────┘ │ │ } │ │ │ │ IF runOnce && all_flags_true { │ │ return; // Termina thread │ │ } │ │ │ │ sleep(2) │ │ } │ └───────────────────────────────────────────────────────────────┘ ``` ### Protezione Dati Condivisi ```cpp // Dati protetti da mutex mutex m; // Scrittura (thread poll) m.lock(); strcpy(status1, buffer); m.unlock(); // Lettura (thread main) string *GetQpigsStatus() { m.lock(); string *result = new string(status1); m.unlock(); return result; } // Flag atomici (non necessitano mutex) atomic_bool ups_qmod_changed; // Lettura/scrittura atomica garantita ``` --- ## Gestione Errori e Retry ### Strategia di Resilienza ``` query() failure │ ├─> CRC error │ ├─> Log errore │ ├─> Return false │ └─> Thread poll riprova al ciclo successivo (2s) │ ├─> Device non aperto │ ├─> Tentativo apertura device │ ├─> IF fallisce: sleep(1) e retry │ └─> MAX 3 tentativi prima di abort │ ├─> Timeout lettura │ ├─> read() con timeout implicito │ ├─> Return false │ └─> Retry al prossimo ciclo │ └─> Formato risposta errato ├─> Log hex dump (se debug) ├─> Return false └─> Retry al prossimo ciclo ``` ### Auto-Recovery Container ``` entrypoint.sh │ ├─> IF discovery fallisce │ ├─> Log errore │ ├─> Usa config di default │ └─> Continua con servizi MQTT │ └─> IF inverter_poller crash └─> watch riavvia automaticamente ogni 30s ``` --- ## Ottimizzazioni Prestazioni ### Frequency di Polling ``` Configurabile via run_interval: ├─> 120 = ogni 30 secondi (default) ├─> 60 = ogni minuto └─> 30 = ogni 2 minuti Thread poll interno: ├─> Query sequenziali (non parallele) ├─> Sleep 2s tra cicli └─> Immediate exit se runOnce mode ``` ### Buffer Management ``` Buffer sizes ottimizzati per ogni comando: ├─> QMOD: 5 bytes (fisso) ├─> QPIGS: 110 bytes (varia per modello) ├─> QPIRI: 103 bytes (varia per modello) └─> QPIWS: 40 bytes (varia per modello) Auto-discovery determina sizes esatti: └─> Evita letture incomplete o overhead ``` ### Caching MQTT ``` entrypoint.sh: ├─> .discovery_done persiste parametri ├─> Evita re-discovery ad ogni restart └─> Re-discovery solo se: ├─> FORCE_DISCOVERY=true ├─> Device cambiato └─> File cache non esiste ``` --- ## Debugging e Logging ### Livelli di Debug ``` inverter_poller -d: ├─> Abilita lprintf() output ├─> Stampa parametri configurazione ├─> Hex dump buffer seriali ├─> Log apertura/chiusura device └─> Timestamps su ogni operazione entrypoint.sh: ├─> Echo ogni step esecuzione ├─> Simboli visual: ✓ ✗ ⚠ ℹ └─> Log risultati discovery mqtt-push.sh: └─> Silenzioso (output su /dev/null) ``` ### File di Log ``` /var/log/inverter.log: └─> lprintf() scrive qui (se debugFlag=true) Container logs: └─> docker logs voltronic-mqtt ├─> Output entrypoint.sh ├─> Errori servizi MQTT └─> Messaggi discovery ``` --- ## Variabili d'Ambiente Docker ``` INVERTER_DEVICE: ├─> Default: /dev/ttyUSB0 ├─> Usato da: entrypoint.sh └─> Scritto in: /etc/inverter/inverter.conf FORCE_DISCOVERY: ├─> Default: false ├─> Se true: rm .discovery_done e riesegui └─> Usato da: entrypoint.sh SKIP_DISCOVERY: ├─> Default: false ├─> Se true: usa solo inverter.conf └─> Usato da: entrypoint.sh ``` --- ## Sequence Diagram: Primo Avvio Container ``` Container Start │ ├─> entrypoint.sh │ │ │ ├─> Check .discovery_done: NOT FOUND │ │ │ ├─> run_discovery() │ │ │ │ │ ├─> inverter_poller -d -a │ │ │ │ │ │ │ ├─> cInverter::AutoDiscoverBufferSizes() │ │ │ │ ├─> query_auto("QMOD") │ │ │ │ │ └─> Serial: QMOD + CRC │ │ │ │ │ Inverter ──> (L │ │ │ │ │ └─> Return: 5 │ │ │ │ │ │ │ │ │ ├─> query_auto("QPIGS") │ │ │ │ │ └─> Return: 110 │ │ │ │ │ │ │ │ │ ├─> query_auto("QPIRI") │ │ │ │ │ └─> Return: 103 │ │ │ │ │ │ │ │ │ └─> query_auto("QPIWS") │ │ │ │ └─> Return: 40 │ │ │ │ │ │ │ └─> Output: │ │ │ DISCOVERY_QMOD=5 │ │ │ DISCOVERY_QPIGS=110 │ │ │ DISCOVERY_QPIRI=103 │ │ │ DISCOVERY_QPIWS=40 │ │ │ DISCOVERY_SUCCESS=true │ │ │ │ │ ├─> Parse output (grep + cut) │ │ │ │ │ ├─> update_config_with_discovery() │ │ │ ├─> sed -i "s/^qmod=.*/qmod=5/" │ │ │ ├─> sed -i "s/^qpigs=.*/qpigs=110/" │ │ │ ├─> sed -i "s/^qpiri=.*/qpiri=103/" │ │ │ └─> sed -i "s/^qpiws=.*/qpiws=40/" │ │ │ │ │ └─> echo "device=..." > .discovery_done │ │ │ ├─> Start MQTT services │ │ │ │ │ ├─> watch -n 300 mqtt-init.sh & │ │ │ └─> Auto-discovery HA sensors │ │ │ │ │ ├─> mqtt-subscriber.sh & │ │ │ └─> mosquitto_sub -t .../command │ │ │ │ │ └─> watch -n 30 mqtt-push.sh (foreground) │ │ │ │ │ └─> LOOP ogni 30s: │ │ ├─> inverter_poller -1 │ │ │ │ │ │ │ ├─> Thread poll() │ │ │ ├─> Wait flags │ │ │ └─> Output JSON │ │ │ │ │ └─> mosquitto_pub (ogni metrica) │ │ │ └─> Container running... │ └─> Home Assistant riceve dati MQTT ``` --- ## Summary: Punti Chiave dell'Architettura ### 1. Separazione Responsabilità - **C++**: Comunicazione hardware, parsing protocollo, performance - **Bash**: Orchestrazione, integrazione MQTT, configurazione ### 2. Thread Safety - Atomic flags per sincronizzazione - Mutex per protezione dati condivisi - Pattern producer-consumer (poll → main) ### 3. Resilienza - Auto-discovery automatica - Retry automatico su errori - Graceful degradation (fallback a config default) - Container auto-restart ### 4. Configurabilità - Buffer sizes auto-detected - Device configurabile via ENV - Fattori correzione calibrabili - Intervallo polling personalizzabile ### 5. Estensibilità - Facile aggiungere nuovi comandi inverter - Template MQTT per nuovi sensori - Supporto multi-inverter (container separati) - Plugin InfluxDB opzionale