correction logique commande priorisation
This commit is contained in:
574
WINDOWS/WINDOW_SOURCEV2.js
Normal file
574
WINDOWS/WINDOW_SOURCEV2.js
Normal file
@@ -0,0 +1,574 @@
|
||||
function minutesSince(ts, nowMs) {
|
||||
if (ts === null || nowMs === null) return null;
|
||||
return Math.max(0, (nowMs - ts) / 60000);
|
||||
}
|
||||
|
||||
function secondsSince(ts, nowMs) {
|
||||
if (ts === null || nowMs === null) return null;
|
||||
return Math.max(0, (nowMs - ts) / 1000);
|
||||
}
|
||||
|
||||
function isWithinWindow(nowMin, startMin, stopMin) {
|
||||
if (!Number.isFinite(startMin) || !Number.isFinite(stopMin)) return false;
|
||||
if (startMin === stopMin) return false;
|
||||
if (startMin < stopMin) return nowMin >= startMin && nowMin < stopMin;
|
||||
return nowMin >= startMin || nowMin < stopMin;
|
||||
}
|
||||
|
||||
function clamp(v, min, max) {
|
||||
return Math.min(Math.max(v, min), max);
|
||||
}
|
||||
|
||||
const prev = $input.first().json;
|
||||
const {
|
||||
input,
|
||||
nowMs,
|
||||
now,
|
||||
cfg,
|
||||
parsed,
|
||||
params,
|
||||
history,
|
||||
batteryTune
|
||||
} = prev;
|
||||
|
||||
// =====================================================
|
||||
// STRATEGY
|
||||
// =====================================================
|
||||
const userStrategy = (params.userStrategyRaw ?? 'auto_solar').toString().trim().toLowerCase();
|
||||
|
||||
const isStopMode = userStrategy === 'stop';
|
||||
const forceGridRequested = userStrategy === 'force_grid_now';
|
||||
const forceChargeRequested = userStrategy === 'force_charge_now';
|
||||
const isForceMode = forceGridRequested || forceChargeRequested;
|
||||
|
||||
// =====================================================
|
||||
// LIFECYCLE / TIMING
|
||||
// =====================================================
|
||||
const minutesSinceStart = minutesSince(history.lastStartMs, nowMs);
|
||||
const minutesSinceStop = minutesSince(history.lastStopMs, nowMs);
|
||||
const secondsSinceCurrentChange = secondsSince(history.lastCurrentChangeMs, nowMs);
|
||||
const minutesSinceProbeStart = minutesSince(history.probeStartedMs, nowMs);
|
||||
|
||||
const minRunElapsed =
|
||||
minutesSinceStart === null ? true : minutesSinceStart >= params.minRunMinutes;
|
||||
|
||||
const minStopElapsed =
|
||||
minutesSinceStop === null ? true : minutesSinceStop >= params.minStopMinutes;
|
||||
|
||||
const currentCooldownBaseSec = parsed.evcsChargingSwitchOn
|
||||
? batteryTune.global.stepDownCooldownSeconds
|
||||
: batteryTune.global.stepUpCooldownSeconds;
|
||||
|
||||
const currentCooldownElapsed =
|
||||
secondsSinceCurrentChange === null
|
||||
? true
|
||||
: secondsSinceCurrentChange >= Math.max(
|
||||
params.currentChangeCooldownSeconds,
|
||||
currentCooldownBaseSec
|
||||
);
|
||||
|
||||
// =====================================================
|
||||
// DEPARTURE PRIORITY
|
||||
// =====================================================
|
||||
const minutesUntilDeparture =
|
||||
params.departureTimeMin === null
|
||||
? null
|
||||
: (() => {
|
||||
const rawDiff = params.departureTimeMin - now.minNow;
|
||||
return rawDiff >= 0 ? rawDiff : (1440 - now.minNow + params.departureTimeMin);
|
||||
})();
|
||||
|
||||
const departurePriorityActive =
|
||||
parsed.departurePriorityEnabled &&
|
||||
Number.isFinite(minutesUntilDeparture) &&
|
||||
minutesUntilDeparture >= 0 &&
|
||||
minutesUntilDeparture <= cfg.departurePriorityWindowMinutes &&
|
||||
parsed.evcsVehicleConnected;
|
||||
|
||||
// =====================================================
|
||||
// MANUAL CHARGE SEMANTICS
|
||||
// IMPORTANT:
|
||||
// force_charge_now = bypass fenêtres pour la charge,
|
||||
// MAIS PAS demande implicite de réseau.
|
||||
// =====================================================
|
||||
const manualChargeBypassWindows = forceChargeRequested;
|
||||
const manualChargeLocalRequested = forceChargeRequested && !forceGridRequested;
|
||||
|
||||
// Compatibilité ascendante : on garde le champ dans context,
|
||||
// mais on le neutralise volontairement.
|
||||
const forceChargeDepartureGridOverride = false;
|
||||
|
||||
// =====================================================
|
||||
// SYSTEM DERIVED VALUES
|
||||
// =====================================================
|
||||
const acVoltageRef =
|
||||
Number.isFinite(parsed.vebusAcOutVoltageV) && parsed.vebusAcOutVoltageV > 180
|
||||
? parsed.vebusAcOutVoltageV
|
||||
: cfg.nominalAcVoltageV;
|
||||
|
||||
const batteryVoltageRef =
|
||||
Number.isFinite(parsed.vebusDcVoltageV) && parsed.vebusDcVoltageV > 0
|
||||
? parsed.vebusDcVoltageV
|
||||
: 51.2;
|
||||
|
||||
const houseNonEvcsW = Math.max(0, parsed.houseAcPowerW - parsed.evcsAcPowerW);
|
||||
const vebusNonEvcsW = Math.max(0, parsed.vebusAcOutPowerW - parsed.evcsAcPowerW);
|
||||
|
||||
const totalVebusSoftCeilingW = Math.max(6500, params.acLoadsMaxSafeW || 8500);
|
||||
const totalVebusHardCeilingW = totalVebusSoftCeilingW + 400;
|
||||
|
||||
const safeEvcsByDedicatedCapW = Math.max(0, cfg.evcsDedicatedMaxW - cfg.systemReserveW);
|
||||
const safeEvcsByVebusW = Math.max(0, totalVebusSoftCeilingW - vebusNonEvcsW - cfg.systemReserveW);
|
||||
const safeEvcsCombinedW = Math.max(0, Math.min(safeEvcsByDedicatedCapW, safeEvcsByVebusW));
|
||||
|
||||
const systemCapCurrentA = Math.max(0, Math.floor(safeEvcsCombinedW / acVoltageRef));
|
||||
const physicalCapA = clamp(systemCapCurrentA, 0, params.currentMaxAutoA);
|
||||
const canRunAtMinCurrent = physicalCapA >= params.currentMinA;
|
||||
|
||||
const endDayGuardSoc = Math.max(params.endOfDaySocGuard, params.endOfDaySocTarget);
|
||||
|
||||
const energyNeededForEndDayGuardKwh = Math.max(
|
||||
0,
|
||||
((endDayGuardSoc - parsed.soc) / 100) * cfg.batteryUsableCapacityKwh
|
||||
);
|
||||
|
||||
const forecastBufferKwh =
|
||||
parsed.todayRemainingKwh -
|
||||
params.baseLoadRemainingKwh -
|
||||
params.flexibleSafetyMarginKwh -
|
||||
params.requiredEnergyKwh -
|
||||
energyNeededForEndDayGuardKwh;
|
||||
|
||||
const projectedEndSocAfterCharge = clamp(
|
||||
parsed.soc + (
|
||||
(
|
||||
parsed.todayRemainingKwh -
|
||||
params.baseLoadRemainingKwh -
|
||||
params.flexibleSafetyMarginKwh -
|
||||
params.requiredEnergyKwh
|
||||
) / cfg.batteryUsableCapacityKwh
|
||||
) * 100,
|
||||
0,
|
||||
100
|
||||
);
|
||||
|
||||
const currentReferenceA = clamp(
|
||||
Number.isFinite(parsed.evcsCommandCurrentA)
|
||||
? parsed.evcsCommandCurrentA
|
||||
: Number.isFinite(parsed.evcsSetCurrentA)
|
||||
? parsed.evcsSetCurrentA
|
||||
: params.targetCurrentAutoA,
|
||||
0,
|
||||
32
|
||||
);
|
||||
|
||||
const anyVebusOverload = parsed.vebusOverload || parsed.vebusL1Overload;
|
||||
const anyVebusLowBattery = parsed.vebusLowBattery || parsed.vebusL1LowBattery;
|
||||
|
||||
const vebusAcOutSoftReached = parsed.vebusAcOutPowerW >= totalVebusSoftCeilingW;
|
||||
const vebusAcOutHardReached = parsed.vebusAcOutPowerW >= totalVebusHardCeilingW;
|
||||
|
||||
const vebusDcVoltageSoftLow =
|
||||
Number.isFinite(parsed.vebusDcVoltageV) &&
|
||||
parsed.vebusDcVoltageV > 0 &&
|
||||
parsed.vebusDcVoltageV <= cfg.vebusDcVoltageSoftFloorV;
|
||||
|
||||
const vebusDcVoltageHardLow =
|
||||
Number.isFinite(parsed.vebusDcVoltageV) &&
|
||||
parsed.vebusDcVoltageV > 0 &&
|
||||
parsed.vebusDcVoltageV <= cfg.vebusDcVoltageHardFloorV;
|
||||
|
||||
const houseLoadHighVsHeadroom = houseNonEvcsW > params.houseAcHeadroomW;
|
||||
|
||||
// =====================================================
|
||||
// WINDOWS / PROFILE
|
||||
// =====================================================
|
||||
const inNightHcWindow = isWithinWindow(now.minNow, params.hcNightStartMin, params.hcNightStopMin);
|
||||
const inMorningWindow = isWithinWindow(now.minNow, params.morningStartMin, params.morningStopMin);
|
||||
const inMiddayWindow = isWithinWindow(now.minNow, params.hcMiddayStartMin, params.hcMiddayStopMin);
|
||||
const inEveningWindow = isWithinWindow(now.minNow, params.eveningStartMin, params.eveningStopMin);
|
||||
|
||||
const profile =
|
||||
inNightHcWindow ? 'night_hc' :
|
||||
inMorningWindow ? 'morning_solar' :
|
||||
inMiddayWindow ? 'midday_hc_or_solar' :
|
||||
inEveningWindow ? 'evening_high_soc' :
|
||||
'outside_window';
|
||||
|
||||
// =====================================================
|
||||
// EVCS NIGHT PREVIEW ENGINE
|
||||
// =====================================================
|
||||
const nightSessionActive =
|
||||
profile === 'night_hc' &&
|
||||
parsed.evcsVehicleConnected;
|
||||
|
||||
const nightSessionStartSocPct =
|
||||
nightSessionActive
|
||||
? (
|
||||
Number.isFinite(history.nightSessionStartSocPct)
|
||||
? history.nightSessionStartSocPct
|
||||
: parsed.soc
|
||||
)
|
||||
: null;
|
||||
|
||||
const nightSessionStartAt =
|
||||
nightSessionActive
|
||||
? (history.nightSessionStartAt || '')
|
||||
: '';
|
||||
|
||||
const nightSessionNeedsCapture =
|
||||
nightSessionActive &&
|
||||
(
|
||||
!Number.isFinite(history.nightSessionStartSocPct) ||
|
||||
!history.nightSessionStartAt
|
||||
);
|
||||
|
||||
const nightSessionNeedsClear =
|
||||
!nightSessionActive &&
|
||||
(
|
||||
Number.isFinite(history.nightSessionStartSocPct) ||
|
||||
!!history.nightSessionStartAt
|
||||
);
|
||||
|
||||
const batteryHardFloorPct =
|
||||
params.evcsNightBatteryHardFloorPct;
|
||||
|
||||
const batteryMorningTargetPct =
|
||||
parsed.todayRemainingKwh >= params.evcsNightStrongForecastKwh
|
||||
? params.evcsNightTargetStrongForecastPct
|
||||
: parsed.todayRemainingKwh >= params.evcsNightMediumForecastKwh
|
||||
? params.evcsNightTargetMediumForecastPct
|
||||
: params.evcsNightTargetWeakForecastPct;
|
||||
|
||||
const maxBatteryDischargePoints =
|
||||
nightSessionActive && Number.isFinite(nightSessionStartSocPct)
|
||||
? Math.max(0, nightSessionStartSocPct - batteryMorningTargetPct)
|
||||
: 0;
|
||||
|
||||
const gridTakeoverTriggerPct =
|
||||
Math.max(batteryHardFloorPct, batteryMorningTargetPct);
|
||||
|
||||
const batteryBelowHardFloor =
|
||||
parsed.soc <= batteryHardFloorPct;
|
||||
|
||||
const batteryBelowMorningTarget =
|
||||
parsed.soc < batteryMorningTargetPct;
|
||||
|
||||
const batteryAtOrBelowGridTakeoverTrigger =
|
||||
parsed.soc <= gridTakeoverTriggerPct;
|
||||
|
||||
const batteryKwhMissingToTarget =
|
||||
nightSessionActive
|
||||
? Math.max(
|
||||
0,
|
||||
((batteryMorningTargetPct - parsed.soc) / 100) * cfg.batteryUsableCapacityKwh
|
||||
)
|
||||
: 0;
|
||||
|
||||
const batteryRecoveryMinutesNeeded =
|
||||
nightSessionActive && params.evcsNightBatteryRecoveryPowerKw > 0
|
||||
? (batteryKwhMissingToTarget / params.evcsNightBatteryRecoveryPowerKw) * 60
|
||||
: 0;
|
||||
|
||||
const batteryRecoveryUrgent =
|
||||
nightSessionActive &&
|
||||
Number.isFinite(minutesUntilDeparture) &&
|
||||
minutesUntilDeparture >= 0 &&
|
||||
(
|
||||
batteryRecoveryMinutesNeeded + params.evcsNightDepartureSafetyMin
|
||||
>= minutesUntilDeparture
|
||||
);
|
||||
|
||||
const batteryAssistAllowed =
|
||||
nightSessionActive &&
|
||||
parsed.soc > batteryHardFloorPct &&
|
||||
parsed.soc > gridTakeoverTriggerPct &&
|
||||
!batteryRecoveryUrgent;
|
||||
|
||||
const nightGridTakeoverNeeded =
|
||||
nightSessionActive &&
|
||||
(
|
||||
batteryBelowHardFloor ||
|
||||
batteryAtOrBelowGridTakeoverTrigger ||
|
||||
batteryRecoveryUrgent
|
||||
);
|
||||
|
||||
const nightPreferredSourceMode =
|
||||
!nightSessionActive
|
||||
? 'blocked'
|
||||
: batteryAssistAllowed
|
||||
? 'battery_assist_night'
|
||||
: (
|
||||
batteryBelowHardFloor ||
|
||||
batteryBelowMorningTarget ||
|
||||
batteryRecoveryUrgent
|
||||
)
|
||||
? 'grid_forced'
|
||||
: 'grid_hc_allowed';
|
||||
|
||||
// =====================================================
|
||||
// PROBES / SOLAR POTENTIAL
|
||||
// =====================================================
|
||||
// IMPORTANT:
|
||||
// On ne met plus effectivePvStartMinW à 0 en force_charge_now,
|
||||
// sinon la nuit 0W >= 0W ferait croire à un faux signal solaire.
|
||||
const effectivePvStartMinW = params.pvStartMinW;
|
||||
|
||||
const morningProbeEligible =
|
||||
profile === 'morning_solar' &&
|
||||
params.probeEnabled &&
|
||||
parsed.pvPowerAvg10mW >= params.morningProbePvAvgMinW &&
|
||||
parsed.pvTrendW >= params.morningProbePvTrendMinW &&
|
||||
parsed.soc >= params.morningSocMin &&
|
||||
parsed.batteryCurrentA >= params.morningBatteryMaxDischargeA;
|
||||
|
||||
const middayProbeEligible =
|
||||
profile === 'midday_hc_or_solar' &&
|
||||
params.probeEnabled &&
|
||||
parsed.pvPowerAvg10mW >= params.middayProbePvAvgMinW &&
|
||||
parsed.soc >= params.middaySocMin &&
|
||||
parsed.batteryCurrentA >= params.middayBatteryMaxDischargeA;
|
||||
|
||||
const cloudDebtOkForPotential = params.cloudDebtPrevKwh <= params.cloudDebtMaxKwh;
|
||||
const batteryOkForPotential = parsed.batteryCurrentA >= -Math.max(1, params.batteryNeutralBandA);
|
||||
|
||||
const isHighBatteryForPotential =
|
||||
parsed.soc >= 85 &&
|
||||
forecastBufferKwh > 0 &&
|
||||
cloudDebtOkForPotential &&
|
||||
batteryOkForPotential;
|
||||
|
||||
const potentialAllowed = userStrategy !== 'auto_solar';
|
||||
|
||||
// IMPORTANT:
|
||||
// On impose un vrai PV > 0 pour le test sur pvPowerW.
|
||||
// Ça évite qu'en force_charge_now la nuit, 0W soit pris pour un signal solaire.
|
||||
const strongSolarSignal =
|
||||
(
|
||||
parsed.pvPowerW > 0 &&
|
||||
parsed.pvPowerW >= effectivePvStartMinW
|
||||
) ||
|
||||
parsed.pvPowerAvg10mW >= params.pvHoldMinW ||
|
||||
(departurePriorityActive && parsed.pvPowerAvg10mW >= params.pvHoldMinW);
|
||||
|
||||
// =====================================================
|
||||
// SOURCE MODE
|
||||
// SÉMANTIQUE CORRIGÉE:
|
||||
// - force_charge_now bypass les fenêtres
|
||||
// - mais ne devient jamais grid_forced à lui seul
|
||||
// - outside_window = pas de réseau programmé
|
||||
// - la charge locale peut rester autorisée
|
||||
// =====================================================
|
||||
let sourceMode = 'blocked';
|
||||
|
||||
if (isStopMode) {
|
||||
sourceMode = 'blocked';
|
||||
}
|
||||
|
||||
else if (forceGridRequested) {
|
||||
// Demande explicite de réseau.
|
||||
sourceMode = 'grid_forced';
|
||||
}
|
||||
|
||||
else if (forceChargeRequested) {
|
||||
// IMPORTANT:
|
||||
// force_charge_now = intention de charger maintenant,
|
||||
// sans exiger le réseau.
|
||||
|
||||
if (strongSolarSignal) {
|
||||
sourceMode = 'solar_only';
|
||||
}
|
||||
|
||||
else if (nightSessionActive && batteryAssistAllowed) {
|
||||
sourceMode = 'battery_assist_night';
|
||||
}
|
||||
|
||||
else if (isHighBatteryForPotential && potentialAllowed) {
|
||||
sourceMode = 'solar_potential';
|
||||
}
|
||||
|
||||
else if (profile === 'midday_hc_or_solar' && middayProbeEligible) {
|
||||
sourceMode = 'hybrid_probe';
|
||||
}
|
||||
|
||||
else if (profile === 'morning_solar' && morningProbeEligible) {
|
||||
sourceMode = 'hybrid_probe';
|
||||
}
|
||||
|
||||
else {
|
||||
// Fallback manuel local :
|
||||
// on n'impose pas le grid, on laisse l'étage aval décider
|
||||
// selon les garde-fous physiques / batterie.
|
||||
sourceMode = 'solar_potential';
|
||||
}
|
||||
}
|
||||
|
||||
else if (profile === 'night_hc') {
|
||||
if (nightSessionActive) {
|
||||
sourceMode = nightPreferredSourceMode;
|
||||
} else {
|
||||
sourceMode = 'blocked';
|
||||
}
|
||||
}
|
||||
|
||||
else if (profile === 'midday_hc_or_solar' || profile === 'morning_solar') {
|
||||
if (strongSolarSignal) {
|
||||
sourceMode = 'solar_only';
|
||||
}
|
||||
|
||||
else if (isHighBatteryForPotential && potentialAllowed) {
|
||||
sourceMode = 'solar_potential';
|
||||
}
|
||||
|
||||
else if (profile === 'midday_hc_or_solar' && middayProbeEligible) {
|
||||
sourceMode = 'hybrid_probe';
|
||||
}
|
||||
|
||||
else if (profile === 'morning_solar' && morningProbeEligible) {
|
||||
sourceMode = 'hybrid_probe';
|
||||
}
|
||||
|
||||
else if (profile === 'midday_hc_or_solar' && params.allowGridHcMidday && parsed.gridAvailable) {
|
||||
sourceMode = 'grid_hc_allowed';
|
||||
}
|
||||
|
||||
else {
|
||||
sourceMode = 'blocked';
|
||||
}
|
||||
}
|
||||
|
||||
else if (profile === 'evening_high_soc') {
|
||||
if (strongSolarSignal) {
|
||||
sourceMode = 'solar_only';
|
||||
}
|
||||
|
||||
else if (isHighBatteryForPotential && potentialAllowed) {
|
||||
sourceMode = 'solar_potential';
|
||||
}
|
||||
|
||||
else {
|
||||
sourceMode = 'blocked';
|
||||
}
|
||||
}
|
||||
|
||||
else if (profile === 'outside_window') {
|
||||
if (strongSolarSignal) {
|
||||
sourceMode = 'solar_only';
|
||||
}
|
||||
|
||||
else if (isHighBatteryForPotential && potentialAllowed) {
|
||||
sourceMode = 'solar_potential';
|
||||
}
|
||||
|
||||
else {
|
||||
// IMPORTANT:
|
||||
// hors fenêtre = pas de réseau programmé.
|
||||
// Donc on ne bascule plus vers grid_forced juste parce que le réseau existe.
|
||||
sourceMode = 'blocked';
|
||||
}
|
||||
}
|
||||
|
||||
const operatingMode = profile;
|
||||
|
||||
// =====================================================
|
||||
// OUTPUT
|
||||
// =====================================================
|
||||
return [{
|
||||
json: {
|
||||
...prev,
|
||||
|
||||
strategy: {
|
||||
userStrategy,
|
||||
isStopMode,
|
||||
isForceMode,
|
||||
forceGridRequested,
|
||||
forceChargeRequested,
|
||||
},
|
||||
|
||||
lifecycle: {
|
||||
minutesSinceStart,
|
||||
minutesSinceStop,
|
||||
secondsSinceCurrentChange,
|
||||
minutesSinceProbeStart,
|
||||
minRunElapsed,
|
||||
minStopElapsed,
|
||||
currentCooldownElapsed,
|
||||
minutesUntilDeparture,
|
||||
},
|
||||
|
||||
windows: {
|
||||
inNightHcWindow,
|
||||
inMorningWindow,
|
||||
inMiddayWindow,
|
||||
inEveningWindow,
|
||||
},
|
||||
|
||||
derived: {
|
||||
acVoltageRef,
|
||||
batteryVoltageRef,
|
||||
houseNonEvcsW,
|
||||
vebusNonEvcsW,
|
||||
totalVebusSoftCeilingW,
|
||||
totalVebusHardCeilingW,
|
||||
safeEvcsByDedicatedCapW,
|
||||
safeEvcsByVebusW,
|
||||
safeEvcsCombinedW,
|
||||
systemCapCurrentA,
|
||||
physicalCapA,
|
||||
canRunAtMinCurrent,
|
||||
endDayGuardSoc,
|
||||
energyNeededForEndDayGuardKwh,
|
||||
forecastBufferKwh,
|
||||
projectedEndSocAfterCharge,
|
||||
currentReferenceA,
|
||||
anyVebusOverload,
|
||||
anyVebusLowBattery,
|
||||
vebusAcOutSoftReached,
|
||||
vebusAcOutHardReached,
|
||||
vebusDcVoltageSoftLow,
|
||||
vebusDcVoltageHardLow,
|
||||
houseLoadHighVsHeadroom,
|
||||
},
|
||||
|
||||
context: {
|
||||
departurePriorityActive,
|
||||
forceChargeDepartureGridOverride,
|
||||
manualChargeBypassWindows,
|
||||
manualChargeLocalRequested,
|
||||
morningProbeEligible,
|
||||
middayProbeEligible,
|
||||
isHighBatteryForPotential,
|
||||
strongSolarSignal,
|
||||
effectivePvStartMinW,
|
||||
profile,
|
||||
operatingMode,
|
||||
sourceMode,
|
||||
},
|
||||
|
||||
nightEvcsPreview: {
|
||||
nightSessionActive,
|
||||
nightSessionNeedsCapture,
|
||||
nightSessionNeedsClear,
|
||||
nightSessionStartSocPct,
|
||||
nightSessionStartAt,
|
||||
|
||||
batteryHardFloorPct,
|
||||
batteryMorningTargetPct,
|
||||
maxBatteryDischargePoints,
|
||||
gridTakeoverTriggerPct,
|
||||
|
||||
batteryBelowHardFloor,
|
||||
batteryBelowMorningTarget,
|
||||
batteryAtOrBelowGridTakeoverTrigger,
|
||||
|
||||
batteryKwhMissingToTarget,
|
||||
batteryRecoveryMinutesNeeded,
|
||||
batteryRecoveryUrgent,
|
||||
|
||||
batteryAssistAllowed,
|
||||
nightGridTakeoverNeeded,
|
||||
nightPreferredSourceMode,
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
Reference in New Issue
Block a user