// 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 &EnergyArbitrator::scheduledActions() const { return internalChargingActions(); } void EnergyArbitrator::doExecuteChargingAction(EvCharger *charger, const ChargingAction &action, const QDateTime &now) { executeChargingAction(charger, action, now); } const QHash &EnergyArbitrator::registeredEvChargers() const { return internalEvChargers(); } RootMeter *EnergyArbitrator::registeredRootMeter() const { return internalRootMeter(); } void EnergyArbitrator::update(const QDateTime ¤tDateTime) { // 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(); } }