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
856 lines
43 KiB
Markdown
856 lines
43 KiB
Markdown
# 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](../integrations/jsonrpc-api.md).
|
||
|
||
!!! 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](contrat.md). 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`](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/docs/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 {#transport}
|
||
|
||
- 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.
|
||
|
||
<!-- BEGIN GENERATED: __optimizer_transport__ -->
|
||
|
||
| 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. |
|
||
|
||
<!-- END GENERATED -->
|
||
|
||
## Handshake {#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.
|
||
|
||
<!-- BEGIN GENERATED: __optimizer_handshake__ -->
|
||
|
||
#### `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`](#schema-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`](#schema-handshaketoken) | no | | |
|
||
|
||
**result** (`handshake`): [`HandshakeResult`](#schema-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` {#schema-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` {#schema-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`](#schema-capabilities) | no | | |
|
||
|
||
Undeclared fields: **ignored** (optimizer-emitted form, open).
|
||
|
||
#### `Capabilities` {#schema-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`](#schema-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).
|
||
|
||
<!-- END GENERATED -->
|
||
|
||
## Le contexte : `GetPlan` {#contexte}
|
||
|
||
À 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.
|
||
|
||
<!-- BEGIN GENERATED: __optimizer_context__ -->
|
||
|
||
#### `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`](#schema-trigger) | yes | | |
|
||
| `site` | [`Site`](#schema-site) | yes | | |
|
||
| `meter` | [`Meter`](#schema-meter) | yes | | |
|
||
| `pv` | [`Pv`](#schema-pv) | yes | | |
|
||
| `battery` | [`Battery`](#schema-battery) | yes | | |
|
||
| `loads` | [[`Load`](#schema-load)] | yes | | Charges arbitrées, ⊆ GetLoadConfig. |
|
||
| `forecast` | [`Forecast`](#schema-forecast) \| null | yes | | null sans instantané de prévision. |
|
||
| `tariff` | [`Tariff`](#schema-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` {#schema-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` {#schema-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` {#schema-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` {#schema-battery}
|
||
|
||
One of: [`BatteryAbsent`](#schema-batteryabsent) · [`BatteryPresent`](#schema-batterypresent).
|
||
|
||
#### `Load` {#schema-load}
|
||
|
||
Une charge arbitrée (§5.3) — la forme dépend du mécanisme (adapter).
|
||
|
||
One of: [`LoadWatts`](#schema-loadwatts) · [`LoadEvCharger`](#schema-loadevcharger) · [`LoadSgReady`](#schema-loadsgready).
|
||
|
||
#### `Forecast` {#schema-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`](#schema-pvforecast) \| null | yes | | null sans courbe (site non désigné, charge utile refusée, canal muet). |
|
||
| `baseload` | [`Baseload`](#schema-baseload) | yes | | |
|
||
| `weather` | null | yes | | Aucune source de température en 1.1 : toujours null. |
|
||
| `price` | [`Price`](#schema-price) \| null | yes | | null sans tarif saisi. |
|
||
|
||
Undeclared fields: **rejected** (engine-emitted form, closed).
|
||
|
||
#### `Tariff` {#schema-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`](#schema-tariffentry) | yes | | |
|
||
| `next` | [[`TariffNextEntry`](#schema-tariffnextentry)] | yes | | |
|
||
|
||
Undeclared fields: **rejected** (engine-emitted form, closed).
|
||
|
||
#### `BatteryAbsent` {#schema-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` {#schema-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` {#schema-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`](#schema-domain) | no | | |
|
||
| `learned` | [`LoadLearned`](#schema-loadlearned) | no | | |
|
||
| `needs` | [`LoadNeeds`](#schema-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`](#schema-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` {#schema-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`](#schema-domain) | no | | |
|
||
| `learned` | [`LoadLearned`](#schema-loadlearned) | no | | |
|
||
| `needs` | [`LoadNeeds`](#schema-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`](#schema-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` {#schema-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`](#schema-domain) | no | | |
|
||
| `learned` | [`LoadLearned`](#schema-loadlearned) | no | | |
|
||
| `needs` | [`LoadNeeds`](#schema-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`](#schema-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` {#schema-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`](#schema-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`](#schema-pvfield)] | no | | |
|
||
|
||
Undeclared fields: **rejected** (engine-emitted form, closed).
|
||
|
||
#### `Baseload` {#schema-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`](#schema-baseloadquality) | no | | |
|
||
|
||
Undeclared fields: **rejected** (engine-emitted form, closed).
|
||
|
||
#### `Price` {#schema-price}
|
||
|
||
| Field | Type | Required | Values | Description (contract, French) |
|
||
|---|---|---|---|---|
|
||
| `currency` | string | yes | `"EUR"` | |
|
||
| `importCtkWh` | [number \| null] | yes | | |
|
||
| `exportCtkWh` | [number \| null] | yes | | |
|
||
| `includes` | [[`PriceInclude`](#schema-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` {#schema-tariffentry}
|
||
|
||
| Field | Type | Required | Values | Description (contract, French) |
|
||
|---|---|---|---|---|
|
||
| `label` | string | yes | | |
|
||
| `priceCtkWh` | number | yes | | |
|
||
|
||
Undeclared fields: **rejected** (engine-emitted form, closed).
|
||
|
||
#### `TariffNextEntry` {#schema-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` {#schema-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` {#schema-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`](#schema-energytarget)] | no | ≥ 1 items | |
|
||
|
||
Undeclared fields: **rejected** (engine-emitted form, closed).
|
||
|
||
#### `Measurement` {#schema-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` {#schema-pvfield}
|
||
|
||
| Field | Type | Required | Values | Description (contract, French) |
|
||
|---|---|---|---|---|
|
||
| `id` | string | yes | | ThingId du plan. |
|
||
| `corrections` | [[`Correction`](#schema-correction)] | yes | | |
|
||
|
||
Undeclared fields: **rejected** (engine-emitted form, closed).
|
||
|
||
#### `BaseloadQuality` {#schema-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`](#schema-baseloadloadquality)] | yes | | |
|
||
|
||
Undeclared fields: **rejected** (engine-emitted form, closed).
|
||
|
||
#### `EnergyTarget` {#schema-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` {#schema-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).
|
||
|
||
<!-- END GENERATED -->
|
||
|
||
## La réponse : un plan ou une abstention {#reponse}
|
||
|
||
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é.
|
||
|
||
<!-- BEGIN GENERATED: __optimizer_plan__ -->
|
||
|
||
#### `GetPlan` — result
|
||
|
||
`plan`: [`GetPlanResult`](#schema-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` {#schema-getplanresult}
|
||
|
||
One of: [`Abstention`](#schema-abstention) · [`Plan`](#schema-plan).
|
||
|
||
#### `Abstention` {#schema-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` {#schema-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`](#schema-slot)] | yes | | [] = abstention (empty-plan). Seul le créneau couvrant maintenant est exécuté. |
|
||
| `diagnostics` | [`Diagnostics`](#schema-diagnostics) | no | | |
|
||
|
||
Undeclared fields: **ignored** (optimizer-emitted form, open).
|
||
|
||
#### `Slot` {#schema-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`](#schema-action)] \| null | no | | Absent ou null = aucune action. |
|
||
| `expected` | [`Expected`](#schema-expected) | no | | |
|
||
|
||
Undeclared fields: **ignored** (optimizer-emitted form, open).
|
||
|
||
#### `Diagnostics` {#schema-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` {#schema-action}
|
||
|
||
One of: [`SetpointAction`](#schema-setpointaction) · [`StateAction`](#schema-stateaction) · [`ConstraintAction`](#schema-constraintaction).
|
||
|
||
#### `Expected` {#schema-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` {#schema-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`](#schema-reasoncode) | no | | |
|
||
| `reasonParams` | [`ReasonParams`](#schema-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` {#schema-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`](#schema-reasoncode) | no | | |
|
||
| `reasonParams` | [`ReasonParams`](#schema-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` {#schema-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`](#schema-reasoncode) | no | | |
|
||
| `reasonParams` | [`ReasonParams`](#schema-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` {#schema-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
|
||
|
||
<!-- END GENERATED -->
|
||
|
||
### Codes de motif émissibles {#codes-de-motif}
|
||
|
||
<!-- BEGIN GENERATED: __optimizer_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` | — |
|
||
|
||
<!-- END GENERATED -->
|
||
|
||
## `UpdateLearned` {#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.
|
||
|
||
<!-- BEGIN GENERATED: __optimizer_update_learned__ -->
|
||
|
||
#### `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). |
|
||
|
||
<!-- END GENERATED -->
|
||
|
||
## Machine de repli {#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`).
|
||
|
||
<!-- BEGIN GENERATED: __optimizer_fallback__ -->
|
||
|
||
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` |
|
||
|
||
<!-- END GENERATED -->
|
||
|
||
## Règles de validation {#regles}
|
||
|
||
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).
|
||
|
||
<!-- BEGIN GENERATED: __optimizer_rules__ -->
|
||
|
||
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. |
|
||
|
||
<!-- END GENERATED -->
|
||
|
||
### Tolérances {#tolerances}
|
||
|
||
Ce que le moteur accepte au-delà du contrat. Un optimiseur conforme ne s'en sert pas.
|
||
|
||
<!-- BEGIN GENERATED: __optimizer_tolerances__ -->
|
||
|
||
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).
|
||
|
||
<!-- END GENERATED -->
|
||
|
||
## Listes fermées {#listes-fermees}
|
||
|
||
Valeurs permises, régénérées depuis le code du moteur.
|
||
|
||
<!-- BEGIN GENERATED: __optimizer_closed_lists__ -->
|
||
|
||
#### `ProtocolVersion` {#schema-protocolversion}
|
||
|
||
Version du protocole parlée par le moteur (GEN : OptimizerProtocol::ProtocolVersion).
|
||
|
||
`1.1`
|
||
|
||
Regenerated from the engine code: `OptimizerProtocol::ProtocolVersion`.
|
||
|
||
#### `Trigger` {#schema-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` {#schema-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` {#schema-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` {#schema-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` {#schema-priceinclude}
|
||
|
||
Composante incluse dans le prix (§5.2). GEN : TariffConfig::includes().
|
||
|
||
`energy` · `grid` · `taxes`
|
||
|
||
Regenerated from the engine code: `TariffConfig::includes()`.
|
||
|
||
#### `FallbackCode` {#schema-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` {#schema-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` {#schema-correction}
|
||
|
||
`temperature` · `ac-limit`
|
||
|
||
<!-- END GENERATED -->
|