Sept écrans, quatre 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.

Ajout du 2026-08-27 — la batterie, septième écran. Elle devient une charge du waterfall, donc son bouton Configurer ouvre un écran de mécanisme comme les autres — et, comme les autres, il ne porte pas le rang : celui-ci se règle par glisser-déposer dans la liste. Défaut : la batterie en première position de charge, ce qui reproduit le comportement actuel. Rien de ce qui suit n'existe côté box : ni BatteryAdapter, ni entrée LoadConfig pour le stockage, ni prévision PV. Cette colonne est un brief pour le moteur, comme les six autres l'ont été — et les seuls leviers réels de .75 sont les cinq actions de la classe SunSpec Storage : enableCharging, enableDischarging, chargingRate, dischargingRate, gridCharging, plus l'état controlHealthy. Tout le reste est à créer.

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.
‹

Configurer · Batterie

1re position de chargedéfaut
Le rang se règle par glisser-déposer dans la liste des charges, comme pour toutes les autres — aucun écran de mécanisme n'en porte un second réglage. Il est rappelé ici parce que c'est lui qui répond à « eau chaude avant batterie », et rien d'autre.
Défaut : la batterie en première position de charge. Ce n'est pas un choix de conception, c'est la reproduction du comportement actuel — aujourd'hui l'onduleur charge avant que le HEMS ne serve quoi que ce soit. Une mise à jour ne change jamais l'arbitrage sans geste. La nouveauté n'est pas l'ordre, c'est qu'il devient réglable : remonter « Eau chaude » au-dessus suffit alors à obtenir « ECS d'abord, batterie ensuite ».
50 % · 10 kWh · 0 WHolding
batteryLevel · capacity · currentPower · storageStatus — publiés par le Thing, jamais recalculés ici.
Geler la batterie pendant la recharge du véhiculeplus rien n'est déchargé, maison comprise
Le libellé dit ce que le mécanisme fait, pas ce qu'on voudrait. enableDischarging est binaire, et l'onduleur ne sait pas qui tire — il voit une consommation nette. Coupé, la maison n'est plus servie non plus : le réfrigérateur passe au réseau comme la voiture.

« Servir la maison mais pas la voiture » demanderait de plafonner dischargingRate sur la consommation hors borne, recalculé à chaque cycle — de la modulation continue, pas un interrupteur. Posé comme question au moteur, pas dessiné ici.
Pourquoi c'est le seul réglage de décharge. « Charger l'eau chaude avant la batterie » se règle par le rang, et n'a pas besoin d'un second réglage qui dirait la même chose autrement. Décider en général qui mérite d'être servi par la batterie n'est pas un réglage d'installateur : c'est un arbitrage, et il reviendra à Héos (optimiseur MILP) ou à une décision par le prix. Offrir la question à la main figerait dans un écran ce qui doit se calculer.
Ne jamais descendre sousl'arbitrage s'arrête là
20 %
≈ 2 kWh gardés sur 10 — de quoi tenir l'essentiel quelques heures. Ce plancher ne bloque pas la charge, il borne la décharge.
Une préférence de risque, pas une optimisation — et c'est pour ça qu'elle est ici. Combien d'autonomie garder pour une coupure ne se calcule pas : cela dépend de ce que le client craint, de son congélateur, de son télétravail, de la fiabilité de son réseau local. Héos ne pourra jamais le décider à sa place, quelle que soit la qualité de sa prévision.

Ce n'est PAS « Battery Life ». Victron a conçu le sien pour du plomb : une batterie plomb laissée partiellement déchargée se sulfate, d'où un plancher qui remonte tant qu'une recharge complète n'a pas eu lieu. Sur du LFP, ce problème n'existe quasiment pas — le copier tel quel importerait une contrainte de chimie que cette installation n'a pas. Le plancher gardé ici a un autre motif, et il est nommé.
Cible d'énergieen kWh, pas en %
6 kWh
Terminé àheure de fin
06:00
Ne pas soutirer plus dechargingRate — plafond de puissance
3 kW
Ni prix, ni Tempo, ni marché de gros ici. Cet écran pose le mécanisme : soutirer au réseau une quantité donnée avant une heure donnée. Ce qui décide qu'il faut le faire reste dehors — le jour où une source tarifaire existera, elle remplacera le doigt de l'utilisateur sans que ce bloc ne change.

Le plafond de puissance est le seul emploi de chargingRate dans cet écran, et il a le même motif que le boost, à l'envers : ne pas dépasser ce qu'on a le droit de soutirer. Charger 6 kWh en pleine nuit à pleine puissance sur un abonnement 6 kVA ferait sauter le disjoncteur au premier four allumé.
La batterie n'en a pas besoin, et c'est la seule charge dans ce cas : elle publie currentPower elle-même. Le bloc est dit ici plutôt qu'omis en silence comme dans les cinq autres écrans — une absence expliquée n'est pas un oubli.
Annuler
Enregistrer
Batterie — une charge, enfin
à créer — BatteryAdapter · LoadConfig
Rien de cet écran n'existe. Le stockage n'a aucune entrée GetLoadConfig, et enableCharging / chargingRate / enableDischarging ont zéro occurrence dans energyplugin/. Le levier matériel, lui, est déclaré : la classe SunSpec Storage de .75 publie ces cinq actions.
  • Aucune valeur affichée ici n'est inventée. L'état réel se limite à ce que le Thing publie ; tout le reste porte un badge à créer.
  • Le rang n'est PAS sur cet écran, et c'est volontaire : il se règle par glisser-déposer dans la liste des charges, comme pour toutes les autres. Un second réglage de rang ici dirait la même chose autrement, et les deux finiraient par diverger.
  • Pas de permission par charge non plus. Le seul interrupteur de décharge couvre le cas que le rang ne sait pas dire ; l'arbitrage fin — qui mérite d'être servi par la batterie, et quand — reviendra à Héos ou à une décision par le prix. Un écran ne doit pas figer ce qui doit se calculer.
  • Trois questions au moteur, plus quatre soulevées par la recharge accélérée, sont écrites dans docs/BRIEF_plugin_depuis_maquettes.md — pas ici : ce sont des détails techniques, pas de l'interface.
  • La charge différée n'est pas un réglage non plus — c'est une décision d'Héos. Démarrer assez tard pour arriver à 100 % juste avant la pointe suppose de savoir ce que le soleil va faire : sans prévision, ça ne marche pas, et une heure saisie à la main serait un pari présenté comme un réglage. Le gain reste réel — pic PV de midi libéré pour l'ECS et la voiture, moins de temps passé à 100 % donc moins de vieillissement calendaire, et aucun kWh sacrifié : la même énergie, stockée plus tard. Mais il se calcule.
  • batteryLevelConsideration fait aujourd'hui l'INVERSE de ce que le rang permettra. Sous le seuil, le budget est annulé — la batterie passe donc avant, et l'ECS ne reçoit rien. Si le rang arrive, il faut dire lequel des deux gagne, sinon deux réglages se défont l'un l'autre en silence.
☰

Tableau de bord

⋯
64 %
6,4 / 10 kWh
⚡Recharge accélérée
Charger la voiture plus vite que l'abonnement ne le permet. Le compteur est limité à 6 kVA ; la borne peut monter à 7,5. La batterie comble l'écart — le réseau fournit ce qu'il a le droit de fournir, la maison prend le reste sur son stock, et le disjoncteur ne saute pas.
Puissance de charge viséece que la borne délivre à la voiture
7,5 kW
Réseau 6,0 kW (abonnement) + batterie 1,5 kW (complément) — la batterie ne fixe pas la cible, elle bouche le trou.
Sans descendre sousgarde-fou, pas objectif
20 %
Ou au bout dedurée maximale
45 min
Trois fins, toutes obligatoires : la voiture a atteint sa cible — c'est la fin normale, celle qu'on vise —, la durée est écoulée, ou le véhicule est débranché. Le plancher batterie est un garde-fou, pas un objectif : on ne cherche pas à vider la batterie, on cherche à finir la charge plus tôt. Jamais « jusqu'à annulation » — un transfert sans fin court pendant que personne ne regarde.
Recharge accélérée — hors Configurer
geste immédiat, pas un réglage
Un réglage décrit une intention durable ; ceci est une action ponctuelle, avec un effet immédiat et visible. La mettre dans « Configurer » la rendrait introuvable au moment où on la veut, et persistante alors qu'elle ne doit pas l'être.
  • C'est un argument de vente, et il mérite d'être dit à l'écran : recharger plus vite sans passer de 6 à 9 kVA. Le surcoût d'abonnement évité est chiffrable ; le confort gagné se constate au premier départ pressé.
  • Ce bloc appartient au délestage de LM-1006-1, vu par l'autre bout. Le délestage applique un plafond de soutirage en réduisant les charges pour tenir dessous ; ici on tient sous le même plafond en complétant par la batterie. Même objet, même plafond, même arbitre — pas un mécanisme parallèle, et surtout pas un second commandeur.
  • La remarque « le boost pourrait acheter en cachette » tombe. C'est l'inverse : il existe pour ne pas dépasser ce qu'on a le droit de soutirer.
  • Côté box, enableDischarging + dischargingRate — déjà déclarés par la classe. Le dimensionnement de la marge, la latence de l'onduleur et le rôle de filet de la borne sont des questions moteur, écrites dans le brief, pas dessinées ici.

Un seul motif, pas trois écrans voisins

Trois fonctionnalités demandées à des moments différents ont exactement la même forme. Si la maquette le montre comme un motif unique, le moteur peut l'implémenter une fois au lieu de trois — et l'écran peut le présenter une fois au lieu de trois.

Charge différéecible : 100 % de la batterie
échéance : 2–3 h avant la pointe
non-retour : au-delà, on charge de tout

paramètres calculés par Héos
Charge depuis le réseaucible : 6 kWh soutirés
échéance : 06:00
non-retour : au-delà, pleine puissance

paramètres saisis par l'utilisateur
Échéance véhicule (LM-1009)cible : énergie livrée
échéance : heure de départ
non-retour : au-delà, on passe au réseau

paramètres saisis par l'utilisateur
06:0012:0018:0022:00
┃ point de non-retour — avant, on peut encore attendre ; après, on sert de tout ce qui reste, prévision ou pas. ┃ échéance
C'est la seule partie du motif qui doit être montrée : une prévision n'engage personne, un point de non-retour si.

Une cible, une échéance, un point de non-retour — et cela vaut que les paramètres soient saisis ou calculés. C'est ce qui rend le motif implémentable une fois : le moteur porte la forme, Héos ou l'utilisateur la remplit. Le point de non-retour est la partie qui se perd si on ne la nomme pas, et c'est lui qui garantit qu'une prévision fausse ne coûte rien.

Trois questions pour le moteur — à trancher, pas à deviner
  1. Bloquer demande un INTERDIT ACTIF, pas une allocation à zéro. La batterie agit d'elle-même : l'onduleur charge sans qu'on le lui demande. Ne rien lui allouer ne l'empêche donc de rien — il faut lui dire enableCharging = false. C'est une différence de nature avec toutes les autres charges du waterfall, où « 0 W alloué » suffit à ce qu'il ne se passe rien.
  2. Cet interdit doit EXPIRER. Un HEMS qui tombe après avoir posé enableCharging = false laisse une batterie inerte, sans que rien ne le signale et sans que personne ne sache pourquoi. Il faut un bail à durée limitée, renouvelé tant que l'arbitre vit — l'état sûr d'un stockage, c'est « il fait ce qu'il ferait sans nous », pas « il reste sur le dernier ordre reçu ».
  3. controlHealthy == false doit SORTIR la batterie de l'arbitrage, comme available == false sort une charge en défaut. L'état existe déjà sur la classe. Une batterie qu'on croit piloter et qui ne répond plus fausse le budget en silence, exactement comme le faisait le double commandeur des bornes avant 3g-2.