Patrick Schurig 5bb6da0e9f [3b-iv] ETM_ARBITRATOR actif — iso-fonctionnalité prouvée (simulation + tests charging, diff décisions zéro écart)
- energyplugin/energyplugin.pri : décommente DEFINES += ETM_ARBITRATOR (flip actif)
- energyplugin/etm/energyarbitrator.cpp : ajoute qCDebug "Updating smart charging" en tête
  de update() — comparabilité des logs avec l'amont garantie
- AGENTS.md : 3b →  FAITE, chiffres de preuve, prochaine action 3c

Preuve iso-fonctionnalité :
- Simulation : 226 lignes décisions (Theoretically/Surplus/Current load) — diff = 0
- Tests charging : 57 lignes décisions — diff = 0 ; 46/46 PASS ref ET ETM
- [Arbitre] présents avec raisons françaises (idle, surplus PV, aWATTar, deadline)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 11:29:34 +02:00

13 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.md fait 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 FAITE — iso-fonctionnalité prouvée 5f49e4c, d8ebd65, [3b-iv]

Détail 3b :

  • EnergyArbitrator : public SmartChargingManager — justification dans ## DÉCISIONS DE DESIGN
  • EvAdapter + RuleBasedScheduler implémentés
  • Build : 0 erreur / 0 warning
  • ETM_ARBITRATOR actif dans energyplugin.pri
  • Iso-fonctionnalité prouvée :
    • Simulation : 226 lignes décisions identiques (Theoretically / Surplus / Current load), diff = 0
    • Tests charging : 57 lignes décisions identiques, diff = 0 ; 46/46 PASS ref ET ETM
    • [Arbitre] présents avec raisons françaises pour les 4 cas (idle, surplus PV, aWATTar, deadline)

PROCHAINE ACTION — 3c :

  • EcsRelayAdapter (paliers 0/1/2) — premier adaptateur non-EV
  • Pipeline ETM waterfall (budget surplus → ECS) dans EnergyArbitrator::update()
  • Scénario simulation dédié : chauffe-eau sur surplus PV

Remotes git :

  • origin (https://git.etm-powersync.fr/...) = remote de travail — push normal
  • etm-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 :

  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 (ChargingActionLoadAction, 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É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 :

  1. 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 modifier nymeaenergyjsonhandler.h/.cpp — violation de la règle "Modifier le code amont uniquement pour corriger des bugs".

  2. 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é.

  3. simulationCallUpdate() polymorphe : appelle update() virtuel → redirige automatiquement vers EnergyArbitrator::update(). Les tests amont passent sans modification.

  4. Minimal upstream diff : seuls les attributs protected/virtual changent dans smartchargingmanager.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 signal meterChanged) pour rester actif même si le signal ne fire plus.
  • Mode dégradé = consignes de repli (EV au minimum si pluggedIn, ECS off, etc.)
    • decisionReason non vide + notification EnergyManagerChanged avec degradedMode.
  • verifyOverloadProtection() (L4) est déclenchée par deux mécanismes : (a) signal powerBalanceChanged (temps réel — SCM.cpp ligne 127, mécanisme principal) ; (b) appel cyclique en position 3 d'update() (SCM.cpp ligne 313, filet périodique). La position dans update() est INTOUCHABLE — même dans EnergyArbitrator::update().

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.
  5. 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 future Ems).
  • Carte globale du workspace : ../AGENTS.md.