Patrick Schurig eb7d727d17 docs(optimiseur): liens vers le moteur sur Gitea, plus de renvoi à INTERFACE.md, « optimizer » en anglais
Le miroir GitHub n'est pas encore synchronisé : les liens pointent sur git.etm-powersync.fr.
INTERFACE.md n'est plus dans le dépôt public : la forme des méthodes NymeaEnergy.* se lit sur
la box (JSONRPC.Introspect) ; GetOptimizerStatus n'est pas réservée à l'administrateur.
Orthographe anglaise unifiée : optimizer, optimization.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ry3vBrJT1tHdxAaXdCWi6L
2026-10-18 13:48:22 +02:00

43 KiB
Raw Blame History

Protocole

Le protocole relie le moteur d'énergie de la box à un optimiseur, sur un canal dédié. Ce n'est pas l'API de l'application : celle-ci passe par l'API JSON-RPC.

!!! info "Ce qui fait foi" - La forme des messages (champs, types, valeurs permises, ce que le moteur refuse) est fixée par le contrat machine. Les tableaux de cette page en sont générés à chaque publication : cadre en anglais, descriptions reprises telles quelles du contrat, en français. Ils sont identiques dans les trois langues. - Le sens est décrit en détail, en français, dans OPTIMIZER_PROTOCOL.md, dans le dépôt du moteur. Cette page en donne l'essentiel ; en cas d'écart, les documents du moteur l'emportent.

Transport et rôles

  • L'optimiseur est le serveur, le moteur est le client : le moteur se connecte et pose les questions. La box n'ouvre aucun port.
  • Deux transports, mêmes messages : unix:// (socket local, par défaut) et tcp:// (machine du réseau local).
  • JSON-RPC 2.0, un message par ligne (NDJSON), UTF-8.
  • Si la connexion tombe, le moteur se reconnecte avec un délai croissant et pilote par ses règles en attendant.
Property Value (contract)
Protocol version 1.1
Application protocol JSON-RPC 2.0
Framing ndjson (one message per line)
Encoding utf-8
Endpoint schemes unix:// · tcp://
Default endpoint unix:///run/powersync/optimizer.sock
Client engine
Server optimizer
Max line size 1048576 bytes
Handshake timeout 5 s
Request timeout 10 s
Reconnect backoff 1 s → 60 s, ×2
Requests in flight 1
Request ids entiers croissants sur toute la vie du client, à travers les reconnexions
Line errors ligne non JSON, sans id, id jamais émis, ligne > maxLineBytes : comptée, connexion CONSERVÉE
Token masking toute occurrence du token dans un message reçu est remplacée par « [token masqué] » dès l'analyse

Servers

Name URL Summary (contract) Example values
unix unix:///run/powersync/optimizer.sock Défaut : optimiseur local, socket possédé par root:powersync, mode 0660.
tcp tcp://{host}:{port} Optimiseur sur le LAN. Token recommandé ; il circule en clair sans TLS (E16). host = 192.168.1.10, port = 7400

Methods

Method Direction Kind Timeout Summary (contract)
Handshake engine→optimizer request 5 s Ouverture de session, à chaque (re)connexion (§3).
GetPlan engine→optimizer request 10 s Demande d'un plan pour le contexte courant (§4-§6).
UpdateLearned optimizer→engine notification (no id) — Estimations apprises de l'optimiseur (§8) — notification, sans id.

Handshake

À chaque connexion, le moteur envoie Handshake avec sa version du protocole et, s'il est configuré, le secret partagé. L'optimiseur répond sa version et, s'il le souhaite, son nom et ses capacités. Une majeure différente, un secret refusé ou une réponse illisible : la connexion est refusée, et le moteur pilote par ses règles. Si l'optimiseur annonce les types d'action (kinds) qu'il émet, le moteur refuse ensuite toute action d'un autre type.

Handshake — params

Ouverture de session, à chaque (re)connexion (§3).

Envoyé par le moteur (client) dès la connexion. Délai : 5 s depuis le DÉBUT de la tentative ; au-delà, repli et nouvelle tentative au backoff. Le message porte le secret : il n'est jamais journalisé.

Field Type Required Values Description (contract, French)
protocolVersion ProtocolVersion yes Version du moteur ; seule la majeure est comparée.
engine string yes pattern ^etm-powersync-energy-plugin-etm/ Moteur et sa version.
token HandshakeToken no

result (handshake): HandshakeResult

Errors

Code Message Note (contract)
-32001 token refusé Recommandé (optimiseur de référence). Toute réponse error est traitée pareil : handshake-refused, connexion fermée.

HandshakeToken

Secret partagé (§3, E-T2-8) : 16 caractères au moins, 4 096 octets au plus (borne en octets non exprimable ici : contrôlée par le moteur), imprimable, sans blanc ni caractère de contrôle, UTF-8 admis. Envoyé seulement s'il est configuré ; jamais journalisé.

Type: string — ≥ 16 char. ; pattern ^[^\s\x00-\x1f\x7f]+$

HandshakeResult

Field Type Required Values Description (contract, French)
protocolVersion string yes ≥ 1 char. ; pattern ^[0-9]+(\.[0-9]+)*$ Version semver de l'optimiseur ; même MAJEURE que le moteur exigée (règle major-mismatch).
name string no Nom affiché (mode installateur), non contrôlé par le moteur (E-T7-3).
planning boolean no Vrai si l'optimiseur produit des plans multi-créneaux. Informatif.
capabilities Capabilities no

Undeclared fields: ignored (optimizer-emitted form, open).

Capabilities

Capacités annoncées (§3, 1.1, optionnel). Affichées en mode installateur, réduites aux clés et valeurs connues (capacitesPubliees) ; un champ inconnu est ignoré (§9).

Field Type Required Values Description (contract, French)
maxHorizonH number no ≥ 0 Horizon maximal planifié, en heures.
resolutionS number no ≥ 0 Pas des créneaux, en secondes.
kinds [ActionKind] no PRÉSENT, il restreint les kinds acceptés (règle kind-not-announced).
usesForecast [string] no pv · baseload · price · temperature
reasonCodes boolean no L'optimiseur émet des reasonCode du catalogue.

Undeclared fields: ignored (optimizer-emitted form, open).

Le contexte : GetPlan

À chaque cycle, et sur événement (branchement d'un véhicule, nouvelle prévision, modification de la configuration…), le moteur envoie GetPlan avec un contexte : état du site et du compteur de tête, production, batterie, charges arbitrées avec leurs bornes, verrous et besoins, prévisions sur une grille temporelle unique.

Quelques règles de lecture, qui changent les décisions :

  • le compteur est transmis en mesure brute, jamais le bilan après pilotage ;
  • une valeur inconnue vaut null, jamais 0 ;
  • une charge indisponible (available: false) ne doit recevoir aucune consigne, mais elle consomme : elle reste comptée ;
  • les verrous en cours (lockMinPowerW, lockMaxPowerW) disent ce que la charge peut faire maintenant ; le moteur les applique quoi qu'il arrive ;
  • ce que mesure une charge se lit dans measurement.source, jamais dans son mécanisme (adapter) ; une puissance déclarée n'est jamais une mesure.

La forme envoyée par le moteur est fermée : il n'émet aucun champ hors du contrat.

GetPlan — params

Demande d'un plan pour le contexte courant (§4-§6).

À chaque cycle (~1/min) et sur événement (trigger). Une requête en vol au plus : une plus récente remplace l'attente de la précédente (sa réponse est ignorée par id, ce n'est pas un échec). Délai : 10 s par requête. La réponse à la requête N sert au cycle N+1 (modèle cache). Le JSON Schema dit la FORME ; x-etm-semantic-rules dit ce que le validateur refuse en plus.

Field Type Required Values Description (contract, French)
timestamp string yes instant with offset Instant du cycle — le « maintenant » du validateur.
trigger Trigger yes
site Site yes
meter Meter yes
pv Pv yes
battery Battery yes
loads [Load] yes Charges arbitrées, ⊆ GetLoadConfig.
forecast Forecast | null yes null sans instantané de prévision.
tariff Tariff | null yes

Errors

Code Message Note (contract)
-32603 erreur interne Toute réponse error compte comme un échec de la requête (rpc-error, §7) ; le code n'est pas lu.

Site

Field Type Required Values Description (contract, French)
contractedPowerW number | null yes Puissance souscrite (W) ; null si inconnue (E1), jamais 0.
gridCapW null yes RÉSERVÉ (signaux réseau type §14a EnWG) : toujours null en 1.1.
phaseLimitA [number] | null yes Limite par phase en A ; null si inconnue.

Undeclared fields: rejected (engine-emitted form, closed).

Meter

Field Type Required Values Description (contract, French)
source string | null yes ThingId du compteur racine désigné ; null si inconnu.
importW number yes ≥ 0 Puissance importée (W).
exportW number yes ≥ 0 Puissance exportée (W), mesure BRUTE (AGENTS.md règle 8) — jamais le net après pilotage.
perPhaseA [number] yes Courant par phase (A).

Undeclared fields: rejected (engine-emitted form, closed).

Pv

Field Type Required Values Description (contract, French)
currentW number | null yes ≥ 0 Production instantanée (W), positive ; null sans producteur (jamais 0).

Undeclared fields: rejected (engine-emitted form, closed).

Battery

One of: BatteryAbsent · BatteryPresent.

Load

Une charge arbitrée (§5.3) — la forme dépend du mécanisme (adapter).

One of: LoadWatts · LoadEvCharger · LoadSgReady.

Forecast

Prévisions sur UNE grille (§5.2) ; tableaux alignés index par index.

Field Type Required Values Description (contract, French)
start string yes instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.
resolutionS integer yes ≥ 1 Pas de la grille (s) — fait foi, jamais supposé.
steps integer yes ≥ 0 Nombre de cases ; toutes les séries ont cette longueur.
pv PvForecast | null yes null sans courbe (site non désigné, charge utile refusée, canal muet).
baseload Baseload yes
weather null yes Aucune source de température en 1.1 : toujours null.
price Price | null yes null sans tarif saisi.

Undeclared fields: rejected (engine-emitted form, closed).

Tariff

Tarif 1.0, conservé pour compatibilité ; un optimiseur 1.1 lit forecast.price. Aucun écrivain aujourd'hui (E3) : null en pratique.

Field Type Required Values Description (contract, French)
provider string | null yes
current TariffEntry yes
next [TariffNextEntry] yes

Undeclared fields: rejected (engine-emitted form, closed).

BatteryAbsent

Aucun stockage déclaré : rien d'autre n'est émis.

Field Type Required Values Description (contract, French)
present boolean yes false

Undeclared fields: rejected (engine-emitted form, closed).

BatteryPresent

Field Type Required Values Description (contract, French)
present boolean yes true
socPercent number yes
powerW number yes Positif = charge, négatif = décharge.
capacityWh number | null yes null si inconnue.
maxChargeW number | null yes null si inconnue.
maxDischargeW number | null yes null si inconnue.
reserveSocPercent integer | null yes Réserve de secours (%) ; null si inconnue.

Undeclared fields: rejected (engine-emitted form, closed).

LoadWatts

Charge pilotée en watts : combinaison de relais (relay-router) ou consigne continue (etmvariableload).

Field Type Required Values Description (contract, French)
id string yes ≥ 1 char. Identifiant de la charge — clé des actions.
label string yes Nom lisible.
priority integer yes Rang effectif (1 = premier servi).
domain Domain no
learned LoadLearned no
needs LoadNeeds no
adapter string yes relay-router · etmvariableload
declared object yes
declared.minPowerW number yes 0 = aucun plancher.
declared.maxPowerW number yes
telemetry object yes
telemetry.currentPowerW number yes Puissance courante (W) telle que l'adaptateur la publie.
telemetry.available boolean yes Gouverne l'ALLOCATION, pas la COMPTABILITÉ (§5.3).
telemetry.measurement Measurement no
telemetry.lockMinPowerW number yes Plancher imposé MAINTENANT par un verrou.
telemetry.lockMaxPowerW number yes Plafond imposé maintenant ; négatif = aucun plafond.
limits object yes
limits.minOnS integer yes
limits.minOffS integer yes

Undeclared fields: rejected (engine-emitted form, closed).

LoadEvCharger

Borne. Une borne CONFIGURÉE dont l'appareil manque n'a ni declared, ni limits, ni pluggedIn/charging : ses zéros diraient « inconnu ».

Field Type Required Values Description (contract, French)
id string yes ≥ 1 char. Identifiant de la charge — clé des actions.
label string yes Nom lisible.
priority integer yes Rang effectif (1 = premier servi).
domain Domain no
learned LoadLearned no
needs LoadNeeds no
adapter string yes "evcharger"
declared object no
declared.minA number yes
declared.maxA number yes
declared.phases integer yes Phases VISÉES ce cycle, pas celles de la plaque (E26).
telemetry object yes
telemetry.currentPowerW number yes Puissance courante (W) telle que l'adaptateur la publie.
telemetry.available boolean yes Gouverne l'ALLOCATION, pas la COMPTABILITÉ (§5.3).
telemetry.measurement Measurement no
telemetry.pluggedIn boolean no
telemetry.charging boolean no
telemetry.sessionWh number no Absent si la borne ne compte pas sa session (LM-1009 C1).
limits object no
limits.chargingEnabledLockS integer yes
limits.currentChangeLockS integer yes

Undeclared fields: rejected (engine-emitted form, closed).

LoadSgReady

PAC SG-Ready : 4 états normés, kind state.

Field Type Required Values Description (contract, French)
id string yes ≥ 1 char. Identifiant de la charge — clé des actions.
label string yes Nom lisible.
priority integer yes Rang effectif (1 = premier servi).
domain Domain no
learned LoadLearned no
needs LoadNeeds no
adapter string yes "sg-ready"
declared object yes
declared.states [integer] yes ≥ 1 ; ≤ 4
declared.estimatedPowerW map of number yes
telemetry object yes
telemetry.currentPowerW number yes Puissance courante (W) telle que l'adaptateur la publie.
telemetry.available boolean yes Gouverne l'ALLOCATION, pas la COMPTABILITÉ (§5.3).
telemetry.measurement Measurement no
telemetry.state integer yes ≥ 0 ; ≤ 4 État commandé (1-4) ; 0 = non applicable.
telemetry.minState integer yes Fenêtre d'états autorisée MAINTENANT par minStateHold.
telemetry.maxState integer yes
limits object yes
limits.minStateHoldS integer yes

Undeclared fields: rejected (engine-emitted form, closed).

PvForecast

Field Type Required Values Description (contract, French)
p50W [number | null] yes Puissance AC moyenne sur [t, t+pas), sommée sur les plans puis plafonnée à acLimitW ; null = case inconnue. Un plafond, pas une espérance (§5.2).
conversion string yes physics Seule valeur émise par le moteur GPL ; un consommateur ignore une valeur inconnue.
corrections [Correction] yes [] = conversion brute.
weatherModel string | null yes Modèle qui a RÉPONDU ; null si inconnu.
issuedAt string | null yes instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.
contentChangedAt string | null yes instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.
acLimitW integer no Plafond AC du site (W), s'il est configuré.
perField [PvField] no

Undeclared fields: rejected (engine-emitted form, closed).

Baseload

Field Type Required Values Description (contract, French)
model string yes profile · none Le moteur GPL émet profile ou none (learned n'est pas émis).
p50W [number | null] yes
quality BaseloadQuality no

Undeclared fields: rejected (engine-emitted form, closed).

Price

Field Type Required Values Description (contract, French)
currency string yes "EUR"
importCtkWh [number | null] yes
exportCtkWh [number | null] yes
includes [PriceInclude] yes
source string yes fixed · tou Tarif SAISI : une zone (fixed) ou des zones horaires (tou). tempo et day-ahead ne sont pas émis en 1.1.
knownUntil string yes instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.

Undeclared fields: rejected (engine-emitted form, closed).

TariffEntry

Field Type Required Values Description (contract, French)
label string yes
priceCtkWh number yes

Undeclared fields: rejected (engine-emitted form, closed).

TariffNextEntry

Field Type Required Values Description (contract, French)
label string yes
priceCtkWh number yes
from string | null yes instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.

Undeclared fields: rejected (engine-emitted form, closed).

LoadLearned

Absent tant qu'aucun profil n'a été reçu (E12).

Field Type Required Values Description (contract, French)
dailyEnergyWh number yes
confidence number yes ≥ 0 ; ≤ 1

Undeclared fields: rejected (engine-emitted form, closed).

LoadNeeds

Besoins (§5.3). Champs 1.0 émis tels quels quand ils sont déclarés ; un optimiseur 1.1 préfère energyTargets.

Field Type Required Values Description (contract, French)
dailyDeadline string no pattern ^[0-9]{2}:[0-9]{2}$ Heure murale locale HH:MM (1.0).
minEnergyWhPerDay integer no > 0
targetSocPercent integer no > 0
deadline string no instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.
energyTargets [EnergyTarget] no ≥ 1 items

Undeclared fields: rejected (engine-emitted form, closed).

Measurement

Mesure par charge (§5.3, LM-1105/1106) : décider sur source, jamais sur adapter.

variant 1 — Rien ne mesure : état positif, pas une erreur.

Field Type Required Values Description (contract, French)
source string yes "none"

Undeclared fields: rejected (engine-emitted form, closed).

variant 2

Field Type Required Values Description (contract, French)
source string yes meter · device
powerW number yes

Undeclared fields: rejected (engine-emitted form, closed).

PvField

Field Type Required Values Description (contract, French)
id string yes ThingId du plan.
corrections [Correction] yes

Undeclared fields: rejected (engine-emitted form, closed).

BaseloadQuality

Field Type Required Values Description (contract, French)
windowDays integer yes
minDays integer yes
resolutionS integer yes
daysCovered object yes
daysCovered.workday integer yes
daysCovered.weekend integer yes
minutes integer yes
commandedMinutes integer yes
negativeMinutes integer yes
incompleteMinutes integer yes
computedAt string no instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.
loads [BaseloadLoadQuality] yes

Undeclared fields: rejected (engine-emitted form, closed).

EnergyTarget

Field Type Required Values Description (contract, French)
by string yes instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.
minWh number yes > 0 Énergie minimale à avoir délivrée à l'échéance.
source string yes installer · user-intent learned n'est pas émis en 1.1.

Undeclared fields: rejected (engine-emitted form, closed).

BaseloadLoadQuality

Field Type Required Values Description (contract, French)
loadId string yes
source string yes meter · device · none
minutesBySource map of integer yes keys meter · device · none
constantMeasurement boolean no

Undeclared fields: rejected (engine-emitted form, closed).

La réponse : un plan ou une abstention

L'optimiseur répond vite — avec son dernier plan en cache s'il calcule longtemps — et recalcule en tâche de fond. Il répond soit :

  • un plan par créneaux : chaque créneau porte une action par charge disponible (consigne en watts ou en ampères, ou état SG-Ready), chacune avec son motif ; seul le créneau en cours est exécuté, et validUntil borne la durée de vie du plan ;
  • une abstention (abstain: true) : « pas de plan maintenant ». Le moteur pilote par ses règles pour ce cycle, sans considérer l'optimiseur comme en panne.

Le motif d'une action a deux formes : un texte libre en français, obligatoire, pour les traces ; et, facultatif, un code du catalogue (reasonCode) avec ses paramètres. Vers l'application, seul le code est transmis, et l'application le traduit. Un code inconnu part sous OPTIMIZER_DECISION.

La forme reçue par le moteur est ouverte : un champ inconnu est ignoré.

GetPlan — result

plan: GetPlanResult

Errors

Code Message Note (contract)
-32603 erreur interne Toute réponse error compte comme un échec de la requête (rpc-error, §7) ; le code n'est pas lu.

GetPlanResult

One of: Abstention · Plan.

Abstention

« Pas de plan maintenant » (§7) : repli pour ce cycle, pas un échec.

Field Type Required Values Description (contract, French)
abstain boolean yes true
reason string no

Undeclared fields: ignored (optimizer-emitted form, open).

Plan

Plan par créneaux (§6).

Field Type Required Values Description (contract, French)
abstain boolean no false
planId string no
strategy string no Chaîne libre, informative.
computedAt string no instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.
validUntil string | null no instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.
slots [Slot] yes [] = abstention (empty-plan). Seul le créneau couvrant maintenant est exécuté.
diagnostics Diagnostics no

Undeclared fields: ignored (optimizer-emitted form, open).

Slot

Field Type Required Values Description (contract, French)
from string yes instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.
to string yes instant with offset Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.
actions [Action] | null no Absent ou null = aucune action.
expected Expected no

Undeclared fields: ignored (optimizer-emitted form, open).

Diagnostics

Informatif : mode installateur et traces.

Field Type Required Values Description (contract, French)
solver string no
status string no optimal · feasible · best-so-far · infeasible-relaxed
gapPercent number no
computeMs number no ≥ 0
inputsUsed object no
inputsUsed.pv string no physics · hybrid · none

Undeclared fields: ignored (optimizer-emitted form, open).

Action

One of: SetpointAction · StateAction · ConstraintAction.

Expected

Trajectoire prévue : affichage et mesure d'écart, JAMAIS pour commander.

Field Type Required Values Description (contract, French)
gridImportW number no
gridExportW number no
batterySocPercent any no RÉSERVÉ (annexe F) : ignoré.

Undeclared fields: ignored (optimizer-emitted form, open).

SetpointAction

Field Type Required Values Description (contract, French)
loadId string yes ≥ 1 char. Charge visée : connue du contexte (règle unknown-load), une fois par créneau (duplicate-load).
reason string yes ≥ 1 char. ; pattern \S Texte français, OBLIGATOIRE et non vide (règle missing-reason). Traces et journal seulement, jamais vers l'app.
reasonCode ReasonCode no
reasonParams ReasonParams no
force any no INTERDIT sur le fil (§6) : ignoré, signalé une fois.
kind string yes "setpoint"
powerW number no ≥ 0 Consigne en watts (relay-router, etmvariableload ; ou borne).
currentA number no ≥ 0 Borne : courant (0 = suspendre).
phases enum no 1 · 3 Borne : phases visées ; absent = inchangé.

Undeclared fields: ignored (optimizer-emitted form, open).

At least one of: powerW, currentA.

StateAction

Field Type Required Values Description (contract, French)
loadId string yes ≥ 1 char. Charge visée : connue du contexte (règle unknown-load), une fois par créneau (duplicate-load).
reason string yes ≥ 1 char. ; pattern \S Texte français, OBLIGATOIRE et non vide (règle missing-reason). Traces et journal seulement, jamais vers l'app.
reasonCode ReasonCode no
reasonParams ReasonParams no
force any no INTERDIT sur le fil (§6) : ignoré, signalé une fois.
kind string yes "state"
state integer yes ≥ 1 ; ≤ 4 1 = verrouillage, 2 = normal, 3 = recommandation, 4 = forcé.

Undeclared fields: ignored (optimizer-emitted form, open).

ConstraintAction

RÉSERVÉ (annexe F) : forme décrite, toujours refusée (kind-mismatch).

Field Type Required Values Description (contract, French)
loadId string yes ≥ 1 char. Charge visée : connue du contexte (règle unknown-load), une fois par créneau (duplicate-load).
reason string yes ≥ 1 char. ; pattern \S Texte français, OBLIGATOIRE et non vide (règle missing-reason). Traces et journal seulement, jamais vers l'app.
reasonCode ReasonCode no
reasonParams ReasonParams no
force any no INTERDIT sur le fil (§6) : ignoré, signalé une fois.
kind string yes "constraint"
charge enum no allow · forbid
discharge enum no allow · forbid

Undeclared fields: ignored (optimizer-emitted form, open).

ReasonParams

Paramètres du motif : valeurs nombre, booléen ou code [A-Za-z]+ ; clés admises par code : x-etm-reason-params. Toute autre clé est SUPPRIMÉE avant l'app (filtrée, pas refusée).

Type: map of number | boolean | string | null

Codes de motif émissibles

reasonCode Accepted reasonParams keys
SURPLUS_SETPOINT budgetW, setpointW, stepped
SURPLUS_INSUFFICIENT budgetW
BELOW_MIN_POWER budgetW, minPowerW, state
EV_DEADLINE —
SG_FORCED budgetW, estimatedW
SG_RECOMMENDED budgetW, estimatedW
SG_NORMAL budgetW
ECO_FLOOR_GRID gridW, atMaximum, remainingWh, targetWh, remainingS, regime, minAlwaysW, boundBy
PRICE_SLOT priceCtkWh, setpointW
OPTIMIZER_DECISION —

UpdateLearned

Notification envoyée par l'optimiseur au moteur, sans réponse, pour transmettre ce qu'il a appris des charges. Dans la version 1.1 du moteur, elle est reçue et journalisée, mais pas encore exploitée.

UpdateLearned — params

Estimations apprises de l'optimiseur (§8) — notification, sans id.

Envoyée par l'OPTIMISEUR (sens inverse des deux autres), sans id, sans réponse. État 1.1 du moteur : reçue, journalisée une fois, IGNORÉE — rien n'est persisté, le contenu n'est pas lu. Forme non normée en 1.1.

Field Type Required Values Description (contract, French)
loads [object] no Non normé en 1.1 : intention §8 (learned + confidence par charge).

Machine de repli

Il n'y a pas de battement de cœur séparé : une réponse valide à GetPlan signifie que l'optimiseur est vivant. Les échecs se comptent par requête ; après plusieurs échecs consécutifs, l'optimiseur est déclaré absent et le moteur pilote par ses règles jusqu'au prochain plan valide. L'application voit cet état en continu (optimizerAlive, activeStrategy).

Machine de repli §7 (OptimizerStatus) : heartbeat implicite — une réponse valide à GetPlan = optimiseur vivant. Les échecs se comptent PAR REQUÊTE.

Consecutive failed requests before optimizerAlive = false: 2.

Situation (contract) optimizerAlive activeStrategy
réponse plan valide true optimizer
abstain, plan vide, aucun créneau courant inchangé rules (ce cycle)
plan expiré (validUntil dépassé) inchangé rules jusqu'au prochain plan valide
timeout / erreur / plan invalide sur deux requêtes consécutives false rules
socket absent / fermé / handshake refusé false rules
aucun endpoint configuré false rules
mode dégradé L2 (compteur muet > 90 s) inchangé none

Règles de validation

Le JSON Schema du contrat dit la forme. Le moteur juge ensuite chaque réponse sur le contexte de sa requête, règle par règle, dans l'ordre ci-dessous ; la première qui échoue donne son code (fallbackCode, visible dans l'application).

Ce que le moteur refuse EN PLUS de la forme, dans l'ordre de ses contrôles (OptimizerPlan::valider, OptimizerClient::handleHandshakeReply). name = code du verdict. issue : abstention (repli ce cycle, pas un échec), expired, invalid (plan refusé en entier, compté comme échec), connection-refused (handshake). needs : ce qu'il faut connaître en plus de la réponse (la requête, le handshake).

# Rule Method Outcome fallbackCode lastErrorCode Counts as failure Also needs Schema catches it too What is rejected (contract)
1 abstain GetPlan abstention abstain — no — no result.abstain vaut true.
2 malformed-plan GetPlan invalid malformed-plan malformed-plan yes — yes slots absent ou non liste ; un créneau qui n'est pas un objet ; actions non liste.
3 empty-plan GetPlan abstention empty-plan — no — no slots est une liste vide.
4 bad-time GetPlan invalid bad-time bad-time yes — yes from/to illisibles ou sans décalage, ou from ≥ to ; validUntil illisible ou sans décalage.
5 slots-unordered GetPlan invalid slots-unordered slots-unordered yes — no un créneau commence avant la fin du précédent.
6 malformed-action GetPlan invalid malformed-action malformed-action yes — yes action qui n'est pas un objet, ou sans loadId (chaîne non vide).
7 unknown-load GetPlan invalid unknown-load unknown-load yes request no loadId absent des loads[] de la requête.
8 unknown-kind GetPlan invalid unknown-kind unknown-kind yes — yes kind hors ActionKind.
9 kind-mismatch GetPlan invalid kind-mismatch kind-mismatch yes request no kind différent de celui du mécanisme de la charge (setpoint : evcharger, relay-router, etmvariableload ; state : sg-ready ; constraint : aucun).
10 kind-not-announced GetPlan invalid kind-not-announced kind-not-announced yes handshake no capabilities.kinds annoncé au handshake et kind absent de la liste.
11 missing-reason GetPlan invalid missing-reason missing-reason yes — yes reason absent, non textuel, ou vide une fois les caractères de contrôle retirés.
12 bad-value GetPlan invalid bad-value bad-value yes request yes state absent ou hors 1-4 ; borne : currentA/powerW absents ou négatifs, phases hors {1, 3} ; charge en watts : powerW absent ou négatif.
13 duplicate-load GetPlan invalid duplicate-load duplicate-load yes — no deux actions pour la même charge dans un créneau.
14 expired GetPlan expired expired — no request no validUntil ≤ timestamp de la requête.
15 no-current-slot GetPlan abstention no-current-slot — no request no aucun créneau ne couvre le timestamp de la requête.
16 incomplete-slot GetPlan invalid incomplete-slot incomplete-slot yes request no une charge available de la requête n'a pas d'action dans le créneau courant (décision Q1).
17 handshake-refused Handshake connection-refused no-plan handshake-refused no — no réponse error (token absent ou différent : -32001 recommandé) → connexion fermée, nouvelle tentative au backoff.
18 invalid-handshake Handshake connection-refused no-plan invalid-handshake no — yes result non objet, protocolVersion absent ou vide, capabilities présent et non objet.
19 major-mismatch Handshake connection-refused no-plan major-mismatch no — no MAJEURE de protocolVersion (ce qui précède le premier point) différente de celle du moteur.

Tolérances

Ce que le moteur accepte au-delà du contrat. Un optimiseur conforme ne s'en sert pas.

Ce que le moteur ACCEPTE au-delà du schéma — un optimiseur conforme ne s'en sert pas.

  • reasonCode absent ou hors ReasonCode : action acceptée, motif transmis sous OPTIMIZER_DECISION sans paramètres.
  • reasonParams : clé non admise pour le code (x-etm-reason-params) ou valeur refusée : clé supprimée, action acceptée.
  • planId, strategy : lus comme chaînes quel que soit leur type ; computedAt illisible : ignoré.
  • diagnostics, expected : non objets ignorés ; valeurs inconnues filtrées avant l'app.
  • abstain : toute valeur « vraie » au sens de QVariant::toBool (true, chaîne non vide autre que « false »/« 0 », nombre non nul).
  • champs inconnus (params et result) : ignorés (§9) ; force et champs réservés (annexe F) : ignorés et signalés une fois.
  • state : 3.0 accepté comme 3 (entier au sens JSON Schema).

Listes fermées

Valeurs permises, régénérées depuis le code du moteur.

ProtocolVersion

Version du protocole parlée par le moteur (GEN : OptimizerProtocol::ProtocolVersion).

1.1

Regenerated from the engine code: OptimizerProtocol::ProtocolVersion.

Trigger

Cause de la requête (§5.1). GEN : SocketScheduler::knownTriggers() — du moins au plus significatif.

cycle · forecast · config · load-change · intent

Regenerated from the engine code: SocketScheduler::knownTriggers().

Domain

Intention déclarée par l'installateur, jamais déduite du mécanisme, non lue par l'arbitre (§5.3). GEN : LoadConfig::knownDomains().

ecs · ev · heating · hvac · battery

Regenerated from the engine code: LoadConfig::knownDomains().

ActionKind

Type d'action (§6). GEN : OptimizerPlan::kinds(). constraint est RÉSERVÉ (annexe F) : reconnu par le validateur, toujours refusé (règle kind-mismatch) faute de mécanisme porteur.

setpoint · state · constraint

Regenerated from the engine code: OptimizerPlan::kinds().

ReasonCode

Codes de motif qu'un optimiseur peut émettre (§6, liste blanche). GEN : DecisionCode::optimizerEmittableCodes(). Clés de paramètres admises par code : x-etm-reason-params.

SURPLUS_SETPOINT · SURPLUS_INSUFFICIENT · BELOW_MIN_POWER · EV_DEADLINE · SG_FORCED · SG_RECOMMENDED · SG_NORMAL · ECO_FLOOR_GRID · PRICE_SLOT · OPTIMIZER_DECISION

Regenerated from the engine code: DecisionCode::optimizerEmittableCodes().

PriceInclude

Composante incluse dans le prix (§5.2). GEN : TariffConfig::includes().

energy · grid · taxes

Regenerated from the engine code: TariffConfig::includes().

FallbackCode

Côté app (NymeaEnergy.GetPlan.fallbackCode, INTERFACE.md) : pourquoi l'optimiseur n'a pas commandé le cycle. Hors fil. GEN : SocketScheduler::knownFallbackCodes().

no-plan · abstain · empty-plan · no-current-slot · expired · malformed-plan · bad-time · slots-unordered · malformed-action · unknown-load · unknown-kind · kind-mismatch · kind-not-announced · missing-reason · bad-value · duplicate-load · incomplete-slot · no-shadow-slot

Regenerated from the engine code: SocketScheduler::knownFallbackCodes().

LastErrorCode

Côté app (NymeaEnergy.GetOptimizerStatus.lastErrorCode, INTERFACE.md) : dernier code d'erreur vu (connexion, requête, plan). Hors fil. GEN : SocketScheduler::knownLastErrorCodes().

invalid-endpoint · token-too-short · connect-failed · closed-during-handshake · connection-lost · handshake-timeout · handshake-refused · invalid-handshake · major-mismatch · line-too-long · invalid-json · missing-id · unknown-id · timeout · rpc-error · invalid-response · disconnected · malformed-plan · bad-time · slots-unordered · malformed-action · unknown-load · unknown-kind · kind-mismatch · kind-not-announced · missing-reason · bad-value · duplicate-load · incomplete-slot

Regenerated from the engine code: SocketScheduler::knownLastErrorCodes().

Correction

temperature · ac-limit