docs: journal du passage sur appareil — §7-9 et §7-5 clos, §7-6 inatteignable
Premier passage de l'app sur matériel (Redmi Note 9S). MIUI refuse INJECT_EVENTS : Patrick a piloté, l'agent a observé. Captures et trace dans screenshots/ (ignoré). Acquis à l'écran : - §7-9 : trois Hello, tous « demandée fr_FR → retenue fr_FR », la valeur relue venant de m_clientLocales côté box. Mais la locale de l'APPAREIL (en-GB) ne part pas : _requestedLocale est figé à fr_FR et rien ne câble l'un sur l'autre. - §7-5 : l'écran suit l'arbitre sans un geste — budget, allocation, motif recomposé, et le recrédit anti-clignotement visible en clair. - Écriture de domaine faite au doigt, confirmée par la box et le fichier persisté. - SetLoadConfig 19:53:46.007 → LoadConfigChanged .288 → ack .303 : la confirmation a battu l'acquittement de 15 ms. Conclure sur l'ack, c'est courir après sa propre notification. §7-6 : l'état « données anciennes » est INATTEIGNABLE par perte de lien — la garde de routage évacue vers /installations une seconde après la coupure, bien avant le seuil de 180 s. Le compteur de fraîcheur, lui, a été vu vivre. Seul sinceCycleAdvanced peut donc réellement déclencher le bandeau : l'arbitre figé socket vivante. Huit défauts constatés, aucun corrigé (constat d'abord) : pas de reconnexion automatique, /energy/setup qui décrit une autre installation que la vraie, cumuls aberrants venus de la box, thème ignoré, débordements, titre dupliqué, instantané effacé hors connexion, bruit de transport. Décisions de Patrick : l'ordre se règle dans « Charges pilotables » au glisser-déposer, /energy/loads disparaît, deux niveaux conservés, chip « À déclarer » → bouton Configurer ouvrant le mécanisme et ses Things. 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
31ef03fd4b
commit
47cc0b1f72
108
RECAP.md
108
RECAP.md
@ -152,6 +152,114 @@ dart run tools/rpc/domain_assign.dart 192.168.1.75 --clear --yes
|
||||
La charge fictive de la sonde de défaut a été **retirée**, et la configuration relue est
|
||||
identique clé par clé à celle lue avant la sonde.
|
||||
|
||||
## Passage sur appareil — 2026-08-25 (Redmi Note 9S, USB)
|
||||
|
||||
Premier passage de l'app sur matériel réel. `adb` voit l'appareil, mais **MIUI refuse
|
||||
l'injection d'événements** (`INJECT_EVENTS`) : captures et journaux passent, `input tap`
|
||||
non. Patrick a piloté à l'écran, l'agent a observé. Une capture par étape dans
|
||||
`screenshots/2026-08-25-passage-appareil/` (git-ignoré), trace complète dans son
|
||||
`trace-app.log`.
|
||||
|
||||
### Le pont qui a rendu la session possible
|
||||
|
||||
`flutter run` **n'imprime pas** les traces `dart:developer log()` — elles partent au VM
|
||||
Service, ni sur stdout ni dans logcat. Tout ce que `NymeaService._log()` journalise était
|
||||
donc invisible. Le pont `scratchpad/vm_logs.dart` s'abonne au flux `Logging` du VM Service
|
||||
et rend la trace. **Sans lui, aucun des constats ci-dessous n'était atteignable.** À
|
||||
récupérer au prochain passage (attention : horodater sur `event.timestamp`, pas à la
|
||||
réception — le VM Service livre l'historique bufférisé d'un coup).
|
||||
|
||||
### Critères
|
||||
|
||||
| Critère | Verdict sur appareil |
|
||||
|---|---|
|
||||
| **§7-9 locale** | ✅ **trois `Hello`** (19:37:59, 19:43:55, 19:50:55), tous `demandée "fr_FR" → retenue "fr_FR"`. La valeur relue vient de `m_clientLocales` côté box : c'est la box qui dit ce qu'elle a retenu, pas l'app qui se répond à elle-même. ⚠️ **La locale de l'APPAREIL ne part pas** : le téléphone est en `en-GB`, `_requestedLocale` est figé à `'fr_FR'` (`nymea_service.dart:61`) et rien ne câble l'un sur l'autre. Conforme au brief (app française seule), mais à savoir le jour où l'app parlera allemand. |
|
||||
| **§7-5 télémétrie vivante** | ✅ **à l'écran, sans un geste** : entre 19:51 et 19:52, budget 943 → 2 571 W, alloué 500 → 3 000 W, motif recomposé, « dernier cycle il y a 51 s » → « 41 s ». Le recrédit anti-clignotement est **visible** (0 → 500 W : la charge consommait déjà, on lui rend avant d'arrondir). |
|
||||
| **§7-6 fraîcheur** | ⚠️ **l'état « données anciennes » est INATTEIGNABLE par cette voie.** Détail ci-dessous. |
|
||||
| **Écriture (§7-4 depuis l'UI)** | ✅ affectation de domaine faite au doigt → « Configuration confirmée par la box » → `pac-terrain → heating` vérifié sur la box **et** dans le fichier persisté. |
|
||||
| **Acquittement ≠ application** | ✅ mesuré : `SetLoadConfig` à 19:53:46.007, `LoadConfigChanged` à **.288**, acquittement traité à **.303**. La confirmation a battu l'ack de 15 ms. |
|
||||
|
||||
### §7-6 — pourquoi la bascule n'a pas eu lieu
|
||||
|
||||
```
|
||||
20:13:00 dernière trame LoadTelemetryChanged
|
||||
20:13:20 coupure WiFi (adb shell svc wifi disable)
|
||||
20:13:21 🔌 Connection closed
|
||||
20:14:21 (T+60) → l'app est déjà sur « Installations »
|
||||
20:17:47 (T+260) → toujours là. Seuil de 180 s jamais atteint à l'écran.
|
||||
```
|
||||
|
||||
**La garde de routage évacue l'écran avant que la fraîcheur ait le temps d'expirer.**
|
||||
`hasActiveConnection` tombe à faux, le routeur redirige vers `/installations`, et l'écran
|
||||
qui devait virer au « données anciennes » n'existe plus.
|
||||
|
||||
Ce n'est pas un défaut de la fraîcheur — le compteur a été vu vivre et s'incrémenter à
|
||||
l'écran (51 s → 41 s → 6 s). C'est que **la perte de lien n'est pas le chemin qui mène à cet
|
||||
état**. Le seul déclencheur réel du bandeau est l'**arbitre figé socket vivante** : les
|
||||
trames continuent d'arriver, le `timestamp` cesse d'avancer. C'est exactement le cas que le
|
||||
battement de cœur du plugin existe pour rendre détectable — et il n'est pas provocable
|
||||
depuis le réseau : il faudrait figer l'arbitre sans tuer nymead.
|
||||
|
||||
**Conséquence de conception à retenir :** des trois signaux de fraîcheur, seul
|
||||
`sinceCycleAdvanced` peut réellement se déclencher dans l'app telle qu'elle est. Les deux
|
||||
autres (`cycleAge`, `sinceLastFrame`) sont court-circuités par l'éjection.
|
||||
|
||||
### Défauts constatés — aucun corrigé (constat d'abord, décision de Patrick)
|
||||
|
||||
1. **Aucune reconnexion automatique.** `_onDone()` pose `_connected = false` et notifie,
|
||||
point final. Une coupure WiFi d'une seconde éjecte vers « Installations », **fait
|
||||
retomber le mode installateur**, et l'installateur doit tout refaire à la main. Observé
|
||||
deux fois.
|
||||
2. **`/energy/setup` décrit autre chose que l'installation.** À 19:49 l'écran affichait
|
||||
« 6 / 6 rôles configurés · tout est assigné » avec un « Compteur réseau — Onduleur
|
||||
SolarEdge » — alors que `.75` n'a **aucun** SolarEdge (11 things : Fronius ×3, Terra AC,
|
||||
6 relais). La trace disait `SetRootMeter ignoré (sim-inverter)` : des assignations
|
||||
héritées du **mode démo** avaient survécu dans la vue d'une box réelle. Et dans l'autre
|
||||
sens : après réassignation, l'app annonçait « Compteur réseau — aucun appareil » quand la
|
||||
box avait bien `Fronius Three Phase Meter`. L'écran **affirme** dans les deux cas.
|
||||
Cinq `[LoadConfig] N descripteur(s) construits mais NON émis` pendant l'exercice.
|
||||
3. **Cumuls aberrants venus de la box.** `totalReturn = 9.10e+33 kWh`,
|
||||
`totalAcquisition = 7.53e+33`, `totalConsumption = -1.57e+33` — seul `totalProduction`
|
||||
est sain. L'app les affiche fidèlement, sur trois lignes, en notation scientifique. Le
|
||||
défaut est en amont (compteurs cumulés de l'experience-plugin energy sur ce banc), mais
|
||||
une valeur physiquement impossible ne doit pas être mise en forme comme une donnée. Les
|
||||
gardes de `EnergyRatiosInterim` ont tenu : ratios à `0 %` plutôt qu'une absurdité.
|
||||
4. **Thème ignoré sur l'écran des charges.** Couleurs claires en dur (`Scaffold`
|
||||
`0xFFF0F2F5`, AppBar blanche, textes `0xFF1A1A2E`) sur un téléphone en thème sombre :
|
||||
les titres de domaine et les libellés de charge passent en sombre-sur-sombre, presque
|
||||
illisibles. Reprises par mimétisme de l'écran existant — `CLAUDE.md` dit pourtant
|
||||
« utiliser `app_theme.dart` systématiquement ».
|
||||
5. **Trois débordements de texte** : « Palier 3 000 W sur 3 50… », « Maintien ON — 60 s
|
||||
restantes (au dernier cyc… », et « Autoconsommat/ion » coupé en plein mot au dashboard.
|
||||
6. **Titre d'écran dupliqué** : la clé ARB `installerLoadsTitle` vaut « Rôles & appareils »,
|
||||
soit le titre de l'AUTRE écran. Deux écrans, un seul titre.
|
||||
7. **`load()` hors connexion efface l'instantané.** `getLoadTelemetry()` renvoie `null` pour
|
||||
`!_connected` **et** pour « méthode absente » ; le provider traite les deux pareil et pose
|
||||
`_telemetry = null` → l'écran n'affiche rien, en silence, au lieu de « données anciennes »
|
||||
ou de « box déconnectée ». À corriger avec la refonte : ce chemin sera emprunté chaque
|
||||
fois que l'app est hors ligne.
|
||||
8. **Bruit de transport.** Les notifications nymea portent un `id` : `_processMessage` les
|
||||
journalise d'abord en « réponse non attendue (déjà timeout?) » avant de les traiter
|
||||
correctement. Et le heartbeat interroge `Energy.GetPowerBalance` **toutes les 5 s** alors
|
||||
que `Energy.PowerBalanceChanged` arrive en poussé.
|
||||
|
||||
### Décisions de Patrick prises pendant le passage
|
||||
|
||||
- **L'ordre de service se règle dans « Rôles & appareils » → « Charges pilotables »**, au
|
||||
glisser-déposer. **L'écran « Ordre de service des charges » (`/energy/loads`) disparaît**,
|
||||
et son bloc télémétrie part sur la carte de chaque charge.
|
||||
- **Le regroupement à deux niveaux est conservé** (domaines ordonnés, charges ordonnées
|
||||
dedans).
|
||||
- Le chip « À déclarer dans Configurer » devient un **bouton Configurer**, qui ouvre le
|
||||
détail du mécanisme : relay-router / etmvariableload / sg-ready, ses relais, ses Things.
|
||||
- Conséquence assumée : le lot « label + enabled » et le lot « mécanisme + Things »
|
||||
redeviennent un seul chantier — celui qui touche `sameHardware()`, donc reconstruction des
|
||||
adaptateurs et réarmement des verrous à froid.
|
||||
- **Cette refonte suppose que les cartes de « Charges pilotables » SOIENT les charges lues
|
||||
par `GetLoadConfig`**, et non des `LoadDescriptor` reconstruits depuis des `EmsRole`. C'est
|
||||
la cause racine du défaut 2 ; sans ça, un glisser-déposer n'y écrira jamais un `priority`
|
||||
réel.
|
||||
|
||||
## Vérifié / non vérifié (lot B app)
|
||||
|
||||
| Critère | Statut |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user