Chaîne complète vérifiée sur l'appareil : schéma lu, écran fusionné monté, charges réelles
affichées ([chauffe-eau, pac-terrain]) avec leur télémétrie, écriture confirmée par
LoadConfigChanged, restauration.
Le dernier obstacle était le plus instructif : /energy/setup est une route réservée au mode
installateur, et le routeur la renvoie vers / tant qu'il est verrouillé. Le symptôme était
« aucune charge lue » — on aurait cherché un défaut de lecture RPC là où le verrou faisait
son travail. C'est le contrôle de montage d'écran ajouté au harnais qui a tranché.
Le test déverrouille avec le PIN par défaut et reverrouille APRÈS la pause d'observation :
l'ordre inverse passait les assertions mais renvoyait l'écran au tableau de bord.
Relevé : docs/RELEVE_LOTC.md §2 et §6 mis à jour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HajXLUczEyZd22JeewRfff
Le run rebranché est allé plus loin que jamais : connexion à .75 et lecture du schéma sur
l'appareil, puis blocage sur la lecture des charges. Un contrôle de montage de l'écran est
ajouté pour distinguer « navigation non faite » de « lecture en échec » — mais il n'a pas
pu s'exécuter : MIUI a remis à zéro « Installer via USB » à la reconnexion et refuse
désormais toute installation (INSTALL_FAILED_USER_RESTRICTED, sans dialogue). L'app n'est
de ce fait plus installée sur le téléphone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HajXLUczEyZd22JeewRfff
adb shell input tap est refusé par MIUI (INJECT_EVENTS). integration_test contourne le
problème par construction : le test s'exécute DANS le processus de l'app et tape sur les
widgets depuis l'intérieur, sans injection d'événements Android.
Ce que le harnais a déjà prouvé SUR L'APPAREIL, contre .75 : l'app démarre, se connecte
(« connecté à 1.15.2+202606191336~trixie1 ») et SchemaProvider lit JSONRPC.Introspect
(« NymeaEnergy 0.8 · AirConditioning 1.1 »). Le grisage dérivé du schéma n'est donc pas
une théorie : il est alimenté sur le téléphone par la vraie box.
Le run n'est pas allé au bout. Trois obstacles : GoRouter.of() cherché au-dessus du Router
(corrigé) ; le test supposait une installation déjà enregistrée (corrigé — il enregistre la
box lui-même via connectNew) ; puis le téléphone s'est déconnecté du bus USB, et l'ADB sans
fil n'est pas activé. C'est là que ça s'est arrêté.
EFFET DE BORD ASSUMÉ : flutter test integration_test RÉINSTALLE l'app, ce qui vide
shared_preferences et le stockage sécurisé — les deux installations enregistrées sur le
téléphone ont été effacées. Le harnais ne dépend plus de cet état et restaurera .75 au
premier run mené à terme ; nymea-dev-kutz est à rajouter à la main.
Relevé mis à jour : docs/RELEVE_LOTC.md §2 et §6.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HajXLUczEyZd22JeewRfff
FUSION. L'écran « Ordre de service » est supprimé ; sa route redirige. C'est son CHEMIN DE
DONNÉES (LoadConfigProvider) qui a migré dans la présentation de « Rôles & appareils »,
jamais l'inverse. EmsRole fige six rôles dont dhw ET heatPump séparés — or LM-201 dit qu'une
PAC sans ballon séparé est UNE charge : le modèle des rôles aurait exigé deux charges sur un
canal unique, ce que validateSet() refuse à raison. Les charges viennent donc de
GetLoadConfig. Les compteurs restent sur EnergySetupProvider (autre contrat, SetRootMeter).
La note « le pilote peut bousculer l'ordre les jours Tempo rouge ou à l'approche d'une
échéance » est retirée : aucun de ces comportements n'existe dans le moteur.
ANNONCE AVANT ÉCRITURE. Miroir exact de EnergyArbitrator::sameHardware(). Le piège, vérifié
sur .75 : la comparaison des relais est INDEXÉE, donc réordonner relays[] sans rien changer
d'autre reconstruit l'adaptateur — contacts ouverts (applySafeState, force) et verrous
réarmés à froid. Un glisser-déposer dans la liste des contacteurs est un geste matériel.
L'écran le nomme, avec la durée de réarmement.
GRISAGE PAR INTROSPECTION. RpcSchema lit JSONRPC.Introspect à la connexion ; trois états et
non deux — écrivable, r: (mesure, pas fonction manquante), absent — plus « inconnu » quand le
schéma n'a pas pu être lu. Chaque champ grisé porte sa raison et le nom du champ manquant.
La version affichée est celle de l'API d'expérience (NymeaEnergy 0.8) : le paquet +etm15
n'est exposé par aucune méthode.
CORRIGÉ AU PASSAGE — les modes de recharge étaient INOPÉRANTS. setChargingInfo() appelait
EnergyPlugin.SetChargingInfo (namespace inexistant) avec mode/targetSoc/endTime/minCurrent
au lieu de chargingMode/targetPercentage/endDateTime, et minCurrent n'existe dans aucun
champ. Vérifié par appel réel : « Missing required key: chargingMode ». Le refus était de
plus avalé et l'état local mis à jour quand même. Désormais l'état ne suit que si la box
accepte, et le refus remonte sans en inventer la cause.
ZONES — constaté, pas corrigé. L'hypothèse « champs grisés » est fausse : AirConditioning
publie 8 méthodes toutes en écriture. ac_screen.dart n'en appelle aucune, ses 4 pièces sont
des constantes de maquette, et .75 déclare zéro zone. getZones() ajouté + bandeau qui lit le
vrai compte et annonce la maquette. Le câblage est un lot en soi, non exerçable sans zone.
Tests 80/80, analyze 0 erreur. Relevé : docs/RELEVE_LOTC.md — dont §6, ce qui n'a PAS pu
être vérifié : l'IU n'a pas pu être pilotée sur l'appareil, MIUI refusant INJECT_EVENTS.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HajXLUczEyZd22JeewRfff
Le miroir INTERFACE_etmvariableload.md est passé en rév. 3 pendant la session (corps
identique au canonique du dépôt plugin, vérifié). Son §10 s'adresse nommément à l'agent
app — et il RATIFIE les maquettes dessinées aujourd'hui : relays[] = {thingId, powerW}
pour l'ECS relais, maxPowerW seul pour le continu, powerLevels absents de la config et
dérivés par le routeur.
Trois recalages :
- Le picker de relais filtre sur l'interface nymea « power » et présente les things par
NOM — nommé dans la maquette, il ne l'était pas.
- Le point 4 du brief plugin est tranché par la rév. 3 : la duplication de la
combinatoire est voulue (l'app dérive pour montrer, le routeur pour exécuter). La
demande devient donc un moyen de détecter la divergence — la télémétrie publie déjà
maxStageW, l'app peut y confronter le maximum de sa propre liste.
- En-tête du pont VM Service, que le commit précédent avait laissé dans sa forme brute.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H
Trois maquettes, neuf écrans, après le passage sur appareil qui a montré que
/energy/setup décrit autre chose que l'installation.
- charges_pilotables_mockup.html : la liste à deux niveaux, avec le bandeau
d'arbitrage hérité de /energy/loads (qui disparaît) et la télémétrie sur la carte
de chaque charge.
- configurer_mecanismes_mockup.html : les six « Configurer » — routeur de relais,
modulable, SG-Ready, borne, zones HVAC, sélecteur d'appareils.
- raccordement_reseau_mockup.html : compteur réseau et protection de surcharge.
Décisions de conception que les images ne portent pas seules : les paliers
n'existent que chez le routeur de relais et y sont DÉDUITS des combinaisons ; le
mode fixed d'etmvariableload (legacy) n'est plus proposé mais reste lisible, parce
qu'ouvrir un écran ne doit pas convertir une config déployée ; SG1/SG2 sont une
convention d'app retrouvée par l'encodage standard, la box ne stocke qu'une liste
de relais par état ; le compteur par charge est dessiné inactif avec sa raison ; la
borne règle un mode par DÉFAUT, pas une commande.
CLAUDE.md corrigé contre JSONRPC.Introspect (plugin 1.15.2+etm15) :
- Energy.SetChargingMode n'existe pas — le « bug critique » décrivait un appel
impossible. Energy.* n'a que cinq méthodes.
- Le namespace EnergyPlugin.* n'existe pas : c'est NymeaEnergy.*, 20 méthodes et
14 notifications, toutes listées.
- ChargingInfo : chargingMode / targetPercentage / endDateTime (Uint), et non
mode / targetSoc / endTime. minCurrent n'existe nulle part.
- phasePowerLimit est en AMPÈRES par phase, et 0 coupe toute la recharge
intelligente.
- AirConditioning expose 8 méthodes, pas 3.
Toutes les méthodes citées ont été revérifiées une par une : aucune introuvable.
Règle ajoutée : contrôler ce fichier contre Introspect avant chaque lot — trois
fois aujourd'hui il a envoyé un agent dans le vide.
Et docs/BRIEF_plugin_depuis_maquettes.md : les six points que les maquettes
remontent vers le moteur, dont un seul blocage dur (pas de champ de courant
minimum pour le mode Min+PV).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H
Intègre la vérif terrain (sonde .75) : GetPowerBalanceLogs sépare import/export
en cumulé (totalAcquisition / totalReturn) → autoconso/autonomie dérivables des
logs, la réserve de la décision-2 est levée. Aligne le doc tracké sur le code
(seam EnergyRatiosInterim, commit 3821bf3).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Source de vérité data-ownership UI, compagnon de DASHBOARD_SPEC.md.
Décisions figées rev.4 : flow §1.1 (Héos centre = métaphore, sens/couleurs),
ratios non calculés côté app (§2 → state energymanager), Things hand-off +
ModbusRTU d'abord, série canonique Héos Route B (§7.3), persistance Influx
(brut stocké + ratios dérivés + plan tagué run_id, §8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>