Patrick Schurig 5d67dc943d [3c-3-fix] waterfall ECS : surplus net signé + clamp lock-aware (protection compresseur)
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>
2026-06-09 21:25:22 +02:00

76 lines
3.6 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
#pragma once
#include <QDateTime>
#include "../types/loadaction.h"
#include "../types/loaddescriptor.h"
#include "../types/surpluscontext.h"
/*!
* \brief Vue runtime minimale exposée par un adaptateur à l'arbitre.
*/
struct LoadTelemetry {
double currentPowerW = 0; //!< Puissance mesurée (W).
bool available = true; //!< Faux si l'appareil nymea est absent ou en erreur.
QDateTime lastActionAt; //!< Dernier instant où applyAction() a produit un effet.
};
/*!
* \brief Interface pure des adaptateurs de charge.
*
* Les implémentations concrètes héritent de QObject + ILoadAdapter et déclarent leurs
* propres signaux (telemetryChanged, descriptorChanged).
*
* \invariant Les adaptateurs EXÉCUTENT, ils ne décident pas (AGENTS règle 2).
* \invariant applyAction() écrête les valeurs selon les limites matérielles réelles
* (second filet après l'écrêtage de l'arbitre).
* \invariant applyAction() avec \c reason vide doit être rejetée silencieusement.
* \invariant Les méthodes non-applyAction() retournent immédiatement (pas de I/O bloquant).
* \invariant **Temps = paramètre, jamais l'horloge.** Toute logique temporelle d'un
* adaptateur (verrous minOn/minOff, fenêtres, fraîcheur…) utilise EXCLUSIVEMENT le
* \c now (= \c ctx.timestamp) reçu en paramètre de \c toLoadContext()/applyAction().
* JAMAIS \c QDateTime::currentDateTime(). C'est cette source unique, partagée avec le
* scheduler, qui rend impossible toute divergence décision/exécution et qui rend la
* logique injectable en simulation. Contrat pour tout futur adaptateur (SgReady, Battery).
*/
class ILoadAdapter {
public:
virtual ~ILoadAdapter() = default;
/*!
* \brief Description statique de la charge : capacités, limites, priorité, needs.
* \return LoadDescriptor construit depuis la configuration matérielle.
* \note Peut être rappelé à chaque cycle — l'implémentation doit être légère.
*/
virtual LoadDescriptor descriptor() const = 0;
/*!
* \brief Télémétrie runtime (puissance, disponibilité, dernière action).
* \return LoadTelemetry issue de l'état courant de l'appareil nymea.
*/
virtual LoadTelemetry telemetry() const = 0;
/*!
* \brief Construit l'entrée §5 loads[] pour le SurplusContext.
* \param now Temps de cycle (\c ctx.timestamp). Source unique pour l'évaluation des
* verrous (minStage/maxStage) — JAMAIS \c QDateTime::currentDateTime() côté adaptateur,
* afin que décision (scheduler) et exécution (applyAction) partagent le même temps.
* \return LoadContext incluant declared, limits, needs et télémétrie type-spécifique.
*/
virtual LoadContext toLoadContext(const QDateTime &now) const = 0;
/*!
* \brief Applique l'action et retourne ce qui a réellement été envoyé au matériel.
*
* L'arbitre a déjà écrêté selon les limites et le budget — ceci est le second filet.
*
* \param action Action à appliquer. Doit avoir \c reason non vide.
* \param now Temps de cycle (\c ctx.timestamp) — MÊME source que toLoadContext(),
* pour que l'évaluation des verrous coïncide avec celle vue par le scheduler.
* \return L'action après écrêtage matériel (peut différer de l'entrée).
* \note Retour silencieux sans effet si \c action.reason est vide.
*/
virtual LoadAction applyAction(const LoadAction &action, const QDateTime &now) = 0;
};