Files
docker-voltronic-homeassistant/documentation/CODE_ARCHITECTURE.md
T
Pi Developer 6af9fcad7e
Build Docker Image for Raspberry Pi / build-and-push (push) Failing after 4m42s
feat(v2.0): Auto-discovery, correzione bug critici, e documentazione completa
🆕 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
2026-01-25 15:00:48 +01:00

894 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <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)
```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<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