293 lines
12 KiB
JavaScript
293 lines
12 KiB
JavaScript
// =====================================================
|
|
// PARAMETERS
|
|
// =====================================================
|
|
const params = {
|
|
userStrategyRaw: input.victron_evcs_strategy || 'auto_solar',
|
|
|
|
targetCurrentAutoA: clamp(num(input.target_current_auto_a, 6), 0, 32),
|
|
pvStartMinW: Math.max(0, num(input.pv_start_min_w, 1800)),
|
|
pvHoldMinW: Math.max(0, num(input.pv_hold_min_w, 1200)),
|
|
houseAcHeadroomW: Math.max(0, num(input.house_ac_headroom_w, 1200)),
|
|
|
|
batteryDischargeSoftA: num(input.battery_discharge_soft_a, -5),
|
|
batteryDischargeHardA: num(input.battery_discharge_hard_a, -15),
|
|
batteryReserveUpA: Math.max(0, num(input.battery_reserve_up_a, 8)),
|
|
batteryNeutralBandA: Math.max(0, num(input.battery_neutral_band_a, 3)),
|
|
|
|
socStartMin: clamp(num(input.soc_start_min, 60), 0, 100),
|
|
socStopMin: clamp(num(input.soc_stop_min, 35), 0, 100),
|
|
|
|
currentMinA: clamp(num(input.current_min_a, 6), 6, 32),
|
|
currentMaxAutoA: clamp(
|
|
num(input.current_max_auto_a, 16),
|
|
clamp(num(input.current_min_a, 6), 6, 32),
|
|
32
|
|
),
|
|
currentStepUpA: clamp(num(input.current_step_up_a, 1), 1, 5),
|
|
currentStepDownA: clamp(num(input.current_step_down_a, 2), 1, 8),
|
|
|
|
currentChangeCooldownSeconds: Math.max(5, num(input.current_change_cooldown_seconds, 60)),
|
|
minRunMinutes: Math.max(0, num(input.min_run_minutes, 3)),
|
|
minStopMinutes: Math.max(0, num(input.min_stop_minutes, 10)),
|
|
|
|
requiredEnergyKwh: Math.max(0, num(input.required_energy_kwh, 0)),
|
|
socComfortTarget: clamp(num(input.soc_comfort_target, 85), 0, 100),
|
|
endOfDaySocGuard: clamp(num(input.end_of_day_soc_guard, 85), 0, 100),
|
|
baseLoadRemainingKwh: Math.max(0, num(input.base_load_remaining_kwh, 0)),
|
|
flexibleSafetyMarginKwh: Math.max(0, num(input.flexible_safety_margin_kwh, 0)),
|
|
endOfDaySocTarget: clamp(num(input.end_of_day_soc_target, 85), 0, 100),
|
|
|
|
acLoadsMaxSafeW: Math.max(0, num(input.ac_loads_max_safe_w, 8500)),
|
|
batteryFullSoc: clamp(num(input.battery_full_soc, 90), 0, 100),
|
|
batteryFullForecastMinKwh: Math.max(0, num(input.battery_full_forecast_min_kwh, 8)),
|
|
forecastCommitSocFloor: clamp(num(input.forecast_commit_soc_floor, 35), 0, 100),
|
|
forecastCommitMinKwh: Math.max(0, num(input.forecast_commit_min_kwh, 25)),
|
|
|
|
cloudDebtPrevKwh: Math.max(0, num(input.cloud_debt_kwh, 0)),
|
|
cloudDebtMaxKwh: Math.max(0.05, num(input.cloud_debt_max_kwh, 1.2)),
|
|
cloudToleranceMinutes: Math.max(1, num(input.cloud_tolerance_minutes, 20)),
|
|
|
|
departureTimeMin: parseTimeToMinutes(input.departure_time, null),
|
|
dayStartTimeMin: parseTimeToMinutes(input.day_start_time, 7 * 60 + 30),
|
|
dayStopTimeMin: parseTimeToMinutes(input.day_stop_time, 20 * 60 + 30),
|
|
|
|
hcNightStartMin: parseTimeToMinutes(input.hc_night_start_time, 23 * 60),
|
|
hcNightStopMin: parseTimeToMinutes(input.hc_night_stop_time, 6 * 60),
|
|
morningStartMin: parseTimeToMinutes(
|
|
input.morning_start_time,
|
|
parseTimeToMinutes(input.day_start_time, 7 * 60 + 30)
|
|
),
|
|
morningStopMin: parseTimeToMinutes(input.morning_stop_time, 11 * 60),
|
|
hcMiddayStartMin: parseTimeToMinutes(input.hc_midday_start_time, 12 * 60 + 21),
|
|
hcMiddayStopMin: parseTimeToMinutes(input.hc_midday_stop_time, 14 * 60 + 21),
|
|
eveningStartMin: parseTimeToMinutes(input.evening_start_time, 17 * 60),
|
|
eveningStopMin: parseTimeToMinutes(
|
|
input.evening_stop_time,
|
|
parseTimeToMinutes(input.day_stop_time, 20 * 60 + 30)
|
|
),
|
|
|
|
allowGridHcNight: low(input.allow_grid_hc_night || 'off') === 'on',
|
|
allowGridHcMidday: low(input.allow_grid_hc_midday || 'off') === 'on',
|
|
|
|
probeEnabled: low(input.probe_enabled || 'off') === 'on',
|
|
probeMinutes: Math.max(1, num(input.probe_minutes, 3)),
|
|
probeRetryMinutes: Math.max(5, num(input.probe_retry_minutes, 45)),
|
|
probeCurrentA: clamp(
|
|
num(input.probe_current_a, clamp(num(input.current_min_a, 6), 6, 32)),
|
|
clamp(num(input.current_min_a, 6), 6, 32),
|
|
clamp(num(input.current_max_auto_a, 16), clamp(num(input.current_min_a, 6), 6, 32), 32)
|
|
),
|
|
|
|
gridBatterySoftA: num(input.grid_battery_soft_a, -3),
|
|
gridBatteryHardA: num(input.grid_battery_hard_a, -6),
|
|
|
|
morningProbePvAvgMinW: Math.max(0, num(input.morning_probe_pv_avg_min_w, 1500)),
|
|
morningProbePvTrendMinW: num(input.morning_probe_pv_trend_min_w, 0),
|
|
middayProbePvAvgMinW: Math.max(0, num(input.midday_probe_pv_avg_min_w, 1200)),
|
|
|
|
morningSocMin: clamp(num(input.morning_soc_min, 35), 0, 100),
|
|
middaySocMin: clamp(num(input.midday_soc_min, 75), 0, 100),
|
|
eveningSocStartMin: clamp(num(input.evening_soc_start_min, 85), 0, 100),
|
|
eveningSocStopMin: clamp(num(input.evening_soc_stop_min, 80), 0, 100),
|
|
|
|
morningBatteryMaxDischargeA: num(input.morning_battery_max_discharge_a, -20),
|
|
middayBatteryMaxDischargeA: num(input.midday_battery_max_discharge_a, -40),
|
|
eveningBatteryMaxDischargeA: num(input.evening_battery_max_discharge_a, -30),
|
|
|
|
// =====================================================
|
|
// EVCS NIGHT ENGINE — NEW PARAMETERS
|
|
// =====================================================
|
|
evcsNightBatteryHardFloorPct: clamp(
|
|
num(input.evcs_night_battery_hard_floor_pct, 22),
|
|
0,
|
|
100
|
|
),
|
|
|
|
evcsNightStrongForecastKwh: Math.max(
|
|
0,
|
|
num(input.evcs_night_strong_forecast_kwh, 18)
|
|
),
|
|
|
|
evcsNightMediumForecastKwh: Math.max(
|
|
0,
|
|
num(input.evcs_night_medium_forecast_kwh, 10)
|
|
),
|
|
|
|
evcsNightTargetStrongForecastPct: clamp(
|
|
num(input.evcs_night_target_strong_forecast_pct, 35),
|
|
0,
|
|
100
|
|
),
|
|
|
|
evcsNightTargetMediumForecastPct: clamp(
|
|
num(input.evcs_night_target_medium_forecast_pct, 55),
|
|
0,
|
|
100
|
|
),
|
|
|
|
evcsNightTargetWeakForecastPct: clamp(
|
|
num(input.evcs_night_target_weak_forecast_pct, 85),
|
|
0,
|
|
100
|
|
),
|
|
|
|
evcsNightBatteryRecoveryPowerKw: Math.max(
|
|
0.1,
|
|
num(input.evcs_night_battery_recovery_power_kw, 2.5)
|
|
),
|
|
|
|
evcsNightDepartureSafetyMin: Math.max(
|
|
0,
|
|
num(input.evcs_night_departure_safety_min, 30)
|
|
),
|
|
};
|
|
|
|
// =====================================================
|
|
// HISTORY / PERSISTED HELPERS
|
|
// =====================================================
|
|
const nightSessionStartSocRaw = num(
|
|
input.evcs_night_session_start_soc ?? input.night_session_start_soc,
|
|
NaN
|
|
);
|
|
|
|
const nightSessionStartAtRaw = cleanText(
|
|
input.evcs_night_session_start_at ?? input.night_session_start_at
|
|
);
|
|
|
|
const history = {
|
|
lastBlockReason: cleanText(input.last_block_reason, 'none') || 'none',
|
|
blockedUntilAt: cleanText(input.blocked_until),
|
|
probeStartedAt: cleanText(input.probe_started_at),
|
|
lastStartAt: cleanText(input.last_start_at),
|
|
lastStopAt: cleanText(input.last_stop_at),
|
|
lastCurrentChangeAt: cleanText(input.last_current_change_at),
|
|
lastSignature: cleanText(input.last_signature),
|
|
cloudDebtStartedAt: cleanText(input.cloud_debt_started_at),
|
|
evcsPriorityManualUntilAt: cleanText(input.evcs_priority_manual_until),
|
|
|
|
blockedUntilMs: parseParisStampToMs(cleanText(input.blocked_until)),
|
|
probeStartedMs: parseParisStampToMs(cleanText(input.probe_started_at)),
|
|
lastStartMs: parseParisStampToMs(cleanText(input.last_start_at)),
|
|
lastStopMs: parseParisStampToMs(cleanText(input.last_stop_at)),
|
|
lastCurrentChangeMs: parseParisStampToMs(cleanText(input.last_current_change_at)),
|
|
cloudDebtStartedMsPrev: parseParisStampToMs(cleanText(input.cloud_debt_started_at)),
|
|
evcsPriorityManualUntilMs: parseParisStampToMs(cleanText(input.evcs_priority_manual_until)),
|
|
|
|
// =====================================================
|
|
// EVCS NIGHT SESSION MEMORY
|
|
// =====================================================
|
|
nightSessionStartSocPct: Number.isFinite(nightSessionStartSocRaw)
|
|
? clamp(nightSessionStartSocRaw, 0, 100)
|
|
: null,
|
|
|
|
nightSessionStartAt: nightSessionStartAtRaw || '',
|
|
nightSessionStartMs: parseParisStampToMs(nightSessionStartAtRaw),
|
|
};
|
|
|
|
// =====================================================
|
|
// BATTERY TUNING
|
|
// =====================================================
|
|
const batteryTune = {
|
|
global: {
|
|
houseSpikeGraceMinutes: Math.max(0, num(input.house_spike_grace_minutes, 10)),
|
|
solarPotentialGraceMinutes: Math.max(0, num(input.solar_potential_grace_minutes, 12)),
|
|
stepUpCooldownSeconds: Math.max(5, num(input.step_up_cooldown_seconds, params.currentChangeCooldownSeconds)),
|
|
stepDownCooldownSeconds: Math.max(5, num(input.step_down_cooldown_seconds, params.currentChangeCooldownSeconds)),
|
|
forceModeAutoRevertMinutes: Math.max(0, num(input.force_mode_auto_revert_minutes, 120)),
|
|
overloadStepDownA: Math.max(1, num(input.overload_step_down_a, 2)),
|
|
overloadHoldSeconds: Math.max(5, num(input.overload_hold_seconds, 30)),
|
|
overloadStopAfterSeconds: Math.max(10, num(input.overload_stop_after_seconds, 120)),
|
|
},
|
|
|
|
bands: {
|
|
gt90: {
|
|
dischargeSoftA: num(input.batt_discharge_soft_gt90_a, -50),
|
|
dischargeHardA: num(input.batt_discharge_hard_gt90_a, -100),
|
|
chargeReserveA: Math.max(0, num(input.batt_charge_reserve_gt90_a, 5)),
|
|
softToleranceMin: Math.max(0, num(input.batt_soft_tol_gt90_min, 12)),
|
|
hardToleranceSec: Math.max(0, num(input.batt_hard_tol_gt90_sec, 20)),
|
|
recoveryMin: Math.max(0, num(input.batt_recovery_gt90_min, 6)),
|
|
},
|
|
|
|
'80_90': {
|
|
dischargeSoftA: num(input.batt_discharge_soft_80_90_a, -40),
|
|
dischargeHardA: num(input.batt_discharge_hard_80_90_a, -80),
|
|
chargeReserveA: Math.max(0, num(input.batt_charge_reserve_80_90_a, 10)),
|
|
softToleranceMin: Math.max(0, num(input.batt_soft_tol_80_90_min, 10)),
|
|
hardToleranceSec: Math.max(0, num(input.batt_hard_tol_80_90_sec, 20)),
|
|
recoveryMin: Math.max(0, num(input.batt_recovery_80_90_min, 6)),
|
|
},
|
|
|
|
'60_80': {
|
|
dischargeSoftA: num(input.batt_discharge_soft_60_80_a, -20),
|
|
dischargeHardA: num(input.batt_discharge_hard_60_80_a, -50),
|
|
chargeReserveA: Math.max(0, num(input.batt_charge_reserve_60_80_a, 40)),
|
|
softToleranceMin: Math.max(0, num(input.batt_soft_tol_60_80_min, 6)),
|
|
hardToleranceSec: Math.max(0, num(input.batt_hard_tol_60_80_sec, 15)),
|
|
recoveryMin: Math.max(0, num(input.batt_recovery_60_80_min, 8)),
|
|
},
|
|
|
|
'40_60': {
|
|
dischargeSoftA: num(input.batt_discharge_soft_40_60_a, -12),
|
|
dischargeHardA: num(input.batt_discharge_hard_40_60_a, -30),
|
|
chargeReserveA: Math.max(0, num(input.batt_charge_reserve_40_60_a, 70)),
|
|
softToleranceMin: Math.max(0, num(input.batt_soft_tol_40_60_min, 4)),
|
|
hardToleranceSec: Math.max(0, num(input.batt_hard_tol_40_60_sec, 10)),
|
|
recoveryMin: Math.max(0, num(input.batt_recovery_40_60_min, 10)),
|
|
},
|
|
|
|
'25_40': {
|
|
dischargeSoftA: num(input.batt_discharge_soft_25_40_a, -5),
|
|
dischargeHardA: num(input.batt_discharge_hard_25_40_a, -15),
|
|
chargeReserveA: Math.max(0, num(input.batt_charge_reserve_25_40_a, 110)),
|
|
softToleranceMin: Math.max(0, num(input.batt_soft_tol_25_40_min, 2)),
|
|
hardToleranceSec: Math.max(0, num(input.batt_hard_tol_25_40_sec, 5)),
|
|
recoveryMin: Math.max(0, num(input.batt_recovery_25_40_min, 12)),
|
|
},
|
|
|
|
lt25: {
|
|
dischargeSoftA: num(input.batt_discharge_soft_lt25_a, -3),
|
|
dischargeHardA: num(input.batt_discharge_hard_lt25_a, -8),
|
|
chargeReserveA: Math.max(0, num(input.batt_charge_reserve_lt25_a, 150)),
|
|
softToleranceMin: Math.max(0, num(input.batt_soft_tol_lt25_min, 1)),
|
|
hardToleranceSec: Math.max(0, num(input.batt_hard_tol_lt25_sec, 2)),
|
|
recoveryMin: Math.max(0, num(input.batt_recovery_lt25_min, 15)),
|
|
},
|
|
},
|
|
};
|
|
|
|
// =====================================================
|
|
// OPTIONAL DEBUG / SUMMARY
|
|
// =====================================================
|
|
const evcsNightConfig = {
|
|
batteryHardFloorPct: params.evcsNightBatteryHardFloorPct,
|
|
strongForecastKwh: params.evcsNightStrongForecastKwh,
|
|
mediumForecastKwh: params.evcsNightMediumForecastKwh,
|
|
targetStrongForecastPct: params.evcsNightTargetStrongForecastPct,
|
|
targetMediumForecastPct: params.evcsNightTargetMediumForecastPct,
|
|
targetWeakForecastPct: params.evcsNightTargetWeakForecastPct,
|
|
batteryRecoveryPowerKw: params.evcsNightBatteryRecoveryPowerKw,
|
|
departureSafetyMin: params.evcsNightDepartureSafetyMin,
|
|
sessionStartSocPct: history.nightSessionStartSocPct,
|
|
sessionStartAt: history.nightSessionStartAt || null,
|
|
};
|
|
|
|
// =====================================================
|
|
// OUTPUT
|
|
// =====================================================
|
|
return [{
|
|
json: {
|
|
raw,
|
|
input,
|
|
nowMs,
|
|
now,
|
|
cfg: CFG,
|
|
parsed,
|
|
params,
|
|
history,
|
|
batteryTune,
|
|
evcsNightConfig,
|
|
}
|
|
}];
|
|
|