From 5685ee8596f62d45418ed70422903c50bd311204 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Wed, 26 Aug 2026 11:57:24 +0200 Subject: [PATCH] fix(arbitre): un filtre par interface qui ne trouve rien doit le DIRE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Un filterByInterface qui rend une liste vide est toujours ambigu entre « il n'y a pas de batterie » et « il y en a une, mal déclarée ». Le second désactive une règle de sécurité sans produire ni erreur ni panne — même mode de défaillance qu'un seuil de réserve mal réglé : rien ne se signale. Constaté sur le banc : le Thing « SunSpec Storage » déclare battery mais pas energystorage. La réserve ne s'est donc JAMAIS appliquée — ni dans le waterfall que je viens d'écrire, ni dans le proxy amont qui utilisait le même filtre depuis toujours, ni dans le bilan de puissance de l'experience-plugin qui filtre pareil. GetPowerBalance rend currentPowerStorage: 0 pendant que la batterie affiche 50 % et « Holding » : elle n'est pas à zéro, elle est invisible. L'arbitre émet maintenant un avertissement quand des appareils portent 'battery' sans 'energystorage'. Il ne corrige rien — la déclaration appartient au plugin — mais il rend la cause lisible au lieu de la laisser deviner. Brief émis vers etm-powersync-plugins-modbus : ce n'est pas une ligne de JSON, l'interface exige currentPower et capacity que la classe ne fournit pas, et un currentPower de batterie à zéro serait pire que son absence. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H --- energyplugin/etm/energyarbitrator.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/energyplugin/etm/energyarbitrator.cpp b/energyplugin/etm/energyarbitrator.cpp index fd0bd99..76288a1 100644 --- a/energyplugin/etm/energyarbitrator.cpp +++ b/energyplugin/etm/energyarbitrator.cpp @@ -437,6 +437,28 @@ SurplusContext EnergyArbitrator::buildContext(const QDateTime &now) const ctx.battery.present = true; ctx.battery.socPercent = niveauTotal / n; ctx.battery.powerW = puissanceTotale; // > 0 charge, < 0 décharge + } else { + // Un filtre par interface qui ne trouve rien est TOUJOURS ambigu : « il n'y a pas + // de batterie » et « il y en a une, mal déclarée » se ressemblent, et la seconde + // désactive une règle de sécurité EN SILENCE. C'est le même mode de défaillance + // qu'un seuil de réserve mal réglé : rien ne se signale. + // + // Constaté sur le banc le 2026-08-26 : le Thing « SunSpec Storage » déclare + // `battery` mais pas `energystorage`, donc la réserve ne s'est jamais appliquée — + // ni ici, ni dans le proxy amont qui utilisait le même filtre, ni dans le bilan de + // puissance de l'experience-plugin qui filtre pareil. + const Things candidates = m_tm->configuredThings().filterByInterface("battery"); + if (!candidates.isEmpty()) { + QStringList noms; + for (Thing *t : candidates) + noms << t->name(); + qCWarning(dcNymeaEnergy()) + << "[Arbitre] Réserve de stockage INACTIVE :" << candidates.count() + << "appareil(s) portant l'interface 'battery' mais PAS 'energystorage' —" + << noms.join(", ") << ". Le waterfall ne peut pas les prendre en compte, et" + << "le bilan de puissance de nymea non plus. À corriger dans le plugin qui" + << "déclare la classe, pas ici."; + } } }