From ea3b9bdd519abac5877bcd129c147a678aae7aac Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Wed, 5 Aug 2026 20:05:13 +0200 Subject: [PATCH] fix(sunspec): interroger le thing de connexion, pas une recherche par id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le garde de présence batterie demandait isFroniusChild(thing->id()), qui résout le parent par myThings().findById(). Pendant le setup initial la connexion n'est pas encore enregistrée : la recherche répond faux, le garde est contourné, et l'enfant storage est créé alors qu'InBatV et StorAval sont nuls. Constaté en boucle fermée sur le simulateur, avec la trace nymead "Invalid parent thing id ... Not adding thing to the system". Le garde interroge maintenant directement le thing de connexion que la fonction tient déjà. Le signe compteur passe de même par le parentThing déjà résolu, dont la non-nullité est garantie quelques lignes plus haut. La suite logique ne pouvait pas attraper ce défaut : elle couvre la décision pure, pas le câblage. Consigné en D-7. Co-Authored-By: Claude Opus 5 (1M context) --- sunspec/DIVERGENCES.md | 27 +++++++++++++++++++++++++++ sunspec/integrationpluginsunspec.cpp | 9 +++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/sunspec/DIVERGENCES.md b/sunspec/DIVERGENCES.md index cfef716..3d50d93 100644 --- a/sunspec/DIVERGENCES.md +++ b/sunspec/DIVERGENCES.md @@ -173,3 +173,30 @@ installée porte une tension au repos » est donc **INFÉRÉE des deux côtés** batterie et vérifier `InBatV`/`StorAval` non nuls. **À faire avant tout déploiement** : un discriminateur trop strict ferait disparaître une vraie batterie chez un client. + +--- + +## D-7 — la présence batterie ne peut pas être vérifiée par la suite logique (MESURÉ) + +**Contexte.** La première version du garde de D-6 demandait +`isFroniusChild(thing->id())`, qui résout le parent par `myThings().findById()`. + +**L'écart.** Pendant le setup initial, la connexion parente n'est **pas encore +enregistrée** dans `myThings()` : la recherche répond faux, le garde Fronius est +contourné, et l'enfant storage est créé malgré `InBatV = StorAval = 0`. Trace +nymead correspondante : +`ThingManager: Invalid parent thing id … Not adding thing to the system.` + +Le garde interroge désormais directement le `thing` de connexion que la fonction +tient déjà (`thing->thingClassId() == froniusConnectionThingClassId`), sans +aucune recherche. Même traitement pour le signe compteur, qui utilise le +`parentThing` déjà résolu et non nul. + +**Ce que cela apprend.** La suite `tests/logic` ne peut pas attraper ce défaut : +elle couvre la décision pure (`FroniusRules`), pas le câblage du plugin, c'est à +dire *quel prédicat est interrogé et à quel moment du cycle de vie*. Seul un +essai en boucle fermée contre un simulateur le révèle. Toute modification du +garde de présence doit donc être rejouée sur banc, pas seulement en unitaire. + +**Comment lever.** Étendre `tests/plugin`, qui instancie réellement le plugin, à +un scénario de setup initial avec un modèle 124 à zéro. Hors périmètre ici. diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 94ccdeb..390a5b5 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -841,8 +841,12 @@ void IntegrationPluginSunSpec::processDiscoveryResult(Thing *thing, SunSpecConne * phantom battery stuck on batteryCritical. Only the Fronius * path is gated; the generic and solarEdge vendors keep their * previous behaviour. */ + /* Asked of the connection thing we already hold, not through + * isFroniusChild(): during the initial setup the parent is not + * registered in myThings() yet, so a lookup answers false and + * the gate would be skipped exactly when it is needed. */ SunSpecStorageModel *storageModel = qobject_cast(model); - if (storageModel && isFroniusChild(thing->id()) + if (storageModel && thing->thingClassId() == froniusConnectionThingClassId && !FroniusRules::hasBattery(storageModel->inBatV(), storageModel->storAval())) { qCDebug(dcSunSpec()) << "Fronius model 124 found but no battery is installed" << "(InBatV" << storageModel->inBatV() @@ -1853,7 +1857,8 @@ void IntegrationPluginSunSpec::onMeterBlockUpdated() /* Sign applied to every power reading of this block. The generic vendors * keep the historical negation; a meter sitting under a Fronius connection * already reports in the nymea convention. See FroniusRules. */ - const double powerSign = FroniusRules::meterPowerSign(isFroniusChild(thing->parentId())); + const double powerSign = FroniusRules::meterPowerSign( + parentThing->thingClassId() == froniusConnectionThingClassId); switch (model->modelId()) { case SunSpecModelFactory::ModelIdMeterSinglePhase: {