From 2f94fd741f403068d43a17fb30fdc973d04a30b0 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Wed, 26 Aug 2026 11:57:26 +0200 Subject: [PATCH] =?UTF-8?q?docs(brief):=20SunSpec=20Storage=20doit=20d?= =?UTF-8?q?=C3=A9clarer=20energystorage=20=E2=80=94=20et=20pourquoi=20ce?= =?UTF-8?q?=20n'est=20pas=20une=20ligne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Émis par l'agent moteur. Le Thing « Fronius Storage » déclare battery + connectable, donc trois consommateurs distincts qui le cherchent par filterByInterface(energystorage) ne le trouvent pas : le bilan de puissance de nymea (currentPowerStorage, que le CLIENT lit sur son tableau de bord), la réserve de stockage du proxy VE, et celle du waterfall. Vérifié sur .75 : currentPowerStorage vaut 0 pendant que la batterie affiche 50 % et « Holding » — elle n'est pas à zéro, elle est invisible. Ce n'est pas une ligne de JSON : energystorage exige currentPower (signé, W) et capacity (kWh), que sunspecStorage ne fournit pas. Les déclarer sans les câbler donnerait deux états à zéro — et un currentPower de batterie à zéro est PIRE que son absence, puisque la réserve lirait « ni charge ni décharge » au lieu de « je ne sais pas ». Rappel de vigilance : d08cf50 a montré ce que coûte un float32 mal ordonné sur ce même plugin. Un currentPower de batterie mal signé produirait la même famille de dégât, en plus discret : la réserve croirait la batterie en charge quand elle se vide. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H --- BRIEF_sunspec_energystorage.md | 103 +++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 BRIEF_sunspec_energystorage.md diff --git a/BRIEF_sunspec_energystorage.md b/BRIEF_sunspec_energystorage.md new file mode 100644 index 0000000..c59691a --- /dev/null +++ b/BRIEF_sunspec_energystorage.md @@ -0,0 +1,103 @@ +# Brief — `SunSpec Storage` doit déclarer `energystorage` + +**Émis par** : l'agent moteur (`etm-powersync-energy-plugin-etm`), 2026-08-26. +**Constaté sur** : banc `.75`, `nymea-plugin-sunspec-etm01 1.15.2+etm9`. +**Ce n'est pas une ligne de JSON** — voir §3. C'est la raison de ce brief. + +--- + +## 1. Le constat + +``` +Thing « Fronius Storage » → classe « SunSpec Storage » (9a643ba8) +interfaces : ["battery", "connectable"] ← PAS "energystorage" +``` + +**Conséquence : cette batterie n'est comptée nulle part.** Trois consommateurs distincts la +cherchent par `filterByInterface("energystorage")`, et aucun ne la trouve : + +| Qui | Ce qui est perdu | +|---|---| +| `EnergyManagerImpl::updatePowerBalance()` (experience-plugin nymea, l. 269) | `currentPowerStorage` et `totalFromStorage` — **le bilan de puissance que le client lit sur son tableau de bord** | +| `SmartChargingManager::planSurplusCharging()` (l. 1170) | la réserve de stockage pour la **recharge du véhicule** | +| `EnergyArbitrator::buildContext()` (moteur, 3g-1) | la réserve de stockage pour **toutes** les charges pilotées | + +Vérifié sur `.75` : `Energy.GetPowerBalance` rend `"currentPowerStorage": 0` pendant que la +batterie affiche 50 % et l'état « Holding ». Elle n'est pas à zéro : **elle est invisible**. + +> **Ce qui rend ce défaut coûteux, c'est son silence.** Un filtre par interface qui ne trouve +> rien est ambigu entre « il n'y a pas de batterie » et « il y en a une, mal déclarée ». Le +> second désactive une règle de sécurité sans produire ni erreur ni panne. Le moteur émet +> désormais un avertissement dans ce cas précis (« interface `battery` mais pas +> `energystorage` »), mais l'avertissement ne remplace pas la déclaration. + +--- + +## 2. Ce que l'interface exige + +`libnymea/interfaces/energystorage.json` : + +```json +{ "extends": ["battery", "smartmeter"], + "states": [ + { "name": "currentPower", "type": "double", "unit": "Watt", "logged": true }, + { "name": "capacity", "type": "double", "unit": "KiloWattHour" } ] } +``` + +--- + +## 3. Pourquoi ce n'est PAS une ligne de JSON + +`sunspecStorage` déclare aujourd'hui ces états : + +``` +batteryCritical · batteryLevel · chargeGradient · chargingRate · chargingState · connected +controlHealthy · dischargeGradient · dischargingRate · enableCharging · enableDischarging +gridCharging · storageStatus · version +``` + +**Ni `currentPower`, ni `capacity`.** Ajouter `"energystorage"` aux interfaces sans ajouter les +deux états produirait, au mieux, un refus de chargement du plugin ; au pire, deux états à zéro. +Et **un `currentPower` de batterie à zéro est pire que son absence** : la règle de réserve +lirait « la batterie ne charge ni ne décharge » au lieu de « je ne sais pas », et le bilan de +puissance du client afficherait un stockage éternellement inerte. + +Il faut donc **câbler les deux états sur des registres** : + +- **`currentPower`** — puissance instantanée du stockage, **signée** : positif = charge, + négatif = décharge (convention nymea, cf. la description de l'interface). À établir depuis + quel modèle SunSpec elle se lit sur un Symo — le modèle 124 porte l'état de charge, pas + nécessairement la puissance ; elle peut devoir se déduire des canaux DC (modèle 160). +- **`capacity`** — capacité nominale en kWh. Candidat : `WHRtg` du modèle 120 (plaque + signalétique), à convertir en kWh. + +⚠️ **Attention au signe et à l'ordre des mots.** Le lot `d08cf50` a montré ce que coûte un +float32 lu à mots permutés sur ce même plugin — un compteur important 2,7 kW rapporté comme +exportant 107 MW, et des cumuls empoisonnés pendant dix-neuf jours. Un `currentPower` de +batterie mal signé produirait la même famille de dégât, en plus discret : la réserve croirait +la batterie en charge quand elle se vide. + +--- + +## 4. Ce qui est demandé + +1. **Garder `battery`** — ne pas la remplacer. D'autres choses peuvent en dépendre, et + `energystorage` l'étend de toute façon. +2. **Ajouter les états `currentPower` et `capacity`**, câblés sur les bons registres et avec le + bon signe. +3. **Puis ajouter `"energystorage"`** aux interfaces de `sunspecStorage`. +4. **Vérifier sur `.75`** que `Energy.GetPowerBalance` rend enfin un `currentPowerStorage` non + nul et cohérent avec l'état de la batterie. + +Une fois cela fait, le moteur pourra éprouver sa règle de réserve pour la première fois — la +bascule `batteryLevelConsideration` 0,4 / 0,9 sur un banc dont la batterie est à 50 %. + +--- + +## 5. Question ouverte, à trancher côté SunSpec + +**Les autres classes du plugin sont-elles dans le même cas ?** Sur `.75`, l'onduleur déclare +bien `smartmeterproducer` + `smartmeter` et le compteur `smartmeter` — donc eux sont vus. Mais +le relevé du lot D avait noté que l'onduleur ne publie **aucun cumul** (`thingCache` à +`0.0 / 0.0`), ce qui gèle `totalProduction` côté nymea. Cause différente du présent brief — +il est trouvé, mais il ne publie pas `totalEnergyProduced` — et qui mérite le même examen.