Étape 4 du chantier de documentation. tools/ci-quality.sh porte les trois
vérifications qui se posent au même endroit — elles partagent la même sortie
(doc-generated/) et la même commande de réparation, les séparer ferait trois jobs
qui régénèrent trois fois la même chose :
1. l'index des règles est VRAI (code de retour de gen-rules.py) ;
2. la doc Doxygen est complète — 0 avertissement, DoD §5 ;
3. l'index est REPRODUCTIBLE : deux passes, un seul résultat.
PAS EN SERVICE, et l'extension le garantit : ci/gitea-workflow.yml.disabled ne
déclenche rien tant qu'il n'est pas déplacé dans .gitea/workflows/. La raison est
datée dans le fichier — forge muette, plus de trente commits en attente ici et
plus de soixante côté app ; un job activé à l'aveugle échouerait au moment précis
où tout le monde pousse, et le premier réflexe serait de le désactiver.
CHAQUE ÉCHEC DONNE LA COMMANDE À LANCER. Un job qui annonce « l'index est
périmé » sans dire quoi faire se contourne en le désactivant : c'est le chemin de
moindre effort, et il gagne toujours.
VÉRIFIÉ AVANT D'ÊTRE DÉCLARÉ PRÊT, et ça ne passait pas du premier coup.
— Les 26 avertissements Doxygen de ce matin sont CORRIGÉS, pas contournés par un
cliquet sur un compte de référence : c'était de la dette DoD-5 (retours et
paramètres non documentés dans relayrouter.h, energyarbitrator.h, loadconfig.h,
les trois clearFault(), ILoadAdapter::updateSoftConfig), plus deux que j'avais
introduits — un \rule{} dans un titre \par, et un \param energyLogs qui ne
correspond à aucun argument. Le seuil est donc ZÉRO, le seul qui ne rote pas.
— Et le script lui-même échouait sur un dépôt SAIN : `grep -c ... || echo 0`
affiche 0 PUIS sort en 1 quand il ne trouve rien, si bien que le « || » empile
un second zéro et que le test entier casse. Corrigé ici et dans gen-doc.sh, qui
portait le même piège. Une commande de mesure dont l'échec produit une lecture
fausse — le motif de la semaine, appliqué à l'outil.
CE QUI N'EST PAS VÉRIFIÉ, et pourquoi : « régénérer et refuser si ça diffère de
ce qui est commité » n'a rien à comparer, doc-generated/ étant dans .gitignore.
Le risque qu'un tel diff attraperait — un artefact commité qui dérive de sa
source — a été SUPPRIMÉ en ne le commitant pas, pas déplacé. Ce qui reste à
garantir est que l'index soit vrai (contrôle 1) et déterministe (contrôle 3).
État au 2026-08-29 : ./tools/ci-quality.sh → RC=0, 113 règles, 0 avertissement.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
133 lines
6.5 KiB
C++
133 lines
6.5 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 "iloadadapter.h"
|
|
|
|
class EvCharger;
|
|
class EnergyArbitrator;
|
|
|
|
/*!
|
|
* \brief Adaptateur pour une borne de recharge VE (interface evcharger nymea).
|
|
*
|
|
* Traduit les LoadAction(Setpoint) en appels matériels via
|
|
* EnergyArbitrator::doExecuteChargingAction() — le seul chemin d'exécution.
|
|
*
|
|
* \invariant applyAction() rejette silencieusement toute LoadAction dont \c reason est vide.
|
|
* \invariant currentA est écrêté à [maxChargingCurrentMinValue, maxChargingCurrentMaxValue].
|
|
* \invariant phaseCount est écrêté selon canSetPhaseCount() et phaseCount() du EvCharger.
|
|
* \invariant Les kinds autres que Setpoint sont retournés sans effet.
|
|
*/
|
|
class EvAdapter : public QObject, public ILoadAdapter
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
/*!
|
|
* \brief Constructeur.
|
|
* \param evCharger Borne VE à piloter (doit rester valide tant que l'adaptateur existe).
|
|
* \param parent EnergyArbitrator propriétaire — utilisé pour l'exécution matérielle.
|
|
*/
|
|
explicit EvAdapter(EvCharger *evCharger, EnergyArbitrator *parent);
|
|
|
|
/*!
|
|
* \brief Retourne la description statique de la charge.
|
|
* \return LoadDescriptor construit depuis les capacités actuelles du EvCharger.
|
|
* \note Recalculé à chaque appel depuis l'état nymea du Thing.
|
|
*/
|
|
LoadDescriptor descriptor() const override;
|
|
|
|
/*!
|
|
* \brief Retourne la télémétrie runtime (puissance mesurée, disponibilité).
|
|
* \return LoadTelemetry avec currentPowerW, available et lastActionAt.
|
|
*/
|
|
LoadTelemetry telemetry() const override;
|
|
|
|
/*!
|
|
* \brief Construit l'entrée loads[] §5 du SurplusContext.
|
|
* \param now Temps de cycle (\c ctx.timestamp). Inutilisé ici : l'EV n'a pas de verrou
|
|
* de palier (hors waterfall ECS/SG-Ready). Présent pour l'uniformité de \c ILoadAdapter.
|
|
* \return LoadContext incluant declared, limits, needs et télémétrie EV.
|
|
*/
|
|
LoadContext toLoadContext(const QDateTime &now) const override;
|
|
|
|
/*!
|
|
* \brief Applique une consigne Setpoint sur la borne VE.
|
|
*
|
|
* **Inactif jusqu'à 3g** : non appelée par \c EnergyArbitrator::update() en beta.
|
|
* Le dispatch EV passe par \c adjustEvChargers() amont (hérité). Cette méthode
|
|
* sera câblée lors de la transplantation EV dans \c RuleBasedScheduler (phase 3g).
|
|
* \c descriptor() et \c telemetry() sont eux actifs dès maintenant pour le SurplusContext.
|
|
*
|
|
* \param action LoadAction de kind Setpoint. Autres kinds : retour sans effet.
|
|
* \param now Temps de cycle (\c ctx.timestamp) — passé à \c doExecuteChargingAction()
|
|
* (locks anti-rebond de la borne). MÊME source que toLoadContext() (contrat ILoadAdapter).
|
|
* \return L'action après écrêtage matériel (currentA, phaseCount bornés).
|
|
*
|
|
* \invariant action.reason non vide requis — log warning et retour sans effet sinon.
|
|
* \invariant currentA écrêté à [minValue, maxValue] avant envoi à executeChargingAction.
|
|
* \invariant phaseCount ajusté selon canSetPhaseCount() du EvCharger.
|
|
*/
|
|
LoadAction applyAction(const LoadAction &action, const QDateTime &now) override;
|
|
|
|
//! \brief Sans effet : l'EV n'est pas construit depuis \c LoadConfig (rang fixé à 100,
|
|
//! cf. « Dette 3g »), et son nom d'affichage vient du Thing, pas de la config.
|
|
//! \param label Ignoré. \param priority Ignoré. \param needs Ignoré.
|
|
void updateSoftConfig(const QString &label, int priority, const LoadNeeds &needs) override
|
|
{ Q_UNUSED(label) Q_UNUSED(priority) Q_UNUSED(needs) }
|
|
|
|
//! \brief Sans effet : l'EV n'implémente pas encore l'échelle de défaut ECS-410.
|
|
//! La généralisation est portée par **ECS-414** (lot de mise en configuration
|
|
//! du SgReadyAdapter). Déclaré explicitement plutôt qu'hérité d'un défaut vide.
|
|
//! Aucun mécanisme de défaut côté EV : il n'y a jamais rien à lever. Le dire
|
|
//! explicitement plutôt que de laisser un corps vide (règle 7-c).
|
|
//! \return Vrai si un défaut a réellement été levé ; faux s'il n'y en avait pas.
|
|
bool clearFault() override { return false; }
|
|
|
|
/*!
|
|
* \brief Vue runtime — déclarée, mais HORS de la charge utile \c GetLoadTelemetry.
|
|
*
|
|
* \param now Ignoré : les verrous EV (chargingEnabledLock, currentChangeLock) vivent dans
|
|
* \c SmartChargingManager amont, pas ici.
|
|
* \return \c mechanismKind "evcharger" et l'état de la borne.
|
|
*
|
|
* \note L'EV n'apparaît PAS dans \c NymeaEnergy.GetLoadTelemetry en beta : il est décidé
|
|
* par l'amont EN AMONT du waterfall (décision B), sa puissance est réservée dans
|
|
* \c PlanBudget::evReservedW et non allouée depuis le budget publié. L'y faire figurer
|
|
* ferait une somme \c loads[] qui ne retomberait pas sur \c budget.allocatedW. La
|
|
* méthode est néanmoins IMPLÉMENTÉE, et non laissée en \c Q_UNIMPLEMENTED : quand 3g
|
|
* transplantera l'EV dans le waterfall unifié, il n'y aura rien à écrire ici.
|
|
*/
|
|
LoadRuntimeView runtimeView(const QDateTime &now) const override;
|
|
|
|
/*!
|
|
* \brief Mesure propre de la borne (§11 / LM-1105).
|
|
* \return La puissance que la borne rapporte elle-même, quand sa ThingClass porte l'état.
|
|
* \note Une borne est **traversée** par la puissance qu'elle délivre : sa mesure est bien
|
|
* celle de la charge, et non celle d'un organe de commande. C'est le cas qui a motivé
|
|
* LM-1105 — il fallait jusqu'ici désigner la borne comme son propre \c meterThingId
|
|
* pour publier une valeur qu'elle donne d'elle-même.
|
|
*/
|
|
LoadMeasurement deviceMeasurement() const override;
|
|
|
|
//! \brief Sans effet : l'EV n'est pas construit depuis \c LoadConfig et ne peut donc pas
|
|
//! être « désactivé » par ce chemin. \param now Ignoré.
|
|
void applySafeState(const QDateTime &now) override { Q_UNUSED(now) }
|
|
|
|
/*!
|
|
* \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:
|
|
EvCharger *m_charger;
|
|
EnergyArbitrator *m_parent;
|
|
QDateTime m_lastActionAt;
|
|
//! Nombre de phases VISÉ, décidé à la construction du contexte et réutilisé à
|
|
//! l'exécution. Les deux doivent parler du MÊME, sinon le plancher publié ne décrit pas
|
|
//! la borne qui sera réellement commandée (§12 / 3g-1).
|
|
mutable uint m_targetPhases = 1;
|
|
};
|