Le hub reconnait les deux groupes — LED verte et trois bips, son accuse de reception — mais le Cerbo n en enumere qu un : 4 modules et 400 Ah la ou le parc en compte 9 et 650. Le releve de reference apporte le fait qui manquait : a 20:14 les quatre US5000 debitent 24 A au total pendant que le Cerbo annonce 41 A au parc. Les 17 A d ecart sont le pack 2, qui porte donc bien du courant sans etre enumere. Le courant mesure au shunt n est pas un indicateur d enumeration — seuls nombre_de_modules_en_ligne et capacite_installee le sont. Quatre etapes par risque croissant, la premiere ne touchant a aucun cable : un redemarrage du Cerbo, l inventaire ayant deja change une fois le 13/09 a 21:35, evenement isole verifie comme n etant pas un redemarrage de Home Assistant. Regle de securite tiree de l incident d aujourd hui : fermer EDF AVANT toute manoeuvre. Couper la communication du BMS arrete la gestion batterie, donc l onduleur, donc la maison — 72 secondes dans le noir a 16:24. EDF deja ferme, la maison passe en direct et ne voit rien. L etape 4 renvoie a la documentation constructeur et n avance aucune theorie de cablage : sur du materiel batterie je ne raisonne pas de memoire. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
108 lines
5.2 KiB
JavaScript
108 lines
5.2 KiB
JavaScript
#!/usr/bin/env node
|
|
// RELEVE DE L ETAT DU PARC BATTERIE — LECTURE SEULE, rejouable.
|
|
//
|
|
// node tools/releve-parc-batterie.mjs "etape 0 - reference"
|
|
// node tools/releve-parc-batterie.mjs "etape 1 - apres reboot cerbo" --console /dev/ttyUSB0
|
|
//
|
|
// Prend une photo complete de ce que le Cerbo voit du parc, et si une console
|
|
// serie est branchee, de ce que la chaine declare de son cote. Ecrit un journal
|
|
// horodate dans docs/mesures/ pour que deux etapes se comparent ligne a ligne.
|
|
//
|
|
// AUCUNE ECRITURE vers Home Assistant, vers le Cerbo ou vers les modules.
|
|
import fs from 'fs';
|
|
import { execFileSync } from 'child_process';
|
|
|
|
const etiquette = process.argv[2] || 'releve';
|
|
const port = process.argv.includes('--console')
|
|
? process.argv[process.argv.indexOf('--console') + 1] : null;
|
|
|
|
const env = fs.readFileSync('.env', 'utf8');
|
|
const jeton = (env.match(/^HA_TOKEN=(.*)$/m)?.[1] || '').trim().replace(/^["']|["']$/g, '');
|
|
const HA = 'http://10.10.10.17:8123';
|
|
|
|
// Ce que le CERBO declare du parc. Les deux premieres lignes sont le critere de
|
|
// reussite du protocole : 9 modules et 650 Ah (4 x 100 + 5 x 50) diraient qu il
|
|
// voit enfin les deux groupes.
|
|
const CERBO = [
|
|
['sensor.airtria_8_us2000c_nombre_de_modules_en_ligne', 'modules en ligne'],
|
|
['sensor.airtria_8_us2000c_capacite_installee', 'capacite installee'],
|
|
['sensor.airtria_8_us2000c_maximum_allowed_charge_current', 'charge autorisee'],
|
|
['sensor.airtria_8_us2000c_maximum_allowed_discharge_current', 'decharge autorisee'],
|
|
['sensor.airtria_8_us2000c_maximum_allowed_charging_voltage', 'tension autorisee'],
|
|
['sensor.airtria_8_us2000c_nombre_de_modules_bloquant_la_charge', 'modules bloquant charge'],
|
|
['sensor.airtria_8_us2000c_nombre_de_modules_hors_ligne', 'modules hors ligne'],
|
|
['sensor.airtria_8_us2000c_tension_minimale_de_la_cellule', 'cellule la plus basse'],
|
|
['sensor.airtria_8_us2000c_tension_maximale_de_la_cellule', 'cellule la plus haute'],
|
|
['sensor.airtria_8_us2000c_id_de_la_cellule_a_tension_maximale', 'id cellule haute'],
|
|
['sensor.victron_battery_soc', 'SOC'],
|
|
['sensor.victron_battery_voltage', 'tension parc'],
|
|
['sensor.victron_battery_current', 'courant parc'],
|
|
['sensor.victron_max_charge_current_setting', 'DVCC appliquee'],
|
|
['switch.edf_general_ok_switch', 'contacteur EDF'],
|
|
['sensor.victron_consumption_power_l1', 'maison'],
|
|
['sensor.victron_pv_power_system', 'production'],
|
|
];
|
|
|
|
const etat = async (id) => {
|
|
try {
|
|
const r = await fetch(`${HA}/api/states/${id}`, { headers: { Authorization: `Bearer ${jeton}` } });
|
|
if (!r.ok) return null;
|
|
const s = await r.json();
|
|
return { v: s.state, depuis: new Date(s.last_changed).toLocaleString('fr-FR', { timeZone: 'Europe/Paris' }) };
|
|
} catch { return null; }
|
|
};
|
|
|
|
const lignes = [];
|
|
const dire = (s) => { lignes.push(s); console.log(s); };
|
|
|
|
const maintenant = new Date();
|
|
dire(`RELEVE PARC BATTERIE — ${etiquette}`);
|
|
dire(maintenant.toLocaleString('fr-FR', { timeZone: 'Europe/Paris' }));
|
|
dire('='.repeat(72));
|
|
|
|
dire('\nCE QUE LE CERBO DECLARE');
|
|
for (const [id, libelle] of CERBO) {
|
|
const e = await etat(id);
|
|
dire(' ' + libelle.padEnd(26) + '= ' + String(e ? e.v : '(absent)').padEnd(22) + (e ? '(depuis ' + e.depuis + ')' : ''));
|
|
}
|
|
|
|
dire('\nALARMES');
|
|
try {
|
|
const tout = await (await fetch(`${HA}/api/states`, { headers: { Authorization: `Bearer ${jeton}` } })).json();
|
|
const mauvais = tout.filter((x) => /alarm|alarme|error|erreur|bloquant|hors_ligne/i.test(x.entity_id)
|
|
&& !/^(no_alarm|no_error|off|0|0\.0|unavailable|unknown|normal|ok)$/i.test(String(x.state)));
|
|
if (!mauvais.length) dire(' aucune');
|
|
for (const x of mauvais.slice(0, 14)) dire(' ' + x.entity_id.slice(0, 58).padEnd(60) + '= ' + x.state);
|
|
} catch (e) { dire(' lecture impossible : ' + e.message); }
|
|
|
|
if (port) {
|
|
dire(`\nCE QUE LA CHAINE DECLARE (console sur ${port})`);
|
|
for (const cmd of ['pwr', 'info 1', 'info 2', 'info 3', 'info 4', 'info 5']) {
|
|
try {
|
|
const out = execFileSync('node', ['tools/pylontech-console.mjs', '--port', port, '--commande', cmd],
|
|
{ encoding: 'utf8', timeout: 45000 });
|
|
if (cmd === 'pwr') {
|
|
const l = out.split('\n').filter((x) => /^\d+\s/.test(x));
|
|
const presents = l.filter((x) => !/Absent/.test(x));
|
|
dire(` modules qui repondent : ${presents.length}`);
|
|
for (const x of presents) dire(' ' + x.trim().slice(0, 100));
|
|
} else {
|
|
const b = out.match(/Barcode\s*:\s*(\S+)/)?.[1];
|
|
const d = out.match(/Device name\s*:\s*(\S+)/)?.[1];
|
|
const f = out.match(/Main Soft version\s*:\s*(\S+)/)?.[1];
|
|
if (b) dire(` ${cmd.padEnd(8)} ${String(d).padEnd(9)} ${b} firmware ${f}`);
|
|
}
|
|
} catch { if (cmd === 'pwr') dire(' console injoignable'); }
|
|
}
|
|
} else {
|
|
dire('\n(pas de console branchee — passer --console /dev/ttyUSB0 pour lire la chaine)');
|
|
}
|
|
|
|
dire('\n' + '='.repeat(72));
|
|
dire('CRITERE DE REUSSITE : 9 modules en ligne et 650 Ah (4 x 100 + 5 x 50).');
|
|
dire('Tant que le Cerbo annonce 4 modules et 400 Ah, il ne voit qu un groupe.');
|
|
|
|
const nom = `docs/mesures/${maintenant.toISOString().slice(0, 16).replace(/[-:T]/g, '')}-parc-${etiquette.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}.log`;
|
|
fs.writeFileSync(nom, lignes.join('\n') + '\n');
|
|
console.log(`\n--- journalise dans ${nom}`);
|