- DECISION: branche completed prioritaire dans la session active (avant VDF/source-lost) → état collant écrit au 1er cycle status=3, relais encore fermé - WINDOWS: garde puissance <=300W sur Fix D (anti-glitch) - Hystérésis: turn_off_recovery_done (bypass min_on, dry-run 5 cas) - golden 23→25 (session armée status 3, flap statut post-latch) - HANDOFF §18
78 lines
3.1 KiB
JavaScript
78 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
|
// Patch 09/08/2026 — FIX E2 (relais) : coupure EDF immédiate quand l'état
|
|
// surplus est 'night_recovery_done_idle' (collant : le takeover ne peut plus
|
|
// revenir) et qu'aucun override n'est actif. Évite les 25 min de
|
|
// hold_on_min_runtime après la tentative de charge d'un véhicule déjà plein.
|
|
// Cible : workflows/victron-grid-hysteresis-v1.json (node "Compute Grid Hysteresis").
|
|
// Rejouable : détecte si déjà appliqué.
|
|
|
|
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('turn_off_recovery_done')) {
|
|
console.log('Déjà appliqué — rien à faire.');
|
|
process.exit(0);
|
|
}
|
|
|
|
const anchor = ` state = 'turn_off_vehicle_unplugged';
|
|
reason.push('Vehicule debranche + SOC >= seuil OFF : coupure immediate du reseau (bypass min_on)');
|
|
forcedCut = true;
|
|
}
|
|
`;
|
|
|
|
const insertion = ` state = 'turn_off_vehicle_unplugged';
|
|
reason.push('Vehicule debranche + SOC >= seuil OFF : coupure immediate du reseau (bypass min_on)');
|
|
forcedCut = true;
|
|
}
|
|
|
|
// ======================================================
|
|
// FIX E2 (09/08/2026): RECOVERY DONE -> COUPURE IMMEDIATE
|
|
// 'night_recovery_done_idle' est un etat COLLANT du controleur EVCS :
|
|
// charge terminee ET batterie a la cible, le takeover ne reviendra pas
|
|
// (sauf SOC <= cible-2, qui repasserait par l'override). Attendre le
|
|
// min_on 25 min ne protege ici de rien : coupure immediate.
|
|
// ======================================================
|
|
if (
|
|
!forcedCut &&
|
|
latchIsOn &&
|
|
gridIsOn &&
|
|
!evcsGridOverride &&
|
|
evcsSurplusState === 'night_recovery_done_idle' &&
|
|
soc >= gridSocOff
|
|
) {
|
|
desiredGrid = 'off';
|
|
desiredLatch = 'off';
|
|
state = 'turn_off_recovery_done';
|
|
reason.push('Charge terminee + batterie a la cible : coupure immediate du reseau (bypass min_on)');
|
|
forcedCut = true;
|
|
}
|
|
`;
|
|
|
|
if (!code.includes(anchor)) throw new Error('Ancre turn_off_vehicle_unplugged introuvable — appliquer d abord patch-20260808-c-unplug-cut.mjs');
|
|
code = code.replace(anchor, insertion);
|
|
|
|
const notifAnchor = `} else if (state === 'turn_off_recovered') {`;
|
|
const notifInsertion = `} else if (state === 'turn_off_recovery_done') {
|
|
notifyTitle = '✅ EDF coupé (charge terminée)';
|
|
notifyMessage = \`Charge VE terminée et batterie à la cible (SOC \${round1(soc)}%). Coupure immédiate du réseau.\`;
|
|
notifyWanted = true;
|
|
|
|
} else if (state === 'turn_off_recovered') {`;
|
|
|
|
if (!code.includes(notifAnchor)) throw new Error('Ancre notification turn_off_recovered introuvable');
|
|
code = code.replace(notifAnchor, notifInsertion);
|
|
|
|
node.parameters.jsCode = code;
|
|
fs.writeFileSync(FILE, JSON.stringify(wf, null, 2) + '\n');
|
|
console.log('Patch appliqué →', FILE, `(jsCode ${code.length} chars)`);
|