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
894 lines
37 KiB
Markdown
894 lines
37 KiB
Markdown
# 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
|