275 lines
6.7 KiB
JavaScript
275 lines
6.7 KiB
JavaScript
// =====================================================
|
|
// PROBES / SOLAR POTENTIAL
|
|
// =====================================================
|
|
// IMPORTANT:
|
|
// On ne met plus effectivePvStartMinW à 0 en force_charge_now,
|
|
// sinon la nuit 0W >= 0W ferait croire à un faux signal solaire.
|
|
const effectivePvStartMinW = params.pvStartMinW;
|
|
|
|
const morningProbeEligible =
|
|
profile === 'morning_solar' &&
|
|
params.probeEnabled &&
|
|
parsed.pvPowerAvg10mW >= params.morningProbePvAvgMinW &&
|
|
parsed.pvTrendW >= params.morningProbePvTrendMinW &&
|
|
parsed.soc >= params.morningSocMin &&
|
|
parsed.batteryCurrentA >= params.morningBatteryMaxDischargeA;
|
|
|
|
const middayProbeEligible =
|
|
profile === 'midday_hc_or_solar' &&
|
|
params.probeEnabled &&
|
|
parsed.pvPowerAvg10mW >= params.middayProbePvAvgMinW &&
|
|
parsed.soc >= params.middaySocMin &&
|
|
parsed.batteryCurrentA >= params.middayBatteryMaxDischargeA;
|
|
|
|
const cloudDebtOkForPotential = params.cloudDebtPrevKwh <= params.cloudDebtMaxKwh;
|
|
const batteryOkForPotential = parsed.batteryCurrentA >= -Math.max(1, params.batteryNeutralBandA);
|
|
|
|
const isHighBatteryForPotential =
|
|
parsed.soc >= 85 &&
|
|
forecastBufferKwh > 0 &&
|
|
cloudDebtOkForPotential &&
|
|
batteryOkForPotential;
|
|
|
|
const potentialAllowed = userStrategy !== 'auto_solar';
|
|
|
|
// IMPORTANT:
|
|
// On impose un vrai PV > 0 pour le test sur pvPowerW.
|
|
// Ça évite qu'en force_charge_now la nuit, 0W soit pris pour un signal solaire.
|
|
const strongSolarSignal =
|
|
(
|
|
parsed.pvPowerW > 0 &&
|
|
parsed.pvPowerW >= effectivePvStartMinW
|
|
) ||
|
|
parsed.pvPowerAvg10mW >= params.pvHoldMinW ||
|
|
(departurePriorityActive && parsed.pvPowerAvg10mW >= params.pvHoldMinW);
|
|
|
|
// =====================================================
|
|
// SOURCE MODE
|
|
// =====================================================
|
|
let sourceMode = 'blocked';
|
|
|
|
if (isStopMode) {
|
|
sourceMode = 'blocked';
|
|
}
|
|
|
|
else if (forceGridRequested) {
|
|
// Demande explicite de réseau, même si le réseau n'est pas encore présent.
|
|
// Cela permet aux étages suivants / sorties HA de le réclamer.
|
|
sourceMode = 'grid_forced';
|
|
}
|
|
|
|
else if (forceChargeRequested) {
|
|
// PATCH EVCS PRIORITAIRE :
|
|
// force_charge_now + voiture branchée + départ défini => grid_forced
|
|
if (forceChargeDepartureGridOverride) {
|
|
sourceMode = 'grid_forced';
|
|
}
|
|
|
|
else if (nightSessionActive) {
|
|
sourceMode = nightPreferredSourceMode;
|
|
}
|
|
|
|
else if (strongSolarSignal) {
|
|
sourceMode = 'solar_only';
|
|
}
|
|
|
|
else if (isHighBatteryForPotential && potentialAllowed) {
|
|
sourceMode = 'solar_potential';
|
|
}
|
|
|
|
else if (profile === 'midday_hc_or_solar' && parsed.gridAvailable) {
|
|
sourceMode = params.allowGridHcMidday ? 'grid_hc_allowed' : 'grid_forced';
|
|
}
|
|
|
|
else if (parsed.gridAvailable) {
|
|
sourceMode = 'grid_forced';
|
|
}
|
|
|
|
else if (profile === 'midday_hc_or_solar' && middayProbeEligible) {
|
|
sourceMode = 'hybrid_probe';
|
|
}
|
|
|
|
else if (profile === 'morning_solar' && morningProbeEligible) {
|
|
sourceMode = 'hybrid_probe';
|
|
}
|
|
|
|
else {
|
|
// Si force_charge_now est demandé mais qu'aucune autre source locale ne permet
|
|
// un démarrage fiable, on garde quand même l'intention EVCS prioritaire.
|
|
sourceMode = 'grid_forced';
|
|
}
|
|
}
|
|
|
|
else if (profile === 'night_hc') {
|
|
if (nightSessionActive) {
|
|
sourceMode = nightPreferredSourceMode;
|
|
} else {
|
|
sourceMode = 'blocked';
|
|
}
|
|
}
|
|
|
|
else if (profile === 'midday_hc_or_solar' || profile === 'morning_solar') {
|
|
if (strongSolarSignal) {
|
|
sourceMode = 'solar_only';
|
|
}
|
|
|
|
else if (isHighBatteryForPotential && potentialAllowed) {
|
|
sourceMode = 'solar_potential';
|
|
}
|
|
|
|
else if (profile === 'midday_hc_or_solar' && middayProbeEligible) {
|
|
sourceMode = 'hybrid_probe';
|
|
}
|
|
|
|
else if (profile === 'morning_solar' && morningProbeEligible) {
|
|
sourceMode = 'hybrid_probe';
|
|
}
|
|
|
|
else if (profile === 'midday_hc_or_solar' && params.allowGridHcMidday && parsed.gridAvailable) {
|
|
sourceMode = 'grid_hc_allowed';
|
|
}
|
|
|
|
else {
|
|
sourceMode = 'blocked';
|
|
}
|
|
}
|
|
|
|
else if (profile === 'evening_high_soc') {
|
|
if (strongSolarSignal) {
|
|
sourceMode = 'solar_only';
|
|
}
|
|
|
|
else if (isHighBatteryForPotential && potentialAllowed) {
|
|
sourceMode = 'solar_potential';
|
|
}
|
|
|
|
else {
|
|
sourceMode = 'blocked';
|
|
}
|
|
}
|
|
|
|
else if (profile === 'outside_window') {
|
|
if (strongSolarSignal) {
|
|
sourceMode = 'solar_only';
|
|
}
|
|
|
|
else if (isHighBatteryForPotential && potentialAllowed) {
|
|
sourceMode = 'solar_potential';
|
|
}
|
|
|
|
else if (forceChargeRequested) {
|
|
sourceMode = 'grid_forced';
|
|
}
|
|
|
|
else if (parsed.gridAvailable) {
|
|
sourceMode = 'grid_forced';
|
|
}
|
|
|
|
else {
|
|
sourceMode = 'blocked';
|
|
}
|
|
}
|
|
|
|
const operatingMode = profile;
|
|
|
|
// =====================================================
|
|
// OUTPUT
|
|
// =====================================================
|
|
return [{
|
|
json: {
|
|
...prev,
|
|
|
|
strategy: {
|
|
userStrategy,
|
|
isStopMode,
|
|
isForceMode,
|
|
forceGridRequested,
|
|
forceChargeRequested,
|
|
},
|
|
|
|
lifecycle: {
|
|
minutesSinceStart,
|
|
minutesSinceStop,
|
|
secondsSinceCurrentChange,
|
|
minutesSinceProbeStart,
|
|
minRunElapsed,
|
|
minStopElapsed,
|
|
currentCooldownElapsed,
|
|
minutesUntilDeparture,
|
|
},
|
|
|
|
windows: {
|
|
inNightHcWindow,
|
|
inMorningWindow,
|
|
inMiddayWindow,
|
|
inEveningWindow,
|
|
},
|
|
|
|
derived: {
|
|
acVoltageRef,
|
|
batteryVoltageRef,
|
|
houseNonEvcsW,
|
|
vebusNonEvcsW,
|
|
totalVebusSoftCeilingW,
|
|
totalVebusHardCeilingW,
|
|
safeEvcsByDedicatedCapW,
|
|
safeEvcsByVebusW,
|
|
safeEvcsCombinedW,
|
|
systemCapCurrentA,
|
|
physicalCapA,
|
|
canRunAtMinCurrent,
|
|
endDayGuardSoc,
|
|
energyNeededForEndDayGuardKwh,
|
|
forecastBufferKwh,
|
|
projectedEndSocAfterCharge,
|
|
currentReferenceA,
|
|
anyVebusOverload,
|
|
anyVebusLowBattery,
|
|
vebusAcOutSoftReached,
|
|
vebusAcOutHardReached,
|
|
vebusDcVoltageSoftLow,
|
|
vebusDcVoltageHardLow,
|
|
houseLoadHighVsHeadroom,
|
|
},
|
|
|
|
context: {
|
|
departurePriorityActive,
|
|
forceChargeDepartureGridOverride,
|
|
morningProbeEligible,
|
|
middayProbeEligible,
|
|
isHighBatteryForPotential,
|
|
strongSolarSignal,
|
|
effectivePvStartMinW,
|
|
profile,
|
|
operatingMode,
|
|
sourceMode,
|
|
},
|
|
|
|
nightEvcsPreview: {
|
|
nightSessionActive,
|
|
nightSessionNeedsCapture,
|
|
nightSessionNeedsClear,
|
|
nightSessionStartSocPct,
|
|
nightSessionStartAt,
|
|
|
|
batteryHardFloorPct,
|
|
batteryMorningTargetPct,
|
|
maxBatteryDischargePoints,
|
|
gridTakeoverTriggerPct,
|
|
|
|
batteryBelowHardFloor,
|
|
batteryBelowMorningTarget,
|
|
batteryAtOrBelowGridTakeoverTrigger,
|
|
|
|
batteryKwhMissingToTarget,
|
|
batteryRecoveryMinutesNeeded,
|
|
batteryRecoveryUrgent,
|
|
|
|
batteryAssistAllowed,
|
|
nightGridTakeoverNeeded,
|
|
nightPreferredSourceMode,
|
|
}
|
|
}
|
|
}];
|
|
|