docs/TEST_TERRAIN.md : procédure Palier 1 (14 tests T1-T14) pour le banc nymea-dev arm64 — §0 pré-vol (forçables [À LIRE SUR LA BOX]), §1 déploiement (cross-arm64→scp→dpkg, logging NymeaEnergy.debug, déclaration adaptateurs codée dans energypluginnymea.cpp), §2-3 ECS 1/3 relais (dont transition non-cascadée 1500→2000), §4 SG-Ready (montée/ atomicité/hystérésis), §5 watchdog L2, §6 interaction priorités, §7 EV optionnel. AGENTS.md ÉTAT : 3e clôturée (testEcsRelayTopologies dfdd988), audit Doxygen (5→0), TEST_TERRAIN créé ; déféré = passe README+contrats (force/minStage-maxStage/min-maxState/ degradedMode pas encore dans le protocole publié), Waveshare, V2C, 3f, 3g, config priorités, arm64 CI, Doxyfile+CI. Prochaine action : test terrain vendredi puis passe contrats. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 KiB
TEST_TERRAIN.md — Procédure de test Palier 1 (nymea-dev arm64)
Test terrain du moteur etm-powersync-energy-plugin-etm sur banc : compteur mock
forçable (puissance injectée par HTTP) + relais GPIO réels (vérifiés par gpioget).
14 tests (T1–T14). Chaque test : inject → log attendu → vérif GPIO → case ✓/✗.
Convention puissance compteur :
currentPower < 0= export (surplus PV) ;> 0= import. Le moteur calcule un surplus net SIGNÉ(exportW − importW)qui cascade par priorité.
§0 — Pré-vol (à remplir SUR LA BOX)
| Élément | Valeur | Source |
|---|---|---|
| IP nymea-dev | __________ |
[À REMPLIR] (réseau banc) |
| Port JSON-RPC | nymeas://<IP>:2222 (TCP+TLS) · wss://<IP>:4444 |
connu |
| Auth requise ? | __________ |
[À LIRE SUR LA BOX] JSONRPC.Hello → champ authenticationRequired |
| Token (si auth) | __________ |
Users.Authenticate {username,password,deviceName} → token |
| ThingClassId compteur mock | 2721a051-6e12-471a-baba-21d87c4cebc9 |
connu (energymocks) |
| ThingId compteur mock | __________ |
[À LIRE] après Integrations.AddThing, ou Integrations.GetThings |
| Port HTTP compteur mock | 26655 (param port par défaut) |
connu |
| ThingId relais R500 / R1000 / R2000 | __________ |
[À LIRE] Integrations.GetThings (plugin GPIO relais réel) |
| ThingId relais SG-Ready K1 / K2 | __________ |
[À LIRE] idem |
| gpiochip + offsets relais | __________ |
[À LIRE] gpiodetect puis gpioinfo gpiochip0 |
| Conteneur build cross-arm64 | __________ |
[À LIRE DANS etm-powersync-deploy/DEPLOY.md] |
[À LIRE SUR LA BOX]= dépend du déploiement, pas inventé ici. Tout le reste est figé.
§1 — Déploiement (option a : build cross-arm64 → scp → dpkg)
a. Build cross-arm64 (depuis le poste dev)
# Dans le conteneur de build cross-arm64 (cf. DEPLOY.md du repo etm-powersync-deploy).
# Produit le .deb arm64 : nymea-energy-plugin-nymea_<ver>_arm64.deb
# (TARGET inchangé = libnymea_energypluginnymea.so, drop-in remplaçant l'amont).
Le nom de paquet/TARGET est inchangé (décision Phase 1) → le
.debETM remplace le plugin énergie amont. Un seul plugin énergie chargé.
b. Déploiement sur la box
BOX=<IP> # [À REMPLIR]
scp nymea-energy-plugin-nymea_*_arm64.deb root@$BOX:/tmp/
ssh root@$BOX 'dpkg -i /tmp/nymea-energy-plugin-nymea_*_arm64.deb && systemctl restart nymead'
ssh root@$BOX 'journalctl -u nymead -f' # suivre les logs
.soinstallé dans/usr/lib/<multiarch>/nymea/energy/libnymea_energypluginnymea.so.
c. Activer le logging [Arbitre] (SINON aucune trace de décision)
La catégorie est exactement NymeaEnergy (NYMEA_LOGGING_CATEGORY(dcNymeaEnergy, "NymeaEnergy")).
Méthode robuste — drop-in systemd (Qt logging rules) :
ssh root@$BOX 'mkdir -p /etc/systemd/system/nymead.service.d && \
printf "[Service]\nEnvironment=QT_LOGGING_RULES=NymeaEnergy.debug=true\n" \
> /etc/systemd/system/nymead.service.d/etm-logging.conf && \
systemctl daemon-reload && systemctl restart nymead'
Alternative selon la version :
nymead --logging <règles>ou la section logging de/etc/nymea/nymead.conf. [VÉRIFIERnymead --helpSUR LA BOX] si le drop-in ne suffit pas. Les lignes attendues commencent par[Arbitre]et[EcsRelayAdapter]/[SgReadyAdapter].
d. Déclarer les adaptateurs de test — LA VRAIE MÉTHODE
⚠️ Il n'existe pas encore de config runtime des adaptateurs (déféré : couche « config
priorités »). En production, energypluginnymea.cpp::init() crée l'EnergyArbitrator mais
n'enregistre aucun adaptateur ECS/SG-Ready. La déclaration se fait donc par un bloc de
code ajouté à energypluginnymea.cpp (juste après la création de chargingManager,
ligne ~54), recompilé dans le .deb de test.
Workflow : déployer une 1re fois → Integrations.AddThing le compteur mock + les relais GPIO
→ relever leurs ThingId (GetThings) → coller le bloc ci-dessous avec ces ThingId → rebuild → redéployer.
// energypluginnymea.cpp, init(), APRÈS la ligne :
// EnergyArbitrator *chargingManager = new EnergyArbitrator(...);
#ifdef ETM_ARBITRATOR
{
ThingManager *tm = thingManager();
// --- ECS 3 relais (8 niveaux binaires) — ThingId réels [À REMPLIR] ---
const QString R500 = "{__________}"; // relais GPIO 500 W
const QString R1000 = "{__________}"; // relais GPIO 1000 W
const QString R2000 = "{__________}"; // relais GPIO 2000 W
auto *ecs = new EcsRelayAdapter(
tm, "ecs-terrain", "ECS banc",
QList<int>({0, 500, 1000, 1500, 2000, 2500, 3000, 3500}),
QList<QList<QString>>({ {}, {R500}, {R1000}, {R500,R1000},
{R2000}, {R500,R2000}, {R1000,R2000}, {R500,R1000,R2000} }),
/*minOnS*/ 60, /*minOffS*/ 60, /*priority*/ 1, chargingManager);
chargingManager->registerEcsAdapter(ecs);
// --- SG-Ready (2 bits K1/K2) — ThingId réels [À REMPLIR] ---
const QString K1 = "{__________}";
const QString K2 = "{__________}";
auto *pac = new SgReadyAdapter(
tm, "pac-terrain", "PAC banc",
QHash<int,QList<QString>>({ {1,{K1}}, {2,{}}, {3,{K2}}, {4,{K1,K2}} }),
QHash<int,double>({ {1,0.0}, {2,0.0}, {3,1500.0}, {4,3000.0} }),
/*minStateHoldS*/ 300, /*priority*/ 2, chargingManager);
chargingManager->registerSgReadyAdapter(pac);
}
#endif
Inclure
#include "etm/adapters/ecsrelayadapter.h"etetm/adapters/sgreadyadapter.h. Le root meter = le compteur mock : le déclarer via l'expérience énergie nymea (Energy.SetRootMeter/ config) avec le ThingId du compteur mock. Pour l'ECS simple (T1/T2), utiliser un seul relais :stages {0,2000},mapping {{},{R2000}}.
Helpers bash (poste dev ou box)
BOX=<IP>; MPORT=26655 # [À REMPLIR]
# Injecter une puissance compteur (W). Négatif = export (surplus PV), positif = import.
inject(){ curl -s "http://$BOX:$MPORT/setstates?connected=true¤tPowerPhaseA=$(($1/3))¤tPowerPhaseB=$(($1/3))¤tPowerPhaseC=$(($1/3))" >/dev/null; echo "compteur = $1 W"; }
# Compteur MUET : on cesse d'injecter (>90 s) → watchdog L2 bascule (QTimer 30 s, seuil 90 s).
mute(){ echo "NE PLUS injecter pendant >90 s…"; sleep 95; }
# Lire un relais GPIO réel (0/1) : relay <gpiochip> <offset>
relay(){ ssh root@$BOX "gpioget $1 $2"; } # ex. relay gpiochip0 17
# Suivre les décisions
logs(){ ssh root@$BOX "journalctl -u nymead -f | grep -E 'Arbitre|EcsRelay|SgReady'"; }
Le compteur mock pose
currentPower = somme des 3 phases.RootMeter::currentPower()le relit.
§2 — ECS simple (1 relais, paliers {0, 2000})
T1 — Montée sur surplus
- inject :
inject -2500 - log attendu :
[Arbitre] … Surplus PV … ECS palier 1puis[EcsRelayAdapter] … → stage 1 - vérif GPIO :
relay <chip> <R2000>→ 1 (ON) - ✓ / ✗ :
____
T2 — Délestage (import)
- inject :
inject 1000(import → surplus net négatif) - log attendu :
Surplus insuffisant … ECS éteint;→ stage 0 - vérif GPIO : relais → 0 (OFF)
- ✓ / ✗ :
____
§3 — ECS 3 relais (8 niveaux binaires R500/R1000/R2000)
T3 — Cascade montante
- inject :
inject -1700(budget 1700 → palier 1500) - log :
ECS palier 3 (1500 W) - vérif GPIO : R500=1, R1000=1, R2000=0
- ✓ / ✗ :
____
T4 — Transition NON-CASCADÉE 1500 → 2000 (le test clé)
- contexte : on part de T3 (palier 1500, l'ECS mesure 1500 W).
- inject :
inject -700(budget = 700 + 1500 recrédit = 2200 → palier 2000) - log :
ECS palier 4 (2000 W); commutation off-before-on (R500/R1000 coupés avant R2000) - vérif GPIO : R500=0, R1000=0, R2000=1 ← set final R2000 SEUL, pas un état parasite durable
- ✓ / ✗ :
____
T5 — Protection minOn (anti court-cycling)
- contexte : ECS vient de commuter (< minOn 60 s).
- inject :
inject 1200(import → le budget voudrait éteindre) - log attendu :
Verrou minOn — … maintenu palier … - vérif GPIO : relais inchangés (maintien). Attendre > 60 s puis re-
inject 1200→ délestage. - ✓ / ✗ :
____
T6 — Délestage complet
- inject :
inject 2000(import franc, > minOn écoulé) - vérif GPIO : R500=R1000=R2000=0
- ✓ / ✗ :
____
§4 — SG-Ready (PAC, 2 bits K1/K2 ; P3=1500, P4=3000)
T7 — Montée d'états 2 → 3 → 4
- inject
-1000→ état 2 (K1=0,K2=0) ; inject-2000→ état 3 (K2=1) ; attendre > minStateHold (300 s) puis inject-2500→ état 4 (K1=1,K2=1). - log :
… recommandée (état 3 …)puis… forcée (état 4 …) - vérif GPIO : état 3 → K1=0,K2=1 ; état 4 → K1=1,K2=1
- ✓ / ✗ :
____
T8 — Atomicité (transitoire bénin)
- contexte : transition 2→4 (00→11) commute K1 ET K2.
- vérif GPIO : set final K1=1, K2=1 (état 4). Le transitoire passe par K2 d'abord
(01=reco), jamais 10=blocage — sub-ms, non observable à
gpiogetmais garanti code (transientHarm). Confirmer simplement le set final correct. - ✓ / ✗ :
____
T9 — Hystérésis (pas de bascule 3↔4)
- contexte : PAC en état 4 ; faire osciller le surplus dans la zone morte.
- inject
-300(budget ≈ 3300) → reste 4 ;-500(≈3500) → reste 4 ;-100(≈3100) → reste 4. - vérif GPIO : K1=K2=1 stable (aucun clignotement 3↔4). Passer
inject 300(budget < 3000) → sort en 3. - ✓ / ✗ :
____
§5 — Watchdog L2 (compteur muet)
T10 — Compteur muet → mode dégradé
- inject
-2500(ECS/PAC servis), puismute(>90 s sans injection). - log attendu :
[Arbitre] Compteur muet depuis … mode dégradé L2; ECS palier 0 ; PAC état 2. - vérif GPIO : relais ECS → 0 ; PAC K1=0,K2=0 (état 2, jamais blocage).
- ✓ / ✗ :
____
T11 — Stabilité (faux surplus piège)
- contexte : toujours muet ; la dernière valeur injectée (-2500) reste « collée » au compteur.
- vérif GPIO : sur plusieurs minutes muettes, ECS reste 0 malgré ce surplus stale (planification suspendue — pas de replanif sur cache mort).
- ✓ / ✗ :
____
T12 — Reprise
- inject
-2500(le compteur re-parle). - log attendu :
Compteur de nouveau actif — sortie du mode dégradé; recalcul normal. - vérif GPIO : ECS resuit le surplus (palier > 0). Pas de restauration d'ancienne consigne.
- ✓ / ✗ :
____
§6 — Interaction budget partagé
T13 — Ordre de priorité ECS↔PAC inversable
- contexte : surplus moyen
inject -3000, ECS palier 2400 vs PAC P3 1500. - ECS prio 1 / PAC prio 2 : ECS se sert (palier), PAC voit le reliquat (600) → état 2.
- Inverser les priorités (échanger
prioritydans le bloc §1.d, rebuild/redeploy) → PAC se sert (état 3), ECS voit le reliquat (1500 < 2400) → palier 0. - vérif GPIO : le service s'inverse selon la priorité (preuve du waterfall unifié).
- ✓ / ✗ :
____
§7 — OPTIONNEL EV / V2C (si plugin prêt)
T14 — Ordre étagé EV → ECS (beta : EV servi avant le waterfall)
- contexte : borne EV branchée + surplus moyen.
- attendu : l'EV est servi en premier (proxy amont, décision B), l'ECS voit le reliquat.
- vérif : courant EV puis palier ECS sur le reliquat. (V2C = session dédiée, hors Palier 1.)
- ✓ / ✗ :
____
Checklist sécurité (AVANT mise sous tension)
- Disjoncteur L0 du banc accessible et identifié (coupure physique immédiate).
- Banc câblé hors tension ; sections/calibres relais conformes aux puissances (R2000 = 2000 W).
- Polarité / repérage K1/K2 SG-Ready vérifié (1:0=blocage, 0:0=normal, 0:1=reco, 1:1=forcé).
- Relais résistifs ECS : pas de charge inductive sur ces voies.
gpioinforelevé et mapping offset↔relais consigné avant tout test.- L1 (failsafe bornes/relais) configuré si applicable ; L0 reste le filet ultime.
- Un opérateur à la main sur le disjoncteur pendant T1–T6 (montées de puissance).