É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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H
5.0 KiB
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
batterymais pasenergystorage»), mais l'avertissement ne remplace pas la déclaration.
2. Ce que l'interface exige
libnymea/interfaces/energystorage.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 :WHRtgdu 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é
- Garder
battery— ne pas la remplacer. D'autres choses peuvent en dépendre, etenergystoragel'étend de toute façon. - Ajouter les états
currentPoweretcapacity, câblés sur les bons registres et avec le bon signe. - Puis ajouter
"energystorage"aux interfaces desunspecStorage. - Vérifier sur
.75queEnergy.GetPowerBalancerend enfin uncurrentPowerStoragenon 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.