// 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) { // 1-4 : préparation + sécurité (ordre garanti, identique à l'amont) updateManualSoCsWithoutMeter(currentDateTime); prepareInformation(currentDateTime); verifyOverloadProtection(currentDateTime); verifyOverloadProtectionRecovery(currentDateTime); // 5 : planification via IScheduler (RuleBasedScheduler pour l'instant) syncAdapters(); SurplusContext ctx = buildContext(currentDateTime); Plan plan = m_scheduler->getPlan(ctx); Slot slot = plan.slotCovering(currentDateTime); // 6 : log des decisionReason (DoD 3b) 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 + mise à jour des états de charge (via amont iso-fonctionnel) 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(); } }