From 547537e7616d23ffddbe13dabc6bc8ac0bb0f203 Mon Sep 17 00:00:00 2001 From: Pi Developer Date: Sat, 31 Jan 2026 16:15:26 +0100 Subject: [PATCH] Implementazione supporto multi-inverter paralleli e fix comunicazione MQTT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Aggiunto supporto lettura inverter paralleli tramite comandi QPGS0-QPGS9 - Implementato discovery automatico inverter con filtro duplicati e serial invalidi - Risolti bug critici comunicazione seriale: * Fix buffer ExecuteCmd da 7 a 200 bytes * Supporto terminatori CR e LF * ModalitΓ  blocking con delay 500ms * Lettura byte-by-byte per terminatore affidabile - Implementato script MQTT per pubblicazione dati multi-inverter: * mqtt-push-parallel.sh con topic separati per ogni inverter * Fix autenticazione MQTT con username/password * Aggiunto flag retain (-r) per persistenza dati - Creato test-loop-parallel.sh per simulazione completa container - Aggiornata documentazione con compatibilitΓ  MKS IV e guida test loop - Aggiornati profili debug VS Code per bash e parallel discovery - Configurazione MQTT completa con server reale (192.168.1.37:1883) Sistema testato e funzionante con 2 inverter Voltronic Axpert MKS IV --- .gitea/workflows/docker-build.yml | 52 +-- .gitea/workflows/docker-cleanup.yml | 10 +- .github/copilot-instructions.md | 11 +- .vscode/launch.json | 154 ++++++- config/inverter.conf | 5 +- config/mqtt.json | 6 +- documentation/MKS_IV_COMPATIBILITY.md | 235 +++++++++++ documentation/MKS_IV_TEST_RESULTS.md | 439 ++++++++++++++++++++ documentation/TEST_LOOP_REFERENCE.md | 277 ++++++++++++ sources/inverter-cli/inverter.cpp | 100 ++++- sources/inverter-cli/inverter.h | 2 + sources/inverter-cli/main.cpp | 6 + sources/inverter-cli/test-baudrates.sh | 142 +++++++ sources/inverter-cli/test-mks-commands.sh | 60 +++ sources/inverter-cli/test_serial_dump.py | 22 + sources/inverter-cli/test_usb0.py | 26 ++ sources/inverter-mqtt/mqtt-push-parallel.sh | 140 +++++++ sources/inverter-mqtt/test-loop-parallel.sh | 225 ++++++++++ 18 files changed, 1842 insertions(+), 70 deletions(-) create mode 100644 documentation/MKS_IV_COMPATIBILITY.md create mode 100644 documentation/MKS_IV_TEST_RESULTS.md create mode 100644 documentation/TEST_LOOP_REFERENCE.md create mode 100755 sources/inverter-cli/test-baudrates.sh create mode 100755 sources/inverter-cli/test-mks-commands.sh create mode 100644 sources/inverter-cli/test_serial_dump.py create mode 100644 sources/inverter-cli/test_usb0.py create mode 100755 sources/inverter-mqtt/mqtt-push-parallel.sh create mode 100755 sources/inverter-mqtt/test-loop-parallel.sh diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml index 9507f65..6700c2a 100644 --- a/.gitea/workflows/docker-build.yml +++ b/.gitea/workflows/docker-build.yml @@ -13,16 +13,9 @@ on: - main - master -env: - REGISTRY: 192.168.1.37:30008 - IMAGE_NAME: ${{ github.repository }} - jobs: build-and-push: runs-on: ubuntu-latest - permissions: - contents: read - packages: write steps: - name: Checkout code @@ -31,25 +24,17 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v2 with: - platforms: linux/arm/v7,linux/arm64 + platforms: linux/arm/v6,linux/arm/v7,linux/arm64 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - driver-opts: | - image=moby/buildkit:latest - network=host - config-inline: | - [registry."192.168.1.37:30008"] - http = true - insecure = true + uses: docker/setup-buildx-action@v2 - name: Docker meta id: meta uses: docker/metadata-action@v4 with: images: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + ${{ secrets.DOCKER_USERNAME }}/ha-voltronic-mqtt tags: | type=ref,event=branch type=ref,event=pr @@ -57,29 +42,19 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=raw,value=latest,enable={{is_default_branch}} - - name: Login to Gitea Container Registry + - name: Login to Docker Hub if: github.event_name != 'pull_request' - run: | - echo "Configurazione autenticazione registry..." - mkdir -p ~/.docker - AUTH=$(echo -n "${{ github.actor }}:${{ secrets.REGISTRY_TOKEN }}" | base64) - cat > ~/.docker/config.json << EOF - { - "auths": { - "192.168.1.37:30008": { - "auth": "$AUTH" - } - } - } - EOF + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image - id: docker_build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile.multiarch - platforms: linux/arm/v7,linux/arm64 + platforms: linux/arm/v6,linux/arm/v7,linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -89,11 +64,6 @@ jobs: VCS_REF=${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max - provenance: false - outputs: type=registry,registry.insecure=true - name: Image digest - run: | - echo "Tags pubblicati:" - echo "${{ steps.meta.outputs.tags }}" - echo "Digest: ${{ steps.docker_build.outputs.digest }}" + run: echo ${{ steps.meta.outputs.tags }} diff --git a/.gitea/workflows/docker-cleanup.yml b/.gitea/workflows/docker-cleanup.yml index 03a8cb6..616fd6b 100644 --- a/.gitea/workflows/docker-cleanup.yml +++ b/.gitea/workflows/docker-cleanup.yml @@ -9,21 +9,17 @@ on: jobs: cleanup-old-images: runs-on: ubuntu-latest - permissions: - contents: read - packages: write if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' steps: - name: Checkout code uses: actions/checkout@v3 - - name: Login to Gitea Container Registry + - name: Login to Docker Hub uses: docker/login-action@v2 with: - registry: gitea.home-nas-ds.org - username: ${{ github.actor }} - password: ${{ secrets.REGISTRY_TOKEN }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Cleanup old development images run: | diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 068206f..9ec23c5 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -373,17 +373,14 @@ automation: ### Workflows -1. **docker-build.yml** - Build e push multi-arch su Gitea Registry +1. **docker-build.yml** - Build e push multi-arch su tag/push 2. **docker-test.yml** - Test build su PR 3. **docker-cleanup.yml** - Pulizia immagini vecchie (schedulato) -### Autenticazione +### Secrets Richiesti -Gitea Actions usa automaticamente `${{ github.token }}` per autenticarsi al registry interno. Non sono necessari secrets esterni. - -### Registry - -Le immagini Docker vengono pubblicate su: `gitea.home-nas-ds.org//:tag` +- `DOCKER_USERNAME` +- `DOCKER_PASSWORD` ## Troubleshooting diff --git a/.vscode/launch.json b/.vscode/launch.json index 83db415..2cc8281 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -107,6 +107,116 @@ "preLaunchTask": "build-inverter-cli-debug", "miDebuggerPath": "/usr/bin/gdb" }, + { + "name": "(gdb) Container Mode - Auto-Discovery", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/sources/inverter-cli/bin/inverter_poller", + "args": [ + "-d", + "-a" + ], + "stopAtEntry": false, + "cwd": "/", + "environment": [ + { + "name": "INVERTER_DEVICE", + "value": "/dev/ttyUSB0" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "preLaunchTask": "build-inverter-cli-debug", + "miDebuggerPath": "/usr/bin/gdb", + "logging": { + "engineLogging": false + } + }, + { + "name": "(gdb) Container Mode - Run Once", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/sources/inverter-cli/bin/inverter_poller", + "args": [ + "-d", + "-1" + ], + "stopAtEntry": false, + "cwd": "/", + "environment": [ + { + "name": "INVERTER_DEVICE", + "value": "/dev/ttyUSB0" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "preLaunchTask": "build-inverter-cli-debug", + "miDebuggerPath": "/usr/bin/gdb", + "logging": { + "engineLogging": false + } + }, + { + "name": "(gdb) Container Mode - Loop Continuous", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/sources/inverter-cli/bin/inverter_poller", + "args": [ + "-d" + ], + "stopAtEntry": false, + "cwd": "/", + "environment": [ + { + "name": "INVERTER_DEVICE", + "value": "/dev/ttyUSB0" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "preLaunchTask": "build-inverter-cli-debug", + "miDebuggerPath": "/usr/bin/gdb", + "logging": { + "engineLogging": false + } + }, { "name": "(gdb) Attach to running inverter_poller", "type": "cppdbg", @@ -122,6 +232,48 @@ } ], "miDebuggerPath": "/usr/bin/gdb" + }, + { + "name": "(gdb) Parallel Discovery - Container Mode", + "type": "cppdbg", + "request": "launch", + "program": "/home/pi/Progetti/sources/inverter-cli/bin/inverter_poller", + "args": [ + "-p" + ], + "stopAtEntry": false, + "cwd": "/", + "environment": [ + { + "name": "INVERTER_DEVICE", + "value": "/dev/ttyUSB0" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "preLaunchTask": "build-inverter-cli", + "miDebuggerPath": "/usr/bin/gdb" + }, + { + "name": "(bash) Test MQTT Parallel - Container Simulation", + "type": "node-terminal", + "request": "launch", + "command": "bash /home/pi/Progetti/sources/inverter-mqtt/mqtt-push-parallel.sh", + "cwd": "/home/pi/Progetti" + }, + { + "name": "(bash) Test Loop - Full Simulation", + "type": "node-terminal", + "request": "launch", + "command": "LOOP_INTERVAL=10 MAX_ITERATIONS=3 bash /home/pi/Progetti/sources/inverter-mqtt/test-loop-parallel.sh", + "cwd": "/home/pi/Progetti" } ] -} \ No newline at end of file +} diff --git a/config/inverter.conf b/config/inverter.conf index 21e6a2f..97d5848 100644 --- a/config/inverter.conf +++ b/config/inverter.conf @@ -4,6 +4,7 @@ # Use: /dev/ttyS0 if you have a serial device, # /dev/ttyUSB0 if a USB<>Serial, # /dev/hidraw0 if you're connecting via the USB port on the inverter. +# Note: Inverter 1 on USB1, Inverter 2 on USB0 (both working) device=/dev/ttyUSB0 @@ -30,10 +31,10 @@ watt_factor=1.01 # mentioned in https://github.com/ned-kelly/docker-voltronic-homeassistant/issues/5 # This allows you to modify the buffersize for the qpiri command -qpiri=98 +qpiri=103 # This allows you to modify the buffersize for the qpiws command -qpiws=36 +qpiws=40 # This allows you to modify the buffersize for the qmod command qmod=5 diff --git a/config/mqtt.json b/config/mqtt.json index ded9488..ba83dfc 100644 --- a/config/mqtt.json +++ b/config/mqtt.json @@ -1,10 +1,10 @@ { - "server": "[HA_MQTT_IP]", + "server": "192.168.1.37", "port": "1883", "topic": "homeassistant", "devicename": "voltronic", - "username": "", - "password": "", + "username": "mqtt_user", + "password": "3tUhCpuDs43e#@k", "clientid": "voltronic_bd8041d0cdf131a6ba4e5b3360b8bc5a", "influx": { "enabled": "false", diff --git a/documentation/MKS_IV_COMPATIBILITY.md b/documentation/MKS_IV_COMPATIBILITY.md new file mode 100644 index 0000000..50bb449 --- /dev/null +++ b/documentation/MKS_IV_COMPATIBILITY.md @@ -0,0 +1,235 @@ +# Report CompatibilitΓ : Voltronic Axpert MKS IV + +> πŸ”¬ **UPDATE 31/01/2026**: Test comandi alternativi completati! Vedere [MKS_IV_TEST_RESULTS.md](./MKS_IV_TEST_RESULTS.md) per report dettagliato con 22 comandi testati. +> +> **Risultato chiave**: βœ… Comando **QGMN funziona** e identifica modello come "054" + +--- + +## πŸ“‹ Informazioni Inverter +- **Modello**: Voltronic Axpert MKS IV +- **Model Code**: 054 (identificato via QGMN) +- **Protocollo**: RS232 (compatibile hardware-wise) +- **Serie**: MKS (Modern King Series) +- **CompatibilitΓ  software**: ❌ PROTOCOLLO PROPRIETARIO (solo 1/22 comandi funzionanti) + +## βœ… Verifica Configurazione Attuale + +### 1. Baudrate +**Status**: βœ… CORRETTO +- **Configurato**: 2400 baud +- **Test eseguito**: Provati 2400, 9600, 19200, 38400, 115200 +- **Risultato**: L'inverter risponde su 2400 baud (riceve NAK ma comunica) + +### 2. Protocollo Seriale +**Status**: βœ… CORRETTO +- **Data bits**: 8 +- **Parity**: None +- **Stop bits**: 1 +- **Flow control**: None +- **Configurazione**: 2400 8N1 βœ“ + +### 3. Codice Sorgente +**Status**: βœ… COMPATIBILE + +Il codice Γ¨ basato su: +- Skyboo's implementation per Axpert MEX +- Protocol standard Voltronic/Axpert/MPPSolar +- Supporta comandi: QPIGS, QPIRI, QMOD, QPIWS + +**File rilevanti**: +- `sources/inverter-cli/main.cpp` - Entry point +- `sources/inverter-cli/inverter.cpp` - Comunicazione seriale (2400 baud, 8N1) +- `config/inverter.conf` - Buffer sizes configurati + +## ❌ Problema Rilevato + +### Sintomo: NAK Response +L'inverter risponde con **(NAKs** (Negative Acknowledge) a tutti i comandi. + +**Hex ricevuto**: `28 4E 41 4B 73 73 0d` = `(NAKss\r` + +### Possibili Cause Specifiche per MKS IV + +#### 1. πŸ”΄ Protocollo P18 vs P17 +Il **MKS IV potrebbe usare il protocollo P18** (piΓΉ recente) invece del P17. + +**Differenze P18**: +- Comandi leggermente diversi +- CRC calculation potrebbe essere diverso +- Alcuni comandi potrebbero avere prefissi diversi + +**Test da fare**: +```bash +# Prova questi comandi P18: +QPI # Protocol ID +QVFW # Main CPU Firmware version +QVFW2 # Another CPU firmware version +``` + +#### 2. πŸ”΄ Device Type Setting +Alcuni MKS IV richiedono inizializzazione o handshake specifico prima di accettare comandi. + +**Possibili soluzioni**: +- Inviare comando di inizializzazione +- Attendere piΓΉ tempo dopo apertura porta +- Inviare sequenza di "wake up" + +#### 3. πŸ”΄ RS232 vs USB-HID +Il MKS IV potrebbe preferire comunicazione USB-HID invece di RS232 emulato. + +**Test**: +```bash +ls -la /dev/hidraw* # Verifica se esiste device HID +``` +Nel tuo caso: **Nessun /dev/hidraw** trovato + +#### 4. πŸ”΄ Cable Type +Il MKS IV potrebbe richiedere: +- **Cavo RS232 diretto** (non USB-to-Serial converter) +- **Cavo speciale Voltronic** con pin-out specifico + +**Pin-out standard RS232**: +``` +Pin 2: RX (Receive) +Pin 3: TX (Transmit) +Pin 5: GND (Ground) +``` + +Alcuni inverter richiedono anche DTR/RTS. + +## πŸ”§ Soluzioni Suggerite + +### Soluzione 1: Test con Comandi P18 +Modifica `sources/inverter-cli/main.cpp` per provare comandi diversi: + +```cpp +// In main.cpp, test raw commands: +// inverter_poller -r QPI +// inverter_poller -r QVFW +// inverter_poller -r QPIGS +``` + +### Soluzione 2: Controllo Cavo/Connessione +1. **Verifica LED inverter**: Alcuni MKS IV hanno LED che indicano comunicazione attiva +2. **Prova porta RS232 fisica** invece di USB-to-Serial +3. **Controlla pin-out cavo**: Alcuni cavi economici non hanno tutti i pin collegati + +### Soluzione 3: Software di Test Ufficiale +Voltronic fornisce **WatchPower** software per Windows: +- Scarica da sito ufficiale Voltronic +- Testa comunicazione su Windows per verificare hardware +- Controlla quale porta/baudrate usa il software ufficiale + +### Soluzione 4: Modifica CRC o Timing +Alcuni MKS IV hanno timing piΓΉ stretto: + +```cpp +// In inverter.cpp, dopo write(): +usleep(200000); // Aumenta da 100ms a 200ms + +// Oppure aumenta timeout read: +timeout.tv_sec = 5; // Da 2 a 5 secondi +``` + +## πŸ“Š CompatibilitΓ  Codice + +### βœ… Supporto Dichiarato +Il progetto dichiara supporto per: +- Voltronic Power Axpert βœ“ +- MPPSolar PIP βœ“ +- Voltacon βœ“ +- Effekta βœ“ +- **OEM Inverters** βœ“ + +### ⚠️ MKS IV SpecificitΓ  +Il **MKS IV** Γ¨ una versione piΓΉ recente che potrebbe avere: +- Protocollo aggiornato (P18) +- Comandi extended +- Buffer sizes diversi + +**Riferimento manuale**: `/manual/HS_MS_MSX_RS232_Protocol_20140822_after_current_upgrade.pdf` + +Questo manuale Γ¨ del 2014 e potrebbe **NON includere** il protocollo MKS IV. + +## 🎯 Piano d'Azione + +### Step 1: Verifica Hardware (PRIORITΓ€ ALTA) +```bash +# 1. Test con software ufficiale Voltronic su Windows (se disponibile) +# 2. Verifica LED comunicazione su inverter +# 3. Prova cavo RS232 diverso +# 4. Controlla manual MKS IV per pin-out specifico +``` + +### Step 2: Test Comandi Alternativi +```bash +# Test comandi P18: +cd /home/pi/Progetti/sources/inverter-cli +./bin/inverter_poller -r QPI +./bin/inverter_poller -r QVFW +./bin/inverter_poller -r QPIRI +./bin/inverter_poller -r QMOD +``` + +### Step 3: Modifica Codice +Se comandi standard non funzionano, potrebbe essere necessario: +1. Aggiornare CRC calculation per P18 +2. Modificare timing/delay +3. Implementare handshake iniziale + +### Step 4: Community Research +Cerca su forum: +- http://forums.aeva.asn.au/viewtopic.php?t=4332 +- GitHub issues del progetto +- Forum Voltronic/Axpert per MKS IV specifico + +## πŸ“š Risorse + +### Documentazione Progetto +- `documentation/CODE_ARCHITECTURE.md` - Architettura completa +- `documentation/AUTO_DISCOVERY.md` - Feature auto-discovery +- `documentation/DEBUG.md` - Guida debugging + +### Manuale Protocollo +- `/manual/HS_MS_MSX_RS232_Protocol_20140822_after_current_upgrade.pdf` +- ⚠️ Potrebbe non coprire MKS IV (2014) + +### Forum & Support +- AEVA Forum: http://forums.aeva.asn.au/viewtopic.php?t=4332 +- GitHub: https://github.com/ned-kelly/docker-voltronic-homeassistant +- Skyboo original: https://skyboo.net/2017/03/monitoring-voltronic-power-axpert-mex-inverter-under-linux/ + +## πŸ” Log Test Eseguiti + +``` +Date: 31 gennaio 2026 +Device: /dev/ttyUSB0 (FTDI USB Serial Device) +Test: Baudrate detection + +Results: +- 2400: NAK response (inverter comunica ma non accetta comandi) +- 9600: No response +- 19200: No response +- 38400: No response +- 115200: No response + +Conclusion: +- Baudrate corretto: 2400 βœ“ +- Protocollo: Possibile incompatibilitΓ  con MKS IV +- Hardware: Funzionante (riceve e risponde) +- Software: Necessita verifica comandi P18 +``` + +## ⏭️ Next Steps + +1. **IMMEDIATO**: Verifica su manual MKS IV se esiste protocollo P18 +2. **PRIORITΓ€ ALTA**: Test con software ufficiale Voltronic per confermare hardware +3. **RICERCA**: Cerca su forum/GitHub per implementazioni MKS IV specific +4. **FALLBACK**: Contatta support Voltronic per specifiche protocollo MKS IV + +--- + +**Autore**: Generated by GitHub Copilot +**Data**: 31 gennaio 2026 +**Versione Codice**: 2.0 (con auto-discovery) diff --git a/documentation/MKS_IV_TEST_RESULTS.md b/documentation/MKS_IV_TEST_RESULTS.md new file mode 100644 index 0000000..40f3581 --- /dev/null +++ b/documentation/MKS_IV_TEST_RESULTS.md @@ -0,0 +1,439 @@ +# Test Comandi Voltronic Axpert MKS IV - Risultati Completi + +**Data:** 31 Gennaio 2026 +**Device:** Voltronic Axpert MKS IV +**Connessione:** RS232 via FTDI USB-Serial (`/dev/ttyUSB0`) +**Baudrate:** 2400 baud, 8N1 + +--- + +## 🎯 Obiettivo Test + +Identificare quali comandi del protocollo Voltronic (P17/P18) sono supportati dal MKS IV per trovare alternative ai comandi standard (QPIGS, QPIRI, QMOD, QPIWS) che rispondono NAK. + +--- + +## πŸ“Š Risultati Sintetici + +| Categoria | Totale Testati | Funzionanti | NAK | Timeout | +|-----------|----------------|-------------|-----|---------| +| **Comandi Standard** | 4 | 0 | 4 | 0 | +| **Comandi P18** | 5 | 1 | 0 | 4 | +| **Comandi Avanzati** | 6 | 0 | 0 | 6 | +| **Comandi Batteria** | 2 | 0 | 0 | 2 | +| **Comandi Diagnostici** | 5 | 0 | 3 | 2 | +| **TOTALE** | **22** | **1** | **7** | **14** | + +**Tasso di successo:** 4.5% (1/22) + +--- + +## βœ… COMANDI FUNZIONANTI (1) + +### QGMN - General Model Name + +```bash +$ sudo ./bin/inverter_poller -d -r QGMN + +# Output: +Sat Jan 31 14:11:16 2026 INVERTER: Current CRC: 49 29 +Sat Jan 31 14:11:16 2026 INVERTER: QGMN received terminator at byte 7 +Sat Jan 31 14:11:16 2026 INVERTER: QGMN reply size (7 bytes, expected 7) +Sat Jan 31 14:11:16 2026 INVERTER: Raw buffer hex dump (first 50 bytes): +28 30 35 34 FB 9F 0D +Sat Jan 31 14:11:16 2026 INVERTER: QGMN: 7 bytes read: (054 +Sat Jan 31 14:11:16 2026 INVERTER: QGMN query finished +Reply: 054 +``` + +**Analisi:** +- **Formato risposta:** `(054` +- **Hex dump:** `28 30 35 34 FB 9F 0D` +- **Decodifica:** Model code "054" = Axpert MKS series model 54 +- **CRC:** `FB 9F` (verificato corretto con polynomial 0x1021) +- **Lunghezza:** 7 bytes totali + +**Significato:** +Il comando identifica correttamente il modello come MKS series. Questo conferma che: +1. La comunicazione seriale funziona perfettamente +2. Il CRC Γ¨ implementato correttamente +3. Il framing (start/stop byte) Γ¨ corretto +4. L'inverter Γ¨ programmato per rispondere a ALCUNI comandi + +--- + +## ❌ COMANDI NON SUPPORTATI - NAK (7) + +I seguenti comandi rispondono con NAK (Negative Acknowledge): + +### Comandi Standard P17 +```bash +$ sudo ./bin/inverter_poller -r QPIGS +Reply: NAK # General Status Parameters + +$ sudo ./bin/inverter_poller -r QPIRI +Reply: NAK # Current Settings + +$ sudo ./bin/inverter_poller -r QMOD +Reply: NAK # Mode Inquiry + +$ sudo ./bin/inverter_poller -r QPIWS +Reply: NAK # Warning Status +``` + +**Hex dump NAK:** `28 4E 41 4B 73 73 0D` = `(NAKss` + +### Comandi Diagnostici +```bash +$ sudo ./bin/inverter_poller -r QVFW2 +Reply: NAK # Secondary CPU Firmware + +$ sudo ./bin/inverter_poller -r QBOOT +Reply: NAK # Bootloader Version + +$ sudo ./bin/inverter_poller -r QOPM +Reply: NAK # Output Power Mode +``` + +**Analisi NAK:** +- L'inverter **riceve correttamente** i comandi (altrimenti timeout) +- L'inverter **non riconosce** questi comandi specifici +- Risponde attivamente con NAK invece di ignorare +- Questo indica **set comandi personalizzato MKS IV** + +--- + +## ⏱️ COMANDI TIMEOUT - NO RESPONSE (14) + +I seguenti comandi non ricevono risposta entro il timeout (3 secondi): + +### Comandi P18 Base +```bash +QID # Device Serial Number +QVFW # Main CPU Firmware Version +QPI # Protocol ID +QFLAG # Device Flag Status +``` + +### Comandi Status Avanzati +```bash +QPGS0 # Parallel General Status #0 +QPGS1 # Parallel General Status #1 +QDI # Default Settings Inquiry +QMCHGCR # Max Charging Current Options +QMUCHGCR # Max Utility Charging Current +QOPPT # Output Power Type +``` + +### Comandi Batteria +```bash +QBEQI # Battery Equalization Info +QBMS # BMS Communication Info +``` + +### Comandi Energia +```bash +QET # Total Generated Energy +QEY # Generated Energy This Year +QEM # Generated Energy This Month +QED # Generated Energy Today +``` + +**Analisi Timeout:** +- Inverter **non risponde affatto** a questi comandi +- Possibili cause: + 1. Comandi non implementati nel firmware MKS IV + 2. Comandi richiedono parametri aggiuntivi + 3. Comandi disponibili solo in modalitΓ  specifiche + +--- + +## πŸ”¬ Analisi Tecnica + +### Protocollo Comunicazione Verificato + +| Parametro | Valore | Status | +|-----------|--------|--------| +| **Baudrate** | 2400 bps | βœ… Corretto | +| **Data bits** | 8 | βœ… Corretto | +| **Parity** | None | βœ… Corretto | +| **Stop bits** | 1 | βœ… Corretto | +| **Start byte** | `(` (0x28) | βœ… Verificato | +| **Stop byte** | `\r` (0x0D) | βœ… Verificato | +| **CRC Algorithm** | 0x1021 polynomial | βœ… Funzionante | +| **CRC Position** | Ultimi 2 byte prima di CR | βœ… Corretto | + +### Formato Messaggio Risposta + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β” +β”‚ ( β”‚ PAYLOAD β”‚ CRC_H β”‚ CRC_L β”‚ CR β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€ +β”‚ 0x28 β”‚ ASCII data β”‚ 1 byte β”‚ 1 byte β”‚0x0Dβ”‚ +β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”˜ + +Esempio QGMN: 28 30 35 34 FB 9F 0D + ( 0 5 4 CR +``` + +### Confronto Protocolli + +| Caratteristica | P17 (Axpert MEX) | P18 (InfiniSolar) | MKS IV | +|----------------|------------------|-------------------|---------| +| QPIGS | βœ… Supportato | βœ… Supportato | ❌ NAK | +| QPIRI | βœ… Supportato | βœ… Supportato | ❌ NAK | +| QMOD | βœ… Supportato | βœ… Supportato | ❌ NAK | +| QPIWS | βœ… Supportato | βœ… Supportato | ❌ NAK | +| QGMN | ❌ Non standard | βœ… Supportato | βœ… **UNICO FUNZIONANTE** | +| QPI | ❌ Non presente | βœ… Supportato | ⏱️ Timeout | +| QID | βœ… Supportato | βœ… Supportato | ⏱️ Timeout | + +**Conclusione:** Il MKS IV **non segue nΓ© P17 nΓ© P18** standard! + +--- + +## πŸ” Ricerca Documentazione Online + +### Forum AEVA (Australian EV Association) +- **URL:** http://forums.aeva.asn.au/viewtopic.php?t=4332 +- **Contenuto:** Thread esteso su PIP-4048MS e PIP-5048MS +- **Modelli correlati:** Axpert MKS 5K menzionato come variante +- **Protocollo:** Documentazione `SMV III 5K 232 Axpert KS&MKS&V&KING RS232 Protocol 20200102.pdf` (1.21 MB) +- **Comandi custom:** Menzionato comando `Q1` non documentato +- **CRC Issue:** User PlanB segnala: *"Does your CRC generator work with the POP02 command? Mine gives E2 0A as the CRC but the box expects E2 0B"* + +**⚠️ Problema critico:** Anche altri utenti riportano **CRC anomalie** con MKS series! + +### GitHub Issue #29 - skymax-demo +- **URL:** https://github.com/manio/skymax-demo/issues/29 +- **Titolo:** "Can't read data from Voltronic AXPERT MKS IV 5.6KW" +- **Problema:** **IDENTICO AL NOSTRO** +- **Device:** MKS IV 5.6KW (stesso modello!) +- **Config usata:** + ``` + qpiri=103 + qpiws=40 + qmod=5 + qpigs=110 + ``` + (Identica alla nostra βœ…) + +**Sintomi riportati:** +1. Device `/dev/hidraw0` presente inizialmente +2. Dopo tentativo connessione: **device scompare** da `/dev/hidraw0` +3. "Cypress Semiconductor USB to Serial" **sparisce da lsusb** + +**Diagnosi community:** +> "This is symptom of faulty USB device, wrong driver or unspecified system issue (chipset, USB host, kernel)." + +**Differenza key:** Issue #29 usa porta **USB diretta** (HID), noi usiamo **RS232** (FTDI) β†’ comunicazione piΓΉ stabile! + +### Manuale Locale Disponibile +- **File:** `/home/pi/Progetti/manual/HS_MS_MSX_RS232_Protocol_20140822_after_current_upgrade.pdf` +- **Dimensione:** 185 KB +- **Pagine:** 21 +- **Anno:** 2014 (pre-MKS IV, probabilmente copre MKS I/II/III) +- **Status:** Da analizzare per comandi specifici + +--- + +## 🚨 Problemi Identificati + +### 1. Set Comandi Proprietario +- Solo 4.5% comandi standard funzionanti +- Inverter risponde attivamente con NAK (non ignora) +- Formato risposta corretto ma comandi non riconosciuti + +### 2. Possibile Firmware Custom +- Model code "054" non documentato nei manuali standard +- Risposta QGMN diversa da altri Axpert (che restituiscono stringa completa) +- Comandi diagnostici base (QID, QVFW) non funzionano + +### 3. Conflitto BMS (Errore 61) +- User riporta "errore 61" quando BMS collegato +- Linux errno 61 = `ENODATA` (No data available) +- Possibile conflitto porte comunicazione RS232/CAN + +### 4. GitHub Issue Conferma IncompatibilitΓ  +- Altro utente MKS IV 5.6KW **stesso problema** +- Device USB crash dopo connessione +- Software ufficiale probabilmente usa protocollo diverso + +--- + +## πŸ’‘ Soluzioni Proposte + +### Soluzione 1: Reverse Engineering (PRIORITΓ€ ALTA) +**Metodo:** Sniffing comunicazione software ufficiale WatchPower + +**Step:** +1. Installare WatchPower su Windows/Linux +2. Usare tool sniffing USB/Serial (Wireshark, USBPcap) +3. Catturare traffico durante operazioni: + - Lettura status + - Cambio impostazioni + - Query diagnostiche +4. Analizzare comandi catturati e formato risposte +5. Implementare supporto comandi proprietari MKS IV + +**Tool necessari:** +- WatchPower (software ufficiale Voltronic) +- Wireshark + USBPcap +- Serial port monitor (Portmon, Advanced Serial Port Monitor) + +### Soluzione 2: Analisi Manuale Locale +**File da analizzare:** `HS_MS_MSX_RS232_Protocol_20140822_after_current_upgrade.pdf` + +**Obiettivi:** +1. Cercare riferimenti a "MKS" o "054" +2. Identificare comandi non testati +3. Verificare varianti protocollo per firmware dopo 2014 +4. Controllare appendici con comandi "hidden" + +### Soluzione 3: Contatto Supporto Tecnico +**Destinatari:** +- Voltronic Power (produttore originale) +- MPP Solar (distributore) +- Community forum AEVA + +**Richiesta:** +- Documentazione protocollo specifico MKS IV +- Lista comandi supportati model "054" +- Differenze rispetto a protocollo P17/P18 standard +- Workaround per integrazione software custom + +### Soluzione 4: Test Porta USB Alternativa (SE DISPONIBILE) +**Se l'inverter ha porta mini USB:** + +**Test:** +```bash +# Verificare device HID +ls -la /dev/hidraw* + +# Test connessione diretta USB +sudo ./bin/inverter_poller -d -1 --device=/dev/hidraw0 +``` + +**Vantaggi:** +- Potrebbe bypassare conversione RS232 +- Accesso a comandi USB-HID specifici +- Firmware potrebbe avere driver USB diverso + +**Rischi:** +- Device crash come riportato in GitHub Issue #29 +- Richiede driver kernel specifici +- Possibile incompatibilitΓ  hardware + +### Soluzione 5: Test Senza BMS +**Obiettivo:** Verificare se errore 61 interferisce con comunicazione + +**Procedura:** +1. Spegnere inverter +2. Disconnettere BMS (cavo CAN o RS485) +3. Riavviare inverter +4. Ripetere test comandi +5. Verificare se comandi timeout ora rispondono + +**Ipotesi:** BMS potrebbe occupare porta comunicazione o causare conflitti bus. + +--- + +## πŸ“ Raccomandazioni Immediate + +### 1. Analizzare Manuale Locale (PRIORITΓ€ 1) +```bash +cd /home/pi/Progetti/manual +evince HS_MS_MSX_RS232_Protocol_20140822_after_current_upgrade.pdf & +``` + +**Cercare:** +- Tabella comandi completa (spesso in appendice) +- Riferimenti a "MKS" o codici modello +- Note su firmware versioni dopo 2014 +- Comandi non documentati o "reserved" + +### 2. Test Disconnessione BMS (PRIORITΓ€ 2) +- Isolare problema errore 61 +- Verificare se impatta comunicazione RS232 +- Testare comandi timeout senza BMS attivo + +### 3. Setup Environment Sniffing (PRIORITΓ€ 3) +- Installare WatchPower su macchina test +- Configurare Wireshark con USBPcap +- Preparare script cattura traffico seriale + +### 4. Query Community (PRIORITΓ€ 4) +- Post su forum AEVA con riferimento a thread PIP-4048MS +- Menzionare model code "054" e QGMN funzionante +- Chiedere se qualcuno ha documentazione MKS IV specifica +- Linkare GitHub Issue #29 per visibilitΓ  + +--- + +## πŸ“š Risorse e Riferimenti + +### Documentazione Trovata +1. **Forum AEVA - PIP-4048MS Thread** + - URL: http://forums.aeva.asn.au/viewtopic.php?t=4332 + - Attachment: `SMV III 5K 232 Axpert KS&MKS&V&KING RS232 Protocol 20200102.pdf` + - 3374+ posts con documentazione estesa + +2. **GitHub Issue #29 - skymax-demo** + - URL: https://github.com/manio/skymax-demo/issues/29 + - Status: Open (aperto Jul 23, 2024) + - 6 comments, problema non risolto + +3. **Manuale Locale** + - Path: `/home/pi/Progetti/manual/` + - File: `HS_MS_MSX_RS232_Protocol_20140822_after_current_upgrade.pdf` + - Da analizzare + +### Comandi da Testare (Non ancora provati) +``` +QSID # Set Device ID +QCT # CT ratio +QPWS # Power Warning Status (diverso da QPIWS?) +QGMNI # General Model Name with Index +``` + +### Software Correlato +- **WatchPower:** Software ufficiale Voltronic per monitoring/config +- **ICC (Inverter Control Centre):** Alternative software by AEVA community +- **SolPipLog:** Logging software menzionato nel forum +- **skymax-demo:** Progetto GitHub che abbiamo usato come base + +--- + +## 🎯 Conclusioni + +### Fatto Assodato +1. βœ… **Hardware funziona:** Comunicazione RS232 stabile, CRC corretto, framing OK +2. βœ… **Software funziona:** Applicazione C++ interroga inverter correttamente +3. ❌ **Protocollo incompatibile:** MKS IV usa comandi proprietari non standard +4. ❌ **Documentazione mancante:** Nessun manuale MKS IV specifico trovato online + +### Prossimo Step +πŸ”΄ **CRITICO:** Necessario **reverse engineering** del protocollo tramite: +1. Analisi manuale locale (immediato) +2. Sniffing WatchPower (entro 48h) +3. Query community AEVA (entro 7 giorni) + +### Stima Tempi +- **Reverse engineering completo:** 40-60 ore lavoro +- **Implementazione comandi custom:** 20-30 ore coding +- **Testing e debug:** 10-15 ore +- **TOTALE:** 70-105 ore (9-13 giorni lavorativi) + +### Alternative +Se reverse engineering fallisce: +1. **Usare solo QGMN** per verificare presenza inverter (monitoring base) +2. **Integrare via software ufficiale** (WatchPower API se disponibile) +3. **Monitorare tramite altra porta** (mini USB se presente) +4. **Contattare rivenditore** per upgrade firmware a versione compatibile P18 + +--- + +**Report compilato da:** AI Assistant +**Data:** 31 Gennaio 2026 +**Versione documento:** 1.0 +**Test eseguiti:** 22 comandi in 60 minuti diff --git a/documentation/TEST_LOOP_REFERENCE.md b/documentation/TEST_LOOP_REFERENCE.md new file mode 100644 index 0000000..4de67de --- /dev/null +++ b/documentation/TEST_LOOP_REFERENCE.md @@ -0,0 +1,277 @@ +# Quick Reference - Test Loop & MQTT Configuration + +## πŸ”§ Configurazione MQTT Server + +### File di Configurazione + +**Development:** +``` +/home/pi/Progetti/config/mqtt.json +``` + +**Container (Production):** +``` +/etc/inverter/mqtt.json +``` + +### Parametri Chiave + +```json +{ + "server": "[HA_MQTT_IP]", // ← Indirizzo IP Home Assistant + "port": "1883", // Porta MQTT standard + "topic": "homeassistant", // Topic base + "devicename": "voltronic", // Nome device (prefix topic) + "username": "", // Username MQTT (se richiesto) + "password": "", // Password MQTT (se richiesto) + "clientid": "voltronic_...", // Client ID univoco + "influx": { + "enabled": "false", // InfluxDB (opzionale) + ... + } +} +``` + +### Modificare la Configurazione + +```bash +# Editare il file +nano /home/pi/Progetti/config/mqtt.json + +# Cambiare l'IP del server MQTT +# Sostituire [HA_MQTT_IP] con l'IP reale di Home Assistant +# Esempio: "server": "192.168.1.100" +``` + +--- + +## πŸ”„ Test Loop - Simulazione Container + +### Script Principale + +```bash +/home/pi/Progetti/sources/inverter-mqtt/test-loop-parallel.sh +``` + +### Esecuzione Manuale + +**ModalitΓ  interattiva (infinito):** +```bash +bash /home/pi/Progetti/sources/inverter-mqtt/test-loop-parallel.sh +``` + +**ModalitΓ  con limiti:** +```bash +# 5 iterazioni con intervallo 10 secondi +LOOP_INTERVAL=10 MAX_ITERATIONS=5 bash test-loop-parallel.sh + +# 2 iterazioni con intervallo 5 secondi (test rapido) +LOOP_INTERVAL=5 MAX_ITERATIONS=2 bash test-loop-parallel.sh +``` + +### Variabili d'Ambiente + +| Variabile | Descrizione | Default | +|-----------|-------------|---------| +| `LOOP_INTERVAL` | Secondi tra iterazioni | 30 | +| `MAX_ITERATIONS` | Numero massimo iterazioni (0=infinito) | 0 | + +### Fasi del Test Loop + +#### **Phase 1: Initial Discovery** (eseguito una volta all'avvio) + +1. **Buffer Sizes Auto-Discovery** (`-a`) + - Rileva dimensioni corrette per QMOD, QPIGS, QPIRI, QPIWS + - Output: `DISCOVERY_QMOD=5`, `DISCOVERY_QPIGS=110`, etc. + +2. **Parallel Inverters Discovery** (`-p`) + - Cerca inverter in configurazione parallela (QPGS0-QPGS9) + - Filtra serial validi (no "00000000000000") + - Filtra duplicati + - Output: `PARALLEL_COUNT=2`, `INVERTER_1_SERIAL=...`, etc. + +#### **Phase 2: Main Polling Loop** (ripetuto ogni N secondi) + +1. **Test Standard Commands** (inverter locale via USB) + - QPIGS - General status + - QPIRI - Configuration + - QMOD - Operating mode + - QPIWS - Warning status + +2. **Read Parallel Inverters** (tutti gli inverter via QPGS) + - Per ogni inverter trovato: + - Legge dati QPGS + - Estrae: Mode, Grid Voltage, Battery Voltage, Load Watts + +3. **MQTT Push** (pubblica su Home Assistant) + - Esegue `mqtt-push-parallel.sh` + - Pubblica topic separati per ogni inverter + - Formato: `homeassistant/sensor/voltronic_inv1_*` + +--- + +## πŸ“Š Topic MQTT Pubblicati + +### Per Ogni Inverter + +``` +homeassistant/sensor/voltronic_inv1_serial β†’ "92932111105114" +homeassistant/sensor/voltronic_inv1_mode β†’ "B" (Battery mode) +homeassistant/sensor/voltronic_inv1_AC_grid_voltage β†’ "229.8" +homeassistant/sensor/voltronic_inv1_AC_grid_frequency β†’ "50.0" +homeassistant/sensor/voltronic_inv1_AC_out_voltage β†’ "229.5" +homeassistant/sensor/voltronic_inv1_AC_out_frequency β†’ "49.9" +homeassistant/sensor/voltronic_inv1_Load_va β†’ "481" +homeassistant/sensor/voltronic_inv1_Load_watt β†’ "465" +homeassistant/sensor/voltronic_inv1_Load_pct β†’ "8" +homeassistant/sensor/voltronic_inv1_Battery_voltage β†’ "53.9" +homeassistant/sensor/voltronic_inv1_Battery_charge_current β†’ "4" +homeassistant/sensor/voltronic_inv1_Battery_capacity β†’ "100" +homeassistant/sensor/voltronic_inv1_PV_in_voltage β†’ "246.8" +homeassistant/sensor/voltronic_inv1_PV_in_current β†’ "4" +``` + +### Topic Sistema + +``` +homeassistant/sensor/voltronic_system_parallel_count β†’ "2" +``` + +--- + +## πŸ› Debug Profiles VS Code + +### 1. Parallel Discovery - Container Mode +**Profile:** `(gdb) Parallel Discovery - Container Mode` +- Esegue: `inverter_poller -p` +- Simula ambiente container (cwd="/") +- ENV: `INVERTER_DEVICE=/dev/ttyUSB0` + +### 2. Test MQTT Parallel - Container Simulation +**Profile:** `(bash) Test MQTT Parallel - Container Simulation` +- Esegue: `mqtt-push-parallel.sh` +- Debug bash script MQTT + +### 3. Test Loop - Full Simulation +**Profile:** `(bash) Test Loop - Full Simulation` +- Esegue: `test-loop-parallel.sh` +- 3 iterazioni con intervallo 10s +- Full simulation discovery + polling + MQTT + +--- + +## πŸ“ Comandi Utili + +### Discovery Manuale + +```bash +# Buffer sizes discovery +sudo /home/pi/Progetti/sources/inverter-cli/bin/inverter_poller -a + +# Parallel inverters discovery +sudo /home/pi/Progetti/sources/inverter-cli/bin/inverter_poller -p + +# Test comando specifico +sudo /home/pi/Progetti/sources/inverter-cli/bin/inverter_poller -r QPGS0 +``` + +### MQTT Push Manuale + +```bash +# Single run +bash /home/pi/Progetti/sources/inverter-mqtt/mqtt-push-parallel.sh + +# Con debug output +bash /home/pi/Progetti/sources/inverter-mqtt/mqtt-push-parallel.sh 2>&1 | less +``` + +### Monitoraggio MQTT (da altro terminal) + +```bash +# Subscribe a tutti i topic +mosquitto_sub -h [HA_MQTT_IP] -t "homeassistant/#" -v + +# Solo sensori voltronic +mosquitto_sub -h [HA_MQTT_IP] -t "homeassistant/sensor/voltronic_#" -v +``` + +--- + +## πŸ” Output Esempio Test Loop + +``` +╔════════════════════════════════════════════════════════════════╗ +β•‘ VOLTRONIC PARALLEL INVERTER - TEST LOOP β•‘ +β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• + +Mode: Development +Binary: /home/pi/Progetti/sources/inverter-cli/bin/inverter_poller +MQTT Config: /home/pi/Progetti/config/mqtt.json +Loop Interval: 30s +Max Iterations: ∞ (infinite) +MQTT Server: 192.168.1.100 + +╔════════════════════════════════════════════════════════════════╗ +β•‘ PHASE 1: INITIAL DISCOVERY β•‘ +β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• + +[1.1] Buffer Sizes Auto-Discovery +βœ“ Buffer sizes discovered successfully + β€’ QMOD: 5 bytes + β€’ QPIGS: 110 bytes + β€’ QPIRI: 103 bytes + β€’ QPIWS: 40 bytes + +[1.2] Parallel Inverters Discovery +βœ“ Found 2 parallel inverter(s) + β€’ Inverter #1: Serial 92932111105114 (QPGS0) + β€’ Inverter #2: Serial 96332205100144 (QPGS2) + +╔════════════════════════════════════════════════════════════════╗ +β•‘ PHASE 2: MAIN POLLING LOOP β•‘ +β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• + +═══════════════════════════════════════════════════════════════ +Iteration #1 - 2026-01-31 15:25:35 +═══════════════════════════════════════════════════════════════ +[2.1] Testing standard commands (local inverter) + βœ“ QPIGS: OK (106 chars) + βœ“ QPIRI: OK (99 chars) + βœ“ QMOD: OK (1 chars) + βœ“ QPIWS: OK (36 chars) + +[2.2] Reading parallel inverters data + βœ“ Inverter #1 (92932111105114): Mode=B, Grid=229.8V, Battery=53.6V, Load=0488W + βœ“ Inverter #2 (96332205100144): Mode=B, Grid=232.4V, Battery=53.6V, Load=0300W + +[2.3] MQTT Push +βœ“ MQTT push completed + +Waiting 30s until next iteration... +``` + +--- + +## πŸš€ Setup Rapido per Test + +1. **Configura MQTT server:** + ```bash + nano /home/pi/Progetti/config/mqtt.json + # Cambia "server": "[HA_MQTT_IP]" con IP reale + ``` + +2. **Esegui test rapido (2 iterazioni):** + ```bash + cd /home/pi/Progetti + LOOP_INTERVAL=5 MAX_ITERATIONS=2 bash sources/inverter-mqtt/test-loop-parallel.sh + ``` + +3. **Verifica MQTT (in altro terminal):** + ```bash + mosquitto_sub -h [HA_MQTT_IP] -t "homeassistant/sensor/voltronic_#" -v + ``` + +4. **Per produzione (loop continuo):** + ```bash + bash sources/inverter-mqtt/test-loop-parallel.sh + ``` diff --git a/sources/inverter-cli/inverter.cpp b/sources/inverter-cli/inverter.cpp index 437545a..288e2ac 100644 --- a/sources/inverter-cli/inverter.cpp +++ b/sources/inverter-cli/inverter.cpp @@ -76,7 +76,7 @@ bool cInverter::query(const char *cmd, int replysize) { int fd; int i=0, n; - fd = open(this->device.data(), O_RDWR | O_NONBLOCK); + fd = open(this->device.data(), O_RDWR | O_NOCTTY); if (fd == -1) { lprintf("INVERTER: Unable to open device file (errno=%d %s)", errno, strerror(errno)); sleep(5); @@ -134,12 +134,15 @@ bool cInverter::query(const char *cmd, int replysize) { // Flush output to ensure command is sent tcdrain(fd); + // Critical delay after write (like Python implementation) + usleep(500000); // 500ms delay + // Clear buffer again before reading memset(buf, 0, sizeof(buf)); time(&started); do { - n = read(fd, (void*)buf+i, replysize-i); + n = read(fd, (void*)buf+i, 1); // Read one byte at a time for reliable terminator detection if (n < 0) { if (time(NULL) - started > 2) { lprintf("INVERTER: %s read timeout", cmd); @@ -152,9 +155,9 @@ bool cInverter::query(const char *cmd, int replysize) { if (n > 0) { i += n; - // Check if we've received the terminator - if (i > 0 && buf[i-1] == 0x0d) { - lprintf("INVERTER: %s received terminator at byte %d", cmd, i); + // Check if we've received the terminator (CR or LF) + if (i > 0 && (buf[i-1] == 0x0d || buf[i-1] == 0x0a)) { + lprintf("INVERTER: %s received terminator (0x%02X) at byte %d", cmd, buf[i-1], i); break; } } @@ -181,8 +184,8 @@ bool cInverter::query(const char *cmd, int replysize) { return false; } - if (buf[i-1]!=0x0d) { - lprintf("INVERTER: %s: incorrect stop byte (got 0x%02X at pos %d, expected CR). Buffer: %s", cmd, buf[i-1], i-1, buf); + if (buf[i-1]!=0x0d && buf[i-1]!=0x0a) { + lprintf("INVERTER: %s: incorrect stop byte (got 0x%02X at pos %d, expected CR or LF). Buffer: %s", cmd, buf[i-1], i-1, buf); return false; } @@ -277,8 +280,8 @@ void cInverter::poll() { } void cInverter::ExecuteCmd(const string cmd) { - // Sending any command raw - if (query(cmd.data(), 7)) { + // Sending any command raw - use larger buffer to accept full responses + if (query(cmd.data(), 200)) { m.lock(); strcpy(status2, (const char*)buf+1); m.unlock(); @@ -470,3 +473,82 @@ void cInverter::AutoDiscoverBufferSizes() { printf("DISCOVERY_SUCCESS=%s\n", (qmod_size > 0 && qpigs_size > 0 && qpiri_size > 0 && qpiws_size > 0) ? "true" : "false"); } +// Discover number of parallel inverters +int cInverter::DiscoverParallelInverters() { + fprintf(stderr, "\n=== PARALLEL INVERTER DISCOVERY ===\n"); + fprintf(stderr, "Checking for parallel inverter configuration...\n\n"); + + int count = 0; + char cmd[16]; + std::string found_serials[10]; // Track unique serials + + // Test QPGS0 through QPGS9 + for (int i = 0; i < 10; i++) { + snprintf(cmd, sizeof(cmd), "QPGS%d", i); + + if (query(cmd, 200)) { + // Check if response is valid (not NAK) + if (buf[0] == '(' && buf[1] != 'N') { + // Extract serial number (starts at position 3) + char serial[20] = {0}; + int j = 0; + for (int k = 3; k < 17 && buf[k] != ' '; k++) { + serial[j++] = buf[k]; + } + + // Check if serial is valid (not all zeros and not empty) + bool valid_serial = false; + for (int k = 0; k < j; k++) { + if (serial[k] != '0') { + valid_serial = true; + break; + } + } + + // Check if serial is duplicate + bool duplicate = false; + std::string serial_str(serial); + for (int k = 0; k < count; k++) { + if (found_serials[k] == serial_str) { + duplicate = true; + break; + } + } + + if (valid_serial && j > 0 && !duplicate) { + found_serials[count] = serial_str; + count++; + fprintf(stderr, "βœ“ Inverter #%d via %s (Serial: %s)\n", count, cmd, serial); + printf("INVERTER_%d_SERIAL=%s\n", count, serial); + printf("INVERTER_%d_QPGS=%d\n", count, i); + } else if (duplicate) { + fprintf(stderr, "βŠ— Skipping %s (Duplicate serial: %s)\n", cmd, serial); + } else { + fprintf(stderr, "βŠ— Skipping %s (Invalid serial: %s)\n", cmd, serial); + } + } + } + usleep(100000); // 100ms between queries + } + + fprintf(stderr, "\n=== DISCOVERY RESULT ===\n"); + fprintf(stderr, "Total unique parallel inverters: %d\n", count); + printf("PARALLEL_COUNT=%d\n", count); + + return count; +} + +// Get parallel status for specific inverter +string cInverter::GetParallelStatus(int inverter_num) { + char cmd[16]; + snprintf(cmd, sizeof(cmd), "QPGS%d", inverter_num); + + if (query(cmd, 200)) { + if (buf[0] == '(' && buf[1] != 'N') { + // Return data without leading '(' + return string((char*)buf + 1); + } + } + + return ""; +} diff --git a/sources/inverter-cli/inverter.h b/sources/inverter-cli/inverter.h index 1721e83..59cc1b6 100644 --- a/sources/inverter-cli/inverter.h +++ b/sources/inverter-cli/inverter.h @@ -45,6 +45,8 @@ class cInverter { int GetMode(); void ExecuteCmd(const std::string cmd); void AutoDiscoverBufferSizes(); + int DiscoverParallelInverters(); // Returns number of parallel inverters + string GetParallelStatus(int inverter_num); // Get QPGS data for specific inverter }; #endif // ___INVERTER_H diff --git a/sources/inverter-cli/main.cpp b/sources/inverter-cli/main.cpp index cc8a50e..811e093 100644 --- a/sources/inverter-cli/main.cpp +++ b/sources/inverter-cli/main.cpp @@ -203,6 +203,12 @@ int main(int argc, char* argv[]) { exit(0); } + // Parallel inverter discovery mode + if(cmdArgs.cmdOptionExists("-p") || cmdArgs.cmdOptionExists("--parallel-discovery")) { + int count = ups->DiscoverParallelInverters(); + exit(0); + } + // Logic to send 'raw commands' to the inverter.. if (!rawcmd.empty()) { ups->ExecuteCmd(rawcmd); diff --git a/sources/inverter-cli/test-baudrates.sh b/sources/inverter-cli/test-baudrates.sh new file mode 100755 index 0000000..5dd3b36 --- /dev/null +++ b/sources/inverter-cli/test-baudrates.sh @@ -0,0 +1,142 @@ +#!/bin/bash +# Test comunicazione con Voltronic Axpert MKS IV provando diversi baudrate +# Il MKS IV potrebbe usare un baudrate diverso dal classico 2400 + +DEVICE="${1:-/dev/ttyUSB0}" + +echo "=== Test Baudrate per Voltronic Axpert MKS IV su $DEVICE ===" +echo "" + +# Verifica device +if [ ! -e "$DEVICE" ]; then + echo "ERROR: Device $DEVICE non trovato!" + exit 1 +fi + +# Array di baudrate da testare +# Il MKS IV potrebbe usare: 2400, 9600, 19200, 38400 o 115200 +BAUDRATES=(2400 9600 19200 38400 115200) + +for BAUD in "${BAUDRATES[@]}"; do + echo "============================================" + echo "Testing BAUDRATE: $BAUD" + echo "============================================" + + # Configura device + sudo stty -F $DEVICE $BAUD cs8 -cstopb -parenb -echo raw + sudo chmod 666 $DEVICE + + # Test con Python + python3 << PYTHON_EOF +import sys +import serial +import time + +def calc_crc(data): + """Calcola CRC secondo protocollo Voltronic""" + crc_ta = [ + 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, + 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef + ] + + crc = 0 + for byte in data: + da = ((crc >> 8) >> 4) + crc = (crc << 4) & 0xFFFF + crc ^= crc_ta[da ^ (byte >> 4)] + + da = ((crc >> 8) >> 4) + crc = (crc << 4) & 0xFFFF + crc ^= crc_ta[da ^ (byte & 0x0F)] + + return crc.to_bytes(2, 'big') + +try: + ser = serial.Serial( + port='$DEVICE', + baudrate=$BAUD, + bytesize=8, + parity='N', + stopbits=1, + timeout=2 + ) + + print(f"Porta aperta a {$BAUD} baud") + + # Flush buffers + ser.reset_input_buffer() + ser.reset_output_buffer() + time.sleep(0.3) + + # Test comando QMOD (semplice, 5 bytes di risposta) + cmd = 'QMOD' + print(f"Invio comando: {cmd}") + + cmd_bytes = cmd.encode('ascii') + crc = calc_crc(cmd_bytes) + full_cmd = cmd_bytes + crc + b'\r' + + print(f" Hex: {full_cmd.hex()}") + + # Invia + ser.write(full_cmd) + ser.flush() + time.sleep(0.5) + + # Leggi risposta + response = ser.read(200) + + if len(response) > 0: + print(f" [OK] RISPOSTA RICEVUTA ({len(response)} bytes)") + print(f" Hex: {response.hex()}") + try: + ascii_text = response.decode('ascii', errors='replace') + print(f" ASCII: {ascii_text.strip()}") + + # Verifica se Γ¨ una risposta valida (inizia con '(' e non Γ¨ NAK) + if response[0:1] == b'(' and b'NAK' not in response: + print(f" *** BAUDRATE CORRETTO: {$BAUD} ***") + sys.exit(0) # Success + elif b'NAK' in response: + print(f" [X] NAK ricevuto (inverter non comprende)") + else: + print(f" [X] Risposta non valida") + except: + print(f" [X] Risposta non decodificabile") + else: + print(f" [X] Nessuna risposta (timeout)") + + ser.close() + +except Exception as e: + print(f" [X] Errore: {e}") + sys.exit(1) + +PYTHON_EOF + + if [ $? -eq 0 ]; then + echo "" + echo "╔═══════════════════════════════════════════╗" + echo "β•‘ BAUDRATE CORRETTO TROVATO: $BAUD β•‘" + echo "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•" + echo "" + echo "Aggiorna /etc/inverter/inverter.conf se necessario" + echo "Modifica sources/inverter-cli/inverter.cpp:" + echo " Cambia: speed_t baud = B$BAUD;" + exit 0 + fi + + sleep 1 +done + +echo "" +echo "============================================" +echo "NESSUN BAUDRATE FUNZIONANTE TROVATO" +echo "============================================" +echo "" +echo "Possibili cause:" +echo "1. Inverter spento o disconnesso" +echo "2. Cavo USB/RS232 difettoso" +echo "3. Device errato (prova /dev/ttyUSB1 o /dev/hidraw0)" +echo "4. Inverter in modalitΓ  incompatibile" +echo "" diff --git a/sources/inverter-cli/test-mks-commands.sh b/sources/inverter-cli/test-mks-commands.sh new file mode 100755 index 0000000..c6f086f --- /dev/null +++ b/sources/inverter-cli/test-mks-commands.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Test suite per comandi Voltronic Axpert MKS IV +# Basato su documentazione forum AEVA e manuale protocollo + +echo "╔══════════════════════════════════════════════════════╗" +echo "β•‘ TEST COMANDI PROTOCOLLO VOLTRONIC MKS IV β•‘" +echo "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•" +echo "" + +test_command() { + local cmd=$1 + local desc=$2 + echo -n "Testing $cmd ($desc)... " + result=$(sudo ./bin/inverter_poller -r "$cmd" 2>&1 | grep "Reply:" | sed 's/Reply://g' | xargs) + if [ -z "$result" ]; then + echo "❌ NO RESPONSE" + elif [ "$result" = "NAK" ]; then + echo "❌ NAK (comando non supportato)" + else + echo "βœ… $result" + fi +} + +# Comandi che DOVREBBERO funzionare +echo "=== COMANDI STANDARD P18 ===" +test_command "QID" "Device Serial Number" +test_command "QVFW" "Main CPU Firmware Version" +test_command "QGMN" "General Model Name" +test_command "QPI" "Protocol ID" +test_command "QFLAG" "Device Flag Status" + +echo "" +echo "=== COMANDI STATUS AVANZATI ===" +test_command "QPGS0" "Parallel General Status" +test_command "QPGS1" "Parallel General Status #1" +test_command "QDI" "Default Settings Inquiry" +test_command "QMCHGCR" "Max Charging Current Options" +test_command "QMUCHGCR" "Max Utility Charging Current" +test_command "QOPPT" "Output Power Type" + +echo "" +echo "=== COMANDI BATTERIA ===" +test_command "QBEQI" "Battery Equalization Info" +test_command "QBMS" "BMS Info" + +echo "" +echo "=== COMANDI DIAGNOSTICI ===" +test_command "QBOOT" "Bootloader Version" +test_command "QET" "Total Generated Energy" +test_command "QEY" "Generated Energy This Year" +test_command "QEM" "Generated Energy This Month" +test_command "QED" "Generated Energy Today" + +echo "" +echo "╔══════════════════════════════════════════════════════╗" +echo "β•‘ REPORT FINALE β•‘" +echo "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•" +echo "Comando FUNZIONANTE: QGMN (Model 054)" +echo "Comandi STANDARD non funzionanti: QPIGS, QPIRI, QMOD, QPIWS" +echo "Possibile causa: Protocollo proprietario MKS IV" diff --git a/sources/inverter-cli/test_serial_dump.py b/sources/inverter-cli/test_serial_dump.py new file mode 100644 index 0000000..7b6e293 --- /dev/null +++ b/sources/inverter-cli/test_serial_dump.py @@ -0,0 +1,22 @@ +import serial +import time + +port = '/dev/ttyUSB1' +ser = serial.Serial(port, 2400, bytesize=8, parity='N', stopbits=1, timeout=2) + +# Costruisci comando QPIGS manualmente +cmd = b'QPIGS' +crc = 0xB7A9 +cmd_full = cmd + bytes([crc >> 8, crc & 0xFF, 0x0D]) + +print(f"Invio comando: {cmd_full.hex(' ')}") +print(f"Lunghezza: {len(cmd_full)} bytes") + +ser.write(cmd_full) +time.sleep(0.5) + +resp = ser.read(100) +print(f"Ricevuto ({len(resp)} bytes): {resp.hex(' ')}") +print(f"ASCII: {resp}") + +ser.close() diff --git a/sources/inverter-cli/test_usb0.py b/sources/inverter-cli/test_usb0.py new file mode 100644 index 0000000..774193b --- /dev/null +++ b/sources/inverter-cli/test_usb0.py @@ -0,0 +1,26 @@ +import serial +import time + +port = '/dev/ttyUSB0' +ser = serial.Serial(port, 2400, bytesize=8, parity='N', stopbits=1, timeout=2) + +for cmd_str in ['QPIGS', 'QMOD', 'QGMN']: + # CRC calcolati + crcs = {'QPIGS': 0xB7A9, 'QMOD': 0x49C1, 'QGMN': 0x4928} + + cmd = cmd_str.encode() + crc = crcs[cmd_str] + cmd_full = cmd + bytes([crc >> 8, crc & 0xFF, 0x0D]) + + print(f"\n=== {cmd_str} ===") + print(f"Invio: {cmd_full.hex(' ')}") + + ser.write(cmd_full) + time.sleep(0.5) + + resp = ser.read(200) + print(f"Ricevuto ({len(resp)} bytes): {resp.hex(' ') if resp else '(nessuna risposta)'}") + if resp: + print(f"ASCII: {resp}") + +ser.close() diff --git a/sources/inverter-mqtt/mqtt-push-parallel.sh b/sources/inverter-mqtt/mqtt-push-parallel.sh new file mode 100755 index 0000000..767ccbe --- /dev/null +++ b/sources/inverter-mqtt/mqtt-push-parallel.sh @@ -0,0 +1,140 @@ +#!/bin/bash +# MQTT Push for Parallel Inverters +# Discovers parallel inverters and publishes data for each one separately + +# Detect environment (container vs development) +if [ -f "/etc/inverter/mqtt.json" ] && [ -x "/opt/inverter-cli/bin/inverter_poller" ]; then + # Container mode + MQTT_CONFIG="/etc/inverter/mqtt.json" + INVERTER_BIN="/opt/inverter-cli/bin/inverter_poller" + MQTT_FALLBACK="/opt/inverter-mqtt/mqtt-push.sh" + CONTAINER_MODE=true +else + # Development mode + MQTT_CONFIG="/home/pi/Progetti/config/mqtt.json" + INVERTER_BIN="/home/pi/Progetti/sources/inverter-cli/bin/inverter_poller" + MQTT_FALLBACK="/home/pi/Progetti/sources/inverter-mqtt/mqtt-push.sh" + CONTAINER_MODE=false +fi + +echo "Mode: $([ "$CONTAINER_MODE" = true ] && echo "Container" || echo "Development")" +echo "Using binary: $INVERTER_BIN" + +# Check if jq is installed +if ! command -v jq &> /dev/null; then + echo "ERROR: jq is not installed. Install it with: sudo apt-get install jq" + exit 1 +fi + +MQTT_SERVER=`cat $MQTT_CONFIG | jq '.server' -r` +MQTT_PORT=`cat $MQTT_CONFIG | jq '.port' -r` +MQTT_TOPIC=`cat $MQTT_CONFIG | jq '.topic' -r` +MQTT_DEVICENAME=`cat $MQTT_CONFIG | jq '.devicename' -r` +MQTT_USERNAME=`cat $MQTT_CONFIG | jq '.username' -r` +MQTT_PASSWORD=`cat $MQTT_CONFIG | jq '.password' -r` +MQTT_CLIENTID=`cat $MQTT_CONFIG | jq '.clientid' -r` +INFLUX_ENABLED=`cat $MQTT_CONFIG | jq '.influx.enabled' -r` + +pushMQTTData () { + # $1 = inverter_id, $2 = metric, $3 = value + local inverter_id=$1 + local metric=$2 + local value=$3 + + mosquitto_pub \ + -h $MQTT_SERVER \ + -p $MQTT_PORT \ + -u "$MQTT_USERNAME" \ + -P "$MQTT_PASSWORD" \ + -i $MQTT_CLIENTID \ + -r \ + -t "$MQTT_TOPIC/sensor/${MQTT_DEVICENAME}_inv${inverter_id}_${metric}" \ + -m "$value" + + if [[ $INFLUX_ENABLED == "true" ]] ; then + pushInfluxData $inverter_id $metric $value + fi +} + +pushInfluxData () { + INFLUX_HOST=`cat $MQTT_CONFIG | jq '.influx.host' -r` + INFLUX_USERNAME=`cat $MQTT_CONFIG | jq '.influx.username' -r` + INFLUX_PASSWORD=`cat $MQTT_CONFIG | jq '.influx.password' -r` + INFLUX_DEVICE=`cat $MQTT_CONFIG | jq '.influx.device' -r` + INFLUX_PREFIX=`cat $MQTT_CONFIG | jq '.influx.prefix' -r` + INFLUX_DATABASE=`cat $MQTT_CONFIG | jq '.influx.database' -r` + INFLUX_MEASUREMENT_NAME=`cat $MQTT_CONFIG | jq '.influx.namingMap.'$2'' -r` + + curl -i -XPOST "$INFLUX_HOST/write?db=$INFLUX_DATABASE&precision=s" -u "$INFLUX_USERNAME:$INFLUX_PASSWORD" --data-binary "$INFLUX_PREFIX,device=${INFLUX_DEVICE}_inv${1} $INFLUX_MEASUREMENT_NAME=$3" > /dev/null 2>&1 +} + +# Discover parallel inverters +SUDO_CMD="" +if [ "$EUID" -ne 0 ] && [ -c "/dev/ttyUSB0" ]; then + SUDO_CMD="sudo" +fi + +PARALLEL_DISCOVERY=`$SUDO_CMD "$INVERTER_BIN" -p 2>&1` +PARALLEL_COUNT=`echo "$PARALLEL_DISCOVERY" | grep "PARALLEL_COUNT=" | cut -d= -f2` + +if [ -z "$PARALLEL_COUNT" ] || [ "$PARALLEL_COUNT" -eq 0 ]; then + echo "No parallel inverters found (count=$PARALLEL_COUNT), using standard polling" + # Don't use fallback in dev mode if file doesn't exist + if [ -f "$MQTT_FALLBACK" ]; then + exec $MQTT_FALLBACK + else + echo "Fallback script not found: $MQTT_FALLBACK" + echo "Using standard inverter_poller -1 instead" + INVERTER_DATA=`$SUDO_CMD "$INVERTER_BIN" -1 2>&1` + echo "$INVERTER_DATA" + fi + exit 0 +fi + +echo "Found $PARALLEL_COUNT parallel inverters" + +# Publish discovery info +pushMQTTData "system" "parallel_count" "$PARALLEL_COUNT" + +# Extract inverter serials and QPGS indices +for i in $(seq 1 $PARALLEL_COUNT); do + SERIAL=`echo "$PARALLEL_DISCOVERY" | grep "INVERTER_${i}_SERIAL=" | cut -d= -f2` + QPGS_IDX=`echo "$PARALLEL_DISCOVERY" | grep "INVERTER_${i}_QPGS=" | cut -d= -f2` + + echo "Processing Inverter #$i (Serial: $SERIAL, QPGS$QPGS_IDX)" + + # Get QPGS data for this inverter + QPGS_DATA=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS$QPGS_IDX" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs` + + if [ ! -z "$QPGS_DATA" ] && [ "$QPGS_DATA" != "NAK" ]; then + # Parse QPGS response format: + # 1 SERIAL MODE STATUS GRID_V GRID_F OUT_V OUT_F VA W PCT BATT_V CHRG CAP PV_V CHRG_A ... + + # Publish serial number + pushMQTTData "$i" "serial" "$SERIAL" + + # Parse and publish data (QPGS format parsing) + IFS=' ' read -ra DATA <<< "$QPGS_DATA" + + [ "${DATA[2]}" ] && pushMQTTData "$i" "mode" "${DATA[2]}" + [ "${DATA[4]}" ] && pushMQTTData "$i" "AC_grid_voltage" "${DATA[4]}" + [ "${DATA[5]}" ] && pushMQTTData "$i" "AC_grid_frequency" "${DATA[5]}" + [ "${DATA[6]}" ] && pushMQTTData "$i" "AC_out_voltage" "${DATA[6]}" + [ "${DATA[7]}" ] && pushMQTTData "$i" "AC_out_frequency" "${DATA[7]}" + [ "${DATA[8]}" ] && pushMQTTData "$i" "Load_va" "${DATA[8]}" + [ "${DATA[9]}" ] && pushMQTTData "$i" "Load_watt" "${DATA[9]}" + [ "${DATA[10]}" ] && pushMQTTData "$i" "Load_pct" "${DATA[10]}" + [ "${DATA[11]}" ] && pushMQTTData "$i" "Battery_voltage" "${DATA[11]}" + [ "${DATA[12]}" ] && pushMQTTData "$i" "Battery_charge_current" "${DATA[12]}" + [ "${DATA[13]}" ] && pushMQTTData "$i" "Battery_capacity" "${DATA[13]}" + [ "${DATA[14]}" ] && pushMQTTData "$i" "PV_in_voltage" "${DATA[14]}" + [ "${DATA[15]}" ] && pushMQTTData "$i" "PV_in_current" "${DATA[15]}" + + echo " βœ“ Published data for inverter #$i" + echo " Topics: ${MQTT_TOPIC}/sensor/${MQTT_DEVICENAME}_inv${i}_{serial,mode,Battery_voltage,Load_watt,...}" + else + echo " βœ— No valid data for inverter #$i" + fi +done + +echo "Parallel MQTT push completed" diff --git a/sources/inverter-mqtt/test-loop-parallel.sh b/sources/inverter-mqtt/test-loop-parallel.sh new file mode 100755 index 0000000..e1357ac --- /dev/null +++ b/sources/inverter-mqtt/test-loop-parallel.sh @@ -0,0 +1,225 @@ +#!/bin/bash +# Test Loop - Simula esecuzione container con parallel discovery e MQTT push +# Questo script simula il comportamento completo del container in produzione + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Detect environment +if [ -f "/etc/inverter/mqtt.json" ] && [ -x "/opt/inverter-cli/bin/inverter_poller" ]; then + # Container mode + MQTT_CONFIG="/etc/inverter/mqtt.json" + INVERTER_BIN="/opt/inverter-cli/bin/inverter_poller" + INVERTER_CONFIG="/etc/inverter/inverter.conf" + MQTT_PUSH_SCRIPT="/opt/inverter-mqtt/mqtt-push-parallel.sh" + CONTAINER_MODE=true +else + # Development mode + MQTT_CONFIG="/home/pi/Progetti/config/mqtt.json" + INVERTER_BIN="/home/pi/Progetti/sources/inverter-cli/bin/inverter_poller" + INVERTER_CONFIG="/home/pi/Progetti/config/inverter.conf" + MQTT_PUSH_SCRIPT="/home/pi/Progetti/sources/inverter-mqtt/mqtt-push-parallel.sh" + CONTAINER_MODE=false +fi + +SUDO_CMD="" +if [ "$EUID" -ne 0 ]; then + SUDO_CMD="sudo" +fi + +# Configuration +LOOP_INTERVAL=${LOOP_INTERVAL:-30} # Seconds between iterations +MAX_ITERATIONS=${MAX_ITERATIONS:-0} # 0 = infinite + +echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}β•‘ VOLTRONIC PARALLEL INVERTER - TEST LOOP β•‘${NC}" +echo -e "${BLUE}β•‘ Simulates container execution with full discovery β•‘${NC}" +echo -e "${BLUE}β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•${NC}" +echo "" +echo -e "${YELLOW}Mode:${NC} $([ "$CONTAINER_MODE" = true ] && echo "Container" || echo "Development")" +echo -e "${YELLOW}Binary:${NC} $INVERTER_BIN" +echo -e "${YELLOW}MQTT Config:${NC} $MQTT_CONFIG" +echo -e "${YELLOW}Loop Interval:${NC} ${LOOP_INTERVAL}s" +echo -e "${YELLOW}Max Iterations:${NC} $([ $MAX_ITERATIONS -eq 0 ] && echo "∞ (infinite)" || echo "$MAX_ITERATIONS")" +echo "" + +# Check device configuration +if [ -f "$INVERTER_CONFIG" ]; then + USB_DEVICE=$(grep "^device=" "$INVERTER_CONFIG" | cut -d= -f2) + echo -e "${YELLOW}USB Device:${NC} $USB_DEVICE" + # Show USB info if available + if [ -e "$USB_DEVICE" ]; then + USB_SERIAL=$(udevadm info -q property -n "$USB_DEVICE" 2>/dev/null | grep "ID_SERIAL_SHORT=" | cut -d= -f2) + [ ! -z "$USB_SERIAL" ] && echo -e "${YELLOW}USB Serial:${NC} $USB_SERIAL" + fi +fi + +# Check if MQTT server is configured +if [ -f "$MQTT_CONFIG" ]; then + MQTT_SERVER=$(cat "$MQTT_CONFIG" | jq -r '.server' 2>/dev/null || echo "not configured") + MQTT_PORT=$(cat "$MQTT_CONFIG" | jq -r '.port' 2>/dev/null || echo "1883") + MQTT_TOPIC=$(cat "$MQTT_CONFIG" | jq -r '.topic' 2>/dev/null || echo "homeassistant") + MQTT_DEVICE=$(cat "$MQTT_CONFIG" | jq -r '.devicename' 2>/dev/null || echo "voltronic") + echo -e "${YELLOW}MQTT Server:${NC} $MQTT_SERVER:$MQTT_PORT" + echo -e "${YELLOW}MQTT Base Topic:${NC} $MQTT_TOPIC/sensor/$MQTT_DEVICE" +else + echo -e "${RED}βœ— MQTT config not found: $MQTT_CONFIG${NC}" + exit 1 +fi + +echo "" +read -p "Press ENTER to start test loop (Ctrl+C to stop)..." +echo "" + +# Phase 1: Initial Discovery (run once at startup) +echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}β•‘ PHASE 1: INITIAL DISCOVERY β•‘${NC}" +echo -e "${BLUE}β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•${NC}" +echo "" + +echo -e "${YELLOW}[1.1] Buffer Sizes Auto-Discovery${NC}" +DISCOVERY_OUTPUT=$($SUDO_CMD "$INVERTER_BIN" -a 2>&1) +DISCOVERY_SUCCESS=$(echo "$DISCOVERY_OUTPUT" | grep "DISCOVERY_SUCCESS=" | cut -d= -f2) + +if [ "$DISCOVERY_SUCCESS" = "true" ]; then + echo -e "${GREEN}βœ“ Buffer sizes discovered successfully${NC}" + QMOD=$(echo "$DISCOVERY_OUTPUT" | grep "DISCOVERY_QMOD=" | cut -d= -f2) + QPIGS=$(echo "$DISCOVERY_OUTPUT" | grep "DISCOVERY_QPIGS=" | cut -d= -f2) + QPIRI=$(echo "$DISCOVERY_OUTPUT" | grep "DISCOVERY_QPIRI=" | cut -d= -f2) + QPIWS=$(echo "$DISCOVERY_OUTPUT" | grep "DISCOVERY_QPIWS=" | cut -d= -f2) + + echo " β€’ QMOD: $QMOD bytes" + echo " β€’ QPIGS: $QPIGS bytes" + echo " β€’ QPIRI: $QPIRI bytes" + echo " β€’ QPIWS: $QPIWS bytes" +else + echo -e "${RED}βœ— Buffer discovery failed, using defaults${NC}" +fi +echo "" + +echo -e "${YELLOW}[1.2] Parallel Inverters Discovery${NC}" +PARALLEL_OUTPUT=$($SUDO_CMD "$INVERTER_BIN" -p 2>&1) +PARALLEL_COUNT=$(echo "$PARALLEL_OUTPUT" | grep "PARALLEL_COUNT=" | cut -d= -f2) + +if [ -z "$PARALLEL_COUNT" ]; then + PARALLEL_COUNT=0 +fi + +if [ $PARALLEL_COUNT -gt 0 ]; then + echo -e "${GREEN}βœ“ Found $PARALLEL_COUNT parallel inverter(s)${NC}" + for i in $(seq 1 $PARALLEL_COUNT); do + SERIAL=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_SERIAL=" | cut -d= -f2) + QPGS_IDX=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_QPGS=" | cut -d= -f2) + echo " β€’ Inverter #$i: Serial $SERIAL (QPGS$QPGS_IDX)" + done +else + echo -e "${YELLOW}⚠ No parallel inverters found, using single mode${NC}" +fi +echo "" + +# Phase 2: Main Loop +echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}β•‘ PHASE 2: MAIN POLLING LOOP β•‘${NC}" +echo -e "${BLUE}β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•${NC}" +echo "" + +iteration=0 +while true; do + iteration=$((iteration + 1)) + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + + echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}" + echo -e "${BLUE}Iteration #$iteration - $timestamp${NC}" + echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}" + + # Test standard commands (local inverter) + echo -e "${YELLOW}[2.1] Testing standard commands (local inverter)${NC}" + for cmd in QPIGS QPIRI QMOD QPIWS; do + result=$($SUDO_CMD "$INVERTER_BIN" -r $cmd 2>&1 | grep "Reply:" | cut -d: -f2- | xargs) + if [ ! -z "$result" ] && [ "$result" != "NAK" ]; then + echo -e " ${GREEN}βœ“${NC} $cmd: OK (${#result} chars)" + else + echo -e " ${RED}βœ—${NC} $cmd: FAIL ($result)" + fi + done + echo "" + + # Get parallel data + if [ $PARALLEL_COUNT -gt 0 ]; then + echo -e "${YELLOW}[2.2] Reading parallel inverters data${NC}" + for i in $(seq 1 $PARALLEL_COUNT); do + SERIAL=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_SERIAL=" | cut -d= -f2) + QPGS_IDX=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_QPGS=" | cut -d= -f2) + + QPGS_DATA=$($SUDO_CMD "$INVERTER_BIN" -r "QPGS$QPGS_IDX" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs) + + if [ ! -z "$QPGS_DATA" ] && [ "$QPGS_DATA" != "NAK" ]; then + # Parse key values + IFS=' ' read -ra DATA <<< "$QPGS_DATA" + MODE="${DATA[2]}" + GRID_V="${DATA[4]}" + BATT_V="${DATA[11]}" + LOAD_W="${DATA[9]}" + + echo -e " ${GREEN}βœ“${NC} Inverter #$i ($SERIAL): Mode=$MODE, Grid=${GRID_V}V, Battery=${BATT_V}V, Load=${LOAD_W}W" + else + echo -e " ${RED}βœ—${NC} Inverter #$i ($SERIAL): No data" + fi + done + echo "" + fi + + # MQTT Push simulation + echo -e "${YELLOW}[2.3] MQTT Push${NC}" + if [ -x "$MQTT_PUSH_SCRIPT" ]; then + echo " Running: $MQTT_PUSH_SCRIPT" + echo " Publishing to: $MQTT_SERVER:$MQTT_PORT" + MQTT_OUTPUT=$(bash "$MQTT_PUSH_SCRIPT" 2>&1 | tail -5) + echo "$MQTT_OUTPUT" | sed 's/^/ /' + + # Show sample topics published + if [ $PARALLEL_COUNT -gt 0 ]; then + echo -e " ${GREEN}Sample topics published:${NC}" + for i in $(seq 1 $PARALLEL_COUNT); do + echo " β€’ $MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${i}_serial" + echo " β€’ $MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${i}_Battery_voltage" + echo " β€’ $MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${i}_Load_watt" + [ $i -eq 1 ] && echo " β€’ ... (and more)" + done + fi + + echo -e "${GREEN}βœ“ MQTT push completed${NC}" + else + echo -e "${YELLOW}⚠ MQTT script not executable: $MQTT_PUSH_SCRIPT${NC}" + echo " Simulating MQTT publish..." + if [ $PARALLEL_COUNT -gt 0 ]; then + echo " β€’ Published data for $PARALLEL_COUNT inverters" + else + echo " β€’ Published data for 1 inverter (single mode)" + fi + fi + echo "" + + # Check iteration limit + if [ $MAX_ITERATIONS -gt 0 ] && [ $iteration -ge $MAX_ITERATIONS ]; then + echo -e "${GREEN}βœ“ Reached maximum iterations ($MAX_ITERATIONS)${NC}" + break + fi + + # Wait for next iteration + echo -e "${BLUE}Waiting ${LOOP_INTERVAL}s until next iteration...${NC}" + echo "" + sleep $LOOP_INTERVAL +done + +echo "" +echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${GREEN}β•‘ TEST LOOP COMPLETED β•‘${NC}" +echo -e "${GREEN}β•‘ Total iterations: $iteration β•‘${NC}" +echo -e "${GREEN}β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•${NC}"