// === DECISION INIT === let desiredCharging = false; let desiredPermission = false; let desiredLatch = false; let desiredCurrentA = currentMinA; let state = 'idle'; let currentDecision = 'hold'; let dynamicOk = false; let mustStop = false; let reason = []; let setLastStartAt = false; let setLastStopAt = false; let setLastCurrentChangeAt = false; let setCloudDebtStartedAt = false; let setProbeStartedAt = false; let notifyWanted = false; let notifyTitle = 'Victron EVCS Surplus'; let notifyMessage = ''; let nextBlockReason = 'none'; let nextBlockedUntilStamp = now.stamp; // === DECISION TREE (WITH STRATEGY LAYERS) === if (!controllerEnabled) { desiredCharging = evcsChargingSwitchOn; desiredPermission = chargePermissionNow; desiredLatch = controlLatch; desiredCurrentA = clamp(currentReferenceA || currentMinA, currentMinA, currentMaxAutoA); state = 'controller_disabled'; reason.push("Contrôleur désactivé"); } else if (isStopMode) { desiredCharging = false; desiredPermission = false; desiredLatch = false; desiredCurrentA = currentMinA; state = 'stopped_user'; currentDecision = 'stop_stop_mode'; dynamicOk = false; mustStop = false; nextBlockReason = 'manual_stop'; nextBlockedUntilStamp = now.stamp; reason.push("Arrêt manuel via Stratégie STOP"); } else if (evcsModeIsAuto) { desiredCharging = false; desiredPermission = false; desiredLatch = false; desiredCurrentA = currentMinA; state = 'mode_auto_conflict'; currentDecision = 'stop_auto_mode_conflict'; dynamicOk = false; mustStop = true; reason.push("Borne en mode Auto interne"); } else if (!evcsDeviceConnected || !evcsVehicleConnected) { desiredCharging = false; desiredPermission = false; desiredLatch = false; desiredCurrentA = currentMinA; state = 'vehicle_not_connected'; currentDecision = 'stop_not_connected'; dynamicOk = false; mustStop = false; nextBlockReason = 'vehicle_not_connected'; nextBlockedUntilStamp = now.stamp; reason.push("Véhicule ou borne non connecté"); } else if (mustStopHard) { desiredCharging = false; desiredPermission = false; desiredLatch = false; desiredCurrentA = currentMinA; state = anyVebusOverload ? 'vebus_overload_hard_stop' : anyVebusLowBattery ? 'vebus_low_battery_hard_stop' : vebusDcVoltageHardLow ? 'vebus_dc_voltage_hard_stop' : vebusAcOutHardReached ? 'vebus_ac_hard_stop' : batteryCurrentHardExceeded ? 'battery_current_hard_stop' : 'hard_stop'; currentDecision = 'stop_hard'; dynamicOk = false; mustStop = true; nextBlockReason = 'battery_protection_stop'; nextBlockedUntilStamp = formatParisStampFromMs(nowMs + 15 * 60000); reason.push("Hard stop de protection"); } else if (sessionObservedActive) { if (enteringCloudDebt) { setCloudDebtStartedAt = true; } if (gridBatteryProtectionStop) { desiredCharging = false; desiredPermission = false; desiredLatch = false; desiredCurrentA = currentMinA; state = 'stop_grid_battery_guard'; currentDecision = 'stop'; dynamicOk = false; mustStop = true; nextBlockReason = 'grid_battery_guard'; nextBlockedUntilStamp = formatParisStampFromMs(nowMs + 20 * 60000); reason.push(`Arrêt HC réseau: batterie trop sollicitée (${round1(batteryCurrentA)}A)`); } else if (probeFailedNow) { desiredCharging = false; desiredPermission = false; desiredLatch = false; desiredCurrentA = currentMinA; state = 'probe_failed_lock'; currentDecision = 'stop'; dynamicOk = false; mustStop = true; nextBlockReason = 'probe_failed'; nextBlockedUntilStamp = formatParisStampFromMs(nowMs + probeRetryMinutes * 60000); reason.push("Mode sonde échoué"); } else if (physicalCapA < currentMinA) { if (minRunElapsed) { desiredCharging = false; desiredPermission = false; desiredLatch = false; desiredCurrentA = currentMinA; state = 'stop_power_cap'; currentDecision = 'stop_power_cap'; dynamicOk = false; mustStop = true; nextBlockReason = 'battery_protection_stop'; nextBlockedUntilStamp = formatParisStampFromMs(nowMs + 10 * 60000); reason.push("Capacité système insuffisante"); } else { desiredCharging = true; desiredPermission = true; desiredLatch = true; desiredCurrentA = currentMinA; state = 'hold_min_run_power_cap'; currentDecision = 'hold'; dynamicOk = false; mustStop = false; reason.push("Maintien jusqu'au temps mini ON"); } } else if (probeWindowActive && !probeSuccessNow) { if (!probeHoldGuardOk) { desiredCharging = false; desiredPermission = false; desiredLatch = false; desiredCurrentA = currentMinA; state = 'probe_stop_guard'; currentDecision = 'stop'; dynamicOk = false; mustStop = true; nextBlockReason = 'probe_failed'; nextBlockedUntilStamp = formatParisStampFromMs(nowMs + probeRetryMinutes * 60000); reason.push("Sonde interrompue: garde-fous non respectés"); } else {