docs(brief): SunSpec Storage doit déclarer energystorage — et pourquoi ce n'est pas une ligne
É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
This commit is contained in:
parent
366d332bf4
commit
2f94fd741f
103
BRIEF_sunspec_energystorage.md
Normal file
103
BRIEF_sunspec_energystorage.md
Normal file
@ -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.
|
||||
Loading…
x
Reference in New Issue
Block a user