// SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync #pragma once #include #include #include "iloadadapter.h" class EvCharger; class EnergyArbitrator; /*! * \brief Adaptateur pour une borne de recharge VE (interface evcharger nymea). * * Traduit les LoadAction(Setpoint) en appels matériels via * EnergyArbitrator::doExecuteChargingAction() — le seul chemin d'exécution. * * \invariant applyAction() rejette silencieusement toute LoadAction dont \c reason est vide. * \invariant currentA est écrêté à [maxChargingCurrentMinValue, maxChargingCurrentMaxValue]. * \invariant phaseCount est écrêté selon canSetPhaseCount() et phaseCount() du EvCharger. * \invariant Les kinds autres que Setpoint sont retournés sans effet. */ class EvAdapter : public QObject, public ILoadAdapter { Q_OBJECT public: /*! * \brief Constructeur. * \param evCharger Borne VE à piloter (doit rester valide tant que l'adaptateur existe). * \param parent EnergyArbitrator propriétaire — utilisé pour l'exécution matérielle. */ explicit EvAdapter(EvCharger *evCharger, EnergyArbitrator *parent); /*! * \brief Retourne la description statique de la charge. * \return LoadDescriptor construit depuis les capacités actuelles du EvCharger. * \note Recalculé à chaque appel depuis l'état nymea du Thing. */ LoadDescriptor descriptor() const override; /*! * \brief Retourne la télémétrie runtime (puissance mesurée, disponibilité). * \return LoadTelemetry avec currentPowerW, available et lastActionAt. */ LoadTelemetry telemetry() const override; /*! * \brief Construit l'entrée loads[] §5 du SurplusContext. * \param now Temps de cycle (\c ctx.timestamp). Inutilisé ici : l'EV n'a pas de verrou * de palier (hors waterfall ECS/SG-Ready). Présent pour l'uniformité de \c ILoadAdapter. * \return LoadContext incluant declared, limits, needs et télémétrie EV. */ LoadContext toLoadContext(const QDateTime &now) const override; /*! * \brief Applique une consigne Setpoint sur la borne VE. * * **Inactif jusqu'à 3g** : non appelée par \c EnergyArbitrator::update() en beta. * Le dispatch EV passe par \c adjustEvChargers() amont (hérité). Cette méthode * sera câblée lors de la transplantation EV dans \c RuleBasedScheduler (phase 3g). * \c descriptor() et \c telemetry() sont eux actifs dès maintenant pour le SurplusContext. * * \param action LoadAction de kind Setpoint. Autres kinds : retour sans effet. * \param now Temps de cycle (\c ctx.timestamp) — passé à \c doExecuteChargingAction() * (locks anti-rebond de la borne). MÊME source que toLoadContext() (contrat ILoadAdapter). * \return L'action après écrêtage matériel (currentA, phaseCount bornés). * * \invariant action.reason non vide requis — log warning et retour sans effet sinon. * \invariant currentA écrêté à [minValue, maxValue] avant envoi à executeChargingAction. * \invariant phaseCount ajusté selon canSetPhaseCount() du EvCharger. */ LoadAction applyAction(const LoadAction &action, const QDateTime &now) override; //! \brief Sans effet : l'EV n'est pas construit depuis \c LoadConfig (rang fixé à 100, //! cf. « Dette 3g »), et son nom d'affichage vient du Thing, pas de la config. //! \param label Ignoré. \param priority Ignoré. \param needs Ignoré. void updateSoftConfig(const QString &label, int priority, const LoadNeeds &needs) override { Q_UNUSED(label) Q_UNUSED(priority) Q_UNUSED(needs) } //! \brief Sans effet : l'EV n'implémente pas encore l'échelle de défaut ECS-410. //! La généralisation est portée par **ECS-414** (lot de mise en configuration //! du SgReadyAdapter). Déclaré explicitement plutôt qu'hérité d'un défaut vide. //! Aucun mécanisme de défaut côté EV : il n'y a jamais rien à lever. Le dire //! explicitement plutôt que de laisser un corps vide (règle 7-c). //! \return Vrai si un défaut a réellement été levé ; faux s'il n'y en avait pas. bool clearFault() override { return false; } /*! * \brief Vue runtime — déclarée, mais HORS de la charge utile \c GetLoadTelemetry. * * \param now Ignoré : les verrous EV (chargingEnabledLock, currentChangeLock) vivent dans * \c SmartChargingManager amont, pas ici. * \return \c mechanismKind "evcharger" et l'état de la borne. * * \note L'EV n'apparaît PAS dans \c NymeaEnergy.GetLoadTelemetry en beta : il est décidé * par l'amont EN AMONT du waterfall (décision B), sa puissance est réservée dans * \c PlanBudget::evReservedW et non allouée depuis le budget publié. L'y faire figurer * ferait une somme \c loads[] qui ne retomberait pas sur \c budget.allocatedW. La * méthode est néanmoins IMPLÉMENTÉE, et non laissée en \c Q_UNIMPLEMENTED : quand 3g * transplantera l'EV dans le waterfall unifié, il n'y aura rien à écrire ici. */ LoadRuntimeView runtimeView(const QDateTime &now) const override; /*! * \brief Mesure propre de la borne (§11 / LM-1105). * \return La puissance que la borne rapporte elle-même, quand sa ThingClass porte l'état. * \note Une borne est **traversée** par la puissance qu'elle délivre : sa mesure est bien * celle de la charge, et non celle d'un organe de commande. C'est le cas qui a motivé * LM-1105 — il fallait jusqu'ici désigner la borne comme son propre \c meterThingId * pour publier une valeur qu'elle donne d'elle-même. */ LoadMeasurement deviceMeasurement() const override; //! \brief Sans effet : l'EV n'est pas construit depuis \c LoadConfig et ne peut donc pas //! être « désactivé » par ce chemin. \param now Ignoré. void applySafeState(const QDateTime &now) override { Q_UNUSED(now) } /*! * \brief Borne VE sous-jacente (lecture). * \return Pointeur non-propriétaire vers l'EvCharger ; jamais nul (fixé à la construction). */ EvCharger *evCharger() const { return m_charger; } private: EvCharger *m_charger; EnergyArbitrator *m_parent; QDateTime m_lastActionAt; //! Nombre de phases VISÉ, décidé à la construction du contexte et réutilisé à //! l'exécution. Les deux doivent parler du MÊME, sinon le plancher publié ne décrit pas //! la borne qui sera réellement commandée (§12 / 3g-1). mutable uint m_targetPhases = 1; };