430 lines
14 KiB
JavaScript
430 lines
14 KiB
JavaScript
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 === 'battery_assist_night' &&
|
|
conditions.batteryAssistNightShouldStepDown
|
|
) {
|
|
setOn({
|
|
stateName: 'charging_step_down_battery_assist_night',
|
|
decisionName: 'step_down',
|
|
currentA: Math.max(
|
|
params.currentMinA,
|
|
Math.min(
|
|
derived.currentReferenceA - params.currentStepDownA,
|
|
derived.physicalCapA
|
|
)
|
|
),
|
|
reasonText:
|
|
nightEvcs?.batteryRecoveryUrgent
|
|
? 'Réduction: approche départ, préparation takeover réseau'
|
|
: 'Réduction: assistance batterie nuit devient trop coûteuse',
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
|
|
else if (
|
|
(activeSourceMode === 'grid_hc_allowed' || activeSourceMode === 'grid_forced') &&
|
|
conditions.gridShouldStepDownBattery
|
|
) {
|
|
setOn({
|
|
stateName:
|
|
activeSourceMode === 'grid_forced'
|
|
? 'charging_step_down_grid_forced_guard'
|
|
: 'charging_step_down_grid_guard',
|
|
decisionName: 'step_down',
|
|
currentA: Math.max(
|
|
params.currentMinA,
|
|
Math.min(
|
|
derived.currentReferenceA - params.currentStepDownA,
|
|
derived.physicalCapA
|
|
)
|
|
),
|
|
reasonText:
|
|
activeSourceMode === 'grid_forced'
|
|
? 'Réduction réseau forcé: la batterie commence à participer'
|
|
: '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'
|
|
? (
|
|
nightEvcs?.batteryRecoveryUrgent
|
|
? 'night_departure_deadline_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'
|
|
? (
|
|
nightEvcs?.batteryRecoveryUrgent
|
|
? 'Hausse prudente: priorité départ, réseau forcé'
|
|
: '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 === 'battery_assist_night' &&
|
|
conditions.batteryAssistNightShouldStepUp
|
|
) {
|
|
setOn({
|
|
stateName: 'charging_step_up_battery_assist_night',
|
|
decisionName: 'step_up',
|
|
currentA: clamp(
|
|
derived.currentReferenceA + params.currentStepUpA,
|
|
params.currentMinA,
|
|
derived.physicalCapA
|
|
),
|
|
reasonText: 'Hausse prudente du courant en assistance batterie nuit',
|
|
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 === 'battery_assist_night' ? 'charging_hold_battery_assist_night' :
|
|
(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:
|
|
activeSourceMode === 'battery_assist_night'
|
|
? 'Maintien du courant en assistance batterie nuit'
|
|
: '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
|
|
: context.sourceMode === 'battery_assist_night'
|
|
? conditions.batteryAssistNightStartOk
|
|
: 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 (
|
|
(context.sourceMode === 'grid_hc_allowed' || context.sourceMode === 'grid_forced') &&
|
|
!parsed.gridAvailable
|
|
) {
|
|
setOff({
|
|
stateName:
|
|
context.profile === 'night_hc'
|
|
? 'night_wait_grid_takeover'
|
|
: 'wait_grid_source',
|
|
decisionName: 'hold',
|
|
reasonText:
|
|
context.profile === 'night_hc'
|
|
? (
|
|
nightEvcs?.batteryRecoveryUrgent
|
|
? 'Réseau demandé: urgence recharge batterie avant départ'
|
|
: 'Prise de relais réseau demandée pour session nuit EVCS'
|
|
)
|
|
: 'Source réseau demandée mais pas encore disponible',
|
|
dynamic: false,
|
|
mustStopFlag: false,
|
|
blockReason:
|
|
context.profile === 'night_hc'
|
|
? 'grid_hc_unavailable'
|
|
: 'grid_unavailable',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + 10 * 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:
|
|
context.sourceMode === 'battery_assist_night'
|
|
? 'Conditions de démarrage nuit batterie non réunies'
|
|
: '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' ? (
|
|
nightEvcs?.batteryRecoveryUrgent
|
|
? 'night_departure_deadline_grid_forced'
|
|
: 'start_grid_forced_min_current'
|
|
) :
|
|
context.sourceMode === 'grid_hc_allowed' ? 'start_grid_hc_min_current' :
|
|
context.sourceMode === 'battery_assist_night' ? 'start_battery_assist_night_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'
|
|
? (
|
|
nightEvcs?.batteryRecoveryUrgent
|
|
? 'Démarrage réseau forcé: priorité départ / recharge batterie'
|
|
: 'Démarrage en charge réseau forcée'
|
|
)
|
|
: context.sourceMode === 'grid_hc_allowed'
|
|
? 'Démarrage en fenêtre HC réseau'
|
|
: context.sourceMode === 'battery_assist_night'
|
|
? 'Démarrage nuit en assistance batterie contrôlée'
|
|
: 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';
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// CONTEXT REASONS
|
|
// =====================================================
|
|
if (nightEvcs?.nightSessionActive) {
|
|
reason.push(
|
|
`Nuit EVCS: target=${round1(nightEvcs.batteryMorningTargetPct)}% | trigger réseau=${round1(nightEvcs.gridTakeoverTriggerPct)}%`
|
|
);
|
|
|
|
if (Number.isFinite(nightEvcs.nightSessionStartSocPct)) {
|
|
reason.push(
|
|
`SOC départ session nuit=${round1(nightEvcs.nightSessionStartSocPct)}% | décharge autorisée=${round1(nightEvcs.maxBatteryDischargePoints)} pts`
|
|
);
|
|
}
|
|
|
|
if (nightEvcs.batteryRecoveryUrgent) {
|
|
reason.push("Urgence départ: il faut anticiper la reprise réseau");
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// OUTPUT
|
|
// =====================================================
|
|
return [{
|
|
json: {
|
|
...prev,
|
|
decision: {
|
|
desiredCharging,
|
|
desiredPermission,
|
|
desiredLatch,
|
|
desiredCurrentA,
|
|
|
|
state,
|
|
currentDecision,
|
|
dynamicOk,
|
|
mustStop,
|
|
reason,
|
|
|
|
nextBlockReason,
|
|
nextBlockedUntilStamp,
|
|
|
|
setCloudDebtStartedAt,
|
|
setProbeStartedAt,
|
|
|
|
setNightSessionStart,
|
|
clearNightSessionStart,
|
|
|
|
notifyWanted,
|
|
notifyTitle,
|
|
notifyMessage,
|
|
}
|
|
}
|
|
}];
|
|
|