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
e60c52f704
commit
f2c2fb083e
@ -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
|
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
|
* 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.
|
(`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
|
- `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.
|
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
|
// 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).
|
// le cumul de session n'est lisible que là (cf. Derogation::baseCapturee).
|
||||||
|
|
||||||
|
ChargingInfo pendant;
|
||||||
|
bool aBasculer = false;
|
||||||
if (estBorne) {
|
if (estBorne) {
|
||||||
const ThingId id(loadId);
|
const ChargingInfo avant = chargingInfo(ThingId(loadId));
|
||||||
const ChargingInfo avant = chargingInfo(id);
|
|
||||||
d.previousMode = static_cast<int>(avant.chargingMode());
|
d.previousMode = static_cast<int>(avant.chargingMode());
|
||||||
// Une borne en MANUEL est hors waterfall : le moteur ne la commande pas, et une
|
// 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
|
// 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é.
|
// durée — et c'est le SEUL cas où le mode est touché.
|
||||||
if (avant.chargingMode() == ChargingInfo::ChargingModeNormal) {
|
if (avant.chargingMode() == ChargingInfo::ChargingModeNormal) {
|
||||||
ChargingInfo pendant = avant;
|
pendant = avant;
|
||||||
pendant.setChargingMode(ChargingInfo::ChargingModeEco);
|
pendant.setChargingMode(ChargingInfo::ChargingModeEco);
|
||||||
setChargingInfo(pendant);
|
|
||||||
d.modeForce = true;
|
d.modeForce = true;
|
||||||
|
aBasculer = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1119,6 +1120,13 @@ bool EnergyArbitrator::poseDerogation(const QString &loadId, int dureeS, double
|
|||||||
: QStringLiteral("sans cible"))
|
: QStringLiteral("sans cible"))
|
||||||
.arg(d.previousMode)
|
.arg(d.previousMode)
|
||||||
.arg(d.modeForce ? QStringLiteral(" (forcé en éco pour la durée)") : QString());
|
.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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1138,17 +1146,27 @@ bool EnergyArbitrator::leveDerogation(const QString &loadId)
|
|||||||
void EnergyArbitrator::rendreModeMemorise(const Derogation &d)
|
void EnergyArbitrator::rendreModeMemorise(const Derogation &d)
|
||||||
{
|
{
|
||||||
// \rule{LM-1500-d} — RETOUR AU MODE MÉMORISÉ, et à lui seul. Revenir à un défaut sortirait
|
// \rule{LM-1500-d} — RETOUR AU MODE MÉMORISÉ, et à lui seul. Revenir à un défaut sortirait
|
||||||
// une borne manuelle
|
// une borne manuelle de la main de son utilisateur — le contraire exact de ce qu'une
|
||||||
// de la main de son utilisateur — le contraire exact de ce qu'une dérogation promet, qui
|
// dérogation promet, qui est de rendre la main.
|
||||||
// est de rendre la main.
|
|
||||||
if (!d.modeForce || d.previousMode < 0)
|
if (!d.modeForce || d.previousMode < 0)
|
||||||
return;
|
return;
|
||||||
const ThingId id(d.loadId);
|
const ThingId id(d.loadId);
|
||||||
if (!internalEvChargers().contains(id))
|
const int mode = d.previousMode;
|
||||||
return; // la borne a disparu : il n'y a plus de réglage à rendre
|
|
||||||
ChargingInfo info = chargingInfo(id);
|
// DIFFÉRÉ D'UN TOUR DE BOUCLE, et ce n'est pas une précaution de style : les fins sont
|
||||||
info.setChargingMode(static_cast<ChargingInfo::ChargingMode>(d.previousMode));
|
// évaluées DEPUIS update(), et setChargingInfo() déclenche lui-même un update(). Écrire ici
|
||||||
setChargingInfo(info);
|
// 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)
|
void EnergyArbitrator::evaluerDerogations(SurplusContext &ctx, const QDateTime &now)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user