sunspecStorage déclarait `battery` mais pas `energystorage`. Les trois consommateurs qui filtrent par cette interface ne la trouvaient donc pas : le bilan de puissance de nymea (`currentPowerStorage`, celui que le CLIENT lit sur son tableau de bord), la réserve de stockage de la recharge VE, et celle du waterfall. Vérifié sans authentification dans `energylogs.sqlite` de .75 : la colonne `storage` de `powerBalance` est à 0.0 sur toutes les lignes, pendant que la batterie affiche 50 % et « Holding ». Elle n'était pas à zéro, elle était invisible. Ce n'était pas une ligne de JSON. `energystorage` exige `currentPower` (W, signé) et `capacity` (kWh), qu'aucun des quatorze états ne portait, et les déclarer sans les câbler aurait donné une batterie éternellement inerte — pire que son absence, puisque la réserve lirait « ni charge ni décharge » au lieu de « je ne sais pas ». D'où viennent les deux états. Le modèle 124 ne porte AUCUNE puissance : WChaMax est un maximum, StorAval est en ampères-heures. La puissance instantanée vit dans le modèle 160, réparti sur deux blocs `StCha` et `StDisCha` dont le `DCW` est un uint16 toujours positif, l'inactif à zéro. D'où `currentPower = DCW[StCha] - DCW[StDisCha]`, positif en charge, conformément à libnymea/interfaces/energystorage.json (« positive is 'consumed' »). C'est l'INVERSE de la convention constructeur du contrat §4, et `FroniusRules::batteryPower()` est réécrite comme la négation stricte de la nouvelle `storageCurrentPower()` pour qu'aucune des deux ne dérive seule. `capacity` vient de WHRtg du modèle 120, et l'état est laissé tranquille plutôt que mis à zéro quand le nameplate ne l'implémente pas. Le signe est MESURÉ, pas déduit. Banc .211 unité 1, consigne batterie imposée par MQTT, registres relus en lecture seule : +2000 W -> StCha 2000,0 W StDisCha 0,0 W ChaSt Charging -1500 W -> StCha 0,0 W StDisCha 1500,0 W ChaSt Discharging Le même relevé a montré `DCW_SF` passant de -2 à -1 entre repos et charge : le facteur d'échelle n'est jamais mis en cache d'un relevé à l'autre. Écart assumé, D-9 dans DIVERGENCES.md : `sunspecStorage` est partagé avec le vendor générique, et les interfaces se déclarent par thingClass. Un appareil générique publiant le 124 sans le 160 porte désormais `energystorage` avec un `currentPower` figé à zéro. Les trois alternatives sont pires — une thingClass Fronius distincte détruirait le Thing existant et son historique. Le plugin avertit donc UNE FOIS par thing quand il ne trouve pas de source de puissance, en distinguant « pas de modèle 160 » de « modèle 160 sans bloc batterie ». Ça ne remplace pas la mesure, mais ça retire le silence. La cohérence des unités avec l'interface est vérifiée à la COMPILATION par nymea-plugininfocompiler : mettre `KiloWattHour` sur `currentPower` fait échouer le build. Onze tests `test_D9_*` figent le reste. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y3gzsuwENJfJtvuNDT3xGn
SunSpec
Connect nymea to SunSpec devices over Modbus TCP
Supported Things
- SunSpec Inverter
- Model ID 101 & 111 Single phase inverter
- Model ID 102 & 112 Split phase inverter
- Model ID 103 & 113 Three phase inverter
- SunSpec Meter
- Model ID 201 & 211 Single phase meter
- Model ID 202 & 212 Split phase meter
- Model ID 203 & 213 Three phase meter
- SunSpec Storage
- Model ID 124
Tested connections
- SolarEdge (with custom battery)
Requirements
- The package 'nymea-plugin-sunspec' must be installed.
- The SunSpec device must support SunSpec over modbus TCP