From af87a76a6536cfdfc58499df8085fa96ef287b83 Mon Sep 17 00:00:00 2001 From: Tallagrand Thierry Date: Fri, 12 Jun 2026 02:45:24 +0200 Subject: [PATCH] feat: Add afternoon_probe for outside-window solar charging - Add afternoonProbeEligible in WINDOW_SOURCEV2.js - Enable hybrid_probe sourceMode in outside_window when probe conditions met - Add afternoon probe parameters in INPUTPARSE (inputparse.js + inputparseP2.js) - Update probeStartOk in BATTERY_CLOUD_LOCKS to accept afternoon probe - Update blockStillRelevant for probe_failed to include outside_window Fixes the issue where strong afternoon sun with SOC>85% was blocked by high cloudDebt and no probe mechanism. --- BATTERY/BATTERY_CLOUD_LOCKS.js | 2 +- WINDOWS/WINDOW_SOURCEV2.js | 12 ++++++++++++ decoupesurplus/BATTERY_CLOUD_LOCKS.js | 4 ++-- decoupesurplus/V2/INPUTPARSE/inputparse.js | 4 ++++ decoupesurplus/V2/INPUTPARSE/inputparseP2.js | 4 ++++ 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/BATTERY/BATTERY_CLOUD_LOCKS.js b/BATTERY/BATTERY_CLOUD_LOCKS.js index f8a2a03..b8792e3 100644 --- a/BATTERY/BATTERY_CLOUD_LOCKS.js +++ b/BATTERY/BATTERY_CLOUD_LOCKS.js @@ -594,7 +594,7 @@ const probeModeActive = context.sourceMode === 'hybrid_probe'; const probeStartOk = probeModeActive && commonSafetyStartOk && - (context.morningProbeEligible || context.middayProbeEligible); + (context.morningProbeEligible || context.middayProbeEligible || context.afternoonProbeEligible); const probeHoldGuardOk = probeModeActive && diff --git a/WINDOWS/WINDOW_SOURCEV2.js b/WINDOWS/WINDOW_SOURCEV2.js index 4b5d0ae..e9e59d8 100644 --- a/WINDOWS/WINDOW_SOURCEV2.js +++ b/WINDOWS/WINDOW_SOURCEV2.js @@ -329,6 +329,13 @@ const middayProbeEligible = parsed.soc >= params.middaySocMin && parsed.batteryCurrentA >= params.middayBatteryMaxDischargeA; +const afternoonProbeEligible = + profile === 'outside_window' && + params.probeEnabled && + parsed.soc >= params.afternoonProbeSocMin && + parsed.batteryCurrentA >= params.afternoonBatteryMaxDischargeA && + parsed.pvPowerAvg10mW >= params.afternoonProbePvAvgMinW; + const cloudDebtOkForPotential = params.cloudDebtPrevKwh <= params.cloudDebtMaxKwh; const batteryOkForPotential = parsed.batteryCurrentA >= -Math.max(1, params.batteryNeutralBandA); @@ -460,6 +467,10 @@ else if (profile === 'outside_window') { sourceMode = 'solar_potential'; } + else if (afternoonProbeEligible) { + sourceMode = 'hybrid_probe'; + } + else { // IMPORTANT: // hors fenêtre = pas de réseau programmé. @@ -537,6 +548,7 @@ return [{ manualChargeLocalRequested, morningProbeEligible, middayProbeEligible, + afternoonProbeEligible, isHighBatteryForPotential, strongSolarSignal, effectivePvStartMinW, diff --git a/decoupesurplus/BATTERY_CLOUD_LOCKS.js b/decoupesurplus/BATTERY_CLOUD_LOCKS.js index 1c2e724..ec46b30 100644 --- a/decoupesurplus/BATTERY_CLOUD_LOCKS.js +++ b/decoupesurplus/BATTERY_CLOUD_LOCKS.js @@ -10,7 +10,7 @@ const nextAllowedWindowStartMs = const blockActive = blockedUntilMs !== null && blockedUntilMs > nowMs + 1000; const blockStillRelevant = lastBlockReason === 'outside_window' ? profile === 'outside_window' : - lastBlockReason === 'probe_failed' ? (profile === 'morning_solar' || profile === 'midday_hc_or_solar') : + lastBlockReason === 'probe_failed' ? (profile === 'morning_solar' || profile === 'midday_hc_or_solar' || profile === 'outside_window') : lastBlockReason === 'battery_protection_stop' ? ( batteryCurrentA < -batteryNeutralBandA || anyVebusLowBattery || vebusDcVoltageSoftLow ) : @@ -133,7 +133,7 @@ const gridHoldOk = const probeStartOk = sourceMode === 'hybrid_probe' && commonSafetyStartOk && - (morningProbeEligible || middayProbeEligible); + (morningProbeEligible || middayProbeEligible || afternoonProbeEligible); const probeHoldGuardOk = commonSafetyHoldOk && batteryCurrentA >= profileBatteryDischargeFloorA; diff --git a/decoupesurplus/V2/INPUTPARSE/inputparse.js b/decoupesurplus/V2/INPUTPARSE/inputparse.js index 5edb275..92f42be 100644 --- a/decoupesurplus/V2/INPUTPARSE/inputparse.js +++ b/decoupesurplus/V2/INPUTPARSE/inputparse.js @@ -346,6 +346,10 @@ const params = { morningProbePvTrendMinW: num(input.morning_probe_pv_trend_min_w, 0), middayProbePvAvgMinW: Math.max(0, num(input.midday_probe_pv_avg_min_w, 1200)), + afternoonProbePvAvgMinW: Math.max(0, num(input.afternoon_probe_pv_avg_min_w, 1000)), + afternoonProbeSocMin: clamp(num(input.afternoon_probe_soc_min, 85), 0, 100), + afternoonBatteryMaxDischargeA: num(input.afternoon_battery_max_discharge_a, -20), + 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), diff --git a/decoupesurplus/V2/INPUTPARSE/inputparseP2.js b/decoupesurplus/V2/INPUTPARSE/inputparseP2.js index 3bfc6b8..7240fcc 100644 --- a/decoupesurplus/V2/INPUTPARSE/inputparseP2.js +++ b/decoupesurplus/V2/INPUTPARSE/inputparseP2.js @@ -85,6 +85,10 @@ const params = { morningProbePvTrendMinW: num(input.morning_probe_pv_trend_min_w, 0), middayProbePvAvgMinW: Math.max(0, num(input.midday_probe_pv_avg_min_w, 1200)), + afternoonProbePvAvgMinW: Math.max(0, num(input.afternoon_probe_pv_avg_min_w, 1000)), + afternoonProbeSocMin: clamp(num(input.afternoon_probe_soc_min, 85), 0, 100), + afternoonBatteryMaxDischargeA: num(input.afternoon_battery_max_discharge_a, -20), + 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),