docs(spec): spec_ecs 0.5.2 — câblage 500/1000/2000 et DÉCOUPLAGE des puissances

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) <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-08-08 13:34:43 +02:00
parent 3846a2e29c
commit fa35ff9ba2

View File

@ -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<QList<QString>>`, 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