Initial release — Growatt SPF TCP proxy with decoder and HA MQTT integration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
# invraw — Growatt SPF Inverter Raw Decoder
|
||||
|
||||
Proxy TCP transparent entre un onduleur Growatt SPF et le cloud Growatt, qui :
|
||||
- **Capture** chaque paquet en temps réel
|
||||
- **Déchiffre** le protocole V2 (XOR "Growatt")
|
||||
- **Décode** les 40+ champs du layout `T05NNNNSPF` / `T06NNNNSPF`
|
||||
- **Affiche** tout dans une web UI live (hex dump + valeurs lisibles)
|
||||
- **Publie** vers Home Assistant via MQTT auto-discovery
|
||||
|
||||
Stack : Python 3.12 · asyncio · aiohttp · paho-mqtt · Docker
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Onduleur Growatt SPF
|
||||
│ TCP :5279
|
||||
▼
|
||||
[ invraw ] ──── décode + UI live ────▶ https://invraw.nas.local
|
||||
│ (hex dump + champs décodés)
|
||||
│ TCP :5279
|
||||
▼
|
||||
server.growatt.com
|
||||
│
|
||||
▼ (optionnel)
|
||||
[ Mosquitto ]
|
||||
│ MQTT
|
||||
▼
|
||||
Home Assistant
|
||||
```
|
||||
|
||||
L'onduleur se reconnecte automatiquement — aucune modification de sa config n'est nécessaire si l'IP du NAS est déjà configurée.
|
||||
|
||||
---
|
||||
|
||||
## Déploiement
|
||||
|
||||
**Prérequis** : Docker, Docker Compose, Traefik sur le réseau `proxy`.
|
||||
|
||||
```bash
|
||||
git clone http://<gitea>/perco/invraw.git && cd invraw
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
L'onduleur doit pointer vers l'IP du NAS sur le port **5279** (même port que le cloud Growatt).
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Toute la configuration se fait via variables d'environnement dans `docker-compose.yml` :
|
||||
|
||||
| Variable | Défaut | Description |
|
||||
|----------|--------|-------------|
|
||||
| `GROWATT_HOST` | `server.growatt.com` | Adresse du cloud Growatt |
|
||||
| `GROWATT_PORT` | `5279` | Port cloud Growatt |
|
||||
| `LISTEN_PORT` | `5279` | Port d'écoute du proxy |
|
||||
| `WEB_PORT` | `8080` | Port de la web UI (interne) |
|
||||
| `MAX_PACKETS` | `500` | Nombre de paquets gardés en mémoire |
|
||||
| `MQTT_ENABLED` | `true` | Activer la publication MQTT |
|
||||
| `MQTT_HOST` | `192.168.1.29` | Broker MQTT (container name ou IP) |
|
||||
| `MQTT_PORT` | `1883` | Port MQTT |
|
||||
| `HA_DISCOVERY` | `true` | Activer le MQTT auto-discovery HA |
|
||||
| `DEVICE_NAMES` | *(vide)* | Mapping serial → nom HA (voir ci-dessous) |
|
||||
|
||||
### Nommer les appareils dans Home Assistant
|
||||
|
||||
Par défaut, l'appareil est créé avec le numéro de série de l'onduleur. Pour lui donner un nom personnalisé :
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- DEVICE_NAMES=JNK1CM70FU:Onduleur_Est,JNK1CM70H5:Onduleur_Sud
|
||||
```
|
||||
|
||||
Format : `SERIAL1:NOM1,SERIAL2:NOM2` — plusieurs onduleurs séparés par des virgules.
|
||||
|
||||
---
|
||||
|
||||
## Web UI
|
||||
|
||||
Accessible via `https://invraw.nas.percolouco.com` (ou le domaine Traefik configuré).
|
||||
|
||||
**Fonctionnalités :**
|
||||
- Live feed des paquets (Server-Sent Events)
|
||||
- Résumé inline : statut onduleur, SOC batterie %, puissance PV totale
|
||||
- Expand → données décodées par catégorie (Panneaux solaires, Batterie, Réseau, Sortie, Température, Énergie)
|
||||
- Hex dump complet + hex brut copiable
|
||||
- Filtre par direction (onduleur→cloud / cloud→onduleur)
|
||||
- Filtre "données seulement" pour masquer les pings
|
||||
- Bouton Pause / Vider
|
||||
|
||||
---
|
||||
|
||||
## Protocole Growatt SPF décodé
|
||||
|
||||
### Structure d'un paquet
|
||||
|
||||
```
|
||||
Offset Taille Description
|
||||
0-3 4 B ID datalogger
|
||||
4-5 2 B Longueur payload (big-endian)
|
||||
6 1 B Version protocole (0x05 / 0x06 = chiffré V2)
|
||||
7 1 B Type d'enregistrement (0x04 = live, 0x50 = tampon)
|
||||
8+ N B Payload (XOR "Growatt" si protocole 05/06)
|
||||
```
|
||||
|
||||
### Déchiffrement V2
|
||||
|
||||
```python
|
||||
mask = [ord(c) for c in "Growatt"] # [71, 114, 111, 119, 97, 116, 116]
|
||||
# Les 8 premiers octets (header) ne sont pas chiffrés
|
||||
for i, j in zip(range(len(data) - 8), cycle(range(7))):
|
||||
decrypted[i + 8] = data[i + 8] ^ mask[j]
|
||||
```
|
||||
|
||||
### Champs décodés (layout T06NNNNSPF)
|
||||
|
||||
| Champ | Offset hex | Longueur | Diviseur | Unité |
|
||||
|-------|-----------|----------|----------|-------|
|
||||
| datalogserial | 16 | 10 | — | texte |
|
||||
| pvserial | 76 | 10 | — | texte |
|
||||
| pvstatus | 158 | 2 | 1 | — |
|
||||
| vpv1 / vpv2 | 162 / 166 | 2 | 10 | V |
|
||||
| ppv1 / ppv2 | 170 / 178 | 4 | 10 | W |
|
||||
| buck1curr / buck2curr | 186 / 190 | 2 | 10 | A |
|
||||
| op_watt | 194 | 4 | 10 | W |
|
||||
| bat_Volt | 226 | 2 | 100 | V |
|
||||
| batterySoc | 230 | 2 | 1 | % |
|
||||
| grid_volt | 238 | 2 | 10 | V |
|
||||
| line_freq | 242 | 2 | 100 | Hz |
|
||||
| outputvolt | 246 | 2 | 10 | V |
|
||||
| invtemp | 258 | 2 | 10 | °C |
|
||||
| loadpercent | 266 | 2 | 10 | % |
|
||||
| buck1_ntc | 286 | 2 | 10 | °C |
|
||||
| AC_InWatt | 302 | 4 | 10 | W |
|
||||
| pvenergytoday | 358 | 4 | 10 | kWh |
|
||||
| pvenergytotal | 366 | 4 | 10 | kWh |
|
||||
| ebatDischarToday/Total | 406 / 414 | 4 | 10 | kWh |
|
||||
| BatWatt | 474 | 4 signé | 10 | W |
|
||||
| … | … | … | … | … |
|
||||
|
||||
Le layout `T05NNNNSPF` (protocole 0x05) utilise les mêmes champs avec des offsets décalés — voir `proxy.py`.
|
||||
|
||||
---
|
||||
|
||||
## Home Assistant
|
||||
|
||||
Quand `MQTT_ENABLED=true`, invraw publie automatiquement :
|
||||
|
||||
1. **MQTT Discovery** (premier paquet reçu) — crée les sensors dans HA
|
||||
- Topic config : `homeassistant/sensor/invraw/{device}_{key}/config`
|
||||
2. **State** (chaque paquet data) — met à jour les valeurs
|
||||
- Topic state : `homeassistant/invraw/{device}/state`
|
||||
|
||||
Les sensors expirent après 10 minutes sans données (`expire_after: 600`).
|
||||
|
||||
---
|
||||
|
||||
## Structure du projet
|
||||
|
||||
```
|
||||
invraw/
|
||||
├── proxy.py # Proxy TCP + décodeur + serveur web (asyncio)
|
||||
├── ha_mqtt.py # Publication MQTT / HA auto-discovery
|
||||
├── templates/
|
||||
│ └── index.html # Web UI (SSE live, hex dump, decoded view)
|
||||
├── Dockerfile
|
||||
└── docker-compose.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Versioning
|
||||
|
||||
| Version | Changements |
|
||||
|---------|-------------|
|
||||
| v1.1.0 | MQTT HA auto-discovery, mapping serial → nom custom (`DEVICE_NAMES`) |
|
||||
| v1.0.0 | Proxy TCP + décodeur SPF complet (43 champs) + web UI live |
|
||||
Reference in New Issue
Block a user