Morceaux 0-2 implémentés et compilés (0 erreur / 0 warning) : - M0 : LoadAction.force=false (bypass verrous anti-rebond sécurité) - M1 : EcsRelayAdapter (.h+.cpp) — N paliers powerswitch, anti-rebond, etm.pri - M2 : buildContext() — SurplusMeter brut, loads EV+ECS, registerEcsAdapter() AGENTS.md : section PLAN 3C ajoutée avec corrections A+B intégrées. Corrections A (déduction EV unique dans scheduler) et B (recrédit conso propre anti-clignotement) documentées avant implémentation morceau 3. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
109 lines
4.6 KiB
C++
109 lines
4.6 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
|
|
#pragma once
|
|
|
|
#include <QObject>
|
|
#include <QDateTime>
|
|
#include <QList>
|
|
#include <QString>
|
|
#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<int> &stages,
|
|
const QList<QList<QString>> &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<int> m_stages; //!< Puissances W par palier, [0]=off.
|
|
QList<QList<QString>> 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;
|
|
};
|