Quatre fonctions, une connexion de SONDE sur l'optimiseur (sans le moteur), pour ce que le client du moteur n'envoie jamais : - testGetPlanBeforeTheHandshakeIsRefused (Q3) : -32600 à son id, puis le handshake sur la même connexion est accepté (connexion gardée) ; - testARefusedHandshakeIsClosedByTheOptimizer (Q3, Q4) : autre majeure → -32602 puis l'optimiseur ferme ; avec un token attendu, token faux → -32001, fermé pareil ; - testABatchIsRefused (Q8) : un lot → une erreur -32600 à id null, son contenu non traité, connexion gardée ; - testALineOverTheLimitIsRefused (Q7) : maxLineBytes + 1 → -32600 à id null, une ligne d'exactement maxLineBytes est lue. Le traitement d'un message se constate par une réponse normée sans contexte : UpdateLearned avec id → -32601 (Q6). Réponses lues brutes : un lot renvoyé se verrait. README : tableau, boucle de lancement, ce qui n'est pas vérifié. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.8 KiB
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 |
testGetPlanBeforeTheHandshakeIsRefused |
protocole 1.2, §3 : GetPlan avant un handshake réussi → erreur -32600 à son id, connexion gardée (le handshake qui suit est accepté) |
testARefusedHandshakeIsClosedByTheOptimizer |
protocole 1.2, §3 : handshake d'une autre majeure → -32602, puis l'optimiseur ferme ; si un token est attendu, token faux → -32001, fermé pareil |
testABatchIsRefused |
protocole 1.2, §2 : un lot […] → une erreur -32600 à id: null, rien du lot traité, connexion gardée |
testALineOverTheLimitIsRefused |
protocole 1.2, §2 : une ligne de maxLineBytes + 1 octets → -32600 à id: null, connexion gardée ; une ligne d'exactement maxLineBytes est lue |
Ces deux dernières lignes valent pour le mode plan (défaut). Voir Optimiseur qui s'abstient.
Les fonctions 6 à 9 ne branchent pas le moteur : elles envoient, par une connexion de sonde, ce
que son client n'envoie jamais, et lisent la réponse brute (un lot renvoyé se verrait). Elles
utilisent des requêtes dont la réponse est normée sans contexte — UpdateLearned avec id →
-32601 (§8) — pour constater qu'un message a été, ou n'a pas été, traité.
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 testGetPlanBeforeTheHandshakeIsRefused \
testARefusedHandshakeIsClosedByTheOptimizer testABatchIsRefused testALineOverTheLimitIsRefused; 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 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 émis par l'optimiseur (ignoré par le moteur ; sa
forme n'est pas normée en 1.2), le token envoyé à un optimiseur qui n'en exige pas (§3, 1.2 : accepté ;
le moteur n'envoie de token que si l'installateur en a posé un), la réponse à un contexte GetPlan
invalide (§7, 1.2 : -32602 ; la forme d'error.data n'est pas normée), le comportement sous défaut
de transport. Le contrat : docs/etm-powersync-optimizer-protocol.openrpc.json
(prose : docs/OPTIMIZER_PROTOCOL.md, annexe G).