7 Commits

Author SHA1 Message Date
Patrick Schurig
fa7ee442d3 docs: resserrer la conclusion du volet 1 + piège du rootmeter (ECS-504)
Deux corrections de fond.

1. « Les 500 W consommés de plus que comptés sont démontrés » allait un cran trop
   loin. Dans le cycle à verrou expiré, le routeur applique RÉELLEMENT 3000 W : il
   n'y a aucun écart consommé/compté dans ce cycle-là. Ce qui est démontré
   directement, c'est la moitié comptable — au même budget, honorer ou ignorer le
   verrou change le résidu de 500 W et fait basculer la charge de rang 3. L'autre
   moitié est observée dans le PREMIER cycle. Le résultat tient par composition de
   deux moitiés observées dans deux cycles différents du même binaire : solide,
   mais pas une observation directe. Un +etm2 réel reste le seul moyen de voir les
   deux dans le même cycle.

2. Rétractation erronée corrigée. J'avais écrit que la question du compteur ECS ne
   se posait pas, LoadConfig n'ayant aucun champ de rattachement. Le danger n'a
   jamais été là : si le compteur de la charge est désigné comme ROOTMETER dans
   nymea, sa puissance entre dans le bilan de surplus que l'arbitre lit par
   internalRootMeter(), et le budget est faussé pour toutes les charges. Aucune
   configuration n'est nécessaire — une désignation dans l'interface suffit.
   Inscrit sous ECS-504 comme piège de mise en service, applicable au câblage du
   compteur Modbus prévu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 08:54:32 +02:00
Patrick Schurig
6beee20f43 fix(etm): ECS-412 — armement à froid TRANSITOIRE (blocage circulaire corrigé)
Défaut constaté AU BANC le 2026-08-09, pas en test : une charge sonde de rang 3
est restée figée à 0 W sous 4 kW de surplus disponible.

Mécanisme. lockWindow() traitait un m_lastSwitch nul comme sentinelle
« elapsed = 0 » à CHAQUE cycle. Une charge démarrant au palier 0 avec
minOffS > 0 voyait donc offHeld vrai en permanence, maxStage forcé à 0 : elle ne
pouvait jamais s'enclencher, donc jamais commuter, donc jamais valider
m_lastSwitch. Blocage circulaire. L'intention d'ECS-412 — armer plutôt que
purger — était juste ; mon écriture rendait l'armement DÉFINITIF au lieu de
transitoire.

Portée réelle : toute charge relay-router démarrant relais ouverts avec
minOffS > 0 était gelée. Le chauffe-eau du banc (minOffS = 60) n'y échappait que
parce que ses relais étaient déjà fermés et qu'ECS-411 lui faisait reprendre un
palier non nul — au premier démarrage sur installation froide, il aurait été
bloqué lui aussi.

Correction : armement PARESSEUX. applyAction(), chemin non-const qui reçoit le
temps de cycle, estampille m_lastSwitch au premier now s'il est invalide. Le
verrou expire alors après sa durée configurée. Le repli « elapsed = 0 » ne
couvre plus que les cycles précédant la première action. L'invariant « temps =
paramètre, jamais l'horloge » est préservé : aucune horloge n'est lue, et le
contrat d'ILoadAdapter n'est pas modifié — passer now au constructeur aurait
changé l'interface pour un cas particulier.

Symétrie vérifiée : relais fermés au départ → ECS-411 donne un palier non nul,
c'est minOn qui s'arme ; relais ouverts → palier 0, c'est minOff. Les deux sont
désormais transitoires de la même façon.

Test — le vrai livrable : testEcsColdStartLockExpires. Palier 0 au départ,
minOffS = 120, budget de 5000 W. La charge reste éteinte à t0 et à t0+119, puis
s'enclenche à t0+121. Couvre aussi l'expiration de la fenêtre exposée au
scheduler (lockMaxPowerW : 0 pendant le verrou, 1000 après). Aucun test ne
combinait « palier 0 au départ » et « minOffS > 0 » — c'était le trou exact.

spec_ecs.md : ECS-412 précise que l'armement est transitoire, d'une durée égale
au verrou configuré, posé paresseusement, avec le tableau de symétrie et le
renvoi au test.

Build amd64 0 erreur. Simulation : 15/15.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 08:24:41 +02:00
Patrick Schurig
1996d7da7c docs(spec): INST-104 — .75 réglée pour émuler l'installation de référence
La configuration de la box de test déclarait 2 relais à 600/1200 W ; elle en
déclare désormais 3 à 500/1000/2000, posés par SetLoadConfig (RPC, rebuild à
chaud). Trois relais GPIO, Relay1/2/3 ; Relay4/5 restent au SG-Ready.

Formulation retenue : « réglée pour ÉMULER l'installation de référence », pas
« alignée sur le câblage réel ». La différence n'est pas rhétorique — elle dit
que la vérification physique en attente (INST-102) porte sur l'installation
CLIENTE, pas sur les broches de .75. Mesurer le banc à la pince ne confirmerait
que le banc.

Fait établi au passage : le câblage de .75 était DÉJÀ documenté dans
docs/TEST_TERRAIN.md:24-26 sous les noms R500/R1000/R2000, broches BCM 5/6/13,
avec les mêmes ThingId. La configuration à 600/1200 était donc un reliquat,
contredit par la documentation du banc elle-même — ce n'est pas la spec qui a été
imposée au banc, c'est le banc qui a été remis d'accord avec sa propre doc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 14:57:51 +02:00
Patrick Schurig
fa35ff9ba2 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>
2026-08-08 13:34:43 +02:00
Patrick Schurig
1011a6ccd7 docs(spec): spec_ecs 0.5.1 — câblage réel 500/1000/1500, les trois consignations s'inversent
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) <noreply@anthropic.com>
2026-08-08 12:15:40 +02:00
Patrick Schurig
1e80a83df9 docs(spec): spec_ecs 0.5.0 — §13-1/§13-2 tranchés, étape 1 ouverte, câblage confirmé
Précondition de la consigne d'étape 1 : les deux arbitrages étaient RELEVÉS au
journal, pas TRANCHÉS. Ils le sont maintenant, datés, dans les termes reçus.

§13-1 CLOS — le schéma « ARCHITECTURE CIBLE » d'AGENTS.md a été redessiné par le
lot de consolidation (f6a4be5) : RelayRouter et EtmVariableLoadAdapter y
figurent, le kind Stage en est retiré, SocketScheduler et BatteryAdapter sont
marqués non écrits.

§13-2 CLOS, lecture (b) — ECS-412 réduit à m_lastSwitch et redirigé vers sa cause
racine (rebuild incrémental : ne reconstruire que ce qui a changé) ; ECS-411
remonté en étape 1. L'étape 1 porte donc TROIS exigences : ECS-306, ECS-411,
ECS-412. Ajouté explicitement : au démarrage à froid, m_lastSwitch étant
irrécupérable, le verrou DOIT être ARMÉ et non purgé — l'implémentation naturelle
(QDateTime nul = verrou inactif) fait l'inverse et court-circuiterait la
protection compresseur sur une boucle de redémarrage.

Câblage du banc confirmé 500/1000/2000 (pondération binaire, 8 paliers de 0 à
3500 W au pas de 500, aucune redondance). Trois conséquences consignées :
  - ECS-302 n'est PAS exercé par ce matériel — un seul encodage par palier. La
    restructuration de m_relayMapping perd sa justification immédiate et cesse
    d'être un préalable structurel de l'étape 3.
  - ECS-305 monte en importance : sur un balayage complet R500 commute 7 fois,
    R1000 3 fois, R2000 une seule. C'est la seule mesure d'usure utile.
  - ECS-303 CRÉÉ — point dur 1500→2000 W : les trois relais basculent d'un coup,
    donc trou de puissance à chaque franchissement avec l'ordre
    coupure-avant-fermeture. La parade est un seuil d'hystérésis élargi à cette
    frontière, pas un réordonnancement. À MESURER au banc avant de coder.

Aucun code touché.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:56:53 +02:00
Patrick Schurig
9624b6ec87 docs(specs): spec_ecs 0.4.3 + spec_loadmodel 0.1.1
Deux documents normatifs, préalables au lot AGENTS.md qui les référence.

spec_ecs.md 0.4.3 — spécification ECS multi-palier, écrite après audit du code :
  - 0.4.1 : statut réel des binaires de test, portée du point de gouvernance
    AGENTS.md, citation de règle dans ECS-306, recouvrement ECS-411/ECS-412 ;
  - 0.4.2 : étape 2 « type domaine » → « noyau de calcul » (collision avec LM-100) ;
  - 0.4.3 : ECS-110 étendu — la validation ne doit pas reposer sur Q_ASSERT,
    absent du binaire release (QT_NO_DEBUG).

spec_loadmodel.md 0.1.1 — modèle de charges (domaine × mécanisme), intention de
conception. Ne déclenche aucun travail.

Aucun code touché.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 10:50:50 +02:00