- energyplugin/energyplugin.pri : décommente DEFINES += ETM_ARBITRATOR (flip actif)
- energyplugin/etm/energyarbitrator.cpp : ajoute qCDebug "Updating smart charging" en tête
de update() — comparabilité des logs avec l'amont garantie
- AGENTS.md : 3b → ✅ FAITE, chiffres de preuve, prochaine action 3c
Preuve iso-fonctionnalité :
- Simulation : 226 lignes décisions (Theoretically/Surplus/Current load) — diff = 0
- Tests charging : 57 lignes décisions — diff = 0 ; 46/46 PASS ref ET ETM
- [Arbitre] présents avec raisons françaises (idle, surplus PV, aWATTar, deadline)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
3.9 KiB
C++
112 lines
3.9 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
|
|
|
|
#include "energyarbitrator.h"
|
|
#include "adapters/evadapter.h"
|
|
#include "scheduler/rulebasedscheduler.h"
|
|
#include "types/surpluscontext.h"
|
|
#include "types/plan.h"
|
|
|
|
#include "plugininfo.h"
|
|
|
|
EnergyArbitrator::EnergyArbitrator(EnergyManager *em, ThingManager *tm,
|
|
SpotMarketManager *sm,
|
|
EnergyManagerConfiguration *conf,
|
|
QObject *parent)
|
|
: SmartChargingManager(em, tm, sm, conf, parent)
|
|
, m_scheduler(new RuleBasedScheduler(this, this))
|
|
{
|
|
qCDebug(dcNymeaEnergy()) << "[EnergyArbitrator] Arbitre ETM initialisé.";
|
|
}
|
|
|
|
void EnergyArbitrator::runSurplusPlanning(const QDateTime &now)
|
|
{
|
|
planSurplusCharging(now);
|
|
}
|
|
|
|
void EnergyArbitrator::runSpotMarketPlanning(const QDateTime &now)
|
|
{
|
|
planSpotMarketCharging(now);
|
|
}
|
|
|
|
const QHash<EvCharger *, ChargingActions> &EnergyArbitrator::scheduledActions() const
|
|
{
|
|
return internalChargingActions();
|
|
}
|
|
|
|
void EnergyArbitrator::doExecuteChargingAction(EvCharger *charger,
|
|
const ChargingAction &action,
|
|
const QDateTime &now)
|
|
{
|
|
executeChargingAction(charger, action, now);
|
|
}
|
|
|
|
const QHash<ThingId, EvCharger *> &EnergyArbitrator::registeredEvChargers() const
|
|
{
|
|
return internalEvChargers();
|
|
}
|
|
|
|
RootMeter *EnergyArbitrator::registeredRootMeter() const
|
|
{
|
|
return internalRootMeter();
|
|
}
|
|
|
|
void EnergyArbitrator::update(const QDateTime ¤tDateTime)
|
|
{
|
|
qCDebug(dcNymeaEnergy()) << "Updating smart charging";
|
|
// Ordre IDENTIQUE à SmartChargingManager::update() — INTERDIT de réordonner.
|
|
// SCM : 1.updateManual 2.prepareInfo 3.verifyOverload 4.verifyRecovery
|
|
// 5.planSpot 6.planSurplus 7.adjustEv
|
|
// ETM : idem 1-4 ; insertions ETM entre 4 et 7 ;
|
|
// planSpot + planSurplus appelés via m_scheduler->getPlan() (position 5-6).
|
|
|
|
// 1-4 : préparation + sécurité (même ordre que l'amont)
|
|
updateManualSoCsWithoutMeter(currentDateTime);
|
|
prepareInformation(currentDateTime);
|
|
verifyOverloadProtection(currentDateTime);
|
|
verifyOverloadProtectionRecovery(currentDateTime);
|
|
|
|
// ETM-only : sync adapters + proxy planification → log [Arbitre]
|
|
// getPlan() appelle planSpotMarketCharging() + planSurplusCharging() (position 5-6 amont).
|
|
syncAdapters();
|
|
SurplusContext ctx = buildContext(currentDateTime);
|
|
Plan plan = m_scheduler->getPlan(ctx);
|
|
Slot slot = plan.slotCovering(currentDateTime);
|
|
|
|
for (const LoadAction &action : slot.actions) {
|
|
qCInfo(dcNymeaEnergy()) << "[Arbitre]"
|
|
<< action.loadId << "→" << action.reason
|
|
<< "| activé:" << action.chargingEnabled
|
|
<< "| courant:" << action.currentA << "A"
|
|
<< "| phases:" << action.phaseCount
|
|
<< "| stratégie:" << plan.strategy;
|
|
}
|
|
|
|
// 7 : dispatch matériel (même position que l'amont — m_chargingActions rempli par getPlan())
|
|
adjustEvChargers(currentDateTime);
|
|
}
|
|
|
|
SurplusContext EnergyArbitrator::buildContext(const QDateTime &now) const
|
|
{
|
|
// 3b stub — seul le timestamp est renseigné.
|
|
// Contexte §5 complet (site/meter/pv/battery/loads) prévu en 3d.
|
|
SurplusContext ctx;
|
|
ctx.timestamp = now;
|
|
return ctx;
|
|
}
|
|
|
|
void EnergyArbitrator::syncAdapters()
|
|
{
|
|
// Crée les adapters manquants
|
|
for (auto it = internalEvChargers().constBegin(); it != internalEvChargers().constEnd(); ++it) {
|
|
const QString id = it.key().toString();
|
|
if (!m_adapters.contains(id))
|
|
m_adapters[id] = new EvAdapter(it.value(), this);
|
|
}
|
|
// Supprime les adapters obsolètes
|
|
for (const QString &id : m_adapters.keys()) {
|
|
if (!internalEvChargers().contains(ThingId(id)))
|
|
m_adapters.take(id)->deleteLater();
|
|
}
|
|
}
|