Files
EVCS/decoupesurplus/V2/HASERVICEOUTPUTP1.js

192 lines
5.5 KiB
JavaScript

function round1(v) {
return Math.round(v * 10) / 10;
}
function round2(v) {
return Math.round(v * 100) / 100;
}
function clamp(v, min, max) {
return Math.min(Math.max(v, min), max);
}
function to2(n) {
return String(n).padStart(2, '0');
}
function formatParisStampFromMs(ms) {
const fmt = new Intl.DateTimeFormat('fr-FR', {
timeZone: 'Europe/Paris',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
});
const parts = Object.fromEntries(
fmt.formatToParts(new Date(ms))
.filter(p => p.type !== 'literal')
.map(p => [p.type, p.value])
);
const hour = Number(parts.hour) === 24 ? 0 : Number(parts.hour);
return `${parts.year}-${parts.month}-${parts.day} ${to2(hour)}:${parts.minute}:${parts.second}`;
}
const prev = $input.first().json;
const {
input,
now,
nowMs,
cfg,
parsed,
params,
history,
strategy,
windows,
context,
derived,
batteryTune,
batteryPolicy,
locks,
cloud,
modes,
safeguards,
conditions,
decision,
} = prev;
// =====================================================
// FINAL ACTIONS
// =====================================================
let desiredCurrentA = decision.desiredCurrentA;
desiredCurrentA = decision.desiredCharging
? clamp(
desiredCurrentA,
params.currentMinA,
Math.max(params.currentMinA, derived.physicalCapA || params.currentMinA)
)
: params.currentMinA;
const chargingAction =
decision.desiredCharging === parsed.evcsChargingSwitchOn
? 'none'
: decision.desiredCharging
? 'turn_on'
: 'turn_off';
const permissionAction =
decision.desiredPermission === parsed.chargePermissionNow
? 'none'
: decision.desiredPermission
? 'turn_on'
: 'turn_off';
const latchAction =
decision.desiredLatch === parsed.controlLatch
? 'none'
: decision.desiredLatch
? 'turn_on'
: 'turn_off';
const autoStartAction =
parsed.controllerEnabled &&
parsed.evcsVehicleConnected &&
parsed.evcsAutoStartSwitchOn
? 'turn_off'
: 'none';
const currentAction =
decision.desiredCharging &&
(
!Number.isFinite(derived.currentReferenceA) ||
Math.abs(desiredCurrentA - derived.currentReferenceA) >= 0.5
)
? 'set'
: 'none';
const setLastStartAt = chargingAction === 'turn_on';
const setLastStopAt = chargingAction === 'turn_off';
const setLastCurrentChangeAt = currentAction === 'set';
// =====================================================
// CLEAN BLOCKING LOGIC
// =====================================================
const blockedUntilToWrite =
decision.nextBlockReason === 'none'
? now.stamp
: decision.nextBlockedUntilStamp;
const needsBlockReasonUpdate =
decision.nextBlockReason !== history.lastBlockReason;
// =====================================================
// GLOBAL EVCS PRIORITY
// AUTO + OVERRIDE MANUEL
// =====================================================
const forceWindowMinutes = Math.max(
30,
batteryTune?.global?.forceModeAutoRevertMinutes ?? 120
);
const activeChargeIntent =
decision.desiredCharging ||
['start', 'hold', 'step_up', 'step_down', 'step_down_fast'].includes(decision.currentDecision);
// -------------------------
// PRIORITÉ AUTO
// -------------------------
let autoEvcsPriorityActive = false;
let autoEnergyPriorityMode = 'normal';
let autoEnergyPriorityReason = 'Aucune priorité EVCS';
let autoEvcsPriorityUntil = now.stamp;
if (parsed.evcsVehicleConnected && !strategy.isStopMode) {
if (strategy.forceChargeRequested || strategy.forceGridRequested) {
autoEvcsPriorityActive = true;
autoEnergyPriorityMode = 'evcs_priority';
autoEnergyPriorityReason =
strategy.forceGridRequested
? 'EVCS prioritaire : force_grid_now'
: 'EVCS prioritaire : force_charge_now';
autoEvcsPriorityUntil = formatParisStampFromMs(nowMs + forceWindowMinutes * 60000);
} else if (context.departurePriorityActive && Number.isFinite(prev.lifecycle.minutesUntilDeparture)) {
autoEvcsPriorityActive = true;
autoEnergyPriorityMode = 'evcs_priority';
autoEnergyPriorityReason = `EVCS prioritaire : départ dans ${Math.round(prev.lifecycle.minutesUntilDeparture)} min`;
autoEvcsPriorityUntil = formatParisStampFromMs(
nowMs + Math.max(1, prev.lifecycle.minutesUntilDeparture) * 60000
);
} else if (activeChargeIntent && context.profile === 'midday_hc_or_solar') {
autoEvcsPriorityActive = true;
autoEnergyPriorityMode = 'evcs_priority';
autoEnergyPriorityReason = 'EVCS prioritaire : recharge active fenêtre midi';
autoEvcsPriorityUntil = formatParisStampFromMs(nowMs + 90 * 60000);
}
}
// -------------------------
// OVERRIDE MANUEL
// -------------------------
const manualPriorityOverrideActive =
parsed.evcsPriorityManualOverride &&
parsed.evcsPriorityManualMode === 'evcs_priority' &&
history.evcsPriorityManualUntilMs !== null &&
history.evcsPriorityManualUntilMs > nowMs + 1000;
let evcsPriorityActive = autoEvcsPriorityActive;
let energyPriorityMode = autoEnergyPriorityMode;
let energyPriorityReason = autoEnergyPriorityReason;
let evcsPriorityUntil = autoEvcsPriorityUntil;
let evcsPrioritySource = 'auto';
if (manualPriorityOverrideActive) {
evcsPriorityActive = true;
energyPriorityMode = 'evcs_priority';
energyPriorityReason =
parsed.evcsPriorityManualReason || 'EVCS prioritaire : override manuel';
evcsPriorityUntil =
history.evcsPriorityManualUntilAt || autoEvcsPriorityUntil;
evcsPrioritySource = 'manual';
}