Vérifié par sonde avant de couper : `measuredW` a disparu de la charge utile, `measurement` est en ligne. L'alias n'aura vécu que le temps de l'intervalle qu'il devait couvrir, et `MeasurementSource.legacy` part avec lui. Ce qui reste, `MeasurementSource.inconnue`, ne le remplace pas : il couvre une box PLUS RÉCENTE que l'app — la même règle que partout ailleurs dans ce fichier, un code inconnu tombe sur un repli lisible plutôt que sur une phrase inventée. On affiche la valeur, on ne prétend rien sur ce qu'un écart prouverait. ── La garantie qui manquait à ce que j'avais écrit ────────────────────────── `measurement` est présent pour toute charge de `loads[]`, et pour elle seule. C'est elle qui rend la lecture sûre, et elle est maintenant au contrat : son absence ne peut PAS vouloir dire « pas mesurable » — ça, c'est `none`, un état publié — elle ne peut vouloir dire que « charge hors de loads[] », cas où l'on ne conclut rien. J'avais fait la distinction par prudence sur l'écran ; elle est désormais fondée sur une garantie, pas sur une intuition. ── Les sources du banc ne sont pas celles annoncées, et c'est instructif ──── chauffe-eau meter, 1 500 W porte ECS-Meter pac-terrain meter, 800 W porte PAC-Meter — pas `none` V2C Trydan absente de loads[] (pluggedIn: false) `pac-terrain` en `meter` ne contredit pas LM-1105-b : cette règle décrit ce qu'une PAC publie SANS compteur dédié. On lui en a posé un — ce que le brief recommandait — et le compteur explicite l'emporte toujours. Et la V2C illustre la distinction sur machine : absente, pas `none`. Elle porte `currentPower` et publiera sa mesure au cycle où une voiture y sera branchée. ⚠️ Le test sur appareil exigeait `measuredW == null` après détachement du compteur. C'était vrai avant `+etm26`, c'est faux depuis : le moteur retombe sur l'appareil quand il publie lui-même sa puissance. Ce qui doit cesser, c'est que la source vaille `meter` sans compteur désigné — pas que la mesure disparaisse. CLAUDE.md porte le contrat complet et les deux pièges, avec l'illustration du banc. 205 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
14 KiB
TODO — ETM PowerSync App
Ouvert au 2026-08-27
-
⏳ Retirer l'alias— fait le 2026-08-28,measuredW1.15.2+etm26déployé sur.75et vérifié par sonde :measuredWa disparu de la charge utile,measurementest en ligne.MeasurementSource.legacyest parti avec lui ; ce qui reste,MeasurementSource.inconnue, couvre une box plus récente que l'app, pas plus ancienne. Sources relevées sur le banc :meter1 500 W (chauffe-eau, ECS-Meter),meter800 W (pac-terrain, PAC-Meter), et la V2C absente deloads[]— pasnone. -
🔴 Bilan JOURNALIER— fait le 2026-08-28. Les quatre tuiles montrent la journée, sous un en-tête « Aujourd'hui » qui le dit une fois. Méthode : différence des compteurs entre minuit local et maintenant, jamais l'intégration des puissances — mesurée contre estimée, 0,8 % d'écart sur.75. Le contrôle qui la valide est l'identité production + soutiré == consommation + injecté, vérifiée exactement sur le banc.- Deux bugs d'unité trouvés en chemin : les compteurs
total*sont en kWh et le modèle les nommaitWh. L'écran Énergie les divisait donc encore par mille — une journée de 78 kWh s'y lisait « 78 Wh » — et la simulation accumulait des Wh. - Vérifié sur appareil contre
.75en lecture seule. Le passage a trouvé quatre libellés tronqués qu'aucun des 138 tests unitaires ne pouvait voir.
- Deux bugs d'unité trouvés en chemin : les compteurs
-
❓ « ECS d'abord, batterie ensuite » — à porter au moteur. Demandé le 2026-08-27, et pas exprimable aujourd'hui.
batteryLevelConsiderationfait l'inverse : il annule le budget sous le seuil, donc la batterie passe avant. La batterie n'est pas dans le waterfall — aucune entréeGetLoadConfig, etenableCharging/chargingRate/enableDischargingn'apparaissent nulle part dans le plugin ETM. Le levier existe pourtant : la classe SunSpec Storage de.75déclare ces cinq actions. Question : le stockage est-il une charge arbitrable, ou reste-t-il hors du budget ? Une règleRules.*serait un contournement — deux commandeurs sur la batterie, exactement le défaut que 3g-2 vient de supprimer sur les bornes. -
Zones de climatisation — cadrage écrit, banc à préparer. Voir
docs/CADRAGE_zones_clim.md. Le blocage n'est pas « aucune zone déclarée » : sur les 58 classes de.75, aucune ne portethermostat,closablesensor,temperaturesensorninotifications— aucun plugin installé ne sait en créer une. Deux paquets du dépôt apt du banc suffisent (nymea-plugin-generic-heatingcooling,nymea-plugin-generic-sensors). Six décisions à prendre avant de coder. -
EV_GRID_START/PHASE_LIMIT— éprouvés par test seulement. Les clés ARB sont 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à. -
⏳
sessionEnergyest CONSTRUIT par le plugin — mais la trace n'existe pas, et n'existera pas sur cette borne. Le plugin V2C1.15.2+etm13(déployé le 2026-08-28 à midi) accumule les incréments dechargeEnergy, détecte les remises à zéro, et ne repart qu'à l'ouverture d'une session : ce n'est pas un alias du registre0x0BC4. 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,sessionEnergyqui continue — se verra sur elle. Aucun avancement affiché d'ici là (LM-1009 §C1), et la note reste en place telle quelle. -
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éfixecurrentPower, identifiants de borne avec/sans accolades), la règle, la façon de le chercher, et un renvoi depuis les deux sites de code plustools/rpc/README.md— là où quelqu'un croise deux réponses. -
Plugin V2C — déclarer
smartmetersur la classetrydan, ce qui suppose un cumul d'énergie. Relevé du 2026-08-28 : la classewallbox(borne simulée) déclareconnectable, evcharger, power, smartmeter, smartmeterconsumer; la classetrydann'a queconnectable, evcharger, power. La V2C n'est donc pas proposée comme compteur — l'app a élargi son sélecteur aux bornes publiantcurrentPower(correctif du jour), mais c'est un contournement de l'écart, pas sa disparition. Le manque est de fond, et c'est le même que celui qui a bloqué LM-1009 : l'interfacesmartmeterattend un cumul d'énergie ; la wallbox simulée publietotalEnergyConsumed, la Trydan n'a quesessionEnergy/chargeEnergy— une session, pas un total. À chercher dans la carte Modbus V2C, ou à construire commesessionEnergyl'a été (1.15.2+etm13). C'est un lot plugin, pas un correctif d'app. -
Historique des états d'une borne —
sessionEnergyn'est pas enregistré.suggestLoggingest écrasé par les préférences persistées dethings.confsur un Thing déjà appairé, et la V2C n'y a queconnected,charging,power. La valeur courante est juste — « la nuit relue à 7 h » fonctionne — mais il n'y aura pas de courbe. Le réglage est côté app : choisir les états enregistrés d'un Thing. À décider si un historique de recharge est voulu ; sans lui, l'écran ne peut montrer qu'un instantané. -
⏳— répondu le 2026-08-28 par l'origine du champ (entrée ci-dessus). Le relevé qui a posé la question est conservé :sessionEnergyexiste — mais rien ne prouve qu'il survive à une interruptionIntegrations.GetThingssur.75, cet après-midi — la V2C publiesessionEnergyetchargeEnergy, tous deux à 4,229 kWh — égaux, ce qui est exactement ce qu'on attend d'une session sans interruption. Le test qui tranche est une remise à zéro observée dechargeEnergyavec unsessionEnergyqui continue. Tant qu'elle n'est pas vue, LM-1009 §C1 reste entière : pas d'avancement affiché. À vérifier sur la campagne de mesures en cours.- Deux attentes levées au même relevé : le renommage
currentL1/L2/L3(unité Ampère) est déployé — plus depowerL1/L2/L3— etphaseCountvaut 3 sur la Trydan, le réglage ayant été posé.PREPA_BANC_20260828.md(dépôt plugin) est corrigé en conséquence : il décrivait l'état de la veille au soir.
- Deux attentes levées au même relevé : le renommage
-
— tranché le 2026-08-28 : elle part. La monter aurait publié de la fiction. Trois des grandeurs qu'elle affiche ne sont jamais lues sur la box :EVChargingCardn'est montée par AUCUN écranchargingMode,chargingPower(3,6 kW) etsolarSourcePercent(82 %) sont des valeurs par défaut deEnergyData, et le mode n'est écrit qu'en optimiste après une commande — jamais relu parGetChargingInfos. Sa feuille « Paramètres de charge » proposait en plus un courant minimum, qui n'existe dans aucun contrat, et son bouton « Enregistrer » ne faisait queNavigator.pop: rien n'était écrit. -
La tuile EV des favoris affiche des valeurs par défaut comme des mesures— fait le 2026-08-28 : rendue muette, pas retirée. La retirer aurait laissé un trou dans une liste que le client a composée lui-même.EvFavoriteTile(widgets/ev_favorite_tile.dart) lit la borne —pluggedIn,charging,currentPowerdu Thing, mode parGetChargingInfos+ChargingInfoChanged— et dit ce qui n'est pas publié plutôt que d'afficher zéro. L'écriture du mode restitue le refus et les cibles tactiles passent de 7–10 px à 44 px. Les trois champs fictifs deEnergyData(chargingMode,chargingPower,solarSourcePercent) sont supprimés du modèle : la fiction n'a plus de porte d'entrée. -
La vue « consommation » du Sankey reste une ESTIMATION — elle le dit désormais.
features/dashboard/widgets/energy_flow_card.dart: PAC = 38 % de la maison, Eau chaude = 18 %, Autres = 22 % sont trois ratios en dur posés à la maquette. Depuis le 2026-08-28 les trois portent le cercle pointillé (estimated) — un seul l'avait — et une légende sous le schéma nomme les branches concernées : « seule la voiture est mesurée à la borne ». La voiture, elle, lit la mesure réelle (currentPower, « non lu » sans borne) au lieu des 3,6 kW en dur qu'elle affichait en permanence. Reste le vrai lot : brancher les trois branches surGetLoadTelemetry.measuredW(relevé du jour : chauffe-eau 1500 W, PAC 800 W). C'est une refonte de l'écran d'accueil, pas un correctif, et le banc est pris par la campagne V2C. Ce qu'une carte de recharge dédiée pourrait dire, si elle revient un jour :- le mode courant, lu par
NymeaEnergy.GetChargingInfoset tenu à jour parChargingInfoChanged— l'écriture existe déjà et est vérifiée (nymea_service.setChargingInfo, lecture → patch → réécriture complète) ; - la puissance mesurée de la borne, prise sur le Thing (
currentPower) ; - l'échéance (
targetPercentage+endDateTime) — mais uniquement comme intention saisie, jamais comme un avancement : LM-1009 §C1. Pas de SOC véhicule (il n'existe pas), pas dechargeEnergy(remis à zéro à chaque interruption), pas de courant minimum (aucun champ), pas dephaseCount(la V2C ne le publie pas).
- le mode courant, lu par
-
Le passage sur appareil doit devenir une habitude, pas un rattrapage. Il a glissé deux fois, et au premier passage il a trouvé deux cibles tactiles hors norme (26 px, 18 px) qu'aucun des 129 tests unitaires ne pouvait voir.
integration_test/ui_3g2_on_device_test.dartest le harnais. Rappel MIUI : la confirmation d'installation est redemandée à chaque fois — faire unadb install -r -tpréalable, puis lancer le test. -
🔴 Journal de recharges — à porter au MOTEUR, lot à part. Une ligne par recharge : début, fin, énergie livrée ; plus tard le véhicule et la part surplus / réseau, que
fundingdistingue déjà. La raison est un manque, pas un confort : il n'existe aucun rapport, nulle part — ni par session, ni par charge, ni par mois. Le bilan journalier s'arrête à la journée.- Pourquoi le moteur et pas l'app : c'est lui qui voit le début et la fin d'une session, et qui connaît déjà la ventilation surplus / réseau. L'app ne peut que la reconstituer.
- Pourquoi pas en intégrant une courbe : ça reviendrait à ré-estimer une somme que le
plugin connaît exactement —
sessionEnergyde la V2C est accumulé au watt-heure près (validé le 2026-08-28 : 9,04023 + 0,182573 = 9,222803, somme exacte, pas un ordre de grandeur). Intégrer une puissance pour retrouver ce chiffre serait perdre de la précision qu'on a déjà. - Ne remplace pas l'enregistrement de
currentPower, qui reste nécessaire : il couvre la consommation sur une période indépendamment des sessions, donc y compris une recharge lancée à la main sur la borne, hors pilotage — cas qu'un journal de sessions raterait.
Charges pilotables — ce qui attend la box (2026-08-26)
— fait le 2026-08-27. Le motif est publié :kCodesReserveBatterieBATTERY_RESERVE, paramssocPercent/reservePercent/withheldW. Les clés que le fichier cherchait (batteryLevel,threshold) n'avaient jamais existé côté plugin — le motif sortait donc en repli anglais sur le seul cas qu'aucune mesure ne révèle.minPowerW == nullsans véhicule branché — couvert en test unitaire (test/ev_runtime_test.dart), pas exercé sur appareil : la borne n'entre pas dansloads[]avant3g. À reprendre quand l'arbitrage prendra la recharge en charge.- Réserve batterie — portée élargie. Le jour où le seuil gouverne tout le budget,
reprendre le texte de
BatteryReserveCard: il annonce aujourd'hui un futur, il devra décrire un présent. Zones — à ne pas entreprendre— décision revue le 2026-08-27 : le lot est cadré (docs/CADRAGE_zones_clim.md), voir l'entrée en tête de ce fichier.ac_screen.dartreste de la donnée fictive — quatre pièces, huit températures crédibles affichées comme des mesures.
En cours / priorité haute
-
TariffProvider — connexion RPC réelle
load()appelle encore unFuture.delayedfictif- Brancher sur nymea JSON-RPC pour récupérer les prix live (ENTSO-E, Tibber, etc.)
-
EnergySetupProvider — persistance
- Les assignations de rôles (EV, PAC, batterie…) ne survivent pas au redémarrage
- Sérialiser en SharedPreferences ou JSON local
-
SchedulerProvider — exécution réelle
forceRecalc()ne fait rien pour l'instant- Brancher sur l'API nymea Energy Scheduler (si disponible)
Screens à implémenter
- DeveloperScreen — logs, introspection JSON-RPC, test ping nymea
- AboutScreen — version app, version nymea, licences
- SystemSettingsScreen — adresse serveur nymea, port, transport (TCP/WS)
Amélioration UX
- Timeline — remplacer la simulation bell-curve par les vraies créneaux du scheduler nymea
- RoleConfigFlow Step 3 — test de connexion sur RPC réel au lieu du délai simulé
- InstallerModeProvider — persister le hash PIN dans SharedPreferences (actuellement en mémoire)
- AppSettingsProvider — persister les préférences (thème, densité, écrans actifs) dans SharedPreferences
Intégration nymea live
- Mapping
EmsRole → nymea thingClasspour les installations réelles - Vérifier les interfaces nymea sur serveur réel (comparer avec
docs/nymea_integrations.md) Energy.GetSchedule/Energy.SetSchedulesi l'API le supporte
Technique / dette
- Ajouter des tests unitaires pour
TariffProvideretEnergySetupProvider - Gérer le cas où
NymeaService.thingClassesest vide en mode live (classes non chargées) - Revoir
_Step1ThingList: passer encontext.watchpour réactivité si les things changent