Un temoin sain, et l'historique de toute une chaine par un seul branchement
datalist reste local, mais data <adresse> history <n> est adressable et rend l'enregistrement complet, quinze cellules comprises. Les trois modules de la chaine ancienne sont donc extraits d'un seul branchement, tous corriges du meme decalage lu au chronometre : 369 minutes. Les jeux sont enfin comparables entre eux, ce que les decalages deduits - divergents de deux heures et demie - ne permettaient pas. Le module d'adresse 2 donne le temoin qui manquait : meme chaine, memes courants, memes journees, et ses quinze cellules tiennent dans onze millivolts d'amplitude les unes des autres, ecart median 6 mV. Ce qui a tue les autres ne vient donc ni du regime de charge, ni du pilotage, ni de l'installation. Sur cette base commune la cellule 14 du module ...0322 passe a 273 mV et rejoint les atteintes : dix cellules, pas neuf. L'horloge se propage bien du maitre a sa chaine - j'avais conclu l'inverse en verifiant une seconde apres l'ecriture, il en faut vingt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
@@ -6,23 +6,25 @@ en dessous. Mettre a jour une mesure, relancer, republier.
|
||||
"""
|
||||
import pathlib, datetime
|
||||
|
||||
MAJ = "5 septembre 2026, 16 h"
|
||||
MAJ = "5 septembre 2026, 16 h 30"
|
||||
|
||||
# Amplitude min->max de chaque cellule, relevee sur l'historique du BMS.
|
||||
# C'est LA grandeur qui separe une cellule saine d'une cellule morte : une
|
||||
# cellule qui a perdu sa capacite se remplit et se vide avant ses voisines,
|
||||
# donc elle balaye une plage bien plus large a courant identique.
|
||||
MODULES = [
|
||||
("K220803C30160322", "pack ancien · adresse 1", "1681 relevés, 14/08 → 05/09", [
|
||||
182,178,178,178,177,548,371,181,179,179,182,691,178,251,178]),
|
||||
("K220803C30160319", "pack ancien · adresse 3", "1798 relevés, 30/07 → 05/09", [
|
||||
184,521,187,186,183,318,185,186,187,473,187,445,186,189,424]),
|
||||
("K220803C30160323", "pack ancien · adresse 2 — LE TÉMOIN", "1793 relevés, 30/07 → 05/09", [
|
||||
191,188,188,189,190,197,187,190,189,192,191,188,187,192,198]),
|
||||
("K220803C30160322", "pack ancien · adresse 1", "1793 relevés, 14/08 → 05/09", [
|
||||
182,178,178,178,177,548,371,181,179,179,182,691,178,273,178]),
|
||||
("K220803C30160319", "pack ancien · adresse 3", "1476 relevés, 30/07 → 05/09", [
|
||||
183,521,184,186,182,318,183,186,184,473,184,443,184,188,424]),
|
||||
("K220803C30160324", "isolée (BAT3) · adresse 4", "322 relevés, 23/07 → 05/09", [
|
||||
138,124,138,137,139,132,134,142,143,135,136,137,128,135,683]),
|
||||
]
|
||||
|
||||
PARC = [
|
||||
("K220803C30160322", "ancien", "1", "3", "6 · 7 · 12", 498, 86, "en service"),
|
||||
("K220803C30160322", "ancien", "1", "4", "6 · 7 · 12 · 14", 498, 86, "en service"),
|
||||
("K220803C30160323", "ancien", "2", "0", "—", 0, 0, "en service"),
|
||||
("K220803C30160319", "ancien", "3", "5", "2 · 6 · 10 · 12 · 15", 416, 179, "en service"),
|
||||
("K220803C30160324", "ancien", "4", "1", "15", 29, 59, "isolée"),
|
||||
@@ -192,14 +194,14 @@ footer {{ margin-top:5rem; padding-top:1.6rem; border-top:1px solid var(--trait)
|
||||
<header>
|
||||
<div class="etiq">Parc Pylontech · diagnostic en cours</div>
|
||||
<h1>Les cent cinq cellules</h1>
|
||||
<p class="chapeau">Huit modules, quinze cellules chacun. Neuf de ces cellules
|
||||
brident la charge des cent cinq autres. Voici lesquelles, comment on le sait,
|
||||
<p class="chapeau">Huit modules, quinze cellules chacun. Dix de ces cellules
|
||||
brident la charge des quatre-vingt-quinze autres. Voici lesquelles, comment on le sait,
|
||||
et ce qu’on peut en faire.</p>
|
||||
<div class="maj">Dernière mise à jour : {MAJ}</div>
|
||||
</header>
|
||||
|
||||
<div class="verdict">
|
||||
<div><div class="n">9</div><div class="l">cellules atteintes sur 105</div></div>
|
||||
<div><div class="n">10</div><div class="l">cellules atteintes sur 105</div></div>
|
||||
<div><div class="n">3</div><div class="l">modules concernés sur 8</div></div>
|
||||
<div><div class="n">5 s</div><div class="l">entre le franchissement du seuil et la coupure</div></div>
|
||||
<div><div class="n">30 A</div><div class="l">plafond imposé au parc entier, pour 150 A nominaux</div></div>
|
||||
@@ -211,9 +213,9 @@ footer {{ margin-top:5rem; padding-top:1.6rem; border-top:1px solid var(--trait)
|
||||
soixante cellules sans une seule anomalie, après plus de trois cents cycles. Un
|
||||
cinquième l’est tout autant.</p>
|
||||
<p>Trois modules portent l’ensemble du problème, et à l’intérieur de ces trois,
|
||||
<strong>neuf cellules</strong>. Quand l’une d’elles atteint 3550 mV, le BMS réduit
|
||||
<strong>dix cellules</strong>. Quand l’une d’elles atteint 3550 mV, le BMS réduit
|
||||
le courant de charge — non pas de son module, mais <strong>de tout le parc</strong>.
|
||||
Cinq modules sains sont bridés par neuf cellules.</p>
|
||||
Cinq modules sains sont bridés par dix cellules.</p>
|
||||
</div>
|
||||
|
||||
<figure>
|
||||
@@ -225,11 +227,17 @@ Cinq modules sains sont bridés par neuf cellules.</p>
|
||||
<span><i class="pastille" style="background:var(--mort)"></i>terminale</span>
|
||||
</div>
|
||||
<figcaption>Amplitude balayée par chaque cellule entre sa tension minimale et
|
||||
sa tension maximale sur la période d’observation. Une cellule saine suit ses
|
||||
voisines à quelques millivolts près. Une cellule qui a perdu de la capacité se
|
||||
remplit et se vide avant elles, donc elle balaye une plage bien plus large à
|
||||
courant identique — c’est le signe qui ne trompe pas, et il ne demande aucun
|
||||
démontage.</figcaption>
|
||||
sa tension maximale sur la période d’observation. Le premier module est le
|
||||
<strong>témoin</strong> : il vit dans la même chaîne que les deux abîmés,
|
||||
a subi les mêmes courants et les mêmes journées, et ses quinze cellules tiennent
|
||||
dans <strong>onze millivolts d’amplitude les unes des autres</strong>. C’est à
|
||||
cela que ressemble un module sain, et c’est ce qui rend les autres barres
|
||||
lisibles. Une cellule qui a perdu de la capacité se remplit et se vide avant ses
|
||||
voisines, donc elle balaye une plage bien plus large à courant identique — le
|
||||
signe ne demande aucun démontage. Les amplitudes du module isolé sont plus
|
||||
faibles parce qu’il a passé la période hors service, sur une plage de charge
|
||||
plus étroite : c’est l’écart à ses propres voisines qui compte, pas la
|
||||
valeur absolue.</figcaption>
|
||||
</figure>
|
||||
|
||||
<div class="mesure">
|
||||
|
||||
@@ -123,6 +123,28 @@ function lireHistorique(f) {
|
||||
return { module: bc[1], decalageMin, origine, lignes, nb: rangs.length };
|
||||
}
|
||||
|
||||
// ------------------------------------------------- historique distant (CSV)
|
||||
// Produit par `--historique-distant <adresse>` : deja horodate a l'heure
|
||||
// REELLE (le decalage est lu et applique a l'ecriture) et deja nomme par
|
||||
// code-barres. Rien a corriger, rien a deviner.
|
||||
function lireHistoDistant(f) {
|
||||
const lignes = [];
|
||||
const l = lireTexte(f).trim().split('\n');
|
||||
const cols = l[0].split(',');
|
||||
const iC = cols.indexOf('c1_mV');
|
||||
for (const ligne of l.slice(1)) {
|
||||
const c = ligne.split(',');
|
||||
if (c.length < iC + 15) continue;
|
||||
for (let k = 0; k < 15; k++) {
|
||||
const mv = +c[iC + k];
|
||||
if (!(mv > 1000)) continue;
|
||||
lignes.push([c[0], c[1], k + 1, mv, c[4] === '' ? null : +c[4],
|
||||
c[3] === '' ? null : +c[3], c[2] || null, 'historique']);
|
||||
}
|
||||
}
|
||||
return lignes.length ? { module: l[1].split(',')[1], lignes } : null;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------- suivi
|
||||
function lireSuivi(f) {
|
||||
const lignes = [];
|
||||
@@ -157,7 +179,10 @@ const paquets = [];
|
||||
for (const f of fichiers.sort()) {
|
||||
const n = path.basename(f);
|
||||
try {
|
||||
if (n.endsWith('-suivi.csv')) {
|
||||
if (n.includes('-histo-distant-')) {
|
||||
const r = lireHistoDistant(f);
|
||||
if (r) { paquets.push(r.lignes); console.log(` ${n}\n ${r.module}, ${r.lignes.length / 15} enregistrements, horodates a l'export`); }
|
||||
} else if (n.endsWith('-suivi.csv')) {
|
||||
const r = lireSuivi(f);
|
||||
if (r) { paquets.push(r.lignes); console.log(` ${n} suivi, ${r.lignes.length / 15} releves, modules ${r.modules.join(' ')}`); }
|
||||
} else if (/histor/i.test(n) && n.endsWith('.txt')) {
|
||||
|
||||
@@ -238,6 +238,60 @@ async function main() {
|
||||
return;
|
||||
}
|
||||
|
||||
// --historique-distant <adresse> : l'historique d'un AUTRE module de la
|
||||
// chaine. `datalist` est local, mais `data <adresse> history <n>` est
|
||||
// adressable et rend l'enregistrement complet, quinze cellules comprises.
|
||||
// Un seul branchement par chaine suffit donc pour tout son historique.
|
||||
if (arg('historique-distant')) {
|
||||
const ad = parseInt(String(arg('historique-distant')), 10);
|
||||
if (!(ad >= 1 && ad <= 16)) { console.log('Adresse invalide.'); process.exit(1); }
|
||||
|
||||
// Le decalage se mesure AVANT, car les enregistrements stockes portent
|
||||
// l'heure qu'avait le module quand il les a ecrits.
|
||||
const rtc = await commande(l, 'time', 8000);
|
||||
const mr = rtc.match(/RTC\s+(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/);
|
||||
let decMs = 0;
|
||||
if (mr) {
|
||||
const [d, h] = mr[1].split(' ');
|
||||
const [A, M, J] = d.split('-').map(Number); const [H, N, S] = h.split(':').map(Number);
|
||||
decMs = new Date(A, M - 1, J, H, N, S).getTime() - Date.now();
|
||||
console.log(`Horloge du module : ${mr[1]} — ${Math.round(decMs / 60000)} min d'avance, corriges a l'ecriture.`);
|
||||
} else {
|
||||
console.log('Horloge non lue : les horodatages seront ceux du module, non corriges.');
|
||||
}
|
||||
|
||||
const info = await commande(l, `info ${ad}`, 8000);
|
||||
const bc = (info.match(/Barcode\s*:\s*(\S+)/) || [])[1] || `adresse${ad}`;
|
||||
const f = path.join(DOSSIER, `${tampon()}-histo-distant-${bc}.csv`);
|
||||
fs.mkdirSync(DOSSIER, { recursive: true });
|
||||
fs.writeFileSync(f, 'heure_reelle,module,etat,courant_mA,soc,' +
|
||||
Array.from({ length: 15 }, (_, i) => `c${i + 1}_mV`).join(',') + ',ecart_mV\n');
|
||||
console.log(`Module ${bc} (adresse ${ad}) -> ${f}`);
|
||||
|
||||
let ecrits = 0, vides = 0;
|
||||
for (let n = 1; n <= 1800; n++) {
|
||||
const t = await commande(l, `data ${ad} history ${n}`, 8000);
|
||||
const mt = t.match(/Time\s*:\s*(\d\d-\d\d-\d\d \d\d:\d\d:\d\d)/);
|
||||
const cel = cellules(t);
|
||||
if (!mt || !cel || cel.length < 15) {
|
||||
if (++vides > 20) break; // fin du tampon
|
||||
continue;
|
||||
}
|
||||
vides = 0;
|
||||
const [d, h] = mt[1].split(' ');
|
||||
const [A, M, J] = d.split('-').map(Number); const [H, N, S] = h.split(':').map(Number);
|
||||
const quand = new Date(new Date(2000 + A, M - 1, J, H, N, S).getTime() - decMs);
|
||||
const v = cel.map((c) => c.mV);
|
||||
const soc = (t.match(/Percent\s*:\s*(\d+)/) || [])[1] || '';
|
||||
fs.appendFileSync(f, [quand.toISOString(), bc, cel[0].etat, cel[0].mA, soc,
|
||||
...v, Math.max(...v) - Math.min(...v)].join(',') + '\n');
|
||||
ecrits++;
|
||||
if (n % 25 === 0) process.stdout.write(`\r ${n} interroges, ${ecrits} ecrits `);
|
||||
}
|
||||
console.log(`\n${ecrits} enregistrements ecrits dans ${f}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg('suivi')) {
|
||||
const pas = Math.max(10, +(arg('suivi', '30')) || 30);
|
||||
const nom = `${tampon()}-suivi.csv`;
|
||||
|
||||
Reference in New Issue
Block a user