diff --git a/RECAP.md b/RECAP.md index 55a0e67..00bccde 100644 --- a/RECAP.md +++ b/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 |