Bug : exportW clampé à max(0,-p) AVANT recrédit → sur-crédit en import (ECS restait allumé sur le réseau, ne délestait jamais). Fix : surplus net SIGNÉ (exportW - importW). Régime export inchangé. Le délestage strict est borné par minOn/minOff (protection compresseur, pas confort) : l'adaptateur expose minStage/maxStage (fenêtre de verrou évaluée au temps de cycle), le scheduler clampe bestStage et décrémente au palier réel → budget correct pour les charges suivantes (puissance verrouillée = engagée non-coupable). Seam de temps unifié : now=ctx.timestamp partagé par toLoadContext()/applyAction() ; lockWindow() est l'unique calcul, lockActive() en dérive (décision==exécution). Interface ILoadAdapter étendue (now) + contrat "temps=paramètre, jamais l'horloge" documenté pour les futurs adaptateurs. EvAdapter aligné. Build 0 erreur / 0 warning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
95 lines
3.2 KiB
C++
95 lines
3.2 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
|
|
|
|
#include "evadapter.h"
|
|
#include "../energyarbitrator.h"
|
|
#include "../../evcharger.h"
|
|
#include "../../types/chargingaction.h"
|
|
|
|
#include "plugininfo.h"
|
|
|
|
EvAdapter::EvAdapter(EvCharger *evCharger, EnergyArbitrator *parent)
|
|
: QObject(parent)
|
|
, m_charger(evCharger)
|
|
, m_parent(parent)
|
|
{
|
|
}
|
|
|
|
LoadDescriptor EvAdapter::descriptor() const
|
|
{
|
|
LoadDescriptor d;
|
|
d.id = m_charger->thing()->id().toString();
|
|
d.label = m_charger->name();
|
|
d.adapter = QStringLiteral("evcharger");
|
|
d.priority = 100;
|
|
|
|
d.declared.minA = m_charger->maxChargingCurrentMinValue();
|
|
d.declared.maxA = m_charger->maxChargingCurrentMaxValue();
|
|
d.declared.phases = static_cast<int>(m_charger->phaseCount());
|
|
|
|
d.limits.chargingEnabledLockS = static_cast<int>(m_charger->chargingEnabledLockDuration());
|
|
d.limits.currentChangeLockS = static_cast<int>(m_charger->chargingCurrentLockDuration());
|
|
|
|
d.supportedKinds = { LoadAction::Setpoint };
|
|
return d;
|
|
}
|
|
|
|
LoadTelemetry EvAdapter::telemetry() const
|
|
{
|
|
LoadTelemetry t;
|
|
t.currentPowerW = m_charger->currentPower();
|
|
t.available = m_charger->available();
|
|
t.lastActionAt = m_lastActionAt;
|
|
return t;
|
|
}
|
|
|
|
LoadContext EvAdapter::toLoadContext(const QDateTime &now) const
|
|
{
|
|
Q_UNUSED(now) // L'EV n'a pas de verrou de palier (pas de waterfall ECS) — now inutilisé ici.
|
|
LoadContext ctx;
|
|
ctx.id = m_charger->thing()->id().toString();
|
|
ctx.adapter = QStringLiteral("evcharger");
|
|
ctx.label = m_charger->name();
|
|
ctx.declared = descriptor().declared;
|
|
ctx.limits = descriptor().limits;
|
|
|
|
ctx.telemetry.currentPowerW = m_charger->currentPower();
|
|
ctx.telemetry.pluggedIn = m_charger->pluggedIn();
|
|
ctx.telemetry.charging = m_charger->charging();
|
|
return ctx;
|
|
}
|
|
|
|
LoadAction EvAdapter::applyAction(const LoadAction &action, const QDateTime &now)
|
|
{
|
|
if (action.kind != LoadAction::Setpoint)
|
|
return action;
|
|
|
|
if (action.reason.isEmpty()) {
|
|
qCWarning(dcNymeaEnergy()) << "[EvAdapter]" << m_charger->name()
|
|
<< "— LoadAction sans reason rejetée.";
|
|
return action;
|
|
}
|
|
|
|
const uint minA = m_charger->maxChargingCurrentMinValue();
|
|
const uint maxA = m_charger->maxChargingCurrentMaxValue();
|
|
const uint clampedA = static_cast<uint>(
|
|
qBound(static_cast<double>(minA), action.currentA, static_cast<double>(maxA)));
|
|
|
|
const uint phases = (m_charger->canSetPhaseCount() && action.phaseCount > 0)
|
|
? qBound(1u, action.phaseCount, m_charger->phaseCount())
|
|
: m_charger->phaseCount();
|
|
|
|
const auto issuer = (action.funding == LoadAction::Surplus)
|
|
? ChargingAction::ChargingActionIssuerSurplusCharging
|
|
: ChargingAction::ChargingActionIssuerTimeRequirement;
|
|
|
|
ChargingAction ca(action.chargingEnabled, clampedA, phases, issuer, false);
|
|
m_parent->doExecuteChargingAction(m_charger, ca, now); // now = temps de cycle (injectable)
|
|
m_lastActionAt = now;
|
|
|
|
LoadAction applied = action;
|
|
applied.currentA = clampedA;
|
|
applied.phaseCount = phases;
|
|
return applied;
|
|
}
|