diff --git a/AGENTS.md b/AGENTS.md index c758a31..0e21929 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -753,6 +753,9 @@ supérieures n'affecte pas les couches inférieures. Voir `docs/SAFETY.md` pour - `docs/RELEVE_3g1.md` — relevé MACHINE de 3g-1 sur `.75` : ce que le banc a dit, y compris les trois défauts qu'il a révélés et que 3g-2 corrige. - `docs/RECAP_3g2.md` — 3g-2 : ce que le lot corrige, et pourquoi. +- `docs/BRIEF_depuis_plugin.md` — **brief ÉMIS vers l'agent app** après 3g-2 : ce qui bouge pour + l'écran (l'inclusion rétablie, la mise en garde sur `allocatedW` qui tombe, `EV_GRID_START`, + le rang de borne au glisser-déposer). - `docs/RELEVE_3g2.md` — relevé MACHINE de 3g-2 sur `.75` (`+etm23`) : le doublet disparu, le plancher passé de 2 070 à 4 140 W, l'entrée `evcharger` auto-provisionnée — **et les deux défauts que le passage a trouvés**, dont le trou de `pluggedIn`. diff --git a/docs/BRIEF_depuis_plugin.md b/docs/BRIEF_depuis_plugin.md new file mode 100644 index 0000000..cdd6e09 --- /dev/null +++ b/docs/BRIEF_depuis_plugin.md @@ -0,0 +1,140 @@ +# Brief agent app — ce que 3g-2 change pour l'écran + +**Émis par** : l'agent plugin, après 3g-2 · **Date** : 2026-08-27 +**Banc** : `.75`, plugin `1.15.2+etm24`, nymea `1.15.2+202606191336~trixie1` +**Contexte** : `docs/RELEVE_3g2.md` (ce dépôt) porte les traces machine, cycle par cycle. +**Contrat** : `INTERFACE.md` fait autorité — ce brief ne fait qu'en signaler les mouvements. + +> Quatre points. Le deuxième **retire une mise en garde** que l'app portait, et il est le plus +> intéressant pour vous : du code conditionnel peut disparaître. + +--- + +## 1. `loads[] ⊆ GetLoadConfig` est RÉTABLI + +3g-1 avait rompu l'invariant sans le signaler : les bornes entraient dans `loads[]` de +`GetLoadTelemetry` **sans figurer dans aucune `GetLoadConfig`**. C'était exactement la charge +fantôme que le lot B-bis avait supprimée, réapparue par l'autre bout — et vous vous appuyez sur +cet invariant pour résoudre libellé, domaine et rang de chaque charge publiée. + +**Depuis `+etm23`, toute borne détectée reçoit d'office une entrée `LoadConfig`.** Sur le banc, +`GetLoadConfig` est passé de **2 à 4 entrées** au premier cycle après la mise à jour : + +``` +chauffe-eau adapter=relay-router prio=1 domain=ecs +PAC banc adapter=sg-ready prio=2 domain=heating +Simulated wallbox adapter=evcharger prio=1 domain=ev ← nouvelle +Terra AC Charger (TCP) adapter=evcharger prio=1 domain=ev ← nouvelle +``` + +Forme d'une entrée `evcharger` : `id` (le ThingId de la borne), `adapter: "evcharger"`, +`mode: "dynamic"`, `domain: "ev"`, plus `label` / `priority` / `enabled`. **Aucune charge +utile** — pas de `relays`, pas de `sgReady`, pas de `powerLevels`/`maxPowerW`/`minPowerW`, pas +de `minOnS`/`minOffS`. Les limites d'une borne viennent du Thing et changent avec le véhicule +branché ; les déclarer en configuration en ferait une seconde source de vérité. + +**Rien à faire de votre côté pour que ça marche** : l'aller-retour `GetLoadConfig` → +`SetLoadConfig` verbatim est **neutre**, vérifié sur machine — l'objet relu est identique et +aucun adaptateur n'est reconstruit. + +**Le sens de l'inclusion reste le même** : `GetLoadConfig` peut contenir des charges absentes de +`loads[]`. Pour une borne, ça veut dire *hors arbitrage* — pas de voiture assignée, mode manuel, +ou **aucun véhicule branché** (ce dernier cas depuis `+etm24`). Une borne configurée absente de +`loads[]` se lit « pas arbitrée en ce moment », jamais « perdue ». + +--- + +## 2. ⚠️ La mise en garde sur `allocatedW` des bornes TOMBE + +Vous portiez : *« pour une borne, `allocatedW` est la décision de l'arbitre et non l'état de la +borne »*. C'était juste, et ça ne l'est plus. + +**Le défaut derrière cette mise en garde** : jusqu'à `+etm22`, l'arbitre décidait et publiait, +puis `adjustEvChargers()` — le chemin d'exécution hérité de l'amont — re-décidait derrière lui +et commandait autre chose. La décision publiée n'atteignait pas le matériel. + +**`+etm23` ferme ça.** Une charge, un commandeur : les actions EV du waterfall sont dispatchées +vers l'adaptateur, et les bornes ainsi commandées sortent du chemin proxy. Mesuré sur machine : +9 commandes émises à la borne sur la fenêtre d'observation, **9 par l'arbitre, 0 par le proxy**, +et 6 cycles sur 6 portent la ligne « commandée par le waterfall, pas de re-décision ici ». + +**Conséquence pour vous** : `allocationIsCommand` doit rendre `true` **partout**, et les +branches conditionnelles qui en dépendaient peuvent disparaître. + +**Une réserve, et elle est petite mais réelle** : l'allocation reste ce que l'arbitre a +**commandé**, pas ce que la borne **tire**. Un plafond matériel, un véhicule qui refuse ou un +câble débranché feront toujours diverger les deux. Ce que vous pouvez enfin affirmer, c'est que +*personne d'autre ne commande*. Pour l'écart commande/réalité, la charge utile `mechanism` de la +borne porte `chargingEnabled`, `currentA`, `phaseCount` et `pluggedIn` — c'est là qu'il se lit, +pas dans `allocatedW`. + +--- + +## 3. Nouvelle clé ARB : `EV_GRID_START` + +``` +EV_GRID_START { budgetW, floorW, gridW } funding: "grid" +``` + +**Une borne qui DÉMARRE en soutirant au réseau.** Le surplus ne paie pas son plancher, mais le +réglage `acquisitionTolerance` (défaut 0,5) autorise l'appoint : à 0,5, une borne qui exige +4 140 W démarre dès 2 070 W de surplus et tire le reste du réseau. + +`gridW = floorW − budgetW` est **ce qui est acheté**. C'est le chiffre qui fait de cette ligne +une décision, et pas un effet de bord : il mérite d'être montré, pas rangé dans un repli. + +**Comptabilité — c'est le seul cas où l'`allocatedW` d'une charge se partage** entre les deux +compteurs du budget : `budgetW` vient du surplus et entre dans `budget.allocatedW`, `gridW` +vient du réseau et entre dans `budget.evReservedW`. `budgetW + gridW == allocatedW`, toujours. +Si vous réconciliez la somme des allocations avec le budget, c'est la ligne à traiter à part. + +**Borné par construction** : le démarrage exige du surplus réel et consomme tout le budget +restant, donc **au plus une borne par cycle** peut soutirer. + +> **Pas encore vu sur machine.** Guetté six cycles au banc, jamais fabriqué : le budget de la +> borne est passé de 385 W à 2 350 W sans s'arrêter dans la fenêtre de tolérance. Le motif est +> éprouvé par la simulation. Prévoyez la clé, mais ne construisez pas d'écran autour d'une +> chose que le terrain n'a pas encore montrée. + +Seconde clé du même lot, plus rare : `PHASE_LIMIT { limitW, requiredW }` — c'est la **limite de +phase** qui interdit, pas le budget. Deux causes, deux gestes : l'une se règle en délestant la +maison ou en revoyant l'abonnement, l'autre en attendant le soleil. + +--- + +## 4. Le rang d'une borne est configurable, et il se classe avec les autres + +`LoadConfig.priority`, comme toute autre charge. **Il n'y a pas de second système de priorité** +et il n'y en aura pas : un classement propre aux bornes ne saurait pas exprimer +« VE1 > ECS > VE2 », et deux classements qui ne peuvent pas s'interclasser sont un défaut, pas +deux fonctionnalités. `ChargingInfo` ne porte donc **aucun** champ de rang. + +**Le glisser-déposer marche donc sur une seule liste**, bornes comprises, et se pose par +`SetLoadConfig` comme le reste. Vérifié sur machine, y compris l'inversion de rang entre deux +charges. + +Le tri est **total** : à rang égal, l'identifiant départage. Laquelle est servie est donc +reproductible d'un cycle à l'autre, même avant tout réglage. + +> **Réserve sur le rang par défaut, et elle vous concerne.** À la création automatique, la borne +> reçoit « le plus petit rang existant moins un, borné à 1 ». Quand une charge occupe déjà le +> rang 1 — c'est le cas du banc — l'intention « en tête » **dégénère en égalité** : les deux +> bornes et le chauffe-eau sont tous à 1. Sans conséquence sur la reproductibilité, mais +> **l'ordre affiché à l'installateur avant qu'il n'ait rien réglé n'est pas un choix, c'est un +> artefact**. Ne le présentez pas comme un réglage tant que le point n'est pas tranché côté +> moteur (`docs/RELEVE_3g2.md` §3.2). Les vrais rangs se posent à la mise en service, par vous. + +--- + +## Ce qui n'a PAS changé, et qu'il ne faut pas déduire + +- **L'échéance de départ et le tarif dynamique restent au proxy**, financés au réseau + (`funding: "grid"`, motifs `EV_DEADLINE` / `EV_SPOT_MARKET`). Leur puissance vit dans + `budget.evReservedW`, pas dans `budget.allocatedW`. +- **`chargingState` de `ChargingInfo`** garde son sens et reste publié par l'arbitre pour les + bornes qu'il commande. +- **`SetChargingInfo` n'est toujours pas partiel** — l'objet remplace le stocké, et un champ + writable omis retombe sur son défaut. La description publiée le dit désormais, et + `INTERFACE.md` en donne la table. Lire → modifier → renvoyer complet. +- **`repeatDays` va de 1 (lundi) à 7 (dimanche)**, pas de 0 à 6. La documentation disait le + contraire ; le code a toujours refusé le 0.