powersync-docs/docs/optimiseur/protocole.en.md
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

Protocol

The protocol links the box's energy engine to an optimizer, over a dedicated channel. It is not the app's API: the app goes through the JSON-RPC API.

!!! info "What is authoritative" - The shape of messages (fields, types, allowed values, what the engine rejects) is set by the machine-readable contract. The tables on this page are generated from it on every publication: framing in English, descriptions taken as-is from the contract, in French. They are identical in all three languages. - The meaning is described in detail, in French, in OPTIMIZER_PROTOCOL.md, in the engine repository. This page gives the essentials; where they differ, the engine's documents prevail.

Transport and roles

  • The optimizer is the server, the engine is the client: the engine connects and asks the questions. The box opens no port.
  • Two transports, same messages: unix:// (local socket, default) and tcp:// (machine on the local network).
  • JSON-RPC 2.0, one message per line (NDJSON), UTF-8.
  • If the connection drops, the engine reconnects with a growing delay and drives the loads by its rules in the meantime.
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

On every connection, the engine sends Handshake with its protocol version and, if configured, the shared secret. The optimizer answers with its version and, if it wishes, its name and capabilities. A different major version, a refused secret or an unreadable answer: the connection is refused, and the engine drives by its rules. If the optimizer announces the action kinds (kinds) it emits, the engine then rejects any action of another kind.

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).

The context: GetPlan

On every cycle, and on events (vehicle plugged in, new forecast, configuration change…), the engine sends GetPlan with a context: state of the site and of the grid meter, production, battery, the arbitrated loads with their bounds, locks and needs, and forecasts on a single time grid.

A few reading rules that change decisions:

  • the meter is sent as a raw measurement, never as the balance after control;
  • an unknown value is null, never 0;
  • an unavailable load (available: false) must receive no setpoint, but it consumes: it is still counted;
  • current locks (lockMinPowerW, lockMaxPowerW) say what the load can do now; the engine enforces them regardless;
  • what a load measures is read from measurement.source, never from its mechanism (adapter); a declared power is never a measurement.

The shape sent by the engine is closed: it emits no field outside the contract.

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).

The answer: a plan or an abstention

The optimizer answers quickly — with its last cached plan if it computes for long — and recomputes in the background. It answers either:

  • a plan in time slots: each slot carries one action per available load (setpoint in watts or amps, or SG-Ready state), each with its reason; only the current slot is executed, and validUntil bounds the plan's lifetime;
  • an abstention (abstain: true): "no plan right now". The engine drives by its rules for that cycle, without treating the optimizer as failed.

An action's reason has two forms: a free text in French, mandatory, for the traces; and, optionally, a code from the catalogue (reasonCode) with its parameters. Only the code is passed on to the app, which translates it. An unknown code goes out as OPTIMIZER_DECISION.

The shape received by the engine is open: an unknown field is ignored.

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

Emittable reason codes

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 sent by the optimizer to the engine, with no answer, to pass on what it has learned about the loads. In version 1.1 of the engine, it is received and logged, but not yet used.

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).

Fallback state machine

There is no separate heartbeat: a valid answer to GetPlan means the optimizer is alive. Failures are counted per request; after several consecutive failures, the optimizer is declared absent and the engine drives by its rules until the next valid plan. The app sees this state continuously (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

Validation rules

The contract's JSON Schema states the shape. The engine then judges each answer against the context of its request, rule by rule, in the order below; the first that fails gives its code (fallbackCode, visible in the app).

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.

Tolerances

What the engine accepts beyond the contract. A conformant optimizer does not rely on it.

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).

Closed lists

Allowed values, regenerated from the engine's code.

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