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
This commit is contained in:
Patrick Schurig 2026-10-18 13:48:22 +02:00
parent 0b8ccc6c9d
commit eb7d727d17
17 changed files with 90 additions and 96 deletions

View File

@ -14,11 +14,11 @@ app. That is an architectural decision, not an option.
## Is the project open source? ## Is the project open source?
The energy engine (`etm-powersync-energy-plugin-etm`) and the plugins are under The energy engine (`etm-powersync-energy-plugin-etm`) and the plugins are under
GPL-3.0, including the optimiser client built into the engine — everything linked against GPL-3.0, including the optimizer client built into the engine — everything linked against
nymea is. The engine works on its own, without an optimiser. The nymea is. The engine works on its own, without an optimizer. The
[protocol between the engine and an optimiser](optimiseur/index.md) is open and [protocol between the engine and an optimizer](optimiseur/index.md) is open and
published, with a free reference optimiser and a conformance suite: anyone can write their published, with a free reference optimizer and a conformance suite: anyone can write their
own. The **ETM optimiser** and its business logic (tariff arbitration, forecasts learned own. The **ETM optimizer** and its business logic (tariff arbitration, forecasts learned
from your installation), however, are proprietary: they run in a separate process, reached from your installation), however, are proprietary: they run in a separate process, reached
through that protocol, at your place — on the box or on a machine in your local network. through that protocol, at your place — on the box or on a machine in your local network.
That process boundary is the heart of the Open Core model — the engine stays free, the That process boundary is the heart of the Open Core model — the engine stays free, the

View File

@ -12,7 +12,7 @@ An overview of the HEMS features and their real state.
| Baseload forecast | <span class="badge nightly">COMING</span> | | Baseload forecast | <span class="badge nightly">COMING</span> |
| [JSON-RPC API](../integrations/jsonrpc-api.md) / [MQTT](../integrations/mqtt.md) | <span class="badge stable">AVAILABLE</span> | | [JSON-RPC API](../integrations/jsonrpc-api.md) / [MQTT](../integrations/mqtt.md) | <span class="badge stable">AVAILABLE</span> |
| Dynamic tariffs — FR: Tempo, off-peak · DE: §14a EnWG, EPEX spot | <span class="badge nightly">ROADMAP</span> | | Dynamic tariffs — FR: Tempo, off-peak · DE: §14a EnWG, EPEX spot | <span class="badge nightly">ROADMAP</span> |
| CO₂ optimisation — RTE (FR) / SMARD (DE) | <span class="badge nightly">ROADMAP</span> | | CO₂ optimization — RTE (FR) / SMARD (DE) | <span class="badge nightly">ROADMAP</span> |
| Charge planner | <span class="badge nightly">ROADMAP</span> | | Charge planner | <span class="badge nightly">ROADMAP</span> |
| Heat pump | <span class="badge nightly">ROADMAP</span> | | Heat pump | <span class="badge nightly">ROADMAP</span> |

View File

@ -18,7 +18,7 @@ different rates and with different priorities.
there is headroom again. It does not wait for the cycle and overrides everything else. there is headroom again. It does not wait for the cycle and overrides everything else.
- **The allocation cycle, about once a minute.** It shares the available power between - **The allocation cycle, about once a minute.** It shares the available power between
the loads. This is the only place a strategy acts: the built-in rules, or an the loads. This is the only place a strategy acts: the built-in rules, or an
[optimiser](../optimiseur/index.md) if one is connected. [optimizer](../optimiseur/index.md) if one is connected.
If the grid meter stops answering, the engine suspends allocation and falls back to safety If the grid meter stops answering, the engine suspends allocation and falls back to safety
mode: no measurement, no decision. mode: no measurement, no decision.
@ -60,7 +60,7 @@ whatever the strategy:
The engine takes no decision without stating why. Every decision carries a non-empty The engine takes no decision without stating why. Every decision carries a non-empty
**reason** (`decisionReason`): a stable code and its parameters, which the app displays in **reason** (`decisionReason`): a stable code and its parameters, which the app displays in
your language — "surplus insufficient", "below minimum power", "charging deadline"… The your language — "surplus insufficient", "below minimum power", "charging deadline"… The
list of codes is published with the [optimiser protocol](../optimiseur/protocole.md#reponse). list of codes is published with the [optimizer protocol](../optimiseur/protocole.md#reponse).
## The measurement, never the result of control ## The measurement, never the result of control
@ -75,8 +75,8 @@ enforces safety and answers the app on your local network. Only the
edition, any more than the [entered tariff](../tarifs.md): both are displayed and edition, any more than the [entered tariff](../tarifs.md): both are displayed and
recorded, never used by the rules. recorded, never used by the rules.
## What about an optimiser? ## What about an optimizer?
An optimiser can propose a plan over several hours, based on the forecasts. It replaces An optimizer can propose a plan over several hours, based on the forecasts. It replaces
the allocation step, **never** the safety loop or the bounds. See the the allocation step, **never** the safety loop or the bounds. See the
[Optimizer](../optimiseur/index.md) section. [Optimizer](../optimiseur/index.md) section.

View File

@ -71,7 +71,7 @@ therefore neither an "expected error" nor a "confidence interval".
## What the forecast does not do ## What the forecast does not do
This is the central rule of the Community edition: **without an optimiser, control is This is the central rule of the Community edition: **without an optimizer, control is
rule-based** (solar surplus, load shedding, priorities). The PV forecast, the rule-based** (solar surplus, load shedding, priorities). The PV forecast, the
[entered tariff](../tarifs.md) and the baseload are **displayed** and **recorded [entered tariff](../tarifs.md) and the baseload are **displayed** and **recorded
locally**, but never used to decide. An automated test checks this: the engine makes locally**, but never used to decide. An automated test checks this: the engine makes
@ -95,12 +95,12 @@ coordinates** (rounded to about 1 km in the plugin versions that carry this feat
tariff and consumption stay on the box. Remote access is disabled by default: it opens tariff and consumption stay on the box. Remote access is disabled by default: it opens
only if you enable it yourself in the app. only if you enable it yourself in the app.
## What about an optimiser? ## What about an optimizer?
An optimiser, on the other hand, can use these forecasts to plan: the engine hands them An optimizer, on the other hand, can use these forecasts to plan: the engine hands them
over, together with the entered tariff and the baseload, through an over, together with the entered tariff and the baseload, through an
[open, published protocol](../optimiseur/protocole.md). The optimiser runs at your place, [open, published protocol](../optimiseur/protocole.md). The optimizer runs at your place,
on the box or on a machine in your local network, and gets its data from the engine only; on the box or on a machine in your local network, and gets its data from the engine only;
its setpoints stay capped by the engine's bounds and safety. Connecting an optimiser is its setpoints stay capped by the engine's bounds and safety. Connecting an optimizer is
being tested; the Community edition works entirely without one. See the being tested; the Community edition works entirely without one. See the
[Optimizer](../optimiseur/index.md) section. [Optimizer](../optimiseur/index.md) section.

View File

@ -1,11 +1,11 @@
# ETM PowerSync # ETM PowerSync
**Local HEMS for solar PV, heat pump and EV charging installations.** ETM PowerSync **Local HEMS for solar PV, heat pump and EV charging installations.** ETM PowerSync
coordinates photovoltaics, battery, heat pump and EV charger under a single optimisation coordinates photovoltaics, battery, heat pump and EV charger under a single optimization
logic — running at your site, on your hardware, with no cloud dependency. logic — running at your site, on your hardware, with no cloud dependency.
Built on [nymea.io](https://nymea.io), following an **Open Core** architecture: a free Built on [nymea.io](https://nymea.io), following an **Open Core** architecture: a free
foundation (GPL-3.0) and a proprietary optimisation layer. foundation (GPL-3.0) and a proprietary optimization layer.
!!! info "Honest statuses" !!! info "Honest statuses"
Every feature shows its real state: <span class="badge stable">STABLE</span> Every feature shows its real state: <span class="badge stable">STABLE</span>
@ -17,7 +17,7 @@ foundation (GPL-3.0) and a proprietary optimisation layer.
- [Installation](installation/index.md) — deploy ETM PowerSync from the APT repository - [Installation](installation/index.md) — deploy ETM PowerSync from the APT repository
- [Features](fonctionnalites/index.md) — what the HEMS does, and its real state; - [Features](fonctionnalites/index.md) — what the HEMS does, and its real state;
[how the engine decides](fonctionnalites/moteur.md) [how the engine decides](fonctionnalites/moteur.md)
- [Optimizer](optimiseur/index.md) — the open protocol between the engine and an optimiser - [Optimizer](optimiseur/index.md) — the open protocol between the engine and an optimizer
- [Compatibility](appareils/compatibilite.md) — supported equipment - [Compatibility](appareils/compatibilite.md) — supported equipment
- [Integrations](integrations/jsonrpc-api.md) — JSON-RPC API, MQTT, Home Assistant - [Integrations](integrations/jsonrpc-api.md) — JSON-RPC API, MQTT, Home Assistant

View File

@ -42,10 +42,8 @@ Der hier veröffentlichte Vertrag folgt der Entwicklung der Engine; Ihre Box kan
die Box: Die JSON-RPC-Methode `NymeaEnergy.GetOptimizerProtocol` liefert den, den sie die Box: Die JSON-RPC-Methode `NymeaEnergy.GetOptimizerProtocol` liefert den, den sie
mitbringt. Dieser ist für einen an diese Box angebundenen Optimierer maßgeblich. mitbringt. Dieser ist für einen an diese Box angebundenen Optimierer maßgeblich.
Die Methode ist in Sie gehört zu den `NymeaEnergy.*`-Methoden des Optimierers (Verbindungspunkt, gemeinsames
[`INTERFACE.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/INTERFACE.md) Geheimnis, Zustand); ihre genaue Form liefert die Box selbst über `JSONRPC.Introspect`. Die Verbindung zur API
im Engine-Repository beschrieben, zusammen mit den übrigen `NymeaEnergy.*`-Methoden des
Optimierers (Verbindungspunkt, gemeinsames Geheimnis, Zustand). Die Verbindung zur API
beschreibt die Seite [JSON-RPC-API](../integrations/jsonrpc-api.md). beschreibt die Seite [JSON-RPC-API](../integrations/jsonrpc-api.md).
## Nachrichten prüfen {#valider} ## Nachrichten prüfen {#valider}

View File

@ -39,12 +39,10 @@ repository. Its record:
The contract published here follows the engine's development; your box may carry an The contract published here follows the engine's development; your box may carry an
earlier version. To get **the** contract of the installed engine, ask the box: the earlier version. To get **the** contract of the installed engine, ask the box: the
JSON-RPC method `NymeaEnergy.GetOptimizerProtocol` returns the one it embeds. That is the JSON-RPC method `NymeaEnergy.GetOptimizerProtocol` returns the one it embeds. That is the
one that is authoritative for an optimiser connected to that box. one that is authoritative for an optimizer connected to that box.
The method is described in It belongs to the optimizer `NymeaEnergy.*` methods (endpoint, shared secret, status); the
[`INTERFACE.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/INTERFACE.md), box itself returns their exact form through `JSONRPC.Introspect`. Connecting to the API is described on the
in the engine repository, along with the other optimiser `NymeaEnergy.*` methods
(endpoint, shared secret, status). Connecting to the API is described on the
[JSON-RPC API](../integrations/jsonrpc-api.md) page. [JSON-RPC API](../integrations/jsonrpc-api.md) page.
## Validating messages {#valider} ## Validating messages {#valider}

View File

@ -41,10 +41,8 @@ antérieure. Pour connaître **le** contrat du moteur installé, demandez-le à
méthode JSON-RPC `NymeaEnergy.GetOptimizerProtocol` rend celui qu'elle embarque. C'est méthode JSON-RPC `NymeaEnergy.GetOptimizerProtocol` rend celui qu'elle embarque. C'est
celui-là qui fait foi pour un optimiseur branché sur cette box. celui-là qui fait foi pour un optimiseur branché sur cette box.
La méthode est décrite dans Elle fait partie des méthodes `NymeaEnergy.*` de l'optimiseur (point de connexion, secret
[`INTERFACE.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/INTERFACE.md), partagé, état) ; la box en rend la forme exacte par `JSONRPC.Introspect`. La connexion à l'API est décrite sur la page
dans le dépôt du moteur, avec les autres méthodes `NymeaEnergy.*` de l'optimiseur
(point de connexion, secret partagé, état). La connexion à l'API est décrite sur la page
[API JSON-RPC](../integrations/jsonrpc-api.md). [API JSON-RPC](../integrations/jsonrpc-api.md).
## Valider des messages {#valider} ## Valider des messages {#valider}

View File

@ -27,7 +27,7 @@ Drei Punkte, die man nicht übersehen sollte:
Das Engine-Repository enthält einen freien Referenz-Optimierer (GPL-3.0) in Python, nur Das Engine-Repository enthält einen freien Referenz-Optimierer (GPL-3.0) in Python, nur
mit der Standardbibliothek: mit der Standardbibliothek:
[`tools/etm-powersync-ref-optimizer/`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/tree/main/tools/etm-powersync-ref-optimizer). [`tools/etm-powersync-ref-optimizer/`](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/tools/etm-powersync-ref-optimizer).
Er antwortet mit einem Plan aus einem Zeitfenster, der die Kaskade der Engine-Regeln Er antwortet mit einem Plan aus einem Zeitfenster, der die Kaskade der Engine-Regeln
nachbildet, und liest dabei nur den Kontext. Beim Handshake meldet er sich als nachbildet, und liest dabei nur den Kontext. Beim Handshake meldet er sich als
`powersync-optimizer`. `powersync-optimizer`.
@ -36,12 +36,12 @@ Er ist Ausgangspunkt für einen eigenen Optimierer und Vergleichsmaßstab: Er la
standardmäßig am lokalen Socket oder an `tcp://` mit einem gemeinsamen Geheimnis, das aus standardmäßig am lokalen Socket oder an `tcp://` mit einem gemeinsamen Geheimnis, das aus
einer Datei gelesen wird. Er kann auch Fehler einspeisen (Enthaltung, Langsamkeit, einer Datei gelesen wird. Er kann auch Fehler einspeisen (Enthaltung, Langsamkeit,
ungültiger Plan, andere Hauptversion…), um den Rückfall der Engine zu testen. ungültiger Plan, andere Hauptversion…), um den Rückfall der Engine zu testen.
Anleitung: [seine `README.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/tools/etm-powersync-ref-optimizer/README.md). Anleitung: [seine `README.md`](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/tools/etm-powersync-ref-optimizer/README.md).
## Die Engine an den eigenen Optimierer anbinden {#brancher} ## Die Engine an den eigenen Optimierer anbinden {#brancher}
Drei JSON-RPC-Methoden der Box, dem Administrator vorbehalten, beschrieben in Drei JSON-RPC-Methoden der Box (die ersten beiden dem Administrator vorbehalten); ihre genaue
[`INTERFACE.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/INTERFACE.md): Form liefert die Box selbst über `JSONRPC.Introspect`:
- `NymeaEnergy.SetOptimizerEndpoint` — wohin verbinden (`unix:///…` oder `tcp://host:port`); - `NymeaEnergy.SetOptimizerEndpoint` — wohin verbinden (`unix:///…` oder `tcp://host:port`);
- `NymeaEnergy.SetOptimizerToken` — das gemeinsame Geheimnis, dasselbe wie beim Optimierer; - `NymeaEnergy.SetOptimizerToken` — das gemeinsame Geheimnis, dasselbe wie beim Optimierer;
@ -78,7 +78,7 @@ export ETM_POWERSYNC_CONFORMANCE_EXPECT=plan # oder abstain
Jede Funktion läuft in einem eigenen Prozess. Der vollständige Befehl (Netzisolierung Jede Funktion läuft in einem eigenen Prozess. Der vollständige Befehl (Netzisolierung
empfohlen), die übrigen Variablen (gemeinsames Geheimnis, Startfrist…) und das Lesen der empfohlen), die übrigen Variablen (gemeinsames Geheimnis, Startfrist…) und das Lesen der
Fehlschläge stehen in der Fehlschläge stehen in der
[`README.md` der Suite](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/tests/auto/conformance/README.md). [`README.md` der Suite](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/tests/auto/conformance/README.md).
### Erwarteter Modus: `plan` oder `abstain` {#mode-attendu} ### Erwarteter Modus: `plan` oder `abstain` {#mode-attendu}

View File

@ -1,14 +1,14 @@
# Writing an optimizer # Writing an optimizer
An optimiser is a **server** that speaks the [protocol](protocole.md): it answers the An optimizer is a **server** that speaks the [protocol](protocole.md): it answers the
handshake, then every `GetPlan` with a plan of one or more slots, or with an abstention. handshake, then every `GetPlan` with a plan of one or more slots, or with an abstention.
Any language will do. A minimal optimiser fits in about fifty lines of Python. Any language will do. A minimal optimizer fits in about fifty lines of Python.
Minimal session, for illustration (the exact shape is in the [contract](contrat.md)): Minimal session, for illustration (the exact shape is in the [contract](contrat.md)):
```text ```text
→ {"jsonrpc":"2.0","id":1,"method":"Handshake","params":{"protocolVersion":"1.1","engine":"etm-powersync-energy-plugin-etm/…"}} → {"jsonrpc":"2.0","id":1,"method":"Handshake","params":{"protocolVersion":"1.1","engine":"etm-powersync-energy-plugin-etm/…"}}
← {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"1.1","name":"my-optimiser","planning":false}} ← {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"1.1","name":"my-optimizer","planning":false}}
→ {"jsonrpc":"2.0","id":2,"method":"GetPlan","params":{ …context… }} → {"jsonrpc":"2.0","id":2,"method":"GetPlan","params":{ …context… }}
← {"jsonrpc":"2.0","id":2,"result":{"abstain":true,"reason":"premier calcul en cours"}} ← {"jsonrpc":"2.0","id":2,"result":{"abstain":true,"reason":"premier calcul en cours"}}
``` ```
@ -19,14 +19,14 @@ Three things not to miss:
text: otherwise the whole plan is rejected (the reason text is in French); text: otherwise the whole plan is rejected (the reason text is in French);
- **answer quickly**: serve the last cached plan, or abstain, and compute in the - **answer quickly**: serve the last cached plan, or abstain, and compute in the
background; background;
- **fetch nothing elsewhere**: everything the optimiser needs, forecasts included, is in - **fetch nothing elsewhere**: everything the optimizer needs, forecasts included, is in
the context. the context.
## The reference optimiser {#reference} ## The reference optimizer {#reference}
The engine repository contains a free reference optimiser (GPL-3.0), in Python, standard The engine repository contains a free reference optimizer (GPL-3.0), in Python, standard
library only: library only:
[`tools/etm-powersync-ref-optimizer/`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/tree/main/tools/etm-powersync-ref-optimizer). [`tools/etm-powersync-ref-optimizer/`](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/tools/etm-powersync-ref-optimizer).
It answers a one-slot plan that reproduces the cascade of the engine's rules, reading only It answers a one-slot plan that reproduces the cascade of the engine's rules, reading only
the context. At handshake it announces itself as `powersync-optimizer`. the context. At handshake it announces itself as `powersync-optimizer`.
@ -34,52 +34,52 @@ It is a starting point for writing your own, and a point of comparison: it liste
local socket by default, or on `tcp://` with a shared secret read from a file. It can also local socket by default, or on `tcp://` with a shared secret read from a file. It can also
inject faults (abstention, slowness, invalid plan, different major version…) to test the inject faults (abstention, slowness, invalid plan, different major version…) to test the
engine's fallback. Instructions: engine's fallback. Instructions:
[its `README.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/tools/etm-powersync-ref-optimizer/README.md). [its `README.md`](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/tools/etm-powersync-ref-optimizer/README.md).
## Pointing the engine at your optimiser {#brancher} ## Pointing the engine at your optimizer {#brancher}
Three JSON-RPC methods of the box, reserved to the administrator, described in Three JSON-RPC methods of the box (the first two reserved to the administrator); the box itself
[`INTERFACE.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/INTERFACE.md): returns their exact form through `JSONRPC.Introspect`:
- `NymeaEnergy.SetOptimizerEndpoint` — where to connect (`unix:///…` or `tcp://host:port`); - `NymeaEnergy.SetOptimizerEndpoint` — where to connect (`unix:///…` or `tcp://host:port`);
- `NymeaEnergy.SetOptimizerToken` — the shared secret, the same as the optimiser's; - `NymeaEnergy.SetOptimizerToken` — the shared secret, the same as the optimizer's;
- `NymeaEnergy.GetOptimizerStatus` — connection status: optimiser alive, active strategy, - `NymeaEnergy.GetOptimizerStatus` — connection status: optimizer alive, active strategy,
announced name, last error code. announced name, last error code.
Over `tcp://` without encryption, the secret travels in clear text on the local network. Over `tcp://` without encryption, the secret travels in clear text on the local network.
## The conformance suite {#conformite} ## The conformance suite {#conformite}
The `etm-powersync-optimizer-conformance` suite starts **your** optimiser and connects the The `etm-powersync-optimizer-conformance` suite starts **your** optimizer and connects the
**real** engine to it, on simulated devices. It checks, one function per point: **real** engine to it, on simulated devices. It checks, one function per point:
1. the handshake is accepted; 1. the handshake is accepted;
2. every `GetPlan` is answered within the engine's timeout; 2. every `GetPlan` is answered within the engine's timeout;
3. the whole session, both ways, conforms to the contract embedded in the engine, 3. the whole session, both ways, conforms to the contract embedded in the engine,
validation rules included; validation rules included;
4. every plan is applied: the optimiser is in control; 4. every plan is applied: the optimizer is in control;
5. the safety loop stays on top: an out-of-bounds setpoint is clamped, a phase overload is 5. the safety loop stays on top: an out-of-bounds setpoint is clamped, a phase overload is
shed. shed.
It does not judge the **quality** of the plans. It does not judge the **quality** of the plans.
### Running it against your optimiser ### Running it against your optimizer
Build the engine with its tests, then give the command that starts your server; Build the engine with its tests, then give the command that starts your server;
`{endpoint}` in it is replaced by the address to listen on: `{endpoint}` in it is replaced by the address to listen on:
```sh ```sh
export ETM_POWERSYNC_OPTIMIZER_CMD='exec /path/to/my-optimiser --listen {endpoint}' export ETM_POWERSYNC_OPTIMIZER_CMD='exec /path/to/my-optimizer --listen {endpoint}'
export ETM_POWERSYNC_CONFORMANCE_EXPECT=plan # or abstain export ETM_POWERSYNC_CONFORMANCE_EXPECT=plan # or abstain
``` ```
Each function runs in its own process. The full command (network isolation recommended), Each function runs in its own process. The full command (network isolation recommended),
the other variables (shared secret, start-up delay…) and how to read failures are in the the other variables (shared secret, start-up delay…) and how to read failures are in the
[suite's `README.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/tests/auto/conformance/README.md). [suite's `README.md`](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/tests/auto/conformance/README.md).
### Expected mode: `plan` or `abstain` {#mode-attendu} ### Expected mode: `plan` or `abstain` {#mode-attendu}
`ETM_POWERSYNC_CONFORMANCE_EXPECT` tells the suite what your optimiser does: `ETM_POWERSYNC_CONFORMANCE_EXPECT` tells the suite what your optimizer does:
- **`plan`** (default): it is in control. Every cycle must apply its plan. - **`plan`** (default): it is in control. Every cycle must apply its plan.
- **`abstain`**: it abstains on every cycle — that is allowed, and conformant. The suite - **`abstain`**: it abstains on every cycle — that is allowed, and conformant. The suite
@ -95,5 +95,5 @@ The mode is given, not guessed.
The engine can record, on the box, a trace of every cycle (off by default, The engine can record, on the box, a trace of every cycle (off by default,
`NymeaEnergy.SetOptimizerTrace`). The `tools/etm-powersync-trace-replay` tool replays these `NymeaEnergy.SetOptimizerTrace`). The `tools/etm-powersync-trace-replay` tool replays these
traces against a candidate optimiser and summarises the differences from the recorded plan. traces against a candidate optimizer and summarises the differences from the recorded plan.
Traces stay on site. Traces stay on site.

View File

@ -27,7 +27,7 @@ Trois points à ne pas manquer :
Le dépôt du moteur contient un optimiseur de référence libre (GPL-3.0), en Python, Le dépôt du moteur contient un optimiseur de référence libre (GPL-3.0), en Python,
bibliothèque standard seule : bibliothèque standard seule :
[`tools/etm-powersync-ref-optimizer/`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/tree/main/tools/etm-powersync-ref-optimizer). [`tools/etm-powersync-ref-optimizer/`](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/tools/etm-powersync-ref-optimizer).
Il répond un plan à un créneau qui reproduit la cascade des règles du moteur, en ne lisant Il répond un plan à un créneau qui reproduit la cascade des règles du moteur, en ne lisant
que le contexte. Il s'annonce `powersync-optimizer` au handshake. que le contexte. Il s'annonce `powersync-optimizer` au handshake.
@ -35,12 +35,12 @@ C'est un point de départ pour écrire le sien, et un point de comparaison : il
le socket local par défaut, ou sur `tcp://` avec un secret partagé lu dans un fichier. Il le socket local par défaut, ou sur `tcp://` avec un secret partagé lu dans un fichier. Il
sait aussi injecter des défauts (abstention, lenteur, plan invalide, majeure différente…) sait aussi injecter des défauts (abstention, lenteur, plan invalide, majeure différente…)
pour tester le repli du moteur. Mode d'emploi : pour tester le repli du moteur. Mode d'emploi :
[son `README.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/tools/etm-powersync-ref-optimizer/README.md). [son `README.md`](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/tools/etm-powersync-ref-optimizer/README.md).
## Brancher le moteur sur son optimiseur {#brancher} ## Brancher le moteur sur son optimiseur {#brancher}
Trois méthodes JSON-RPC de la box, réservées à l'administrateur, décrites dans Trois méthodes JSON-RPC de la box (les deux premières réservées à l'administrateur) ; la box
[`INTERFACE.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/INTERFACE.md) : en rend la forme exacte par `JSONRPC.Introspect` :
- `NymeaEnergy.SetOptimizerEndpoint` — où se connecter (`unix:///…` ou `tcp://hôte:port`) ; - `NymeaEnergy.SetOptimizerEndpoint` — où se connecter (`unix:///…` ou `tcp://hôte:port`) ;
- `NymeaEnergy.SetOptimizerToken` — le secret partagé, le même que celui de l'optimiseur ; - `NymeaEnergy.SetOptimizerToken` — le secret partagé, le même que celui de l'optimiseur ;
@ -77,7 +77,7 @@ export ETM_POWERSYNC_CONFORMANCE_EXPECT=plan # ou abstain
Chaque fonction se lance dans son propre processus. La commande complète (isolement Chaque fonction se lance dans son propre processus. La commande complète (isolement
réseau recommandé), les autres variables (secret partagé, délai de démarrage…) et la réseau recommandé), les autres variables (secret partagé, délai de démarrage…) et la
lecture des échecs sont dans le lecture des échecs sont dans le
[`README.md` de la suite](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/tests/auto/conformance/README.md). [`README.md` de la suite](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/tests/auto/conformance/README.md).
### Mode attendu : `plan` ou `abstain` {#mode-attendu} ### Mode attendu : `plan` ou `abstain` {#mode-attendu}

View File

@ -2,57 +2,57 @@
<span class="badge testing">IN PROGRESS</span> <span class="badge testing">IN PROGRESS</span>
The protocol between the engine and an optimiser is published (version 1.1). Connecting The protocol between the engine and an optimizer is published (version 1.1). Connecting
an optimiser is being tested on our test installation; the box does not need one to work. an optimizer is being tested on our test installation; the box does not need one to work.
## What an optimiser is for ## What an optimizer is for
The [engine's rules](../fonctionnalites/moteur.md) decide on the present: this minute's The [engine's rules](../fonctionnalites/moteur.md) decide on the present: this minute's
surplus, shared according to your priorities. An optimiser looks further ahead. From the surplus, shared according to your priorities. An optimizer looks further ahead. From the
forecasts the engine hands it — PV production, base consumption, entered tariff — it forecasts the engine hands it — PV production, base consumption, entered tariff — it
proposes a **plan in time slots** over several hours: shift the water heater to the proposes a **plan in time slots** over several hours: shift the water heater to the
expected production peak, keep headroom for evening charging, and so on. expected production peak, keep headroom for evening charging, and so on.
## The optimiser advises, the engine decides ## The optimizer advises, the engine decides
- The engine only executes the **current slot** of the plan. The rest is shown as a - The engine only executes the **current slot** of the plan. The rest is shown as a
forecast, and requested again on the next cycle. forecast, and requested again on the next cycle.
- Every setpoint is **capped** by the load's bounds (floor, ceiling, anti-flapping locks) - Every setpoint is **capped** by the load's bounds (floor, ceiling, anti-flapping locks)
and by the real-time **safety loop**, which stays with the engine and overrides and by the real-time **safety loop**, which stays with the engine and overrides
everything. An optimiser cannot bypass a lock. everything. An optimizer cannot bypass a lock.
- Every action must carry its **reason**. An action without one is rejected. - Every action must carry its **reason**. An action without one is rejected.
- A malformed plan, or one that targets an unknown load, is rejected as a whole. - A malformed plan, or one that targets an unknown load, is rejected as a whole.
At worst, a faulty optimiser can therefore only propose a poor allocation — bounded by the At worst, a faulty optimizer can therefore only propose a poor allocation — bounded by the
engine. engine.
## The box works without an optimiser ## The box works without an optimizer
Optimiser missing, silent, abstaining, plan expired or rejected: the engine returns to its Optimizer missing, silent, abstaining, plan expired or rejected: the engine returns to its
**rules** (`rules`) for that cycle, with no interruption of control. The app shows at all **rules** (`rules`) for that cycle, with no interruption of control. The app shows at all
times which strategy is in charge. The Community edition works entirely this way. times which strategy is in charge. The Community edition works entirely this way.
## Its data come from the engine only ## Its data come from the engine only
- The engine connects to the optimiser, never the other way round: the box opens no port. - The engine connects to the optimizer, never the other way round: the box opens no port.
- The optimiser runs on the box (local socket) or on a machine in your local network - The optimizer runs on the box (local socket) or on a machine in your local network
(TCP). On the local network, a shared secret (token) is recommended. (TCP). On the local network, a shared secret (token) is recommended.
- It receives **only** the context the engine sends, forecasts included: it calls neither - It receives **only** the context the engine sends, forecasts included: it calls neither
a weather service nor a tariff provider, and opens no connection to the outside. a weather service nor a tariff provider, and opens no connection to the outside.
- What it learns stays on site. - What it learns stays on site.
## Who can write an optimiser ## Who can write an optimizer
Anyone. The protocol is open and described by a [machine-readable contract](contrat.md) Anyone. The protocol is open and described by a [machine-readable contract](contrat.md)
published under GPL-3.0. A free **reference optimiser** and a **conformance suite** let you published under GPL-3.0. A free **reference optimizer** and a **conformance suite** let you
write your own and check it against the real engine — see write your own and check it against the real engine — see
[Writing an optimizer](ecrire.md). [Writing an optimizer](ecrire.md).
## The ETM optimiser ## The ETM optimizer
ETM-Schurig offers its own proprietary optimiser, included in the Pro edition. It connects ETM-Schurig offers its own proprietary optimizer, included in the Pro edition. It connects
through this same protocol, within the same limits: it advises, the engine decides. At through this same protocol, within the same limits: it advises, the engine decides. At
handshake it announces itself as `etm-optimizer`; the reference optimiser announces itself handshake it announces itself as `etm-optimizer`; the reference optimizer announces itself
as `powersync-optimizer`. as `powersync-optimizer`.
## In this section ## In this section
@ -60,4 +60,4 @@ as `powersync-optimizer`.
- [Protocol](protocole.md) — transport, handshake, context, plan, fallback, validation - [Protocol](protocole.md) — transport, handshake, context, plan, fallback, validation
rules. rules.
- [Contract](contrat.md) — the OpenRPC file that is authoritative on the shape of messages. - [Contract](contrat.md) — the OpenRPC file that is authoritative on the shape of messages.
- [Writing an optimizer](ecrire.md) — reference optimiser and conformance suite. - [Writing an optimizer](ecrire.md) — reference optimizer and conformance suite.

View File

@ -11,7 +11,7 @@ eigenen Kanal. Es ist nicht die API der App: Diese läuft über die
Beschreibungen unverändert aus dem Vertrag, auf Französisch. Sie sind in allen drei Beschreibungen unverändert aus dem Vertrag, auf Französisch. Sie sind in allen drei
Sprachen identisch. Sprachen identisch.
- **Die Bedeutung** ist ausführlich, auf Französisch, in - **Die Bedeutung** ist ausführlich, auf Französisch, in
[`OPTIMIZER_PROTOCOL.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/docs/OPTIMIZER_PROTOCOL.md) [`OPTIMIZER_PROTOCOL.md`](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/docs/OPTIMIZER_PROTOCOL.md)
im Repository der Engine beschrieben. Diese Seite fasst das Wesentliche zusammen; bei im Repository der Engine beschrieben. Diese Seite fasst das Wesentliche zusammen; bei
Abweichungen gelten die Dokumente der Engine. Abweichungen gelten die Dokumente der Engine.

View File

@ -1,6 +1,6 @@
# Protocol # Protocol
The protocol links the box's energy **engine** to an **optimiser**, over a dedicated 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 channel. It is not the app's API: the app goes through the
[JSON-RPC API](../integrations/jsonrpc-api.md). [JSON-RPC API](../integrations/jsonrpc-api.md).
@ -10,13 +10,13 @@ channel. It is not the app's API: the app goes through the
**generated** from it on every publication: framing in English, descriptions taken **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. as-is from the contract, in French. They are identical in all three languages.
- **The meaning** is described in detail, in French, in - **The meaning** is described in detail, in French, in
[`OPTIMIZER_PROTOCOL.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/docs/OPTIMIZER_PROTOCOL.md), [`OPTIMIZER_PROTOCOL.md`](https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-energy-plugin-etm/src/branch/main/docs/OPTIMIZER_PROTOCOL.md),
in the engine repository. This page gives the essentials; where they differ, the in the engine repository. This page gives the essentials; where they differ, the
engine's documents prevail. engine's documents prevail.
## Transport and roles {#transport} ## Transport and roles {#transport}
- The optimiser is the **server**, the engine is the **client**: the engine connects and - The optimizer is the **server**, the engine is the **client**: the engine connects and
asks the questions. The box opens no port. asks the questions. The box opens no port.
- Two transports, same messages: `unix://` (local socket, default) and `tcp://` (machine - Two transports, same messages: `unix://` (local socket, default) and `tcp://` (machine
on the local network). on the local network).
@ -65,10 +65,10 @@ channel. It is not the app's API: the app goes through the
## Handshake {#handshake} ## Handshake {#handshake}
On every connection, the engine sends `Handshake` with its protocol version and, if On every connection, the engine sends `Handshake` with its protocol version and, if
configured, the shared secret. The optimiser answers with its version and, if it wishes, 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 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 unreadable answer: the connection is refused, and the engine drives by its rules. If the
optimiser announces the action kinds (`kinds`) it emits, the engine then rejects any action optimizer announces the action kinds (`kinds`) it emits, the engine then rejects any action
of another kind. of another kind.
<!-- BEGIN GENERATED: __optimizer_handshake__ --> <!-- BEGIN GENERATED: __optimizer_handshake__ -->
@ -505,14 +505,14 @@ Undeclared fields: **rejected** (engine-emitted form, closed).
## The answer: a plan or an abstention {#reponse} ## The answer: a plan or an abstention {#reponse}
The optimiser answers **quickly** — with its last cached plan if it computes for long — The optimizer answers **quickly** — with its last cached plan if it computes for long —
and recomputes in the background. It answers either: and recomputes in the background. It answers either:
- a **plan in time slots**: each slot carries one action per available load (setpoint in - 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 watts or amps, or SG-Ready state), each with its reason; only the current slot is
executed, and `validUntil` bounds the plan's lifetime; executed, and `validUntil` bounds the plan's lifetime;
- an **abstention** (`abstain: true`): "no plan right now". The engine drives by its rules - an **abstention** (`abstain: true`): "no plan right now". The engine drives by its rules
for that cycle, without treating the optimiser as failed. 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; 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 and, optionally, a code from the catalogue (`reasonCode`) with its parameters. Only **the
@ -684,7 +684,7 @@ Type: map of number \| boolean \| string \| null
## `UpdateLearned` {#updatelearned} ## `UpdateLearned` {#updatelearned}
Notification sent by the optimiser to the engine, with no answer, to pass on what it has 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 learned about the loads. In version 1.1 of the engine, it is received and logged, but not
yet used. yet used.
@ -704,8 +704,8 @@ Envoyée par l'OPTIMISEUR (sens inverse des deux autres), sans id, sans réponse
## Fallback state machine {#repli} ## Fallback state machine {#repli}
There is no separate heartbeat: a valid answer to `GetPlan` means the optimiser is alive. 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 optimiser is 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 declared absent and the engine drives by its rules until the next valid plan. The app sees
this state continuously (`optimizerAlive`, `activeStrategy`). this state continuously (`optimizerAlive`, `activeStrategy`).
@ -763,7 +763,7 @@ Ce que le moteur refuse EN PLUS de la forme, dans l'ordre de ses contrôles (Opt
### Tolerances {#tolerances} ### Tolerances {#tolerances}
What the engine accepts beyond the contract. A conformant optimiser does not rely on it. What the engine accepts beyond the contract. A conformant optimizer does not rely on it.
<!-- BEGIN GENERATED: __optimizer_tolerances__ --> <!-- BEGIN GENERATED: __optimizer_tolerances__ -->

View File

@ -9,7 +9,7 @@ dédié. Ce n'est pas l'API de l'application : celle-ci passe par l'[API JSON-RP
**générés** à chaque publication : cadre en anglais, descriptions reprises telles **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. 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 - **Le sens** est décrit en détail, en français, dans
[`OPTIMIZER_PROTOCOL.md`](https://github.com/etmschurig/etm-powersync-energy-plugin-etm/blob/main/docs/OPTIMIZER_PROTOCOL.md), [`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 dans le dépôt du moteur. Cette page en donne l'essentiel ; en cas d'écart, les
documents du moteur l'emportent. documents du moteur l'emportent.

View File

@ -6,6 +6,6 @@ The box integrates with your tools through two local interfaces, on your network
conventions — and its [full reference](api/index.md), generated from the box; conventions — and its [full reference](api/index.md), generated from the box;
- [MQTT](integrations/mqtt.md). - [MQTT](integrations/mqtt.md).
!!! note "What about the optimiser protocol?" !!! note "What about the optimizer protocol?"
The channel between the engine and an optimiser is **not** an integration API: it The channel between the engine and an optimizer is **not** an integration API: it
has its own protocol, described in the [Optimizer](optimiseur/protocole.md) section. has its own protocol, described in the [Optimizer](optimiseur/protocole.md) section.

View File

@ -17,15 +17,15 @@ range. A tariff with several ranges is shown there read-only.
!!! note "Exposed, not used to decide" !!! note "Exposed, not used to decide"
The engine **exposes** the tariff (display, local recording); it does not decide The engine **exposes** the tariff (display, local recording); it does not decide
with it. Without an optimiser, control stays rule-based — see with it. Without an optimizer, control stays rule-based — see
[PV production forecast](fonctionnalites/prevision-pv.md#what-the-forecast-does-not-do). [PV production forecast](fonctionnalites/prevision-pv.md#what-the-forecast-does-not-do).
## Dynamic tariffs <span class="badge nightly">ROADMAP</span> ## Dynamic tariffs <span class="badge nightly">ROADMAP</span>
Arbitration on national tariff signals — **France**: Tempo (blue/white/red), off-peak Arbitration on national tariff signals — **France**: Tempo (blue/white/red), off-peak
and peak hours, dynamic offers; CO₂ optimisation from the RTE eco2mix signal. and peak hours, dynamic offers; CO₂ optimization from the RTE eco2mix signal.
**Germany**: variable tariffs, spot offers (EPEX), grid-responsive control of **Germany**: variable tariffs, spot offers (EPEX), grid-responsive control of
controllable loads under **§14a EnWG**; CO₂ optimisation from **SMARD** controllable loads under **§14a EnWG**; CO₂ optimization from **SMARD**
(Bundesnetzagentur). (Bundesnetzagentur).
!!! note "Roadmap" !!! note "Roadmap"