// SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync #pragma once #include #include #include #include #include "iloadadapter.h" class Thing; class ThingManager; /*! * \brief Adaptateur pour chauffe-eau ou tout relais N paliers (interface relay-stages). * * Pilote en production N Things powerswitch nymea : \c m_relayMapping[stage] contient * la liste des ThingIds à mettre ON pour ce palier (les autres sont mis OFF). * * Exemple — chauffe-eau 2400W, 2 résistances Waveshare : * stage 0 : {} → A=OFF, B=OFF * stage 1 : {"thingId-A"} → A=ON, B=OFF (1200 W) * stage 2 : {"thingId-A", "thingId-B"} → A=ON, B=ON (2400 W) * * \invariant applyAction() rejette silencieusement toute action dont \c reason est vide. * \invariant applyAction() applique les verrous anti-rebond \c minOnS / \c minOffS * SAUF si \c action.force == true (réservé L2 watchdog). * \invariant Le stage est écrêté à [0, stages().size()-1] avant envoi matériel. * \invariant Seul le kind Stage est traité ; les autres kinds retournent sans effet. */ class EcsRelayAdapter : public QObject, public ILoadAdapter { Q_OBJECT public: /*! * \brief Constructeur. * \param thingManager Gestionnaire nymea pour résoudre les ThingIds en Things. * \param id Identifiant logique de la charge (ThingId de l'objet ECS dans nymea, * ou identifiant arbitraire unique pour le mock). * \param label Nom lisible affiché dans les logs et l'app. * \param stages Puissances en W par palier, index 0 = off : [0, 1200, 2400]. * \param relayMapping relayMapping[i] = liste de ThingIds powerswitch ON pour le palier i. * \param minOnS Durée minimale ON (s) — anti-rebond. * \param minOffS Durée minimale OFF (s) — anti-rebond. * \param priority Priorité dans le waterfall (200=deadline, 100=normal, 0=différable). * \param parent Propriétaire Qt. */ explicit EcsRelayAdapter(ThingManager *thingManager, const QString &id, const QString &label, const QList &stages, const QList> &relayMapping, int minOnS, int minOffS, int priority, QObject *parent = nullptr); /*! * \brief Description statique de la charge. * \return LoadDescriptor avec adapter="relay-stages", stages, minOnS/minOffS, priority. */ LoadDescriptor descriptor() const override; /*! * \brief Télémétrie runtime : puissance mesurée, stage courant, lastSwitch. * \return LoadTelemetry avec currentPowerW issu de la somme des Things actifs. */ LoadTelemetry telemetry() const override; /*! * \brief Construit l'entrée loads[] §5 du SurplusContext. * \return LoadContext incluant declared, limits et télémétrie ECS (stage, currentPowerW, lastSwitch). */ LoadContext toLoadContext() const override; /*! * \brief Applique un changement de palier sur les relais. * * \param action LoadAction de kind Stage. Autres kinds : retour sans effet. * \return L'action après écrêtage (stage borné à [0, stages.size()-1]). * * \invariant Si \c action.reason est vide → retour sans effet (log warning). * \invariant Si verrous anti-rebond actifs ET \c action.force == false → retour sans effet (log). * \invariant Si \c action.force == true → bypass verrous (L2 watchdog uniquement). * \invariant Toute modification de stage met à jour \c m_lastSwitch. */ LoadAction applyAction(const LoadAction &action) override; /*! \brief Stage courant (0 = off). */ int currentStage() const { return m_currentStage; } private: bool lockActive(int newStage) const; void applyRelayStage(int stage); ThingManager *m_thingManager; QString m_id; QString m_label; QList m_stages; //!< Puissances W par palier, [0]=off. QList> m_relayMapping; //!< ThingIds ON par palier. int m_minOnS; int m_minOffS; int m_priority; int m_currentStage = 0; QDateTime m_lastSwitch; //!< Dernier changement de palier (null = jamais). QDateTime m_lastActionAt; };