From e70a60c180580b4a68ec4fcf59ce55d0381bd778 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Sat, 8 Aug 2026 11:47:05 +0200 Subject: [PATCH] =?UTF-8?q?docs(etm):=20contrats=20Doxygen=20=E2=80=94=20t?= =?UTF-8?q?ypes=20de=20config/contexte=20et=20adaptateurs=20stables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Couverture des fichiers non visés par la restructuration à venir : l'extraction du noyau de calcul (étape 2) et celle de m_relayMapping (étape 3) ne les touchent pas. relayrouter.h et energyarbitrator.h sont volontairement DIFFÉRÉS — documenter ce qui va changer produirait du bruit d'historique. Mesure sur la config du Doxyfile : 113 → 31 avertissements. loadconfig.h 41→0 · surpluscontext.h 30→0 · sgreadyadapter.h 4→1 (le \return restant relève du lot suivant) · etmvariableloadadapter.h 2→0 · evadapter.h 1→0 Ce sont des contrats, pas des étiquettes : enabled dit qu'une charge déclarée mais exclue n'est JAMAIS pilotée ; priority qu'il s'agit d'un rang ascendant et non d'un poids ; timestamp qu'il est la source unique du temps, dont dérivent verrous et fenêtres ; setPowerLevels qu'il trie et déduplique ; fromMap qu'il retourne une config NON validée ; internalRootMeter() qu'il peut être nul. Ajouts « // [ETM] » hors etm/ (smartchargingmanager.h) : ils échappent au périmètre du Doxyfile, la frontière étant un répertoire. Un inventaire explicite est posé au marqueur [ETM] BEGIN, distinguant les trois cas — degradedMode() et les trois accesseurs internal* sont des ajouts ETM et sont documentés ; les huit changements de visibilité seule gardent la documentation de l'amont. La définition de fait d'AGENTS.md renvoie à cet inventaire et l'étend explicitement aux ajouts hors etm/. Build amd64 0 erreur. Simulation : 7/7. Co-Authored-By: Claude Opus 5 (1M context) --- AGENTS.md | 4 ++ .../etm/adapters/etmvariableloadadapter.h | 8 ++- energyplugin/etm/adapters/evadapter.h | 5 +- energyplugin/etm/adapters/sgreadyadapter.h | 10 ++- energyplugin/etm/types/loadconfig.h | 57 +++++++++++++++++ energyplugin/etm/types/surpluscontext.h | 63 ++++++++++--------- energyplugin/smartchargingmanager.h | 46 +++++++++++++- 7 files changed, 160 insertions(+), 33 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d9a82fe..eef0166 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -488,6 +488,10 @@ supérieures n'affecte pas les couches inférieures. Voir `docs/SAFETY.md` pour `\brief`, `\param`, `\return`, et surtout le **contrat de comportement** (invariants, écrêtage, hypothèses que l'appelant peut faire). Les headers 3a servent de modèle — les convertir au format Doxygen lors du passage 3b. + **Y compris les ajouts `// [ETM]` hors `etm/`** : ils échappent au périmètre du + `Doxyfile` (frontière = un répertoire) et se documentent à la main. Leur inventaire + est tenu dans `energyplugin/smartchargingmanager.h`, au marqueur `[ETM] BEGIN` — + toute nouvelle marque `[ETM]` dans un en-tête amont s'y ajoute. ## ROADMAP — configuration des priorités par l'utilisateur (post-beta) diff --git a/energyplugin/etm/adapters/etmvariableloadadapter.h b/energyplugin/etm/adapters/etmvariableloadadapter.h index 498a2fe..808eae8 100644 --- a/energyplugin/etm/adapters/etmvariableloadadapter.h +++ b/energyplugin/etm/adapters/etmvariableloadadapter.h @@ -80,6 +80,8 @@ public: /*! * \brief Télémétrie runtime. \c currentPowerW = state \c currentPowerW MESURÉ du thing * (juge de quantification, contrat §4) ; \c available faux si le thing est absent. + * \return LoadTelemetry ; \c currentPowerW vaut 0 si le thing est absent ou n'expose + * pas l'état \c currentPowerW (jamais de puissance fantôme). */ LoadTelemetry telemetry() const override; @@ -104,7 +106,11 @@ public: */ LoadAction applyAction(const LoadAction &action, const QDateTime &now) override; - /*! \brief Dernière consigne (W) effectivement écrite (avant écrêtage thing). */ + /*! + * \brief Dernière consigne (W) effectivement écrite (avant écrêtage thing). + * \return Consigne commandée en W, bornée à \c maxPowerW ; 0 tant qu'aucune action + * n'a été appliquée. C'est le COMMANDÉ, pas le mesuré. + */ double currentSetpointW() const { return m_currentSetpointW; } private: diff --git a/energyplugin/etm/adapters/evadapter.h b/energyplugin/etm/adapters/evadapter.h index 51bde1d..c0570b2 100644 --- a/energyplugin/etm/adapters/evadapter.h +++ b/energyplugin/etm/adapters/evadapter.h @@ -71,7 +71,10 @@ public: */ LoadAction applyAction(const LoadAction &action, const QDateTime &now) override; - /*! \brief Borne VE sous-jacente (lecture). */ + /*! + * \brief Borne VE sous-jacente (lecture). + * \return Pointeur non-propriétaire vers l'EvCharger ; jamais nul (fixé à la construction). + */ EvCharger *evCharger() const { return m_charger; } private: diff --git a/energyplugin/etm/adapters/sgreadyadapter.h b/energyplugin/etm/adapters/sgreadyadapter.h index cd8d433..5f48e9a 100644 --- a/energyplugin/etm/adapters/sgreadyadapter.h +++ b/energyplugin/etm/adapters/sgreadyadapter.h @@ -68,12 +68,16 @@ public: * (\c declared.estimatedPowerW : 0 pour états 1/2, P3/P4 pour 3/4). C'est la base du * recrédit budget — PAS la conso mesurée de la PAC (l'état 2 autonome est déjà au * compteur, invariant 8 : la recréditer double-compterait). + * \return LoadTelemetry ; \c available toujours vrai (l'encodage relais ne dépend pas + * d'une liaison montante), \c lastActionAt nul tant qu'aucune transition n'a eu lieu. */ LoadTelemetry telemetry() const override; /*! * \brief Construit l'entrée loads[] §5 (adapter="sg-ready"). * \param now Temps de cycle (\c ctx.timestamp) — source unique de la fenêtre minState/maxState. + * \return LoadContext incluant \c telemetry.state et la fenêtre \c minState/\c maxState + * évaluée à \p now : c'est sur elle que le scheduler clampe sa cible. */ LoadContext toLoadContext(const QDateTime &now) const override; @@ -85,7 +89,11 @@ public: */ LoadAction applyAction(const LoadAction &action, const QDateTime &now) override; - /*! \brief État SG-Ready courant (1-4). */ + /*! + * \brief État SG-Ready courant (1-4). + * \return Dernier état réellement commuté ; valeur initiale tant qu'aucune transition + * n'a eu lieu. Reflète le COMMANDÉ — la PAC reste seule juge de son fonctionnement. + */ int currentState() const { return m_currentState; } private: diff --git a/energyplugin/etm/types/loadconfig.h b/energyplugin/etm/types/loadconfig.h index 2854a38..a320831 100644 --- a/energyplugin/etm/types/loadconfig.h +++ b/energyplugin/etm/types/loadconfig.h @@ -20,14 +20,22 @@ class LoadConfigNeeds Q_PROPERTY(QString dailyDeadline READ dailyDeadline WRITE setDailyDeadline) Q_PROPERTY(int minEnergyWhPerDay READ minEnergyWhPerDay WRITE setMinEnergyWhPerDay) public: + //! \brief Construit des besoins vides : aucune échéance, aucune énergie minimale. LoadConfigNeeds() {} + //! \return Heure limite quotidienne "HH:MM" ; chaîne vide = pas d'échéance. QString dailyDeadline() const { return m_dailyDeadline; } + //! \param v Heure limite "HH:MM", ou chaîne vide pour retirer l'échéance. void setDailyDeadline(const QString &v) { m_dailyDeadline = v; } + //! \return Énergie minimale à fournir par jour (Wh) ; 0 = aucune exigence. int minEnergyWhPerDay() const { return m_minEnergyWhPerDay; } + //! \param v Énergie minimale par jour (Wh) ; 0 pour retirer l'exigence. void setMinEnergyWhPerDay(int v) { m_minEnergyWhPerDay = v; } + //! \return Forme sérialisée ; les champs à leur valeur par défaut sont OMIS (jamais nuls). QVariantMap toMap() const; + //! \brief Construit depuis la forme sérialisée. \param map Map §4 \c needs. + //! \return Besoins ; les clés absentes prennent leur valeur par défaut. static LoadConfigNeeds fromMap(const QVariantMap &map); private: @@ -46,7 +54,11 @@ struct LoadConfigRelay QString thingId; //!< ThingId du thing implémentant l'interface \c power. int powerW = 0; //!< Puissance nominale de ce relais (W). + //! \return Forme sérialisée \c {thingId, powerW}. QVariantMap toMap() const; + //! \brief Construit depuis la forme sérialisée. \param map Map \c {thingId, powerW}. + //! \return Relais ; \c thingId vide et \c powerW nul si les clés manquent — c'est + //! \c LoadConfig::isValid() qui rejette ces cas, pas ce constructeur. static LoadConfigRelay fromMap(const QVariantMap &map); }; @@ -78,45 +90,77 @@ class LoadConfig Q_PROPERTY(int minOnS READ minOnS WRITE setMinOnS) Q_PROPERTY(int minOffS READ minOffS WRITE setMinOffS) public: + //! \brief Construit une config vide — \c adapter vaut "etmvariableload", \c enabled vrai. LoadConfig() {} + //! \return Identifiant logique de la charge (clé de dispatch). Requis, non vide. QString id() const { return m_id; } + //! \param v Identifiant logique ; une valeur vide fait échouer isValid(). void setId(const QString &v) { m_id = v; } + //! \return Nom lisible affiché dans les logs et l'app. QString label() const { return m_label; } + //! \param v Nom lisible. void setLabel(const QString &v) { m_label = v; } + //! \return Type d'adaptateur : "relay-router" ou "etmvariableload". QString adapter() const { return m_adapter; } + //! \param v Type d'adaptateur ; toute autre valeur fait échouer isValid(). void setAdapter(const QString &v) { m_adapter = v; } + //! \return Mode : "fixed" (paliers) ou "dynamic" (modulation continue). QString mode() const { return m_mode; } + //! \param v Mode "fixed" ou "dynamic" ; toute autre valeur fait échouer isValid(). void setMode(const QString &v) { m_mode = v; } //! powerLevels en QVariantList (introspection) ; trié + dédupliqué à l'écriture. + //! \return Paliers déclarés (W), triés croissants. Vide en *dynamic*. QVariantList powerLevels() const; + //! \param v Paliers (W) dans un ordre quelconque : ils sont TRIÉS et DÉDUPLIQUÉS ici + //! (contrat §6, l'energymanager re-trie par sécurité). void setPowerLevels(const QVariantList &v); //! Accès typé pour la construction de l'adaptateur. + //! \return Les mêmes paliers, typés, triés croissants. QList powerLevelsInt() const { return m_powerLevels; } + //! \return Plafond physique de la charge (W). Requis (> 0) en *dynamic*. int maxPowerW() const { return m_maxPowerW; } + //! \param v Plafond physique (W). void setMaxPowerW(int v) { m_maxPowerW = v; } + //! \return Rang dans le waterfall, ASCENDANT : 1 = servi en premier. Un rang, pas un poids. int priority() const { return m_priority; } + //! \param v Rang de service (1 = premier servi). void setPriority(int v) { m_priority = v; } + //! \return Faux si la charge est déclarée mais EXCLUE de l'arbitrage (contrat §9) : + //! aucun adaptateur n'est construit, elle n'est jamais pilotée. bool enabled() const { return m_enabled; } + //! \param v Faux pour exclure la charge de l'arbitrage sans supprimer sa config. void setEnabled(bool v) { m_enabled = v; } + //! \return Besoins déclarés par l'utilisateur (échéance, énergie minimale). LoadConfigNeeds needs() const { return m_needs; } + //! \param v Besoins déclarés. void setNeeds(const LoadConfigNeeds &v) { m_needs = v; } //! relays en QVariantList (introspection / sérialisation) ; typé via relaysList(). + //! \return Relais sérialisés \c [{thingId, powerW}] ; vide hors cas relay-router. QVariantList relays() const; + //! \param v Relais \c [{thingId, powerW}] ; réservé à \c relay-router (isValid() rejette + //! une liste non vide sur \c etmvariableload). void setRelays(const QVariantList &v); + //! \return Les mêmes relais, typés — source de la combinatoire du RelayRouter. QList relaysList() const { return m_relays; } + //! \return Durée minimale ON (s) — protection relais/compresseur. 0 = pas de verrou. int minOnS() const { return m_minOnS; } + //! \param v Durée minimale ON (s), ≥ 0. void setMinOnS(int v) { m_minOnS = v; } + //! \return Durée minimale OFF (s) — anti court-cycling. 0 = pas de verrou. int minOffS() const { return m_minOffS; } + //! \param v Durée minimale OFF (s), ≥ 0. void setMinOffS(int v) { m_minOffS = v; } //! Vrai si la charge est un routeur de relais (rév. 3 : combinatoire watts→relais côté routeur). + //! \return Vrai si \c adapter == "relay-router" — décide de la classe construite au rebuild. bool isRelayRouter() const { return m_adapter == QStringLiteral("relay-router"); } //! Vrai si *dynamic* (modulation continue). Sinon *fixed*. + //! \return Vrai si \c mode == "dynamic". bool isDynamic() const { return m_mode == QStringLiteral("dynamic"); } /*! @@ -126,7 +170,13 @@ public: */ bool isValid(QString *error = nullptr) const; + //! \return Forme sérialisée §4. Les champs \c relays/\c minOnS/\c minOffS ne sont émis + //! que s'il existe au moins un relais ; \c powerLevels que s'il est non vide. QVariantMap toMap() const; + //! \brief Construit depuis la forme sérialisée §4 (ce que reçoit \c SetLoadConfig). + //! \param map Map §4. Clés absentes → valeurs par défaut (\c adapter "etmvariableload", + //! \c enabled vrai). \c powerLevels est trié et dédupliqué au passage. + //! \return Config NON validée — appeler \c isValid() avant usage. static LoadConfig fromMap(const QVariantMap &map); private: @@ -151,9 +201,16 @@ class LoadConfigs : public QList Q_GADGET Q_PROPERTY(int count READ count) public: + //! \brief Liste vide. LoadConfigs() {} + //! \brief Conversion depuis une QList typée. \param other Configs à reprendre. LoadConfigs(const QList &other) : QList(other) {} + //! \brief Accès indexé pour l'introspection JSON-RPC. + //! \param index Position, 0-based ; doit être valide (aucun contrôle de borne). + //! \return La config à \p index, encapsulée en QVariant. Q_INVOKABLE QVariant get(int index) const; + //! \brief Ajout pour l'introspection JSON-RPC. + //! \param variant Config à ajouter ; un QVariant d'un autre type ajoute une config vide. Q_INVOKABLE void put(const QVariant &variant); }; Q_DECLARE_METATYPE(LoadConfigs) diff --git a/energyplugin/etm/types/surpluscontext.h b/energyplugin/etm/types/surpluscontext.h index 0c12bad..a7a10a3 100644 --- a/energyplugin/etm/types/surpluscontext.h +++ b/energyplugin/etm/types/surpluscontext.h @@ -29,26 +29,26 @@ struct SurplusPv { /*! \brief État courant du système de stockage (batterie). */ struct SurplusBattery { - bool present = false; - double socPercent = 0; + bool present = false; //!< Faux si aucun stockage déclaré — les autres champs sont alors sans objet. + double socPercent = 0; //!< État de charge (%), 0-100. double powerW = 0; //!< Positif = charge, négatif = décharge. - double capacityWh = 0; - int reserveSocPercent = 0; - double maxChargeW = 0; - double maxDischargeW = 0; + double capacityWh = 0; //!< Capacité nominale (Wh), plaque signalétique. + int reserveSocPercent = 0; //!< SOC plancher (%) non déchargeable (réserve de secours). + double maxChargeW = 0; //!< Puissance de charge maximale admissible (W). + double maxDischargeW = 0; //!< Puissance de décharge maximale admissible (W). }; /*! \brief Entrée tarifaire (créneau HP/HC ou spot market). */ struct TariffEntry { - QDateTime from; + QDateTime from; //!< Début du créneau ; il court jusqu'au \c from du suivant. QString label; //!< "HP", "HC", "Tempo-Rouge", ... double priceCtkWh = 0; //!< Prix en ct€/kWh. }; /*! \brief Contexte tarifaire : créneau courant + prochains créneaux. */ struct SurplusTariff { - QString provider; - TariffEntry current; + QString provider; //!< Fournisseur/offre ("aWATTar", "Tempo", ...) ; vide si inconnu. + TariffEntry current; //!< Créneau en cours à \c SurplusContext::timestamp. QList next; //!< Créneaux suivants, ordre chronologique. }; @@ -56,27 +56,29 @@ struct SurplusTariff { struct LoadContextTelemetry { double currentPowerW = 0; //!< Puissance mesurée (W). // --- evcharger --- - bool pluggedIn = false; - bool charging = false; + bool pluggedIn = false; //!< Véhicule branché. Une borne non branchée est ignorée du plan. + bool charging = false; //!< Charge effectivement en cours (courant non nul côté borne). double sessionWh = 0; //!< Énergie chargée dans la session courante (Wh). // --- etmvariableload (EV/ECS/routeur) : pas de fenêtre de verrou côté moteur — // l'anti-rebond (minOn/minOff) vit dans le thing (contrat rév. 2 §3). Seul // currentPowerW (ci-dessus) sert au résidu de la règle d'arrondi (contrat §5). // --- sg-ready --- - int state = 0; + int state = 0; //!< État SG-Ready courant commandé (1-4) ; 0 = non applicable. //! Fenêtre d'états autorisée MAINTENANT par le verrou minStateHold (protection //! court-cycling PAC) : gel total (minState == maxState == state) si non écoulé, - //! sinon [1, 4]. Même mécanique que minStage/maxStage de l'ECS, source = temps de cycle. + //! sinon [1, 4]. Source = temps de cycle. Borne BASSE de cette fenêtre. int minState = 0; + //! Borne HAUTE de la même fenêtre de verrou. Le scheduler clampe sa cible sur + //! [minState, maxState] ; décision et exécution partagent donc le même verrou. int maxState = 0; // --- battery / electricvehicle --- - double socPercent = 0; + double socPercent = 0; //!< État de charge (%) de la batterie ou du véhicule. QDateTime lastSwitch; //!< Dernier changement d'état. }; /*! \brief Données apprises par l'optimiseur §8 (renvoyées pour persistance). */ struct LoadLearned { - double dailyEnergyWh = 0; + double dailyEnergyWh = 0; //!< Énergie quotidienne apprise (Wh) ; 0 tant qu'aucun profil. //! Confiance 0–1 ; < 0.7 = "profil en apprentissage". double confidence = 0.0; }; @@ -86,15 +88,17 @@ struct LoadLearned { * Construit par l'arbitre depuis ILoadAdapter::descriptor() + toLoadContext(). */ struct LoadContext { - QString id; + QString id; //!< Identifiant logique de la charge — clé de dispatch des LoadAction. QString adapter; //!< "evcharger"|"etmvariableload"|"sg-ready"|"battery". - QString label; + QString label; //!< Nom lisible (logs, decisionReason, app). + //! Rang dans le waterfall, ASCENDANT : 1 = servi en premier (protocole §5 + annexe C). + //! C'est un rang, pas un poids. int priority = 0; - LoadDeclared declared; - LoadLearned learned; - LoadContextTelemetry telemetry; - LoadNeeds needs; - LoadLimits limits; + LoadDeclared declared; //!< Capacités déclarées par l'installateur (référence de planification). + LoadLearned learned; //!< Profil appris §8, renvoyé pour persistance ; jamais une consigne. + LoadContextTelemetry telemetry;//!< État runtime au \c timestamp du contexte. + LoadNeeds needs; //!< Besoins déclarés par l'utilisateur (échéance, énergie minimale). + LoadLimits limits; //!< Contraintes anti-rebond et verrous temporels. }; /*! @@ -105,12 +109,15 @@ struct LoadContext { * (AGENTS invariant 8 : pas de boucle de feedback). */ struct SurplusContext { + //! Instant de début de cycle. SOURCE UNIQUE du temps : verrous, fenêtres et estampilles + //! en dérivent, jamais \c QDateTime::currentDateTime() — c'est ce qui rend décision et + //! exécution impossibles à désynchroniser, et la logique injectable en simulation. QDateTime timestamp; - SurplusSite site; - SurplusMeter meter; - SurplusPv pv; - SurplusBattery battery; - QList loads; - SurplusTariff tariff; + SurplusSite site; //!< Paramètres fixes du site (puissance souscrite, limites par phase). + SurplusMeter meter; //!< Mesures BRUTES du compteur principal (invariant 8). + SurplusPv pv; //!< Production PV mesurée brute. + SurplusBattery battery; //!< État du stockage ; \c present faux si absent. + QList loads; //!< Une entrée par charge pilotable, tous types confondus. + SurplusTariff tariff; //!< Contexte tarifaire (créneau courant + suivants). // forecast : opaque, transmis tel quel si présent — réservé V2 }; diff --git a/energyplugin/smartchargingmanager.h b/energyplugin/smartchargingmanager.h index 472da96..b39ead4 100644 --- a/energyplugin/smartchargingmanager.h +++ b/energyplugin/smartchargingmanager.h @@ -72,8 +72,16 @@ public: ChargingSchedules chargingSchedules() const; - // [ETM] Mode dégradé L2 (watchdog fraîcheur compteur). Base = false ; - // overridé dans EnergyArbitrator. Exposé pour la notification JSON-RPC. + /*! + * \brief [ETM] Mode dégradé L2 — le compteur principal est muet depuis plus de 90 s. + * + * Base amont : toujours faux (l'amont n'a pas de watchdog). Redéfini par + * \c EnergyArbitrator, qui le pose depuis \c applyDegradedMode() (SAFETY.md §L2). + * Exposé publiquement pour la notification JSON-RPC \c ChargingSchedulesChanged. + * + * \return Vrai si la planification est suspendue et les consignes de repli posées. + * La sécurité L4 (\c verifyOverloadProtection) reste active dans cet état. + */ virtual bool degradedMode() const { return false; } SpotMarketManager *spotMarketManager() const; @@ -100,6 +108,22 @@ signals: // [ETM] BEGIN — SmartChargingManager protected API for EnergyArbitrator (etm/). // All changes below are visibility-only (private → protected / virtual added). // Zero logic change. Revert by deleting this block and restoring private slots. +// +// INVENTAIRE des ajouts ETM de ce fichier — ils vivent HORS de energyplugin/etm/ et +// échappent donc au périmètre du Doxyfile ; ils sont documentés à la main, et la +// définition de fait d'AGENTS.md les couvre explicitement. Toute nouvelle marque +// [ETM] dans un en-tête amont DOIT être ajoutée ici : +// +// 1. degradedMode() — ajout ETM, virtuel, documenté ci-dessus. +// 2. update() — virtual ajouté (le corps reste amont). +// 3. prepareInformation, planSpotMarketCharging, planSurplusCharging, +// adjustEvChargers, updateManualSoCsWithoutMeter, verifyOverloadProtection, +// verifyOverloadProtectionRecovery, executeChargingAction +// — visibilité seule (private → protected). +// Sémantique amont inchangée : NON documentés +// ici, la doc appartient à l'amont. +// 4. internalEvChargers(), internalChargingActions(), internalRootMeter() +// — accesseurs ETM neufs, documentés ci-dessous. protected slots: virtual void update(const QDateTime ¤tDateTime); // [ETM] virtual added void prepareInformation(const QDateTime ¤tDateTime); // [ETM] private → protected @@ -114,8 +138,26 @@ protected: void executeChargingAction(EvCharger *evCharger, const ChargingAction &chargingAction, const QDateTime ¤tDateTime); // [ETM] private → protected // [ETM] Read-only state accessors — inline, no copies, no logic. + + /*! + * \brief [ETM] Bornes VE enregistrées, en lecture seule. + * \return Référence sur la table interne — aucune copie. Valide tant que le manager + * vit ; ne pas conserver au-delà d'un cycle \c update(). + */ const QHash &internalEvChargers() const { return m_evChargers; } // [ETM] new + + /*! + * \brief [ETM] Consignes de charge du cycle courant, en lecture seule. + * \return Référence sur la table remplie par \c planSurplusCharging() / + * \c planSpotMarketCharging(). N'a de sens qu'APRÈS ces appels dans le même cycle. + */ const QHash &internalChargingActions() const { return m_chargingActions; } // [ETM] new + + /*! + * \brief [ETM] Compteur principal du site, en lecture seule. + * \return Pointeur non-propriétaire ; **peut être nul** si aucun root meter n'est + * configuré — tout appelant doit le tester. + */ RootMeter *internalRootMeter() const { return m_rootMeter; } // [ETM] new // [ETM] END