// SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync #pragma once #include #include "ischeduler.h" class EvCharger; class ChargingAction; class EnergyArbitrator; /*! * \brief Planificateur basé sur les règles GPL (EV surplus + aWATTar). * * **Rôle en beta (jusqu'à 3g)** : PROXY AMONT POUR L'EV — délègue toute la * planification EV à \c planSurplusCharging() et \c planSpotMarketCharging() * héritées de SmartChargingManager. Ces méthodes décident (acquisitionTolerance, * batteryLevelConsideration, waterfall, spot market) ; ce scheduler relit leurs * sorties (m_chargingActions) et les reformate en LoadAction annotées d'un * \c reason français — pour le log [Arbitre] uniquement. * * À partir de **3g** : la logique de surplus sera transplantée ici (waterfall * unifié sur toutes les charges, EV inclus). Voir AGENTS.md § "3b révisé". * * \invariant getPlan() retourne IMMÉDIATEMENT (AGENTS invariant 5). * \invariant getPlan() retourne toujours un Plan valide (isValid() == true). * \invariant Toute LoadAction a un \c reason non vide, en français. * \invariant Priorité : Deadline VE > Surplus PV > aWATTar > Min courant > Idle. * Identique à adjustEvChargers() amont — iso-fonctionnel 3b. */ class RuleBasedScheduler : public QObject, public IScheduler { Q_OBJECT public: /*! * \brief Constructeur. * \param arbitrator Arbitre propriétaire — fournit l'accès à la planification et à l'état. * \param parent Propriétaire Qt. */ explicit RuleBasedScheduler(EnergyArbitrator *arbitrator, QObject *parent = nullptr); /*! * \brief Retourne le plan pour le slot courant. * * **Beta (proxy)** : appelle \c runSpotMarketPlanning() puis \c runSurplusPlanning() * (qui écrivent dans m_chargingActions via les méthodes parent), lit le résultat * via \c scheduledActions(), et construit les LoadAction correspondantes. * Les LoadAction servent uniquement au log [Arbitre] — le dispatch réel reste dans * \c adjustEvChargers() amont. * * \param ctx SurplusContext courant. En 3b : seul \c ctx.timestamp est utilisé. * \return Plan à 1 créneau couvrant \c ctx.timestamp + 60 s. */ Plan getPlan(const SurplusContext &ctx) override; private: /*! * \brief Construit un LoadAction pour le cas "délai requis" (TimeRequirement). * \param ev EvCharger concerné. * \param ca ChargingAction planifiée (courant et phases déjà calculés par planSurplusCharging). * \return LoadAction avec funding=Grid et reason "Deadline VE". */ LoadAction buildTimeRequirementAction(EvCharger *ev, const ChargingAction &ca) const; /*! * \brief Construit un LoadAction "courant minimum" pour les modes EcoMin. * \param ev EvCharger concerné. * \return LoadAction avec funding=Surplus, chargingEnabled=true, currentA=min. */ LoadAction buildMinCurrentAction(EvCharger *ev) const; /*! * \brief Construit un LoadAction "idle" (recharge désactivée, aucun surplus). * \param ev EvCharger concerné. * \return LoadAction avec chargingEnabled=false et reason appropriée. */ LoadAction buildIdleAction(EvCharger *ev) const; /*! * \brief Construit un LoadAction "stage" ECS par cascade de surplus (waterfall §6). * * Retient le palier déclaré le plus haut dont la puissance tient dans le budget courant. * Correction B (anti-clignotement) : recrédite d'abord \c lc.telemetry.currentPowerW au * budget (la conso actuelle de l'ECS est déjà soustraite de l'export mesuré), puis * décrémente \c remainingSurplusW de la puissance du palier retenu. * * \param lc Charge ECS (adapter == "relay-stages") du SurplusContext. * \param[in,out] remainingSurplusW Budget de surplus restant (W) ; mis à jour pour la * charge ECS suivante (priorité inférieure / rang supérieur). * \return LoadAction kind=Stage, funding=Surplus, \c reason français non vide. */ LoadAction buildEcsStageAction(const LoadContext &lc, double &remainingSurplusW) const; EnergyArbitrator *m_arbitrator; };