Compare commits
2 Commits
3beefff8de
...
9592ac94ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9592ac94ea | ||
|
|
1debfe3c0e |
56
HANDOFF.md
56
HANDOFF.md
@@ -420,3 +420,59 @@ const nightTakeoverLatched =
|
||||
---
|
||||
*Document mis à jour le : 04/08/2026 (~10:00) — Fix gridAllowedBySoc + nightTakeoverLatched via REST API*
|
||||
*État live à la clôture : Workflow patché, à tester en conditions réelles*
|
||||
|
||||
## 11. Session du 07/08/2026 — P7 : Charge HC nuit automatique (VW hybride) + outillage push
|
||||
|
||||
### Problème (nuits du 05-07/08, observé sur l'historique HA)
|
||||
- VW hybride branchée le soir + stratégie `auto_solar_plus` → **aucune charge en HC**.
|
||||
01:24 : `idle_wait_conditions` / « Conditions de démarrage nuit batterie non réunies »
|
||||
(SOC 53 %, target=trigger=35 %). L'utilisateur devait intervenir manuellement
|
||||
(force_charge_now, ou BOOST EDF 28A à 05:50 le 07/08).
|
||||
|
||||
### Root causes
|
||||
1. **`battery_assist_night` ne démarre jamais** : `batteryAssistNightStartOk` exige
|
||||
`batteryCurrentA >= profileBatteryDischargeFloorA` (= `battery_discharge_soft_a` = 0 A).
|
||||
La nuit, la maison tire sur la batterie (~ −14 A) → condition impossible.
|
||||
→ le système attendait que le SOC dérive jusqu'au trigger 35 % (des heures) avant le grid.
|
||||
(= backlog §7.5 « conditions de démarrage nuit batterie non réunies », résolu.)
|
||||
2. **`allow_grid_hc_night` (toggle « Réseau HC nuit ») était décoratif** : parsé dans
|
||||
INPUT_PARSE (`params.allowGridHcNight`) mais utilisé nulle part
|
||||
(contrairement à `allowGridHcMidday`).
|
||||
|
||||
### Fix P7 (déployé 07/08 ~15:45, dry-run 7 scénarios validés)
|
||||
Dans **WINDOWS_SOURCEV2** et **BATTERY_CLOUD_LOCKS** (logique nuit dupliquée) :
|
||||
```javascript
|
||||
const nightGridHcPreferred =
|
||||
nightSessionActive && // fenêtre HC nuit + véhicule branché
|
||||
params.allowGridHcNight; // toggle "Réseau HC nuit" ON
|
||||
```
|
||||
- `batteryAssistAllowed` : ajout `&& !nightGridHcPreferred`
|
||||
- `nightGridTakeoverNeeded` : ajout `nightGridHcPreferred ||` dans le OR
|
||||
|
||||
**Effet** : dès l'ouverture de la fenêtre HC (01:24) avec véhicule branché,
|
||||
`nightPreferredSourceMode` = `grid_hc_allowed` (ou `grid_forced` si SOC < cible matin)
|
||||
→ `edf_general_ok_on` allume EDF → takeover helper ON → ACM `evcs_takeover_hold`
|
||||
(batterie tenue 2 A) → `gridStartOk` → `start_grid_hc_min_current` puis step-up.
|
||||
- Toggle « Réseau HC nuit » OFF → comportement historique (battery assist) intact.
|
||||
- Jour / pas de véhicule / force_charge_now : aucun changement (dry-run vérifié).
|
||||
- Garde-fous intacts : hard floor 22 %, recovery urgent, latch anti-créneaux P2, P4/P5.
|
||||
|
||||
### Outillage (résout le blocage minimax « impossible de pousser dans n8n »)
|
||||
- **`tools/n8n-pull.mjs <id> <fichier>`** : GET workflow → JSON local (clé API lue
|
||||
automatiquement dans `~/.config/opencode/opencode.json`).
|
||||
- **`tools/n8n-push.mjs <fichier> [id]`** : PUT REST (body minimal validé 04/08) avec
|
||||
**backup serveur auto pré-push** (`modified/prepush-*.json`) et **`node --check`
|
||||
sur chaque jsCode** (abandon si erreur de syntaxe).
|
||||
- **`tools/patch-20260807-night-hc-grid.mjs`** : le patch P7 (remplacements exacts, rejoue-able).
|
||||
- **`tools/dryrun-20260807-night-hc.mjs`** : harnais dry-run WINDOWS→BCL avec snapshot simulé.
|
||||
- Cycle git : commit backup AVANT (`1debfe3`) → patch → push → commit APRÈS.
|
||||
Rollback : `git checkout 1debfe3 -- workflows/evcs-surplus-controller-v2.6.json && node tools/n8n-push.mjs workflows/evcs-surplus-controller-v2.6.json`.
|
||||
|
||||
### À surveiller (prochaine nuit HC 01:24→07:24, VW branchée)
|
||||
1. 01:24-01:25 : `source_mode` → `Réseau HC autorisé`, EDF ON, takeover helper ON ;
|
||||
2. `start_grid_hc_min_current` → montée en courant (plafond EDF ON = helper 20 A / boost 28 A) ;
|
||||
3. ACM en `evcs_takeover_hold` (batterie ~2 A, SOC stable) ;
|
||||
4. 07:24 : fin de fenêtre → EDF OFF, retour profil jour.
|
||||
|
||||
---
|
||||
*Document mis à jour le : 07/08/2026 (~15:50) — P7 charge HC nuit auto + tools n8n-pull/n8n-push*
|
||||
|
||||
178
tools/dryrun-20260807-night-hc.mjs
Normal file
178
tools/dryrun-20260807-night-hc.mjs
Normal file
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env node
|
||||
// Dry-run du patch nightGridHcPreferred : exécute le jsCode réel de WINDOWS_SOURCEV2
|
||||
// avec un snapshot simulé (nuit HC, véhicule branché) et affiche la décision nuit.
|
||||
|
||||
import fs from 'fs';
|
||||
import vm from 'vm';
|
||||
|
||||
const wf = JSON.parse(fs.readFileSync('workflows/evcs-surplus-controller-v2.6.json', 'utf8'));
|
||||
const windowsCode = wf.nodes.find(n => n.name === 'WINDOWS_SOURCEV2').parameters.jsCode;
|
||||
|
||||
function buildPrev({ soc, allowGridHcNight, vehicleConnected, minNow, hour, batteryCurrentA, takeoverPrev, gridAvailable }) {
|
||||
return {
|
||||
input: { night_takeover_prev: takeoverPrev ? 'on' : 'off' },
|
||||
nowMs: Date.now(),
|
||||
now: { minNow, hour, minute: minNow % 60, stamp: '2026-08-07 01:30:00', date: '2026-08-07' },
|
||||
cfg: {
|
||||
batteryUsableCapacityKwh: 17.5,
|
||||
nominalAcVoltageV: 230,
|
||||
systemReserveW: 300,
|
||||
evcsDedicatedMaxW: 4900,
|
||||
vebusDcVoltageHardFloorV: 47,
|
||||
vebusDcVoltageSoftFloorV: 48,
|
||||
},
|
||||
parsed: {
|
||||
soc,
|
||||
batteryCurrentA,
|
||||
pvPowerW: 0,
|
||||
pvPowerAvg10mW: 0,
|
||||
pvTrendW: 0,
|
||||
houseLoadW: 700,
|
||||
houseAcPowerW: 700,
|
||||
houseAcPowerPrevW: 700,
|
||||
evcsAcPowerW: 0,
|
||||
vebusAcOutPowerW: 700,
|
||||
vebusAcOutVoltageV: 230,
|
||||
acInAvailable: false,
|
||||
evcsPowerW: 0,
|
||||
evcsVehicleConnected: vehicleConnected,
|
||||
evcsDeviceConnected: true,
|
||||
evcsChargingSwitchOn: false,
|
||||
evcsModeIsAuto: false,
|
||||
controlLatch: false,
|
||||
gridAvailable: gridAvailable ?? false,
|
||||
acVoltageV: 230,
|
||||
batteryVoltageV: 51,
|
||||
vebusAcOutW: 700,
|
||||
vebusDcVoltageV: 51,
|
||||
todayRemainingKwh: 0,
|
||||
tomorrowKwh: 25,
|
||||
lastStartAt: '',
|
||||
lastStopAt: '',
|
||||
lastCurrentChangeAt: '',
|
||||
probeStartedAt: '',
|
||||
departureTime: '08:50:00',
|
||||
departurePriority: true,
|
||||
requestedEnergyKwh: 10.5,
|
||||
currentSetA: 6,
|
||||
},
|
||||
params: {
|
||||
userStrategyRaw: 'auto_solar_plus',
|
||||
allowGridHcNight,
|
||||
allowGridHcMidday: true,
|
||||
hcNightStartMin: 1 * 60 + 24,
|
||||
hcNightStopMin: 7 * 60 + 24,
|
||||
morningStartMin: 7 * 60 + 30,
|
||||
morningStopMin: 11 * 60,
|
||||
hcMiddayStartMin: 12 * 60 + 24,
|
||||
hcMiddayStopMin: 14 * 60 + 24,
|
||||
eveningStartMin: 17 * 60,
|
||||
eveningStopMin: 20 * 60 + 30,
|
||||
currentMinA: 6,
|
||||
currentMaxA: 22,
|
||||
currentMaxAutoA: 22,
|
||||
gridCurrentMaxA: 20,
|
||||
acLoadsMaxSafeW: 8500,
|
||||
houseAcHeadroomW: 3500,
|
||||
endOfDaySocGuard: 30,
|
||||
endOfDaySocTarget: 45,
|
||||
pvStartMinW: 1000,
|
||||
pvHoldMinW: 700,
|
||||
probeEnabled: true,
|
||||
socStartMin: 80,
|
||||
socStopMin: 25,
|
||||
morningSocMin: 35,
|
||||
middaySocMin: 75,
|
||||
eveningSocStartMin: 85,
|
||||
eveningSocStopMin: 60,
|
||||
batteryDischargeSoftA: 0,
|
||||
batteryDischargeHardA: -15,
|
||||
morningBatteryMaxDischargeA: -5,
|
||||
middayBatteryMaxDischargeA: -8,
|
||||
eveningBatteryMaxDischargeA: -10,
|
||||
gridBatterySoftA: -3,
|
||||
gridBatteryHardA: -6,
|
||||
cloudDebtMaxKwh: 1.2,
|
||||
evcsNightBatteryHardFloorPct: 22,
|
||||
evcsNightStrongForecastKwh: 18,
|
||||
evcsNightMediumForecastKwh: 10,
|
||||
evcsNightTargetStrongForecastPct: 35,
|
||||
evcsNightTargetMediumForecastPct: 55,
|
||||
evcsNightTargetWeakForecastPct: 85,
|
||||
evcsNightBatteryRecoveryPowerKw: 1.5,
|
||||
evcsNightDepartureSafetyMin: 45,
|
||||
morningProbePvAvgMinW: 200,
|
||||
morningProbePvTrendMinW: 20,
|
||||
middayProbePvAvgMinW: 300,
|
||||
afternoonProbePvAvgMinW: 300,
|
||||
probeCurrentA: 10,
|
||||
},
|
||||
history: {
|
||||
nightSessionStartSocPct: soc,
|
||||
nightSessionStartAt: '2026-08-07 01:24:00',
|
||||
nightSessionStartMs: Date.now() - 6 * 60000,
|
||||
lastBlockReason: 'none',
|
||||
blockedUntilAt: '',
|
||||
},
|
||||
batteryTune: {
|
||||
global: { stepUpCooldownSeconds: 90, stepDownCooldownSeconds: 45 },
|
||||
profile: {},
|
||||
bands: {
|
||||
gt90: { dischargeSoftA: -50, dischargeHardA: -100, chargeReserveA: 5, softToleranceMin: 12, hardToleranceSec: 20, recoveryMin: 6 },
|
||||
'80_90': { dischargeSoftA: -40, dischargeHardA: -80, chargeReserveA: 10, softToleranceMin: 10, hardToleranceSec: 20, recoveryMin: 6 },
|
||||
'60_80': { dischargeSoftA: -20, dischargeHardA: -50, chargeReserveA: 40, softToleranceMin: 6, hardToleranceSec: 15, recoveryMin: 8 },
|
||||
'40_60': { dischargeSoftA: -12, dischargeHardA: -30, chargeReserveA: 70, softToleranceMin: 4, hardToleranceSec: 10, recoveryMin: 10 },
|
||||
'25_40': { dischargeSoftA: -5, dischargeHardA: -15, chargeReserveA: 110, softToleranceMin: 2, hardToleranceSec: 5, recoveryMin: 12 },
|
||||
lt25: { dischargeSoftA: -3, dischargeHardA: -8, chargeReserveA: 150, softToleranceMin: 1, hardToleranceSec: 2, recoveryMin: 15 },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function run(code, prev) {
|
||||
const ctx = {
|
||||
$input: { first: () => ({ json: prev }) },
|
||||
console,
|
||||
Date,
|
||||
Intl,
|
||||
Math,
|
||||
Number,
|
||||
Object,
|
||||
String,
|
||||
JSON,
|
||||
};
|
||||
vm.createContext(ctx);
|
||||
const result = vm.runInContext(`(function(){\n${code}\n})()`, ctx);
|
||||
return result[0].json;
|
||||
}
|
||||
|
||||
const scenarios = [
|
||||
{ label: 'Nuit HC 01:30, SOC 53, VW branchée, toggle HC nuit ON (nuit dernière)', soc: 53, allowGridHcNight: true, vehicleConnected: true, minNow: 90, hour: 1, batteryCurrentA: -14, takeoverPrev: false },
|
||||
{ label: 'Nuit HC 01:30, SOC 53, VW branchée, toggle HC nuit OFF (comportement historique)', soc: 53, allowGridHcNight: false, vehicleConnected: true, minNow: 90, hour: 1, batteryCurrentA: -14, takeoverPrev: false },
|
||||
{ label: 'Nuit HC 01:30, SOC 30 (< cible 35), branchée, toggle ON', soc: 30, allowGridHcNight: true, vehicleConnected: true, minNow: 90, hour: 1, batteryCurrentA: -14, takeoverPrev: false },
|
||||
{ label: 'Nuit HC 01:30, pas de véhicule, toggle ON', soc: 53, allowGridHcNight: true, vehicleConnected: false, minNow: 90, hour: 1, batteryCurrentA: -14, takeoverPrev: false },
|
||||
{ label: 'Jour 15:00, SOC 53, branchée, toggle ON (hors fenêtre nuit)', soc: 53, allowGridHcNight: true, vehicleConnected: true, minNow: 900, hour: 15, batteryCurrentA: 10, takeoverPrev: false },
|
||||
{ label: 'Nuit HC 03:00, takeover déjà engagé (latch), SOC 40, toggle ON', soc: 40, allowGridHcNight: true, vehicleConnected: true, minNow: 180, hour: 3, batteryCurrentA: 2, takeoverPrev: true },
|
||||
{ label: 'Nuit HC 02:00, EDF ON (cycle suivant), SOC 53, batterie tenue +2A, toggle ON', soc: 53, allowGridHcNight: true, vehicleConnected: true, minNow: 120, hour: 2, batteryCurrentA: 2, takeoverPrev: true, gridAvailable: true },
|
||||
];
|
||||
|
||||
for (const sc of scenarios) {
|
||||
const out = run(windowsCode, buildPrev(sc));
|
||||
const n = out.nightEvcsPreview;
|
||||
console.log(`\n=== ${sc.label}`);
|
||||
console.log(` profile=${out.context.profile} | sourceMode=${out.context.sourceMode}`);
|
||||
console.log(` nightSessionActive=${n.nightSessionActive} | batteryAssistAllowed=${n.batteryAssistAllowed}`);
|
||||
console.log(` nightGridTakeoverNeeded=${n.nightGridTakeoverNeeded} | nightPreferredSourceMode=${n.nightPreferredSourceMode}`);
|
||||
console.log(` target=${n.batteryMorningTargetPct}% trigger=${n.gridTakeoverTriggerPct}%`);
|
||||
|
||||
// Chaînage BATTERY_CLOUD_LOCKS sur la sortie de WINDOWS_SOURCEV2
|
||||
const bCode = wf.nodes.find(x => x.name === 'BATTERY_CLOUD_LOCKS').parameters.jsCode;
|
||||
try {
|
||||
const bOut = run(bCode, out);
|
||||
const c = bOut.conditions || {};
|
||||
const ne = bOut.nightEvcs || {};
|
||||
console.log(` [BCL] nightPreferred=${ne.nightPreferredSourceMode} takeoverNeeded=${ne.nightGridTakeoverNeeded} | gridStartOk=${c.gridStartOk} (gridAvailable=${sc.gridAvailable ?? false}) | assistNightStartOk=${c.batteryAssistNightStartOk}`);
|
||||
} catch (e) {
|
||||
console.log(` [BCL] échec exécution: ${e.message}`);
|
||||
}
|
||||
}
|
||||
47
tools/n8n-pull.mjs
Normal file
47
tools/n8n-pull.mjs
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env node
|
||||
// Usage: node tools/n8n-pull.mjs [workflowId] [outputFile]
|
||||
// GET un workflow n8n et l'écrit en JSON (pretty) dans un fichier local.
|
||||
// Clé API lue depuis ~/.config/opencode/opencode.json (mcp.n8n-mcp.environment).
|
||||
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
const WORKFLOW_ID = process.argv[2] || 'ATswvGfG4KJUls0K';
|
||||
const OUT = process.argv[3] || `workflows/pull-${WORKFLOW_ID}.json`;
|
||||
|
||||
function loadApiConfig() {
|
||||
if (process.env.N8N_API_KEY) {
|
||||
return {
|
||||
url: process.env.N8N_URL || 'https://n8n.airtria.pro',
|
||||
key: process.env.N8N_API_KEY
|
||||
};
|
||||
}
|
||||
const cfgPath = path.join(os.homedir(), '.config/opencode/opencode.json');
|
||||
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
||||
const env = cfg?.mcp?.['n8n-mcp']?.environment || {};
|
||||
if (!env.N8N_API_KEY) {
|
||||
console.error('Clé API n8n introuvable (env N8N_API_KEY ou opencode.json)');
|
||||
process.exit(1);
|
||||
}
|
||||
return {
|
||||
url: (env.N8N_API_URL || 'https://n8n.airtria.pro/api/v1').replace(/\/api\/v1\/?$/, ''),
|
||||
key: env.N8N_API_KEY
|
||||
};
|
||||
}
|
||||
|
||||
const { url, key } = loadApiConfig();
|
||||
|
||||
const res = await fetch(`${url}/api/v1/workflows/${WORKFLOW_ID}`, {
|
||||
headers: { 'X-N8N-API-KEY': key }
|
||||
});
|
||||
if (!res.ok) {
|
||||
console.error(`GET échoué: ${res.status} ${res.statusText}`);
|
||||
console.error(await res.text());
|
||||
process.exit(1);
|
||||
}
|
||||
const wf = await res.json();
|
||||
fs.mkdirSync(path.dirname(OUT), { recursive: true });
|
||||
fs.writeFileSync(OUT, JSON.stringify(wf, null, 2) + '\n');
|
||||
console.log(`OK: "${wf.name}" (${wf.nodes?.length} nodes, active=${wf.active}) -> ${OUT}`);
|
||||
console.log(`updatedAt: ${wf.updatedAt}`);
|
||||
102
tools/n8n-push.mjs
Normal file
102
tools/n8n-push.mjs
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env node
|
||||
// Usage: node tools/n8n-push.mjs <inputFile> [workflowId]
|
||||
// PUT un workflow n8n depuis un fichier JSON local (méthode validée 04/08/2026 :
|
||||
// body minimal {name, nodes, connections, settings, staticData}, settings épuré).
|
||||
// Fait automatiquement un backup GET avant le PUT dans modified/.
|
||||
// Clé API lue depuis ~/.config/opencode/opencode.json (mcp.n8n-mcp.environment).
|
||||
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
const INPUT = process.argv[2];
|
||||
if (!INPUT) {
|
||||
console.error('Usage: node tools/n8n-push.mjs <inputFile> [workflowId]');
|
||||
process.exit(1);
|
||||
}
|
||||
const local = JSON.parse(fs.readFileSync(INPUT, 'utf8'));
|
||||
const WORKFLOW_ID = process.argv[3] || local.id || 'ATswvGfG4KJUls0K';
|
||||
|
||||
function loadApiConfig() {
|
||||
if (process.env.N8N_API_KEY) {
|
||||
return {
|
||||
url: process.env.N8N_URL || 'https://n8n.airtria.pro',
|
||||
key: process.env.N8N_API_KEY
|
||||
};
|
||||
}
|
||||
const cfgPath = path.join(os.homedir(), '.config/opencode/opencode.json');
|
||||
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
||||
const env = cfg?.mcp?.['n8n-mcp']?.environment || {};
|
||||
if (!env.N8N_API_KEY) {
|
||||
console.error('Clé API n8n introuvable (env N8N_API_KEY ou opencode.json)');
|
||||
process.exit(1);
|
||||
}
|
||||
return {
|
||||
url: (env.N8N_API_URL || 'https://n8n.airtria.pro/api/v1').replace(/\/api\/v1\/?$/, ''),
|
||||
key: env.N8N_API_KEY
|
||||
};
|
||||
}
|
||||
|
||||
const { url, key } = loadApiConfig();
|
||||
const headers = { 'X-N8N-API-KEY': key, 'Content-Type': 'application/json' };
|
||||
|
||||
// 1. Backup automatique de l'état serveur AVANT push
|
||||
const getRes = await fetch(`${url}/api/v1/workflows/${WORKFLOW_ID}`, { headers });
|
||||
if (!getRes.ok) {
|
||||
console.error(`GET backup échoué: ${getRes.status} ${getRes.statusText}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const remote = await getRes.json();
|
||||
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const backupPath = `modified/prepush-${WORKFLOW_ID}-${stamp}.json`;
|
||||
fs.mkdirSync('modified', { recursive: true });
|
||||
fs.writeFileSync(backupPath, JSON.stringify(remote, null, 2) + '\n');
|
||||
console.log(`Backup serveur avant push: ${backupPath}`);
|
||||
|
||||
// 2. Body minimal (n8n API v1 rejette les propriétés additionnelles — erreur 1003)
|
||||
const body = {
|
||||
name: local.name,
|
||||
nodes: local.nodes,
|
||||
connections: local.connections,
|
||||
settings: {
|
||||
executionOrder: local.settings?.executionOrder || 'v1',
|
||||
callerPolicy: local.settings?.callerPolicy || 'workflowsFromSameOwner'
|
||||
},
|
||||
staticData: local.staticData ?? null
|
||||
};
|
||||
|
||||
// 3. Sanity check : node --check sur chaque jsCode
|
||||
let syntaxErrors = 0;
|
||||
for (const node of body.nodes || []) {
|
||||
const js = node.parameters?.jsCode;
|
||||
if (!js) continue;
|
||||
const tmp = path.join(os.tmpdir(), `n8n-check-${node.id || node.name}.js`.replace(/[^\w.-]/g, '_'));
|
||||
fs.writeFileSync(tmp, js);
|
||||
const { spawnSync } = await import('child_process');
|
||||
const r = spawnSync('node', ['--check', tmp]);
|
||||
if (r.status !== 0) {
|
||||
syntaxErrors++;
|
||||
console.error(`Syntaxe KO dans node "${node.name}":\n${r.stderr.toString().slice(0, 500)}`);
|
||||
}
|
||||
fs.unlinkSync(tmp);
|
||||
}
|
||||
if (syntaxErrors) {
|
||||
console.error(`ABANDON: ${syntaxErrors} node(s) avec erreur de syntaxe. Rien n'a été poussé.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Syntaxe OK sur tous les nodes jsCode.');
|
||||
|
||||
// 4. PUT
|
||||
const putRes = await fetch(`${url}/api/v1/workflows/${WORKFLOW_ID}`, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const text = await putRes.text();
|
||||
if (!putRes.ok) {
|
||||
console.error(`PUT REFUSÉ: ${putRes.status} ${putRes.statusText}`);
|
||||
console.error(text.slice(0, 1000));
|
||||
process.exit(1);
|
||||
}
|
||||
const updated = JSON.parse(text);
|
||||
console.log(`PUT OK: "${updated.name}" (updatedAt: ${updated.updatedAt}, active=${updated.active})`);
|
||||
114
tools/patch-20260807-night-hc-grid.mjs
Normal file
114
tools/patch-20260807-night-hc-grid.mjs
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env node
|
||||
// Patch 07/08/2026 : charge HC nuit automatique quand un véhicule est branché
|
||||
// et que le toggle "Réseau HC nuit" (input_boolean.victron_evcs_allow_grid_hc_night) est ON.
|
||||
// Modifie WINDOWS_SOURCEV2 et BATTERY_CLOUD_LOCKS dans le JSON local du workflow.
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
const FILE = 'workflows/evcs-surplus-controller-v2.6.json';
|
||||
const wf = JSON.parse(fs.readFileSync(FILE, 'utf8'));
|
||||
|
||||
function patchNode(nodeName, replacements) {
|
||||
const node = wf.nodes.find(n => n.name === nodeName);
|
||||
if (!node) throw new Error(`Node introuvable: ${nodeName}`);
|
||||
let code = node.parameters.jsCode;
|
||||
for (const [i, { from, to }] of replacements.entries()) {
|
||||
if (!code.includes(from)) {
|
||||
throw new Error(`${nodeName}: pattern #${i + 1} introuvable:\n---\n${from}\n---`);
|
||||
}
|
||||
if (code.split(from).length !== 2) {
|
||||
throw new Error(`${nodeName}: pattern #${i + 1} non unique`);
|
||||
}
|
||||
code = code.replace(from, to);
|
||||
}
|
||||
node.parameters.jsCode = code;
|
||||
console.log(`OK: ${nodeName} patché (${replacements.length} remplacement(s))`);
|
||||
}
|
||||
|
||||
const HEADER = `// FIX 07/08/2026 : fenêtre HC nuit + véhicule branché + toggle "Réseau HC nuit" ON
|
||||
// => préférence réseau HC directe (la VW hybride charge sans intervention manuelle).
|
||||
// Toggle OFF => comportement historique (battery_assist_night puis takeover à la cible).
|
||||
const nightGridHcPreferred =
|
||||
nightSessionActive &&
|
||||
params.allowGridHcNight;
|
||||
|
||||
`;
|
||||
|
||||
// ---------- WINDOWS_SOURCEV2 ----------
|
||||
patchNode('WINDOWS_SOURCEV2', [
|
||||
{
|
||||
from: `const batteryAssistAllowed =
|
||||
nightSessionActive &&
|
||||
parsed.soc > batteryHardFloorPct &&
|
||||
parsed.soc > gridTakeoverTriggerPct &&
|
||||
!nightTakeoverLatched &&
|
||||
!batteryRecoveryUrgent;`,
|
||||
to: HEADER + `const batteryAssistAllowed =
|
||||
nightSessionActive &&
|
||||
!nightGridHcPreferred &&
|
||||
parsed.soc > batteryHardFloorPct &&
|
||||
parsed.soc > gridTakeoverTriggerPct &&
|
||||
!nightTakeoverLatched &&
|
||||
!batteryRecoveryUrgent;`
|
||||
},
|
||||
{
|
||||
from: `const nightGridTakeoverNeeded =
|
||||
nightSessionActive &&
|
||||
(
|
||||
batteryBelowHardFloor ||
|
||||
batteryAtOrBelowGridTakeoverTrigger ||
|
||||
batteryRecoveryUrgent ||
|
||||
nightTakeoverLatched
|
||||
);`,
|
||||
to: `const nightGridTakeoverNeeded =
|
||||
nightSessionActive &&
|
||||
(
|
||||
nightGridHcPreferred ||
|
||||
batteryBelowHardFloor ||
|
||||
batteryAtOrBelowGridTakeoverTrigger ||
|
||||
batteryRecoveryUrgent ||
|
||||
nightTakeoverLatched
|
||||
);`
|
||||
}
|
||||
]);
|
||||
|
||||
// ---------- BATTERY_CLOUD_LOCKS ----------
|
||||
patchNode('BATTERY_CLOUD_LOCKS', [
|
||||
{
|
||||
from: `const batteryAssistAllowed =
|
||||
nightSessionActive &&
|
||||
!batteryBelowHardFloor &&
|
||||
!batteryRecoveryUrgent &&
|
||||
!nightTakeoverLatched &&
|
||||
parsed.soc > gridTakeoverTriggerPct;`,
|
||||
to: HEADER + `const batteryAssistAllowed =
|
||||
nightSessionActive &&
|
||||
!nightGridHcPreferred &&
|
||||
!batteryBelowHardFloor &&
|
||||
!batteryRecoveryUrgent &&
|
||||
!nightTakeoverLatched &&
|
||||
parsed.soc > gridTakeoverTriggerPct;`
|
||||
},
|
||||
{
|
||||
from: `const nightGridTakeoverNeeded =
|
||||
nightSessionActive &&
|
||||
(
|
||||
batteryBelowHardFloor ||
|
||||
batteryAtOrBelowGridTakeoverTrigger ||
|
||||
batteryRecoveryUrgent ||
|
||||
nightTakeoverLatched
|
||||
);`,
|
||||
to: `const nightGridTakeoverNeeded =
|
||||
nightSessionActive &&
|
||||
(
|
||||
nightGridHcPreferred ||
|
||||
batteryBelowHardFloor ||
|
||||
batteryAtOrBelowGridTakeoverTrigger ||
|
||||
batteryRecoveryUrgent ||
|
||||
nightTakeoverLatched
|
||||
);`
|
||||
}
|
||||
]);
|
||||
|
||||
fs.writeFileSync(FILE, JSON.stringify(wf, null, 2) + '\n');
|
||||
console.log(`Écrit: ${FILE}`);
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user