docs(contract): INTERFACE charge pilotée rév. 3 — frontière optimiseur↔routeur
Vérification nymea : un integration-plugin ne peut pas piloter les things d'un autre
plugin (thingManager privé ; seuls cœur/règles/scripts/experience-plugins le font).
Donc la combinatoire watts→relais ne peut vivre que côté experience-plugin.
rév. 3 acte le déplacement de frontière :
- Optimiseur watt-pur (ne connaît jamais un relais) | ROUTEUR (watts→relais,
experience-plugin, a le ThingManager) | relais = things "power" nus.
- ECS multipalier : LoadConfig devient une LISTE de relais power (relays[] +
minOnS/minOffS) ; powerLevels DÉRIVÉS des combinaisons (source = les relais).
- Cas continu (EV/triac, EtmVariableLoadAdapter) inchangé.
- §10 IMPACT APP explicite : l'écran « Configurer » passe à une liste de relais +
picker findConfiguredThings("power") (UI-only) — l'agent app doit lire rév. 3
avant de toucher l'UI.
À déposer À L'IDENTIQUE dans etm-powersync-app (miroir manuel, hors de ce repo).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7184fe4e1d
commit
1f976f0189
@ -1,169 +1,238 @@
|
||||
# INTERFACE `etmvariableload` — contrat charge pilotée · **rév. 2**
|
||||
# INTERFACE charge pilotée — contrat · **rév. 3**
|
||||
|
||||
> **Source de vérité unique.** Déposé **à l'identique dans les deux repos**
|
||||
> (`etm-powersync-app` et `etm-powersync-energy-plugin-etm`). Toute divergence se tranche **ici**,
|
||||
> pas dans le code. Un agent qui a besoin d'un champ absent met à jour ce doc **d'abord**, dans
|
||||
> les deux repos.
|
||||
>
|
||||
> **rév. 2 — corrige la rév. 1 sur un point structurant :** `etmvariableload` ne couvre **que les
|
||||
> charges à puissance pilotable** (EV, ECS résistif, routeur PV). **La PAC SG-Ready en est exclue** :
|
||||
> elle reste un modèle **à états**, interface séparée et conservée (voir §8). `LoadAction` garde
|
||||
> donc **deux** kinds (`Setpoint` ET l'état SG-Ready), pas un seul.
|
||||
> ### ⚠️ rév. 3 — déplacement de frontière (lire avant de coder)
|
||||
> La rév. 2 plaçait la combinatoire matérielle « dans le thing ». **Vérification faite : un
|
||||
> integration-plugin nymea ne peut PAS piloter les things d'un autre plugin** (`thingManager`
|
||||
> y est privé ; seuls le cœur, le moteur de règles, les scripts et les **experience-plugins**
|
||||
> commandent des things tiers). Donc la combinatoire watts→relais **ne peut vivre que côté
|
||||
> experience-plugin** — là où l'arbitre a déjà le `ThingManager`.
|
||||
>
|
||||
> **rév. 3 acte cette réalité** : un relais est un **thing `power` nu** (comme n'importe quel
|
||||
> relais Modbus/MQTT) ; la combinaison watts→relais vit dans une couche **ROUTEUR** distincte,
|
||||
> **sous** l'optimiseur, côté experience-plugin. L'optimiseur reste **watt-pur** et ne connaît
|
||||
> **jamais** un relais. **Impact app majeur en §10 — l'écran « Configurer » change.**
|
||||
|
||||
---
|
||||
|
||||
## 1. Principe & frontière
|
||||
## 1. Principe & frontière (rév. 3)
|
||||
|
||||
Trois niveaux, trois responsables. **Aucune notion de matériel ne remonte au-dessus du thing.**
|
||||
**Trois couches, la frontière entre OPTIMISEUR et ROUTEUR.**
|
||||
|
||||
| Niveau | Où | Quoi |
|
||||
| Couche | Où | Raisonne en | Connaît |
|
||||
|---|---|---|---|
|
||||
| **Optimiseur** | `RuleBasedScheduler` / `SocketScheduler` (GPL ou socket) | **watts** | priorités, `powerLevels` (W), surplus, besoins. **Jamais** un relais. |
|
||||
| **— FRONTIÈRE —** | | | l'optimiseur émet `LoadAction{Setpoint, powerW}` ; rien d'autre ne descend |
|
||||
| **Routeur** | couche adaptateur, **experience-plugin** (a le `ThingManager`) | watts → **relais** | la liste de relais, la combinatoire, `minOn/minOff`, l'agrégation `currentPowerW` |
|
||||
| **Relais** | things `power` (plugins GPIO / Modbus / MQTT) | on/off | leur propre matériel |
|
||||
|
||||
> **Invariant directeur (rév. 3).** L'**optimiseur** raisonne uniquement en watts : aucun relais,
|
||||
> aucun index de combinaison ne franchit la frontière vers le haut. Le **routeur** traduit le
|
||||
> setpoint W en commutation de relais et n'expose vers le haut que des watts (`powerLevels`,
|
||||
> `currentPowerW`). **L'optimiseur décide de la puissance ; le routeur décide des relais.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Portée
|
||||
|
||||
| Charge | Couche d'exécution | Nature |
|
||||
|---|---|---|
|
||||
| **Câblage** | thing / plugin device (wizard Things) | GPIO, registres Modbus, quelle résistance sur quelle phase |
|
||||
| **Déclaration** | config charge pilotée (app) | mode, paliers atteignables **en W**, priorité, besoins |
|
||||
| **Runtime** | interface `etmvariableload` | setpoint W envoyé ↔ puissance réelle remontée |
|
||||
| **ECS résistif multipalier** | **`RelayRouter`** (rév. 3) | N relais `power` → combinaison watts |
|
||||
| **ECS simple on/off** | `RelayRouter` (cas dégénéré : 1 relais, niveaux `[0, nominal]`) | 1 relais `power` |
|
||||
| **EV charger / Routeur PV / triac** (modulation continue) | **`EtmVariableLoadAdapter`** (inchangé depuis rév. 2) | 1 thing à puissance pilotable, setpoint direct |
|
||||
| **PAC SG-Ready** | `SgReadyAdapter` (modèle à **états**, §8) | **exclue** — pas une charge en watts |
|
||||
|
||||
> **Invariant directeur.** L'energymanager raisonne **uniquement en watts**. Aucune résistance,
|
||||
> aucun relais, aucun index de palier matériel ne franchit l'app ni n'atteint l'arbitre.
|
||||
> **Le thing décide du matériel ; l'energymanager décide de la puissance.**
|
||||
Les deux exécuteurs (`RelayRouter`, `EtmVariableLoadAdapter`) parlent à l'optimiseur **la même
|
||||
langue** : `LoadAction{Setpoint, powerW}`. Chacun exécute selon sa nature — combinaison de relais
|
||||
pour l'un, écriture d'un setpoint continu pour l'autre. Un triac n'est **pas** un routeur de
|
||||
relais (pas de combinatoire) : on ne le force pas dans ce moule.
|
||||
|
||||
---
|
||||
|
||||
## 2. Portée de `etmvariableload`
|
||||
## 3. Les exécuteurs sous l'optimiseur
|
||||
|
||||
**EST une `etmvariableload`** (charge à puissance pilotable) :
|
||||
- **EV charger** (setpoint courant/puissance),
|
||||
- **ECS résistif** — classique (on/off) ou multipalier,
|
||||
- **Routeur PV / triac** (modulation continue).
|
||||
### 3.a Cas relais (ECS multipalier) — `RelayRouter`
|
||||
- **Pas de thing `etmvariableload` unique.** La charge logique est une **liste de relais `power`**.
|
||||
- Chaque relais est un thing implémentant l'interface nymea **`power`** (state `power` bool
|
||||
writable ; optionnellement `currentPower` W en lecture). Fourni par le plugin device
|
||||
(GPIO/Modbus/MQTT) — **rien de spécifique ECS dans ce plugin**.
|
||||
- Le `RelayRouter` (experience-plugin) :
|
||||
1. reçoit `LoadAction{Setpoint, powerW}` de l'optimiseur ;
|
||||
2. **mappe** `powerW` → la combinaison de relais la plus haute ≤ `powerW` ;
|
||||
3. applique l'anti-rebond **`minOn`/`minOff`** (ici, pas dans le thing) et l'ordre
|
||||
**off-before-on** (pas de sur-puissance transitoire sur une transition non-cascadée) ;
|
||||
4. commande chaque relais via `ThingManager::executeAction` (state `power`) ;
|
||||
5. **agrège `currentPowerW`** = somme des `currentPower` des relais ON, **ou** à défaut de
|
||||
mesure (relais GPIO bool sans wattmètre) la **somme des nominaux commandés**. Dans ce
|
||||
dernier cas `currentPowerW` reflète le *commandé*, pas le *mesuré* — acceptable, à garder
|
||||
en tête pour le diagnostic terrain.
|
||||
- **`powerLevels`/`maxPowerW` sont DÉRIVÉS** des combinaisons atteignables de la liste de
|
||||
relais — **source de vérité unique = les relais**. Le routeur les calcule et les expose à
|
||||
l'optimiseur ; l'assistant app fait le même calcul pour l'affichage.
|
||||
|
||||
**N'EST PAS une `etmvariableload`** :
|
||||
- **PAC SG-Ready** → modèle à **états** (2 bits, hystérésis, `minStateHold`). Interface distincte,
|
||||
conservée telle quelle. Voir §8. Un signal SG-Ready est une *suggestion d'état* à la PAC, pas
|
||||
une consigne de puissance en watts — le forcer en setpoint W serait faux.
|
||||
### 3.b Cas continu (EV / routeur PV) — interface device `etmvariableload`
|
||||
Inchangé rév. 2 : 1 thing exposant un state `powerSetpoint` (write) + `currentPowerW` (read),
|
||||
piloté par `EtmVariableLoadAdapter`. (`maxPowerW`/`powerLevels` en read optionnels.)
|
||||
|
||||
| State | Accès | Type | Sémantique |
|
||||
|---|---|---|---|
|
||||
| `powerSetpoint` | **write** | uint W | consigne de l'optimiseur |
|
||||
| `currentPowerW` | read | uint W | puissance réellement appliquée |
|
||||
| `maxPowerW` | read | uint W | plafond physique |
|
||||
|
||||
---
|
||||
|
||||
## 3. Interface device `etmvariableload`
|
||||
## 4. `LoadConfig` — config app → energymanager (rév. 3)
|
||||
|
||||
Implémentée par le thing (ECS / EV / routeur). **Côté moteur, seul l'`EtmVariableLoadAdapter`
|
||||
consommateur est livré** ; la déclaration de l'interface nymea + le driver (résistances/triac/
|
||||
anti-rebond/phases) sont une **session device dédiée, hors repo moteur**.
|
||||
|
||||
| State | Accès | Type | Unité | Sémantique |
|
||||
|---|---|---|---|---|
|
||||
| `maxPowerW` | read | uint | W | Plafond physique de la charge. |
|
||||
| `powerLevels` | read | list<uint> | W | Paliers atteignables, triés croissants, **`0` inclus**. Présent en *fixed*. Vide/absent ⇒ modulation continue. |
|
||||
| `powerSetpoint` | **write** | uint | W | Consigne demandée par l'energymanager. |
|
||||
| `currentPowerW` | read | uint | W | Puissance **réellement** appliquée. Ferme la boucle de quantification. |
|
||||
|
||||
**Le thing, en recevant un `powerSetpoint` :** choisit la combinaison matérielle concrète,
|
||||
applique ses **verrous anti-rebond** (minOn/minOff) et son **équilibrage de phases**, puis publie
|
||||
`currentPowerW`. L'energymanager n'a aucune visibilité là-dessus — c'est voulu.
|
||||
|
||||
---
|
||||
|
||||
## 4. `LoadDescriptor` — config app → energymanager
|
||||
|
||||
Émis par l'écran « Configurer charge pilotée » (sous-ensemble de `OPTIMIZER_PROTOCOL.md §5`,
|
||||
noms identiques) :
|
||||
Émis par l'écran « Configurer charge pilotée » ; persisté **côté plugin**
|
||||
(`/var/lib/nymea/energy-load-configuration.json`), jamais en SharedPreferences. App = éditeur :
|
||||
`NymeaEnergy.GetLoadConfig` / `SetLoadConfig`.
|
||||
|
||||
### Cas relais (`adapter: "relay-router"`, mode `fixed`)
|
||||
```jsonc
|
||||
{
|
||||
"id": "uuid-thing-ecs",
|
||||
"label": "Chauffe-eau",
|
||||
"adapter": "etmvariableload",
|
||||
"mode": "fixed", // "fixed" | "dynamic"
|
||||
"powerLevels": [0, 600, 1200, 1800, 2400], // REQUIS si fixed — absent si dynamic
|
||||
"maxPowerW": 3000, // REQUIS si dynamic ; plafond aussi en fixed
|
||||
"priority": 2, // rang ; 1 = servi en premier
|
||||
"enabled": true,
|
||||
"needs": { "dailyDeadline": "06:00", "minEnergyWhPerDay": 4000 }
|
||||
"id": "ecs-chauffe-eau", // charge LOGIQUE (plus un thingId unique)
|
||||
"label": "Chauffe-eau",
|
||||
"adapter": "relay-router",
|
||||
"mode": "fixed",
|
||||
"priority": 2, // rang ; 1 = servi en premier
|
||||
"enabled": true,
|
||||
"needs": { "dailyDeadline": "06:00", "minEnergyWhPerDay": 4000 },
|
||||
"relays": [ // ← la combinatoire vit ICI (routeur), pas chez l'optimiseur
|
||||
{ "thingId": "{uuid-relais-500W}", "powerW": 500 },
|
||||
{ "thingId": "{uuid-relais-1000W}", "powerW": 1000 },
|
||||
{ "thingId": "{uuid-relais-2000W}", "powerW": 2000 }
|
||||
],
|
||||
"minOnS": 60,
|
||||
"minOffS": 60
|
||||
// powerLevels / maxPowerW : ABSENTS de la config — DÉRIVÉS de relays[] par le routeur.
|
||||
}
|
||||
```
|
||||
|
||||
- `powerLevels` saisi par l'installateur — directement ou via l'**assistant résistances** (UI-only :
|
||||
additionne les unités, pré-remplit, puis disparaît ; **aucune résistance n'est stockée ni envoyée**).
|
||||
- Config **persistée côté plugin** (`/var/lib/nymea/energy-load-configuration.json`), jamais en
|
||||
SharedPreferences. App = éditeur : `NymeaEnergy.GetLoadConfig` / `SetLoadConfig`.
|
||||
### Cas continu (`adapter: "etmvariableload"`, mode `dynamic`)
|
||||
```jsonc
|
||||
{
|
||||
"id": "{uuid-thing-triac}", "label": "Routeur PV salon", "adapter": "etmvariableload",
|
||||
"mode": "dynamic", "maxPowerW": 3000, "priority": 1, "enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
Règles :
|
||||
- **`relays[]` ne franchit jamais la frontière** : il vit dans le routeur. L'optimiseur ne reçoit
|
||||
que `powerLevels`/`maxPowerW` (watts dérivés) via le contexte.
|
||||
- `relays[].thingId` = ThingId du thing `power`. **Choix par PICKER côté app** (voir §10) — le
|
||||
picker est **UI-only** et ne fait qu'aider la saisie : il résout *nom du relais → ThingId* et
|
||||
écrit des ThingIds dans `relays[]`. **Aucun champ supplémentaire au contrat.**
|
||||
- `minOnS`/`minOffS` : verrous anti-rebond (protection relais / compresseur), **par charge**.
|
||||
- `enabled:false` → rôle déclaré mais **exclu** de l'arbitrage (aucune action, aucun adaptateur
|
||||
construit).
|
||||
|
||||
---
|
||||
|
||||
## 5. Règle d'arrondi (energymanager)
|
||||
## 5. Règle d'arrondi (optimiseur) — inchangée
|
||||
|
||||
À chaque cycle, pour une charge dont c'est le tour dans la priorité :
|
||||
- **Fixed** : `level = max( l ∈ powerLevels | l ≤ budget )` puis `Setpoint(level)`. L'optimiseur
|
||||
connaît la granularité **dérivée** → pas de sur-allocation, **zéro cycle de retard**. (Le routeur
|
||||
traduit ensuite `level` → relais.)
|
||||
- **Dynamic** : `Setpoint( clamp(budget, 0, maxPowerW) )`.
|
||||
- **Résidu** : lire `currentPowerW` **de début de cycle** (télémétrie de l'exécuteur — PAS de
|
||||
relecture post-setpoint : invariant 8). Le résidu `budget − currentPowerW` repart vers la
|
||||
charge suivante de la priorité **du même cycle**.
|
||||
|
||||
- **Fixed** : `level = max( l ∈ powerLevels | l ≤ budget )` puis `setPowerSetpoint(level)`.
|
||||
L'arbitre connaît la **granularité déclarée** → pas de sur-allocation, **zéro cycle de retard**.
|
||||
- **Dynamic** : `setPowerSetpoint( clamp(budget, 0, maxPowerW) )`.
|
||||
- **Résidu** : lire le `currentPowerW` **de début de cycle** (télémétrie de l'adapter, comme la
|
||||
Correction B — **PAS** une relecture post-setpoint : invariant 8, aucune boucle de feedback).
|
||||
Le résidu `budget − currentPowerW` repart vers la charge suivante de la priorité **du même cycle**.
|
||||
|
||||
**Déclaré vs réel :** la **déclaration** (`powerLevels`/`maxPowerW`) est la référence de
|
||||
**planification**. `currentPowerW` est le **juge de paix runtime**. Si le réel plafonne durablement
|
||||
sous le déclaré → erreur de déclaration, l'installateur corrige. L'energymanager **n'écrase jamais**
|
||||
la déclaration.
|
||||
**Déclaré vs réel :** `powerLevels`/`maxPowerW` (dérivés des relais) = référence de planification.
|
||||
`currentPowerW` = juge runtime. Si le réel plafonne durablement sous le déclaré → erreur de
|
||||
câblage/déclaration, l'installateur corrige. L'optimiseur **n'écrase jamais** la déclaration.
|
||||
|
||||
---
|
||||
|
||||
## 6. `LoadAction` — **deux** kinds
|
||||
## 6. `LoadAction` — **deux** kinds (inchangé)
|
||||
|
||||
| Kind | Pour | Porte |
|
||||
|---|---|---|
|
||||
| `Setpoint` | EV / ECS / routeur (`etmvariableload`) | `powerW` |
|
||||
| `Setpoint` | EV / ECS / routeur PV (via `RelayRouter` **ou** `EtmVariableLoadAdapter`) | `powerW` |
|
||||
| *état SG-Ready* | PAC (interface à états, §8) | `state` (1–4) |
|
||||
| `Constraint` | batterie (déféré 3f) | conservé |
|
||||
|
||||
- **Supprimer `Stage`** (ECS → `Setpoint`). Retirer `stage/minStage/maxStage` de
|
||||
`LoadContextTelemetry` (le verrou ECS redescend dans le thing).
|
||||
- **Conserver le kind d'état SG-Ready** et `state/minState/maxState` pour la PAC — son
|
||||
`minStateHold` (protection court-cycling compresseur) reste dans son chemin, **inchangé**.
|
||||
- `reason` reste porté par chaque `LoadAction` (déjà le cas).
|
||||
L'index de combinaison de relais **n'existe pas** au niveau `LoadAction` : il est **privé au
|
||||
routeur**. Le kind `Stage` reste supprimé (rév. 2). `reason` reste porté par chaque `LoadAction`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Expo du `reason` — tableau additif `loadActions[]`
|
||||
|
||||
Sérialiser dans `GetChargingSchedules` / `ChargingSchedulesChanged` un **champ additif**
|
||||
(rétro-compatible, ne touche pas la structure EV `ChargingAction`) :
|
||||
## 7. Expo du `reason` — tableau additif `loadActions[]` (inchangé)
|
||||
|
||||
Dans `GetChargingSchedules` / `ChargingSchedulesChanged` :
|
||||
```jsonc
|
||||
"loadActions": [
|
||||
{ "loadId": "uuid-ecs", "setpointW": 1200, "currentPowerW": 1180, "reason": "Surplus PV 1.3 kW — ECS rang 2" },
|
||||
{ "loadId": "uuid-pac", "state": 3, "reason": "Surplus PV — PAC en état 3" }
|
||||
{ "loadId": "ecs-chauffe-eau", "setpointW": 1500, "currentPowerW": 1480, "reason": "Surplus PV 1.6 kW — ECS rang 2" },
|
||||
{ "loadId": "uuid-pac", "state": 3, "reason": "Surplus PV — PAC en état 3" }
|
||||
]
|
||||
```
|
||||
|
||||
- Couvre **toutes** les charges, y compris la PAC. `setpointW`/`currentPowerW` **optionnels**
|
||||
(absents pour une charge à états) ; `loadId` + `reason` **toujours** présents.
|
||||
- Nécessite que l'arbitre **retienne le dernier plan** (aujourd'hui loggé puis jeté,
|
||||
`energyarbitrator.cpp:140-153`) — petite addition d'état.
|
||||
- C'est le canal que consomme la **decision card** de l'app : « pour chaque charge, le pourquoi ».
|
||||
`setpointW`/`currentPowerW` optionnels (absents pour une charge à états) ; `loadId` + `reason`
|
||||
**toujours** présents. C'est le canal de la **decision card** de l'app.
|
||||
|
||||
---
|
||||
|
||||
## 8. PAC SG-Ready — modèle à états (conservé)
|
||||
## 8. PAC SG-Ready — modèle à états (conservé, inchangé)
|
||||
|
||||
Hors `etmvariableload`. Le `SgReadyAdapter` existant (états 1–4, combos K1/K2, hystérésis,
|
||||
`minStateHold`) **n'est pas réécrit**. L'arbitre lui envoie un **état** (kind §6), pas un setpoint W.
|
||||
Repli L2 : état 2 (normal, mains off), jamais état 1 (blocage) — comportement actuel conservé.
|
||||
|
||||
> Une PAC pilotée par Modbus qui exposerait une vraie puissance pilotable pourra, *plus tard*,
|
||||
> implémenter `etmvariableload`. Tant qu'on pilote par signal SG-Ready, c'est un modèle à états.
|
||||
Hors charge en watts. `SgReadyAdapter` (états 1–4, combos K1/K2, hystérésis, `minStateHold`)
|
||||
n'est pas réécrit. L'arbitre lui envoie un **état**, pas un setpoint W. Repli L2 : état 2 (normal,
|
||||
mains off), jamais état 1 (blocage).
|
||||
|
||||
---
|
||||
|
||||
## 9. Sécurité (déjà en place)
|
||||
## 9. Sécurité — repli L2 (rév. 3)
|
||||
|
||||
- **Watchdog L2** (`evaluateMeterFreshness`, 90 s, `applyDegradedMode`) existant. En dégradé,
|
||||
repli charge pilotée = **`setPowerSetpoint(0)` `force=true`** (remplace l'ECS stage-0 actuel) ;
|
||||
PAC → état 2. Conserver l'ordre `update()` (sécurité avant planif) intact.
|
||||
- `enabled:false` → rôle déclaré mais **exclu** de l'arbitrage (aucune action envoyée).
|
||||
- **Watchdog L2** (`evaluateMeterFreshness`, 90 s, `applyDegradedMode`) inchangé. En dégradé :
|
||||
- charges en watts (`RelayRouter` **et** `EtmVariableLoadAdapter`) → `Setpoint(0)` `force=true`.
|
||||
Pour le `RelayRouter`, ça coupe **tous** les relais (`force` bypasse `minOn/minOff`).
|
||||
- PAC → état 2.
|
||||
- Ordre `update()` (sécurité avant planif) intact. `enabled:false` → exclu de l'arbitrage.
|
||||
|
||||
---
|
||||
|
||||
## 10. Dépendances liées (à coder en même temps, côté plugin)
|
||||
## 10. IMPACT APP — écran « Configurer charge pilotée » (⚠️ lire avant de coder l'UI)
|
||||
|
||||
- Handler `nymeaenergyjsonhandler` : `Get/SetLoadConfig` + persistance.
|
||||
- `loadActions[]` dans `GetChargingSchedules` / `ChargingSchedulesChanged`.
|
||||
- Câbler l'arbitre pour construire ses adapters depuis `LoadConfig` au lieu du registre en dur
|
||||
(`energypluginnymea.cpp:58-80` — le « 3g »).
|
||||
**Ce qui change pour `etm-powersync-app` en rév. 3.** L'agent app doit lire ceci **avant** de
|
||||
toucher à l'écran Configurer, sinon il code contre une frontière périmée (rév. 2).
|
||||
|
||||
- **AVANT (rév. 2)** : configurer une ECS = choisir **1 thing `etmvariableload`** + saisir des
|
||||
`powerLevels`.
|
||||
- **APRÈS (rév. 3)** : configurer une ECS = **ajouter une liste de relais** (things `power`) avec,
|
||||
pour chacun, sa **puissance (W)**, + les verrous **`minOnS`/`minOffS`**. Plus de « 1 thing
|
||||
etmvariableload » pour le cas relais.
|
||||
- **Picker relais (UI-only)** : l'app présente les things implémentant l'interface `power`
|
||||
(via l'API d'intégration nymea, équivalent `findConfiguredThings("power")`) pour que
|
||||
l'installateur **choisisse ses relais par NOM** (taper un UUID au doigt = erreur garantie).
|
||||
Le picker **résout nom → ThingId** et écrit des ThingIds dans `relays[]`. Il **n'ajoute aucun
|
||||
champ au contrat** (comme l'assistant résistances).
|
||||
- **Assistant résistances (UI-only, conservé)** : à partir des relais et de leurs puissances,
|
||||
calcule et **affiche** les `powerLevels` atteignables (combinaisons) — pour que l'installateur
|
||||
voie ce qu'il obtient. Ces `powerLevels` ne sont **pas** envoyés (dérivés côté plugin).
|
||||
- **`SetLoadConfig`** reçoit donc `relays[]` (+ `minOnS`/`minOffS`) pour une charge relais ; le cas
|
||||
continu (triac) garde un `thingId` + `maxPowerW`. Voir §4.
|
||||
|
||||
---
|
||||
|
||||
## 11. Dépendances liées (côté plugin)
|
||||
|
||||
- Handler `nymeaenergyjsonhandler` : `Get/SetLoadConfig` + persistance (fait ; schéma étendu rév. 3).
|
||||
- `loadActions[]` dans `GetChargingSchedules` / `ChargingSchedulesChanged` (fait).
|
||||
- Arbitre : construit `RelayRouter` (si `relays[]`) **ou** `EtmVariableLoadAdapter` (si continu)
|
||||
depuis `LoadConfig` ; repli L2 unifié sur les exécuteurs en watts.
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
- **rév. 1** : `etmvariableload` interface unique, watts, un seul kind d'action.
|
||||
- **rév. 2** : PAC SG-Ready **exclue** (modèle à états séparé) ; `LoadAction` garde **deux** kinds
|
||||
(`Setpoint` + état SG-Ready) ; suppression du kind `Stage`.
|
||||
- **rév. 3** : **frontière déplacée** entre optimiseur (watt-pur) et **routeur** (watts→relais,
|
||||
experience-plugin) ; un relais = thing `power` nu ; `LoadConfig` du cas ECS devient une **liste
|
||||
de relais** (`relays[]` + `minOnS`/`minOffS`) ; `powerLevels` **dérivés** ; cas continu
|
||||
(`EtmVariableLoadAdapter`) inchangé. **Impact app §10.**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user