Files
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

37 KiB
Raw Permalink Blame History

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

// 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