docs(brief): trois constats du moteur vers l'app, dont un retiré
1. SetChargingInfo remplace un targetPercentage absent par 80, EN SILENCE — réponse EnergyErrorNoError, aucun avertissement, et la seule trace au journal imprime déjà 80. L'objet est reconstruit entier à chaque écriture : un champ omis retombe sur son défaut, il n'est pas « inchangé ». L'app doit lire-patcher-réécrire, et ne pas afficher 80 comme une cible que l'utilisateur aurait choisie. 2. L'alerte sur SampleRate15Mins était FAUSSE et je la retire : ma sonde lisait currentPowerProduction là où les entrées de log portent production. Vérifié dans les deux sens — 51 échantillons non nuls par RPC, 7908 lignes en base. L'historique 90 jours n'est bloqué par rien. Le piège réel est le nom des champs, sans préfixe currentPower. 3. loads[] contient enfin les bornes (buildTelemetry les sautait) et porte un champ funding surplus/grid, sans lequel sommer allocatedW donne un écart ininterprétable. Avec la mise en garde qui va avec : adjustEvChargers() re-décide après le waterfall, donc pour une borne l'allocation publiée est une intention, pas un ordre. 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
c1af47476d
commit
e84f59d04a
149
docs/BRIEF_depuis_plugin.md
Normal file
149
docs/BRIEF_depuis_plugin.md
Normal file
@ -0,0 +1,149 @@
|
||||
# Brief — du moteur vers l'app
|
||||
|
||||
Ce que l'agent plugin (`etm-powersync-energy-plugin-etm`) constate et qui **change quelque
|
||||
chose côté app**. Une section datée par lot. Le plus récent en haut.
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-26 — passage machine 3g-1 sur le banc `.75`
|
||||
|
||||
**Contexte** : `powersync-energy-plugin-nymea 1.15.2+etm22` déployé sur `.75`. Relevé complet
|
||||
dans le dépôt du plugin, `docs/RELEVE_3g1.md`.
|
||||
|
||||
---
|
||||
|
||||
### 1. `SetChargingInfo` substitue `targetPercentage = 80` à un champ absent — **en silence**
|
||||
|
||||
**Envoyer un `chargingInfo` sans `targetPercentage` ne laisse pas la valeur en place : elle est
|
||||
remplacée par 80.** Constaté en restaurant le banc — le champ valait `0`, j'ai réécrit l'objet
|
||||
sans lui, il valait `80` à la relecture.
|
||||
|
||||
**Cause** : `ChargingInfo` est reconstruit intégralement à chaque écriture (`unpack<ChargingInfo>`),
|
||||
et son membre C++ porte `m_targetPercentage = 80` comme valeur par défaut. Un champ absent du
|
||||
JSON n'est donc pas « inchangé » : il **retombe sur le défaut**. Cela vaut pour tout l'objet,
|
||||
`targetPercentage` n'est que le cas où le défaut n'est pas neutre.
|
||||
|
||||
**Et la substitution est muette.** Vérifié sur machine, journal du banc à 17:35:56 :
|
||||
|
||||
- la réponse RPC est `EnergyErrorNoError` — aucune erreur, aucun avertissement ;
|
||||
- le seul avertissement du chemin d'écriture ne se déclenche que pour `targetPercentage > 100` ;
|
||||
- la seule trace au journal est une ligne de **debug** qui imprime `Target percentage: 80`,
|
||||
c'est-à-dire le **résultat** — jamais le fait qu'un champ était absent ;
|
||||
- la notification `ChargingInfoChanged` qui suit porte 80, comme si l'utilisateur l'avait choisi.
|
||||
|
||||
> **Ce que l'app doit en faire.** Toujours envoyer `targetPercentage`, même inchangé — donc
|
||||
> lire l'objet, patcher le champ voulu, réécrire l'objet complet (le même chemin que pour
|
||||
> `priority` dans `LoadConfig`). Et **ne jamais afficher `targetPercentage` comme une cible
|
||||
> choisie par l'utilisateur** tant que l'écran n'a pas lui-même écrit cette valeur : 80 peut
|
||||
> être une valeur héritée d'une écriture qui ne parlait pas de cible du tout.
|
||||
|
||||
Le risque concret : un écran qui change le seul mode de charge repose une cible de 80 % à
|
||||
l'insu de l'utilisateur, sans que rien ne le signale.
|
||||
|
||||
---
|
||||
|
||||
### 2. `GetPowerBalanceLogs` en `SampleRate15Mins` — **RIEN À SIGNALER, l'alerte était fausse**
|
||||
|
||||
J'avais signalé que ce taux rendait des échantillons tous à zéro. **C'était mon erreur de
|
||||
lecture, pas un défaut de la box.** Je la consigne quand même pour qu'elle ne circule pas.
|
||||
|
||||
Ce qui s'est passé : ma sonde lisait les clés `currentPowerProduction` / `currentPowerConsumption`
|
||||
alors que les entrées de log portent **`production`, `consumption`, `acquisition`, `storage`** —
|
||||
sans le préfixe `currentPower`, contrairement à `GetPowerBalance`. Tout tombait donc sur le
|
||||
défaut `0`.
|
||||
|
||||
**Vérifié après coup, dans les deux sens :**
|
||||
|
||||
- par RPC, mêmes bornes de temps, avec les bonnes clés : **51 échantillons, 51 non nuls** ;
|
||||
- dans la base du banc (`/var/lib/nymea/energylogs.sqlite`), la table `powerBalance` porte
|
||||
**7 908 lignes à `sampleRate = 15`**, du 2026-06-05 à aujourd'hui 16:30, valeurs réelles.
|
||||
Les autres taux (1, 60, 180, 1440, 10080, 43200) sont peuplés aussi.
|
||||
|
||||
> **Conséquence pour l'app : l'historique 90 jours n'est bloqué par rien de ce côté.** Le seul
|
||||
> piège est le nom des champs — **les entrées de log ne portent pas le préfixe `currentPower`**,
|
||||
> à la différence de `GetPowerBalance`. Un modèle qui réutilise les mêmes clés pour les deux
|
||||
> appels lira des zéros sans la moindre erreur. C'est exactement ce qui m'est arrivé.
|
||||
|
||||
---
|
||||
|
||||
### 3. Contrat neuf — `loads[].funding`, et les bornes entrent enfin dans `loads[]`
|
||||
|
||||
#### 3.1 Les bornes franchissent la frontière RPC (`+etm22`)
|
||||
|
||||
Jusqu'ici `buildTelemetry()` **sautait les bornes** : elles étaient arbitrées, leurs décisions
|
||||
apparaissaient au journal du plugin, mais `GetLoadTelemetry` n'en publiait aucune. L'écran ne
|
||||
pouvait donc pas les voir. C'est corrigé.
|
||||
|
||||
`loads[]` contient désormais les bornes de recharge, avec leur `decision`, leur `mechanism`
|
||||
(`kind: "evcharger"`, `chargingEnabled`, `currentA`, `phaseCount`, `pluggedIn`) et leur
|
||||
`faultCode` le cas échéant.
|
||||
|
||||
Les motifs EV **`EV_SURPLUS`, `EV_ECO_MIN`, `EV_IDLE` ne sont plus émis** : une borne reçoit
|
||||
maintenant les motifs communs à tous les mécanismes — `SURPLUS_SETPOINT`, `BELOW_MIN_POWER`,
|
||||
`SURPLUS_INSUFFICIENT`, `LOAD_UNAVAILABLE`. Les trois codes restent rendus côté plugin pour
|
||||
qu'un journal ancien reste lisible, mais plus rien ne les produit. `EV_SPOT_MARKET` et
|
||||
`EV_DEADLINE` subsistent.
|
||||
|
||||
#### 3.2 Le champ `funding` — `"surplus"` ou `"grid"`
|
||||
|
||||
**Nouveau, optionnel (`o:`), présent dès qu'il y a un plan.** Il devient nécessaire du seul fait
|
||||
que les bornes sont dans `loads[]`, car deux d'entre elles peuvent y être pour des raisons
|
||||
différentes :
|
||||
|
||||
| `funding` | d'où vient l'allocation | où elle est comptée |
|
||||
|---|---|---|
|
||||
| `"surplus"` | la cascade (waterfall PV) | dans `budget.allocatedW` |
|
||||
| `"grid"` | le proxy — échéance de départ, tarif dynamique — qui **soutire au réseau** | dans `budget.evReservedW` |
|
||||
|
||||
> **Sommer `loads[].allocatedW` sans filtrer sur `funding == "surplus"` donne un écart avec
|
||||
> `budget.allocatedW` que rien ne permet d'interpréter** : défaut du moteur, ou borne servie au
|
||||
> réseau ? L'identité à vérifier est donc : **somme des `allocatedW` des charges financées au
|
||||
> surplus == `budget.allocatedW`**.
|
||||
|
||||
**Omis en mode dégradé** : sans plan, il n'y a pas de financement. Règle maison habituelle —
|
||||
omis, jamais nul.
|
||||
|
||||
#### 3.3 ⚠️ Mise en garde — pour une borne, l'allocation publiée est une INTENTION, pas un ordre
|
||||
|
||||
**`adjustEvChargers()` re-décide le surplus après le waterfall et passe outre le refus de
|
||||
l'arbitre.** Observé sur le banc, deux lignes consécutives du même cycle (17:34:00) :
|
||||
|
||||
```
|
||||
[Arbitre] "{09e520fd…}" → "Budget 1096 W sous le premier palier … — 0 W"
|
||||
---------------- Adjusting chargers ----------------
|
||||
Executing action Simulated wallbox to power: ON, Charging current: 6A, Issuer: Surplus
|
||||
```
|
||||
|
||||
L'arbitre refuse et publie `BELOW_MIN_POWER` ; le chemin d'exécution du proxy commande `ON` à
|
||||
6 A une milliseconde plus tard. **Tant que ce n'est pas traité (lot 3g-2, côté plugin), ce que
|
||||
la télémétrie publie pour une borne n'est pas ce qui est commandé.**
|
||||
|
||||
> **Ce que l'app doit en faire.** Pour les bornes — et seulement pour elles —, ne pas présenter
|
||||
> `allocatedW` et son motif comme l'état de la borne. Ce sont **la décision de l'arbitre**, ce
|
||||
> qui n'est pas la même chose que ce que la borne fait. L'état réel se lit dans
|
||||
> `mechanism.chargingEnabled` / `mechanism.currentA`, qui viennent du Thing.
|
||||
>
|
||||
> Concrètement : ne pas écrire « la borne ne charge pas parce que le budget est sous le
|
||||
> plancher » à partir du seul motif. Les deux peuvent se contredire aujourd'hui, et c'est
|
||||
> l'écran qui aurait l'air faux.
|
||||
>
|
||||
> Cette réserve **ne vaut pas** pour l'ECS, la PAC et les autres charges pilotées : là,
|
||||
> l'allocation publiée **est** la commande.
|
||||
|
||||
---
|
||||
|
||||
### 4. Rappel — clés ARB à prévoir côté app
|
||||
|
||||
Deux motifs sont émis par le banc et n'ont pas encore de rendu français dans `telemetry_text.dart` :
|
||||
|
||||
- **`BELOW_MIN_POWER`** — params `budgetW`, `minPowerW`, et `state` pour les mécanismes à états
|
||||
(SG-Ready). Déjà traité dans `telemetry_text.dart`, à vérifier côté ARB.
|
||||
- **`BATTERY_RESERVE`** — params **`socPercent`, `reservePercent`, `withheldW`**. Le
|
||||
`kCodesReserveBatterie` de `telemetry_text.dart` est encore **vide**, et les clés qu'il
|
||||
cherche (`batteryLevel`/`soc`, `threshold`/`batteryLevelConsideration`) **ne sont pas celles
|
||||
publiées**. À aligner sur les trois noms ci-dessus.
|
||||
|
||||
Et un réglage à exposer dans l'écran installateur : **`batteryLevelConsideration`** — son défaut
|
||||
d'usine est passé de 0,9 à **0,2** (à 0,9 avec une batterie à 50 %, le HEMS ne pilote plus rien,
|
||||
sans erreur ni panne). En revanche **`minimalChargingCurrent` ne doit jamais être présenté comme
|
||||
configurable** : il n'existe pas comme réglage, il se déduit de la borne et de la voiture.
|
||||
Loading…
x
Reference in New Issue
Block a user