diff --git a/specs/spec_loadmodel.md b/specs/spec_loadmodel.md index 54176a8..af9cbb8 100644 --- a/specs/spec_loadmodel.md +++ b/specs/spec_loadmodel.md @@ -1,12 +1,18 @@ # SPEC — Modèle de charges (domaine × mécanisme) -Version : 0.1.2 +Version : 0.1.3 Dépôt : `etm-powersync-energy-plugin-etm` Statut : **intention de conception figée.** §3 (schéma persisté) est **en cours d'implémentation** depuis le 2026-08-09 : `relay-router`, `etmvariableload` et `sg-ready` sont trois charges utiles discriminées par `adapter`. Le reste du document reste une intention. +> **0.1.3** — LM-200 ajouté (§2) : toutes les cases de la matrice ne se valent pas. +> Certaines sont *non prévues*, d'autres sont **interdites par la physique** — le cas de la +> PAC, qui ne se pilote qu'en `SgReady` ou en communicant. Précise LM-101, qui était trop +> permissif lu seul. Aucune décision de conception renversée ; une contrainte qui existait +> dans le matériel et pas dans le document. + > **0.1.2** — LM-104 ajouté (§1) : « supposer conservateur pour annoncer, tenter > systématiquement pour agir ». Principe dégagé des tests d'ECS-411 et ECS-414, > inscrit ici parce qu'il vaut pour tout mécanisme et non pour l'ECS seul. @@ -37,7 +43,11 @@ document reste une intention. **Écrite une seule fois, partagée par tous les domaines.** **LM-101** — Une charge configurée choisit **un domaine et un mécanisme**. Le -domaine ne détermine pas le mécanisme et réciproquement. +domaine ne détermine pas le mécanisme et réciproquement — mais il en **exclut** +certains, cf. **LM-200**. L'indépendance des deux couches porte sur le *couplage +de conception*, pas sur la liberté de combiner : elle dit qu'aucune des deux +n'a besoin de connaître l'autre pour être écrite, jamais que toute paire est +réalisable. **LM-102** — Aucune des deux couches ne répartit de budget. La couche domaine se retire de l'allocation via `available` ; elle ne la négocie pas. Règles absolues @@ -83,7 +93,55 @@ SmartHome. | `Ev` | `Relay` (prise commandée : GreenUp, Witty) · `ModbusSetpoint` (borne) | | `SmartHome` | `Relay` — voir §7, sémantique différente | -Les cases vides ne sont pas interdites, elles ne sont simplement pas prévues. +**LM-200 — Une case vide se lit de deux façons, et il faut les distinguer.** La +plupart sont *non prévues* : rien ne les interdit, elles n'ont simplement pas +d'usage identifié. D'autres sont **impossibles**, parce que le mécanisme ne sait +pas commander cet organe-là. Les confondre revient à traiter une contrainte +physique comme une lacune de feuille de route. + +**Le cas dur, et le seul identifié à ce jour : la PAC.** Une pompe à chaleur se +pilote de **deux** façons, et de deux seulement : + +| Voie | Mécanisme | Ce qui circule | +|---|---|---| +| **SG-Ready** | `SgReady` | deux contacts **secs** vers l'entrée de signalisation — 4 états normés | +| **Communicante** | mécanisme *communicant*, via un plugin nymea | mode, consignes, limite de puissance — le modèle **propre** de la machine | + +**Ni `Relay`, ni `Variable`.** Ce ne sont pas des cases à remplir plus tard : + +- **`Relay` couperait l'ALIMENTATION du compresseur.** Un `RelayRouter` étage de la + puissance réelle ; appliqué à une PAC, il lui retire son autorité sur son propre + cycle — dégivrage, retour d'huile, temporisation de redémarrage — et transforme + chaque décision d'arbitrage en court-cycling. Le verrou `minOn/minOff` limite la + casse, il ne rend pas le geste légitime. +- **`Variable` suppose un cadran en watts.** Un triac module une résistance ; un + compresseur n'est pas gradable. Une PAC communicante peut certes accepter une + *limite* de puissance, mais elle l'interprète selon son propre modèle et ne + s'engage sur aucune valeur instantanée — ce n'est pas la sémantique d'enveloppe + que `Variable` promet à l'optimiseur, et cette promesse alimente le budget. + +**⚠️ Le piège de lecture : « ma PAC est pilotée par deux relais, donc `Relay` ».** +Non. Les contacts SG-Ready **sont** des relais, et au banc `.75` ce sont bien deux +things `power` (K1/K2). Mais ils **signalent**, ils ne **portent** pas la charge. +C'est exactement ce qui sépare les deux mécanismes : `Relay` commute de la +puissance, `SgReady` commute du sens. Voir des relais dans une configuration ne +dit donc rien du mécanisme qui convient. + +**Ce que ça implique pour le mécanisme communicant, qui n'existe pas encore.** Il +est appelé `ModbusSetpoint` dans ce document : **le nom est trop étroit.** Ce qui +le définit n'est pas Modbus — c'est qu'un **thing nymea** porte le modèle de la +machine et que le moteur lui parle dans ce modèle-là. Le transport (Modbus +RTU/TCP, IP propriétaire, passerelle cloud) est une affaire de plugin +d'intégration, invisible d'ici. À renommer quand il sera implémenté ; à ne pas +confondre avec `Variable` d'ici là, malgré la ressemblance de surface d'une +« consigne » à écrire. + +**Rien n'empêche aujourd'hui d'exprimer une case interdite.** `domain` est une +métadonnée d'intention, **optionnelle**, que l'arbitre ne lit pas : une charge +`domain: "hvac"` + `adapter: "relay-router"` est acceptée par `isValid()`, et +l'omission du domaine contournerait de toute façon tout contrôle qui s'y +appuierait. Faire du domaine un discriminant de validation est une **décision de +contrat**, pas un correctif — elle est portée en §8. --- @@ -321,6 +379,22 @@ distinct, pas comme une variante. 3. Sémantique d'action du SmartHome (LM-701) : à spécifier avant toute implémentation, elle ne se déduit pas du reste. 4. Rotation d'équité (LM-503) : politique à définir si le besoin se confirme. +5. **Le domaine doit-il devenir un discriminant de validation (LM-200) ?** Aujourd'hui + `domain` est une métadonnée optionnelle que l'arbitre ne lit pas ; rien ne refuse + `domain: "hvac"` + `adapter: "relay-router"`. Trois façons de traiter la case interdite, + par coût croissant : + - **la documenter seulement** — l'app guide le choix, le moteur ne juge pas. C'est + l'état actuel, désormais écrit ; + - **refuser dans `isValid()`** — cohérent avec « les états invalides doivent être + inexprimables » (LM-302), mais `domain` cesse d'être une métadonnée : ce serait un + changement de contrat, à annoncer à l'app. Et le contrôle reste **contournable par + omission**, puisque le domaine est facultatif — un refus qu'on esquive en effaçant + un champ protège mal ; + - **avertir sans refuser** — une ligne de journal à la construction de l'adaptateur. + Ne bloque pas une installation atypique, laisse une trace lisible au banc. + + Le troisième est le seul qui ne mente ni sur le statut de `domain`, ni sur la force + réelle du contrôle. **À trancher, pas par moi.** ---