[3e-4] arbitre : registerSgReadyAdapter + dispatch State + mode dégradé → état 2

registerSgReadyAdapter + m_sgReadyAdapters ; buildContext inclut les PAC ;
applyActionsToAdapters dispatche kind==State → m_sgReadyAdapters. Mode dégradé L2 :
SG-Ready → état 2 (NORMAL, mains off, force=true), JAMAIS état 1 (blocage). SAFETY.md
table L2 corrigée (état 2, pas 1). Build 0/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-06-09 23:25:09 +02:00
parent c5c22bfe19
commit f7aa7ca7e6
3 changed files with 58 additions and 15 deletions

View File

@ -61,7 +61,7 @@ repli **conservatrice** : le repli n'INITIE rien, il borne ce qui tourne déjà.
| EV branché mais **pas en charge** | Inchangé — reste off (off possiblement volontaire : HC/spot à venir) | | EV branché mais **pas en charge** | Inchangé — reste off (off possiblement volontaire : HC/spot à venir) |
| EV débranché | Aucune action | | EV débranché | Aucune action |
| ECS | Relais coupé (palier 0, `force=true`) | | ECS | Relais coupé (palier 0, `force=true`) |
| SG-Ready PAC | État 1 (normal) | | SG-Ready PAC | État **2** (normal — mains off, la PAC chauffe selon son thermostat). JAMAIS état 1 (blocage) : bloquer une PAC sous compteur muet = maison qui ne chauffe plus sans raison visible. |
| Batterie | Aucune charge réseau (surplus uniquement, plafonné à 0 si compteur muet) | | Batterie | Aucune charge réseau (surplus uniquement, plafonné à 0 si compteur muet) |
« Maintenu » ≠ « démarré » : le mode dégradé ne force jamais l'activation d'une charge. « Maintenu » ≠ « démarré » : le mode dégradé ne force jamais l'activation d'une charge.

View File

@ -4,6 +4,7 @@
#include "energyarbitrator.h" #include "energyarbitrator.h"
#include "adapters/evadapter.h" #include "adapters/evadapter.h"
#include "adapters/ecsrelayadapter.h" #include "adapters/ecsrelayadapter.h"
#include "adapters/sgreadyadapter.h"
#include "scheduler/rulebasedscheduler.h" #include "scheduler/rulebasedscheduler.h"
#include "types/surpluscontext.h" #include "types/surpluscontext.h"
#include "types/plan.h" #include "types/plan.h"
@ -96,6 +97,18 @@ void EnergyArbitrator::registerEcsAdapter(EcsRelayAdapter *adapter)
qCDebug(dcNymeaEnergy()) << "[EnergyArbitrator] EcsRelayAdapter enregistré:" << adapter->descriptor().label; qCDebug(dcNymeaEnergy()) << "[EnergyArbitrator] EcsRelayAdapter enregistré:" << adapter->descriptor().label;
} }
void EnergyArbitrator::registerSgReadyAdapter(SgReadyAdapter *adapter)
{
const QString id = adapter->descriptor().id;
if (m_sgReadyAdapters.contains(id)) {
qCWarning(dcNymeaEnergy()) << "[EnergyArbitrator] SgReadyAdapter déjà enregistré:" << id;
return;
}
adapter->setParent(this);
m_sgReadyAdapters[id] = adapter;
qCDebug(dcNymeaEnergy()) << "[EnergyArbitrator] SgReadyAdapter enregistré:" << adapter->descriptor().label;
}
void EnergyArbitrator::update(const QDateTime &currentDateTime) void EnergyArbitrator::update(const QDateTime &currentDateTime)
{ {
qCDebug(dcNymeaEnergy()) << "Updating smart charging"; qCDebug(dcNymeaEnergy()) << "Updating smart charging";
@ -170,6 +183,10 @@ SurplusContext EnergyArbitrator::buildContext(const QDateTime &now) const
for (auto it = m_ecsAdapters.constBegin(); it != m_ecsAdapters.constEnd(); ++it) for (auto it = m_ecsAdapters.constBegin(); it != m_ecsAdapters.constEnd(); ++it)
ctx.loads.append(it.value()->toLoadContext(now)); ctx.loads.append(it.value()->toLoadContext(now));
// --- loads[] : SG-Ready adapters (PAC) ---
for (auto it = m_sgReadyAdapters.constBegin(); it != m_sgReadyAdapters.constEnd(); ++it)
ctx.loads.append(it.value()->toLoadContext(now));
return ctx; return ctx;
} }
@ -191,17 +208,22 @@ void EnergyArbitrator::syncAdapters()
void EnergyArbitrator::applyActionsToAdapters(const Slot &slot, const QDateTime &now) void EnergyArbitrator::applyActionsToAdapters(const Slot &slot, const QDateTime &now)
{ {
for (const LoadAction &action : slot.actions) { for (const LoadAction &action : slot.actions) {
// EV (Setpoint) : dispatché par adjustEvChargers() amont jusqu'à 3g. // L'adaptateur applique, écrête et verrouille — il ne décide pas (règle 2).
if (action.kind != LoadAction::Stage) if (action.kind == LoadAction::Stage) {
continue; EcsRelayAdapter *adapter = m_ecsAdapters.value(action.loadId);
if (adapter)
adapter->applyAction(action, now);
else
qCWarning(dcNymeaEnergy()) << "[Arbitre] action Stage sans adaptateur ECS:" << action.loadId;
EcsRelayAdapter *adapter = m_ecsAdapters.value(action.loadId); } else if (action.kind == LoadAction::State) {
if (!adapter) { SgReadyAdapter *adapter = m_sgReadyAdapters.value(action.loadId);
qCWarning(dcNymeaEnergy()) << "[Arbitre] action Stage sans adaptateur ECS:" << action.loadId; if (adapter)
continue; adapter->applyAction(action, now);
else
qCWarning(dcNymeaEnergy()) << "[Arbitre] action State sans adaptateur SG-Ready:" << action.loadId;
} }
// L'adaptateur applique, écrête et verrouille (anti-rebond) — il ne décide pas. // EV (Setpoint) : dispatché par adjustEvChargers() amont jusqu'à 3g.
adapter->applyAction(action, now);
} }
} }
@ -266,6 +288,18 @@ void EnergyArbitrator::applyDegradedMode(const QDateTime &now)
ev->setMaxChargingCurrent(ev->maxChargingCurrentMinValue(), now, true); ev->setMaxChargingCurrent(ev->maxChargingCurrentMinValue(), now, true);
} }
// SG-Ready (état 1) / Batterie (aucune charge réseau) : repli ajouté avec leurs // SG-Ready (PAC) : repli en état 2 (NORMAL — mains off), JAMAIS état 1 (blocage).
// adaptateurs (3e/3f). Le flag degradedMode + notification client arrivent en 3c-6. // Sous compteur muet on cesse de piloter : la PAC chauffe selon son propre thermostat
// (la bloquer = maison qui ne chauffe plus sans raison visible). force=true → bypass minStateHold.
for (SgReadyAdapter *adapter : m_sgReadyAdapters) {
LoadAction la;
la.loadId = adapter->descriptor().id;
la.kind = LoadAction::State;
la.state = 2;
la.force = true;
la.reason = reason;
adapter->applyAction(la, now);
}
// Batterie (aucune charge réseau) : repli ajouté avec son adaptateur (3f).
} }

View File

@ -13,6 +13,7 @@ class QTimer;
class EvAdapter; class EvAdapter;
class EcsRelayAdapter; class EcsRelayAdapter;
class SgReadyAdapter;
class RuleBasedScheduler; class RuleBasedScheduler;
/*! /*!
@ -84,6 +85,13 @@ public:
*/ */
void registerEcsAdapter(EcsRelayAdapter *adapter); void registerEcsAdapter(EcsRelayAdapter *adapter);
/*!
* \brief Enregistre un SgReadyAdapter (PAC) pour inclusion dans le contexte et le dispatch.
* \param adapter Adaptateur à enregistrer ; son \c descriptor().id doit être unique.
* Adopté comme enfant Qt de l'arbitre. Appelé par le test (setup) ou la config production.
*/
void registerSgReadyAdapter(SgReadyAdapter *adapter);
/*! /*!
* \brief Mode dégradé L2 actif (compteur muet > 90 s) — override de SmartChargingManager. * \brief Mode dégradé L2 actif (compteur muet > 90 s) — override de SmartChargingManager.
* \return \c true tant que les consignes de repli L2 tiennent ; \c false en régime normal. * \return \c true tant que les consignes de repli L2 tiennent ; \c false en régime normal.
@ -125,7 +133,7 @@ protected:
* 3. verifyOverloadProtection() + verifyOverloadProtectionRecovery() * 3. verifyOverloadProtection() + verifyOverloadProtectionRecovery()
* (si \c m_degradedMode actif : retour immédiat — planification/dispatch suspendus, L2) * (si \c m_degradedMode actif : retour immédiat — planification/dispatch suspendus, L2)
* 4. m_scheduler->getPlan() → log des decisionReason * 4. m_scheduler->getPlan() → log des decisionReason
* 5. applyActionsToAdapters() (ECS Stage) + adjustEvChargers() (EV) → dispatch matériel * 5. applyActionsToAdapters() (ECS Stage + SG-Ready State) + adjustEvChargers() (EV) → dispatch
* *
* \param currentDateTime Instant courant (timer ou simulation). * \param currentDateTime Instant courant (timer ou simulation).
*/ */
@ -189,8 +197,9 @@ private:
void applyDegradedMode(const QDateTime &now); void applyDegradedMode(const QDateTime &now);
RuleBasedScheduler *m_scheduler = nullptr; RuleBasedScheduler *m_scheduler = nullptr;
QHash<QString, EvAdapter *> m_adapters; //!< loadId (ThingId string) → EvAdapter*. QHash<QString, EvAdapter *> m_adapters; //!< loadId (ThingId string) → EvAdapter*.
QHash<QString, EcsRelayAdapter *> m_ecsAdapters; //!< loadId → EcsRelayAdapter*. QHash<QString, EcsRelayAdapter *> m_ecsAdapters; //!< loadId → EcsRelayAdapter*.
QHash<QString, SgReadyAdapter *> m_sgReadyAdapters; //!< loadId → SgReadyAdapter* (PAC).
// --- L2 watchdog fraîcheur compteur (SAFETY.md §L2) --- // --- L2 watchdog fraîcheur compteur (SAFETY.md §L2) ---
QTimer *m_meterWatchdog = nullptr; //!< Tick 30 s, indépendant des signaux compteur. QTimer *m_meterWatchdog = nullptr; //!< Tick 30 s, indépendant des signaux compteur.