[brief] AGENTS.md définitif (arbitre+LoadAdapters), CLAUDE.md pointeur, protocole versionné
This commit is contained in:
parent
39f8c7ae18
commit
074fa71308
21
AGENTS.md
Normal file
21
AGENTS.md
Normal file
@ -0,0 +1,21 @@
|
||||
# AGENTS.md — etm-powersync-energy-plugin-etm
|
||||
|
||||
Moteur HEMS. Fork GPL de `nymea-energy-plugin-nymea`, étendu de l'optimisation EV
|
||||
vers un gestionnaire d'énergie complet (ECS, PAC, batterie, relais).
|
||||
|
||||
- **Licence** : GPL-3.0 · **Miroir public** : OUI
|
||||
- **Agent** : energy-etm · **Branche** : feature/beta-rulebased · **Scope** : energyplugin/
|
||||
|
||||
## Invariants locaux
|
||||
1. Tourne SANS `etm-powersync-optimizer` (socket absent → repli stratégie règles).
|
||||
2. Sécurité jamais déléguée : `verifyOverloadProtection()` (temps réel) borne toute sortie de l'optimiseur.
|
||||
3. Pas de boucle de feedback : surplus = PV mesurée + compteur, jamais le net.
|
||||
4. `decisionReason` non vide, en français, sur chaque décision.
|
||||
5. Aucun composant propriétaire ici (Héos vit dans `etm-powersync-optimizer`).
|
||||
6. Première tâche (revue) : renommer `nymea-energy-plugin-nymea.pro` → `.pro` ETM
|
||||
(+ TARGET, debian/). NE PAS toucher aux noms de paquets publiés.
|
||||
|
||||
## Références
|
||||
- `README.md` (architecture), `INTERFACE.md` (fait autorité sur l'API), `etm_powersync_energy.svg`.
|
||||
|
||||
Carte globale et frontières : voir `../AGENTS.md`.
|
||||
174
CLAUDE.md
174
CLAUDE.md
@ -1,173 +1 @@
|
||||
# Agent Plugin — `powersync-energy-plugin-etm` (GPL3)
|
||||
> Lire aussi le `CLAUDE.md` du dossier parent avant de commencer.
|
||||
|
||||
---
|
||||
|
||||
## Mon rôle
|
||||
Je suis le **plugin nymea Community** du HEMS ETM-PowerSync.
|
||||
Je contiens toute la logique GPL3 : recharge EV sur surplus, tarif HP/HC,
|
||||
protection surcharge, et le pont vers `powersync-optimizer` pour les tiers payants.
|
||||
|
||||
**Licence : GPL3** — tout mon code est open-source assumé.
|
||||
Origine : fork de nymea-energy-plugin-nymea (source nymea/Chargebyte, GPL3).
|
||||
Pas de remote upstream Git public — mises à jour via portage manuel depuis
|
||||
`etm-nymea/nymea-energy-plugin-nymea`.
|
||||
|
||||
---
|
||||
|
||||
## Règle fondamentale
|
||||
|
||||
```
|
||||
Ce repo = fonctionnalités Community UNIQUEMENT.
|
||||
Zéro logique Auto / Predict AI dans ce repo.
|
||||
Ces features passent exclusivement par PowerSyncClient → optimizer.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ce que je FOURNIS
|
||||
|
||||
### API JSON-RPC `EnergyPlugin.*`
|
||||
| Méthode | Rôle | Tier |
|
||||
|---|---|---|
|
||||
| `GetChargingInfos(evChargerId)` | Config recharge EV | Community |
|
||||
| `SetChargingInfo(chargingInfo)` | Mettre à jour config borne EV | Community |
|
||||
| `GetChargingSchedules(evChargerId)` | Planning calculé | Community |
|
||||
| `GetAvailableSpotMarketProviders()` | Liste providers tarifs | Community |
|
||||
| `SetSpotMarketConfiguration(enabled, providerId)` | Activer provider | Community |
|
||||
| `GetSpotMarketScoreEntries(date)` | Cotations horaires | Community |
|
||||
| `SetPhasePowerLimit(Uint)` | Protection surcharge | Community |
|
||||
| `SetAcquisitionTolerance(Double)` | Seuil surplus | Community |
|
||||
| `SetBatteryLevelConsideration(Double)` | Facteur batterie | Community |
|
||||
|
||||
### Notifications push
|
||||
`ChargingInfoAdded/Removed/Changed`, `ChargingSchedulesChanged`,
|
||||
`SpotMarketConfigurationChanged`, `SpotMarketScoreEntriesChanged`,
|
||||
`PhasePowerLimitChanged`
|
||||
|
||||
### `.so` produit
|
||||
```
|
||||
libnymea_energypluginnymea.so ← nom identique à l'upstream (drop-in replacement)
|
||||
install : /usr/lib/nymea/energy/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ce que je CONSOMME
|
||||
|
||||
### Interfaces nymea (par interface, jamais par ThingClassId)
|
||||
| Interface | États lus | Actions envoyées |
|
||||
|---|---|---|
|
||||
| `evcharger` | `chargingEnabled`, `maxChargingCurrent`, `pluggedIn`, `charging`, phases | `setChargingEnabled`, `setMaxChargingCurrent` |
|
||||
| `electricvehicle` | `batteryLevel`, `maxChargingCurrent`, `capacity` | — |
|
||||
| `rootmeter` / `energymeter` | `currentPowerPhaseA/B/C`, `currentPhaseA/B/C` | — |
|
||||
| `energystorage` | `currentPower`, `batteryLevel` | — |
|
||||
|
||||
### Signal déclencheur
|
||||
`PowerBalanceEntryAdded` depuis `nymea-experience-plugin-energy` → cycle ~1 min.
|
||||
|
||||
### Service propriétaire (optionnel)
|
||||
`PowerSyncClient` → Unix socket `/run/powersync/optimizer.sock`
|
||||
Si absent → mode Community local, aucune erreur.
|
||||
|
||||
---
|
||||
|
||||
## Architecture interne
|
||||
|
||||
```
|
||||
powersync-energy-plugin-etm/
|
||||
│
|
||||
├── [code upstream nymea/Chargebyte] ← ne pas modifier directement
|
||||
│ ├── SmartChargingManager.* ← à corriger (bugs phase EV)
|
||||
│ ├── SpotMarketManager.* ← aWATTar AT/DE ✅
|
||||
│ └── NymeaEnergyJsonHandler.* ← API JSON-RPC
|
||||
│
|
||||
└── etm/ ← tout notre code ETM ici
|
||||
├── PowerSyncClient.* ← pont vers optimizer (Unix socket)
|
||||
├── tariff/
|
||||
│ └── StaticHcHpProvider.* ← HP/HC statique (Community)
|
||||
└── [futures extensions Community]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PowerSyncClient — le pont vers l'optimizer
|
||||
|
||||
```cpp
|
||||
class PowerSyncClient : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
// Vérifie si powersync-optimizer tourne
|
||||
bool isAvailable() const;
|
||||
|
||||
// Demande une décision d'optimisation (Auto/Predict AI)
|
||||
OptimizationResult requestOptimization(const SurplusData &data);
|
||||
|
||||
// Récupère la météo J+1 (Auto)
|
||||
WeatherForecast getWeatherForecast();
|
||||
|
||||
// Récupère le tarif dynamique courant (Predict AI)
|
||||
TariffData getDynamicTariff();
|
||||
|
||||
signals:
|
||||
void availabilityChanged(bool available);
|
||||
void optimizationResultReceived(OptimizationResult result);
|
||||
};
|
||||
```
|
||||
|
||||
**Comportement du cycle principal :**
|
||||
```cpp
|
||||
void SmartChargingManager::runCycle() {
|
||||
if (m_powerSyncClient->isAvailable()) {
|
||||
// Auto / Predict AI — délègue à l'optimizer
|
||||
auto result = m_powerSyncClient->requestOptimization(buildSurplusData());
|
||||
applyOptimizationResult(result);
|
||||
} else {
|
||||
// Community — logique GPL3 locale
|
||||
planSurplusCharging(); // EV sur surplus
|
||||
planSpotMarketCharging(); // EV sur aWATTar
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## État actuel du code
|
||||
|
||||
### ✅ Fonctionnel
|
||||
- SmartChargingManager : recharge EV surplus (mode Eco) + aWATTar AT/DE
|
||||
- Overload protection triphasée
|
||||
- API JSON-RPC `EnergyPlugin.*` complète
|
||||
- Détection appareils par interface (zéro UUID hardcodé)
|
||||
|
||||
### ❌ À corriger en priorité
|
||||
| Fichier | Problème | Priorité |
|
||||
|---|---|---|
|
||||
| `evcharger.cpp:171`, `smartchargingmanager.cpp:394,477,517` | Assume toujours phase A — faux pour EV sur phase B/C | 🔴 |
|
||||
| `EnergyPluginNymea::init()` | Pas de guard si `EnergyManager*` null | 🟠 |
|
||||
| `smartchargingmanager.cpp:59` | Récurrence hebdo non terminée | 🟠 |
|
||||
| `smartchargingmanager.cpp:884` | Planification limitée à 24h | 🟠 |
|
||||
| `smartchargingmanager.cpp:1835` | Actions EV non séquentielles, pas de retry | 🟠 |
|
||||
|
||||
### ❌ À créer (code ETM dans `etm/`)
|
||||
- `PowerSyncClient` (pont Unix socket vers optimizer)
|
||||
- `StaticHcHpProvider` (tarif HP/HC statique — Community)
|
||||
|
||||
---
|
||||
|
||||
## Règles de modification
|
||||
|
||||
- Tout code ETM va dans `etm/` — jamais dans le code upstream
|
||||
- Modifier le code upstream uniquement pour corriger des bugs (FIXME existants)
|
||||
- Tout changement d'API `EnergyPlugin.*` → mettre à jour `INTERFACE.md`
|
||||
- Ne jamais ajouter de logique Auto/Predict AI dans ce repo
|
||||
- Build : `qmake energyplugin.pro && make -j$(nproc)`
|
||||
|
||||
---
|
||||
|
||||
## Portage des mises à jour nymea/Chargebyte
|
||||
|
||||
Quand une nouvelle version est disponible dans `etm-nymea/nymea-energy-plugin-nymea` :
|
||||
1. `diff -r etm-nymea/nymea-energy-plugin-nymea/ powersync-energy-plugin-etm/`
|
||||
2. Porter manuellement les corrections hors dossier `etm/`
|
||||
3. Ne jamais écraser `etm/`
|
||||
Lis AGENTS.md — il fait autorité sur ce repo.
|
||||
|
||||
291
docs/OPTIMIZER_PROTOCOL.md
Normal file
291
docs/OPTIMIZER_PROTOCOL.md
Normal file
@ -0,0 +1,291 @@
|
||||
# OPTIMIZER_PROTOCOL — v1 (draft)
|
||||
|
||||
Contrat entre le moteur d'énergie **etm-powersync-energy-plugin-etm** (couche A, GPL-3.0)
|
||||
et un **optimiseur** externe (couche B : Héos, ou toute implémentation tierce).
|
||||
|
||||
Ce document fait autorité. Il sert trois usages :
|
||||
1. Interface interne entre l'arbitrage central et la stratégie par défaut (rule-based).
|
||||
2. Spécification du protocole socket pour un optimiseur en process séparé.
|
||||
3. Documentation publique : n'importe qui peut implémenter son propre optimiseur.
|
||||
|
||||
Statut : **draft v1** — figé à la fin de la beta. Versionné en semver (`protocolVersion`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Philosophie
|
||||
|
||||
- **Le plugin ne fait pas confiance à l'optimiseur.** Tout plan reçu est écrêté par les
|
||||
bornes de sécurité du plugin (limites par charge, protection de surcharge, anti-flapping).
|
||||
Un optimiseur bugué ou malveillant ne peut produire que des choix sous-optimaux *dans
|
||||
des limites sûres* — jamais un état dangereux.
|
||||
- **La boucle de sécurité n'est pas négociable.** `verifyOverloadProtection()` (temps réel,
|
||||
sur `powerBalanceChanged`) s'exécute indépendamment et prime sur toute consigne.
|
||||
- **Le système fonctionne sans optimiseur.** Optimiseur absent, mort ou abstentionniste →
|
||||
repli automatique sur la stratégie rule-based interne (GPL). La maison reste pilotée.
|
||||
- **Toute décision est expliquée.** Chaque action porte un `reason` lisible (français),
|
||||
non vide. C'est une exigence du contrat, pas une option.
|
||||
|
||||
## 2. Topologie et transport
|
||||
|
||||
- **Le plugin est le CLIENT. L'optimiseur est le SERVEUR.** Le plugin se connecte à un
|
||||
endpoint configuré et demande des plans. La box n'ouvre aucun port.
|
||||
- Endpoint configurable :
|
||||
- `unix:///run/powersync/optimizer.sock` — défaut. Optimiseur local (Héos).
|
||||
Droits : socket possédé par `root:powersync`, mode `0660`.
|
||||
- `tcp://<host>:<port>` — optimiseur sur le LAN (NAS, serveur). Pour les
|
||||
implémentations communautaires (ex. pont Akkudoktor-EOS, annexe B).
|
||||
- Protocole applicatif : **JSON-RPC 2.0, un message par ligne (NDJSON), UTF-8.**
|
||||
- Le contrat est transport-agnostique : mêmes messages sur unix et tcp.
|
||||
- Reconnexion : backoff exponentiel (1 s → 60 s max). Pendant la déconnexion :
|
||||
stratégie de repli active, `optimizerAlive = false`.
|
||||
|
||||
## 3. Handshake
|
||||
|
||||
À chaque (re)connexion, le plugin envoie :
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","id":1,"method":"Handshake","params":{
|
||||
"protocolVersion":"1.0",
|
||||
"engine":"etm-powersync-energy-plugin-etm/<version>",
|
||||
"token":"<secret partagé, optionnel — requis si configuré côté plugin>"
|
||||
}}
|
||||
```
|
||||
|
||||
Réponse attendue :
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","id":1,"result":{
|
||||
"protocolVersion":"1.0",
|
||||
"name":"heos",
|
||||
"planning":true
|
||||
}}
|
||||
```
|
||||
|
||||
- `protocolVersion` : même **majeure** requise. Majeure différente → le plugin refuse,
|
||||
log explicite, repli rule-based.
|
||||
- `planning` : `true` si l'optimiseur produit des plans multi-créneaux ; `false` s'il
|
||||
ne répond qu'au présent (plan à un créneau). Informationnel.
|
||||
- `token` : si configuré côté plugin et absent/incorrect côté serveur → connexion fermée.
|
||||
Recommandé pour tout endpoint `tcp://`.
|
||||
- Handshake invalide ou timeout (5 s) → repli, nouvelle tentative au backoff.
|
||||
|
||||
## 4. Cycle et heartbeat
|
||||
|
||||
- Le plugin appelle `GetPlan` à chaque cycle d'optimisation (~1/min) **et** sur
|
||||
événement significatif (charge branchée/débranchée, changement de config).
|
||||
- **Le heartbeat est implicite** : une réponse valide à `GetPlan` = optimiseur vivant.
|
||||
Deux cycles consécutifs sans réponse valide (timeout par requête : 10 s) →
|
||||
`optimizerAlive = false`, repli rule-based, notification `Ems.CapabilitiesChanged`.
|
||||
- L'optimiseur peut être lent à *calculer* (EOS : minutes) — il doit alors répondre
|
||||
vite avec son **dernier plan en cache** ou `abstain`, et recalculer en tâche de fond.
|
||||
Une requête n'attend jamais un calcul long.
|
||||
|
||||
## 5. Requête : `GetPlan(SurplusContext)`
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","id":42,"method":"GetPlan","params":{
|
||||
"timestamp":"2026-06-07T13:42:00+02:00",
|
||||
"site":{
|
||||
"contractedPowerW":12000,
|
||||
"gridCapW":null,
|
||||
"phaseLimitA":[63,63,63]
|
||||
},
|
||||
"meter":{"importW":0,"exportW":2310,"perPhaseA":[3.2,4.1,2.8]},
|
||||
"pv":{"currentW":3400},
|
||||
"battery":{
|
||||
"present":true,"socPercent":62,"powerW":-800,
|
||||
"capacityWh":10000,"reserveSocPercent":15,
|
||||
"maxChargeW":5000,"maxDischargeW":5000
|
||||
},
|
||||
"loads":[
|
||||
{
|
||||
"id":"ecs-1","adapter":"relay-stages","label":"Chauffe-eau",
|
||||
"declared":{"nominalPowerW":2400,"stages":[0,1200,2400]},
|
||||
"defaults":{"dailyEnergyWh":7000},
|
||||
"learned":{"dailyEnergyWh":6400,"confidence":0.35},
|
||||
"telemetry":{"state":1,"currentPowerW":1200,"lastSwitch":"2026-06-07T13:10:00+02:00"},
|
||||
"needs":{"dailyDeadline":"18:00","minEnergyWhPerDay":4000},
|
||||
"priority":2,
|
||||
"limits":{"minOnS":300,"minOffS":300}
|
||||
},
|
||||
{
|
||||
"id":"ev-1","adapter":"evcharger","label":"Borne garage",
|
||||
"declared":{"minA":6,"maxA":16,"phases":3},
|
||||
"telemetry":{"pluggedIn":true,"charging":true,"currentPowerW":4100,"sessionWh":5200},
|
||||
"needs":{"targetSocPercent":80,"deadline":"2026-06-08T07:00:00+02:00"},
|
||||
"priority":1,
|
||||
"limits":{"chargingEnabledLockS":300,"currentChangeLockS":60}
|
||||
},
|
||||
{
|
||||
"id":"pac-1","adapter":"sg-ready","label":"PAC",
|
||||
"declared":{"states":[1,2,3,4],"estimatedPowerW":{"3":1800,"4":2600}},
|
||||
"telemetry":{"state":2},
|
||||
"priority":3,
|
||||
"limits":{"minStateHoldS":900}
|
||||
},
|
||||
{
|
||||
"id":"bat-1","adapter":"battery","label":"Batterie Fronius",
|
||||
"priority":4
|
||||
}
|
||||
],
|
||||
"forecast":{ "...":"objet Contract A (openmeteo) transmis tel quel, ou null" },
|
||||
"tariff":{
|
||||
"provider":"tempo","current":{"label":"HP_BLEU","priceCtkWh":16.1},
|
||||
"next":[{"from":"2026-06-08T06:00","label":"ROUGE","priceCtkWh":75.6}]
|
||||
}
|
||||
}}
|
||||
```
|
||||
|
||||
Notes de schéma :
|
||||
- `loads[].declared` : saisi par l'installateur (plaque signalétique, câblage). Fiable.
|
||||
- `loads[].defaults` : valeurs approximatives par catégorie. Point de départ.
|
||||
- `loads[].learned` : estimations de l'optimiseur, **renvoyées** au plugin pour
|
||||
persistance et affichage, avec `confidence` (0–1). Le rule-based les ignore.
|
||||
- `loads[].priority` : rang dans la liste ordonnée définie par le client (1 = premier
|
||||
servi). Voir annexe C.
|
||||
- `loads[].limits` : verrous anti-flapping. Le plugin LES APPLIQUE quoi qu'il arrive ;
|
||||
ils sont transmis pour que l'optimiseur planifie intelligemment, pas pour qu'il
|
||||
les fasse respecter.
|
||||
- `site.gridCapW` : réservé (signaux réseau type §14a EnWG). `null` en France v1.
|
||||
- `forecast` : contenu défini par `CONTRACT_A_openmeteo.md`. Peut être `null`
|
||||
(l'optimiseur peut sourcer ses propres prévisions — il est libre).
|
||||
|
||||
## 6. Réponse : plan par créneaux
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","id":42,"result":{
|
||||
"planId":"2026-06-07T13:42:00Z-a1",
|
||||
"strategy":"heos-predict",
|
||||
"slots":[
|
||||
{
|
||||
"from":"2026-06-07T13:42:00+02:00","to":"2026-06-07T14:00:00+02:00",
|
||||
"actions":[
|
||||
{"loadId":"ev-1","kind":"setpoint","currentA":10,
|
||||
"reason":"Surplus PV 2,3 kW → VE prioritaire (cible 80% à 07:00)"},
|
||||
{"loadId":"ecs-1","kind":"stage","stage":1,
|
||||
"reason":"Reliquat 0,9 kW → ECS palier 1 (besoin estimé 6,4 kWh/j, en apprentissage)"},
|
||||
{"loadId":"bat-1","kind":"constraint","charge":"allow","discharge":"forbid",
|
||||
"reason":"Jour ROUGE demain : décharge bloquée, on préserve le SoC"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"from":"2026-06-07T22:00:00+02:00","to":"2026-06-08T06:00:00+02:00",
|
||||
"actions":[
|
||||
{"loadId":"bat-1","kind":"setpoint","powerW":3000,"source":"grid",
|
||||
"reason":"Charge réseau HC avant jour ROUGE (plafond 3 kW : abonnement 12 kVA)"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}}
|
||||
```
|
||||
|
||||
Règles d'exécution côté plugin :
|
||||
- **Seul le créneau couvrant `now` est exécuté.** Le reste du plan est informatif
|
||||
(affichage app : « prévision programmée ») et sera re-demandé au cycle suivant.
|
||||
- Plan vide ou sans créneau courant = abstention (voir §7).
|
||||
- Toute action est **écrêtée** : bornes min/max de l'adaptateur, verrous temporels,
|
||||
protection de surcharge. Une action écrêtée est exécutée au plus proche possible
|
||||
et loggée (`clamped`).
|
||||
- `reason` absent ou vide → action **rejetée**, log d'avertissement. (Exigence
|
||||
decisionReason.)
|
||||
- Un rule-based interne répond au même contrat avec un plan à un seul créneau.
|
||||
|
||||
### Types d'action (`kind`)
|
||||
|
||||
| `kind` | Charges | Champs | Exemple |
|
||||
|---|---|---|---|
|
||||
| `setpoint` | evcharger, battery | `currentA` ou `powerW` (+ `source:"grid"` pour charge réseau batterie) | borne à 10 A ; batterie +3000 W depuis réseau |
|
||||
| `stage` | relay-stages (ECS…) | `stage` (entier, parmi `declared.stages`) | ECS palier 1 (1200 W) |
|
||||
| `state` | sg-ready | `state` (1=verrouillage, 2=normal, 3=recommandation, 4=forcé) | PAC en état 3 |
|
||||
| `constraint` | battery (v1) | `charge`/`discharge` : `allow`\|`forbid` | décharge interdite |
|
||||
|
||||
## 7. Abstention et repli
|
||||
|
||||
L'optimiseur peut répondre :
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","id":42,"result":{"abstain":true,"reason":"recalcul en cours"}}
|
||||
```
|
||||
|
||||
Sémantique : « je n'ai pas de plan à proposer maintenant ». Le plugin bascule sur la
|
||||
stratégie rule-based **pour ce cycle**, sans considérer l'optimiseur comme mort
|
||||
(`optimizerAlive` reste `true`). C'est le comportement attendu pendant un recalcul,
|
||||
un démarrage à froid, ou une absence de données.
|
||||
|
||||
Tableau de repli complet :
|
||||
|
||||
| Situation | optimizerAlive | Stratégie active |
|
||||
|---|---|---|
|
||||
| Réponse plan valide | true | optimiseur |
|
||||
| `abstain` | true | rules (ce cycle) |
|
||||
| Timeout / erreur / plan invalide ×2 | false | rules |
|
||||
| Socket absent / fermé | false | rules |
|
||||
| `optimizerExpected=false` (tier Community) | false | rules |
|
||||
|
||||
L'objet capabilities exposé à l'app (`tier`, `optimizerExpected`, `optimizerAlive`,
|
||||
`activeStrategy`) reflète cet état en continu ; tout changement émet une notification.
|
||||
|
||||
## 8. Apprentissage et transparence
|
||||
|
||||
- L'optimiseur peut renvoyer ses estimations (`learned` + `confidence`) dans une
|
||||
notification `UpdateLearned` (sans id, fire-and-forget) ; le plugin les persiste
|
||||
et les retransmet dans les contextes suivants.
|
||||
- Tant que `confidence < 0.7` (seuil indicatif), les `reason` concernés mentionnent
|
||||
l'apprentissage (« besoin estimé …, en apprentissage ») et l'app peut afficher un
|
||||
badge « profil en apprentissage » sur la charge.
|
||||
- Les données d'apprentissage restent **locales** (buffer circulaire, ~2 mois,
|
||||
écrasement). Rien ne quitte le site. C'est un engagement produit, pas un détail.
|
||||
|
||||
## 9. Versionnement
|
||||
|
||||
- `protocolVersion` en semver. Même majeure = compatible.
|
||||
- Champs inconnus dans `params`/`result` : **ignorés silencieusement** (tolérance
|
||||
ascendante). Ajouts mineurs = nouveaux champs optionnels.
|
||||
- Changements incompatibles (renommage, sémantique) = majeure + entrée changelog
|
||||
dans ce fichier.
|
||||
|
||||
---
|
||||
|
||||
## Annexe A — Session minimale
|
||||
|
||||
```
|
||||
→ {"jsonrpc":"2.0","id":1,"method":"Handshake","params":{"protocolVersion":"1.0","engine":"etm/0.9"}}
|
||||
← {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"1.0","name":"mon-optimiseur","planning":false}}
|
||||
→ {"jsonrpc":"2.0","id":2,"method":"GetPlan","params":{ ...SurplusContext... }}
|
||||
← {"jsonrpc":"2.0","id":2,"result":{"planId":"x","strategy":"custom","slots":[{ "from":"...","to":"...","actions":[...] }]}}
|
||||
```
|
||||
|
||||
Un optimiseur conforme minimal tient en ~50 lignes de Python : répondre au handshake,
|
||||
renvoyer un plan à un créneau (ou `abstain`). C'est le point d'entrée communautaire.
|
||||
|
||||
## Annexe B — Exemple : brancher Akkudoktor-EOS
|
||||
|
||||
EOS (github.com/Akkudoktor-EOS/EOS) est un optimiseur-planificateur HTTP (Python,
|
||||
algorithme génétique, horizon 48 h). Intégration par un **pont** (~200 lignes) tournant
|
||||
où l'on veut sur le LAN :
|
||||
|
||||
```
|
||||
plugin (client) ──tcp://nas:7600──► eos-bridge ──HTTP──► serveur EOS
|
||||
```
|
||||
|
||||
Le pont : (1) répond au Handshake ; (2) traduit `SurplusContext` → payload EOS
|
||||
(séries horaires PV/conso/prix — il peut compléter avec les prévisions Akkudoktor) ;
|
||||
(3) traduit le plan EOS → `slots[]`/`LoadAction` ; (4) sert le plan en cache et
|
||||
recalcule en arrière-plan (EOS est lent, le protocole exige des réponses rapides) ;
|
||||
(5) répond `abstain` pendant un recalcul initial.
|
||||
|
||||
Les bornes de sécurité du plugin s'appliquent intégralement : un plan EOS aberrant
|
||||
est écrêté, pas exécuté aveuglément.
|
||||
|
||||
## Annexe C — Priorités : modèle utilisateur
|
||||
|
||||
- **Liste ordonnée globale** (drag-and-drop dans l'app) : l'ordre de service du
|
||||
surplus. Le rule-based l'exécute en cascade (waterfall) ; un planificateur s'en
|
||||
sert comme préférence quand tout ne peut pas être servi.
|
||||
- **Besoins par charge** (fiche équipement) : échéances (`deadline`,
|
||||
`dailyDeadline`), minimums (`minEnergyWhPerDay`), cible (`targetSocPercent`).
|
||||
- **Promotions conditionnelles** : une échéance qui approche ou un signal tarifaire
|
||||
(Tempo ROUGE) peut faire passer une charge devant la liste — toujours expliqué
|
||||
dans `reason`.
|
||||
|
||||
Pas de poids, pas de pourcentages : un ordre, des besoins, des exceptions expliquées.
|
||||
Loading…
x
Reference in New Issue
Block a user