Patrick Schurig 5f49e4ca3c [3b-wip] EnergyArbitrator + RuleBasedScheduler + EvAdapter (dispatch amont, ETM_ARBITRATOR désactivé)
- EnergyArbitrator : public SmartChargingManager — raison documentée dans AGENTS.md §DÉCISIONS DE DESIGN
- SmartChargingManager : protected slots + virtual update() + 3 accesseurs inline [ETM]
- RuleBasedScheduler::getPlan() wraps planSurplusCharging/planSpotMarketCharging, annote chaque action d'un reason français
- EvAdapter : ILoadAdapter concret pour evcharger — applyAction() implémenté, NON appelé en 3b (dispatch via adjustEvChargers() amont, iso-fonctionnel)
- ETM_ARBITRATOR : commenté dans .pro — ne s'active qu'après preuve iso-fonctionnelle (3b-iv)
- Doxygen \brief + invariants + contrats sur toutes les classes/méthodes publiques etm/ (DoD §5)
- plan.h : timeSlots (pas slots, mot-clé Qt) ; commentaire JSON sérialisation "slots" OPTIMIZER_PROTOCOL §6
- .clangd : flags de repli Qt/nymea pour clangd via symlink ~/Schreibtisch/
- compile_commands.json gitignore (chemins absolus locaux)
- Build : 0 erreurs, 0 warnings — libnymea_energypluginnymea.so 914 KB

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 23:16:49 +02:00

8.4 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.

⚠️ 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-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.


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.