From f71e0405b48c9135b712cb7d23f32c070ff8a54c Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Mon, 8 Jun 2026 17:04:09 +0200 Subject: [PATCH] =?UTF-8?q?[3c-6]=20degradedMode()=20+=20notification=20Ch?= =?UTF-8?q?argingSchedulesChanged=20+=20invariant=20z=C3=A9ro-cloud?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit virtual degradedMode() dans SmartChargingManager (base false, [ETM] additif), override EnergyArbitrator. Champ o:degradedMode (additif) dans la notification NymeaEnergy.ChargingSchedulesChanged, émise aussi aux transitions du mode dégradé (planif suspendue → push du flag via emit chargingSchedulesChanged()). INTERFACE.md : champ degradedMode documenté. SAFETY.md : notification réconciliée (ChargingSchedulesChanged, pas EnergyManagerChanged) + limite "valeur figée non détectée". Correction ZÉRO CLOUD : suppression de la section "Alertes externes" / mécanisme n8n, remplacée par une signalisation 100% locale (notification nymea in-app + buzzer/relais via règle nymea, aucun canal réseau sortant). Invariant 10 "ZÉRO cloud" gravé dans AGENTS.md. Build 0 erreur / 0 warning. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 6 +++++ INTERFACE.md | 12 +++++++-- docs/SAFETY.md | 34 ++++++++++++++++++++++--- energyplugin/etm/energyarbitrator.cpp | 2 ++ energyplugin/etm/energyarbitrator.h | 8 ++++++ energyplugin/nymeaenergyjsonhandler.cpp | 4 +++ energyplugin/smartchargingmanager.h | 4 +++ 7 files changed, 64 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4758392..60a5e65 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -181,6 +181,12 @@ Règles absolues : après pilotage. 9. **Aucun composant propriétaire ici** (Héos = repo privé `etm-powersync-optimizer`). Ce repo doit compiler et tourner seul, GPL pur. +10. **ZÉRO cloud** — aucun appel réseau sortant vers un service distant (ni n8n, ni mail, + ni push tiers). Le système fonctionne sans internet (autoconsommation, local-first). + Toute alerte est **locale** : notification nymea in-app + signalisation physique + (buzzer/relais via règle nymea). Le moteur expose l'état, il ne contacte personne. + Exception : le plugin est CLIENT d'un optimiseur sur socket local/LAN (OPTIMIZER_PROTOCOL, + `unix://` ou `tcp://` du réseau de l'installation) — jamais un service cloud externe. ## RÉPONSES FIGÉES (ne plus poser ces questions) diff --git a/INTERFACE.md b/INTERFACE.md index e4810a6..7049930 100644 --- a/INTERFACE.md +++ b/INTERFACE.md @@ -453,10 +453,18 @@ S'abonner via `JSONRPC.SetNotificationStatus` avec le namespace `"NymeaEnergy"`. --- ### `NymeaEnergy.ChargingSchedulesChanged` -Émis à chaque recalcul du planning (cycle ~1 min). +Émis à chaque recalcul du planning (cycle ~1 min), **et** à chaque transition du mode +dégradé L2 (watchdog fraîcheur compteur). ```json -{ "chargingSchedules": [ ... ] } +{ + "chargingSchedules": [ ... ], + "o:degradedMode": false +} ``` +- `degradedMode` *(bool, optionnel — [ETM])* : `true` quand le compteur est muet depuis + > 90 s et que les consignes de repli L2 sont actives (planification suspendue, ECS coupé, + EV en charge clampé au minimum). Repasse à `false` au retour du compteur. Champ additif : + les clients antérieurs l'ignorent. Détail : `docs/SAFETY.md` §L2. --- diff --git a/docs/SAFETY.md b/docs/SAFETY.md index 19b4a74..a81841f 100644 --- a/docs/SAFETY.md +++ b/docs/SAFETY.md @@ -88,13 +88,35 @@ indépendante de la défaillance du contrôleur est le rôle de L3 (watchdog sys ### Notification client (dans ce repo) -- Une notification nymea `EnergyManagerChanged` est émise avec un flag `degradedMode: true`. +- La notification JSON-RPC `NymeaEnergy.ChargingSchedulesChanged` porte un champ additif + `degradedMode` (bool). Elle est émise aux **transitions** du mode dégradé (entrée/sortie), + en plus des recalculs de planning. Voir `INTERFACE.md`. - L'application affiche : *"Supervision compteur perdue — charge EV maintenue au minimum"*. -### Alertes externes (hors de ce repo) +### Limite — détection par fraîcheur uniquement -Les notifications push/mail/SMS sont déclenchées via l'infra ETM (n8n) sur réception -de l'événement nymea. Aucun code de notification externe dans ce plugin. +Le watchdog L2 détecte l'**absence de mise à jour** du compteur (plus de signal +`powerBalanceChanged` depuis > 90 s), pas une **valeur figée**. Un compteur qui continue +d'émettre une valeur strictement constante (capteur bloqué mais lien vivant) n'est **pas** +détecté par cette couche — `m_lastMeterUpdate` reste frais. Détecter une valeur figée +(variance nulle sur fenêtre) est hors scope L2 ; le cas est couvert au niveau matériel/L0 +et par la supervision externe. + +### Signalisation locale (zéro cloud) + +ETM PowerSync est **100 % autonome, zéro cloud** : aucune alerte ne sort vers un service +distant (ni n8n, ni mail, ni push tiers). Le système est conçu pour fonctionner **sans +internet** (argument produit : autoconsommation, local-first). Le `degradedMode` est +signalé par deux canaux strictement locaux : + +- **Notification nymea in-app** (déjà implémentée : champ `degradedMode`) — canal principal + vers le client connecté à l'application. +- **Signal sonore local optionnel** (buzzer GPIO ou canal relais) piloté par une **Règle + nymea** déclenchée sur `degradedMode` — pour le client sur place, sans application. + Aucun code buzzer dans ce repo : le moteur **expose** l'état, la signalisation est une + Thing nymea + une règle (configuration d'installation, cf. `## Signalisation locale`). + +**Aucun canal sortant réseau.** Voir l'invariant « ZÉRO cloud » dans `AGENTS.md`. ### Sortie du mode dégradé @@ -169,6 +191,10 @@ déclenchée sur ces événements — configuration installation, documentée da Aucun code buzzer/relais dans ce repo. Principe : le moteur émet, la configuration d'installation décide quoi signaler. +**Zéro cloud** : toute la signalisation est locale (notification nymea in-app + +signalisation physique). Aucun appel réseau sortant vers un service distant — le système +fonctionne sans internet. Invariant gravé dans `AGENTS.md`. + --- ## Correspondance couches / scénarios de défaillance diff --git a/energyplugin/etm/energyarbitrator.cpp b/energyplugin/etm/energyarbitrator.cpp index f809203..6d991ba 100644 --- a/energyplugin/etm/energyarbitrator.cpp +++ b/energyplugin/etm/energyarbitrator.cpp @@ -38,6 +38,7 @@ EnergyArbitrator::EnergyArbitrator(EnergyManager *em, ThingManager *tm, if (m_degradedMode) { qCInfo(dcNymeaEnergy()) << "[Arbitre] Compteur de nouveau actif — sortie du mode dégradé L2."; m_degradedMode = false; + emit chargingSchedulesChanged(); // pousse degradedMode=false (planif reprend au cycle suivant) } }); // QTimer (et non signal) : doit rester actif quand le compteur est muet. @@ -223,6 +224,7 @@ void EnergyArbitrator::onMeterWatchdogTick() void EnergyArbitrator::applyDegradedMode(const QDateTime &now) { m_degradedMode = true; + emit chargingSchedulesChanged(); // pousse degradedMode=true (notification client L2) const QString reason = QStringLiteral("Compteur muet depuis >90 s — consigne de repli (L2 watchdog)"); diff --git a/energyplugin/etm/energyarbitrator.h b/energyplugin/etm/energyarbitrator.h index 71062ed..a0c21b9 100644 --- a/energyplugin/etm/energyarbitrator.h +++ b/energyplugin/etm/energyarbitrator.h @@ -84,6 +84,14 @@ public: */ void registerEcsAdapter(EcsRelayAdapter *adapter); + /*! + * \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. + * \note Exposé dans la notification \c NymeaEnergy.ChargingSchedulesChanged (champ + * \c degradedMode), émise aussi aux transitions de ce flag. + */ + bool degradedMode() const override { return m_degradedMode; } + protected: /*! * \brief Boucle principale ETM — surcharge SmartChargingManager::update(). diff --git a/energyplugin/nymeaenergyjsonhandler.cpp b/energyplugin/nymeaenergyjsonhandler.cpp index f3ad873..28583db 100644 --- a/energyplugin/nymeaenergyjsonhandler.cpp +++ b/energyplugin/nymeaenergyjsonhandler.cpp @@ -197,6 +197,9 @@ NymeaEnergyJsonHandler::NymeaEnergyJsonHandler(SpotMarketManager *spotMarketMana params.clear(); description = "Emitted whenever the planed charging schedules have changed."; params.insert("chargingSchedules", QVariantList() << objectRef()); + // [ETM] degradedMode : true quand le watchdog L2 a basculé en repli (compteur muet). + // Optionnel (additif, rétro-compatible) ; émis aussi aux transitions du mode dégradé. + params.insert("o:degradedMode", enumValueName(Bool)); registerNotification("ChargingSchedulesChanged", description, params); // Charing manager @@ -223,6 +226,7 @@ NymeaEnergyJsonHandler::NymeaEnergyJsonHandler(SpotMarketManager *spotMarketMana schedules << pack(schedule); } params.insert("chargingSchedules", schedules); + params.insert("degradedMode", m_smartChargingManager->degradedMode()); // [ETM] L2 emit ChargingSchedulesChanged(params); }); diff --git a/energyplugin/smartchargingmanager.h b/energyplugin/smartchargingmanager.h index 3bb2847..472da96 100644 --- a/energyplugin/smartchargingmanager.h +++ b/energyplugin/smartchargingmanager.h @@ -72,6 +72,10 @@ public: ChargingSchedules chargingSchedules() const; + // [ETM] Mode dégradé L2 (watchdog fraîcheur compteur). Base = false ; + // overridé dans EnergyArbitrator. Exposé pour la notification JSON-RPC. + virtual bool degradedMode() const { return false; } + SpotMarketManager *spotMarketManager() const; #ifdef ENERGY_SIMULATION