Bug : exportW clampé à max(0,-p) AVANT recrédit → sur-crédit en import (ECS restait allumé sur le réseau, ne délestait jamais). Fix : surplus net SIGNÉ (exportW - importW). Régime export inchangé. Le délestage strict est borné par minOn/minOff (protection compresseur, pas confort) : l'adaptateur expose minStage/maxStage (fenêtre de verrou évaluée au temps de cycle), le scheduler clampe bestStage et décrémente au palier réel → budget correct pour les charges suivantes (puissance verrouillée = engagée non-coupable). Seam de temps unifié : now=ctx.timestamp partagé par toLoadContext()/applyAction() ; lockWindow() est l'unique calcul, lockActive() en dérive (décision==exécution). Interface ILoadAdapter étendue (now) + contrat "temps=paramètre, jamais l'horloge" documenté pour les futurs adaptateurs. EvAdapter aligné. Build 0 erreur / 0 warning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
124 lines
5.6 KiB
C++
124 lines
5.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 Rang dans le waterfall (OPTIMIZER_PROTOCOL §5) : valeur plus BASSE
|
|
* = servi en premier (rang 1 = premier servi).
|
|
* \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.
|
|
* \param now Temps de cycle (\c ctx.timestamp) — source unique pour minStage/maxStage.
|
|
* \return LoadContext incluant declared, limits et télémétrie ECS (stage, currentPowerW,
|
|
* lastSwitch, et la fenêtre de verrou \c minStage/maxStage évaluée à \p now).
|
|
*/
|
|
LoadContext toLoadContext(const QDateTime &now) const override;
|
|
|
|
/*!
|
|
* \brief Applique un changement de palier sur les relais.
|
|
*
|
|
* \param action LoadAction de kind Stage. Autres kinds : retour sans effet.
|
|
* \param now Temps de cycle (\c ctx.timestamp) — MÊME source que toLoadContext(),
|
|
* utilisée pour l'évaluation des verrous et l'estampille \c m_lastSwitch.
|
|
* \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 (= \p now).
|
|
*/
|
|
LoadAction applyAction(const LoadAction &action, const QDateTime &now) override;
|
|
|
|
/*! \brief Stage courant (0 = off). */
|
|
int currentStage() const { return m_currentStage; }
|
|
|
|
private:
|
|
/*!
|
|
* \brief Fenêtre de paliers autorisée à l'instant \p now par les verrous minOn/minOff.
|
|
* \param now Temps de cycle.
|
|
* \param[out] minStage Plancher : palier courant si minOn non écoulé (puissance engagée
|
|
* non-coupable), sinon 0.
|
|
* \param[out] maxStage Plafond : 0 si à l'arrêt et minOff non écoulé (redémarrage interdit),
|
|
* sinon le palier le plus haut.
|
|
*/
|
|
void lockWindow(const QDateTime &now, int &minStage, int &maxStage) const;
|
|
|
|
bool lockActive(int newStage, const QDateTime &now) 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;
|
|
};
|