Releve du pack 1 (US5000) sur yd-5CG6110DG0 : service, regle udev, suffixe de fichier
Le cable du pack 1 est a puce Prolific, sans pilote sur DSM : lu par un portable a noyau recent. --suffixe pack1 separe les CSV des deux lecteurs ; evcs_cellules regarde 15 min (deux lecteurs non synchronises) et publie modules_lus (9 attendus). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uJ47NxgfpGnuuMr3f2n7Z
This commit is contained in:
2
deploy/releve-cellules/pack1/90-pylontech.rules
Normal file
2
deploy/releve-cellules/pack1/90-pylontech.rules
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Cable Pylontech du pack 1 (Prolific PL2303, 26/09/2026) : nom stable par numero de serie
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="23a3", ATTRS{serial}=="BJDAb2A7N11", SYMLINK+="pylontech-pack1", GROUP="dialout", MODE="0660"
|
||||||
28
deploy/releve-cellules/pack1/releve-pack1.service
Normal file
28
deploy/releve-cellules/pack1/releve-pack1.service
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Releve des cellules du PACK 1 (4 x US5000) sur yd-5CG6110DG0 (10.10.10.61).
|
||||||
|
# Le pack 2 est lu par le conteneur du Synology .17 ; le pack 1 ne peut pas
|
||||||
|
# l'etre : son cable est a puce Prolific PL2303 (067b:23a3), sans pilote sur DSM.
|
||||||
|
# Ce portable (noyau 7.0) le reconnait.
|
||||||
|
#
|
||||||
|
# Installation : sudo cp releve-pack1.service /etc/systemd/system/ &&
|
||||||
|
# sudo systemctl daemon-reload && sudo systemctl enable --now releve-pack1
|
||||||
|
[Unit]
|
||||||
|
Description=Releve des cellules Pylontech du pack 1 (US5000)
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target remote-fs.target
|
||||||
|
# Le lecteur lit son code et ecrit ses CSV dans le depot, monte en NFS.
|
||||||
|
RequiresMountsFor=/mnt/projects/EVCS/EVCS
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=tmzf5455
|
||||||
|
WorkingDirectory=/mnt/projects/EVCS/EVCS
|
||||||
|
# /dev/pylontech-pack1 : lien stable pose par 90-pylontech.rules d'apres le
|
||||||
|
# NUMERO DE SERIE du cable, jamais d'apres l'ordre d'enumeration.
|
||||||
|
# --suffixe pack1 : fichiers ...-pack1-suivi.csv, distincts de ceux du pack 2.
|
||||||
|
ExecStart=/usr/bin/node tools/pylontech-console.mjs --port /dev/pylontech-pack1 --suivi 600 --modules 1,2,3,4 --suffixe pack1
|
||||||
|
# Cable absent, port fige (garde anti-blocage du lecteur), NFS coupe, coupure
|
||||||
|
# de courant : on relance, sans limite. Le portable n'a plus de batterie.
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -132,6 +132,10 @@ export function etatCellules(r) {
|
|||||||
statut: r.spread_max === null ? 'muet' : 'frais',
|
statut: r.spread_max === null ? 'muet' : 'frais',
|
||||||
age_releve_min: age, seuil_perime_min: RELEVE_PERIME_MIN,
|
age_releve_min: age, seuil_perime_min: RELEVE_PERIME_MIN,
|
||||||
module_le_plus_sensible: r.weakest || null,
|
module_le_plus_sensible: r.weakest || null,
|
||||||
|
// Nombre de modules dans le releve : 9 attendus quand les deux packs sont
|
||||||
|
// lus (4 US5000 + 5 US2000C). Moins = un lecteur muet, l'ecart ne couvre
|
||||||
|
// alors qu'une partie du parc.
|
||||||
|
...(r.modules_lus !== undefined ? { modules_lus: r.modules_lus } : {}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1873,14 +1873,18 @@ async function publierCellulesHelpers(client) {
|
|||||||
if (!client) { await muet('base injoignable'); return 0; }
|
if (!client) { await muet('base injoignable'); return 0; }
|
||||||
const { rows: [r] } = await client.query(
|
const { rows: [r] } = await client.query(
|
||||||
`WITH mx AS (SELECT max(ts) AS t FROM cellule),
|
`WITH mx AS (SELECT max(ts) AS t FROM cellule),
|
||||||
|
-- 15 min et non 5 (26/09) : deux lecteurs, un par pack, chacun toutes les
|
||||||
|
-- 10 min et sans synchronisation ; une fenetre plus courte que le pas
|
||||||
|
-- ferait disparaitre un pack sur deux selon l'instant du calcul.
|
||||||
recent AS (SELECT DISTINCT ON (module) module, ts FROM cellule
|
recent AS (SELECT DISTINCT ON (module) module, ts FROM cellule
|
||||||
WHERE ts > (SELECT t FROM mx) - interval '5 minutes' ORDER BY module, ts DESC),
|
WHERE ts > (SELECT t FROM mx) - interval '15 minutes' ORDER BY module, ts DESC),
|
||||||
spreads AS (SELECT c.module, max(c.mv) - min(c.mv) AS ecart
|
spreads AS (SELECT c.module, max(c.mv) - min(c.mv) AS ecart
|
||||||
FROM cellule c JOIN recent rc ON rc.module = c.module AND rc.ts = c.ts
|
FROM cellule c JOIN recent rc ON rc.module = c.module AND rc.ts = c.ts
|
||||||
GROUP BY c.module)
|
GROUP BY c.module)
|
||||||
SELECT (SELECT max(ecart) FROM spreads) AS spread_max,
|
SELECT (SELECT max(ecart) FROM spreads) AS spread_max,
|
||||||
(SELECT module FROM spreads ORDER BY ecart DESC NULLS LAST LIMIT 1) AS weakest,
|
(SELECT module FROM spreads ORDER BY ecart DESC NULLS LAST LIMIT 1) AS weakest,
|
||||||
(SELECT round(EXTRACT(EPOCH FROM (now() - t)) / 60)::int FROM mx) AS age_min`)
|
(SELECT round(EXTRACT(EPOCH FROM (now() - t)) / 60)::int FROM mx) AS age_min,
|
||||||
|
(SELECT count(*) FROM spreads)::int AS modules_lus`)
|
||||||
.catch(async (e) => { await muet('base injoignable').catch(() => {}); throw e; });
|
.catch(async (e) => { await muet('base injoignable').catch(() => {}); throw e; });
|
||||||
// Capteur unique pour l'agent (25/09) : releve perime => ni ecart ni module
|
// Capteur unique pour l'agent (25/09) : releve perime => ni ecart ni module
|
||||||
// visibles (G13b du banc OpenClaw). Publie avant le test de table vide, pour
|
// visibles (G13b du banc OpenClaw). Publie avant le test de table vide, pour
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
// node tools/pylontech-console.mjs --port /dev/ttyUSB0 --commande help
|
// node tools/pylontech-console.mjs --port /dev/ttyUSB0 --commande help
|
||||||
// node tools/pylontech-console.mjs --port /dev/ttyUSB0 --historique
|
// node tools/pylontech-console.mjs --port /dev/ttyUSB0 --historique
|
||||||
// node tools/pylontech-console.mjs --port /dev/ttyUSB0 --suivi 30
|
// node tools/pylontech-console.mjs --port /dev/ttyUSB0 --suivi 30
|
||||||
|
// node tools/pylontech-console.mjs --port /dev/pylontech-pack1 --suivi 600 --modules 1,2,3,4 --suffixe pack1
|
||||||
|
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
@@ -371,7 +372,12 @@ async function main() {
|
|||||||
|
|
||||||
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`;
|
// --suffixe pack1 : deux lecteurs (un par pack, sur deux machines depuis le
|
||||||
|
// 26/09) ecrivent dans le MEME dossier ; sans suffixe, deux demarrages dans la
|
||||||
|
// meme minute melangeraient leurs lignes dans un seul fichier. Le nom finit
|
||||||
|
// toujours par -suivi.csv, seul motif que lit l'ingestion.
|
||||||
|
const suffixe = arg('suffixe') ? `-${String(arg('suffixe')).replace(/[^\w-]/g, '')}` : '';
|
||||||
|
const nom = `${horodateFichier()}${suffixe}-suivi.csv`;
|
||||||
const f = path.join(DOSSIER, nom);
|
const f = path.join(DOSSIER, nom);
|
||||||
fs.mkdirSync(DOSSIER, { recursive: true });
|
fs.mkdirSync(DOSSIER, { recursive: true });
|
||||||
if (!fs.existsSync(f)) {
|
if (!fs.existsSync(f)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user