12 KiB
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 (EV, ECS, PAC SG-Ready, batterie).
- Licence : GPL-3.0 · Miroir public : OUI
- Branche de travail :
feature/beta-rulebased - Document d'interface faisant autorité :
docs/OPTIMIZER_PROTOCOL.md(le contrat stratégie/arbitrage — interne ET socket).INTERFACE.mdfait autorité sur l'API JSON-RPC.
ÉTAT
| Phase | Statut | Commit(s) |
|---|---|---|
| 0 — analyse fork / structure | ✅ FAITE | f4d5b20 |
| 1 — renommage .pro + métadonnées debian | ✅ FAITE | f4d5b20 |
| 2 — design arbitre validé | ✅ FAITE | 074fa71 |
| 3a — structs protocole + interfaces | ✅ FAITE | 4ae1939 |
| 3b — EnergyArbitrator + scheduler + adapter | ⏳ CODE COMMITÉ, iso-fonctionnalité non prouvée | 5f49e4c |
Détail 3b (5f49e4c) :
EnergyArbitrator : public SmartChargingManager— justification dans## DÉCISIONS DE DESIGNEvAdapter+RuleBasedSchedulerimplémentés- Build : 0 erreur / 0 warning
ETM_ARBITRATORcommenté dans.pro— flip inactif jusqu'à preuve iso
PROCHAINE ACTION — 3b-iv :
- Décommenter
DEFINES += ETM_ARBITRATORdansenergyplugin.pro - Lancer
docker-simulation.sh - Vérifier les
decisionReasondans les logs ([Arbitre] <thingId> → <reason>) et comparer le comportement EV avec l'amont sur les mêmes scénarios - Preuve iso-fonctionnelle validée → commit d'activation
Remotes git :
origin(https://git.etm-powersync.fr/...) = remote de travail — push normaletm-public(gitea-lan:...powersync-energy-plugin-etm) = miroir public GPL → push MANUEL par Patrick uniquement (sync-public.sh)etm-pro= reliquat historique — ne pas utiliser, cartographie à clarifier
⚠️ 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).
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 :
- 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.
- 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 lesLoadActionreçues. Aucune logique de répartition dedans. - 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. - La boucle de sécurité est intouchable :
verifyOverloadProtection()(temps réel) + bornes par adaptateur écrêtent TOUTE sortie de stratégie, interne ou socket. - 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(). - Repli toujours fonctionnel : optimiseur absent/mort/abstain → rule-based.
Capabilities (
tier,optimizerExpected,optimizerAlive,activeStrategy) reflètent l'état en continu. decisionReasonnon vide, en français, sur chaque action. Action sans reason = rejetée.- Pas de boucle de feedback : surplus = PV mesurée + compteur, jamais le net après pilotage.
- 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 mvdu.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, mappingLoadAction→adaptateurs, où vitIScheduler. 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.shqui 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ÉCISIONS DE DESIGN (écarts et justifications)
3b révisé — délégation EV à l'amont (beta assumée)
Décision Patrick : hybride étagé pour la beta.
En beta : les décisions EV restent dans les méthodes amont
planSurplusCharging / planSpotMarketCharging (SmartChargingManager), inchangées.
RuleBasedScheduler::getPlan() les appelle en proxy et reformate leurs sorties
(ChargingActions) en LoadAction pour le log [Arbitre].
EvAdapter::applyAction() est inactif jusqu'à 3g — mais descriptor() et
telemetry() sont utilisés dès maintenant pour le SurplusContext.
Pipeline ETM réel (waterfall budget Surplus/Grid, applyAction) arrive en 3c
pour les charges non-EV (ECS, SG-Ready), alimenté par le surplus restant après
déduction de l'addedPower des consignes EV du cycle courant (pas encore visible
au compteur).
Limitations beta assumées :
- EV toujours prioritaire ; waterfall appliqué uniquement aux charges non-EV.
- Le classement drag-and-drop (priorités) ne portera que sur les charges non-EV.
Étape 3g (post-beta) : transplantation réelle de la logique EV dans
RuleBasedScheduler → priorités libres entre toutes les charges (EV, ECS, SG-Ready,
batterie).
3b-iii — EnergyArbitrator hérite de SmartChargingManager
Design validé en session : "nouvelle classe dans etm/, n'étend pas SmartChargingManager".
Écart implémenté : EnergyArbitrator : public SmartChargingManager.
Justification :
-
Contrainte NymeaEnergyJsonHandler : ce handler amont prend un
SmartChargingManager*dans son constructeur.
Sans héritage, toute solution propre (interface commune, pointeur générique) nécessiterait de modifiernymeaenergyjsonhandler.h/.cpp— violation de la règle "Modifier le code amont uniquement pour corriger des bugs". -
verifyOverloadProtection() intacte : héritée bit-pour-bit, connectée aux mêmes signaux via le constructeur du parent. Zéro risque de régression sur la sécurité.
-
simulationCallUpdate() polymorphe : appelle
update()virtuel → redirige automatiquement versEnergyArbitrator::update(). Les tests amont passent sans modification. -
Minimal upstream diff : seuls les attributs
protected/virtualchangent danssmartchargingmanager.h(marqués// [ETM]). Zéro logique upstream modifiée.
Risque accepté : EnergyArbitrator a accès à l'état privé de SCM via les
accesseurs internal*. La discipline AGENTS (LoadAdapters exécutent, ne décident pas ;
un seul arbitre) compense. Si SCM était refactorisé en amont pour exposer une interface
publique propre, l'héritage pourrait être remplacé par composition.
MODÈLE DE SÉCURITÉ (décision Patrick — immuable)
Cinq couches indépendantes. Chacune est conçue pour qu'une défaillance des couches
supérieures n'affecte pas les couches inférieures. Voir docs/SAFETY.md pour le détail.
| Couche | Qui | Quoi |
|---|---|---|
| L0 | Disjoncteur / Linky matériel | Coupure physique — hors logiciel |
| L1 | Failsafe natif des bornes | Config installateur, checklist ETM |
| L2 | Watchdog fraîcheur compteur (à coder en 3c) | QTimer piloté : si lastMeterUpdate > 90 s → mode dégradé (EV min/off, ECS off, pas de charge réseau batterie), decisionReason explicite, notification nymea. Scénario simulation dédié : "compteur muet → repli". |
| L3 | Watchdog systemd sur nymead | Repo etm-powersync-deploy, hors scope ici |
| L4 | Logique signal-driven existante | Boucle update() déclenchée par événements |
Règles de code :
- Le watchdog L2 est piloté par
QTimer(pas par signalmeterChanged) pour rester actif même si le signal ne fire plus. - Mode dégradé = consignes de repli sur toutes les charges pilotées (pas d'arrêt
brutal) +
decisionReasonnon vide + notificationEnergyManagerChanged. verifyOverloadProtection()(L4) reste intouchable et appelée avant toute planification.
DÉFINITION DE FAIT (par étape de phase 3)
- Compile amd64 et cross arm64.
- Scénario de simulation ajouté/étendu qui démontre le comportement (le harnais
docker-simulation.sh+tests/autohérités sont le banc de test). decisionReasonvisibles dans les logs de simulation.- Aucune régression des tests amont existants.
- Toute classe/méthode publique de
etm/porte un commentaire Doxygen :\brief,\param,\return, et surtout le contrat de comportement (invariants, écrêtage, hypothèses que l'appelant peut faire). Les headers 3a servent de modèle — les convertir au format Doxygen lors du passage 3b.
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 futureEms).- Carte globale du workspace :
../AGENTS.md.