Six écrans, trois contrats — et ils ne se ressemblent pas

Le bouton Configurer ne peut pas être un formulaire unique. SG-Ready est une LoadConfig comme le routeur de relais ; la borne de recharge et les zones de climatisation n'en sont pas du tout — elles vivent sur d'autres namespaces, avec d'autres champs et d'autres tiers.

Deux écarts relevés en introspectant .75 (plugin 1.15.2+etm15) : ① le ChargingInfo réellement déployé ne porte ni mode, ni minCurrent, ni targetSoc, ni endTime — les noms écrits dans CLAUDE.md ne sont pas ceux de la box. ② AirConditioning expose 8 méthodes, pas 3.

‹

Configurer · Chauffe-eau

Le seul texte de l'installation que l'utilisateur voit dans chaque motif de décision.
Eau chaude sanitaire ▾
Ce sont vos contacteurs La box ouvre et ferme des contacts qu'elle commande directement, et compose les puissances en les combinant. C'est le seul mécanisme où l'app déclare du matériel. Si les étages sont à l'intérieur de l'appareil et qu'on lui envoie une consigne, ce n'est pas ici : c'est « modulable ».
K1
Relay1 Digital Output
×
K2
Relay2 Digital Output
×
K3
Relay3 Digital Output
×
3 relais → paliers atteignables : 0 · 500 · 1 000 · 1 500 · 2 000 · 2 500 · 3 000 · 3 500 W Déduit, jamais enregistré. La box compose elle-même les paliers en essayant toutes les combinaisons de contacts (2N, plafond 16 relais). Deux relais de même puissance ne créent pas de palier supplémentaire — les doublons sont écartés.
Le picker liste les things par NOM, filtrés sur l'interface nymea power — taper un UUID au doigt est une erreur garantie. Il résout nom → ThingId et n'ajoute aucun champ au contrat.
Un ECS simple, c'est un seul relais — deux paliers, 0 et P. Une résistance multipalier en déclare 2, 3 ou 4. La puissance saisie est celle de la résistance, pas le calibre du contacteur : rien dans nymea ne dit ce qu'un relais commande.
⚠ Ce que la box refuse, en bloc et sans le dire relays[] vide · un powerW ≤ 0 · un même Thing sur deux lignes · un mode autre que fixed. Le motif n'apparaît qu'au journal de la box — le formulaire doit donc valider avant d'envoyer.
Maintien ON minimalanti court-cycle à l'enclenchement
60 s
Maintien OFF minimaldélai avant réenclenchement
60 s
Ces deux durées produisent les verrous LOCK_MIN_ON et LOCK_MIN_OFF que la carte de la charge affiche en secondes restantes.
Rattacher un compteur à cette charge
Choisir un appareil de mesure… ▾

Aucun champ n'existe côté box. SetLoadConfig n'accepte que adapter · enabled · id · label · mode · priority · domain · maxPowerW · minOffS · minOnS · needs · powerLevels · relays · sgReady. nymea rejette toute clé inconnue avant le handler : inventer meterThingId ferait échouer l'enregistrement de toutes les charges. Ici le besoin est le plus fort des trois mécanismes : la box estime la consommation depuis les paliers déclarés, elle ne la mesure pas.

Inclure dans l'arbitragerang 1 · Eau chaude sanitaire
À la désactivation, le retrait passe par l'état sûr : tous les relais ouverts. Vérifié sur matériel côté plugin.
Annuler
Enregistrer
Routeur de relais — ECS simple ou multipalier
NymeaEnergy.SetLoadConfig
Le seul mécanisme où l'app déclare des contacteurs. C'est aussi le seul où les paliers ont un sens : ils naissent des combinaisons de relais, ils ne se saisissent pas.
  • mode doit valoir fixed — la box refuse toute autre valeur sur ce mécanisme. Le champ n'a donc pas à être proposé : il est déterminé par le choix du mécanisme.
  • Toucher aux relais franchit sameHardware() : reconstruction de l'adaptateur et réarmement des verrous à froid — contrairement à label, domain, priority et enabled, qui passent en mise à jour sur place.
  • Configuration réelle du banc .75 : trois relais 500 / 1 000 / 2 000 W, huit paliers, maxStageW: 3 500 — la télémétrie l'a confirmé pendant le passage sur appareil.
‹

Configurer · Chauffe-eau

Une consigne continue, pas des étages La box envoie une valeur en watts, n'importe laquelle entre 0 et le plafond, et l'appareil module. Aucun contact n'est commandé, aucun palier n'est déclaré. Des paliers ⇒ ce sont des contacteurs ⇒ c'est « routeur de relais ». Les deux mécanismes ne se recouvrent pas.
Chauffe-eau Modbus · etmvariableload ▾
⚠ Changer d'appareil, c'est changer de charge L'identifiant de la charge est le ThingId piloté. Choisir un autre appareil ne modifie donc pas cette charge : cela en crée une autre et supprime celle-ci — avec son passage par l'état sûr (consigne 0 W). À dire avant d'enregistrer, pas après.
3 000W
Requis, et strictement positif : la box refuse une charge modulable sans plafond. C'est la plaque signalétique de la résistance, pas une estimation.
Maintien ON minimalanti court-cycle
0 s
Maintien OFF minimaldélai avant reprise
0 s
Rattacher un compteur à cette charge
Choisir un appareil de mesure… ▾

Aucun champ n'existe côté box. SetLoadConfig n'accepte que adapter · enabled · id · label · mode · priority · domain · maxPowerW · minOffS · minOnS · needs · powerLevels · relays · sgReady, et nymea rejette toute clé inconnue avant le handler. Ici la question se double : l'interface remonte déjà la puissance réelle de l'appareil — un compteur externe mesurerait-il autre chose ?

Inclure dans l'arbitragerang 1 · Eau chaude sanitaire
À la désactivation, le retrait passe par l'état sûr : consigne 0 W.
Paliers déclarés : 0 · 600 · 1 200 · 1 800 · 2 400 W

L'app ne propose plus ce mode — le plugin lui-même le qualifie de legacy — mais elle ne le réécrit pas en douce : basculer une charge de fixed vers dynamic change l'arbitrage réel, et cela demande un geste explicite. La conversion exige aussi une puissance nominale, que la déclaration en paliers ne porte pas : la box refuse dynamic sans maxPowerW, et refuse powerLevels avec.

Convertir en modulation continue…
Annuler
Enregistrer
Modulable — une consigne continue
NymeaEnergy.SetLoadConfig
etmvariableload : un seul appareil, une consigne en watts, aucun palier. relays[] y est interdit — la box refuse en bloc.
  • Le critère de choix entre les deux mécanismes, et c'est le seul qui compte pour l'installateur : où sont les contacteurs ? Sur les sorties de la box → routeur de relais, et il déclare {Thing, W} par contacteur. Dans l'appareil, piloté par une consigne → modulable, et il ne déclare qu'un plafond.
  • Le mode fixed existe encore côté box — « multipalier natif, legacy » dans les mots du plugin. L'app doit savoir le lire sans le proposer : une config déployée qui l'utilise ne doit pas être convertie par le simple fait d'ouvrir l'écran.
  • Trois niveaux, trois responsables (contrat rév. 2) : le câblage au wizard Things, la déclaration à cet écran, le runtime à l'interface. « Le thing décide du matériel ; l'energymanager décide de la puissance. »
‹

Configurer · PAC banc

SG1
Relay4 Digital Output
SG2
Relay5 Digital Output
« SG1 » et « SG2 » n'existent pas côté box sgReady.states[] ne stocke qu'une liste de relais par état, sans nom ni ordre. Les deux étiquettes sont une convention de cet écran, et elles se retrouvent à la lecture : SG1 = le contact fermé en état 1 (blocage), SG2 = celui fermé en état 3 (recommandation). C'est l'encodage SG-Ready standard, et c'est celui du banc.
⚠ Un encodage non standard perd les étiquettes Si la configuration lue ne correspond pas à ce schéma — trois contacts, ou un état 1 sans contact fermé — l'écran doit renoncer aux noms SG1/SG2 et afficher les relais tels quels, plutôt que d'attribuer une étiquette au hasard. Le tableau ci-dessous reste éditable ; ce sont les noms qui disparaissent, pas la configuration.
1Blocagearrêt forcé
SG1 ferméSG2 ouvert
2Normalaucune consigne
SG1 ouvertSG2 ouvert
3Recommandationincitation douce
SG1 ouvertSG2 fermé
4Forcémarche imposée
SG1 ferméSG2 fermé
⚠ L'état 2 doit être exprimable Si l'encodage des contacts ne permet pas de représenter l'état « normal », la pompe à chaleur n'est jamais pilotable : la box publie UNUSABLE_ENCODING et la charge sort de l'arbitrage définitivement (ECS-110). Le formulaire doit le refuser avant l'envoi.
Une estimation, jamais un engagement estimatedPowerW sert de base au recrédit de budget et au choix d'état : une valeur fausse ne décale pas seulement la PAC, elle décale l'allocation de tout le waterfall en aval. À relever sur la plaque signalétique, puis à mesurer sous les deux régimes.
Maintien d'état minimaldurée avant tout changement d'état
300 s
Pendant ce maintien, la télémétrie publie le verrou LOCK_MIN_STATE_HOLD avec les secondes restantes — c'est ce que l'écran affiche « au dernier cycle », sans le décompter lui-même.
Rattacher un compteur à cette charge
Choisir un appareil de mesure… ▾

Aucun champ n'existe côté box. SetLoadConfig n'accepte que adapter · enabled · id · label · mode · priority · domain · maxPowerW · minOffS · minOnS · needs · powerLevels · relays · sgReady. nymea rejette toute clé inconnue avant le handler : inventer meterThingId ferait échouer l'enregistrement de toutes les charges.

Inclure dans l'arbitragerang 2 · Chauffage
À la désactivation, le retrait passe par l'état sûr : état 2, jamais le blocage. Vérifié sur matériel côté plugin.
Annuler
Enregistrer
SG-Ready — même contrat que le relais
NymeaEnergy.SetLoadConfig
Même famille que le routeur de relais : c'est une LoadConfig, elle est arbitrée par le waterfall, elle apparaît dans GetLoadTelemetry, elle a un rang et un domaine.
  • Ce que les contacts commandent, ce sont des états normés 1→4, pas des watts. Les watts n'y sont qu'une estimation déclarée — d'où le libellé.
  • Toucher aux contacts ou à la table d'états franchit sameHardware() : reconstruction de l'adaptateur et réarmement des verrous à froid.
  • Le choix des Things se fait une fois, en haut — pas état par état. Ce qui se coche dans le tableau, c'est fermé / ouvert, et c'est ça qui devient relays[] à l'enregistrement : la liste des contacts fermés pour cet état.
‹

Configurer · Borne de recharge

Terra AC Charger (TCP) ▾
Recharge sur surplusSurplusCharging
PV → ChargingModeEco · Min+PV → ChargingModeEcoWithMinCurrent · Boost → ChargingModeNormal
avec échéance → ChargingModeEcoWithTargetTime / ChargingModeEcoMinWithTargetTime
C'est un défaut, pas une commande. Le pilotage au jour le jour se fait depuis la carte véhicule du tableau de bord ; ici on règle ce vers quoi la borne revient. Les deux écrivent le même chargingMode — d'où l'importance de ne pas laisser croire que ce bouton agit à l'instant.
Fixer une heure de départbascule le mode en « …WithTargetTime »
Charge cibletargetPercentage
80 %
Prête àendDateTime
07:30
Répéter : Lun · Mar · Mer · Jeu · Ven ▾
Courant maintenu en Min+PV
6 A

Le mode existe, le réglage n'existe pas. ChargingModeEcoWithMinCurrent est bien dans l'énumération de la box, mais ChargingInfo n'a aucun champ de courant minimum — ni minCurrent, ni équivalent. Soit la valeur est fixée ailleurs (paramètre du Thing borne), soit le champ manque. À trancher avec l'agent plugin avant de livrer ce mode : proposer un réglage qui ne part nulle part serait pire que de ne pas le proposer.

Rattacher un compteur à la borne
Choisir un appareil de mesure… ▾

Aucun champ n'existe côté box. ChargingInfo ne porte que chargingMode · evChargerId · assignedCarId · dailySpotMarketPercentage · endDateTime · repeatDays · spotMarketChargingEnabled · targetPercentage, plus chargingState en lecture. Rien pour un compteur — et la borne mesure déjà, ce qui pose la question de quelle mesure fait foi.

Recharger aux heures les moins chèresPredict AI
Annuler
Enregistrer
Borne — un autre contrat
NymeaEnergy.SetChargingInfo
Ce n'est pas une LoadConfig. Le véhicule n'a ni adapter, ni priority, ni domain — et il n'apparaît pas dans GetLoadTelemetry en beta : il est décidé en amont du waterfall, et sa puissance est seulement réservée dans budget.evReservedW. Il rejoindra loads[] à l'étape 3g.
  • Les noms de champs de CLAUDE.md sont périmés. La box déploie chargingMode (pas mode), targetPercentage (pas targetSoc), endDateTime en Uint (pas endTime), plus assignedCarId, repeatDays et deux champs spot market. À corriger dans CLAUDE.md.
  • Le namespace est NymeaEnergy.*, pas EnergyPlugin.*.
‹

Zones de climatisation

+
Séjour
20,4 °consigne 21,0 °
2 thermostats1 vanne 1 sonde int.1 contact fenêtre
⏱ Dérogation 22,0 ° — encore 45 min
Chambres
18,9 °consigne 19,0 °
3 thermostats1 sonde int.
Bureau
21,1 °réduit 17,0 °
1 thermostathumidité 47 % COV 320
Thermostats2 appareils▾
Vannes1 appareil▾
Sondes intérieures1 appareil▾
Sondes extérieuresaucune▾
Contacts de fenêtre1 appareil▾
Notificationsaucune▾
Rattacher un compteur à la zone
Choisir un appareil de mesure… ▾

Aucun champ n'existe côté box. ZoneInfo rattache des indoorSensors et outdoorSensors — des sondes de température, pas de consommation. Un compteur de zone est un champ à ajouter, et il faudra dire s'il mesure la zone ou l'appareil qui la chauffe.

Consigne dérogéesetpointOverride
22,0 °
Duréeminutes — mode minuté seul
60 min
Hors planningstandbySetpoint
17,0 °
7 jours · 4 créneaux par jour ›
Écran dédié — la forme exacte de TemperatureWeekSchedule reste à introspecter, le type n'est pas détaillé dans le schéma de cette box.
Sur le banc, il n'y a aucune zone AirConditioning.GetZones renvoie zones: [] sur .75. L'état vide ci-dessous est donc le premier écran que verra un installateur.
Aucune zone déclarée

Une zone regroupe des thermostats, des vannes et des sondes sous une même consigne. Sans zone, la climatisation n'est pas pilotée par l'énergie.

HVAC — encore un autre contrat
AirConditioning.* · tier Auto
Troisième contrat : ni LoadConfig, ni ChargingInfo. Une zone n'a pas de rang de service ni de domaine — c'est un regroupement de Things sous une consigne de température.
  • 8 méthodes, pas 3 comme l'annonce CLAUDE.md : s'ajoutent AddZone, RemoveZone, SetZoneName, SetZoneThings et SetZoneStandbySetpoint. Le CRUD complet est donc possible depuis l'app.
  • Une zone expose en lecture seule température, humidité, PM2.5, COV, statut, et la dérogation en cours avec sa fin. Tout le reste des champs de ZoneInfo est r: — on écrit par les six méthodes Set*, jamais par un objet complet.
  • Lien avec l'énergie à établir : rien dans ZoneInfo ne relie une zone à une LoadConfig. La PAC SG-Ready et les zones vivent côte à côte sans se connaître — si l'ordre de service doit tenir compte des zones, c'est une question de conception, pas de câblage.
‹

Séjour · Thermostats

✓
Thermostats Vannes Sondes int. Sondes ext. Fenêtres Notifications
Six listes distinctes, six paramètres de SetZoneThings. On n'envoie que celles qu'on a touchées — les autres sont optionnelles et restent en place.
Thermostat Séjour NordDanfoss Ally · thermostat
Thermostat Séjour SudDanfoss Ally · thermostat
Thermostat Chambre 1Danfoss Ally · thermostat
zone « Chambres »
Thermostat BureauShelly TRV · thermostat
Un appareil dans deux zones ? Le schéma ne l'interdit pas — SetZoneThings prend une liste d'UUID sans contrainte d'unicité déclarée. Tant que ce n'est pas éprouvé sur la box, l'app signale l'appartenance existante sans bloquer, et ne prétend pas connaître la règle.
⚠ Sur le banc, ces listes sont vides Les 58 ThingClass de .75 ne portent aucune interface thermostat, vanne ou sonde de température — il n'y a pas de matériel HVAC dessus. Les appareils ci-dessus sont fictifs : cet écran n'est pas éprouvable en l'état, et la première vérification à faire est d'y brancher un thermostat réel.
Rattacher un compteur à la zone
Filtrer sur « energymeter »… ▾

Le filtre, lui, existe déjà. .75 porte les interfaces energymeter, smartmeter, smartmeterconsumer et smartmeterproducer : le jour où le champ est ajouté, l'app saura proposer les bons appareils sans table maintenue à la main. C'est le champ de destination qui manque, pas la façon de choisir.

Annuler
Valider (2)
Zone — choisir les appareils
AirConditioning.SetZoneThings
Le même écran sert aux six rôles : thermostats, vannes, sondes intérieures, sondes extérieures, contacts de fenêtre, notifications. Seul le filtre d'interface change.
  • Filtrer par interface, pas par nom de classe. ThingClass.interfaces est une liste en lecture sur chaque classe ; s'en servir évite la table de correspondance codée en dur que le brief interdit — et qui vieillirait à chaque plugin ajouté.
  • Ne rien attendre du vocabulaire affiché. Mesuré sur .75 : sur 784 chaînes, le français en traduit zéro. Les displayName sont des identifiants anglais, à afficher tels quels.