#!/usr/bin/env node /** * Preuve de bout en bout du CHEMIN D'ÉCRITURE du chien de garde. * * Le `--dry-run` valide la détection, le verrou et le journal, mais il * supprime précisément ce qui compte le jour d'une panne : la publication * MQTT vers le GX. Si le broker refusait les topics `W/`, le chien de garde * serait décoratif — et on ne l'apprendrait qu'au pire moment. Ce test écrit * réellement, relit, puis REMET la valeur d'origine lui-même. * * Sûreté : il n'écrit que sur la consigne de puissance réseau, il mémorise la * valeur trouvée avant de la modifier et la restaure quoi qu'il arrive * (y compris sur erreur ou Ctrl-C). La valeur sonde est volontairement * absurde (777 W) pour être reconnaissable dans un journal. * * Usage : node tools/watchdog-selftest.mjs */ import mqtt from 'mqtt'; const HOST = process.env.GX_MQTT_HOST || '10.10.10.9'; const PORT = Number(process.env.GX_MQTT_PORT || 1883); const P = process.env.GX_PORTAL_ID || 'c0619ab34ac5'; const EV = process.env.GX_EVCHARGER_INSTANCE || '40'; const PROBE_W = 777; const SETPOINT = 'settings/0/Settings/CGwacs/AcPowerSetPoint'; const STARTSTOP = `evcharger/${EV}/StartStop`; const ACIN = 'system/0/Ac/Grid/L1/Power'; const cache = new Map(); const c = mqtt.connect(`mqtt://${HOST}:${PORT}`, { connectTimeout: 8000 }); const read = (p) => cache.get(`N/${P}/${p}`); const write = (p, value) => c.publish(`W/${P}/${p}`, JSON.stringify({ value })); const wait = (ms) => new Promise(r => setTimeout(r, ms)); let original = null; async function restore(why) { if (original === null) return; write(SETPOINT, original); await wait(1500); console.log(` restauration (${why}) → consigne ${read(SETPOINT)} W`); } process.on('SIGINT', async () => { await restore('interruption'); process.exit(1); }); c.on('connect', async () => { for (const t of [SETPOINT, STARTSTOP, ACIN]) c.subscribe(`N/${P}/${t}`); c.publish(`R/${P}/keepalive`, ''); await wait(2500); original = read(SETPOINT); console.log(`GX ${HOST}:${PORT} — connecté`); console.log(`état trouvé : consigne ${original} W · borne StartStop=${read(STARTSTOP)} · réseau ${read(ACIN)} W\n`); if (typeof original !== 'number') { console.log('❌ consigne illisible : le test ne peut pas garantir la restauration. ABANDON.'); c.end(); process.exit(1); } try { console.log(`1) écriture réelle de la sonde : ${PROBE_W} W`); write(SETPOINT, PROBE_W); await wait(3000); const seen = read(SETPOINT); console.log(` relecture : ${seen} W`); const ok = seen === PROBE_W; console.log(ok ? ' ✅ le GX accepte les écritures du chien de garde\n' : ` ❌ écriture SANS EFFET (attendu ${PROBE_W}) — le chien de garde serait décoratif\n`); console.log(`2) remise de la valeur d'origine : ${original} W`); write(SETPOINT, original); await wait(3000); const back = read(SETPOINT); console.log(` relecture : ${back} W ${back === original ? '✅' : '❌'}\n`); console.log(ok && back === original ? 'VERDICT : chemin d\'écriture DÉMONTRÉ, état d\'origine rétabli.' : 'VERDICT : ANOMALIE — voir ci-dessus.'); process.exitCode = ok && back === original ? 0 : 1; } catch (e) { console.error('erreur :', e.message); await restore('erreur'); process.exitCode = 1; } c.end(); setTimeout(() => process.exit(process.exitCode ?? 0), 500); }); c.on('message', (t, m) => { try { const v = JSON.parse(m.toString()); if (v && 'value' in v) cache.set(t, v.value); } catch {} }); c.on('error', (e) => { console.error('MQTT:', e.message); process.exit(1); }); setTimeout(() => { console.error('délai dépassé'); process.exit(1); }, 30000);