Le courant par module entre dans les CSV de releve

La colonne courant_mA etait vide depuis la mise en service : le mode --modules
interroge chaque adresse avec soh <n>, qui donne les quinze tensions mais AUCUN
courant. La source juste est pwr, qui rend courant, tension et SOC pour TOUS les
modules en un seul appel — une commande serie par cycle au lieu d'une par
module.

⚠️ LE TABLEAU pwr N'A PAS LES MEMES COLONNES SELON LA FAMILLE DE MODULES.
Mesure du 16/09 : les US5000 (firmware B69.26) intercalent Tlow.Id, Thigh.Id,
Vlow.Id et Vhigh.Id que les US2000C (B69.13) n'ont pas. Un parseur a index figes
lit donc n'importe quoi des qu'on change de pack — c'est la meme famille de
piege que le trou de 44 h. Les colonnes sont resolues PAR LEUR NOM dans
l'en-tete. Les trois utiles (Volt, Curr, Coulomb) precedent Time, dont la valeur
contient une espace, donc le decoupage positionnel reste valable.

Echec de pwr non bloquant : on ecrit la ligne sans courant plutot que de perdre
les tensions, qui sont l'essentiel du releve.

Verifie sur cinq modules, trois cycles : courant et SOC par module dans le
journal et dans le CSV. Pack 2 a 13:18 — 9,5 + 9,4 + 9,0 + 9,2 + 9,1 = 46,2 A,
SOC 82/79/83/81/87 %.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
Tallagrand Thierry
2026-09-16 13:19:02 +02:00
parent 7d76319509
commit 3587f236da

View File

@@ -332,6 +332,43 @@ async function main() {
return; return;
} }
// ---------------------------------------------------------------------
// COURANT PAR MODULE — un seul `pwr` pour toute la chaine
// ---------------------------------------------------------------------
// `soh <n>` donne les quinze tensions mais AUCUN courant : c'est pourquoi la
// colonne courant_mA du CSV est restee vide depuis la mise en service.
// `pwr` donne courant, tension et SOC pour TOUS les modules en un appel —
// une commande serie par cycle au lieu d'une par module.
//
// ⚠️ LE TABLEAU N'A PAS LES MEMES COLONNES SELON LA FAMILLE DE MODULES.
// Mesure du 16/09 : les US5000 (firmware B69.26) intercalent Tlow.Id,
// Thigh.Id, Vlow.Id et Vhigh.Id que les US2000C (B69.13) n'ont pas. Un
// parseur a index figes lit donc n'importe quoi des qu'on change de pack.
// On resout les colonnes PAR LEUR NOM, dans l'en-tete.
//
// Les colonnes utiles (Volt, Curr, Coulomb) precedent toutes `Time`, dont la
// valeur contient une espace — le decoupage positionnel reste donc valable.
function courantsParModule(txt) {
const lignes = txt.split('\n').map((l) => l.replace(/\r/g, '').trimEnd());
const iEntete = lignes.findIndex((l) => /^\s*Power\s+Volt\s+Curr\b/.test(l));
if (iEntete === -1) return new Map();
const cols = lignes[iEntete].trim().split(/\s+/);
const iCurr = cols.indexOf('Curr');
const iVolt = cols.indexOf('Volt');
const iSoc = cols.indexOf('Coulomb');
if (iCurr === -1) return new Map();
const m = new Map();
for (const l of lignes.slice(iEntete + 1)) {
const c = l.trim().split(/\s+/);
const ad = parseInt(c[0], 10);
if (!Number.isFinite(ad) || c[iCurr] === undefined || c[iCurr] === '-') continue;
const mA = parseInt(c[iCurr], 10);
if (!Number.isFinite(mA)) continue;
m.set(ad, { mA, mV: parseInt(c[iVolt], 10) || null, soc: iSoc > -1 ? c[iSoc] : null });
}
return m;
}
if (arg('suivi')) { if (arg('suivi')) {
const pas = Math.max(10, +(arg('suivi', '30')) || 30); const pas = Math.max(10, +(arg('suivi', '30')) || 30);
const nom = `${horodateFichier()}-suivi.csv`; const nom = `${horodateFichier()}-suivi.csv`;
@@ -370,6 +407,12 @@ async function main() {
console.log('Ctrl-C pour arreter.\n'); console.log('Ctrl-C pour arreter.\n');
for (;;) { for (;;) {
if (adresses.length) { if (adresses.length) {
// Un `pwr` pour toute la chaine, avant la boucle par module. Si la
// lecture echoue on continue sans courant plutot que de perdre les
// tensions, qui sont l'essentiel du releve.
let puis = new Map();
try { puis = courantsParModule(await commande(l, 'pwr', 10000)); }
catch { /* on garde les tensions */ }
for (const ad of adresses) { for (const ad of adresses) {
const t = await commande(l, `soh ${ad}`, 10000); const t = await commande(l, `soh ${ad}`, 10000);
// `soh` : "<n0based> <mV> <SOHCount> <statut>" // `soh` : "<n0based> <mV> <SOHCount> <statut>"
@@ -381,9 +424,12 @@ async function main() {
if (c.length < 10) { console.log(`${horodate()} module ${ad} : illisible`); continue; } if (c.length < 10) { console.log(`${horodate()} module ${ad} : illisible`); continue; }
const v = c.map((x) => x.mV); const v = c.map((x) => x.mV);
const hi = Math.max(...v), lo = Math.min(...v); const hi = Math.max(...v), lo = Math.min(...v);
fs.appendFileSync(f, [new Date().toISOString(), codes.get(ad), '', ...v, hi - lo].join(',') + '\n'); const p = puis.get(ad);
fs.appendFileSync(f, [new Date().toISOString(), codes.get(ad),
p ? p.mA : '', ...v, hi - lo].join(',') + '\n');
console.log(`${horodate()} ${codes.get(ad).slice(-4)} haute ${hi} mV (cellule ${c.find((x) => x.mV === hi).n})` + console.log(`${horodate()} ${codes.get(ad).slice(-4)} haute ${hi} mV (cellule ${c.find((x) => x.mV === hi).n})` +
` basse ${lo} mV ecart ${String(hi - lo).padStart(3)} mV` + ` basse ${lo} mV ecart ${String(hi - lo).padStart(3)} mV` +
(p ? ` ${(p.mA / 1000).toFixed(1)} A ${p.soc || ''}` : '') +
(hi >= 3600 ? ' NIVEAU 4' : hi >= 3550 ? ' NIVEAU 3, le courant se reduit' : '')); (hi >= 3600 ? ' NIVEAU 4' : hi >= 3550 ? ' NIVEAU 3, le courant se reduit' : ''));
} }
await new Promise((r) => setTimeout(r, pas * 1000)); await new Promise((r) => setTimeout(r, pas * 1000));