docs(motif): deux réponses de la même box, deux conventions — et aucun signal
Le piège des accolades n'était pas un détail de chargingModeOf(). C'est la deuxième
occurrence en une semaine d'un motif qui se répétera : deux appels JSON-RPC de la même box,
portant le même objet, n'emploient pas la même convention, et RIEN ne le dit. Écrit comme
tel dans docs/UI_data_contract.md §9, avec renvoi depuis les deux sites de code et depuis
tools/rpc/README.md — là où quelqu'un croise deux réponses brutes.
Les occurrences, leur symptôme :
· GetPowerBalance (currentPowerProduction…) × GetPowerBalanceLogs (production…) →
tout tombe à zéro, l'historique paraît vide sur 7 960 échantillons bien présents
· GetChargingInfos (evChargerId sans accolades) × GetLoadConfig (id avec) →
« mode non lu » sur une borne parfaitement lue, et la note « hors arbitrage » muette
· famille voisine, même silence : currentPower* en W × total* en kWh, facteur 1000
Ce qui en fait un motif et pas trois anecdotes : le symptôme n'est JAMAIS une exception,
c'est une valeur neutre plausible, et un test unitaire qui construit ses deux côtés avec la
même convention passe aussi. D'où les trois gestes écrits au §9 — sonder les deux appels
côte à côte sur les charges utiles brutes, éprouver le rapprochement sur les deux formes
réelles, et se méfier d'un vide qui ne coûte rien à expliquer.
Et le lien avec une règle qu'on suivait déjà sans la relier à ça : une valeur neutre issue
d'un croisement raté est indiscernable d'une vraie absence. « Non lu » laisse une chance de
trouver le bug ; « 0 » l'enterre.
── Campagne V2C arrêtée, et la réserve sessionEnergy avec elle ────────────────
La Trydan non-Pro est en WiFi ESP32 sans repli filaire, lien inexploitable à deux mètres du
point d'accès : 70 % de scrutations avortées, 12 bascules en injoignable en vingt minutes.
Le modèle sort du catalogue ETM ; seule la Trydan Pro (port Ethernet) y reste.
La trace qui lève C1 — chargeEnergy à zéro AVEC un sessionEnergy qui continue — n'a donc
pas été prise et ne viendra pas de cette borne. Les notes restent en place telles quelles,
avec la raison mise à jour : elles seront levées sur une Trydan Pro. Aucun avancement de
charge affiché d'ici là.
164 tests, flutter analyze inchangé à 27 remarques.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQbZKrWqsMFP1Lh2jjjd9f
This commit is contained in:
parent
e0264b5ed6
commit
376acb2181
22
TODO.md
22
TODO.md
@ -35,12 +35,22 @@
|
||||
prêtes, les motifs n'ont **jamais été vus sur machine**. Signaler la première occurrence
|
||||
réelle. Ne rien bâtir autour d'ici là.
|
||||
|
||||
- [ ] ⏳ **`sessionEnergy` est CONSTRUIT par le plugin — il ne reste que la trace.**
|
||||
Le plugin V2C `1.15.2+etm13` (déployé le 2026-08-28 à midi) accumule les incréments de
|
||||
`chargeEnergy`, détecte les remises à zéro, et ne repart qu'à l'ouverture d'une session :
|
||||
ce n'est **pas** un alias du registre `0x0BC4`. La campagne de validation en cours produit
|
||||
le cas qui lève la réserve — `chargeEnergy` à zéro, `sessionEnergy` qui continue. **Aucun
|
||||
avancement affiché avant cette trace** (LM-1009 §C1).
|
||||
- [ ] ⏳ **`sessionEnergy` est CONSTRUIT par le plugin — mais la trace n'existe pas, et
|
||||
n'existera pas sur cette borne.** Le plugin V2C `1.15.2+etm13` (déployé le 2026-08-28 à
|
||||
midi) accumule les incréments de `chargeEnergy`, détecte les remises à zéro, et ne repart
|
||||
qu'à l'ouverture d'une session : ce n'est **pas** un alias du registre `0x0BC4`.
|
||||
**La campagne V2C est arrêtée le 2026-08-28** — la Trydan non-Pro est en WiFi ESP32 sans
|
||||
repli filaire, lien inexploitable à deux mètres du point d'accès (70 % de scrutations
|
||||
avortées, 12 bascules en injoignable en vingt minutes) ; le modèle sort du catalogue ETM,
|
||||
seule la **Trydan Pro** (port Ethernet) y reste. Le cas qui lève la réserve —
|
||||
`chargeEnergy` à zéro, `sessionEnergy` qui continue — se verra sur elle. **Aucun
|
||||
avancement affiché d'ici là** (LM-1009 §C1), et la note reste en place telle quelle.
|
||||
|
||||
- [x] ~~Le piège des accolades~~ — **écrit comme MOTIF le 2026-08-28**, pas comme correctif
|
||||
local : `docs/UI_data_contract.md` §9, « Deux réponses de la même box, deux conventions ».
|
||||
Deux occurrences en une semaine (clés de log sans préfixe `currentPower`, identifiants de
|
||||
borne avec/sans accolades), la règle, la façon de le chercher, et un renvoi depuis les
|
||||
deux sites de code plus `tools/rpc/README.md` — là où quelqu'un croise deux réponses.
|
||||
|
||||
- [ ] **Historique des états d'une borne — `sessionEnergy` n'est pas enregistré.**
|
||||
`suggestLogging` est écrasé par les préférences persistées de `things.conf` sur un Thing
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
|
||||
> Compagnon de `DASHBOARD_SPEC.md`. Tranche la question récurrente
|
||||
> « **qui calcule les valeurs affichées ?** » pour chaque écran.
|
||||
> Rev. 6 — ajout du **§9** : deux réponses de la même box peuvent employer deux
|
||||
> conventions sans qu'aucun signal ne le dise, et l'erreur est toujours muette.
|
||||
> Deux occurrences en une semaine (clés de log sans préfixe `currentPower`,
|
||||
> identifiants de borne avec/sans accolades).
|
||||
> Rev. 5 — sonde `.75` : réserve décision-2 **levée** (cumuls séparés) ; ajout de
|
||||
> 3 garde-fous : 2 conventions de signe (§7.1), buckets par Δ-de-cumuls (§7.3),
|
||||
> gestion reset compteurs (§2.1/§8.1) ; RP calquées sur le template nymea, Influx
|
||||
@ -357,6 +361,54 @@ du « Plan de la journée »).
|
||||
|
||||
---
|
||||
|
||||
## 9. Deux réponses de la même box, deux conventions — et aucun signal
|
||||
|
||||
> **Motif, pas anecdote.** Deux appels JSON-RPC de la même box, portant la même grandeur ou
|
||||
> le même objet, n'emploient pas forcément la même convention. Aucun schéma, aucune erreur,
|
||||
> aucun journal ne le dit : **la divergence est toujours muette**, et elle se manifeste par
|
||||
> un écran vide, un zéro, ou un « non lu » parfaitement crédible. Deux occurrences en une
|
||||
> semaine, chacune découverte en croisant deux réponses.
|
||||
|
||||
### Les occurrences connues
|
||||
|
||||
| Croisement | Convention A | Convention B | Symptôme si on les mélange |
|
||||
|---|---|---|---|
|
||||
| `Energy.GetPowerBalance` × `Energy.GetPowerBalanceLogs` | `currentPowerProduction`, `currentPowerConsumption`, … | `production`, `consumption`, `acquisition`, `storage` — **sans préfixe** | tout tombe sur le défaut `0` : l'historique paraît vide alors que la base porte 7 960 échantillons |
|
||||
| `NymeaEnergy.GetChargingInfos` × `NymeaEnergy.GetLoadConfig` | `evChargerId` **sans** accolades — `4d1adafd-…` | id de charge **avec** accolades — `{4d1adafd-…}` | aucune correspondance : l'écran affiche « mode non lu » sur une borne parfaitement lue, et la note « borne hors arbitrage » ne sort jamais |
|
||||
| *(famille voisine, même silence)* `currentPower*` en **W** × `total*` en **kWh** | watts | kilowattheures | facteur 1000 : une journée de 78 kWh s'affiche « 78 Wh » |
|
||||
|
||||
### La règle
|
||||
|
||||
**Ne jamais rapprocher deux réponses RPC sur une clé ou un identifiant sans l'avoir
|
||||
normalisé — et écrire la normalisation à un seul endroit, nommé.** Dans cette app :
|
||||
`NymeaService.chargingModeOf()` pour les identifiants, le bloc de clés de
|
||||
`fetchPowerBalanceLogs()` pour les noms de champs. Une comparaison brute recopiée dans un
|
||||
troisième écran rouvrira le même trou.
|
||||
|
||||
### Comment on le trouve — et pourquoi il faut le chercher exprès
|
||||
|
||||
Le symptôme n'est **jamais** une exception : c'est une valeur neutre plausible. Un test
|
||||
unitaire qui construit ses deux côtés avec la même convention passe aussi. Trois gestes,
|
||||
dans cet ordre :
|
||||
|
||||
1. **Sonder les deux appels côte à côte** et comparer les charges utiles brutes, pas les
|
||||
modèles Dart — `dart tools/rpc/probe.dart 192.168.1.75 <Méthode>` pour chacun.
|
||||
2. **Éprouver le rapprochement lui-même**, sur les deux formes réelles. Les tests d'accolades
|
||||
(`test/merged_config_screen_test.dart`) sont écrits exactement pour ça, avec les deux
|
||||
graphies de la même borne.
|
||||
3. **Se méfier d'un vide qui ne coûte rien à expliquer.** « Aucune donnée sur cette
|
||||
période », « mode non lu », « 0 W » : quand une lecture rend un neutre sur une
|
||||
installation qu'on sait vivante, la première hypothèse est le croisement, pas la box.
|
||||
|
||||
### Ce que ça implique pour l'affichage
|
||||
|
||||
Une valeur neutre issue d'un croisement raté est **indiscernable** d'une vraie absence.
|
||||
C'est la raison de fond de la règle « ne jamais afficher zéro pour une absence » qui
|
||||
traverse `EvChargerLive`, `DailyBalance` et `LoadMechanism` : dire « non lu » laisse une
|
||||
chance de découvrir le bug ; afficher `0` l'enterre.
|
||||
|
||||
---
|
||||
|
||||
## Récap des décisions
|
||||
|
||||
1. Héos au centre du flow : **Option A — métaphore « cerveau » assumée**
|
||||
|
||||
@ -24,8 +24,9 @@
|
||||
/// portés ici pour autant. Le plugin V2C `1.15.2+etm13` **construit** ce `sessionEnergy` —
|
||||
/// il accumule les incréments de `chargeEnergy`, détecte les remises à zéro, et ne repart
|
||||
/// qu'à l'ouverture d'une session — mais la trace qui le montre à l'œuvre (`chargeEnergy`
|
||||
/// à zéro, `sessionEnergy` qui continue) sort de la campagne en cours. **Aucun avancement
|
||||
/// n'est affiché avant elle** : LM-1009 §C1, cf. `LoadMechanism`.
|
||||
/// à zéro, `sessionEnergy` qui continue) **n'a pas été prise** : la campagne V2C est
|
||||
/// arrêtée, la Trydan non-Pro étant inexploitable en WiFi. Elle se prendra sur une Trydan
|
||||
/// Pro. **Aucun avancement n'est affiché avant elle** : LM-1009 §C1, cf. `LoadMechanism`.
|
||||
library;
|
||||
|
||||
import 'energy_data.dart' show ChargingMode;
|
||||
|
||||
@ -199,11 +199,17 @@ class LoadMechanism {
|
||||
/// incréments de `chargeEnergy`, détecte les remises à zéro, et ne repart qu'à
|
||||
/// l'ouverture d'une session. L'égalité observée s'explique donc d'elle-même.
|
||||
///
|
||||
/// **La réserve tient quand même, jusqu'à la trace.** La campagne de validation en cours
|
||||
/// produit exactement le cas qui la lève — `chargeEnergy` à zéro, `sessionEnergy` qui
|
||||
/// continue. Tant qu'elle n'est pas au dossier, **aucun avancement de charge n'est
|
||||
/// affiché** : la condition C1 de LM-1009 reste entière. Ce n'est pas de la défiance
|
||||
/// envers le plugin, c'est la règle qui a évité d'afficher `chargeEnergy` lui-même.
|
||||
/// **La réserve tient, et elle tiendra un moment : la trace ne viendra pas de cette
|
||||
/// borne.** La campagne V2C est arrêtée le 2026-08-28. La Trydan **non-Pro** est en WiFi
|
||||
/// ESP32 sans repli filaire, et le lien s'est révélé inexploitable à deux mètres du point
|
||||
/// d'accès : 70 % de scrutations avortées, 12 bascules en injoignable en vingt minutes.
|
||||
/// Le modèle sort du catalogue ETM ; seule la **Trydan Pro**, qui a un port Ethernet, y
|
||||
/// reste. C'est sur elle que le cas se verra.
|
||||
///
|
||||
/// Donc : `chargeEnergy` à zéro **avec** un `sessionEnergy` qui continue **n'a pas été
|
||||
/// observé**, et **aucun avancement de charge n'est affiché** — la condition C1 de
|
||||
/// LM-1009 reste entière. Ce n'est pas de la défiance envers le plugin, c'est la règle
|
||||
/// qui a évité d'afficher `chargeEnergy` lui-même.
|
||||
///
|
||||
/// > ⚠️ **`sessionEnergy` ne sera pas historisé.** `suggestLogging` est écrasé par les
|
||||
/// > préférences persistées de `things.conf` sur un Thing déjà appairé, et la V2C n'y a
|
||||
|
||||
@ -7,8 +7,8 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
import '../models/daily_balance.dart';
|
||||
import '../models/energy_data.dart';
|
||||
import '../models/ev_charger_live.dart'; // EnergyData, HistoryPoint, PowerBalanceEntry
|
||||
import '../models/energy_data.dart'; // EnergyData, HistoryPoint, PowerBalanceEntry
|
||||
import '../models/ev_charger_live.dart';
|
||||
import '../models/modbus_models.dart'; // ModbusRtuMaster, SerialPort
|
||||
import '../models/nymea_models.dart';
|
||||
import 'energy_ratios.dart'; // seam interim ratios (§2.1)
|
||||
@ -945,6 +945,10 @@ class NymeaService extends ChangeNotifier {
|
||||
/// Vérifié sur `.75` le 2026-08-27, avec les clés ci-dessous : **7 960 échantillons sur
|
||||
/// 90 jours** en `SampleRate15Mins`, dont 4 975 avec une production non nulle. Rien ne
|
||||
/// bloque l'historique 90 jours de ce côté.
|
||||
///
|
||||
/// > Ce n'est pas un cas isolé : deux réponses de la même box peuvent employer deux
|
||||
/// > conventions sans qu'aucun signal ne le dise, et l'erreur est toujours **muette**.
|
||||
/// > Le motif et ses occurrences connues sont dans `docs/UI_data_contract.md` §9.
|
||||
Future<List<PowerBalanceEntry>> fetchPowerBalanceLogs({
|
||||
required DateTime from,
|
||||
required DateTime to,
|
||||
@ -1375,6 +1379,9 @@ class NymeaService extends ChangeNotifier {
|
||||
/// et c'est la même borne. Comparer les deux tels quels ne rend jamais rien — en
|
||||
/// silence, ce qui est le pire des cas : l'écran conclurait « mode non lu » sur une borne
|
||||
/// parfaitement lue.
|
||||
///
|
||||
/// Deuxième occurrence connue du même motif — cf. `docs/UI_data_contract.md` §9, et la
|
||||
/// première : les clés de `GetPowerBalanceLogs`, sans le préfixe `currentPower`.
|
||||
String? chargingModeOf(String id) {
|
||||
final nu = id.replaceAll('{', '').replaceAll('}', '').toLowerCase();
|
||||
for (final e in _chargingInfos.entries) {
|
||||
|
||||
@ -27,6 +27,13 @@ dart tools/rpc/<script>.dart [host] [args…] # host par défaut : 192.168.1
|
||||
| `domain_assign.dart` | Classe des charges par `o:domain`, **par le code d'écriture de l'app**. | ⚠️ **OUI** |
|
||||
| `fault_probe.dart` | Provoque une charge en défaut, éprouve `ClearLoadFault`, restaure. | ⚠️ **OUI** |
|
||||
|
||||
> ⚠️ **Avant de rapprocher deux réponses.** Deux appels de la même box n'emploient pas
|
||||
> forcément la même convention — clés de champs, graphie des identifiants, unités — et
|
||||
> **rien ne le signale** : le croisement raté rend un neutre plausible, jamais une erreur.
|
||||
> Le motif, ses occurrences connues et la façon de le chercher sont dans
|
||||
> `docs/UI_data_contract.md` §9. C'est ici, la sonde en main, qu'il se voit le plus vite :
|
||||
> comparer les charges utiles **brutes** des deux appels, jamais les modèles Dart.
|
||||
|
||||
### `probe.dart`
|
||||
|
||||
```bash
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user