fix(3g-1): le nombre de phases visé se décide avec la voiture branchée
Défaut de 3g-1, resté latent parce que sa sortie n'atteignait que la télémétrie. Il est corrigé à part du lot 3g-2 pour pouvoir être relu, contesté ou rétroporté sans lui. EvAdapter interrogeait EvCharger::canSetPhaseCount(), qui dit seulement que la borne PORTE l'état desiredPhaseCount. La vraie question — la borne ET la voiture acceptent-elles 1 et 3 phases — est tranchée une fois par cycle par prepareInformation(), qui en tire canSwitchPhaseCount et le couple min/maxPossiblePhaseCount. Les deux prédicats ont divergé : là où le proxy appliquait `qMin(carPhaseCount, getBestPhaseCount(...))`, 3g-1 n'a repris que l'appel. Conséquence : une borne dont la combinaison ne permet qu'une phase se voyait viser 3 phases. Son plancher publié triplait (1,4 kW → 4,1 kW) sans que rien ne puisse jamais le franchir — la borne restait éternellement « sous son plancher », avec un motif juste et une cause fausse. Second défaut, dans applyAction() : le nombre de phases demandé était borné par m_charger->phaseCount(), qui est l'état COURANT de la borne. Une borne en monophasé ne pouvait donc jamais être commandée en triphasé, et le plancher publié pour trois phases n'aurait de toute façon pas pu être servi. evTargetPhases() cesse d'être un passe-plat et lit les bornes du manager, via un accesseur [ETM] read-only de plus (internalProcessInfo). L'arbitre ne redérive rien : une seconde version de cette règle divergerait de la première au premier ajustement, ce qui est exactement ce qui vient d'arriver. Suite charging 16/16, simulation `run` 16/16, testLoadTelemetryRpc vert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TVywtayEeoRJ2yWUW4iZj
This commit is contained in:
parent
50dc3cc489
commit
99ad9a04cb
@ -63,10 +63,12 @@ LoadContext EvAdapter::toLoadContext(const QDateTime &now) const
|
||||
//
|
||||
// Le courant de référence pour ce choix est ce que la borne tire DÉJÀ : c'est la seule
|
||||
// grandeur connue avant l'allocation, et c'est celle que le proxy utilisait aussi.
|
||||
// Plus de test canSetPhaseCount() ici : la question « cette borne peut-elle basculer,
|
||||
// avec CETTE voiture » est tranchée une fois par le manager, et evTargetPhases() la lit.
|
||||
// Le prédicat local n'en voyait qu'une moitié (l'état existe-t-il) et retombait sur le
|
||||
// nombre de phases COURANT, sans la voiture.
|
||||
const double courantActuelA = m_charger->currentPower() / 230.0;
|
||||
m_targetPhases = m_charger->canSetPhaseCount()
|
||||
? m_parent->evTargetPhases(m_charger, courantActuelA)
|
||||
: m_charger->phaseCount();
|
||||
m_targetPhases = m_parent->evTargetPhases(m_charger, courantActuelA);
|
||||
if (m_targetPhases == 0)
|
||||
m_targetPhases = 1;
|
||||
|
||||
@ -123,8 +125,13 @@ LoadAction EvAdapter::applyAction(const LoadAction &action, const QDateTime &now
|
||||
//
|
||||
// Le discriminant est `currentA` : non nul, l'action vient du proxy et s'exécute telle
|
||||
// quelle ; nul avec une puissance, elle vient de la cascade et se convertit.
|
||||
// 3g-2 — le nombre de phases VISÉ, pas le nombre courant. Le qBound bornait la cible par
|
||||
// m_charger->phaseCount(), qui est l'état COURANT de la borne : une borne en monophasé ne
|
||||
// pouvait donc jamais être commandée en triphasé, et le plancher publié pour 3 phases
|
||||
// n'aurait jamais pu être servi. Le plafond véhicule est déjà appliqué en amont, par
|
||||
// EnergyArbitrator::evTargetPhases().
|
||||
const uint phases = (m_charger->canSetPhaseCount() && action.phaseCount > 0)
|
||||
? qBound(1u, action.phaseCount, m_charger->phaseCount())
|
||||
? qBound(1u, action.phaseCount, 3u)
|
||||
: (m_targetPhases > 0 ? m_targetPhases : m_charger->phaseCount());
|
||||
|
||||
double demandeA = action.currentA;
|
||||
|
||||
@ -435,6 +435,25 @@ double EnergyArbitrator::evFloorW(EvCharger *ev, uint phaseCount) const
|
||||
return minA * acquisitionTolerance() * 230.0 * phaseCount;
|
||||
}
|
||||
|
||||
uint EnergyArbitrator::evTargetPhases(EvCharger *ev, double referenceA)
|
||||
{
|
||||
if (!ev)
|
||||
return 1;
|
||||
|
||||
// Les bornes de ce qui est POSSIBLE viennent du manager, qui les a établies ce cycle avec
|
||||
// la voiture branchée (canSwitchPhaseCount, min/maxPossiblePhaseCount). Les redériver ici
|
||||
// en ferait une seconde version — et c'est exactement ce qui a divergé en 3g-1 : viser
|
||||
// 3 phases sur une combinaison qui n'en permet qu'une triplait le plancher publié, et sous
|
||||
// surcharge commandait 3 × 6 A là où le proxy commandait 1 × 6 A.
|
||||
const ChargingProcessInfo pi = internalProcessInfo(ev);
|
||||
if (!pi.canSwitchPhaseCount)
|
||||
return qMax(1u, pi.maxPossiblePhaseCount);
|
||||
|
||||
const uint phases = bestPhaseCountFor(ev, referenceA);
|
||||
return qMax(1u, qBound(pi.minPossiblePhaseCount, phases, pi.maxPossiblePhaseCount));
|
||||
}
|
||||
|
||||
|
||||
SurplusContext EnergyArbitrator::buildContext(const QDateTime &now) const
|
||||
{
|
||||
SurplusContext ctx;
|
||||
|
||||
@ -215,12 +215,23 @@ public:
|
||||
/*!
|
||||
* \brief [3g-1] Nombre de phases visé pour une borne, à un courant donné.
|
||||
*
|
||||
* Simple passe-plat vers la règle de bascule 1↔3 du manager. L'arbitre est la porte
|
||||
* d'entrée de l'adaptateur vers cette règle : la réécrire dans l'adaptateur en ferait
|
||||
* deux versions, et elles divergeraient au premier ajustement.
|
||||
* Passe-plat vers la règle de bascule 1↔3 du manager, PLUS le plafond du véhicule.
|
||||
* L'arbitre est la porte d'entrée de l'adaptateur vers cette règle : la réécrire dans
|
||||
* l'adaptateur en ferait deux versions, et elles divergeraient au premier ajustement.
|
||||
*
|
||||
* \note **[3g-2] Le plafond véhicule fait partie de la règle.** \c getBestPhaseCount()
|
||||
* ne le connaît pas : le proxy appliquait `qMin(carPhaseCount, …)` juste après l'appel.
|
||||
* 3g-1 n'a repris que l'appel, si bien qu'une voiture monophasée pouvait se voir viser
|
||||
* 3 phases — et le plancher publié triplait avec elle, sans qu'aucune allocation ne
|
||||
* puisse jamais la démarrer. Le plafond est donc rendu ici, au seul endroit qui parle
|
||||
* de phases pour le waterfall.
|
||||
*
|
||||
* \param ev Borne concernée.
|
||||
* \param referenceA Courant de référence, en ampères TOTAUX toutes phases confondues —
|
||||
* voir \c evReferenceCurrentA(). Peut être négatif en soutirage.
|
||||
* \return Nombre de phases visé, dans [1, 3], jamais 0.
|
||||
*/
|
||||
uint evTargetPhases(EvCharger *ev, double referenceA)
|
||||
{ return bestPhaseCountFor(ev, referenceA); }
|
||||
uint evTargetPhases(EvCharger *ev, double referenceA);
|
||||
|
||||
/*!
|
||||
* \brief [3g-1] Plancher de modulation d'une borne, en watts, pour le nombre de phases VISÉ.
|
||||
|
||||
@ -122,8 +122,8 @@ signals:
|
||||
// — visibilité seule (private → protected).
|
||||
// Sémantique amont inchangée : NON documentés
|
||||
// ici, la doc appartient à l'amont.
|
||||
// 4. internalEvChargers(), internalChargingActions(), internalRootMeter()
|
||||
// — accesseurs ETM neufs, documentés ci-dessous.
|
||||
// 4. internalEvChargers(), internalChargingActions(), internalRootMeter(),
|
||||
// internalProcessInfo() — accesseurs ETM neufs, documentés ci-dessous.
|
||||
protected slots:
|
||||
virtual void update(const QDateTime ¤tDateTime); // [ETM] virtual added
|
||||
void prepareInformation(const QDateTime ¤tDateTime); // [ETM] private → protected
|
||||
@ -168,6 +168,23 @@ protected:
|
||||
* configuré — tout appelant doit le tester.
|
||||
*/
|
||||
RootMeter *internalRootMeter() const { return m_rootMeter; } // [ETM] new
|
||||
|
||||
/*!
|
||||
* \brief [ETM 3g-2] Informations de processus d'une borne pour le cycle courant.
|
||||
*
|
||||
* \param evCharger Borne. \return Copie de son \c ChargingProcessInfo ; valeurs par
|
||||
* défaut si la borne n'a pas été préparée ce cycle.
|
||||
*
|
||||
* \note N'a de sens qu'APRÈS \c prepareInformation() dans le même cycle — position 2 de
|
||||
* \c update(), donc avant toute construction de contexte.
|
||||
* \note Existe pour que l'arbitre n'ait pas à REDÉRIVER le nombre de phases possible.
|
||||
* \c EvCharger::canSetPhaseCount() dit seulement que la borne PORTE l'état
|
||||
* \c desiredPhaseCount ; la vraie question — la borne ET la voiture acceptent-elles 1 et
|
||||
* 3 phases — est tranchée ici, une fois, avec le véhicule branché. Les deux prédicats ont
|
||||
* divergé en 3g-1 : une borne monophasée sans voiture triphasée s'est vue viser 3 phases,
|
||||
* et sous surcharge cela commandait 3 × 6 A là où le proxy commandait 1 × 6 A.
|
||||
*/
|
||||
ChargingProcessInfo internalProcessInfo(EvCharger *evCharger) const { return m_processInfos.value(evCharger); } // [ETM] new
|
||||
// [ETM] END
|
||||
|
||||
private slots:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user