diff --git a/specs/spec_loadmodel.md b/specs/spec_loadmodel.md index 3d6c645..87df6b9 100644 --- a/specs/spec_loadmodel.md +++ b/specs/spec_loadmodel.md @@ -832,6 +832,11 @@ portant `energymeter` / `smartmeter` ; la sonde parmi celles portant l'interface Nommer un plugin dans le modèle le rendrait faux au prochain plugin qui rend le même service — et obligerait à maintenir une table à la main, ce que LM-600 refuse déjà pour les mécanismes. +> ⚠️ **`meterThingId` n'est plus la SEULE source de mesure — voir LM-1105.** Il l'a été, et cela +> obligeait l'installateur à désigner une borne comme son propre compteur pour qu'elle affiche +> ce qu'elle mesure déjà. Le champ garde tout son sens ; il cesse d'être une corvée de +> configuration là où le moteur peut lire l'appareil directement. + **LM-1101 — Le compteur sert à VÉRIFIER, jamais à décider.** Commandé 3 000 W, mesuré 0 W sur trois cycles consécutifs : la charge ne fait pas ce qu'on lui demande, et c'est **la seule chose** que la mesure autorise à conclure. @@ -858,6 +863,9 @@ soupçon. Il se publie comme **diagnostic**, à côté de `faultCode` et sans le > lent et une charge à forte inertie produiraient sinon un diagnostic permanent. Trois cycles > est un point de départ, pas une valeur figée. +> **Et l'écart ne CONCLUT PAS la même chose selon la source de la mesure** — LM-1106. Un écart +> calculé sur une valeur qui n'est pas une mesure ne conclut, lui, rien du tout. + **LM-1103 — La sonde est en lecture seule, et son vrai usage est différé.** Publiée en télémétrie, **aucun effet** sur les décisions. Deux usages, dont le second est le plus important : @@ -879,6 +887,111 @@ compteur de tableau divisionnaire peut couvrir plusieurs charges. `sensorThingId` dans son contrôle d'unicité des Things. Les y ajouter par symétrie avec `relays[]` refuserait des configurations correctes. +**LM-1105 — Le moteur lit l'appareil qu'il pilote. `meterThingId` cesse d'être la seule +source.** *(Constat d'usage de l'agent app, 2026-08-28 ; proposition retenue.)* + +Pour qu'une charge publie ce qu'elle consomme, il fallait la désigner **comme son propre +compteur** dans `meterThingId`. Ce n'est devinable par personne : un installateur voit un bloc +« compteur dédié » sur une borne qui publie déjà `currentPower`, et n'a aucune raison d'y +remettre la borne elle-même. **C'est de la configuration demandée pour compenser ce que le +moteur peut déduire** — et une configuration qu'on documente en note d'écran est le signe que le +défaut est dans le modèle, pas dans l'app. + +**La règle** : quand le Thing piloté **porte lui-même l'état de puissance**, le moteur le lit, +sans configuration. `meterThingId` ne reste nécessaire que là où rien ne mesure. + +> **La règle porte sur le THING, pas sur le mécanisme.** « Les bornes mesurent, les relais +> non » est vrai en pratique et faux en droit : certains `powerswitch` portent `currentPower`, +> et `RelayRouter` sait déjà les sommer. Écrire la règle par mécanisme y ajouterait la table à +> maintenir à la main que LM-600 refuse. Le test est une question posée à la **ThingClass**. +> +> **Et il se pose sur la CLASSE, jamais sur la valeur ni sur l'instant.** Un `relay-router` au +> palier 0 n'a aucun relais actif : interroger les relais *actifs* ferait osciller la réponse +> d'un cycle à l'autre, sur une installation dont rien n'a changé. La question est « ces Things +> portent-ils l'état ? », posée sur **tous** les Things déclarés de la charge. + +**Le compteur explicite reste PRIORITAIRE, et ce n'est pas d'abord une question de précision.** +Un compteur divisionnaire posé exprès mesure mieux qu'un appareil qui se déclare — c'est vrai, +et ce n'est pas la raison principale. La raison principale est qu'**un réglage qui n'a pas +d'effet est pire que pas de réglage** : si le moteur préférait l'appareil au compteur que +l'installateur a pris la peine de désigner, le champ deviendrait décoratif, et le prochain à le +lire ne saurait pas s'il sert. + +> **La priorité porte sur la SOURCE, pas sur la valeur.** `meterThingId` renseigné mais Thing +> absent, ou Thing sans l'état : on retombe sur l'appareil, et **ce qui est publié dit lequel a +> servi** (LM-1106). Ne rien publier au motif que le compteur déclaré est injoignable +> perdrait une mesure qu'on a — et le silence se lirait « charge non mesurable », ce qui serait +> faux. + +**Ce que LM-1105 ne change PAS** : rien de ce que l'**arbitre** lit. LM-1101 tient mot pour +mot — la mesure vérifie, elle ne budgète pas — et le recrédit anti-clignotement continue de lire +`LoadContext::telemetry.currentPowerW`, qui vient de l'adaptateur et n'a jamais transité par +`meterThingId`. Ce lot déplace **ce qui est publié**, pas ce qui décide. En particulier, il ne +rouvre pas la correction B ni l'artefact du banc `.75` (`AGENTS.md`, PLAN 3C). + +**LM-1106 — Ce qui est publié n'est pas un nombre : c'est un couple {valeur, source}.** +*(Second point soulevé par l'agent app, et c'est lui qui décide de la forme.)* + +Le moteur porte **déjà** trois régimes sous un seul nom, et il jette la distinction : + +| Adaptateur | Ce que vaut `telemetry().currentPowerW` | Régime | +|---|---|---| +| `evcharger` | `EvCharger::currentPower()` | **mesuré par l'appareil** | +| `etmvariableload` | l'état `currentPowerW` du Thing s'il existe ; sinon la consigne en vol ; sinon 0 | **mesuré**, ou **commandé** | +| `relay-router` | somme des relais qui portent `currentPower` ; **sinon le palier NOMINAL** | mesuré ou **nominal** — et le booléen `metered` est calculé, puis jeté | +| `sg-ready` | `estimatedPowerW[état]` | **déclaré**, jamais mesuré, délibérément (invariant 8) | + +Publier ces quatre lignes sous le même `measuredW` serait faux pour deux d'entre elles. Et +publier un `measuredW` **automatiquement rempli** détruirait au passage la seule information que +le champ portait jusqu'ici : sa présence signifiait « un compteur a été posé exprès ». + +**Donc le régime se PUBLIE, il ne se déduit pas d'une valeur.** C'est C2 de LM-1009, mot pour +mot, appliqué à la mesure — et pour la même raison qu'en LM-1009 : *un champ absent se voit ; un +champ qui s'efface se lit comme un zéro.* + +``` +o:measurement { + source : "meter" | "device" | "none", + o:powerW: double // absent si source == "none" +} +``` + +- **`meter`** — un Thing tiers déclaré par `meterThingId`. +- **`device`** — l'appareil piloté publie sa propre puissance. +- **`none`** — **rien ne mesure cette charge.** État à part entière, et positif : c'est C1 de + LM-1009 appliqué ici — une capacité manquante **se dit**. Sans lui, l'app ne peut pas + distinguer « pas mesurable » de « compteur en panne », et continue d'afficher un bloc de + mesure vide sur un `sg-ready` qui n'en aura jamais. + +> **Sous `none`, aucune valeur n'est publiée sous ce nom.** La puissance *nominale* existe et +> reste lisible — `mechanism.stageW`, `mechanism.state`, `mechanism.estimatedPowerW` la portent +> déjà, et disent d'où elle vient. La faire passer pour une mesure produirait un écart +> commande↔mesure **identiquement nul, à tous les cycles, pour toujours** : un « tout +> concorde » permanent, c'est-à-dire le pire des diagnostics. C'est le corollaire LM-104 — +> l'hypothèse de prudence devient un masquage de panne. + +**Ce que la source change au DIAGNOSTIC** (LM-1102), et c'est ce qui la rend utile plutôt que +décorative : + +| Source | Ce qu'un écart commandé↔mesuré autorise à conclure | +|---|---| +| `device` | **La charge n'obéit pas.** Commande et mesure sortent de la même boîte : l'écart parle de la charge et d'elle seule — véhicule qui refuse le courant, thermostat ouvert, plafond interne. | +| `meter` | **La charge, le compteur, ou le câblage.** Trois hypothèses, l'écart n'en tranche aucune seul. C'est le seul cas où « le compteur ne voit pas cette charge » est sur la table — le défaut ouvert du banc, compteur racine aveugle aux résistances ECS. | +| `none` | **Rien.** Aucun écart n'est calculable, et il ne faut surtout pas en fabriquer un. | + +> **L'asymétrie va plus loin que « le compteur mesure mieux », et elle donne à LM-1104 sa +> raison d'être.** Sous `device`, une **concordance** ne prouve pas grand-chose : plusieurs +> wallbox renvoient leur consigne en guise de puissance, et commandé == mesuré n'y est qu'une +> tautologie. Sous `meter`, une concordance est une **preuve**. Autrement dit : `device` +> détecte la **désobéissance**, `meter` est le seul à pouvoir détecter un **mensonge**. C'est +> pourquoi `meterThingId` doit rester **autorisé** sur une charge qui publie déjà sa puissance +> — LM-1104 ne devient pas caduc, il gagne un usage. + +**Conséquence pour l'app, et elle est de présentation, pas de modèle** : le champ « compteur +dédié » se **masque par défaut** là où `source` vaut `device`, et se propose en réglage avancé. +Il ne doit pas être *interdit* : ce serait fermer le cas ci-dessus, et casser le compteur de +tableau divisionnaire couvrant plusieurs charges (LM-1104). + --- ## §12 — Le plancher de modulation