version 2.2
This commit is contained in:
217
decoupesurplus/BATTERY_CLOUD_LOCKS.js
Normal file
217
decoupesurplus/BATTERY_CLOUD_LOCKS.js
Normal file
@@ -0,0 +1,217 @@
|
||||
// === REASON-BASED LOCKS ===
|
||||
const nextWindowCandidates = [
|
||||
nextDailyOccurrenceMs(morningStartMin, nowMs),
|
||||
nextDailyOccurrenceMs(hcMiddayStartMin, nowMs),
|
||||
nextDailyOccurrenceMs(eveningStartMin, nowMs),
|
||||
nextDailyOccurrenceMs(hcNightStartMin, nowMs),
|
||||
].filter(v => Number.isFinite(v));
|
||||
const nextAllowedWindowStartMs =
|
||||
nextWindowCandidates.length > 0 ? Math.min(...nextWindowCandidates) : null;
|
||||
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 === 'battery_protection_stop' ? (
|
||||
batteryCurrentA < -batteryNeutralBandA || anyVebusLowBattery || vebusDcVoltageSoftLow
|
||||
) :
|
||||
lastBlockReason === 'grid_battery_guard' ? sourceMode === 'grid_hc_allowed' :
|
||||
false;
|
||||
const startBlockedByReason = blockActive && blockStillRelevant;
|
||||
|
||||
// === CLOUD DEBT ===
|
||||
const sessionObservedActive = evcsChargingSwitchOn || controlLatch || chargePermissionNow;
|
||||
const batteryNetPowerW = batteryCurrentA * batteryVoltageRef;
|
||||
let cloudDebtNextKwh = cloudDebtPrevKwh;
|
||||
if (sessionObservedActive) {
|
||||
if (batteryNetPowerW < 0 && sourceMode !== 'grid_hc_allowed') {
|
||||
cloudDebtNextKwh += (Math.abs(batteryNetPowerW) * CFG.sampleSeconds) / 3600000;
|
||||
} else if (batteryNetPowerW > 0) {
|
||||
cloudDebtNextKwh -= (batteryNetPowerW * CFG.sampleSeconds) / 3600000;
|
||||
}
|
||||
} else if (batteryNetPowerW > 0) {
|
||||
cloudDebtNextKwh -= (batteryNetPowerW * CFG.sampleSeconds) / 3600000;
|
||||
}
|
||||
cloudDebtNextKwh = clamp(cloudDebtNextKwh, 0, cloudDebtMaxKwh * 4);
|
||||
const cloudDebtMeaningful = cloudDebtNextKwh > CFG.cloudDebtMeaningfulThresholdKwh;
|
||||
const cloudEventDischargingNow =
|
||||
sessionObservedActive &&
|
||||
sourceMode !== 'grid_hc_allowed' &&
|
||||
batteryCurrentA < -Math.max(1, batteryNeutralBandA) &&
|
||||
cloudDebtMeaningful;
|
||||
const continuingCloudEvent =
|
||||
cloudEventDischargingNow &&
|
||||
cloudDebtStartedMsPrev !== null &&
|
||||
cloudDebtPrevKwh > CFG.cloudDebtRecoveryThresholdKwh;
|
||||
const enteringCloudDebt = cloudEventDischargingNow && !continuingCloudEvent;
|
||||
const cloudDebtActive = cloudEventDischargingNow;
|
||||
const cloudDebtStartedMs =
|
||||
cloudEventDischargingNow
|
||||
? (continuingCloudEvent ? cloudDebtStartedMsPrev : nowMs)
|
||||
: null;
|
||||
const cloudDebtDurationMinutes =
|
||||
cloudDebtActive && cloudDebtStartedMs !== null
|
||||
? Math.max(0, (nowMs - cloudDebtStartedMs) / 60000)
|
||||
: 0;
|
||||
const cloudToleranceOk =
|
||||
cloudDebtNextKwh <= cloudDebtMaxKwh &&
|
||||
(
|
||||
!cloudDebtActive ||
|
||||
cloudDebtDurationMinutes <= cloudToleranceMinutes
|
||||
);
|
||||
|
||||
// === LEGACY SOLAR MODES KEPT ===
|
||||
const batteryFullHarvestMode =
|
||||
forecastModeEnabled &&
|
||||
profile !== 'night_hc' &&
|
||||
profile !== 'outside_window' &&
|
||||
soc >= batteryFullSoc &&
|
||||
todayRemainingKwh >= batteryFullForecastMinKwh &&
|
||||
projectedEndSocAfterCharge >= (endDayGuardSoc - 2);
|
||||
const forecastCommitMode =
|
||||
forecastModeEnabled &&
|
||||
profile !== 'night_hc' &&
|
||||
profile !== 'outside_window' &&
|
||||
soc >= forecastCommitSocFloor &&
|
||||
todayRemainingKwh >= forecastCommitMinKwh &&
|
||||
projectedEndSocAfterCharge >= endDayGuardSoc;
|
||||
const batteryPriorityMode = !batteryFullHarvestMode && !forecastCommitMode;
|
||||
|
||||
// === BASE CONDITIONS ===
|
||||
const commonSafetyStartOk =
|
||||
canRunAtMinCurrent &&
|
||||
!anyVebusOverload &&
|
||||
!anyVebusLowBattery &&
|
||||
!vebusDcVoltageSoftLow &&
|
||||
!vebusAcOutSoftReached &&
|
||||
evcsDeviceConnected &&
|
||||
evcsVehicleConnected &&
|
||||
!evcsModeIsAuto;
|
||||
const commonSafetyHoldOk =
|
||||
canRunAtMinCurrent &&
|
||||
!anyVebusOverload &&
|
||||
!anyVebusLowBattery &&
|
||||
!vebusDcVoltageHardLow &&
|
||||
evcsDeviceConnected &&
|
||||
evcsVehicleConnected &&
|
||||
!evcsModeIsAuto;
|
||||
const profileSocStartFloor =
|
||||
profile === 'morning_solar' ? morningSocMin :
|
||||
profile === 'midday_hc_or_solar' ? middaySocMin :
|
||||
profile === 'evening_high_soc' ? eveningSocStartMin :
|
||||
socStartMin;
|
||||
const profileSocHoldFloor =
|
||||
profile === 'morning_solar' ? Math.max(socStopMin, morningSocMin - 10) :
|
||||
profile === 'midday_hc_or_solar' ? Math.max(socStopMin, middaySocMin - 15) :
|
||||
profile === 'evening_high_soc' ? eveningSocStopMin :
|
||||
socStopMin;
|
||||
const profileBatteryDischargeFloorA =
|
||||
profile === 'morning_solar' ? morningBatteryMaxDischargeA :
|
||||
profile === 'midday_hc_or_solar' ? middayBatteryMaxDischargeA :
|
||||
profile === 'evening_high_soc' ? eveningBatteryMaxDischargeA :
|
||||
batteryDischargeSoftA;
|
||||
const solarStartOk =
|
||||
sourceMode === 'solar_only' || sourceMode === 'solar_potential' &&
|
||||
commonSafetyStartOk &&
|
||||
soc >= profileSocStartFloor &&
|
||||
batteryCurrentA >= profileBatteryDischargeFloorA &&
|
||||
(sourceMode === 'solar_potential' || pvPowerW >= effectivePvStartMinW);
|
||||
const solarHoldOk =
|
||||
commonSafetyHoldOk &&
|
||||
soc >= profileSocHoldFloor &&
|
||||
batteryCurrentA >= profileBatteryDischargeFloorA &&
|
||||
(pvPowerW >= pvHoldMinW || cloudToleranceOk);
|
||||
const gridStartOk =
|
||||
(sourceMode === 'grid_hc_allowed' || sourceMode === 'grid_forced') &&
|
||||
commonSafetyStartOk &&
|
||||
gridIsOn &&
|
||||
batteryCurrentA >= gridBatterySoftA;
|
||||
const gridHoldOk =
|
||||
commonSafetyHoldOk &&
|
||||
(sourceMode === 'grid_hc_allowed' || sourceMode === 'grid_forced') &&
|
||||
gridIsOn &&
|
||||
batteryCurrentA >= gridBatteryHardA;
|
||||
const probeStartOk =
|
||||
sourceMode === 'hybrid_probe' &&
|
||||
commonSafetyStartOk &&
|
||||
(morningProbeEligible || middayProbeEligible);
|
||||
const probeHoldGuardOk =
|
||||
commonSafetyHoldOk &&
|
||||
batteryCurrentA >= profileBatteryDischargeFloorA;
|
||||
const probeWindowActive =
|
||||
sessionObservedActive &&
|
||||
probeStartedMs !== null &&
|
||||
minutesSinceProbeStart !== null &&
|
||||
minutesSinceProbeStart < probeMinutes;
|
||||
const probeElapsedReached =
|
||||
sessionObservedActive &&
|
||||
probeStartedMs !== null &&
|
||||
minutesSinceProbeStart !== null &&
|
||||
minutesSinceProbeStart >= probeMinutes;
|
||||
const probeSuccessNow =
|
||||
sessionObservedActive &&
|
||||
probeStartedMs !== null &&
|
||||
(
|
||||
pvPowerW >= pvHoldMinW &&
|
||||
batteryCurrentA >= Math.min(0, batteryDischargeSoftA)
|
||||
);
|
||||
const probeFailedNow = probeElapsedReached && !probeSuccessNow;
|
||||
|
||||
// === STEP LOGIC ===
|
||||
const stepUpByBatteryReserve =
|
||||
batteryPriorityMode &&
|
||||
batteryCurrentA >= batteryReserveUpA;
|
||||
const stepUpByForecastMode =
|
||||
!batteryPriorityMode &&
|
||||
(pvPowerW >= pvHoldMinW || pvPowerAvg10mW >= pvHoldMinW) &&
|
||||
forecastBufferKwh >= -1 &&
|
||||
projectedEndSocAfterCharge >= (endDayGuardSoc - 5) &&
|
||||
cloudToleranceOk;
|
||||
const solarShouldStepUp =
|
||||
currentCooldownElapsed &&
|
||||
currentReferenceA < physicalCapA &&
|
||||
physicalCapA >= currentMinA &&
|
||||
!vebusAcOutSoftReached &&
|
||||
!vebusDcVoltageSoftLow &&
|
||||
!houseLoadHighVsHeadroom &&
|
||||
(stepUpByBatteryReserve || stepUpByForecastMode || pvPowerW >= (effectivePvStartMinW + 400));
|
||||
const gridShouldStepUp =
|
||||
currentCooldownElapsed &&
|
||||
currentReferenceA < physicalCapA &&
|
||||
physicalCapA >= currentMinA &&
|
||||
!vebusAcOutSoftReached &&
|
||||
!vebusDcVoltageSoftLow &&
|
||||
batteryCurrentA >= gridBatterySoftA &&
|
||||
!houseLoadHighVsHeadroom;
|
||||
const solarShouldStepDownBattery =
|
||||
batteryCurrentA <= batteryDischargeSoftA &&
|
||||
currentCooldownElapsed &&
|
||||
currentReferenceA > currentMinA;
|
||||
const solarShouldStepDownCloudDebt =
|
||||
!cloudToleranceOk &&
|
||||
currentCooldownElapsed &&
|
||||
currentReferenceA > currentMinA;
|
||||
const gridShouldStepDownBattery =
|
||||
batteryCurrentA < gridBatterySoftA &&
|
||||
currentCooldownElapsed &&
|
||||
currentReferenceA > currentMinA;
|
||||
const shouldStepDownVebusPower =
|
||||
vebusAcOutSoftReached &&
|
||||
currentCooldownElapsed &&
|
||||
currentReferenceA > currentMinA;
|
||||
const shouldStepDownVebusVoltage =
|
||||
vebusDcVoltageSoftLow &&
|
||||
currentCooldownElapsed &&
|
||||
currentReferenceA > currentMinA;
|
||||
const batteryCurrentHardExceeded = batteryCurrentA <= batteryDischargeHardA;
|
||||
|
||||
// === HARD STOPS ===
|
||||
const mustStopHard =
|
||||
anyVebusOverload ||
|
||||
anyVebusLowBattery ||
|
||||
vebusDcVoltageHardLow ||
|
||||
vebusAcOutHardReached ||
|
||||
batteryCurrentHardExceeded;
|
||||
const gridBatteryProtectionStop =
|
||||
sessionObservedActive &&
|
||||
sourceMode === 'grid_hc_allowed' &&
|
||||
batteryCurrentA < gridBatteryHardA;
|
||||
Reference in New Issue
Block a user