374 lines
11 KiB
JavaScript
374 lines
11 KiB
JavaScript
// =====================================================
|
|
// LEGACY / FORECAST MODES
|
|
// =====================================================
|
|
const batteryFullHarvestMode =
|
|
parsed.forecastModeEnabled &&
|
|
context.profile !== 'night_hc' &&
|
|
context.profile !== 'outside_window' &&
|
|
parsed.soc >= params.batteryFullSoc &&
|
|
parsed.todayRemainingKwh >= params.batteryFullForecastMinKwh &&
|
|
derived.projectedEndSocAfterCharge >= (derived.endDayGuardSoc - 2);
|
|
|
|
const forecastCommitMode =
|
|
parsed.forecastModeEnabled &&
|
|
context.profile !== 'night_hc' &&
|
|
context.profile !== 'outside_window' &&
|
|
parsed.soc >= params.forecastCommitSocFloor &&
|
|
parsed.todayRemainingKwh >= params.forecastCommitMinKwh &&
|
|
derived.projectedEndSocAfterCharge >= derived.endDayGuardSoc;
|
|
|
|
const batteryPriorityMode =
|
|
!batteryFullHarvestMode && !forecastCommitMode;
|
|
|
|
// =====================================================
|
|
// PROFILE-BASED THRESHOLDS
|
|
// =====================================================
|
|
const profileSocStartFloor =
|
|
context.profile === 'morning_solar' ? params.morningSocMin :
|
|
context.profile === 'midday_hc_or_solar' ? params.middaySocMin :
|
|
context.profile === 'evening_high_soc' ? params.eveningSocStartMin :
|
|
params.socStartMin;
|
|
|
|
const profileSocHoldFloor =
|
|
context.profile === 'morning_solar' ? Math.max(params.socStopMin, params.morningSocMin - 10) :
|
|
context.profile === 'midday_hc_or_solar' ? Math.max(params.socStopMin, params.middaySocMin - 15) :
|
|
context.profile === 'evening_high_soc' ? params.eveningSocStopMin :
|
|
params.socStopMin;
|
|
|
|
const profileSpecificDischargeFloorA =
|
|
context.profile === 'morning_solar' ? params.morningBatteryMaxDischargeA :
|
|
context.profile === 'midday_hc_or_solar' ? params.middayBatteryMaxDischargeA :
|
|
context.profile === 'evening_high_soc' ? params.eveningBatteryMaxDischargeA :
|
|
params.batteryDischargeSoftA;
|
|
|
|
const profileBatteryDischargeFloorA =
|
|
Math.max(profileSpecificDischargeFloorA, batteryDischargeSoftDynamicA);
|
|
|
|
// =====================================================
|
|
// GRID RELAX IN force_charge_now
|
|
// =====================================================
|
|
// Assouplissement uniquement pour la garde réseau,
|
|
// sans toucher aux sécurités dures globales.
|
|
const gridForceChargeRelaxSoftA =
|
|
strategy.forceChargeRequested && isGridSource
|
|
? (
|
|
parsed.soc >= 90 ? 28 :
|
|
parsed.soc >= 80 ? 16 :
|
|
parsed.soc >= 60 ? 8 :
|
|
parsed.soc >= 40 ? 4 :
|
|
0
|
|
)
|
|
: 0;
|
|
|
|
const gridForceChargeRelaxHardA =
|
|
strategy.forceChargeRequested && isGridSource
|
|
? (
|
|
parsed.soc >= 90 ? 39 :
|
|
parsed.soc >= 80 ? 29 :
|
|
parsed.soc >= 60 ? 18 :
|
|
parsed.soc >= 40 ? 8 :
|
|
0
|
|
)
|
|
: 0;
|
|
|
|
const gridBatterySoftDynamicABase =
|
|
Math.max(params.gridBatterySoftA, batteryDischargeSoftDynamicA);
|
|
|
|
const gridBatteryHardDynamicABase =
|
|
Math.max(params.gridBatteryHardA, batteryDischargeHardDynamicA);
|
|
|
|
let gridBatterySoftDynamicA =
|
|
gridBatterySoftDynamicABase - gridForceChargeRelaxSoftA;
|
|
|
|
let gridBatteryHardDynamicA =
|
|
gridBatteryHardDynamicABase - gridForceChargeRelaxHardA;
|
|
|
|
// Garantit que le hard reste plus permissif que le soft d'au moins 3 A
|
|
if (gridBatteryHardDynamicA > (gridBatterySoftDynamicA - 3)) {
|
|
gridBatteryHardDynamicA = gridBatterySoftDynamicA - 3;
|
|
}
|
|
|
|
// =====================================================
|
|
// BASE SAFETY
|
|
// =====================================================
|
|
const commonSafetyStartOk =
|
|
derived.canRunAtMinCurrent &&
|
|
!derived.anyVebusOverload &&
|
|
!derived.anyVebusLowBattery &&
|
|
!derived.vebusDcVoltageSoftLow &&
|
|
!derived.vebusAcOutSoftReached &&
|
|
parsed.evcsDeviceConnected &&
|
|
parsed.evcsVehicleConnected &&
|
|
!parsed.evcsModeIsAuto;
|
|
|
|
const commonSafetyHoldOk =
|
|
derived.canRunAtMinCurrent &&
|
|
!derived.anyVebusLowBattery &&
|
|
!derived.vebusDcVoltageHardLow &&
|
|
parsed.evcsDeviceConnected &&
|
|
parsed.evcsVehicleConnected &&
|
|
!parsed.evcsModeIsAuto;
|
|
|
|
// =====================================================
|
|
// START / HOLD CONDITIONS
|
|
// =====================================================
|
|
const solarStartOk =
|
|
(context.sourceMode === 'solar_only' || context.sourceMode === 'solar_potential') &&
|
|
commonSafetyStartOk &&
|
|
parsed.soc >= profileSocStartFloor &&
|
|
parsed.batteryCurrentA >= profileBatteryDischargeFloorA &&
|
|
(
|
|
context.sourceMode === 'solar_potential' ||
|
|
parsed.pvPowerW >= context.effectivePvStartMinW
|
|
);
|
|
|
|
const solarHoldOk =
|
|
(context.sourceMode === 'solar_only' || context.sourceMode === 'solar_potential') &&
|
|
commonSafetyHoldOk &&
|
|
parsed.soc >= profileSocHoldFloor &&
|
|
parsed.batteryCurrentA >= profileBatteryDischargeFloorA &&
|
|
(
|
|
parsed.pvPowerW >= params.pvHoldMinW ||
|
|
cloudToleranceOk
|
|
);
|
|
|
|
const gridStartOk =
|
|
isGridSource &&
|
|
commonSafetyStartOk &&
|
|
parsed.gridAvailable &&
|
|
parsed.batteryCurrentA >= gridBatterySoftDynamicA;
|
|
|
|
const gridHoldOk =
|
|
isGridSource &&
|
|
commonSafetyHoldOk &&
|
|
parsed.gridAvailable &&
|
|
parsed.batteryCurrentA >= gridBatteryHardDynamicA;
|
|
|
|
// =====================================================
|
|
// PROBE LOGIC — STRICTEMENT LIMITÉE AU MODE PROBE
|
|
// =====================================================
|
|
const probeModeActive = context.sourceMode === 'hybrid_probe';
|
|
|
|
const probeStartOk =
|
|
probeModeActive &&
|
|
commonSafetyStartOk &&
|
|
(context.morningProbeEligible || context.middayProbeEligible);
|
|
|
|
const probeHoldGuardOk =
|
|
probeModeActive &&
|
|
commonSafetyHoldOk &&
|
|
parsed.batteryCurrentA >= profileBatteryDischargeFloorA;
|
|
|
|
const probeWindowActive =
|
|
probeModeActive &&
|
|
sessionObservedActive &&
|
|
history.probeStartedMs !== null &&
|
|
lifecycle.minutesSinceProbeStart !== null &&
|
|
lifecycle.minutesSinceProbeStart < params.probeMinutes;
|
|
|
|
const probeElapsedReached =
|
|
probeModeActive &&
|
|
sessionObservedActive &&
|
|
history.probeStartedMs !== null &&
|
|
lifecycle.minutesSinceProbeStart !== null &&
|
|
lifecycle.minutesSinceProbeStart >= params.probeMinutes;
|
|
|
|
const probeSuccessNow =
|
|
probeModeActive &&
|
|
sessionObservedActive &&
|
|
history.probeStartedMs !== null &&
|
|
(
|
|
parsed.pvPowerW >= params.pvHoldMinW &&
|
|
parsed.batteryCurrentA >= Math.min(0, batteryDischargeSoftDynamicA)
|
|
);
|
|
|
|
const probeFailedNow =
|
|
probeModeActive &&
|
|
probeElapsedReached &&
|
|
!probeSuccessNow;
|
|
|
|
// =====================================================
|
|
// STEP-UP LOGIC
|
|
// =====================================================
|
|
const stepUpByBatteryReserve =
|
|
batteryPriorityMode &&
|
|
parsed.batteryCurrentA >= batteryChargeReserveDynamicA;
|
|
|
|
const stepUpByForecastMode =
|
|
!batteryPriorityMode &&
|
|
(
|
|
parsed.pvPowerW >= params.pvHoldMinW ||
|
|
parsed.pvPowerAvg10mW >= params.pvHoldMinW
|
|
) &&
|
|
derived.forecastBufferKwh >= -1 &&
|
|
derived.projectedEndSocAfterCharge >= (derived.endDayGuardSoc - 5) &&
|
|
cloudToleranceOk;
|
|
|
|
const solarShouldStepUp =
|
|
lifecycle.currentCooldownElapsed &&
|
|
derived.currentReferenceA < derived.physicalCapA &&
|
|
derived.physicalCapA >= params.currentMinA &&
|
|
!derived.vebusAcOutSoftReached &&
|
|
!derived.vebusDcVoltageSoftLow &&
|
|
!derived.houseLoadHighVsHeadroom &&
|
|
(
|
|
stepUpByBatteryReserve ||
|
|
stepUpByForecastMode ||
|
|
parsed.pvPowerW >= (context.effectivePvStartMinW + 400)
|
|
);
|
|
|
|
const gridShouldStepUp =
|
|
lifecycle.currentCooldownElapsed &&
|
|
derived.currentReferenceA < derived.physicalCapA &&
|
|
derived.physicalCapA >= params.currentMinA &&
|
|
!derived.vebusAcOutSoftReached &&
|
|
!derived.vebusDcVoltageSoftLow &&
|
|
parsed.batteryCurrentA >= gridBatterySoftDynamicA &&
|
|
!derived.houseLoadHighVsHeadroom;
|
|
|
|
// =====================================================
|
|
// STEP-DOWN LOGIC
|
|
// =====================================================
|
|
const solarShouldStepDownBattery =
|
|
parsed.batteryCurrentA <= batteryDischargeSoftDynamicA &&
|
|
lifecycle.currentCooldownElapsed &&
|
|
derived.currentReferenceA > params.currentMinA;
|
|
|
|
const solarShouldStepDownCloudDebt =
|
|
!cloudToleranceOk &&
|
|
lifecycle.currentCooldownElapsed &&
|
|
derived.currentReferenceA > params.currentMinA;
|
|
|
|
const gridShouldStepDownBattery =
|
|
parsed.batteryCurrentA < gridBatterySoftDynamicA &&
|
|
lifecycle.currentCooldownElapsed &&
|
|
derived.currentReferenceA > params.currentMinA;
|
|
|
|
const shouldStepDownVebusPower =
|
|
derived.vebusAcOutSoftReached &&
|
|
lifecycle.currentCooldownElapsed &&
|
|
derived.currentReferenceA > params.currentMinA;
|
|
|
|
const shouldStepDownVebusVoltage =
|
|
derived.vebusDcVoltageSoftLow &&
|
|
lifecycle.currentCooldownElapsed &&
|
|
derived.currentReferenceA > params.currentMinA;
|
|
|
|
const shouldStepDownOverload =
|
|
derived.anyVebusOverload &&
|
|
lifecycle.currentCooldownElapsed &&
|
|
derived.currentReferenceA > params.currentMinA;
|
|
|
|
const shouldStopOverloadAtMin =
|
|
derived.anyVebusOverload &&
|
|
derived.currentReferenceA <= params.currentMinA &&
|
|
sessionObservedActive;
|
|
|
|
// =====================================================
|
|
// HARD STOPS / PROTECTIONS
|
|
// =====================================================
|
|
const batteryCurrentHardExceeded =
|
|
parsed.batteryCurrentA <= batteryDischargeHardDynamicA;
|
|
|
|
// overload volontairement exclu du hard stop immédiat
|
|
const mustStopHard =
|
|
derived.anyVebusLowBattery ||
|
|
derived.vebusDcVoltageHardLow ||
|
|
derived.vebusAcOutHardReached ||
|
|
batteryCurrentHardExceeded;
|
|
|
|
const gridBatteryProtectionStop =
|
|
sessionObservedActive &&
|
|
isGridSource &&
|
|
parsed.batteryCurrentA < gridBatteryHardDynamicA;
|
|
|
|
// =====================================================
|
|
// OUTPUT
|
|
// =====================================================
|
|
return [{
|
|
json: {
|
|
...prev,
|
|
|
|
batteryPolicy: {
|
|
socBand,
|
|
batteryDischargeSoftDynamicA,
|
|
batteryDischargeHardDynamicA,
|
|
batteryChargeReserveDynamicA,
|
|
batterySoftToleranceMinutes,
|
|
batteryHardToleranceSeconds,
|
|
batteryRecoveryMinutes,
|
|
|
|
gridBatterySoftDynamicA,
|
|
gridBatteryHardDynamicA,
|
|
gridForceChargeRelaxSoftA,
|
|
gridForceChargeRelaxHardA,
|
|
|
|
houseSpikeDetected,
|
|
solarPotentialContext,
|
|
},
|
|
|
|
locks: {
|
|
nextAllowedWindowStartMs,
|
|
blockActive,
|
|
blockStillRelevant,
|
|
startBlockedByReason,
|
|
},
|
|
|
|
cloud: {
|
|
sessionObservedActive,
|
|
batteryNetPowerW,
|
|
cloudDebtNextKwh,
|
|
cloudDebtMeaningful,
|
|
cloudEventDischargingNow,
|
|
continuingCloudEvent,
|
|
enteringCloudDebt,
|
|
cloudDebtActive,
|
|
cloudDebtStartedMs,
|
|
cloudDebtDurationMinutes,
|
|
effectiveCloudToleranceMinutes,
|
|
cloudToleranceOk,
|
|
},
|
|
|
|
modes: {
|
|
batteryFullHarvestMode,
|
|
forecastCommitMode,
|
|
batteryPriorityMode,
|
|
},
|
|
|
|
safeguards: {
|
|
isGridSource,
|
|
commonSafetyStartOk,
|
|
commonSafetyHoldOk,
|
|
profileSocStartFloor,
|
|
profileSocHoldFloor,
|
|
profileBatteryDischargeFloorA,
|
|
batteryCurrentHardExceeded,
|
|
mustStopHard,
|
|
gridBatteryProtectionStop,
|
|
},
|
|
|
|
conditions: {
|
|
solarStartOk,
|
|
solarHoldOk,
|
|
gridStartOk,
|
|
gridHoldOk,
|
|
probeStartOk,
|
|
probeHoldGuardOk,
|
|
probeWindowActive,
|
|
probeElapsedReached,
|
|
probeSuccessNow,
|
|
probeFailedNow,
|
|
solarShouldStepUp,
|
|
gridShouldStepUp,
|
|
solarShouldStepDownBattery,
|
|
solarShouldStepDownCloudDebt,
|
|
gridShouldStepDownBattery,
|
|
shouldStepDownVebusPower,
|
|
shouldStepDownVebusVoltage,
|
|
shouldStepDownOverload,
|
|
shouldStopOverloadAtMin,
|
|
}
|
|
}
|
|
}];
|
|
|