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:
58
HANDOFF.md
58
HANDOFF.md
@@ -960,3 +960,61 @@ activer le toggle sur la carte Conseiller IA (dashboard EVCS Master rechargé).
|
||||
|
||||
---
|
||||
*Document mis à jour le : 09/08/2026 (~07:35) — câblage advisor night_target déployé (toggle OFF par défaut), golden 29/29*
|
||||
|
||||
## 21. Session du 09/08/2026 — P9 : charge réseau avant départ + sélecteur véhicule VW/JEEP
|
||||
|
||||
### Contexte clarifié par l'utilisateur (IMPORTANT pour comprendre les incidents)
|
||||
DEUX véhicules alternent sur la borne :
|
||||
- **VW** (hybride) : départ ~08:50, ne publie PAS franchement "charged" (statut
|
||||
3 rare/fugace) → détection fin de charge = VDF surtout ;
|
||||
- **JEEP** : départ ~06:30, publie "charged" franchement.
|
||||
Relecture des incidents : nuit 08-09/08 = JEEP (rebonds §18) ; matin 09/08 =
|
||||
VW déjà pleine (chargée la veille en solaire) rebranchée à la place de la JEEP.
|
||||
L'heure de départ unique (08:50) était un réglage VW ; la JEEP part à 06:30
|
||||
(dans la fenêtre HC → couverte par la mécanique nuit existante).
|
||||
|
||||
### Nouveaux helpers (package /mnt/ARCH3/docker/HA/packages/victron_evcs_vehicles.yaml)
|
||||
- `input_select.victron_evcs_vehicle` (VW/JEEP, défaut VW) — à sélectionner au
|
||||
branchement. NB : l'ancien `input_select.victron_evcs_target_vehicle`
|
||||
(None/Jeep/Zero, créé UI) n'était câblé nulle part → retiré de la carte,
|
||||
remplacé par le nouveau. À supprimer un jour via l'UI.
|
||||
- `input_datetime.victron_evcs_departure_time_jeep` (06:30). L'existant
|
||||
`victron_evcs_departure_time` (08:50) devient l'heure VW.
|
||||
- `input_boolean.victron_evcs_allow_grid_departure_urgent` (toggle P9, OFF).
|
||||
Carte "Paramètres utilisateur" (evcs_master.yaml) mise à jour.
|
||||
⚠ RELOAD HA requis (input_boolean + input_datetime + input_select).
|
||||
|
||||
### P9 (golden 33/33, déployé 09/08 ~08:27)
|
||||
- INPUT_PARSE : `departureTimeMin` suit le véhicule sélectionné (JEEP →
|
||||
departure_time_jeep, fallback VW) ; impacte aussi departurePriorityActive
|
||||
et batteryRecoveryUrgent nuit (donc la JEEP à 06:30 est mieux servie).
|
||||
- WINDOWS : fenêtre `departureWindowActive` = toggle ON + véhicule branché +
|
||||
HORS fenêtre HC + départ dans ≤ 120 min. Si charge non terminée →
|
||||
`departureUrgentActive` → sourceMode `grid_hc_allowed` (prend le pas sur
|
||||
blocked/solar_potential/hybrid_probe, JAMAIS sur solar_only ni stop/force*).
|
||||
Fin de charge (VDF vehicle_full_or_paused relu, ou statut 3) →
|
||||
`departureChargeDone` → sourceMode blocked + état collant.
|
||||
- BCL : `gridAllowedBySoc` inclut departureUrgentActive (sinon SOC>35 bloquait).
|
||||
- DECISION : branche collante `departure_done_idle` (même principe que Fix F,
|
||||
au-dessus des gardes, mustStop si borne armée).
|
||||
- HA_SERVICE_CALLS : helper takeover tenu ON pendant departureUrgentActive
|
||||
(override hystérésis + maintien batterie ACM pendant la charge pré-départ).
|
||||
- Hystérésis (patch tools/patch-20260809-p9-departure-cut.mjs) : coupure
|
||||
immédiate E2 étendue à `departure_done_idle`.
|
||||
|
||||
### Golden 29 → 33
|
||||
p9-depart-vw-grid (fenêtre active, SOC 45 > 35 → réseau quand même) ·
|
||||
p9-depart-jeep-passe (sélecteur : départ JEEP passé → rien) ·
|
||||
p9-done-idle-collant · p9-toggle-off (comportement historique).
|
||||
|
||||
### Mode d'emploi utilisateur
|
||||
1. Recharger HA (YAML : input_boolean, input_datetime, input_select) ;
|
||||
2. Sur la carte : sélectionner le véhicule au branchement (VW ou JEEP) ;
|
||||
3. Activer « ⚡ Réseau avant départ (P9) » ;
|
||||
4. La JEEP profite de sa vraie heure 06:30 dès cette nuit (recovery urgent
|
||||
nuit) ; la VW aura sa fenêtre 06:50-08:50 hors HC si sous-chargée.
|
||||
Rollback : toggle P9 OFF (30 s) ; sélecteur sans effet si P9 OFF (sauf heure
|
||||
de départ pour la priorité/urgence nuit — voulu).
|
||||
|
||||
---
|
||||
*Document mis à jour le : 09/08/2026 (~08:35) — P9 + sélecteur véhicule déployés, golden 33/33*
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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),
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
@@ -208,6 +208,106 @@ export const scenarios = [
|
||||
},
|
||||
},
|
||||
|
||||
// ===== P9 09/08/2026 : charge réseau avant départ (hors HC) + sélecteur véhicule =====
|
||||
// VW départ 08:50 (helper existant), JEEP départ 06:30 (nouveau helper).
|
||||
// À 08:00 (morning_solar, PV nul) : VW → fenêtre départ active (50 min) ;
|
||||
// JEEP → départ passé depuis 90 min → rien.
|
||||
{
|
||||
id: 'p9-depart-vw-grid',
|
||||
label: 'Matin 08:00, VW sélectionnée, départ 08:50, toggle P9 ON, SOC 45, PV nul → réseau autorisé + EDF demandé',
|
||||
time: '2026-08-10T08:00:00+02:00',
|
||||
overrides: {
|
||||
...VEHICLE_NIGHT, soc_percent: '45',
|
||||
evcs_vehicle: 'VW',
|
||||
departure_time: '08:50:00',
|
||||
departure_time_jeep: '06:30:00',
|
||||
allow_grid_departure_urgent: 'on',
|
||||
},
|
||||
expect: {
|
||||
profile: 'morning_solar',
|
||||
sourceMode: 'grid_hc_allowed',
|
||||
state: 'wait_grid_source',
|
||||
decision: 'hold',
|
||||
desiredCharging: false,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: true,
|
||||
takeoverHelperService: 'turn_on',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'p9-depart-jeep-passe',
|
||||
label: 'Matin 08:00, JEEP sélectionnée (départ 06:30 passé), toggle P9 ON → aucune fenêtre, bloqué',
|
||||
time: '2026-08-10T08:00:00+02:00',
|
||||
overrides: {
|
||||
...VEHICLE_NIGHT, soc_percent: '45',
|
||||
evcs_vehicle: 'JEEP',
|
||||
departure_time: '08:50:00',
|
||||
departure_time_jeep: '06:30:00',
|
||||
allow_grid_departure_urgent: 'on',
|
||||
},
|
||||
expect: {
|
||||
profile: 'morning_solar',
|
||||
sourceMode: 'blocked',
|
||||
state: 'idle_wait_source',
|
||||
decision: 'hold',
|
||||
desiredCharging: false,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: 'turn_off',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'p9-done-idle-collant',
|
||||
label: 'Matin 08:10, VW pleine détectée (prev=vehicle_full_or_paused) pendant la fenêtre départ → veille collante, réseau relâché',
|
||||
time: '2026-08-10T08:10:00+02:00',
|
||||
overrides: {
|
||||
...VEHICLE_NIGHT, soc_percent: '45',
|
||||
evcs_vehicle: 'VW',
|
||||
departure_time: '08:50:00',
|
||||
departure_time_jeep: '06:30:00',
|
||||
allow_grid_departure_urgent: 'on',
|
||||
grid_state: 'on', victron_acin1_available: '1',
|
||||
battery_current_a: '2',
|
||||
prev_surplus_state: 'vehicle_full_or_paused',
|
||||
},
|
||||
expect: {
|
||||
profile: 'morning_solar',
|
||||
sourceMode: 'blocked',
|
||||
state: 'departure_done_idle',
|
||||
decision: 'hold',
|
||||
desiredCharging: false,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: 'turn_off',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'p9-toggle-off',
|
||||
label: 'Matin 08:00, VW départ 08:50 mais toggle P9 OFF → comportement historique (bloqué, pas de réseau)',
|
||||
time: '2026-08-10T08:00:00+02:00',
|
||||
overrides: {
|
||||
...VEHICLE_NIGHT, soc_percent: '45',
|
||||
evcs_vehicle: 'VW',
|
||||
departure_time: '08:50:00',
|
||||
departure_time_jeep: '06:30:00',
|
||||
allow_grid_departure_urgent: 'off',
|
||||
},
|
||||
expect: {
|
||||
profile: 'morning_solar',
|
||||
sourceMode: 'blocked',
|
||||
state: 'idle_wait_source',
|
||||
decision: 'hold',
|
||||
desiredCharging: false,
|
||||
currentA: 6,
|
||||
takeoverNeeded: false,
|
||||
edfOnAction: false,
|
||||
takeoverHelperService: 'turn_off',
|
||||
},
|
||||
},
|
||||
|
||||
// ================= FIN DE CHARGE NUIT (P8) =================
|
||||
{
|
||||
id: 'night-vw-pleine-transition',
|
||||
|
||||
42
tools/patch-20260809-p9-departure-cut.mjs
Normal file
42
tools/patch-20260809-p9-departure-cut.mjs
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env node
|
||||
// Patch 09/08/2026 — P9 : étend la coupure immédiate (bypass min_on) à l'état
|
||||
// collant 'departure_done_idle' (charge terminée pendant la fenêtre départ,
|
||||
// jour). Même sûreté que night_recovery_done_idle : état collant, le takeover
|
||||
// ne revient pas. Prérequis : patch-20260809-e2-recovery-done-cut.mjs.
|
||||
// Rejouable.
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const FILE = path.join(ROOT, 'workflows/victron-grid-hysteresis-v1.json');
|
||||
|
||||
const wf = JSON.parse(fs.readFileSync(FILE, 'utf8'));
|
||||
const node = wf.nodes.find(n => n.name === 'Compute Grid Hysteresis');
|
||||
if (!node) throw new Error('Node "Compute Grid Hysteresis" introuvable');
|
||||
|
||||
let code = node.parameters.jsCode;
|
||||
|
||||
if (code.includes("departure_done_idle")) {
|
||||
console.log('Déjà appliqué — rien à faire.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const anchor = ` evcsSurplusState === 'night_recovery_done_idle' &&
|
||||
soc >= gridSocOff
|
||||
) {`;
|
||||
|
||||
const insertion = ` (
|
||||
evcsSurplusState === 'night_recovery_done_idle' ||
|
||||
evcsSurplusState === 'departure_done_idle'
|
||||
) &&
|
||||
soc >= gridSocOff
|
||||
) {`;
|
||||
|
||||
if (!code.includes(anchor)) throw new Error('Ancre E2 introuvable — appliquer d abord patch-20260809-e2-recovery-done-cut.mjs');
|
||||
code = code.replace(anchor, insertion);
|
||||
|
||||
node.parameters.jsCode = code;
|
||||
fs.writeFileSync(FILE, JSON.stringify(wf, null, 2) + '\n');
|
||||
console.log('Patch appliqué →', FILE, `(jsCode ${code.length} chars)`);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user