From 1011a6ccd70732d636c0a0c0838d039d905e8f76 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Sat, 8 Aug 2026 12:15:40 +0200 Subject: [PATCH] =?UTF-8?q?docs(spec):=20spec=5Fecs=200.5.1=20=E2=80=94=20?= =?UTF-8?q?c=C3=A2blage=20r=C3=A9el=20500/1000/1500,=20les=20trois=20consi?= =?UTF-8?q?gnations=20s'inversent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La 0.5.0 consignait 500/1000/2000 sur la foi de la consigne d'étape 1. Le simulateur du banc contredit cette donnée ; arbitrage : le simulateur reflète l'installation, la donnée était fausse. Câblage : 500/1000/1500 W, additif, 7 valeurs distinctes de 0 à 3000 W, avec UNE redondance — 1500 W admet {R1500} et {R500+R1000}. Trois corrections : 1. ECS-302 est EXERCÉ. Il redevient préalable structurel de l'étape 3 et reprend son rang devant tout ce qui s'appuiera sur m_relayMapping. 2. ECS-303 SUPPRIMÉ. Le « point dur » 1500→2000 n'est pas une exigence d'hystérésis, c'est une conséquence directe d'ECS-302 non traité : relayrouter.cpp:58 retient la première combinaison rencontrée (masque le plus bas), donc {R500+R1000} pour 1500 W, et jette {R1500}. La transition vers 2000 W = {R500+R1500} demande alors d'ouvrir R1000 et de fermer R1500, avec le creux qu'impose l'ordre coupure-avant-fermeture. Avec {R1500} pour 1500 W, la même transition ne coûterait QU'UNE commutation : fermer R500. Aucune entrée ECS-303 ni ECS-605 de ce côté. 3. 7 valeurs et plafond 3000 W, non 8 et 3500. ECS-305 reste valable, comptes corrigés : par balayage complet R500 commute 5 fois, R1000 3 fois, R1500 une seule (vérifié sur l'encodage effectivement retenu par le code actuel). RÉSERVE INSCRITE : la source est le simulateur (sim/sim_ecs_router.py:38, sommé dans _power_w(), confirmé en vol sur MQTT), PAS une mesure physique. Ce chiffre a déjà changé trois fois et il commande désormais l'ordre de l'étape 3 : il doit être confirmé sur plaque signalétique ou au pince ampèremétrique AVANT l'ouverture de cette étape. Consigné en §13-3 et en garde-fou sous l'étape 3. Aucun code touché. Co-Authored-By: Claude Opus 5 (1M context) --- specs/spec_ecs.md | 133 +++++++++++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 56 deletions(-) diff --git a/specs/spec_ecs.md b/specs/spec_ecs.md index 3286997..e1d7e1c 100644 --- a/specs/spec_ecs.md +++ b/specs/spec_ecs.md @@ -1,6 +1,6 @@ # SPEC — ECS multi-palier -Version : 0.5.0 +Version : 0.5.1 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` @@ -33,8 +33,13 @@ remonte. Il ne tranche pas seul. > > **0.5.0** — §13-1 et §13-2 **tranchés et consignés** ; l'étape 1 est débloquée. > ECS-412 réduit à `m_lastSwitch` (lecture (b)) et redirigé vers sa cause racine. -> Câblage du banc confirmé **500/1000/2000** : ECS-302 n'est plus exercé par ce -> matériel, ECS-305 monte, ECS-303 créé pour le point dur 1500→2000 W. +> +> **0.5.1 — CORRECTION du câblage.** La 0.5.0 consignait 500/1000/**2000** ; +> c'était faux. Le câblage réel est **500/1000/1500**. Trois consignations +> s'inversent : ECS-302 **est** exercé et redevient préalable structurel de +> 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. --- @@ -159,8 +164,14 @@ unitaire et conditionne l'étape 3. > parle à aucun matériel. L'étape 2 ne sort **aucune** classe de ce genre : elle > extrait le noyau de calcul **interne au mécanisme relais**. Rien à voir. -**Étape 3 — ECS-302 / ECS-305.** Changement de structure de `m_relayMapping`. -Doit précéder tout ce qui s'appuiera dessus. +**É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). + +> **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. **É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` / @@ -250,44 +261,47 @@ fenêtre de verrou active ». ## §4 — Combinatoire (`RelayRouter`) -> **Câblage du banc CONFIRMÉ le 2026-08-08 : 500 / 1000 / 2000 W.** Pondération -> binaire — huit valeurs distinctes de 0 à 3500 W au pas de 500 W, **aucune -> redondance**, un seul encodage par palier. +> **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. > -> | Palier | Relais ON | W | -> |---|---|---| -> | 0 | — | 0 | -> | 1 | 500 | 500 | -> | 2 | 1000 | 1000 | -> | 3 | 500+1000 | 1500 | -> | 4 | 2000 | 2000 | -> | 5 | 500+2000 | 2500 | -> | 6 | 1000+2000 | 3000 | -> | 7 | 500+1000+2000 | 3500 | +> | 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 | > -> Ceci clôt le point ouvert « 500/1000/1500 ou 500/1000/2000 » et déplace trois -> exigences — voir ECS-302, ECS-305 et ECS-303 ci-dessous. +> **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**. -**ECS-302 — Encodages équivalents. NON EXERCÉ PAR CE MATÉRIEL.** Avec une -pondération binaire il n'y a **rien à arbitrer** : un seul encodage par palier. -L'exigence reste valable pour un câblage 1-2-3 ailleurs (deux encodages pour -1500 W), mais **le changement de structure de `m_relayMapping` perd sa -justification immédiate** : il redescend dans l'ordre de l'étape 3 et **cesse -d'être un préalable structurel**. Ne plus le traiter comme tel. +**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`. -**ECS-303 — Point dur 1500 → 2000 W.** Cette transition bascule **les trois -relais d'un coup** (R500 et R1000 s'ouvrent, R2000 se ferme). Avec l'ordre -coupure-avant-fermeture d'ECS-402, elle produit un **trou de puissance à chaque -franchissement**, en plein milieu de la plage de surplus utile. +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. -La parade n'est PAS dans la combinatoire — aucun réordonnancement ne supprime le -trou, puisque aucun sous-ensemble intermédiaire ne vaut ni 1500 ni 2000 W. Elle -est dans un **seuil d'hystérésis élargi à cette frontière précise**. Entrée -commune avec ECS-605 (hystérésis paramétrable par charge). **À MESURER au banc -avant de coder quoi que ce soit** : c'est le relevé de commutations qui dira -quelle largeur donner au seuil. - -**ECS-302 (suite) — Encodages équivalents.** Un palier DOIT pouvoir porter **plusieurs** +> **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** 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é. @@ -301,15 +315,17 @@ rencontrée gagne, c'est-à-dire le masque le plus bas. Sur un câblage porter deux combinaisons. ECS-302 est donc un changement de structure, pas l'ajout d'un critère. -**ECS-305 — Compteurs de commutations. IMPORTANCE RELEVÉE** par le câblage -binaire confirmé. Chaque relais DOIT porter un compteur de commutations, exposé -en télémétrie. +**ECS-305 — Compteurs de commutations.** Chaque relais DOIT porter un compteur +de commutations, exposé en télémétrie. -Sur un balayage complet des huit paliers, **R500 commute sept fois, R1000 trois -fois, R2000 une seule** : le bit de poids faible s'use sept fois plus vite que le -bit de poids fort. Le comptage par relais devient **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. +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. + +\note Ces comptes dépendent 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 @@ -446,9 +462,8 @@ 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, puis simulation | relevé de commutations à la frontière 1500/2000 — **mesurer avant de coder** | | ECS-305 | unitaire | `testEcsSwitchCount` — compteur par relais | -| ECS-302 | unitaire | `testEcsSwitchCost` — **non exerçable sur le câblage 500/1000/2000** (encodage unique) | +| ECS-302 | unitaire | `testEcsSwitchCost` — **exerçable** : 1500 W = `{R1500}` ou `{R500+R1000}` | | ECS-304 | unitaire | `testEcsLevelsPure` | | ECS-410 | simulation | `testEcsPartialFailure` | | ECS-411 | simulation | `testEcsRestartRecovery` | @@ -521,7 +536,10 @@ protocole dans le même lot.** | 2026-08-08 | **§13-2 CLOS, lecture (b)** — ECS-412 réduit à `m_lastSwitch` + cause racine (rebuild incrémental) ; ECS-411 remonté en étape 1 | | 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 banc confirmé **500/1000/2000** — ECS-302 non exercé, ECS-305 relevé, **ECS-303 créé** (point dur 1500→2000) | +| 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é | --- @@ -529,11 +547,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 7× plus que R2000), sans la trancher. -2. **ECS-303 / ECS-605** — largeur de l'hystérésis à la frontière 1500/2000 W, - et asymétrie montée/descente. **À mesurer au banc avant de coder** : c'est le - relevé de commutations qui donne le chiffre. -3. **Conflit d'écriture** — `connectIO()` est le mécanisme natif si l'on veut un + (R500 commute 5× plus que R1500), 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. +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. @@ -548,8 +568,9 @@ 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** — confirmé **500/1000/2000 W** (cf. §4). ECS-302 n'est pas - exercé par ce matériel ; ECS-305 monte en importance ; ECS-303 créé. +- **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