From fa35ff9ba21530fe0f235c7cd79ca0bd407eaca2 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Sat, 8 Aug 2026 13:34:43 +0200 Subject: [PATCH] =?UTF-8?q?docs(spec):=20spec=5Fecs=200.5.2=20=E2=80=94=20?= =?UTF-8?q?c=C3=A2blage=20500/1000/2000=20et=20D=C3=89COUPLAGE=20des=20pui?= =?UTF-8?q?ssances?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correction. La 0.5.1 avait corrigé le câblage en 500/1000/1500 sur la foi du simulateur du banc ; le simulateur ne reflète pas l'installation. Le câblage réel est 500/1000/2000. Quatrième révision de cette donnée. Les trois consignations reviennent à l'état de la 0.5.0 : - ECS-302 NON exercé — pondération binaire, aucune redondance d'encodage. Il perd son caractère de préalable structurel et redescend derrière ECS-305 dans l'étape 3 ; - ECS-303 RÉTABLI, mais reformulé sans puissances : « hystérésis élargie aux frontières de recombinaison », déclenchée dès qu'un passage de palier impose de basculer plus de deux relais ; - 8 valeurs distinctes, plafond 3500 W, usure 7/3/1 par balayage. DÉCOUPLAGE — c'est le vrai objet de cette version. Les puissances ne vivent plus que dans un bloc unique, §4.0 « Installation de référence », qui porte les trois valeurs, leur source et leur date, ainsi qu'un tableau « ce que cette installation exerce et ce qu'elle n'exerce pas ». Aucune exigence ne contient plus de puissance : celles dont le rang ou la portée en dépendent le disent par renvoi. Vérifié — hors §4.0, il ne subsiste que le changelog, le journal et l'illustration P = U²/R d'ECS-500. Motif : ce paramètre a fait quatre allers-retours, chacun coûtant la réécriture de trois exigences. Le cinquième ne coûtera qu'un paragraphe. Source inscrite telle qu'elle est : DÉCLARATION DE L'UTILISATEUR, NON MESURÉE. Une vérification physique — plaque signalétique ou pince ampèremétrique — est en attente et reste un préalable à l'ouverture de l'étape 3. Note ajoutée sous ECS-411 : sur cette installation les encodages sont uniques, donc le modèle d'état ne peut pas diverger du physique. Sur un câblage à encodages multiples, une correspondance par puissance seule renverrait un palier dont l'encodage canonique diffère de l'ensemble réellement fermé — la reprise doit alors restituer l'ensemble de relais, pas seulement l'index. Le simulateur du banc sera aligné sur cette installation dans un lot SÉPARÉ, sur le dépôt etm-powersync-hems-sim. Aucun code touché. Co-Authored-By: Claude Opus 5 (1M context) --- specs/spec_ecs.md | 197 +++++++++++++++++++++++++++++----------------- 1 file changed, 124 insertions(+), 73 deletions(-) diff --git a/specs/spec_ecs.md b/specs/spec_ecs.md index e1d7e1c..1f4496e 100644 --- a/specs/spec_ecs.md +++ b/specs/spec_ecs.md @@ -1,6 +1,6 @@ # SPEC — ECS multi-palier -Version : 0.5.1 +Version : 0.5.2 Dépôt : `etm-powersync-energy-plugin-etm` (experience-plugin ems) Branche : `feature/beta-rulebased` Contrats faisant autorité : `docs/OPTIMIZER_PROTOCOL.md`, `docs/SAFETY.md`, `AGENTS.md` @@ -40,6 +40,17 @@ remonte. Il ne tranche pas seul. > l'étape 3 ; **ECS-303 est supprimé** (le « point dur » était une conséquence > d'ECS-302 non traité, pas une exigence d'hystérésis) ; 7 valeurs et plafond > 3000 W, non 8 et 3500. Ne pas se fier à une copie 0.5.0. +> +> **0.5.2 — CORRECTION du câblage et DÉCOUPLAGE.** Le câblage réel est +> **500/1000/2000** ; la 0.5.1, qui l'avait corrigé en 500/1000/1500 sur la foi +> du simulateur, était fausse — le simulateur ne reflète pas l'installation. +> Quatrième révision de cette donnée. Trois consignations reviennent à l'état de +> la 0.5.0 : ECS-302 **non exercé** par l'installation de référence et redescendu +> dans l'ordre de l'étape 3 ; **ECS-303 rétabli** ; 8 valeurs, plafond 3500 W. +> +> Pour que cela ne se reproduise pas, les puissances ne vivent plus **que** dans +> le bloc §4.0 « Installation de référence ». Aucune exigence n'en contient. Une +> exigence dont le rang ou la portée en dépend le dit par renvoi. --- @@ -121,8 +132,8 @@ aussi l'I/O — une extraction d'environ 40 lignes, pas une reconstruction. | ECS-101 | Cas à un seul étage | ✅ `relayrouter.cpp:49-64`, testé | | ECS-102 | Configuration externalisée | ✅ `LoadConfigStore` + RPC — était marqué ouvert à tort | | ECS-300 | Palier ≤ budget | ⚠️ vrai hors verrou, faux sous verrou → **ECS-306** | -| ECS-301 | Puissances quelconques, non cascadé | ✅ énumération des 2^N sous-ensembles, testé 1500→2000 | -| ECS-302 | Encodages équivalents | ❌ le second encodage est **jeté**, pas arbitré → structurel | +| ECS-301 | Puissances quelconques, non cascadé | ✅ énumération des 2^N sous-ensembles, testé sur une topologie non cascadée | +| ECS-302 | Encodages équivalents | ❌ le second encodage est jeté (structure) — **non exercé par l'installation de référence**, cf. §4.0 | | ECS-304 | Testable hors nymea | ⚠️ 3 fonctions pures extractibles, le reste couplé | | ECS-400 | Temporisations non codées en dur | ✅ `minOnS`/`minOffS` de la config | | ECS-402 | Coupure avant fermeture | ✅ `relayrouter.cpp:240-244`, testé | @@ -165,13 +176,15 @@ unitaire et conditionne l'étape 3. > extrait le noyau de calcul **interne au mécanisme relais**. Rien à voir. **Étape 3 — ECS-302 / ECS-305.** Changement de structure de `m_relayMapping` : -un palier doit pouvoir porter PLUSIEURS combinaisons. **Préalable structurel** — -doit précéder tout ce qui s'appuiera dessus. Exercé par le câblage réel -(1500 W a deux encodages, §4). +un palier doit pouvoir porter PLUSIEURS combinaisons. -> **Ne pas ouvrir l'étape 3 avant confirmation physique du câblage** (§13-3) : -> c'est lui qui justifie l'ordre de cette étape, et il provient aujourd'hui du -> simulateur, pas d'une mesure. +> **ECS-305 passe devant ECS-302.** L'installation de référence (§4.0) n'exerce +> pas ECS-302 — aucun palier n'y a deux encodages — tandis que sa pondération +> binaire déséquilibre fortement l'usure, ce que seul ECS-305 rend mesurable. +> ECS-302 reste dans cette étape, mais il n'en est plus le préalable structurel. +> +> **Ne pas ouvrir l'étape 3 avant la confirmation physique du câblage** (§4.0, +> §13-3) : c'est elle qui décide de l'ordre interne de cette étape. **Étape 4 — ECS-410** (échec d'écriture). ECS-411 a été **remonté en étape 1** (§13-2). ECS-410 reste ici : il touche le même chemin `applyRelayStage` / @@ -261,71 +274,97 @@ fenêtre de verrou active ». ## §4 — Combinatoire (`RelayRouter`) -> **Câblage CONFIRMÉ le 2026-08-08 : 500 / 1000 / 1500 W.** Additif (les relais -> se somment), **7 valeurs distinctes** de 0 à **3000 W**, avec **une -> redondance** : 1500 W a DEUX encodages. -> -> | W | Encodage(s) | -> |---|---| -> | 0 | — | -> | 500 | R500 | -> | 1000 | R1000 | -> | **1500** | **R1500** *ou* **R500+R1000** ← deux encodages | -> | 2000 | R500+R1500 | -> | 2500 | R1000+R1500 | -> | 3000 | R500+R1000+R1500 | -> -> **Source : le simulateur du banc** (`etm-powersync-hems-sim`, -> `sim/sim_ecs_router.py:38` — `PALIERS = {1: 500.0, 2: 1000.0, 3: 1500.0}`, -> sommés dans `_power_w()`), confirmé en vol sur MQTT (relais 2+3 fermés → -> 2500 W publiés, ce qu'une cascade ne produirait pas). **Ce n'est PAS une -> mesure physique.** Cette valeur a déjà changé trois fois et elle commande -> désormais l'ordre de l'étape 3 : elle DOIT être confirmée sur plaque -> signalétique ou au pince ampèremétrique **avant l'ouverture de l'étape 3**. +## §4.0 — Installation de référence -**ECS-302 — Encodages équivalents. EXERCÉ PAR CE MATÉRIEL.** 1500 W admet -`{R1500}` et `{R500+R1000}` : il y a bien un arbitrage à rendre. L'exigence -**redevient un préalable structurel de l'étape 3** et reprend son rang **devant** -tout ce qui s'appuiera sur `m_relayMapping`. +**Ce bloc est le SEUL endroit de la spec qui porte des puissances.** Aucune +exigence n'en contient. Une exigence dont le rang, la portée ou l'exercice +dépendent du câblage le dit par renvoi ici. Une correction de câblage ne doit +toucher que ce bloc. -Ce que fait le code aujourd'hui, et ce que ça coûte : le constructeur retient la -**première** combinaison rencontrée, c'est-à-dire le masque le plus bas -(`relayrouter.cpp:58`, `if (!byPower.contains(sum))`). Pour 1500 W il retient -donc `{R500+R1000}` et **jette `{R1500}`**. Conséquence directe sur la transition -1500 → 2000 W (`{R500+R1500}`) : il faut ouvrir R1000, garder R500, fermer -R1500 — et comme l'encodage retenu impose de passer par l'ordre -coupure-avant-fermeture d'ECS-402, la charge traverse un creux. Avec l'encodage -`{R1500}` pour 1500 W, la même transition ne demanderait qu'**une seule -commutation** : fermer R500. +| Relais | Puissance | +|---|---| +| R1 | 500 W | +| R2 | 1000 W | +| R3 | 2000 W | -> **Il n'y a donc pas d'exigence d'hystérésis à ouvrir de ce côté.** Le « point -> dur 1500→2000 » qu'envisageait la 0.5.0 est une **conséquence d'ECS-302 non -> traité**, pas un phénomène propre au câblage. Aucune entrée pour ECS-303 (créé -> puis **supprimé** en 0.5.1) ni pour ECS-605. Un palier DOIT pouvoir porter **plusieurs** +Additif, pondération binaire. **8 valeurs distinctes**, de 0 à **3500 W**, au pas +de 500 W. **Aucune redondance** : un seul encodage par palier. + +| W | Encodage | +|---|---| +| 0 | — | +| 500 | R1 | +| 1000 | R2 | +| 1500 | R1+R2 | +| 2000 | R3 | +| 2500 | R1+R3 | +| 3000 | R2+R3 | +| 3500 | R1+R2+R3 | + +**Source : déclaration de l'utilisateur, 2026-08-08. PAS une mesure.** Cette +valeur a changé quatre fois. Une **vérification physique** — plaque signalétique +ou pince ampèremétrique — est **en attente**, et elle est un **préalable à +l'ouverture de l'étape 3**. + +**Le simulateur du banc ne reflète pas cette installation.** +`etm-powersync-hems-sim`, `sim/sim_ecs_router.py:38`, est câblé +`{1: 500.0, 2: 1000.0, 3: 1500.0}`. Son alignement fait l'objet d'un lot séparé +sur ce dépôt. Corriger le banc pour qu'il corresponde au matériel réel n'est pas +« ajuster le banc pour faire passer un test » — c'en est l'inverse. + +### Ce que cette installation exerce, et ce qu'elle n'exerce pas + +| Point | Conséquence | +|---|---| +| Aucune redondance | **ECS-302 n'est pas exercé.** L'exigence reste valable — les puissances sont de la configuration, un autre chantier peut être câblé 1-2-3 — mais elle perd son caractère de préalable structurel et redescend dans l'ordre de l'étape 3. | +| Encodages uniques | La reprise d'état (ECS-411) ne peut pas diverger : toute somme lue correspond à un unique ensemble de relais. La correspondance exacte aboutit toujours. | +| Transition 1500 → 2000 W | `{R1+R2}` → `{R3}` : **les trois relais basculent d'un coup**, deux ouvertures et une fermeture. Seule transition du balayage dans ce cas. Voir **ECS-303**. | +| Usure par balayage complet (0 → 3500 W) | **R1 commute 7 fois, R2 3 fois, R3 une seule** — 11 commutations au total. Voir **ECS-305**. | + +--- + +**ECS-302 — Encodages équivalents.** Un palier DOIT pouvoir porter **plusieurs** combinaisons de relais, et la combinaison retenue DOIT être celle dont le coût de transition depuis l'état courant est le plus faible : d'abord le nombre de relais à basculer, puis le relais dont le compteur de commutations est le moins entamé. -Requalification issue de l'audit : le second encodage n'est pas mal arbitré, il -est **jeté au constructeur**. `relayrouter.cpp:58` fait -`if (!byPower.contains(sum)) byPower.insert(sum, set)` — la première combinaison -rencontrée gagne, c'est-à-dire le masque le plus bas. Sur un câblage -500/1000/1500, le palier 1500 W retient `{R500, R1000}` et `{R1500}` disparaît. +Le second encodage n'est pas mal arbitré, il est **jeté au constructeur**. +`relayrouter.cpp:58` fait `if (!byPower.contains(sum)) byPower.insert(sum, set)` +— la première combinaison rencontrée gagne, c'est-à-dire le masque le plus bas. `m_relayMapping` étant un `QList>`, la structure **ne peut pas** porter deux combinaisons. ECS-302 est donc un changement de structure, pas l'ajout d'un critère. -**ECS-305 — Compteurs de commutations.** Chaque relais DOIT porter un compteur -de commutations, exposé en télémétrie. +> **Non exercé par l'installation de référence** (§4.0) : sa pondération binaire +> ne produit aucune redondance. L'exigence reste valable — les puissances sont +> des données de configuration — mais elle **n'est plus un préalable structurel** +> et redescend dans l'ordre de l'étape 3. Elle redeviendrait prioritaire sur une +> installation à encodages multiples. -Sur un balayage complet des sept paliers (0 → 3000 W), avec l'encodage retenu -aujourd'hui : **R500 commute 5 fois, R1000 3 fois, R1500 une seule**. Le relais -de plus faible puissance s'use cinq fois plus vite que le plus gros. Le comptage -par relais est **la seule façon de savoir quand le contacteur 500 W arrivera en -fin de vie** — la puissance commutée, elle, ne le dit pas. +**ECS-303 — Hystérésis élargie aux frontières de recombinaison.** Lorsque le +passage d'un palier au suivant impose de basculer plus de deux relais +simultanément, le seuil d'hystérésis de cette frontière DOIT être élargi par +rapport aux autres. -\note Ces comptes dépendent de l'encodage choisi : traiter ECS-302 les modifie. - Raison de plus pour instrumenter avant d'optimiser. +Justification : une recombinaison complète coûte le maximum de commutations et, +sous l'ordre coupure-avant-fermeture d'ECS-402, fait traverser un creux de +puissance à la charge. Une oscillation du surplus autour d'une telle frontière +est donc bien plus coûteuse qu'ailleurs, en usure comme en confort. + +L'installation de référence (§4.0) présente exactement une frontière de ce type. +La largeur du seuil DOIT être établie par le relevé de commutations au banc, pas +choisie a priori. + +**ECS-305 — Compteurs de commutations.** Chaque relais DOIT porter un compteur de +commutations, exposé en télémétrie. + +Le relais de plus faible puissance est le bit de poids faible de la combinatoire +et s'use nettement plus vite que les autres ; le déséquilibre exact dépend du +câblage (§4.0). Le comptage par relais est **la seule façon de savoir quand le +premier contacteur arrivera en fin de vie** — la puissance commutée ne le dit pas. + +\note Ces comptes dépendent aussi de l'encodage choisi : traiter ECS-302 les + modifie. Raison de plus pour instrumenter avant d'optimiser. **ECS-307 — Grain des temporisations.** `minOn`/`minOff` sont aujourd'hui **par charge** (un unique `m_lastSwitch`, `relayrouter.h:107`), ce qui satisfait @@ -351,6 +390,14 @@ tenter le retour à l'état précédent ; (3) à défaut, commander l'arrêt tot (4) à défaut, `available = false` et cesser toute commande. Le canal existe déjà, il est simplement ignoré. +> Sur l'installation de référence (§4.0), les encodages étant uniques, la +> correspondance exacte de l'ensemble de relais aboutit toujours et le modèle +> d'état ne peut pas diverger du physique. Sur un câblage à encodages multiples, +> une correspondance par puissance seule renverrait un palier dont l'encodage +> canonique diffère de l'ensemble réellement fermé, et la transition suivante +> calculerait son coupure-avant-fermeture contre un état faux. La reprise DOIT +> donc restituer l'ensemble de relais, pas seulement l'index de palier. + **ECS-411 — Reprise du palier.** `m_currentStage` vaut 0 à la construction (`relayrouter.h:106`) et aucun état de relais n'est relu. Le palier courant DOIT être déduit de l'état réel des Things. @@ -462,8 +509,9 @@ mécanique reste **L0**. Perte de sonde → mode sans sonde, pas arrêt d'urgenc |---|---|---| | ECS-306 | simulation | `testEcsBudgetUnderLock` | | ECS-412 | simulation | `testEcsRebuildPreservesLock` | +| ECS-303 | banc | relevé de commutations à la frontière de recombinaison (§4.0) — **mesurer avant de coder** | | ECS-305 | unitaire | `testEcsSwitchCount` — compteur par relais | -| ECS-302 | unitaire | `testEcsSwitchCost` — **exerçable** : 1500 W = `{R1500}` ou `{R500+R1000}` | +| ECS-302 | unitaire | `testEcsSwitchCost` — non exercé par l'installation de référence ; à tester sur un câblage à encodages multiples | | ECS-304 | unitaire | `testEcsLevelsPure` | | ECS-410 | simulation | `testEcsPartialFailure` | | ECS-411 | simulation | `testEcsRestartRecovery` | @@ -537,9 +585,14 @@ protocole dans le même lot.** | 2026-08-08 | **Étape 1 OUVERTE** — trois exigences : ECS-306, ECS-411, ECS-412 | | 2026-08-08 | ECS-412 : au démarrage à froid, le verrou est **ARMÉ** (défaut sûr), jamais purgé | | 2026-08-08 | ~~Câblage 500/1000/2000~~ — **donnée fausse**, corrigée le jour même (cf. ligne suivante) | -| 2026-08-08 | **Câblage 500/1000/1500** — 7 valeurs, plafond 3000 W, 1500 W à deux encodages. **Source : le simulateur** (`sim/sim_ecs_router.py:38`), **PAS une mesure** — à confirmer sur plaque ou au pince avant l'ouverture de l'étape 3. Troisième volte-face sur ce chiffre, et il commande désormais l'ordre de l'étape 3 | -| 2026-08-08 | **ECS-302 exercé** — redevient préalable structurel de l'étape 3, devant tout ce qui s'appuie sur `m_relayMapping` | -| 2026-08-08 | **ECS-303 SUPPRIMÉ** — le « point dur » 1500→2000 est une conséquence d'ECS-302 non traité, pas une exigence d'hystérésis. Aucune entrée ECS-605 de ce côté | +| 2026-08-08 | ~~Câblage 500/1000/1500 (source simulateur)~~ — **donnée fausse** : le simulateur ne reflète pas l'installation | +| 2026-08-08 | ~~ECS-302 exercé~~ — découlait de la donnée fausse | +| 2026-08-08 | ~~ECS-303 supprimé~~ — découlait de la donnée fausse | +| 2026-08-08 | **Câblage 500/1000/2000** — 8 valeurs, plafond 3500 W, aucune redondance. Source : déclaration utilisateur, **non mesurée**. Quatrième révision de cette donnée ; vérification physique en attente, préalable à l'étape 3 | +| 2026-08-08 | **Découplage** : les puissances ne vivent plus que dans §4.0. Aucune exigence n'en contient | +| 2026-08-08 | **ECS-302 non exercé** par l'installation de référence — n'est plus préalable structurel ; ECS-305 passe devant dans l'étape 3 | +| 2026-08-08 | **ECS-303 rétabli** et reformulé sans puissances : hystérésis élargie aux frontières imposant une recombinaison de plus de deux relais | +| 2026-08-08 | Alignement du simulateur du banc sur 500/1000/2000 — lot séparé sur `etm-powersync-hems-sim` | --- @@ -547,12 +600,13 @@ protocole dans le même lot.** 1. **ECS-307** — grain des temporisations par relais : nécessaire, ou le compteur de commutations suffit-il ? Le câblage binaire rend la question plus concrète - (R500 commute 5× plus que R1500), sans la trancher. + le câblage de référence déséquilibre fortement l'usure (§4.0), ce qui rend + la question plus concrète sans la trancher. 2. **ECS-605** — asymétrie de l'hystérésis montée/descente, à régler à l'usage. - Sans rapport avec la frontière 1500/2000 : celle-ci relève d'ECS-302. -3. **Câblage à confirmer physiquement** — 500/1000/1500 provient du simulateur, - pas d'une mesure. À vérifier sur plaque ou au pince ampèremétrique **avant - l'ouverture de l'étape 3**, dont il commande l'ordre. + La frontière de recombinaison relève désormais d'**ECS-303**. +3. **Câblage à confirmer physiquement** — la valeur de §4.0 provient d'une + déclaration, pas d'une mesure, et a changé quatre fois. Préalable à + l'ouverture de l'étape 3. 4. **Conflit d'écriture** — `connectIO()` est le mécanisme natif si l'on veut un jour exposer un relais ECS au pilotage manuel de l'utilisateur. Ce serait alors un conflit d'écriture à arbitrer, pas une aide. @@ -568,9 +622,6 @@ protocole dans le même lot.** ECS-411 remonte en étape 1. L'étape 1 porte donc trois exigences. `testEcsRebuildPreservesLock` couvre obligatoirement « `SetLoadConfig` pendant une fenêtre de verrou active ». -- **Câblage du banc** — **500/1000/1500 W**, source simulateur (cf. §4, et la - réserve : à confirmer physiquement avant l'étape 3). ECS-302 **est** exercé et - redevient préalable structurel de l'étape 3. **CLOS par l'audit** : le waterfall ne choisit pas entre encodages équivalents (il n'en voit qu'un) ; `ioConnections()`/`connectIO()` est un lien 1↔1