710 lines
21 KiB
JavaScript
710 lines
21 KiB
JavaScript
function clamp(v, min, max) {
|
|
return Math.min(Math.max(v, min), max);
|
|
}
|
|
|
|
function round1(v) {
|
|
return Math.round(v * 10) / 10;
|
|
}
|
|
|
|
function to2(n) {
|
|
return String(n).padStart(2, '0');
|
|
}
|
|
|
|
function formatParisStampFromMs(ms) {
|
|
const fmt = new Intl.DateTimeFormat('fr-FR', {
|
|
timeZone: 'Europe/Paris',
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
hour12: false,
|
|
});
|
|
|
|
const parts = Object.fromEntries(
|
|
fmt.formatToParts(new Date(ms))
|
|
.filter(p => p.type !== 'literal')
|
|
.map(p => [p.type, p.value])
|
|
);
|
|
|
|
const hour = Number(parts.hour) === 24 ? 0 : Number(parts.hour);
|
|
return `${parts.year}-${parts.month}-${parts.day} ${to2(hour)}:${parts.minute}:${parts.second}`;
|
|
}
|
|
|
|
const prev = $input.first().json;
|
|
const {
|
|
now,
|
|
nowMs,
|
|
parsed,
|
|
params,
|
|
history,
|
|
strategy,
|
|
lifecycle,
|
|
context,
|
|
derived,
|
|
locks,
|
|
cloud,
|
|
modes,
|
|
safeguards,
|
|
conditions,
|
|
batteryTune,
|
|
} = prev;
|
|
|
|
// =====================================================
|
|
// DECISION INIT
|
|
// =====================================================
|
|
let desiredCharging = false;
|
|
let desiredPermission = false;
|
|
let desiredLatch = false;
|
|
let desiredCurrentA = params.currentMinA;
|
|
|
|
let state = 'idle';
|
|
let currentDecision = 'hold';
|
|
let dynamicOk = false;
|
|
let mustStop = false;
|
|
|
|
let reason = [];
|
|
|
|
let setCloudDebtStartedAt = false;
|
|
let setProbeStartedAt = false;
|
|
|
|
let notifyWanted = false;
|
|
let notifyTitle = 'Victron EVCS Surplus';
|
|
let notifyMessage = '';
|
|
|
|
let nextBlockReason = 'none';
|
|
let nextBlockedUntilStamp = now.stamp;
|
|
|
|
// =====================================================
|
|
// HELPERS
|
|
// =====================================================
|
|
function setOff({
|
|
stateName,
|
|
decisionName = 'stop',
|
|
reasonText,
|
|
dynamic = false,
|
|
mustStopFlag = false,
|
|
blockReason = 'none',
|
|
blockedUntilStamp = now.stamp,
|
|
}) {
|
|
desiredCharging = false;
|
|
desiredPermission = false;
|
|
desiredLatch = false;
|
|
desiredCurrentA = params.currentMinA;
|
|
|
|
state = stateName;
|
|
currentDecision = decisionName;
|
|
dynamicOk = dynamic;
|
|
mustStop = mustStopFlag;
|
|
nextBlockReason = blockReason;
|
|
nextBlockedUntilStamp = blockedUntilStamp;
|
|
|
|
if (reasonText) reason.push(reasonText);
|
|
}
|
|
|
|
function setOn({
|
|
stateName,
|
|
decisionName = 'hold',
|
|
currentA = params.currentMinA,
|
|
reasonText,
|
|
dynamic = true,
|
|
mustStopFlag = false,
|
|
}) {
|
|
desiredCharging = true;
|
|
desiredPermission = true;
|
|
desiredLatch = true;
|
|
desiredCurrentA = clamp(
|
|
currentA,
|
|
params.currentMinA,
|
|
Math.max(params.currentMinA, derived.physicalCapA || params.currentMinA)
|
|
);
|
|
|
|
state = stateName;
|
|
currentDecision = decisionName;
|
|
dynamicOk = dynamic;
|
|
mustStop = mustStopFlag;
|
|
|
|
if (reasonText) reason.push(reasonText);
|
|
}
|
|
|
|
// =====================================================
|
|
// DECISION TREE
|
|
// =====================================================
|
|
if (!parsed.controllerEnabled) {
|
|
desiredCharging = parsed.evcsChargingSwitchOn;
|
|
desiredPermission = parsed.chargePermissionNow;
|
|
desiredLatch = parsed.controlLatch;
|
|
desiredCurrentA = clamp(
|
|
derived.currentReferenceA || params.currentMinA,
|
|
params.currentMinA,
|
|
params.currentMaxAutoA
|
|
);
|
|
|
|
state = 'controller_disabled';
|
|
currentDecision = 'hold';
|
|
dynamicOk = false;
|
|
mustStop = false;
|
|
reason.push('Contrôleur désactivé');
|
|
}
|
|
|
|
else if (strategy.isStopMode) {
|
|
setOff({
|
|
stateName: 'stopped_user',
|
|
decisionName: 'stop_stop_mode',
|
|
reasonText: 'Arrêt manuel via Stratégie STOP',
|
|
dynamic: false,
|
|
mustStopFlag: false,
|
|
blockReason: 'manual_stop',
|
|
blockedUntilStamp: now.stamp,
|
|
});
|
|
}
|
|
|
|
else if (parsed.evcsModeIsAuto) {
|
|
setOff({
|
|
stateName: 'mode_auto_conflict',
|
|
decisionName: 'stop_auto_mode_conflict',
|
|
reasonText: 'Borne en mode Auto interne',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'mode_auto_conflict',
|
|
blockedUntilStamp: now.stamp,
|
|
});
|
|
}
|
|
|
|
else if (!parsed.evcsDeviceConnected || !parsed.evcsVehicleConnected) {
|
|
setOff({
|
|
stateName: 'vehicle_not_connected',
|
|
decisionName: 'stop_not_connected',
|
|
reasonText: 'Véhicule ou borne non connecté',
|
|
dynamic: false,
|
|
mustStopFlag: false,
|
|
blockReason: 'vehicle_not_connected',
|
|
blockedUntilStamp: now.stamp,
|
|
});
|
|
}
|
|
|
|
else if (safeguards.mustStopHard) {
|
|
setOff({
|
|
stateName:
|
|
derived.anyVebusLowBattery ? 'vebus_low_battery_hard_stop' :
|
|
derived.vebusDcVoltageHardLow ? 'vebus_dc_voltage_hard_stop' :
|
|
derived.vebusAcOutHardReached ? 'vebus_ac_hard_stop' :
|
|
safeguards.batteryCurrentHardExceeded ? 'battery_current_hard_stop' :
|
|
'hard_stop',
|
|
decisionName: 'stop_hard',
|
|
reasonText: 'Hard stop de protection',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason:
|
|
safeguards.batteryCurrentHardExceeded ? 'battery_current_hard_stop' :
|
|
derived.vebusDcVoltageHardLow ? 'vebus_dc_voltage_stop' :
|
|
derived.anyVebusLowBattery ? 'vebus_low_battery_stop' :
|
|
derived.vebusAcOutHardReached ? 'vebus_ac_hard_stop' :
|
|
'battery_protection_stop',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + 15 * 60000),
|
|
});
|
|
}
|
|
|
|
else if (cloud.sessionObservedActive) {
|
|
if (cloud.enteringCloudDebt) {
|
|
setCloudDebtStartedAt = true;
|
|
}
|
|
|
|
if (conditions.shouldStopOverloadAtMin) {
|
|
setOff({
|
|
stateName: 'vebus_overload_stop_min_current',
|
|
decisionName: 'stop',
|
|
reasonText: 'Arrêt: surcharge convertisseur persistante au courant mini',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'vebus_overload_persistent',
|
|
blockedUntilStamp: formatParisStampFromMs(
|
|
nowMs + batteryTune.global.overloadStopAfterSeconds * 1000
|
|
),
|
|
});
|
|
}
|
|
|
|
else if (safeguards.gridBatteryProtectionStop) {
|
|
setOff({
|
|
stateName: 'stop_grid_battery_guard',
|
|
decisionName: 'stop',
|
|
reasonText: `Arrêt charge réseau: batterie trop sollicitée (${round1(parsed.batteryCurrentA)}A)`,
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'grid_battery_guard',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + 20 * 60000),
|
|
});
|
|
}
|
|
|
|
else if (conditions.probeFailedNow) {
|
|
setOff({
|
|
stateName: 'probe_failed_lock',
|
|
decisionName: 'stop',
|
|
reasonText: 'Mode sonde échoué',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'probe_failed',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + params.probeRetryMinutes * 60000),
|
|
});
|
|
}
|
|
|
|
else if (derived.physicalCapA < params.currentMinA) {
|
|
if (lifecycle.minRunElapsed) {
|
|
setOff({
|
|
stateName: 'stop_power_cap',
|
|
decisionName: 'stop_power_cap',
|
|
reasonText: 'Capacité système insuffisante',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'system_capacity_stop',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + 10 * 60000),
|
|
});
|
|
} else {
|
|
setOn({
|
|
stateName: 'hold_min_run_power_cap',
|
|
decisionName: 'hold',
|
|
currentA: params.currentMinA,
|
|
reasonText: "Maintien jusqu'au temps mini ON",
|
|
dynamic: false,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
else if (conditions.probeWindowActive && !conditions.probeSuccessNow) {
|
|
if (!conditions.probeHoldGuardOk) {
|
|
setOff({
|
|
stateName: 'probe_stop_guard',
|
|
decisionName: 'stop',
|
|
reasonText: 'Sonde interrompue: garde-fous non respectés',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'probe_failed',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + params.probeRetryMinutes * 60000),
|
|
});
|
|
} else {
|
|
setOn({
|
|
stateName: 'probe_hold',
|
|
decisionName: 'hold',
|
|
currentA: Math.min(params.probeCurrentA, derived.physicalCapA),
|
|
reasonText: `Mode sonde actif à ${round1(Math.min(params.probeCurrentA, derived.physicalCapA))}A`,
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
else {
|
|
const activeSourceMode = conditions.probeSuccessNow ? 'solar_only' : context.sourceMode;
|
|
|
|
const holdConditionsOk =
|
|
(activeSourceMode === 'grid_hc_allowed' || activeSourceMode === 'grid_forced')
|
|
? conditions.gridHoldOk
|
|
: (activeSourceMode === 'solar_only' || activeSourceMode === 'solar_potential')
|
|
? conditions.solarHoldOk
|
|
: (activeSourceMode === 'hybrid_probe')
|
|
? conditions.probeHoldGuardOk
|
|
: false;
|
|
|
|
if (activeSourceMode === 'blocked') {
|
|
if (lifecycle.minRunElapsed) {
|
|
setOff({
|
|
stateName: 'stop_source_lost',
|
|
decisionName: 'stop',
|
|
reasonText: 'Source autorisée perdue',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: context.profile === 'outside_window' ? 'outside_window' : 'source_unavailable',
|
|
blockedUntilStamp:
|
|
context.profile === 'outside_window' && Number.isFinite(locks.nextAllowedWindowStartMs)
|
|
? formatParisStampFromMs(locks.nextAllowedWindowStartMs)
|
|
: formatParisStampFromMs(nowMs + params.minStopMinutes * 60000),
|
|
});
|
|
} else {
|
|
setOn({
|
|
stateName: 'hold_min_run_source_lost',
|
|
decisionName: 'hold',
|
|
currentA: Math.min(derived.currentReferenceA, derived.physicalCapA),
|
|
reasonText: "Source perdue mais maintien jusqu'au mini ON",
|
|
dynamic: false,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
else if (!holdConditionsOk) {
|
|
if (lifecycle.minRunElapsed) {
|
|
setOff({
|
|
stateName: 'stop_conditions_lost',
|
|
decisionName: 'stop',
|
|
reasonText: 'Conditions de maintien perdues',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'hold_conditions_lost',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + 10 * 60000),
|
|
});
|
|
} else {
|
|
setOn({
|
|
stateName: 'hold_min_run',
|
|
decisionName: 'hold',
|
|
currentA: Math.min(derived.currentReferenceA, derived.physicalCapA),
|
|
reasonText: "Maintien jusqu'au temps mini ON",
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
else if ((derived.currentReferenceA > derived.physicalCapA) && lifecycle.currentCooldownElapsed) {
|
|
setOn({
|
|
stateName: 'charging_step_down_system_cap',
|
|
decisionName: 'step_down',
|
|
currentA: Math.max(
|
|
params.currentMinA,
|
|
Math.min(
|
|
derived.currentReferenceA - Math.max(params.currentStepDownA, 2),
|
|
derived.physicalCapA
|
|
)
|
|
),
|
|
reasonText: 'Réduction pour respecter la capacité système',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
|
|
else if (conditions.shouldStepDownOverload) {
|
|
setOn({
|
|
stateName: 'charging_step_down_overload',
|
|
decisionName: 'step_down_fast',
|
|
currentA: Math.max(
|
|
params.currentMinA,
|
|
Math.min(
|
|
derived.currentReferenceA - Math.max(batteryTune.global.overloadStepDownA, params.currentStepDownA),
|
|
derived.physicalCapA
|
|
)
|
|
),
|
|
reasonText: 'Baisse rapide: surcharge convertisseur',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
|
|
else if (conditions.shouldStepDownVebusPower) {
|
|
setOn({
|
|
stateName: 'charging_step_down_vebus_power',
|
|
decisionName: 'step_down_fast',
|
|
currentA: Math.max(
|
|
params.currentMinA,
|
|
Math.min(
|
|
derived.currentReferenceA - Math.max(params.currentStepDownA + 1, 3),
|
|
derived.physicalCapA
|
|
)
|
|
),
|
|
reasonText: 'Baisse rapide: puissance VE.Bus élevée',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
|
|
else if (conditions.shouldStepDownVebusVoltage) {
|
|
setOn({
|
|
stateName: 'charging_step_down_dc_voltage',
|
|
decisionName: 'step_down_fast',
|
|
currentA: Math.max(
|
|
params.currentMinA,
|
|
Math.min(
|
|
derived.currentReferenceA - Math.max(params.currentStepDownA + 1, 3),
|
|
derived.physicalCapA
|
|
)
|
|
),
|
|
reasonText: 'Baisse rapide: tension DC basse',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
|
|
else if (
|
|
(activeSourceMode === 'grid_hc_allowed' || activeSourceMode === 'grid_forced') &&
|
|
conditions.gridShouldStepDownBattery
|
|
) {
|
|
setOn({
|
|
stateName: 'charging_step_down_grid_guard',
|
|
decisionName: 'step_down',
|
|
currentA: Math.max(
|
|
params.currentMinA,
|
|
Math.min(
|
|
derived.currentReferenceA - params.currentStepDownA,
|
|
derived.physicalCapA
|
|
)
|
|
),
|
|
reasonText: 'Réduction réseau: la batterie commence à participer',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
|
|
else if (
|
|
(activeSourceMode === 'solar_only' || activeSourceMode === 'solar_potential') &&
|
|
conditions.solarShouldStepDownCloudDebt
|
|
) {
|
|
setOn({
|
|
stateName: 'charging_step_down_cloud_debt',
|
|
decisionName: 'step_down',
|
|
currentA: Math.max(
|
|
params.currentMinA,
|
|
Math.min(
|
|
derived.currentReferenceA - Math.max(params.currentStepDownA, 2),
|
|
derived.physicalCapA
|
|
)
|
|
),
|
|
reasonText: 'Réduction: dette nuage trop élevée',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
|
|
else if (
|
|
(activeSourceMode === 'solar_only' || activeSourceMode === 'solar_potential') &&
|
|
conditions.solarShouldStepDownBattery
|
|
) {
|
|
setOn({
|
|
stateName: 'charging_step_down_battery',
|
|
decisionName: 'step_down',
|
|
currentA: Math.max(
|
|
params.currentMinA,
|
|
Math.min(
|
|
derived.currentReferenceA - params.currentStepDownA,
|
|
derived.physicalCapA
|
|
)
|
|
),
|
|
reasonText: 'Réduction: batterie trop sollicitée',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
|
|
else if (
|
|
(activeSourceMode === 'grid_hc_allowed' || activeSourceMode === 'grid_forced') &&
|
|
conditions.gridShouldStepUp
|
|
) {
|
|
setOn({
|
|
stateName:
|
|
activeSourceMode === 'grid_forced'
|
|
? 'charging_step_up_grid_forced'
|
|
: 'charging_step_up_grid_hc',
|
|
decisionName: 'step_up',
|
|
currentA: clamp(
|
|
derived.currentReferenceA + params.currentStepUpA,
|
|
params.currentMinA,
|
|
derived.physicalCapA
|
|
),
|
|
reasonText:
|
|
activeSourceMode === 'grid_forced'
|
|
? 'Hausse prudente du courant en charge réseau forcée'
|
|
: 'Hausse prudente du courant en HC réseau',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
|
|
else if (
|
|
(activeSourceMode === 'solar_only' || activeSourceMode === 'solar_potential') &&
|
|
conditions.solarShouldStepUp
|
|
) {
|
|
setOn({
|
|
stateName:
|
|
conditions.probeSuccessNow
|
|
? 'probe_success_step_up'
|
|
: (modes.batteryPriorityMode
|
|
? 'charging_step_up_battery'
|
|
: 'charging_step_up_forecast'),
|
|
decisionName: 'step_up',
|
|
currentA: clamp(
|
|
derived.currentReferenceA + params.currentStepUpA,
|
|
params.currentMinA,
|
|
derived.physicalCapA
|
|
),
|
|
reasonText: 'Hausse prudente du courant',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
|
|
else {
|
|
setOn({
|
|
stateName:
|
|
conditions.probeSuccessNow ? 'probe_success_hold' :
|
|
(activeSourceMode === 'grid_hc_allowed' || activeSourceMode === 'grid_forced') ? 'charging_hold_grid' :
|
|
cloud.cloudDebtActive ? 'charging_hold_cloud_tolerance' :
|
|
'charging_hold',
|
|
decisionName: 'hold',
|
|
currentA: Math.min(derived.currentReferenceA, derived.physicalCapA),
|
|
reasonText: 'Maintien du courant',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// START BRANCH
|
|
// =====================================================
|
|
else {
|
|
const startConditionsOk =
|
|
(context.sourceMode === 'grid_hc_allowed' || context.sourceMode === 'grid_forced')
|
|
? conditions.gridStartOk
|
|
: (context.sourceMode === 'solar_only' || context.sourceMode === 'solar_potential')
|
|
? conditions.solarStartOk
|
|
: context.sourceMode === 'hybrid_probe'
|
|
? conditions.probeStartOk
|
|
: false;
|
|
|
|
if (!derived.canRunAtMinCurrent) {
|
|
setOff({
|
|
stateName: 'blocked_by_capacity_min_current',
|
|
decisionName: 'hold',
|
|
reasonText: 'Capacité insuffisante pour courant mini',
|
|
dynamic: false,
|
|
mustStopFlag: false,
|
|
blockReason: 'system_capacity_stop',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + 10 * 60000),
|
|
});
|
|
}
|
|
|
|
else if (context.sourceMode === 'blocked') {
|
|
setOff({
|
|
stateName:
|
|
context.profile === 'outside_window'
|
|
? 'blocked_outside_window'
|
|
: (context.profile === 'night_hc'
|
|
? 'night_no_grid_hc_lock'
|
|
: 'idle_wait_source'),
|
|
decisionName: 'hold',
|
|
reasonText:
|
|
context.profile === 'outside_window'
|
|
? 'Hors fenêtre préférée sans source suffisante'
|
|
: context.profile === 'night_hc'
|
|
? 'Nuit sans autorisation HC réseau'
|
|
: 'Source non autorisée ou insuffisante',
|
|
dynamic: false,
|
|
mustStopFlag: false,
|
|
blockReason:
|
|
context.profile === 'outside_window'
|
|
? 'outside_window'
|
|
: context.profile === 'night_hc'
|
|
? 'grid_hc_unavailable'
|
|
: 'source_unavailable',
|
|
blockedUntilStamp:
|
|
Number.isFinite(locks.nextAllowedWindowStartMs)
|
|
? formatParisStampFromMs(locks.nextAllowedWindowStartMs)
|
|
: formatParisStampFromMs(nowMs + 15 * 60000),
|
|
});
|
|
}
|
|
|
|
else if (locks.startBlockedByReason) {
|
|
setOff({
|
|
stateName: 'wait_reason_lock',
|
|
decisionName: 'hold',
|
|
reasonText: `Blocage anti-cliquetis: ${history.lastBlockReason}`,
|
|
dynamic: false,
|
|
mustStopFlag: false,
|
|
blockReason: history.lastBlockReason,
|
|
blockedUntilStamp: history.blockedUntilAt || now.stamp,
|
|
});
|
|
}
|
|
|
|
else if (!startConditionsOk) {
|
|
setOff({
|
|
stateName: 'idle_wait_conditions',
|
|
decisionName: 'hold',
|
|
reasonText: 'Conditions de démarrage non réunies',
|
|
dynamic: false,
|
|
mustStopFlag: false,
|
|
blockReason: 'none',
|
|
blockedUntilStamp: now.stamp,
|
|
});
|
|
}
|
|
|
|
else if (!lifecycle.minStopElapsed) {
|
|
setOff({
|
|
stateName: 'wait_min_stop',
|
|
decisionName: 'hold',
|
|
reasonText: 'Temps mini OFF non écoulé',
|
|
dynamic: false,
|
|
mustStopFlag: false,
|
|
blockReason: 'none',
|
|
blockedUntilStamp: now.stamp,
|
|
});
|
|
}
|
|
|
|
else {
|
|
const startCurrentA =
|
|
context.sourceMode === 'hybrid_probe'
|
|
? clamp(params.probeCurrentA, params.currentMinA, derived.physicalCapA)
|
|
: params.currentMinA;
|
|
|
|
setOn({
|
|
stateName:
|
|
context.sourceMode === 'grid_forced' ? 'start_grid_forced_min_current' :
|
|
context.sourceMode === 'grid_hc_allowed' ? 'start_grid_hc_min_current' :
|
|
context.sourceMode === 'hybrid_probe' ? 'probe_start_6a' :
|
|
context.sourceMode === 'solar_potential' ? 'start_forecast_solar_potential' :
|
|
modes.batteryFullHarvestMode ? 'start_min_current_battery_full_harvest' :
|
|
modes.forecastCommitMode ? 'start_min_current_forecast_commit' :
|
|
'start_min_current',
|
|
decisionName: 'start',
|
|
currentA: startCurrentA,
|
|
reasonText:
|
|
context.sourceMode === 'grid_forced'
|
|
? 'Démarrage en charge réseau forcée'
|
|
: context.sourceMode === 'grid_hc_allowed'
|
|
? 'Démarrage en fenêtre HC réseau'
|
|
: context.sourceMode === 'hybrid_probe'
|
|
? 'Démarrage en mode sonde'
|
|
: context.sourceMode === 'solar_potential'
|
|
? 'Démarrage par potentiel solaire confirmé'
|
|
: modes.batteryFullHarvestMode
|
|
? 'Démarrage harvest solaire'
|
|
: modes.forecastCommitMode
|
|
? 'Démarrage forecast commit'
|
|
: 'Démarrage prudent à courant mini',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
|
|
nextBlockReason = 'none';
|
|
nextBlockedUntilStamp = now.stamp;
|
|
setProbeStartedAt = context.sourceMode === 'hybrid_probe';
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// OUTPUT
|
|
// =====================================================
|
|
return [{
|
|
json: {
|
|
...prev,
|
|
decision: {
|
|
desiredCharging,
|
|
desiredPermission,
|
|
desiredLatch,
|
|
desiredCurrentA,
|
|
state,
|
|
currentDecision,
|
|
dynamicOk,
|
|
mustStop,
|
|
reason,
|
|
nextBlockReason,
|
|
nextBlockedUntilStamp,
|
|
setCloudDebtStartedAt,
|
|
setProbeStartedAt,
|
|
notifyWanted,
|
|
notifyTitle,
|
|
notifyMessage,
|
|
}
|
|
}
|
|
}];
|
|
|