6af9fcad7e
Build Docker Image for Raspberry Pi / build-and-push (push) Failing after 4m42s
🆕 Funzionalità Auto-Discovery - Aggiunto metodo AutoDiscoverBufferSizes() per rilevamento automatico QPIGS/QPIRI/QMOD/QPIWS - Supporto variabili d'ambiente (INVERTER_DEVICE, MQTT_SERVER, etc.) - Caching persistente buffer sizes in /cache/inverter.conf.cache - Flag -a/--auto-discover per modalità auto-detection 🐛 Bug Fixes Critici - **Parsing interi**: Aggiunta attemptAddSettingInt() con stoi() invece di stof() - Fix: stof('98') = 98.0f → 97 (int), ora stoi('98') = 98 direttamente - Applicato a: qpiri, qpiws, qmod, qpigs - **Thread sync**: Aggiunto ups_qpiws_changed a main loop e condizione exit poll() - Fix: loop principale controllava solo 3 flag su 4, causava hang - Fix: thread poll() non usciva in runOnce perché mancava controllo QPIWS - **Config accuracy**: Corretti buffer sizes (qpiri: 98→103, qpiws: 36→40) - Rimosso sources/inverter-cli/inverter.conf che sovrascriveva config globale - Validato con test: inverter_poller -1 completa in 6s con JSON completo 📚 Documentazione Completa - Creato documentation/CODE_ARCHITECTURE.md (38KB) - Mappa logica variabili globali - Flusso esecuzione main() con diagrammi ASCII - Sequence diagram classe cInverter (poll, query, auto-discovery) - Thread synchronization diagrams - MQTT integration bash scripts flow - Mappa concettuale 5-layer system architecture - Error handling e performance optimizations - Organizzati file .md in documentation/ (AUTO_DISCOVERY, IMPLEMENTATION, QUICKSTART, DEBUG) - Aggiornato README.md con sezione v2.0 e indice documentazione - Aggiornato .github/copilot-instructions.md con novità v2.0 🔧 Miglioramenti Build & CI/CD - Gitea Actions per build multi-arch (arm/v6, arm/v7, arm64, amd64, 386) - Configurazione VS Code completa (tasks, launch, debug GDB) - Script test-autodiscovery.sh e test-device.sh ✅ Testing Validato - inverter_poller -1 completa in 6 secondi - Output JSON completo con tutte le metriche - Exit pulito senza timeout (exit code 0) - Tutte le 4 query QMOD/QPIGS/QPIRI/QPIWS funzionanti
37 KiB
37 KiB
Architettura del Codice - Mappa Logica e Flusso di Esecuzione
Panoramica Generale
Il progetto è composto da due componenti principali:
- inverter-cli (C++): Applicazione che comunica con l'inverter via RS232/USB
- 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
// 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 <command> → 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)
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
// 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<CR>
│ │ │ │ │ └─> 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