fix(dérogation): setChargingInfo déclenche un update — pose après, restitution différée
setChargingInfo() appelle update() lui-même, et le lot le touche aux deux bouts. À LA POSE, l'ordre décide d'une minute. L'écriture du mode vient désormais en DERNIER, après l'insertion dans m_derogations : le cycle qu'elle déclenche trouve la dérogation déjà posée et la sert immédiatement. L'ordre inverse la lui faisait manquer d'un cycle — soixante secondes d'attente sur un mécanisme dont la définition est « tout de suite ». À LA FIN, la restitution est différée d'un tour de boucle. Les fins sont évaluées DEPUIS update() ; y appeler setChargingInfo() imbriquait un update() dans un update() — deux plans construits et deux dispatchs pour le même instant, sur des charges qu'un seul des deux croirait avoir commandées. update() n'a aucun verrou de ré-entrance : rien ne l'aurait signalé, et la trace aurait montré une borne commandée deux fois sans cause lisible. simulation 68/68, ci-quality 4/4, build 0 erreur 0 warning. 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
1eae99143b
commit
106806d8ce
@ -1,3 +1,15 @@
|
||||
powersync-energy-plugin-nymea (1.15.2+etm60) trixie; urgency=medium
|
||||
|
||||
* Dérogation : deux effets de setChargingInfo(), qui déclenche lui-même un
|
||||
update(). À la pose, l'écriture du mode vient EN DERNIER — le cycle qu'elle
|
||||
déclenche trouve alors la dérogation déjà posée et la sert immédiatement, au
|
||||
lieu de la manquer d'un cycle. À la fin, la restitution du mode est DIFFÉRÉE
|
||||
d'un tour de boucle : les fins sont évaluées depuis update(), et y écrire le
|
||||
mode imbriquait un update() dans un update() — deux plans et deux dispatchs
|
||||
pour le même instant, sans qu'aucun verrou de ré-entrance le signale.
|
||||
|
||||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 08 Sep 2026 10:52:03 +0200
|
||||
|
||||
powersync-energy-plugin-nymea (1.15.2+etm59) trixie; urgency=medium
|
||||
|
||||
* Le BOOST, implémenté comme une DÉROGATION et non comme un mode. Elle se
|
||||
|
||||
@ -295,3 +295,24 @@ sera reprise avec lui. C'est aussi pourquoi la première vérification au banc s
|
||||
(`LM-1012-d`) — c'est précisément ce que le filet de durée couvre.
|
||||
- `ClearLoadDerogation` publie la cause `cancelled`, qui n'est pas une des quatre fins par
|
||||
mécanisme : c'est une fin **par décision**, et la distinguer est le but.
|
||||
|
||||
### Deux effets de `setChargingInfo()` trouvés en préparant l'essai — `+etm60`
|
||||
|
||||
`SmartChargingManager::setChargingInfo()` **déclenche lui-même un `update()`**. Écrire le mode
|
||||
n'est donc pas un geste neutre, et le lot le touche aux deux bouts.
|
||||
|
||||
**À la pose, l'ordre décide d'une minute.** L'écriture du mode vient **en dernier**, après
|
||||
l'insertion dans `m_derogations` : le cycle qu'elle déclenche trouve alors la dérogation déjà
|
||||
posée et la sert immédiatement. L'ordre inverse la lui faisait manquer d'un cycle — soixante
|
||||
secondes d'attente sur un mécanisme dont la définition est « tout de suite ».
|
||||
|
||||
**À la fin, la restitution est DIFFÉRÉE d'un tour de boucle.** Les fins sont évaluées *depuis*
|
||||
`update()` ; y appeler `setChargingInfo()` imbriquerait un `update()` dans un `update()` — deux
|
||||
plans construits et deux dispatchs pour le même instant, sur des charges qu'un seul des deux
|
||||
croirait avoir commandées. `update()` n'a aucun verrou de ré-entrance, donc rien ne l'aurait
|
||||
signalé. Le décalage ne se voit nulle part : l'événement de fin part dans la trame du cycle
|
||||
courant, et le mode est rendu avant le suivant.
|
||||
|
||||
**Effet heureux du retour à `Normal`** : l'amont restaure alors les valeurs manuelles
|
||||
(`manualChargingEnabled`, `manualMaxChargingCurrent`, `manualDesiredPhaseCount`). La borne
|
||||
retrouve donc *ses* réglages, et pas seulement son mode.
|
||||
|
||||
@ -1095,18 +1095,19 @@ bool EnergyArbitrator::poseDerogation(const QString &loadId, int dureeS, double
|
||||
// baseWh reste à capturer : au moment de la pose, aucun cycle n'a construit de contexte et
|
||||
// le cumul de session n'est lisible que là (cf. Derogation::baseCapturee).
|
||||
|
||||
ChargingInfo pendant;
|
||||
bool aBasculer = false;
|
||||
if (estBorne) {
|
||||
const ThingId id(loadId);
|
||||
const ChargingInfo avant = chargingInfo(id);
|
||||
const ChargingInfo avant = chargingInfo(ThingId(loadId));
|
||||
d.previousMode = static_cast<int>(avant.chargingMode());
|
||||
// Une borne en MANUEL est hors waterfall : le moteur ne la commande pas, et une
|
||||
// dérogation n'y produirait rien du tout. On la fait entrer dans l'arbitrage pour la
|
||||
// durée — et c'est le SEUL cas où le mode est touché.
|
||||
if (avant.chargingMode() == ChargingInfo::ChargingModeNormal) {
|
||||
ChargingInfo pendant = avant;
|
||||
pendant = avant;
|
||||
pendant.setChargingMode(ChargingInfo::ChargingModeEco);
|
||||
setChargingInfo(pendant);
|
||||
d.modeForce = true;
|
||||
aBasculer = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1119,6 +1120,13 @@ bool EnergyArbitrator::poseDerogation(const QString &loadId, int dureeS, double
|
||||
: QStringLiteral("sans cible"))
|
||||
.arg(d.previousMode)
|
||||
.arg(d.modeForce ? QStringLiteral(" (forcé en éco pour la durée)") : QString());
|
||||
|
||||
// L'ÉCRITURE DU MODE VIENT EN DERNIER, et l'ordre est décisif : setChargingInfo() déclenche
|
||||
// lui-même un cycle. La dérogation est donc DÉJÀ posée quand ce cycle passe, et il la sert
|
||||
// immédiatement. L'inverse la lui aurait fait manquer d'un cycle — soixante secondes
|
||||
// d'attente sur un mécanisme dont la définition est « tout de suite ».
|
||||
if (aBasculer)
|
||||
setChargingInfo(pendant);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1138,17 +1146,27 @@ bool EnergyArbitrator::leveDerogation(const QString &loadId)
|
||||
void EnergyArbitrator::rendreModeMemorise(const Derogation &d)
|
||||
{
|
||||
// \rule{LM-1500-d} — RETOUR AU MODE MÉMORISÉ, et à lui seul. Revenir à un défaut sortirait
|
||||
// une borne manuelle
|
||||
// de la main de son utilisateur — le contraire exact de ce qu'une dérogation promet, qui
|
||||
// est de rendre la main.
|
||||
// une borne manuelle de la main de son utilisateur — le contraire exact de ce qu'une
|
||||
// dérogation promet, qui est de rendre la main.
|
||||
if (!d.modeForce || d.previousMode < 0)
|
||||
return;
|
||||
const ThingId id(d.loadId);
|
||||
if (!internalEvChargers().contains(id))
|
||||
return; // la borne a disparu : il n'y a plus de réglage à rendre
|
||||
ChargingInfo info = chargingInfo(id);
|
||||
info.setChargingMode(static_cast<ChargingInfo::ChargingMode>(d.previousMode));
|
||||
setChargingInfo(info);
|
||||
const int mode = d.previousMode;
|
||||
|
||||
// DIFFÉRÉ D'UN TOUR DE BOUCLE, et ce n'est pas une précaution de style : les fins sont
|
||||
// évaluées DEPUIS update(), et setChargingInfo() déclenche lui-même un update(). Écrire ici
|
||||
// imbriquerait un cycle dans un cycle — deux plans construits et deux dispatchs pour le
|
||||
// même instant, sur des charges qu'un seul des deux croirait avoir commandées.
|
||||
//
|
||||
// Le décalage ne se voit nulle part : l'événement de fin est publié dans la trame de CE
|
||||
// cycle, et le mode est rendu avant le suivant.
|
||||
QTimer::singleShot(0, this, [this, id, mode]() {
|
||||
if (!internalEvChargers().contains(id))
|
||||
return; // la borne a disparu entre-temps : il n'y a plus de réglage à rendre
|
||||
ChargingInfo info = chargingInfo(id);
|
||||
info.setChargingMode(static_cast<ChargingInfo::ChargingMode>(mode));
|
||||
setChargingInfo(info);
|
||||
});
|
||||
}
|
||||
|
||||
void EnergyArbitrator::evaluerDerogations(SurplusContext &ctx, const QDateTime &now)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user