R2: git = source de vérité — src/nodes/ + build/extract intégrés au push
- src/nodes/*.js (6 nodes + template snapshot) = source de vérité unique. - n8n-push.mjs reconstruit le JSON depuis src/ avant syntaxe+golden+PUT (src gagne toujours; n8n-extract.mjs pour resynchroniser si édition UI). - n8n-build.mjs --check pour vérifier la synchro. - Copies historiques déplacées dans archive/ (mortes, dérivées de la prod). - .gitignore: backups locaux de rollback. - AGENTS.md: règle R2 pour toutes les futures sessions (dont Qwen local). - Validé: idempotence, mutation/restauration, pipeline push complet en réel.
This commit is contained in:
719
archive/decoupesurplus/V2/HASERVICEOUTPUT.js
Normal file
719
archive/decoupesurplus/V2/HASERVICEOUTPUT.js
Normal file
@@ -0,0 +1,719 @@
|
||||
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';
|
||||
}
|
||||
|
||||
|
||||
// =====================================================
|
||||
// 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,
|
||||
|
||||
},
|
||||
|
||||
decision: {
|
||||
state: decision.state,
|
||||
current_decision: decision.currentDecision,
|
||||
desired_charging: decision.desiredCharging,
|
||||
desired_permission: decision.desiredPermission,
|
||||
desired_latch: decision.desiredLatch,
|
||||
desired_current_a: round1(desiredCurrentA),
|
||||
|
||||
charging_action: chargingAction,
|
||||
permission_action: permissionAction,
|
||||
latch_action: latchAction,
|
||||
current_action: currentAction,
|
||||
auto_start_action: autoStartAction,
|
||||
|
||||
set_last_start_at: setLastStartAt,
|
||||
set_last_stop_at: setLastStopAt,
|
||||
set_last_current_change_at: setLastCurrentChangeAt,
|
||||
set_cloud_debt_started_at: decision.setCloudDebtStartedAt,
|
||||
set_probe_started_at: decision.setProbeStartedAt,
|
||||
|
||||
set_block_reason: needsBlockReasonUpdate || !parsed.controllerEnabled,
|
||||
block_reason: decision.nextBlockReason,
|
||||
|
||||
set_blocked_until: true,
|
||||
blocked_until: blockedUntilToWrite,
|
||||
|
||||
dynamic_ok: decision.dynamicOk,
|
||||
must_stop: decision.mustStop,
|
||||
|
||||
short_reason: shortReason,
|
||||
reason,
|
||||
signature,
|
||||
signature_changed: signatureChanged,
|
||||
|
||||
should_notify: notifyWanted,
|
||||
notify_title: notifyTitle,
|
||||
notify_message: notifyMessage,
|
||||
|
||||
evcs_priority_active: evcsPriorityActive,
|
||||
energy_priority_mode: energyPriorityMode,
|
||||
energy_priority_reason: energyPriorityReason,
|
||||
evcs_priority_until: evcsPriorityUntil,
|
||||
evcs_priority_source: evcsPrioritySource,
|
||||
},
|
||||
|
||||
ha_service_calls: {
|
||||
helper_state: {
|
||||
execute: true,
|
||||
domain: 'input_text',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_text.victron_evcs_surplus_state',
|
||||
value: decision.state
|
||||
}
|
||||
},
|
||||
|
||||
helper_current_decision: {
|
||||
execute: true,
|
||||
domain: 'input_text',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_text.victron_evcs_surplus_current_decision',
|
||||
value: decision.currentDecision
|
||||
}
|
||||
},
|
||||
|
||||
helper_reason: {
|
||||
execute: true,
|
||||
domain: 'input_text',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_text.victron_evcs_surplus_reason',
|
||||
value: shortReason
|
||||
}
|
||||
},
|
||||
|
||||
helper_operating_mode: {
|
||||
execute: true,
|
||||
domain: 'input_text',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_text.victron_evcs_operating_mode',
|
||||
value: context.operatingMode
|
||||
}
|
||||
},
|
||||
|
||||
helper_source_mode: {
|
||||
execute: true,
|
||||
domain: 'input_text',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_text.victron_evcs_source_mode',
|
||||
value: sourceModeLabel
|
||||
}
|
||||
},
|
||||
|
||||
helper_target_current: {
|
||||
execute: true,
|
||||
domain: 'input_number',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_number.victron_evcs_target_current_auto',
|
||||
value: round1(desiredCurrentA)
|
||||
}
|
||||
},
|
||||
|
||||
helper_budget_remaining: {
|
||||
execute: true,
|
||||
domain: 'input_number',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_number.victron_evcs_budget_remaining_kwh',
|
||||
value: round2(derived.forecastBufferKwh)
|
||||
}
|
||||
},
|
||||
|
||||
helper_projected_soc: {
|
||||
execute: true,
|
||||
domain: 'input_number',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_number.victron_evcs_projected_end_soc_after_charge',
|
||||
value: round1(derived.projectedEndSocAfterCharge)
|
||||
}
|
||||
},
|
||||
|
||||
helper_cloud_debt: {
|
||||
execute: true,
|
||||
domain: 'input_number',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_number.victron_evcs_cloud_debt_kwh',
|
||||
value: round2(cloud.cloudDebtNextKwh)
|
||||
}
|
||||
},
|
||||
|
||||
helper_dynamic_ok: {
|
||||
execute: true,
|
||||
domain: 'input_boolean',
|
||||
service: decision.dynamicOk ? 'turn_on' : 'turn_off',
|
||||
data: {
|
||||
entity_id: 'input_boolean.victron_evcs_dynamic_ok'
|
||||
}
|
||||
},
|
||||
|
||||
helper_must_stop: {
|
||||
execute: true,
|
||||
domain: 'input_boolean',
|
||||
service: decision.mustStop ? 'turn_on' : 'turn_off',
|
||||
data: {
|
||||
entity_id: 'input_boolean.victron_evcs_must_stop'
|
||||
}
|
||||
},
|
||||
|
||||
helper_cloud_debt_started_at: {
|
||||
execute: true,
|
||||
domain: 'input_datetime',
|
||||
service: 'set_datetime',
|
||||
data: {
|
||||
entity_id: 'input_datetime.victron_evcs_cloud_debt_started_at',
|
||||
datetime: cloudDebtStartedAtToWrite
|
||||
}
|
||||
},
|
||||
|
||||
helper_last_current_change_at: {
|
||||
execute: setLastCurrentChangeAt,
|
||||
domain: 'input_datetime',
|
||||
service: 'set_datetime',
|
||||
data: {
|
||||
entity_id: 'input_datetime.victron_evcs_last_current_change_at',
|
||||
datetime: lastCurrentChangeAtToWrite
|
||||
}
|
||||
},
|
||||
|
||||
helper_last_start_at: {
|
||||
execute: setLastStartAt,
|
||||
domain: 'input_datetime',
|
||||
service: 'set_datetime',
|
||||
data: {
|
||||
entity_id: 'input_datetime.victron_evcs_last_start_at',
|
||||
datetime: lastStartAtToWrite
|
||||
}
|
||||
},
|
||||
|
||||
helper_last_stop_at: {
|
||||
execute: setLastStopAt,
|
||||
domain: 'input_datetime',
|
||||
service: 'set_datetime',
|
||||
data: {
|
||||
entity_id: 'input_datetime.victron_evcs_last_stop_at',
|
||||
datetime: lastStopAtToWrite
|
||||
}
|
||||
},
|
||||
|
||||
helper_signature: {
|
||||
execute: true,
|
||||
domain: 'input_text',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_text.victron_evcs_control_last_signature',
|
||||
value: signature
|
||||
}
|
||||
},
|
||||
|
||||
helper_block_reason: {
|
||||
execute: needsBlockReasonUpdate || !parsed.controllerEnabled,
|
||||
domain: 'input_text',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_text.victron_evcs_last_block_reason',
|
||||
value: decision.nextBlockReason
|
||||
}
|
||||
},
|
||||
|
||||
helper_blocked_until: {
|
||||
execute: true,
|
||||
domain: 'input_datetime',
|
||||
service: 'set_datetime',
|
||||
data: {
|
||||
entity_id: 'input_datetime.victron_evcs_blocked_until',
|
||||
datetime: blockedUntilToWrite
|
||||
}
|
||||
},
|
||||
|
||||
helper_probe_started_at: {
|
||||
execute: decision.setProbeStartedAt,
|
||||
domain: 'input_datetime',
|
||||
service: 'set_datetime',
|
||||
data: {
|
||||
entity_id: 'input_datetime.victron_evcs_probe_started_at',
|
||||
datetime: probeStartedAtToWrite
|
||||
}
|
||||
},
|
||||
|
||||
helper_evcs_priority_active: {
|
||||
execute: true,
|
||||
domain: 'input_boolean',
|
||||
service: evcsPriorityActive ? 'turn_on' : 'turn_off',
|
||||
data: {
|
||||
entity_id: 'input_boolean.victron_evcs_priority_active'
|
||||
}
|
||||
},
|
||||
|
||||
helper_energy_priority_mode: {
|
||||
execute: true,
|
||||
domain: 'input_select',
|
||||
service: 'select_option',
|
||||
data: {
|
||||
entity_id: 'input_select.victron_energy_priority_mode',
|
||||
option: energyPriorityMode
|
||||
}
|
||||
},
|
||||
|
||||
helper_energy_priority_reason: {
|
||||
execute: true,
|
||||
domain: 'input_text',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'input_text.victron_energy_priority_reason',
|
||||
value: energyPriorityReason
|
||||
}
|
||||
},
|
||||
|
||||
helper_evcs_priority_until: {
|
||||
execute: true,
|
||||
domain: 'input_datetime',
|
||||
service: 'set_datetime',
|
||||
data: {
|
||||
entity_id: 'input_datetime.victron_evcs_priority_until',
|
||||
datetime: evcsPriorityUntil
|
||||
}
|
||||
},
|
||||
|
||||
evcs_auto_start_off: {
|
||||
execute: autoStartAction === 'turn_off',
|
||||
domain: 'switch',
|
||||
service: 'turn_off',
|
||||
data: {
|
||||
entity_id: 'switch.victron_evcs_auto_start'
|
||||
}
|
||||
},
|
||||
|
||||
evcs_charging: {
|
||||
execute: chargingAction !== 'none',
|
||||
domain: 'switch',
|
||||
service: decision.desiredCharging ? 'turn_on' : 'turn_off',
|
||||
data: {
|
||||
entity_id: 'switch.victron_evcs_charging'
|
||||
}
|
||||
},
|
||||
|
||||
helper_permission: {
|
||||
execute: permissionAction !== 'none',
|
||||
domain: 'input_boolean',
|
||||
service: decision.desiredPermission ? 'turn_on' : 'turn_off',
|
||||
data: {
|
||||
entity_id: 'input_boolean.victron_evcs_charge_permission_now'
|
||||
}
|
||||
},
|
||||
|
||||
helper_latch: {
|
||||
execute: latchAction !== 'none',
|
||||
domain: 'input_boolean',
|
||||
service: decision.desiredLatch ? 'turn_on' : 'turn_off',
|
||||
data: {
|
||||
entity_id: 'input_boolean.victron_evcs_control_latch'
|
||||
}
|
||||
},
|
||||
|
||||
evcs_set_current: {
|
||||
execute: currentAction === 'set',
|
||||
domain: 'number',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'number.victron_evcs_set_current_command',
|
||||
value: round1(desiredCurrentA)
|
||||
}
|
||||
},
|
||||
|
||||
logbook: {
|
||||
execute: signatureChanged,
|
||||
domain: 'logbook',
|
||||
service: 'log',
|
||||
data: {
|
||||
name: 'Victron EVCS Surplus',
|
||||
message: shortReason
|
||||
}
|
||||
},
|
||||
|
||||
mobile_notification: {
|
||||
execute: notifyWanted,
|
||||
domain: cfg.mobileNotifyDomain,
|
||||
service: cfg.mobileNotifyService,
|
||||
data: {
|
||||
title: notifyTitle,
|
||||
message: notifyMessage || shortReason
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
Reference in New Issue
Block a user