S'abstenir est une réponse permise (§7). Avec ETM_POWERSYNC_CONFORMANCE_EXPECT=abstain, testEveryPlanIsAccepted exige une abstention à chaque cycle (rules, fallbackCode abstain, verdict abstention/abstain, aucun échec, optimizerAlive inchangé) ; testTheSafetyLoopStaysOnTop vérifie L4 sous le rule-based (shed exigé) puis se termine en QSKIP, rien à écrêter. Mode `plan` (défaut) inchangé. Le mode retenu est affiché en tête de chaque fonction. Pas de déduction depuis le handshake : `planning` est informatif (multi-créneaux ou non) et `kinds` absent veut dire « tout admis » ; la référence en mode abstain annonce des kinds, le mode protocol-1.0 (planning false, sans capabilities) planifie. README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
etm-powersync-optimizer-conformance — conformité d'un optimiseur quelconque
Suite de test du moteur (Qt Test, harnais nymea du dépôt). Elle lance votre optimiseur, branche
dessus le vrai moteur — serveur nymea, arbitre, SocketScheduler, OptimizerClient,
adaptateurs, sur les mocks de la suite simulation — et vérifie, une fonction par point :
| Fonction | Ce qui est vérifié |
|---|---|
testHandshakeIsAccepted |
le client du moteur, directement sur l'optimiseur, passe le handshake (§3) : majeure 1, aucun code d'erreur |
testGetPlanIsAnsweredInTime |
8 cycles : chaque GetPlan répondu sous le délai du moteur (10 s), aucun échec de requête ; latence max affichée |
testTheSessionMatchesTheContract |
la session entière (les deux sens) est conforme au contrat OpenRPC embarqué dans le moteur, règles sémantiques comprises (tools/etm-powersync-schema-check --semantic) |
testEveryPlanIsAccepted |
à chaque cycle, le plan est appliqué : l'optimiseur commande, GetPlan sans fallbackCode |
testTheSafetyLoopStaysOnTop |
la boucle de sécurité reste au-dessus : une consigne hors bornes (réécrite en 100 kW par le relais de test) est écrêtée par l'adaptateur (clamped), une surcharge de phase est délestée par L4 |
Ces deux dernières lignes valent pour le mode plan (défaut). Voir Optimiseur qui s'abstient.
Les fonctions 2 à 5 passent par un relais transparent (une ligne reçue = une ligne renvoyée) qui
capture la session et mesure les délais ; la 5 est la seule où il réécrit une réponse. Banc : deux
charges etmvariableload (0–2 000 W ; plancher 800, 3 000 W) et une PAC SG-Ready (P3 1 500 W,
P4 3 000 W) ; surplus de −1 000 à 9 000 W.
Lancer contre un autre optimiseur
Construire le moteur avec ses tests (qmake6 …/etm-powersync-energy-plugin-etm.pro CONFIG+=build_tests && make -j4), puis, un processus par fonction (le harnais nymea ne rend pas sa mémoire entre deux
tests, AGENTS.md) :
cd <build>/tests/auto/conformance
export NYMEA_PLUGINS_PATH=<build>/tests/mocks/plugins/energymocks:/usr/lib/x86_64-linux-gnu/nymea/plugins
export ETM_POWERSYNC_OPTIMIZER_CMD='exec /chemin/de/mon-optimiseur --listen {endpoint}'
for fn in testHandshakeIsAccepted testGetPlanIsAnsweredInTime testTheSessionMatchesTheContract \
testEveryPlanIsAccepted testTheSafetyLoopStaysOnTop; do
systemd-run --user --scope -q -p MemoryMax=2G \
bwrap --dev-bind / / --unshare-net --tmpfs /tmp --bind "$PWD/../../.." "$PWD/../../.." \
--bind <sources> <sources> --chdir "$PWD" ./target_wrapper.sh ./etm-powersync-optimizer-conformance "$fn"
done
bwrap --unshare-net est recommandé : le harnais ouvre des ports fixes, et l'optimiseur que lance la
commande vit dans le même réseau privé (la boucle locale y fonctionne, tcp://127.0.0.1:… compris).
| Variable | Défaut | Sens |
|---|---|---|
ETM_POWERSYNC_OPTIMIZER_CMD |
l'optimiseur de référence du dépôt, mode normal |
Commande qui démarre le serveur (/bin/sh -c, dans son propre groupe de processus, arrêtée par SIGTERM puis SIGKILL). {endpoint} y est remplacé par l'endpoint où écouter, aussi passé dans ETM_POWERSYNC_OPTIMIZER_ENDPOINT. - : ne rien lancer, l'optimiseur écoute déjà |
ETM_POWERSYNC_OPTIMIZER_ENDPOINT |
unix://<répertoire temporaire du test>/optimiseur.sock |
Où l'optimiseur écoute (unix:///… ou tcp://hôte:port) |
ETM_POWERSYNC_OPTIMIZER_TOKEN_FILE |
aucun | Fichier du token que l'optimiseur attend ; le moteur l'envoie au handshake (16 caractères au moins) |
ETM_POWERSYNC_OPTIMIZER_START_S |
15 |
Délai pour que l'endpoint accepte une connexion |
ETM_POWERSYNC_SOURCE_DIR |
sources du build | Racine où trouver le validateur et l'optimiseur de référence |
ETM_POWERSYNC_CONFORMANCE_EXPECT |
plan |
Ce que la suite attend : plan (l'optimiseur commande) ou abstain (il s'abstient à chaque cycle). Toute autre valeur fait échouer |
L'attente de démarrage ouvre une connexion de sonde, refermée sans handshake : un serveur conforme
la tolère. Un échec dit ce qui manque — code d'erreur du client, écart au contrat (chemin, mot-clé),
cycles non appliqués avec leur fallbackCode, latences.
Optimiseur qui s'abstient
S'abstenir est une réponse permise ({"abstain": true}, §7) : un optimiseur qui s'abstient
toujours est conforme. Avec ETM_POWERSYNC_CONFORMANCE_EXPECT=abstain :
testEveryPlanIsAcceptedexige que chaque cycle (hors le premier, sans réponse encore) finisse enstrategy: rulesavecfallbackCode: abstain, que chaque réponse soit jugéeabstention/abstain, sans échec compté, et qu'optimizerAlivene change pas. Un plan appliqué, un autrefallbackCodeou un échec font échouer ;testTheSafetyLoopStaysOnTopvérifie toujours L4 sous le rule-based, sans consigne de l'optimiseur : la trace doit portershed(la seule baisse des consignes ne suffit pas, l'import la produit déjà). Puis la fonction se termine en SKIP : aucune consigne à écrêter ;- les trois autres fonctions sont inchangées.
À l'inverse, un optimiseur qui s'abstient en mode plan échoue à testEveryPlanIsAccepted et
testTheSafetyLoopStaysOnTop. Chaque fonction affiche en tête le mode retenu ([Conformité] mode attendu : …).
Le mode ne se déduit pas du handshake. Le protocole n'a pas de capacité qui dise « je
planifie » : planning est informatif et distingue le plan multi-créneaux du plan à un créneau
(§3), et capabilities.kinds absent veut dire « tous les kinds admis », pas « aucun ». L'optimiseur
minimal de l'annexe A répond planning: false, sans capabilities, et planifie ; l'optimiseur de
référence en mode abstain annonce kinds: ["setpoint", "state"] et ne planifie jamais. Une règle
fondée sur ces champs jugerait faux dans les deux cas : le mode se donne, il ne se devine pas.
Ce qu'elle ne vérifie pas
La qualité des plans (aucune comparaison au rule-based : c'est refoptimizer, pour la référence
seulement), les bornes VE (aucune borne au banc), UpdateLearned (ignoré par le moteur 1.1), le
comportement sous défaut de transport. Le contrat : docs/etm-powersync-optimizer-protocol.openrpc.json
(prose : docs/OPTIMIZER_PROTOCOL.md, annexe G).