fix(3g-2): une entrée de configuration supprimée est rattrapée au cycle suivant
LM-1209-e. Le provisionnement vivait dans la branche « l'adaptateur vient d'être créé » de syncAdapters(). Or l'adaptateur est construit depuis le THING : il survit à la suppression de l'entrée de configuration. Une entrée supprimée depuis l'app n'était donc jamais recréée — mesuré sur .75, rien en 140 s. Le trou de 3g-2 rouvert par le côté client : la borne restait ARBITRÉE mais ABSENTE de GetLoadConfig, à un rang de queue non réglable. Elle consommait du budget sans que personne puisse la classer, et rien ne le disait. La condition devient un ÉTAT et non un ÉVÉNEMENT — « cette borne n'a pas d'entrée » au lieu de « l'adaptateur vient de naître ». C'est la leçon qui dépasse le défaut : un rattrapage dont la condition est un événement ne rattrape rien, puisqu'il ne se rejoue pas. Même distinction qu'entre ECS-411, qui relit l'état réel des relais, et l'hypothèse de départ qu'il a remplacée. L'entrée recréée revient en rankOrigin "auto" — personne ne l'a classée, son rang est de nouveau un défaut — et le rang posé par l'installateur est PERDU avec l'entrée. Supprimer n'est donc pas un moyen de retirer une borne de l'arbitrage : elle revient, déclassée, en queue et avec son badge. C'est exactement pourquoi la marque doit se voir. enabled: false reste la voie et conserve le rang. Le refus de provisionnement n'est annoncé qu'une fois par borne (m_provisionRefuse) : le rattrapage étant cyclique, le répéter produirait un avertissement par minute et pour toujours — la règle est déjà posée pour les charges sans décision dans buildTelemetry(). Le drapeau tombe au premier succès. testDeletedEvChargerConfigComesBack couvre les deux moitiés : le rattrapage, et le cas négatif — il ne se rejoue PAS quand l'entrée existe, sans quoi il réécrirait la configuration à chaque cycle, donc un changed(), donc un rebuild d'adaptateurs, donc des verrous réarmés en boucle (ECS-412). Vérifié échouant avec l'ancien comportement. Le brief §6-C, qui promettait à l'app l'inverse de ce qui se passait sur le seul geste client qui détruit de la configuration, est corrigé — et dit désormais ce que la suppression coûte vraiment : le rang. Passe aussi LM-1209-d (grouper à l'affichage, jamais à l'écriture) et l'avertissement de sonde en tête d'INTERFACE.md. amd64 0/0. simulation 32/32, charging 16/16, loadmodel 20/20, spotmarket 7/7. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
This commit is contained in:
parent
87c97e407f
commit
2c46f8e798
@ -236,9 +236,12 @@ intactes et rangs inchangés ; `rankOrigin: "auto"` fabriqué par un client **re
|
||||
> absente de la configuration » — rouvert par le côté client. Il ne se referme qu'au
|
||||
> **redémarrage de `nymead`**.
|
||||
>
|
||||
> **Correctif proposé, NON écrit** : reprovisionner toute borne sans entrée de configuration,
|
||||
> pas seulement à la naissance de l'adaptateur. Petit, mais c'est un changement de
|
||||
> comportement — à trancher.
|
||||
> **CORRIGÉ en `1.15.2+etm28`** (LM-1209-e) : le rattrapage est cyclique, sa condition est
|
||||
> « cette borne n'a pas d'entrée » et non « l'adaptateur vient de naître ». Une entrée
|
||||
> supprimée revient au cycle suivant, **en queue et en `rankOrigin: "auto"`** — le rang posé
|
||||
> par l'installateur est perdu avec l'entrée, ce qui fait de la suppression un geste
|
||||
> déclassant, à ne pas confondre avec `enabled: false`. Le REFUS de provisionnement, lui, ne
|
||||
> s'annonce qu'une fois par borne : cyclique, il noierait sinon le journal.
|
||||
|
||||
**DÉPLOYÉ SUR `.75` — `1.15.2+etm26`, 2026-08-28.** Cross-build arm64 dans `build-cross-arm64`
|
||||
(bundle git → `/root/build-etm26`), posé par `deploy.sh`, version relue sur la box, `nymead`
|
||||
|
||||
@ -257,15 +257,21 @@ C'est exactement le « geste, pas une suppression d'office » que vous demandez,
|
||||
> cycle. L'adaptateur est construit depuis le **Thing** — il survit à la suppression de
|
||||
> l'entrée, donc la borne n'est plus jamais proposée à la configuration.
|
||||
>
|
||||
> **Ce que cela produit** : la borne reste **arbitrée** mais **absente de `GetLoadConfig`**, à un
|
||||
> rang de queue non réglable. C'est exactement le trou que 3g-2 avait refermé — « arbitrée mais
|
||||
> absente de la configuration » — rouvert par le côté client. Il ne se referme qu'au
|
||||
> **redémarrage de `nymead`**.
|
||||
> **Ce que cela produisait** : la borne restait **arbitrée** mais **absente de
|
||||
> `GetLoadConfig`**, à un rang de queue non réglable — le trou que 3g-2 avait refermé, rouvert
|
||||
> par le côté client.
|
||||
>
|
||||
> **Donc : ne supprimez pas l'entrée d'une borne dont le Thing vit.** `enabled: false` reste la
|
||||
> bonne voie pour la sortir de l'arbitrage — ce point-là ne change pas. Un correctif moteur est
|
||||
> proposé (reprovisionner toute borne sans entrée, pas seulement à la naissance de
|
||||
> l'adaptateur) ; il n'est pas écrit, la décision appartient à Patrick.
|
||||
> **CORRIGÉ en `1.15.2+etm28`** (`LM-1209-e`). Le rattrapage est désormais **cyclique** : la
|
||||
> condition est « cette borne n'a pas d'entrée », plus « l'adaptateur vient de naître ». Une
|
||||
> entrée supprimée est donc **recréée au cycle suivant**.
|
||||
>
|
||||
> **Mais elle revient DÉCLASSÉE — et c'est ce qu'il faut montrer à l'installateur.** Elle
|
||||
> réapparaît **en queue**, avec `rankOrigin: "auto"` : personne ne l'a classée, son rang est de
|
||||
> nouveau un défaut. **Le rang qu'il avait posé est perdu avec l'entrée.**
|
||||
>
|
||||
> **Donc la suppression d'une entrée n'est PAS un moyen de retirer une borne de l'arbitrage** —
|
||||
> elle revient, à une autre place. `enabled: false` est la voie, et lui conserve son rang. Si
|
||||
> votre écran propose une suppression, il devrait le dire avant, pas après.
|
||||
|
||||
### D — instruction reçue, rien ne bouge côté moteur
|
||||
|
||||
|
||||
@ -790,11 +790,22 @@ void EnergyArbitrator::provisionEvChargerConfig(EvCharger *ev)
|
||||
// 7-c — l'échec ne doit pas être plus discret que le succès : sans entrée, la borne
|
||||
// reste arbitrée mais absente de GetLoadConfig, et c'est précisément le trou qu'on
|
||||
// vient refermer.
|
||||
qCWarning(dcNymeaEnergy()) << "[EnergyArbitrator] Configuration automatique de la borne"
|
||||
<< ev->name() << "REFUSÉE :" << erreur
|
||||
<< "— elle sera arbitrée sans rang configurable.";
|
||||
//
|
||||
// …mais UNE SEULE FOIS par borne. Depuis LM-1209-e le rattrapage est tenté à chaque
|
||||
// cycle : répéter l'avertissement en produirait un par minute et pour toujours, ce qui
|
||||
// noierait le vrai signal — c'est la règle déjà posée pour les bornes sans décision
|
||||
// dans buildTelemetry(). Le drapeau est levé au premier succès, donc un défaut qui se
|
||||
// résout se redit s'il revient.
|
||||
if (!m_provisionRefuse.contains(id)) {
|
||||
m_provisionRefuse.insert(id);
|
||||
qCWarning(dcNymeaEnergy()) << "[EnergyArbitrator] Configuration automatique de la borne"
|
||||
<< ev->name() << "REFUSÉE :" << erreur
|
||||
<< "— elle sera arbitrée sans rang configurable. Réessayé"
|
||||
<< "à chaque cycle ; cet avertissement ne sera pas répété.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_provisionRefuse.remove(id);
|
||||
|
||||
qCInfo(dcNymeaEnergy()) << "[EnergyArbitrator] Borne" << ev->name()
|
||||
<< "ajoutée à la configuration des charges (rang" << c.priority()
|
||||
@ -805,14 +816,25 @@ void EnergyArbitrator::provisionEvChargerConfig(EvCharger *ev)
|
||||
|
||||
void EnergyArbitrator::syncAdapters()
|
||||
{
|
||||
// Crée les adapters manquants
|
||||
// Crée les adapters manquants, et RATTRAPE toute borne sans entrée de configuration.
|
||||
//
|
||||
// LM-1209-e — les deux gestes sont SÉPARÉS, et ils l'étaient à tort. Le provisionnement
|
||||
// vivait dans la branche `!m_adapters.contains(id)`, donc il ne tournait qu'UNE FOIS par
|
||||
// vie d'adaptateur. Or l'adaptateur est construit depuis le THING : il survit à la
|
||||
// suppression de l'entrée de configuration. Une entrée supprimée depuis l'app n'était donc
|
||||
// jamais recréée, et la borne restait ARBITRÉE mais ABSENTE de GetLoadConfig, à un rang de
|
||||
// queue non réglable (evPriority() → 1000) — le trou refermé en 3g-2, rouvert par le côté
|
||||
// client. Mesuré sur `.75` le 2026-08-29 : rien recréé en 140 s.
|
||||
//
|
||||
// Le rattrapage est donc tenté à CHAQUE cycle. provisionEvChargerConfig() rend la main
|
||||
// aussitôt si l'entrée existe : la condition n'est pas « l'adaptateur vient de naître »,
|
||||
// c'est « cette borne n'a pas d'entrée ».
|
||||
for (auto it = internalEvChargers().constBegin(); it != internalEvChargers().constEnd(); ++it) {
|
||||
const QString id = it.key().toString();
|
||||
if (!m_adapters.contains(id)) {
|
||||
if (!m_adapters.contains(id))
|
||||
m_adapters[id] = new EvAdapter(it.value(), this);
|
||||
// 3g-2 — la borne entre AUSSI dans la configuration, pour y porter son rang.
|
||||
provisionEvChargerConfig(it.value());
|
||||
}
|
||||
// 3g-2 — la borne entre AUSSI dans la configuration, pour y porter son rang.
|
||||
provisionEvChargerConfig(it.value());
|
||||
}
|
||||
// Supprime les adapters obsolètes
|
||||
for (const QString &id : m_adapters.keys()) {
|
||||
|
||||
@ -434,19 +434,32 @@ private:
|
||||
* Le rang, le libellé, le domaine \c ev, \c enabled. Aucune charge utile : les limites de
|
||||
* la borne viennent du Thing (voir \c LoadConfig::isEvCharger()).
|
||||
*
|
||||
* \par Rang par défaut
|
||||
* En **tête**, sans bousculer les charges déjà classées : le plus petit rang existant
|
||||
* moins un, borné à 1. Ce n'est pas une politique du plugin — l'ordre des domaines est un
|
||||
* choix de mise en service, porté par l'app ; c'est seulement un point de départ qui ne
|
||||
* réécrit le rang de personne.
|
||||
* \par Rang par défaut — EN QUEUE (\rule{LM-1209})
|
||||
* `1 + max(rangs existants)`, donc libre par construction et sans bousculer personne. Ce
|
||||
* n'est pas une politique du plugin : le rang est un choix de l'installateur, et une
|
||||
* détection ne réordonne pas ce qu'un humain a décidé. Une borne détectée un mois après la
|
||||
* mise en service passerait sinon devant l'ECS que quelqu'un a réglé exprès.
|
||||
*
|
||||
* \note **Journalisée, une ligne par borne, à la création seulement.** Une entrée créée
|
||||
* d'office est une écriture que personne n'a demandée : sans trace, un installateur
|
||||
* verrait apparaître des charges dans son écran sans savoir d'où elles viennent.
|
||||
* \par Appelée à CHAQUE cycle, pas seulement à la naissance de l'adaptateur
|
||||
* \rule{LM-1209-e}. Rend la main aussitôt si l'entrée existe : la condition est « cette
|
||||
* borne n'a pas d'entrée », jamais « l'adaptateur vient de naître ». C'est ce qui **rattrape
|
||||
* une suppression depuis l'app** — l'adaptateur, construit depuis le Thing, survit à la
|
||||
* disparition de l'entrée, et sans ce rattrapage la borne restait arbitrée mais absente de
|
||||
* \c GetLoadConfig, à un rang non réglable.
|
||||
*
|
||||
* \note **Une entrée recréée revient en \c rankOrigin "auto"**, et c'est voulu : personne
|
||||
* ne l'a classée, son rang est de nouveau un défaut. L'installateur qui vient de la
|
||||
* supprimer la voit donc réapparaître **en queue, avec son badge** — ce qui se dit, au
|
||||
* journal et dans la charge utile, plutôt que de se subir.
|
||||
* \note **Journalisée, une ligne par borne à la création.** Une entrée créée d'office est
|
||||
* une écriture que personne n'a demandée : sans trace, un installateur verrait apparaître
|
||||
* des charges dans son écran sans savoir d'où elles viennent. Un REFUS, lui, n'est
|
||||
* annoncé qu'une fois par borne (\c m_provisionRefuse) : le rattrapage étant cyclique,
|
||||
* le répéter produirait un avertissement par minute et noierait le vrai signal.
|
||||
* \note L'entrée n'est JAMAIS supprimée d'office quand le Thing disparaît — c'est une
|
||||
* configuration, et l'installateur a pu la classer. Elle se lit alors comme une charge
|
||||
* dont l'appareil manque (\c LOAD_UNAVAILABLE, \c faultCode \c THING_MISSING).
|
||||
* \param ev Borne fraîchement détectée.
|
||||
* \param ev Borne détectée, ou borne connue dont l'entrée manque.
|
||||
*/
|
||||
void provisionEvChargerConfig(EvCharger *ev);
|
||||
|
||||
@ -557,6 +570,10 @@ private:
|
||||
* consigne, compteur muet, ce que SAFETY.md interdit.
|
||||
*/
|
||||
QSet<QString> m_waterfallEvIds;
|
||||
//! Bornes dont la configuration automatique a été REFUSÉE — pour n'avertir qu'une fois.
|
||||
//! Depuis LM-1209-e le rattrapage est tenté à chaque cycle ; répéter l'avertissement en
|
||||
//! produirait un par minute et noierait le vrai signal. Vidée au premier succès.
|
||||
QSet<QString> m_provisionRefuse;
|
||||
//! loadId → charge ARBITRÉE, tous mécanismes confondus (relay-router | etmvariableload |
|
||||
//! sg-ready). Table UNIQUE, et c'est un invariant de contrat : toutes ses entrées viennent
|
||||
//! de \c LoadConfigStore, donc l'ensemble publié par \c NymeaEnergy.GetLoadTelemetry est
|
||||
|
||||
@ -1369,6 +1369,40 @@ doit pas devenir un geste d'arbitrage.** Renuméroter pour « faire de la place
|
||||
l'ordre de service de charges auxquelles personne n'a touché — et le ferait sans qu'aucune
|
||||
décision d'exploitation ait été prise.
|
||||
|
||||
**LM-1209-e — Le rattrapage est CYCLIQUE : la condition est « cette charge n'a pas d'entrée »,
|
||||
jamais « l'adaptateur vient de naître ».** *(Défaut trouvé sur `.75` le 2026-08-29, corrigé le
|
||||
même jour.)*
|
||||
|
||||
Le provisionnement vivait dans la branche « l'adaptateur vient d'être créé ». Or **l'adaptateur
|
||||
est construit depuis le Thing** : il survit à la suppression de l'entrée de configuration. Une
|
||||
entrée supprimée depuis l'app n'était donc **jamais recréée** — mesuré, rien en 140 s.
|
||||
|
||||
**Ce que cela produisait est le trou de 3g-2, rouvert par le côté client** : la charge restait
|
||||
**arbitrée** mais **absente de `GetLoadConfig`**, à un rang de queue non réglable. Elle
|
||||
consommait du budget sans que personne puisse la classer, et rien ne le disait. Le seul
|
||||
rattrapage était un redémarrage du service.
|
||||
|
||||
> **La leçon dépasse ce défaut** : un rattrapage dont la condition est un **événement**
|
||||
> (« à la naissance de… ») ne rattrape rien, puisqu'il ne se rejoue pas. Une condition d'**état**
|
||||
> (« cette charge n'a pas d'entrée ») se réévalue, donc elle répare. C'est la même distinction
|
||||
> qu'entre ECS-411 — relire l'état réel des relais — et l'hypothèse de départ qu'il a remplacée.
|
||||
|
||||
**Une entrée recréée revient en `rankOrigin: "auto"`**, et c'est juste : personne ne l'a classée,
|
||||
son rang est de nouveau un défaut. Le rang que l'installateur avait posé est **perdu avec
|
||||
l'entrée** — c'est le prix de la suppression, et c'est précisément pourquoi la marque doit se
|
||||
voir : la charge réapparaît **en queue, avec son badge**, plutôt que de revenir en silence à une
|
||||
place que personne n'a choisie.
|
||||
|
||||
> ⚠️ **Corollaire d'exploitation** : supprimer l'entrée d'une charge dont le Thing vit n'est
|
||||
> **pas** un moyen de la retirer de l'arbitrage — elle revient au cycle suivant, déclassée.
|
||||
> `enabled: false` est la voie, et lui conserve son rang.
|
||||
>
|
||||
> **Et le REFUS de provisionnement ne s'annonce qu'une fois par charge.** Le rattrapage étant
|
||||
> cyclique, répéter l'avertissement en produirait un par minute et pour toujours — la règle est
|
||||
> déjà posée pour les charges sans décision (`buildTelemetry()`) : avertir à chaque cycle noie
|
||||
> le vrai signal. Le drapeau tombe au premier succès, donc un défaut qui se résout se redit s'il
|
||||
> revient.
|
||||
|
||||
**LM-1209-d — Le rang est un ordre TOTAL ; les domaines ne le partitionnent pas.** *(Point
|
||||
ouvert soulevé par l'agent app, tranché le 2026-08-29.)*
|
||||
|
||||
|
||||
@ -5883,3 +5883,105 @@ void Simulation::testAutoProvisionedRankGoesToTheQueue()
|
||||
QCOMPARE(rangs.count(), cfg.count());
|
||||
#endif
|
||||
}
|
||||
|
||||
void Simulation::testDeletedEvChargerConfigComesBack()
|
||||
{
|
||||
#ifndef ETM_ARBITRATOR
|
||||
QSKIP("testDeletedEvChargerConfigComesBack nécessite ETM_ARBITRATOR.");
|
||||
#else
|
||||
// [LM-1209-e · mesuré sur `.75` le 2026-08-29] Le provisionnement vivait dans la branche
|
||||
// « l'adaptateur vient de naître ». Or l'adaptateur est construit depuis le THING : il
|
||||
// survit à la suppression de l'entrée de configuration. Une entrée supprimée depuis l'app
|
||||
// n'était donc JAMAIS recréée — et la borne restait arbitrée, absente de GetLoadConfig, à
|
||||
// un rang de queue non réglable. Le trou refermé en 3g-2, rouvert par le côté client.
|
||||
const QString cfgPath = QDir::tempPath() + "/etm-loadcfg-reprov.json";
|
||||
QFile::remove(cfgPath);
|
||||
qputenv("NYMEA_ENERGY_LOAD_CONFIG", cfgPath.toUtf8());
|
||||
|
||||
cleanupTestCase();
|
||||
m_energyLogDbFilePath = ":/databases/2022-06-22-energylogs.sqlite";
|
||||
initTestCase();
|
||||
|
||||
EnergyArbitrator *arb = dynamic_cast<EnergyArbitrator *>(m_experiencePlugin->smartChargingManager());
|
||||
QVERIFY(arb);
|
||||
ThingManager *tm = NymeaCore::instance()->thingManager();
|
||||
|
||||
QUuid meterId = addMeter();
|
||||
m_experiencePlugin->energyManager()->setRootMeter(meterId);
|
||||
Thing *meter = tm->findConfiguredThing(meterId);
|
||||
QVERIFY(meter);
|
||||
meter->setStateValue("connected", true);
|
||||
meter->setStateValue("currentPower", -4000);
|
||||
|
||||
QUuid borneId = addCharger("A", 16, 26657);
|
||||
Thing *borne = tm->findConfiguredThing(borneId);
|
||||
QVERIFY(borne);
|
||||
armerBorne(borne);
|
||||
|
||||
const QDateTime t0 = utcDateTime(QDate(2026, 6, 8), QTime(13, 0, 0));
|
||||
arb->simulationCallUpdate(t0);
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
QHash<QString, QVariantMap> cfg = configsParId(injectAndWait("NymeaEnergy.GetLoadConfig"));
|
||||
QVERIFY2(cfg.contains(borneId.toString()), "la borne n'est pas entrée en configuration");
|
||||
QCOMPARE(cfg.value(borneId.toString()).value("rankOrigin").toString(), QString("auto"));
|
||||
const int rangInitial = cfg.value(borneId.toString()).value("priority").toInt();
|
||||
|
||||
// ---- L'installateur classe la borne : la marque tombe -----------------------------------
|
||||
QVariantList l = injectAndWait("NymeaEnergy.GetLoadConfig").toMap()
|
||||
.value("params").toMap().value("loadConfigs").toList();
|
||||
for (int i = 0; i < l.count(); ++i) {
|
||||
QVariantMap m = l.at(i).toMap();
|
||||
if (m.value("id").toString() == borneId.toString()) m.insert("priority", rangInitial + 3);
|
||||
l[i] = m;
|
||||
}
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetLoadConfig", QVariantMap({{"loadConfigs", l}}))
|
||||
.toMap().value("params").toMap().value("energyError").toString(),
|
||||
QString("EnergyErrorNoError"));
|
||||
cfg = configsParId(injectAndWait("NymeaEnergy.GetLoadConfig"));
|
||||
QVERIFY2(cfg.value(borneId.toString()).value("rankOrigin").toString().isEmpty(),
|
||||
"un rang posé par un humain doit lever la marque");
|
||||
|
||||
// ---- …puis il SUPPRIME l'entrée, le Thing restant vivant --------------------------------
|
||||
QVariantList sans;
|
||||
for (const QVariant &v : injectAndWait("NymeaEnergy.GetLoadConfig").toMap()
|
||||
.value("params").toMap().value("loadConfigs").toList())
|
||||
if (v.toMap().value("id").toString() != borneId.toString())
|
||||
sans.append(v);
|
||||
QCOMPARE(injectAndWait("NymeaEnergy.SetLoadConfig", QVariantMap({{"loadConfigs", sans}}))
|
||||
.toMap().value("params").toMap().value("energyError").toString(),
|
||||
QString("EnergyErrorNoError"));
|
||||
QVERIFY2(!configsParId(injectAndWait("NymeaEnergy.GetLoadConfig")).contains(borneId.toString()),
|
||||
"l'entrée devait être retirée par l'écriture cliente");
|
||||
|
||||
// ---- Le cycle suivant la RATTRAPE -------------------------------------------------------
|
||||
// Sans LM-1209-e, rien ne se passe : l'adaptateur existe déjà, donc le provisionnement
|
||||
// n'est plus jamais tenté, et la borne reste arbitrée hors configuration.
|
||||
arb->simulationCallUpdate(t0.addSecs(60));
|
||||
QCoreApplication::processEvents();
|
||||
cfg = configsParId(injectAndWait("NymeaEnergy.GetLoadConfig"));
|
||||
|
||||
QVERIFY2(cfg.contains(borneId.toString()),
|
||||
"une borne dont le Thing vit doit retrouver une entrée de configuration");
|
||||
|
||||
// Et elle revient EN QUEUE et en « auto » : personne ne l'a classée, son rang est de
|
||||
// nouveau un défaut. Le rang que l'installateur avait posé est perdu avec l'entrée — c'est
|
||||
// le prix de la suppression, et c'est pourquoi la marque doit se voir.
|
||||
QCOMPARE(cfg.value(borneId.toString()).value("rankOrigin").toString(), QString("auto"));
|
||||
int rangMax = 0;
|
||||
for (auto it = cfg.constBegin(); it != cfg.constEnd(); ++it)
|
||||
if (it.key() != borneId.toString())
|
||||
rangMax = qMax(rangMax, it.value().value("priority").toInt());
|
||||
QCOMPARE(cfg.value(borneId.toString()).value("priority").toInt(), qMax(1, rangMax + 1));
|
||||
|
||||
// ---- Et le rattrapage ne se REJOUE pas quand l'entrée existe ----------------------------
|
||||
// Sans quoi il réécrirait la configuration à chaque cycle — donc un changed(), donc un
|
||||
// rebuild d'adaptateurs, donc des verrous réarmés en boucle (ECS-412).
|
||||
const QVariantMap avant = cfg.value(borneId.toString());
|
||||
arb->simulationCallUpdate(t0.addSecs(120));
|
||||
QCoreApplication::processEvents();
|
||||
const QVariantMap apres = configsParId(injectAndWait("NymeaEnergy.GetLoadConfig"))
|
||||
.value(borneId.toString());
|
||||
QCOMPARE(apres, avant);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -185,6 +185,12 @@ private slots:
|
||||
// libre par construction, et l'entrée porte rankOrigin="auto" — « ce rang est un défaut ».
|
||||
void testAutoProvisionedRankGoesToTheQueue();
|
||||
|
||||
// [LM-1209-e] Supprimer l'entrée d'une borne dont le Thing VIT la fait RECRÉER au cycle
|
||||
// suivant — en queue, et de nouveau en rankOrigin "auto" : personne ne l'a classée. Sans
|
||||
// ce rattrapage, la borne reste arbitrée mais absente de GetLoadConfig, à un rang non
|
||||
// réglable — le trou de 3g-2 rouvert par le côté client.
|
||||
void testDeletedEvChargerConfigComesBack();
|
||||
|
||||
void printStates(Thing *thing);
|
||||
void updateChargerMeter(Thing *thing);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user