Patrick Schurig 39fd30d334 docs: RECAP — lot télémétrie déployé, mais attendre l'agent plugin
Le bloqueur consigné en fin de session précédente est levé : GetLoadTelemetry,
LoadTelemetryChanged et o:domain sont sur .75 (20 méthodes / 14 notifications).

Mais consigne explicite de Patrick : ne PAS enchaîner sur la moitié télémétrie
tant que l'agent plugin n'a pas fini son travail sur pac-terrain. L'écart
GetLoadTelemetry (2 charges) / GetLoadConfig (1 charge) est ce travail en
cours, pas une anomalie à remonter — coder contre cet état serait coder contre
une cible mouvante.

Consigne aussi que l'arbitre tourne bien (timestamp +1 min/cycle), ce qui fixe
le plancher du seuil de fraîcheur §7-6 à plus de 60 s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 14:56:18 +02:00

15 KiB

RECAP — etm-powersync-app · reprise de session

Session 2026-08-25. Branche feature/beta-add-config. Rien n'est poussé. La section « État au 2026-06-30 » plus bas reste valable pour tout ce qu'elle décrit.


Session 2026-08-25 — ce qui a été fait

Commits ajoutés

Commit Contenu
7ca4297 Lot B app — ordre de service des charges, locale, ossature i18n
40ac3b2 Fixtures : dump réel GetLoadConfig + schéma introspecté de .75

1. Menus (demande initiale)

  • « Rôles & appareils » et « Options développeur » → section MODE INSTALLATEUR uniquement.
  • Groupes / Scènes / Médias / Garages → masqués par défaut. Le drawer lit désormais AppSettingsProvider.visibleScreens (ces 4 écrans y étaient déjà à visible: false ; le drawer les affichait en dur et ignorait le réglage).
  • Garde de routeur _isInstallerRoute (main.dart:41) : /settings/system*, /settings/app/developer, /energy/setup, /energy/loads → redirection vers / si le mode installateur est verrouillé. Couvre l'URL directe, le deep-link et l'auto-lock 10 min.
  • Liens SUPPORT corrigés : doc → https://docs.etm-powersync.fr/, Telegram → https://t.me/etm_powersync_support, Discord retiré (commenté sur place).

2. Investigations rendues (constats factuels, pas de code)

  • Locale nymea : gérée PAR CONNEXION (o:locale dans Hello, m_clientLocales). Pas de forçage LANG/LC_ALL dans l'unité systemd. Mais sur 784 chaînes affichables de .75, l'allemand en change 1, le français 0 : aucun catalogue .qm fr n'est livré. Les displayName = identifiants anglais.
  • Frontière RPC / runtime de l'arbitre : quasi rien ne sort. Seuls degradedMode (notification ChargingSchedulesChanged uniquement — pas relisable par Get), les deux ratios d'énergie, chargingState de l'EV et le plan de recharge franchissent. Allocation, budget, verrous, paliers, available, motifs : journal seulement. SurplusContext / LoadContextTelemetry n'ont aucune sérialisation sortante.

3. Lot B app — livré

  • models/load_config_entry.dart — vue en lecture au-dessus de la map brute ; écriture par patched() depuis la map d'origine, jamais de re-sérialisation.
  • services/load_priority.dart — groupByDomain / flattenToPayload / applyDefaultDomainOrder / miroir client de validateSet().
  • providers/load_config_provider.dart — séquence pending → LoadConfigChanged → confirmed|diverged. Ne conclut jamais sur l'acquittement du RPC.
  • screens/energy/load_order_screen.dart — route /energy/loads, gatée installateur.
  • i18n : flutter_localizations + intl + lib/l10n/app_fr.arb + l10n.yaml. Extension context.l10n. Français seul.
  • NymeaEnergy.GetLoadConfig / SetLoadConfig deviennent réels (c'étaient des stubs).
  • Locale envoyée à chaque Hello, demandée + retenue journalisées.

4. ⚠️ Piège désamorcé — à ne pas réintroduire

EnergySetupProvider.persist() appelait setLoadConfig(buildLoadDescriptors()), une reconstruction depuis le modèle typé, et _commit() l'appelle à chaque changement d'UI. Inoffensif tant que le RPC était un stub ; destructeur une fois réel (SetLoadConfig remplace tout l'ensemble → la charge chauffe-eau de .75 aurait été écrasée). Émission débranchée, raison documentée sur place.


⏸️ ÉTAT À LA REPRISE — attendre l'agent plugin

Le lot plugin télémétrie A ÉTÉ déployé sur .75 en fin de session (4ade3e4) : 20 méthodes / 14 notifications, GetLoadTelemetry, LoadTelemetryChanged et o:domain présents. Les critères 4 à 8 du brief lot B sont donc techniquement atteignables.

⛔ NE PAS ENCHAÎNER POUR AUTANT. Décision Patrick, 2026-08-25 : attendre que l'agent plugin ait terminé son travail sur pac-terrain avant de construire la moitié télémétrie côté app.

Symptôme observé (reproductible, travail en cours — pas un bug à remonter) : GetLoadTelemetry renvoie 2 charges (chauffe-eau, pac-terrain) là où GetLoadConfig n'en renvoie qu'1 (chauffe-eau). Coder l'écran contre cet état intermédiaire, c'est coder contre une cible mouvante.

Fixtures capturées avant la bascule (déjà committées)

  • test/fixtures/loadtelemetry_hems75.json — dump réel de GetLoadTelemetry
  • test/fixtures/loadconfig_hems75_v2.json — GetLoadConfig avec la clé domain
  • test/fixtures/schema_hems75_v2.json — schémas SET + télémétrie introspectés
  • test/fixtures/loadconfig_hems75.json — dump d'AVANT, conservé comme référence

Forme confirmée de la charge utile (conforme au brief : des codes, jamais des phrases) :

{ "loadId": "chauffe-eau", "allocatedW": 0, "available": true,
  "decision": { "code": "SURPLUS_INSUFFICIENT", "params": { "budgetW": 229 } },
  "lock": { "kind": "minOff", "remainingS": 60 },
  "mechanism": { "kind": "relay", "stageW": 0, "maxStageW": 3500 } }

Acquis à ne pas reperdre

  • L'arbitre n'est PAS figé : le timestamp avance d'une minute par cycle (12:37 → 12:38 → 12:39, vérifié). Le seuil de fraîcheur du §7-6 doit donc être supérieur à 60 s — deux ou trois cycles, pas moins.
  • .75 a désormais une 2e charge (pac-terrain, sg-ready) côté télémétrie : le multi-charges y deviendra exerçable en vrai, contrairement à ce que disait le tableau ci-dessous.

Sondes JSON-RPC — désormais permanentes

tools/rpc/ (voir son README.md). loadconfig_roundtrip.dart est le seul qui écrit et exige --yes.

Vérifié / non vérifié (lot B app)

Critère Statut
§7-1 aller-retour neutre ✅ sur .75 en vrai — SetLoadConfig verbatim → relecture identique
§7-1bis non-rebuild ECS-412 ⚠️ lecture de code seulement (sameHardware()), journal non consulté
§7-2 aplatissement réversible ✅ test unitaire seul — .75 n'a qu'une charge
§7-3 config non contiguë ✅ test unitaire seul — fixture synthétique
§7-4 à §7-8 ❌ bloqués (lot plugin)
§7-9 locale ✅ protocole vérifié sur .75 ; ⚠️ chemin app jamais exécuté sur appareil (adb refusé)
§7-10 analyze / tests / gating ✅ 25 issues = ligne de base, 30/30 tests

Question ouverte à trancher

Le brief désignait « Rôles & appareils » (/energy/setup) comme écran cible, mais cet écran est bâti sur le modèle inverse (il construit des LoadDescriptor depuis des EmsRole). J'ai créé /energy/loads à côté et laissé /energy/setup intact. Fusionner les deux est une refonte, pas un câblage — décision Patrick attendue.

Nouveauté à exploiter à la reprise

CLAUDE.md documente désormais l'accès banc : ssh etm@192.168.1.75 (clé installée), journal via journalctl -u nymead. Cela lève la limite qui rendait §7-1bis invérifiable — au prochain aller-retour neutre, lire le journal pour confirmer qu'aucune reconstruction d'adaptateurs n'a lieu.


RECAP — etm-powersync-app · reprise de session

État au 2026-06-30. Branche feature/beta-add-config (9 commits au-dessus de master). Rien n'est poussé (push différé). Aucun merge vers master/silo.


1. Où on en est (commits, du plus récent au plus ancien)

Commit Contenu
b4a6c97 Lot C — squelette discovery de things (⚠️ pending SDM réel)
b7a7213 Catalogue de things (lecture seule) + filtres (lot A)
3a535e0 Écran Protocoles — CRUD masters Modbus RTU
0ebbde4 chore(docs) — specs/contrats/refs rangés dans docs/
be6694c chore — ignore /screenshots
f755c72 docs — contrat UI rev.5 (réserve décision-2 levée)
3821bf3 refactor(energy) — retrait calcul ratios app-side → seam EnergyRatiosInterim
b5abe42 docs — contrat UI rev.4
c638ec6 Connexions multi-HEMS + Rôles & appareils (gros commit fondateur)

Source de vérité contrats : docs/UI_data_contract.md (rev.5), docs/reference/jsonRPC.txt (introspection — fait foi pour les RPC).


2. Ce qui est LIVRÉ et fonctionnel

Connexions multi-HEMS (validé live)

  • models/installation.dart, services/installation_store.dart (token par UUID via flutter_secure_storage, métadonnées SharedPreferences).
  • services/connection_manager.dart : mono-connexion, switchTo, connectNew, enterDemo (démo = active factice), contrôle d'identité DHCP, invalidation des caches au switch.
  • nymea_service : connect() réveillé (ws://4444, token par UUID), capture Hello (uuid/name/initialSetupRequired), authenticate/createUser, resetState.
  • Écran Installations (3 états) + écran Connexion (auth 2 branches, détection auto).
  • Gate de routage (buildAppRouter, redirect + refreshListenable: merge([cm,svc])), hasActiveConnection = isSimulation || (connected && authenticated).
  • En-tête drawer cliquable → Installations.
  • Testé en vrai : login auth contre .120, .75 ouvert, switch, déverrouillage installateur.

Rôles & appareils

  • EnergySetupProvider tri-état (present/absent/non-configuré), écran roles_devices_screen, LoadDescriptor (rév.2 : PAC exclue → SG-Ready), Energy.SetRootMeter réel, Get/SetLoadConfig en stub loggé.

Energy — Phase 1 (interim)

  • services/energy_ratios.dart = SEAM UNIQUE EnergyRatiosInterim : ratios autoconso/autonomie par Δ-de-cumuls (reseed jour/non-monotone, gardes n/a, clamp). selfConsumptionPower net-signé sans .abs(). Test : test/energy_ratios_test.dart.
  • Calcul ratios app-side retiré de _parsePowerBalance + getter autoconsommationW supprimé.

Installateur — Modbus RTU (validé live)

  • models/modbus_models.dart, 5 RPC ModbusRtu.* dans nymea_service, écran protocols_screen (CRUD), route /settings/system/protocols.

Installateur — Catalogue things lot A (validé live)

  • getVendors() + getAllThingClasses(), écran thing_catalog_screen (liste groupée + 3 filtres : fabricant/type/recherche + détail read-only), route /settings/system/things.

Installateur — Discovery lot C (SQUELETTE, NON validé matériel)

  • ThingDescriptor, discoverThings(), addThingFromDescriptor(), thing_discovery_screen (form slaveAddress → scan → descriptors → AddThing).

3. ⚠️ BLOQUEURS / EN ATTENTE

  • Lot C — merge bloqué tant qu'on n'a pas un descriptor SDM réel sur le bus de hems .75 (besoin : adaptateur USB-RS485 + un vrai compteur SDM branché ; aujourd'hui le master /dev/ttyUSB0 est connected:false). 3 points marqués À REVALIDER SUR SDM RÉEL dans thing_discovery_screen.dart / nymea_service.dart :
    1. résolution master/slave par heuristique de nom de paramTypes ('master'/'modbus'/'slave') ;
    2. timeout scan (_sendRequest = 15 s, à allonger si bus réel plus lent) ;
    3. title/description du descriptor (peuvent être vides/génériques).
  • Push : jamais fait (volontaire).

4. Lots / phases NON FAITS (file d'attente)

  • Lot B — ajout direct CreateMethodUser + JustAdd (form paramTypes). Sous-ensemble = 22 classes (ABB B2x, ABB Terra AC RTU/TCP, génériques/simulés nymea). ⚠️ Les Eastron SDM N'EN SONT PAS (ils sont Discovery → lot C). Piège param : nom du master varie (modbusMasterUuid vs rtuMaster). Reporté après C.
  • Lot D — discovery → pairing (OAuth/PushButton/UserAndPassword).
  • Connexions Étape 3 — découverte mDNS native (multicast_dns, natif only ; web = ajout manuel). Différée.
  • Connexions — branche CreateUser (box neuve) : codée mais non testée live (pas de box vierge/factory-reset sous la main).
  • Energy Phase 2/3/4 (session dédiée, voir mémoire) : Phase 2 = states ratios côté energymanager (autre repo) ; Phase 3 = persistance Influx ; Phase 4 = refonte flow-card §1.1 + bandes + retrait définitif du calcul app-side. Contrat = docs/UI_data_contract.md.

Bugs latents notés (hors lot)

  • dashboard_screen.dart : onRefresh: () => service.startSimulation() → sur box réelle, pull-to-refresh bascule en démo. À corriger au câblage du refresh réel.
  • Affichage signe : sans .abs(), conso reste net-signée → Maison (flow-card) & ligne conso (graphe) montrent le signe brut jusqu'à la Phase 4 (voulu).

5. Banc de test

Cible Détail
hems .75 nymea 1.15.2, ouverte (authenticationRequired:false). Master RTU /dev/ttyUSB0 présent mais bus hors-ligne. Sert aux sondes read-only sans auth.
box 2 192.168.1.120 auth requise (initialSetupRequired:false → login). Vraie installation avec SDM120 PV.
InfluxDB local sur etm-powersync-dev, v1.6.7 (PAS Influx 3 Core), DB nymeatest, RP tiers déjà présentes.
Tél Android Redmi Note 9S, adb WiFi 192.168.1.107:5555.

PIN installateur par défaut = 1234.

Comment lancer / tester

  • Réel (réseau) : flutter run -d 192.168.1.107:5555 (Android WiFi). Pas Chrome pour le réseau (CORS LAN). Voir mémoire web-lan-cors.
  • Linux natif : ❌ bloqué (Flutter snap sans linker ld.lld). Web headless = page blanche (CanvasKit). Captures fiables = adb exec-out screencap -p.
  • Sonde RPC sans GUI : script dart:io WebSocket.connect('ws://<host>:4444') + JSON-RPC (ex. utilisé pour valider Modbus/discovery contre .75).

6. Faits techniques à ne pas reperdre

  • Signes nymea (power balance agrégé) : production positif, consumption NÉGATIF, acquisition net-signé (négatif = export). currentPower thing-level = négatif pour un producteur (convention conso). Ne jamais deviner/.abs() le signe.
  • Cumuls (totalAcquisition / totalReturn) séparés → autoconso/autonomie dérivables des logs. UUID Hello brace-wrapped {...} (normalisé dans _finishConnect).
  • Discovery SDM : discoveryParam = slaveAddress uniquement (Int, déf 1). Le master n'est PAS une entrée — il ressort dans descriptor.params. Ajout final = AddThing(thingDescriptorId).
  • CreateMethod = {User, Auto, Discovery} · SetupMethod = {JustAdd, DisplayPin, EnterPin, PushButton, UserAndPassword, OAuth}. Orthogonaux (une classe JustAdd peut exiger Discovery).

7. Reprise — prochaines actions probables

  1. Brancher un SDM sur .75 → sonde discovery réelle → lever les 3 points + débloquer le merge du lot C.
  2. Puis lot B (ABB + génériques) ou lot D (pairing).
  3. Côté energy : ouvrir la session dédiée Phase 4 (contrat docs/UI_data_contract.md).
  4. Décider du push de feature/beta-add-config.

Mémoires persistantes liées : web-lan-cors, energy-chart-redesign, deploy.