P9: charge réseau avant départ (hors HC) + sélecteur véhicule VW/JEEP

- helpers: input_select.victron_evcs_vehicle, departure_time_jeep (06:30),
  toggle allow_grid_departure_urgent (OFF) — package victron_evcs_vehicles.yaml
- INPUT_PARSE: departureTimeMin par véhicule sélectionné
- WINDOWS: fenêtre départ ≤120min hors HC → grid_hc_allowed (solar_only prioritaire),
  état collant departure_done_idle à la fin de charge
- BCL: gridAllowedBySoc += departureUrgentActive
- DECISION: branche collante departure_done_idle au-dessus des gardes
- HA_SERVICE_CALLS: takeover helper tenu pendant P9 (ACM + hystérésis)
- hystérésis: cut immédiat étendu à departure_done_idle (patch rejouable)
- golden 29→33, HANDOFF §21
This commit is contained in:
Tallagrand Thierry
2026-08-09 08:27:24 +02:00
parent 98c9202bad
commit 0419c741de
11 changed files with 312 additions and 9 deletions

View File

@@ -385,6 +385,8 @@ const nightGridTakeoverNeeded = !!nightPrev.nightGridTakeoverNeeded;
const nightTakeoverLatched = !!nightPrev.nightTakeoverLatched;
const vehicleChargeCompleted = !!nightPrev.vehicleChargeCompleted;
const nightRecoveryDone = !!nightPrev.nightRecoveryDone;
const departureUrgentActive = !!nightPrev.departureUrgentActive;
const departureChargeDone = !!nightPrev.departureChargeDone;
const nightPreferredSourceMode = nightPrev.nightPreferredSourceMode || 'blocked';
// =====================================================
@@ -561,9 +563,11 @@ const solarHoldOk =
rawSolarHoldOk || batteryAssistSolarHoldOk;
// FIX V2.5: Garde SOC - pas de grid pour EVCS si SOC > 35% sauf force_grid_now
// P9 (09/08/2026) : la fenêtre départ imminent autorise aussi le réseau.
const gridAllowedBySoc =
strategy.forceGridRequested ||
context.profile === 'night_hc' ||
departureUrgentActive ||
parsed.soc <= 35;
const gridStartOk =
@@ -844,6 +848,8 @@ return [{
nightTakeoverLatched,
vehicleChargeCompleted,
nightRecoveryDone,
departureUrgentActive,
departureChargeDone,
nightPreferredSourceMode,
},

View File

@@ -519,6 +519,28 @@ else if (
});
}
// =====================================================
// P9 (09/08/2026) : véhicule plein pendant la fenêtre "départ imminent"
// (jour, hors HC) → veille COLLANTE 'departure_done_idle', réécrite chaque
// cycle au-dessus des gardes (même principe que Fix F). Le réseau est
// relâché, l'hystérésis coupe immédiatement (cut E2 étendu).
// =====================================================
else if (
nightEvcs?.departureChargeDone &&
!manualLocalChargeAllowed &&
!strategy.forceGridRequested
) {
setOff({
stateName: 'departure_done_idle',
decisionName: cloud.sessionObservedActive ? 'stop_vehicle_full_or_paused' : 'hold',
reasonText: 'Charge terminée avant départ : veille, réseau relâché',
dynamic: false,
mustStopFlag: !!cloud.sessionObservedActive,
blockReason: 'none',
blockedUntilStamp: now.stamp,
});
}
else if (safeguards.mustStopHard) {
setOff({
stateName:

View File

@@ -870,7 +870,12 @@ return [{
helper_night_grid_takeover_needed: {
execute: true,
domain: 'input_boolean',
service: night.nightGridTakeoverNeeded ? 'turn_on' : 'turn_off',
// P9 : la fenêtre "départ imminent" (jour) tient aussi le helper
// takeover → override hystérésis + maintien batterie ACM pendant
// la charge réseau pré-départ.
service: (night.nightGridTakeoverNeeded || night.departureUrgentActive)
? 'turn_on'
: 'turn_off',
data: {
entity_id: 'input_boolean.victron_evcs_night_grid_takeover_needed'
}

View File

@@ -79,6 +79,9 @@
"operating_mode_helper": states("input_text.victron_evcs_operating_mode"),
"departure_time": states("input_datetime.victron_evcs_departure_time"),
"departure_time_jeep": states("input_datetime.victron_evcs_departure_time_jeep"),
"evcs_vehicle": states("input_select.victron_evcs_vehicle"),
"allow_grid_departure_urgent": states("input_boolean.victron_evcs_allow_grid_departure_urgent"),
"last_start_at": states("input_datetime.victron_evcs_last_start_at"),
"last_stop_at": states("input_datetime.victron_evcs_last_stop_at"),
"last_current_change_at": states("input_datetime.victron_evcs_last_current_change_at"),

View File

@@ -341,7 +341,19 @@ const params = {
cloudDebtMaxKwh: Math.max(0.05, num(input.cloud_debt_max_kwh, 1.2)),
cloudToleranceMinutes: Math.max(1, num(input.cloud_tolerance_minutes, 20)),
departureTimeMin: parseTimeToMinutes(input.departure_time, null),
// P9 (09/08/2026) : deux véhicules alternent sur la borne.
// Le sélecteur input_select.victron_evcs_vehicle choisit l'heure de départ
// effective : VW = victron_evcs_departure_time (~08:50),
// JEEP = victron_evcs_departure_time_jeep (~06:30).
selectedVehicle: cleanText(input.evcs_vehicle, 'VW'),
departureTimeMin:
low(input.evcs_vehicle || 'vw') === 'jeep'
? parseTimeToMinutes(
input.departure_time_jeep,
parseTimeToMinutes(input.departure_time, null)
)
: parseTimeToMinutes(input.departure_time, null),
allowGridDepartureUrgent: low(input.allow_grid_departure_urgent || 'off') === 'on',
dayStartTimeMin: parseTimeToMinutes(input.day_start_time, 7 * 60 + 30),
dayStopTimeMin: parseTimeToMinutes(input.day_stop_time, 20 * 60 + 30),

View File

@@ -388,6 +388,38 @@ const nightRecoveryDone =
)
);
// =====================================================
// P9 (09/08/2026) : CHARGE RÉSEAU AVANT DÉPART, HORS FENÊTRE HC.
// Cas VW : départ 08:50 > fin HC 07:24 → fenêtre morte où le véhicule
// sous-chargé attendait un solaire insuffisant (observé le 09/08 : advisor
// force_grid_now, divergence n°10). Si le toggle est ON et que le départ du
// véhicule SÉLECTIONNÉ est dans ≤ 120 min : réseau autorisé (grid_hc_allowed)
// jusqu'à fin de charge. L'arrêt réutilise TOUTE la mécanique anti-rebond :
// VDF → vehicle_full_or_paused → état collant 'departure_done_idle'
// (réécrit chaque cycle, patterns hystérésis sûrs, coupure immédiate E2).
// =====================================================
const P9_DEPARTURE_LEAD_MIN = 120;
const departureWindowActive =
params.allowGridDepartureUrgent &&
parsed.evcsVehicleConnected &&
!inNightHcWindow &&
Number.isFinite(minutesUntilDeparture) &&
minutesUntilDeparture >= 0 &&
minutesUntilDeparture <= P9_DEPARTURE_LEAD_MIN;
const departureDonePrev =
prevSurplusState === 'departure_done_idle' ||
(!inNightHcWindow && prevSurplusState === 'vehicle_full_or_paused');
const departureChargeDone =
departureWindowActive &&
(departureDonePrev || evcsStatusCharged);
const departureUrgentActive =
departureWindowActive &&
!departureChargeDone;
// P8 : correction du fix 04/08 (logique inversée : "|| vehicleChargeCompleted"
// GARDAIT le latch après la charge au lieu de le libérer).
const nightTakeoverLatched =
@@ -642,6 +674,25 @@ else if (profile === 'outside_window') {
}
}
// P9 : le réseau avant départ prend le pas sur les modes spéculatifs du jour
// (blocked / solar_potential / hybrid_probe), mais JAMAIS sur le solaire réel
// (solar_only), ni sur stop / force_* (déjà traités en tête de cascade).
if (
departureUrgentActive &&
!isStopMode &&
!forceGridRequested &&
!forceChargeRequested &&
profile !== 'night_hc' &&
sourceMode !== 'solar_only'
) {
sourceMode = 'grid_hc_allowed';
}
// P9 : véhicule plein pendant la fenêtre départ → aucune source.
if (departureChargeDone && profile !== 'night_hc' && !isStopMode && !forceGridRequested && !forceChargeRequested) {
sourceMode = 'blocked';
}
const operatingMode = profile;
// =====================================================
@@ -755,6 +806,10 @@ return [{
vehicleChargeCompleted,
nightRecoveryDone,
nightPreferredSourceMode,
// P9
departureUrgentActive,
departureChargeDone,
}
}
}];