// ===================================================== // REASONS / LABELS / SIGNATURE // ===================================================== const reason = [...decision.reason]; reason.push(`Stratégie=${strategy.userStrategy}`); reason.push(`Profil=${context.profile}`); reason.push(`Source=${context.sourceMode}`); reason.push( `Mode=${ modes.batteryFullHarvestMode ? 'battery_full_harvest_mode' : modes.forecastCommitMode ? 'forecast_commit_mode' : 'battery_priority_mode' }` ); reason.push(`SOC=${round1(parsed.soc)}%`); reason.push(`Batt=${round1(parsed.batteryCurrentA)}A`); reason.push(`PV=${Math.round(parsed.pvPowerW)}W`); reason.push(`PV10=${Math.round(parsed.pvPowerAvg10mW)}W`); reason.push(`Grid=${parsed.gridAvailable ? 'on' : 'off'} (${Math.round(parsed.gridPowerW)}W)`); reason.push(`Cap=${derived.physicalCapA}A`); reason.push(`Ref=${round1(derived.currentReferenceA)}A`); reason.push(`Target=${round1(desiredCurrentA)}A`); reason.push(`Debt=${round2(cloud.cloudDebtNextKwh)}kWh`); reason.push(`Vehicle=${parsed.evcsVehicleConnected ? 'on' : 'off'}`); reason.push(`EVCS mode=${parsed.evcsModeRaw}`); reason.push(`VE.Bus AC=${Math.round(parsed.vebusAcOutPowerW)}W`); reason.push( `VE.Bus DC=${ Number.isFinite(parsed.vebusDcVoltageV) ? round1(parsed.vebusDcVoltageV) : 'n/a' }V` ); reason.push(`PrioEVCS=${evcsPriorityActive ? 'on' : 'off'}(${evcsPrioritySource})`); const shortReason = reason.join(' | ').slice(0, 255); const sourceModeLabel = context.sourceMode === 'blocked' ? 'Bloqué' : context.sourceMode === 'solar_only' ? 'Solaire' : context.sourceMode === 'solar_potential' ? 'Potentiel solaire' : context.sourceMode === 'hybrid_probe' ? 'Probe hybride' : context.sourceMode === 'grid_hc_allowed' ? 'Réseau HC autorisé' : context.sourceMode === 'grid_forced' ? 'Réseau forcé' : context.sourceMode; const signature = [ decision.state, `profile=${context.profile}`, `source=${context.sourceMode}`, `chg=${decision.desiredCharging ? '1' : '0'}`, `perm=${decision.desiredPermission ? '1' : '0'}`, `l=${decision.desiredLatch ? '1' : '0'}`, `cur=${round1(desiredCurrentA)}`, `block=${decision.nextBlockReason}`, `stop=${decision.mustStop ? '1' : '0'}`, `prio=${evcsPriorityActive ? '1' : '0'}` ].join('|'); const signatureChanged = signature !== history.lastSignature; // ===================================================== // NOTIFICATIONS // ===================================================== let notifyWanted = false; let notifyTitle = 'Victron EVCS Surplus'; let notifyMessage = shortReason; if (parsed.notificationsEnabled && signatureChanged) { if (decision.currentDecision === 'start') { notifyWanted = true; notifyTitle = '⚡ EVCS démarrage'; } else if (decision.currentDecision === 'step_up') { notifyWanted = true; notifyTitle = '⬆️ EVCS hausse courant'; } else if ( decision.currentDecision === 'step_down' || decision.currentDecision === 'step_down_fast' ) { notifyWanted = true; notifyTitle = '⬇️ EVCS baisse courant'; } else if ( decision.mustStop || decision.currentDecision === 'stop' || decision.currentDecision === 'stop_hard' || decision.currentDecision === 'stop_power_cap' ) { notifyWanted = true; notifyTitle = '⛔ EVCS arrêt'; } else if (decision.state === 'mode_auto_conflict') { notifyWanted = true; notifyTitle = '⚠️ EVCS mode Auto'; } } // ===================================================== // TIMESTAMPS TO WRITE // ===================================================== const cloudDebtStartedAtToWrite = cloud.cloudDebtStartedMs ? formatParisStampFromMs(cloud.cloudDebtStartedMs) : now.stamp; const probeStartedAtToWrite = now.stamp; const lastStartAtToWrite = now.stamp; const lastStopAtToWrite = now.stamp; const lastCurrentChangeAtToWrite = now.stamp; // ===================================================== // OUTPUT // ===================================================== return [{ json: { timestamp_paris: now.stamp, inputs: input, computed: { operating_mode: context.operatingMode, profile: context.profile, source_mode: context.sourceMode, source_mode_label: sourceModeLabel, user_strategy: strategy.userStrategy, in_night_hc_window: windows.inNightHcWindow, in_morning_window: windows.inMorningWindow, in_midday_window: windows.inMiddayWindow, in_evening_window: windows.inEveningWindow, departure_priority_active: context.departurePriorityActive, minutes_until_departure: prev.lifecycle.minutesUntilDeparture, battery_priority_mode: modes.batteryPriorityMode, battery_full_harvest_mode: modes.batteryFullHarvestMode, forecast_commit_mode: modes.forecastCommitMode, physical_cap_current_a: derived.physicalCapA, can_run_at_min_current: derived.canRunAtMinCurrent, forecast_buffer_kwh: round2(derived.forecastBufferKwh), projected_end_soc_after_charge: round1(derived.projectedEndSocAfterCharge), cloud_debt_next_kwh: round2(cloud.cloudDebtNextKwh), cloud_debt_duration_minutes: round1(cloud.cloudDebtDurationMinutes), cloud_debt_active: cloud.cloudDebtActive, cloud_tolerance_ok: cloud.cloudToleranceOk, start_blocked_by_reason: locks.startBlockedByReason, probe_window_active: conditions.probeWindowActive, probe_success_now: conditions.probeSuccessNow, probe_failed_now: conditions.probeFailedNow, any_vebus_overload: derived.anyVebusOverload, any_vebus_low_battery: derived.anyVebusLowBattery, vebus_ac_out_soft_reached: derived.vebusAcOutSoftReached, vebus_ac_out_hard_reached: derived.vebusAcOutHardReached, vebus_dc_voltage_soft_low: derived.vebusDcVoltageSoftLow, vebus_dc_voltage_hard_low: derived.vebusDcVoltageHardLow, soc_band: batteryPolicy.socBand, batt_discharge_soft_dynamic_a: round1(batteryPolicy.batteryDischargeSoftDynamicA), batt_discharge_hard_dynamic_a: round1(batteryPolicy.batteryDischargeHardDynamicA), batt_charge_reserve_dynamic_a: round1(batteryPolicy.batteryChargeReserveDynamicA), batt_soft_tolerance_min: round1(batteryPolicy.batterySoftToleranceMinutes), batt_hard_tolerance_sec: round1(batteryPolicy.batteryHardToleranceSeconds), batt_recovery_min: round1(batteryPolicy.batteryRecoveryMinutes), grid_battery_soft_dynamic_a: round1(batteryPolicy.gridBatterySoftDynamicA), grid_battery_hard_dynamic_a: round1(batteryPolicy.gridBatteryHardDynamicA), house_spike_detected: batteryPolicy.houseSpikeDetected, solar_potential_context: batteryPolicy.solarPotentialContext, evcs_priority_active: evcsPriorityActive, energy_priority_mode: energyPriorityMode, energy_priority_reason: energyPriorityReason, evcs_priority_until: evcsPriorityUntil, evcs_priority_source: evcsPrioritySource, evcs_priority_manual_override_active: manualPriorityOverrideActive, },