diff --git a/AGENTS.md b/AGENTS.md index ce00f0d..5f9e321 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,21 +1,117 @@ # 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). +vers un gestionnaire d'énergie complet (EV, ECS, PAC SG-Ready, batterie). - **Licence** : GPL-3.0 · **Miroir public** : OUI -- **Agent** : energy-etm · **Branche** : feature/beta-rulebased · **Scope** : energyplugin/ +- **Branche de travail** : `feature/beta-rulebased` +- **Document d'interface faisant autorité** : `docs/OPTIMIZER_PROTOCOL.md` (le contrat + stratégie/arbitrage — interne ET socket). `INTERFACE.md` fait autorité sur l'API JSON-RPC. -## 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. +> ⚠️ Tout plan antérieur mentionnant « créer etm/ avec PowerSyncClient et +> StaticHcHpProvider comme première étape » ou « injecter l'optimiseur dans +> SmartChargingManager » est **INVALIDE et ABANDONNÉ**. Ne pas le reprendre, +> quelle qu'en soit la source (fichier, mémoire de session, contexte). -## 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`. +## ARCHITECTURE CIBLE (non négociable) + +``` + ┌──────────────────────────────┐ + │ ARBITRAGE CENTRAL │ ← généralisation du + │ budget de surplus UNIQUE │ SmartChargingManager amont + │ waterfall par priorités │ + └──────┬───────────────────────┘ + │ IScheduler (= contrat OPTIMIZER_PROTOCOL) + ┌───────────┴───────────┐ + RuleBasedScheduler SocketScheduler + (in-process, V1, GPL) (client unix://|tcp://, V1 aussi — + plan à 1 créneau personne en face en beta : repli rules) + │ + │ distribue le budget en LoadAction typées + ┌──────────┬────┴─────┬──────────────┐ + EvAdapter EcsRelayAdapter SgReadyAdapter BatteryAdapter + (setpoint, (stage 0/1/2) (state 1-4) (constraint + + iface setpoint W réseau) + evcharger + nymea) +``` + +Règles absolues : +1. **UN seul arbitre.** Le budget de surplus est une ressource unique, arbitrée à UN + endroit. **INTERDIT : managers frères par type de charge** (EcsManager, + BatteryManager à côté du SmartChargingManager) — deux décideurs sur le même surplus + = sur-engagement et oscillations. +2. **Les LoadAdapters exécutent, ils ne décident pas.** Un adaptateur : parle à son + matériel, déclare ses capacités/contraintes (`declared`, `limits`, types d'action), + expose sa télémétrie, applique les `LoadAction` reçues. Aucune logique de + répartition dedans. +3. **Le SmartChargingManager amont est EV-spécifique** : il se GÉNÉRALISE en arbitrage + multi-charges (`ChargingAction` → `LoadAction`, bornes EV → adaptateurs). On ne + branche PAS l'optimiseur dans le manager EV tel quel. +4. **La boucle de sécurité est intouchable** : `verifyOverloadProtection()` (temps + réel) + bornes par adaptateur écrêtent TOUTE sortie de stratégie, interne ou socket. +5. **Plan par créneaux** (OPTIMIZER_PROTOCOL §6) : seul le créneau courant est exécuté. + Le rule-based répond un plan à 1 créneau. Modèle async = **cache** : le plan du + cycle précédent s'applique, le recalcul se fait en fond. Jamais d'attente dans + `update()`. +6. **Repli toujours fonctionnel** : optimiseur absent/mort/abstain → rule-based. + Capabilities (`tier`, `optimizerExpected`, `optimizerAlive`, `activeStrategy`) + reflètent l'état en continu. +7. **`decisionReason` non vide, en français, sur chaque action.** Action sans reason + = rejetée. +8. **Pas de boucle de feedback** : surplus = PV mesurée + compteur, jamais le net + après pilotage. +9. **Aucun composant propriétaire ici** (Héos = repo privé `etm-powersync-optimizer`). + Ce repo doit compiler et tourner seul, GPL pur. + +## RÉPONSES FIGÉES (ne plus poser ces questions) + +- Plages HC/HP et tarifs : **configuration JSON**, jamais hardcodé. Prévoir Tempo + (6 types de jours), pas seulement HC/HP. +- Async : **modèle cache** (cf. règle 5). +- Bugs upstream : **commits séparés** du code ETM, message préfixé `[upstream-fix]`. + Candidats PR nymea (fix phases EV, Keba) = patchs isolés, propres, upstreamables. +- `protocolVersion` : **constante `"1.0"`**, pas un paramètre de config. +- Renommage : FAIT (Phase 1, commit f4d5b20). TARGET et noms de paquets debian + INCHANGÉS (.so drop-in remplaçant l.amont — garantit un seul plugin énergie chargé). + +## WORKFLOW OBLIGATOIRE + +Chaque phase produit un livrable VALIDÉ PAR PATRICK avant la suivante. Jamais de code +avant validation du design de la phase. + +- **Phase 0 — Analyse (en cours)** : répondre par écrit, code lu à l'appui : + (a) quelles charges SmartChargingManager pilote-t-il (types manipulés) ; + (b) ChargingAction peut-il exprimer « ECS palier 1 » / « batterie décharge + interdite » — citer ses champs ; (c) avec des managers séparés, où vivrait le + budget unique. Zéro code, zéro plan d'implémentation. +- **Phase 1 — Renommage** : `git mv` du `.pro`, TARGET, debian/. Un commit, revue. +- **Phase 2 — Design de l'arbitrage généralisé** : interface `LoadAdapter` (méthodes, + ce qu'un adaptateur déclare), flux du budget, mapping `LoadAction`→adaptateurs, + où vit `IScheduler`. Texte + signatures, pas d'implémentation. Validation Patrick. +- **Phase 3 — Implémentation par étapes** (chacune : compile amd64 + cross arm64, + et un scénario `docker-simulation.sh` qui la prouve = DoD) : + 3a. structs du protocole (contexte, plan, actions) ; + 3b. arbitre + RuleBasedScheduler + EvAdapter (iso-fonctionnel avec l'amont sur EV) ; + 3c. EcsRelayAdapter (paliers) ; 3d. SocketScheduler (handshake/heartbeat/repli, + testé contre un optimiseur factice ~50 lignes) ; 3e. SgReadyAdapter ; + 3f. BatteryAdapter (constraints + charge réseau plafonnée). +- **Bugs upstream** : au fil de l'eau, commits `[upstream-fix]` séparés. + +## DÉFINITION DE FAIT (par étape de phase 3) + +1. Compile amd64 et cross arm64. +2. Scénario de simulation ajouté/étendu qui démontre le comportement (le harnais + `docker-simulation.sh` + `tests/auto` hérités sont le banc de test). +3. `decisionReason` visibles dans les logs de simulation. +4. Aucune régression des tests amont existants. + +## RÉFÉRENCES + +- `docs/OPTIMIZER_PROTOCOL.md` — le contrat. §5 (SurplusContext), §6 (plan/actions), + §7 (repli), annexe C (priorités). +- `README.md` — architecture (deux boucles, frontière), `etm_powersync_energy.svg`. +- `INTERFACE.md` — API JSON-RPC existante (`NymeaEnergy`, cible future `Ems`). +- Carte globale du workspace : `../AGENTS.md`.