feat(3g-1): la prise en compte du stockage monte dans le budget
Première moitié de 3g-1. La règle batterie vivait dans planSurplusCharging(), donc dans le chemin VE — par accident d'histoire, pas par conception. Ce n'a jamais été une règle « véhicule » : c'est une règle de BUDGET. Tant qu'elle y restait, une batterie sous sa réserve empêchait de charger la voiture mais laissait le chauffe-eau se servir : deux poids, deux mesures pour la même énergie. buildContext() remplit désormais ctx.battery (champ qui existait, resté vide depuis « déféré 3f »), et le waterfall applique les quatre comportements de l'amont : sous la réserve le budget est ANNULÉ (et non réduit — servir « un peu » retarderait la recharge sans démarrer personne, planchers du §12) ; batterie en charge sous le seuil, tampon de 500 W ; en charge au-dessus, sa puissance rejoint le budget ; en décharge, sa puissance est retranchée quel que soit le niveau, car ce qu'elle fournit n'est pas du surplus. ⚠️ CHANGEMENT DE COMPORTEMENT assumé pour l'ECS et la PAC, qui n'ont jamais connu cette règle. Une installation qui servait ses charges sous le seuil de réserve ne le fera plus. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPUo3RMr8SzK6qbFtfBm8H
This commit is contained in:
parent
fa45cb12c8
commit
36394cc351
@ -415,7 +415,30 @@ SurplusContext EnergyArbitrator::buildContext(const QDateTime &now) const
|
||||
};
|
||||
}
|
||||
// SurplusPv : interface inverter — déféré (remplissage prévu en 3d)
|
||||
// SurplusBattery : déféré 3f
|
||||
|
||||
// --- Stockage (3g-1) ------------------------------------------------------------------
|
||||
// Rempli ICI et plus dans le chemin VE. La prise en compte de la batterie n'a jamais été
|
||||
// une règle « véhicule » : c'est une règle de BUDGET, et elle vivait dans
|
||||
// planSurplusCharging() par accident d'histoire. Tant qu'elle y restait, une batterie sous
|
||||
// sa réserve empêchait de charger la voiture mais laissait le chauffe-eau se servir.
|
||||
//
|
||||
// Toutes les batteries sont agrégées en une seule — même simplification que l'amont : un
|
||||
// niveau moyen et une puissance totale. Distinguer deux stockages demanderait de savoir
|
||||
// lequel arbitrer en premier, et rien dans le modèle ne le dit.
|
||||
{
|
||||
int n = 0;
|
||||
double niveauTotal = 0, puissanceTotale = 0;
|
||||
for (Thing *storage : m_tm->configuredThings().filterByInterface("energystorage")) {
|
||||
++n;
|
||||
niveauTotal += storage->stateValue("batteryLevel").toDouble();
|
||||
puissanceTotale += storage->stateValue("currentPower").toDouble();
|
||||
}
|
||||
if (n > 0) {
|
||||
ctx.battery.present = true;
|
||||
ctx.battery.socPercent = niveauTotal / n;
|
||||
ctx.battery.powerW = puissanceTotale; // > 0 charge, < 0 décharge
|
||||
}
|
||||
}
|
||||
|
||||
// --- loads[] : EV adapters --- (now = ctx.timestamp : source unique des verrous)
|
||||
for (auto it = m_adapters.constBegin(); it != m_adapters.constEnd(); ++it)
|
||||
|
||||
@ -96,6 +96,45 @@ Plan RuleBasedScheduler::getPlan(const SurplusContext &ctx)
|
||||
// transitoire sous minOn est géré par le verrou de l'adaptateur, pas par le budget.)
|
||||
double remainingSurplusW = (ctx.meter.exportW - ctx.meter.importW) - evReservedW;
|
||||
|
||||
// ---- Prise en compte du stockage (3g-1) ---------------------------------------
|
||||
// Remontée du chemin VE, où elle vivait par accident d'histoire. Ce n'a jamais été une
|
||||
// règle « véhicule » : c'est une règle de BUDGET. Tant qu'elle restait dans
|
||||
// planSurplusCharging(), une batterie sous sa réserve empêchait de charger la voiture mais
|
||||
// laissait le chauffe-eau se servir — deux poids, deux mesures pour la même énergie.
|
||||
//
|
||||
// ⚠️ CHANGEMENT DE COMPORTEMENT pour l'ECS et la PAC, qui n'ont jamais connu cette règle.
|
||||
// Une installation qui servait ses charges sous le seuil de réserve ne le fera plus.
|
||||
if (ctx.battery.present) {
|
||||
const double seuil = m_arbitrator->batteryLevelConsideration() * 100.0;
|
||||
|
||||
if (ctx.battery.socPercent < seuil) {
|
||||
// Sous la réserve : le stockage passe AVANT toute charge pilotée. On ne réduit pas
|
||||
// le budget, on l'annule — servir « un peu » reviendrait à retarder la recharge de
|
||||
// la batterie tout en ne démarrant probablement personne (planchers du §12).
|
||||
if (remainingSurplusW > 0)
|
||||
qCDebug(dcNymeaEnergy()).nospace()
|
||||
<< "[Waterfall] Stockage à " << ctx.battery.socPercent << "% sous la réserve de "
|
||||
<< seuil << "% : budget de surplus annulé (" << remainingSurplusW << " W).";
|
||||
remainingSurplusW = qMin(remainingSurplusW, 0.0);
|
||||
|
||||
// La batterie charge sous son seuil : lui laisser de quoi le faire. Le tampon est
|
||||
// repris tel quel de l'amont — il évite que le waterfall ne mange exactement ce
|
||||
// que la batterie s'apprêtait à prendre.
|
||||
if (ctx.battery.powerW > 0)
|
||||
remainingSurplusW -= 500;
|
||||
} else if (ctx.battery.powerW > 0) {
|
||||
// Au-dessus de la réserve et en charge : cette puissance est disponible pour les
|
||||
// charges pilotées — les servir vaut mieux que de remplir une batterie déjà sûre.
|
||||
remainingSurplusW += ctx.battery.powerW;
|
||||
}
|
||||
|
||||
if (ctx.battery.powerW < 0) {
|
||||
// Décharge : ne pas vider la batterie pour alimenter une charge pilotée, quel que
|
||||
// soit le niveau. Ce qu'elle fournit n'est pas du surplus.
|
||||
remainingSurplusW += ctx.battery.powerW; // powerW < 0 → retranche
|
||||
}
|
||||
}
|
||||
|
||||
// Comptabilité PUBLIÉE du budget (télémétrie RPC). Elle s'alimente DANS la cascade,
|
||||
// pendant que celle-ci se déroule : c'est le seul endroit qui sache ce qui a réellement
|
||||
// été alloué et ce qui a été recrédité. La recalculer ailleurs donnerait deux comptes à
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user