Chemin d ecriture du chien de garde demontre, pas seulement simule
Sonde de 777 W reellement publiee sur le topic de consigne du GX, relue, puis valeur d origine remise par le script. Le dry-run supprimait exactement ce maillon. Et la fin de session s est deroulee dans le bon ordre sans coordination : consigne retombee a 50 W a 03:31:30 avec l etat de fin de charge, puis contacteur ouvert 34 s plus tard. La demande baisse avant que le reseau parte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
31
HANDOFF.md
31
HANDOFF.md
@@ -2321,3 +2321,34 @@ réellement présente — c'est cette colonne-là qui compte.
|
||||
2. **L'historique HA** (4,4 Go) : perdu en cas de restauration. Conséquence
|
||||
réelle pour le chantier du bilan énergétique, qui s'appuie sur les
|
||||
statistiques long terme. À traiter séparément, en hebdomadaire.
|
||||
|
||||
### Validation de bout en bout, nuit du 01/09 (fin de session)
|
||||
|
||||
**Chemin d'écriture du chien de garde DÉMONTRÉ**, pas seulement simulé
|
||||
(`tools/watchdog-selftest.mjs`, rejouable) : écriture réelle d'une sonde de
|
||||
777 W sur `W/<portail>/settings/0/Settings/CGwacs/AcPowerSetPoint`, relecture
|
||||
conforme, remise de la valeur d'origine par le script lui-même. Le `--dry-run`
|
||||
supprimait justement ce maillon-là ; sans cette preuve, le chien de garde
|
||||
n'était que probablement fonctionnel. Test fait EDF ouvert, donc consigne
|
||||
inerte : risque nul.
|
||||
|
||||
**Et la séquence de fin de session s'est déroulée dans le bon ordre**, sans
|
||||
que personne l'ait orchestrée :
|
||||
|
||||
| 03:31:30 | fin de charge → `night_recovery_done_idle`, et **la consigne retombe à 50 W dans le même cycle** |
|
||||
| 03:32:04 | 34 s plus tard, l'hystérésis ouvre le contacteur (`turn_off_recovery_done`) |
|
||||
|
||||
La demande baisse AVANT que le réseau parte — le principe de la séquence
|
||||
d'ouverture (§30), obtenu ici par construction et non par coordination. C'est
|
||||
la garantie que le risque introduit en §31 (consigne laissée haute) ne se
|
||||
matérialise pas en fonctionnement normal ; le chien de garde ne couvre plus que
|
||||
le cas de la panne.
|
||||
|
||||
**Bilan de la nuit HC** : charge démarrée à 01:58 après correction, menée à son
|
||||
terme à 03:31 sans jamais entamer la réserve (batterie tenue entre −2 et +1 A
|
||||
pendant toute la session), SOC stable à 49 %.
|
||||
|
||||
**Clé API n8n** : désormais aussi dans `.env` à la racine (convention du
|
||||
propriétaire), en plus de `~/.config/opencode/opencode.json`. Le repli du chien
|
||||
de garde sur `.env` est vérifié — utile pour tout déploiement hors poste de
|
||||
travail.
|
||||
|
||||
90
tools/watchdog-selftest.mjs
Normal file
90
tools/watchdog-selftest.mjs
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/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);
|
||||
Reference in New Issue
Block a user