Patrick Schurig 7709057335 [wip] 3c morceaux 0-2 compilés + plan 3c validé dans AGENTS.md
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>
2026-06-08 13:34:42 +02:00

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;
};