La correspondance exacte ne journalisait qu'au palier non nul (`if (i > 0)`). Au palier 0 —
le cas de l'armement minOff d'ECS-412, celui qu'on vient précisément observer — le journal
était muet, et « relais lus, tous ouverts » ne se distinguait pas de « reprise non
exécutée ». Constaté au banc le 2026-08-13 : ECS-411 fonctionnait, et son contrôle sur le
chemin réel était impossible depuis les traces.
Le relevé porte désormais l'état de CHAQUE relais — identifiant, puissance déclarée, et
fermé / ouvert / injoignable→supposé fermé — avec le palier retenu, la branche empruntée et
la somme lue. Sortie unique pour les trois branches. Un relais injoignable fait passer la
ligne en avertissement : le palier déduit est plausible, la lecture ne l'est pas, et
« palier repris : 0 » couvrait ces deux situations sans les distinguer.
Un défaut de fond a été trouvé PAR ce relevé, qui affichait « (0W) » pour le relais 1500 W.
La nominale par relais était redéduite des encodages solo, or la déduplication par puissance
peut en évincer un : avec 500/1000/1500, le palier 1500 est encodé {500,1000} (masque 3,
rencontré avant le masque 4) et le relais 1500 n'apparaît seul nulle part. Sa nominale valait
0, donc un démarrage sur ce seul relais donnait sumW = 0, aucune correspondance exacte, et un
palier 0 annoncé pendant que 1500 W circulaient — l'annonce en sous-estimation qu'ECS-411
existe pour interdire. Elle est prise à la déclaration (m_relayNominalW). Rendre la lecture
visible est ce qui a rendu le défaut trouvable : c'est le sens de la règle, pas un effet
secondaire.
Règle 7-c dans AGENTS.md : un silence ne doit jamais être ambigu. Journaliser l'issue, pas
seulement l'issue remarquable ; journaliser ce qui a été lu, pas seulement ce qui en est
déduit. Trois autres endroits signalés dans spec_ecs.md, non corrigés — le retour idempotent
du routeur, clearFault() sur charge saine, et le refus minStateHold en qCDebug quand
l'application est en qCInfo.
RELEVE_ECS306 §3.0, en tête du protocole : un état nymea n'émet changed() que si la valeur
bouge, donc une grandeur forcée constante déclenche le watchdog L2 en 90 s et fait mesurer le
watchdog au lieu de l'arbitrage. Constaté au banc en gelant le simulateur.
Les tests portent sur le TEXTE publié, via un collecteur de messages : un test « journal non
vide » n'aurait rien vu. Trois cas, tous vérifiés échouant sans les correctifs. Suite
complète : 112 tests, 0 échec.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
300 lines
15 KiB
Markdown
300 lines
15 KiB
Markdown
# Relevé banc — ECS-306 et commutations par relais
|
||
|
||
Dépôt : `etm-powersync-energy-plugin-etm` · Banc : `192.168.1.75` (`nymea-dev-rpi`)
|
||
Installation émulée : `specs/spec_ecs.md` §4.0 (INST-100), cf. INST-104.
|
||
|
||
> **Ce document est publié AVANT la mesure.** La section « Prédiction » est
|
||
> horodatée du **2026-08-09** et commitée avant tout profil lancé. Une
|
||
> reconstruction analytique publiée après coup ne vaut rien ; publiée avant, elle
|
||
> est falsifiable. Les résultats viendront s'ajouter en dessous, sans jamais
|
||
> réécrire la prédiction — si elle est fausse, elle reste, et c'est le
|
||
> renseignement utile.
|
||
|
||
---
|
||
|
||
## 1. Dispositif
|
||
|
||
Trois charges, un seul budget de surplus, cascade par rang ascendant.
|
||
|
||
| Charge | Rang | Adaptateur | Paliers / états | Verrous |
|
||
|---|---|---|---|---|
|
||
| `chauffe-eau` | 1 | `relay-router` — Relay1/2/3 (500/1000/2000 W) | 0…3500 W, 8 valeurs | `minOnS`/`minOffS` = 60 s (300 s pour le volet 1, cf. §3) |
|
||
| PAC SG-Ready | 2 | `sg-ready` — Relay4/5, codé en dur | P3 = 1500 W, P4 = 3000 W | `minStateHoldS` = 300 s |
|
||
| `sonde-residu` | 3 | `relay-router` — Relay6, **1 relais, 100 W** | 0 / 100 W | `minOnS`/`minOffS` = 10 s |
|
||
|
||
**La sonde est un instrument de mesure, pas une charge.** Aucune résistance n'est
|
||
câblée sur Relay6 : le relais claque, rien ne consomme. Ce qu'elle rend
|
||
observable est une **décision d'allocation** — le résidu que le waterfall laisse
|
||
à la charge suivante — et non une puissance. Son palier à 100 W est choisi très
|
||
au-dessous de l'écart attendu (500 W) pour que le basculement soit franc.
|
||
|
||
Pilotage du surplus : `banc/cmd/manual {"grandeur":"pv","mode":true,"value":<W>}`
|
||
sur le broker `192.168.1.131`, `modele.py:178`. `banc/cmd/pause` fige l'horloge du
|
||
modèle et rend le profil reproductible.
|
||
|
||
---
|
||
|
||
## 2. Prédiction — 2026-08-09, avant mesure
|
||
|
||
### Pourquoi la télémétrie ne discrimine pas
|
||
|
||
Les Things sont des relais **GPIO** : leur ThingClass n'expose pas `currentPower`.
|
||
`RelayRouter::telemetry()` retombe donc sur le **nominal commandé**
|
||
(`relayrouter.cpp:113-114`), et `currentPowerW == palier commandé` en permanence.
|
||
|
||
Conséquence : `budget_charge = budget + palier_courant`. Tant que le budget reste
|
||
positif, les deux versions choisissent le même palier et laissent le même résidu.
|
||
**La divergence n'apparaît qu'en import net**, budget signé négatif.
|
||
|
||
### Volet 1 — point de fonctionnement discriminant
|
||
|
||
Surplus 3600 W jusqu'à ce que le chauffe-eau atteigne 3500 W et arme `minOn`,
|
||
puis chute à **−300 W** maintenue.
|
||
|
||
```
|
||
budget_charge = −300 + 3500 = 3200
|
||
```
|
||
|
||
| | Palier retenu | Résidu annoncé | PAC (rang 2) | Sonde (rang 3) |
|
||
|---|---|---|---|---|
|
||
| **`+etm2`** | 3000 (plus haut ≤ 3200) | **+200 W** | état 2 (200 < P3) | **ALLUMÉE** (100 ≤ 200) |
|
||
| **`+etm3`** | 3500 (forcé par `lockMinPowerW`) | **−300 W** | état 2 | **ÉTEINTE** (100 > −300) |
|
||
|
||
Sous `+etm2`, le routeur écrête malgré tout à 3500 W (verrou interne) : **500 W
|
||
sont consommés de plus que comptés**, et ce sont ces 500 W que la sonde révèle.
|
||
|
||
**Prédiction falsifiable : la sonde s'allume sous `+etm2`, reste éteinte sous
|
||
`+etm3`. Si elle s'allume sous `+etm3`, le raisonnement est faux.**
|
||
|
||
Prédiction secondaire : **la PAC ne changera pas d'état** entre les deux
|
||
versions — 200 W comme −300 W sont tous deux sous P3 = 1500 W. Le volet 1 ne
|
||
montrera donc rien sur la PAC ; c'est la sonde qui porte la démonstration. Sans
|
||
elle, ce relevé aurait conclu « aucun effet observable » à tort.
|
||
|
||
### Volet 2 — commutations par relais
|
||
|
||
Balayage complet 0 → 3500 W puis retour, plateaux ≥ 90 s (`minOnS` = 60 s).
|
||
|
||
Comptage attendu sur un aller simple :
|
||
|
||
| Relais | Commutations | Part |
|
||
|---|---|---|
|
||
| R500 | **7** | bit de poids faible |
|
||
| R1000 | **3** | |
|
||
| R2000 | **1** | |
|
||
| **total** | **11** | |
|
||
|
||
La transition **1500 → 2000 W** (`{R500,R1000}` → `{R2000}`) bascule **les trois
|
||
relais**, soit **3 des 11** commutations à elle seule. C'est la seule du câblage
|
||
dans ce cas.
|
||
|
||
**Piège prédit — le cliquet.** `currentPowerW` valant le nominal commandé, le
|
||
budget rendu à la charge inclut toujours son propre palier : un profil qui ne
|
||
fait que **décroître en surplus positif** ne fera jamais redescendre le palier.
|
||
Il faut des plateaux à **surplus négatif** pour provoquer la descente, sinon le
|
||
chauffe-eau reste à 3500 W du début à la fin et le relevé est vide.
|
||
|
||
---
|
||
|
||
## 3. Protocole
|
||
|
||
### 3.0 — PRÉALABLE : le compteur doit BOUGER, pas seulement être publié
|
||
|
||
**À lire avant toute autre ligne de ce protocole.** Il conditionne la validité de
|
||
tout ce qui suit, quel que soit le volet.
|
||
|
||
Un état nymea n'émet `changed()` que si **la valeur change**. Le watchdog L2
|
||
compte les *mises à jour* du compteur racine, pas les publications : une valeur
|
||
republiée à l'identique n'en produit aucune. Au bout de **90 s** sans changement,
|
||
l'arbitre déclare le compteur muet, bascule en **mode dégradé L2**, force toutes
|
||
les charges à leur repli et **suspend la planification**.
|
||
|
||
Conséquence directe : **une mesure à valeur forcée constante ne mesure pas
|
||
l'arbitrage, elle mesure le watchdog.** Un profil en plateaux de 90 s ou plus,
|
||
injecté comme une consigne fixe, s'auto-détruit — et le relevé se remplit de
|
||
`consigne 0 W (force) | "Compteur muet depuis >90 s"` au lieu de décisions.
|
||
|
||
> Constaté au banc le 2026-08-13, en forçant le surplus pour observer l'armement
|
||
> minOff : le simulateur avait été **gelé** (`banc/cmd/pause`), sur le
|
||
> raisonnement — faux — que la publication continue suffisait à entretenir la
|
||
> fraîcheur. `tick()` sort avant tout recalcul quand `paused` est vrai, donc la
|
||
> valeur ne bougeait plus. L2 a mordu en 105 s, forcé l'ECS et la PAC à 0 et
|
||
> suspendu la planification. La mesure visée avait été capturée 80 s plus tôt ;
|
||
> elle aurait été perdue avec un cycle de plus.
|
||
|
||
**Règle pour les deux volets.** Toute grandeur forcée doit varier à un pas
|
||
inférieur à 90 s — quelques dizaines de watts suffisent, l'amplitude est
|
||
indifférente, seul le changement compte. Sur le banc, épingler l'horloge simulée
|
||
en alternant deux valeurs proches (ex. `banc/cmd/heure` à 11,9 puis 12,1 toutes
|
||
les 20 s) tient le budget à peu près constant tout en entretenant les
|
||
changements. **Ne jamais utiliser `banc/cmd/pause`** pendant une campagne.
|
||
|
||
Corollaire favorable : des **compteurs réels** bougent d'eux-mêmes. Une fois les
|
||
Eastron et le smartmeter ECS appairés (§7 de `spec_ecs.md`), ce piège disparaît
|
||
de lui-même — c'est une raison de plus de faire l'appairage avant la campagne.
|
||
|
||
### 3.1 — Campagnes
|
||
|
||
Deux campagnes **séparées**, jamais fusionnées : si un résultat surprend, il faut
|
||
pouvoir dire lequel des deux réglages en est la cause.
|
||
|
||
**Volet 1** — `minOnS` du chauffe-eau porté à **300 s** le temps de l'essai, pour
|
||
élargir la fenêtre d'observation. Écart de réglage assumé et déclaré ; remis à
|
||
60 s après.
|
||
|
||
**Volet 2** — `minOnS` à 60 s, plateaux ≥ 90 s, avec plateaux à surplus négatif.
|
||
|
||
**Ordre d'exécution** : `+etm3` étant déjà installé, mesurer l'**après** d'abord,
|
||
puis descendre en `+etm2`, refaire le même profil, puis remonter en `+etm3`.
|
||
|
||
**Vérification obligatoire au retour en `+etm2`** : relire paliers et rangs par
|
||
`NymeaEnergy.GetLoadConfig` **avant** de lancer le profil. La configuration a été
|
||
écrite par `+etm3` ; si `+etm2` la rejetait — silencieusement ou non — on
|
||
mesurerait deux dispositifs différents sans le voir.
|
||
|
||
---
|
||
|
||
## 4. Résultats
|
||
|
||
### 4.0 — Un défaut trouvé avant toute mesure (2026-08-09)
|
||
|
||
La première tentative a révélé un **blocage circulaire dans ECS-412**, pas dans le
|
||
banc : la charge sonde restait figée à 0 W sous 4 kW de surplus disponible. Un
|
||
horodatage de dernière commutation nul servait de sentinelle « écoulé = 0 » à
|
||
chaque cycle, si bien que toute charge démarrant relais ouverts avec `minOffS > 0`
|
||
ne pouvait jamais s'enclencher.
|
||
|
||
**La prédiction du §2 annonçait « sonde éteinte sous `+etm3` » — et elle l'était,
|
||
mais pour la mauvaise raison.** Sans un test de falsification cherchant *pourquoi*
|
||
elle se vérifiait, ce relevé aurait coché la prédiction et conclu à tort. Une
|
||
prédiction juste peut être confirmée par un mécanisme faux ; c'est la limite de
|
||
l'exercice, et elle mérite d'être écrite ici.
|
||
|
||
Corrigé en `1.15.2+etm4` (armement paresseux et transitoire), couvert par
|
||
`testEcsColdStartLockExpires`. **Vérifié au banc après déploiement** : sonde
|
||
éteinte deux cycles, puis enclenchée — le verrou court puis expire.
|
||
|
||
### 4.1 — Volet 1, expérience naturelle sous `+etm4`
|
||
|
||
Le contraste attendu entre `+etm2` et `+etm3` a été observé **au sein d'un même
|
||
binaire**, sur deux cycles consécutifs au même budget : l'un verrou actif, l'autre
|
||
verrou expiré. Le cycle à verrou expiré reproduit exactement ce que ferait
|
||
`+etm2`, puisque sans verrou le clamp d'ECS-306 n'a rien à contraindre.
|
||
|
||
| | Budget charge | Chauffe-eau | Résidu annoncé | Sonde (rang 3) |
|
||
|---|---|---|---|---|
|
||
| **Verrou minOn ACTIF** | 3202 W | **maintenu 3500 W** | **−198 W** | **éteinte** |
|
||
| **Verrou EXPIRÉ** | 3201 W | 3000 W (plus haut ≤ budget) | **+201 W** | **allumée** |
|
||
|
||
Traces brutes :
|
||
|
||
```
|
||
"chauffe-eau" → "Verrou minOn — chauffe-eau maintenue à 3500 W
|
||
(puissance engagée, budget 3202 W) ; le résidu en tient compte"
|
||
"sonde-residu" → "Verrou minOn — sonde residu maintenue à 0 W (budget -198 W)"
|
||
|
||
"chauffe-eau" → "Surplus PV 3201 W — chauffe-eau consigne 3000 W (palier)"
|
||
"sonde-residu" → "Surplus PV 201 W — sonde residu consigne 100 W (palier)"
|
||
```
|
||
|
||
**Arithmétique vérifiée**, à 2 W près du prédit :
|
||
`3202 − 3500 = −298`, puis recrédit de la sonde elle-même (100 W, alors allumée)
|
||
→ `−198`. Côté verrou expiré : `3201 − 3000 = +201`, sonde éteinte donc aucun
|
||
recrédit → `+201`, au-dessus de son palier de 100 W.
|
||
|
||
### Ce qui est démontré, et ce qui ne l'est pas
|
||
|
||
**Démontré, directement** : au même budget, honorer le verrou ou l'ignorer change
|
||
le résidu de 500 W et **fait basculer la charge de rang 3**. C'est la moitié
|
||
*comptable* de l'affaire, et elle est observée sans ambiguïté.
|
||
|
||
**NON démontré directement** : l'écart entre consommé et compté. Dans le cycle à
|
||
verrou expiré, le routeur applique **réellement** 3000 W — il n'y a donc aucun
|
||
écart dans ce cycle-là. L'autre moitié — le routeur qui écrête à 3500 W pendant
|
||
que le scheduler ne compte que 3000 — est observée dans le **premier** cycle, pas
|
||
dans le second.
|
||
|
||
Le résultat tient donc par **composition de deux moitiés observées, chacune dans
|
||
un cycle différent du même binaire**. C'est solide, mais ce n'est pas une
|
||
observation directe du défaut complet. **Un `+etm2` réellement installé reste le
|
||
seul moyen de voir les deux moitiés dans le même cycle** — c'est ce qui reste à
|
||
faire, et c'est pourquoi l'expérience naturelle ne le remplace pas.
|
||
|
||
**La prédiction secondaire est confirmée aussi** : la PAC de rang 2 est restée en
|
||
état 4 sur les deux cycles. Sans la charge sonde, ce relevé aurait conclu « aucun
|
||
effet observable ».
|
||
|
||
### 4.2 — Volet 2, commutations : PRÉDICTION FALSIFIÉE (protocole en cause)
|
||
|
||
Profil de 18 plateaux de 100 s, montée 0 → 3600 W puis descente jusqu'à −3000 W.
|
||
|
||
| Cible surplus | Palier | Commutations |
|
||
|---|---|---|
|
||
| 0 → 3600 (8 plateaux, montée) | **3500 W tout du long** | **0** |
|
||
| 3600 → 500 (6 plateaux, descente) | **3500 W tout du long** | **0** |
|
||
| 0 | 3000 W | 1 — R500 |
|
||
| −500 | 2000 W | 1 — R1000 |
|
||
| −1500 | 500 W | 2 — R500, R2000 |
|
||
| −3000 | 0 W | 1 — R500 |
|
||
|
||
**Mesuré : R500 = 3, R1000 = 1, R2000 = 1 — total 5.**
|
||
**Prédit : 7 / 3 / 1 — total 11.**
|
||
**Transitions à trois relais observées : AUCUNE.** La frontière 1500 → 2000 n'a
|
||
jamais été franchie.
|
||
|
||
#### Le cliquet est confirmé, et plus fort que prédit
|
||
|
||
**Quatorze plateaux consécutifs sans une seule commutation**, de 0 à 3600 W puis
|
||
retour à 500 W. Le palier n'a bougé qu'une fois le surplus **négatif**. La
|
||
prédiction du §2 sur ce point est non seulement juste, elle était trop prudente :
|
||
ce n'est pas seulement la descente en surplus positif qui ne fait rien redescendre,
|
||
c'est **toute la plage positive**.
|
||
|
||
#### Pourquoi la prédiction 7/3/1 est fausse : un EMBALLEMENT propre au banc
|
||
|
||
Ce n'est pas un « mauvais point de départ ». Le chauffe-eau était à 3500 W pour une
|
||
raison **mécanique**, et elle mérite d'être comprise avant qu'on ne « corrige » la
|
||
mauvaise chose.
|
||
|
||
Deux faits se composent sur ce banc :
|
||
|
||
1. les Things sont des relais **GPIO** : leur ThingClass n'expose pas
|
||
`currentPower`, donc le recrédit anti-clignotement (correction B) crédite le
|
||
**nominal commandé** ;
|
||
2. le rootmeter de `.75` est la **vue SunSpec du simulateur**, aveugle aux
|
||
résistances réellement câblées sur ces broches. La consommation de l'ECS ne fait
|
||
donc **jamais** baisser l'export mesuré.
|
||
|
||
Résultat : `budget_charge = surplus + palier` **double-compte**. Chaque cycle, le
|
||
budget apparent dépasse le palier courant, la charge monte d'un cran — et
|
||
recommence. Tant que le surplus dépasse 500 W, le palier grimpe jusqu'au plafond.
|
||
C'est un **emballement**, et il explique à lui seul les quatorze plateaux sans
|
||
commutation.
|
||
|
||
> **NE PAS « CORRIGER » LA CORRECTION B.** Cet emballement est un **artefact de
|
||
> banc**, pas un défaut du moteur. Sur une installation réelle, l'ECS est **derrière
|
||
> le compteur réseau** : sa consommation fait réellement chuter l'export, le
|
||
> recrédit compense exactement ce que la charge vient de retirer, et la boucle est
|
||
> juste. Supprimer ou amoindrir le recrédit casserait l'anti-oscillation qu'il
|
||
> protège (ECS-404) sans rien régler. Le défaut est dans la **cécité du compteur du
|
||
> banc**, pas dans l'arithmétique du budget.
|
||
|
||
#### Conséquence sur ECS-303 : NON affaibli
|
||
|
||
La remarque « la frontière 1500 → 2000 est inatteignable en montée » vaut **pour ce
|
||
banc**, où l'emballement interdit toute montée pas à pas. Elle ne vaut pas pour une
|
||
installation réelle : la rampe matinale y traverse la bande 1500-2000 W lentement,
|
||
et la frontière est franchie normalement.
|
||
|
||
**ECS-303 reste donc justifié.** Et le volet 2 n'est **pas** à refaire depuis le
|
||
palier 0 sur le simulateur — il est à faire **après le câblage** de la résistance
|
||
physique et du compteur, sur la vraie installation. C'est le seul contexte où le
|
||
comptage de commutations dit quelque chose sur la durée de vie des contacteurs.
|
||
|
||
### 4.3 — Ce qui reste à mesurer
|
||
|
||
- **`+etm2` réellement installé**, pour confirmer que le cycle « verrou expiré »
|
||
est bien un substitut fidèle. L'expérience naturelle est convaincante mais elle
|
||
n'est pas la comparaison demandée.
|
||
- **Volet 2** — relevé de commutations par relais : non commencé.
|