docs(sunspec): écrire la dépendance à l'ordre de lecture des SF là où elle porte
Le relevé du 2026-08-26 a montré DCW_SF passant de -2 à -1 entre une batterie à l'arrêt et une batterie en charge, sur le même onduleur, à quelques secondes d'intervalle. Un DCW brut de 20000 vaut 200,0 W avec le premier facteur et 2000,0 W avec le second. Ce que ça casserait est de la même famille que d08cf50 mais plus discret : pas de dénormal, pas d'erreur Modbus, un appareil qui se déclare connected, et une puissance simplement dix fois fausse. La propriété tient aujourd'hui parce que libnymea-sunspec lit les 90 registres du modèle 160 en une transaction et relit les facteurs du bloc fixe AVANT les blocs répétés — deux propriétés qui vivent en amont, que rien dans le compilateur ne tient, et qu'une optimisation de la lecture ferait sauter sans aucun signe extérieur. Le commentaire est donc à l'endroit qui en dépend : au-dessus de storageModuleReadings(), qui consomme dcPower(), avec un pointeur au site de readBlockData() et sur le champ ModuleReading::dcPower. Le README documente en outre le côté exploitation de D-9 : un onduleur qui publie le 124 sans le 160 porte energystorage avec un currentPower figé à zéro, avec les deux lignes de journal exactes à chercher et la commande pour les trouver.
This commit is contained in:
parent
51a211e080
commit
122df17a98
@ -13,7 +13,9 @@ Connect nymea to SunSpec devices over Modbus TCP
|
||||
* Model ID 202 & 212 Split phase meter
|
||||
* Model ID 203 & 213 Three phase meter
|
||||
* SunSpec Storage
|
||||
* Model ID 124
|
||||
* Model ID 124 (state of charge, status, setpoints)
|
||||
* Model ID 160 (instantaneous power, `StCha` / `StDisCha` blocks)
|
||||
* Model ID 120 (rated capacity, `WHRtg`)
|
||||
|
||||
|
||||
## Tested connections
|
||||
@ -27,3 +29,56 @@ Connect nymea to SunSpec devices over Modbus TCP
|
||||
|
||||
## More
|
||||
https://sunspec.org
|
||||
|
||||
---
|
||||
|
||||
## Exploitation — batterie visible mais non mesurable
|
||||
|
||||
Depuis `1.15.2+etm10`, la thingClass `SunSpec Storage` déclare l'interface
|
||||
**`energystorage`**. C'est ce qui la rend visible au bilan de puissance de nymea
|
||||
(`currentPowerStorage`), à la réserve de recharge du véhicule, et à la réserve du
|
||||
waterfall. Avant, elle ne déclarait que `battery` et **aucun des trois ne la
|
||||
trouvait**.
|
||||
|
||||
L'état `currentPower` se lit dans le **modèle 160**, pas dans le modèle 124 qui
|
||||
n'a aucun registre de puissance. Conséquence à connaître avant de diagnostiquer
|
||||
une batterie « à zéro » :
|
||||
|
||||
> **Un onduleur SunSpec qui publie le modèle 124 sans modèle 160 porte
|
||||
> l'interface `energystorage` avec un `currentPower` qui ne bougera jamais de
|
||||
> zéro.** C'est le cas des appareils SunSpec génériques. La batterie apparaît
|
||||
> alors dans le bilan, comptée pour 0 W — ce qui se lit « ni charge ni
|
||||
> décharge », et non « je ne sais pas ».
|
||||
|
||||
Ce cas est **annoncé une fois par thing** dans le journal de `nymead`, au
|
||||
démarrage ou au premier relevé qui suit la découverte. Deux formulations, selon
|
||||
la cause :
|
||||
|
||||
```
|
||||
W | SunSpec: Storage "<nom>" has no model 160 on its connection: currentPower
|
||||
has no register to read and will stay at 0. This storage is visible to the
|
||||
energy balance but not measurable.
|
||||
|
||||
W | SunSpec: Storage "<nom>" has a model 160 but no StCha/StDisCha block:
|
||||
currentPower has nothing to read and will stay at 0.
|
||||
```
|
||||
|
||||
La seconde forme est le cas d'un onduleur hybride **sans batterie câblée** : il
|
||||
publie `N = 2` (les deux strings PV) et aucun bloc batterie.
|
||||
|
||||
À vérifier en premier sur un site où `currentPowerStorage` reste à zéro :
|
||||
|
||||
```bash
|
||||
journalctl -u nymead | grep -i "will stay at 0"
|
||||
```
|
||||
|
||||
Si la ligne est là, le défaut est dans ce que l'appareil publie, pas dans nymea.
|
||||
Si elle est absente, la batterie est mesurée et le zéro est une vraie mesure.
|
||||
|
||||
L'état `capacity` (kWh) vient de `WHRtg` du modèle 120. Quand la plaque
|
||||
signalétique ne l'implémente pas, l'état est **laissé tel quel** plutôt que remis
|
||||
à zéro : un zéro se lirait comme une mesure. Aucune règle du moteur ne lit
|
||||
`capacity` à ce jour.
|
||||
|
||||
Détail du raisonnement, de la mesure du signe et de l'écart assumé :
|
||||
`DIVERGENCES.md`, entrée **D-9**.
|
||||
|
||||
@ -198,6 +198,13 @@ BlockRole classifyBlock(const QString &inputIdString);
|
||||
|
||||
struct ModuleReading {
|
||||
QString inputIdString;
|
||||
|
||||
/* Already scaled by DCW_SF, in watts. That factor is auto-scaled by the
|
||||
* inverter at runtime - measured moving from -2 to -1 between an idle and a
|
||||
* charging battery on the same machine - so it is only valid alongside the
|
||||
* powers of the same Modbus transaction. Whoever fills this struct owns
|
||||
* that guarantee; see the read order note above
|
||||
* IntegrationPluginSunSpec::storageModuleReadings(). */
|
||||
double dcPower;
|
||||
};
|
||||
|
||||
|
||||
@ -1662,7 +1662,12 @@ void IntegrationPluginSunSpec::onRefreshTimer()
|
||||
|
||||
/* Companion models of the storage things. Deduplicated: two storage things
|
||||
* on the same inverter share one model 160, and reading it twice per cycle
|
||||
* would double the Modbus traffic for the same 90 registers. */
|
||||
* would double the Modbus traffic for the same 90 registers.
|
||||
*
|
||||
* Whole model, one readBlockData() each, never a partial read: the power of
|
||||
* the battery blocks is only meaningful next to the scale factor of the
|
||||
* same transaction. See the read order note above
|
||||
* storageModuleReadings(). */
|
||||
QSet<SunSpecModel *> storageCompanions;
|
||||
foreach (SunSpecMpptModel *model, m_storageMpptModels.values())
|
||||
storageCompanions.insert(model);
|
||||
@ -2193,6 +2198,40 @@ void IntegrationPluginSunSpec::bindStorageCompanionModels(Thing *thing, SunSpecM
|
||||
updateStorageCapacity(thing);
|
||||
}
|
||||
|
||||
/* ORDRE DE LECTURE - ne pas "optimiser" la lecture du modele 160.
|
||||
*
|
||||
* `dcPower()` rend une valeur DEJA multipliee par `DCW_SF`. Ce facteur d'echelle
|
||||
* n'est pas une constante de l'appareil : Fronius l'auto-scale EN
|
||||
* FONCTIONNEMENT, sans hysteresis. Mesure du 2026-08-26 sur le banc, meme
|
||||
* onduleur, deux relevees a quelques secondes d'intervalle :
|
||||
*
|
||||
* batterie a l'arret, PV faible -> DCW_SF = -2
|
||||
* batterie en charge 2000 W -> DCW_SF = -1
|
||||
*
|
||||
* Un `DCW` brut de 20000 vaut donc 200,0 W avec le premier facteur et 2000,0 W
|
||||
* avec le second. Lire la puissance avec un facteur qui n'est pas celui de la
|
||||
* MEME transaction, c'est se tromper d'un ordre de grandeur - meme famille de
|
||||
* degat que l'ordre des mots des float32 (D-8), mais bien plus discret : la
|
||||
* valeur reste plausible, elle est juste dix fois trop petite ou trop grande.
|
||||
*
|
||||
* Ce qui protege aujourd'hui :
|
||||
*
|
||||
* 1. `SunSpecModel::readBlockData()` lit les 90 registres du modele 160 en UNE
|
||||
* transaction Modbus (la limite de decoupage est a 125 registres), donc les
|
||||
* facteurs d'echelle et les puissances viennent du meme instantane ;
|
||||
* 2. `SunSpecModel::handleNewBlockData()` appelle `processBlockData()` du bloc
|
||||
* FIXE - qui relit `DCA_SF`/`DCV_SF`/`DCW_SF`/`DCWH_SF` - AVANT de traiter
|
||||
* les blocs repetes, qui lisent ce facteur chez leur parent.
|
||||
*
|
||||
* Ces deux proprietes sont dans libnymea-sunspec, pas ici, et rien dans le
|
||||
* compilateur ne les tient. Les casser en amont - decouper la lecture, mettre
|
||||
* les facteurs en cache a la decouverte, traiter les blocs repetes en premier,
|
||||
* ou lire un bloc repete isolement - casserait cette fonction SANS AUCUN SIGNE
|
||||
* EXTERIEUR : pas d'erreur Modbus, pas de denormal, un appareil qui se declare
|
||||
* `connected`. Toute reprise de la lecture du modele 160 doit etre rejouee au
|
||||
* banc contre une puissance batterie connue, pas seulement compilee.
|
||||
*
|
||||
* Contrat Fronius I5, et D-9 dans DIVERGENCES.md. */
|
||||
QList<FroniusRules::ModuleReading> IntegrationPluginSunSpec::storageModuleReadings(Thing *thing) const
|
||||
{
|
||||
QList<FroniusRules::ModuleReading> readings;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user