Releve cellules : port stable par numero de serie (/dev/pylontech-pack2)

Rebranche pendant que le conteneur tenait ttyUSB0, le cable est revenu en
ttyUSB1 : la garde cree un lien d'apres le numero de serie et recree le
conteneur quand le port change de numero.

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:
Tallagrand Thierry
2026-09-25 16:53:27 +02:00
parent 5ba52023fd
commit 7f97e8a85d
3 changed files with 68 additions and 24 deletions

View File

@@ -95,9 +95,15 @@ eux, couvrent tout le parc (agregat du Cerbo).
Debrancher puis rebrancher le cable recree `/dev/ttyUSB0` accessible a root
seul : le conteneur (uid 1026) meurt (« EIO: i/o error ») et ne repart pas.
`surveiller-port.sh` corrige cela toutes les 5 minutes, en root : pilotes
recharges si absents, droits du port rouverts, conteneur relance s'il est
arrete. Il ne fait rien si le cable est debranche, et ne journalise (tag
Et le NUMERO du port change : rebranche pendant que le conteneur tenait
`ttyUSB0` ouvert, le cable est revenu en `ttyUSB1` (25/09). `surveiller-port.sh`
corrige les deux toutes les 5 minutes, en root : pilotes recharges si absents,
lien stable **`/dev/pylontech-pack2`** cree d'apres le NUMERO DE SERIE du cable
(`A78KC4LE`, jamais l'ordre d'enumeration), droits rouverts, conteneur RECREE
si le port a change de numero (Docker resout le lien a la creation), relance
s'il est simplement arrete. `compose.yaml` mappe `/dev/pylontech-pack2` sur
`/dev/ttyUSB0` dans le conteneur. Un 2e cable FTDI s'ajoutera a la liste
`CABLES` du script, avec son propre service. Il ne fait rien si le cable est debranche, et ne journalise (tag
`evcs-releve`, `journalctl -t evcs-releve`) que lorsqu'il agit.
Pose en timer systemd (le Planificateur DSM n'a pas d'ajout en ligne de

View File

@@ -31,8 +31,13 @@ services:
# Le depot est monte sur le .17 depuis le .22 :
# 10.10.10.22:/volume2/projects -> /volume1/homes/TMZF5455/projects
- /volume1/homes/TMZF5455/projects/EVCS/EVCS:/depot
# Nom STABLE cote hote (25/09) : le noyau numerote les ports a
# l'enumeration, et un rebranchement pendant que le port est tenu ouvert
# donne ttyUSB1 au lieu de ttyUSB0. surveiller-port.sh cree
# /dev/pylontech-pack2 d'apres le NUMERO DE SERIE du cable. Dans le
# conteneur le port reste /dev/ttyUSB0 : rien d'autre ne change.
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
- /dev/pylontech-pack2:/dev/ttyUSB0
# ⚠️ Le pack 2 compte CINQ US2000 (adresses 1..5). Verifier cette liste
# apres toute intervention physique sur la chaine.
command:

View File

@@ -2,19 +2,25 @@
# Garde du releve des cellules sur le Synology .17 (ARCHIVE3). A LANCER EN ROOT,
# toutes les 5 minutes (timer systemd evcs-releve-port.timer, voir README).
#
# Pourquoi (25/09/2026) : debrancher puis rebrancher le cable FTDI recree
# /dev/ttyUSB0 en crw------- root. Le conteneur, qui tourne en uid 1026, ne
# peut plus l'ouvrir : il meurt (« EIO: i/o error ») et ne repart pas seul.
# charger-pilotes.sh ne tourne qu'au demarrage du NAS, donc sans cette garde
# chaque debranchement coupait le releve jusqu'a une intervention manuelle.
# Deux defauts qu'elle corrige, tous deux vus le 25/09/2026 :
# 1. Un rebranchement recree le port accessible a root seul : le conteneur
# (uid 1026) meurt (« EIO: i/o error ») et ne repart pas.
# 2. Le NUMERO du port change : rebranche pendant que le conteneur tenait
# ttyUSB0 ouvert, le cable est revenu en ttyUSB1. Le conteneur pointait
# alors vers un port qui n'existait plus.
# D'ou un nom stable, /dev/pylontech-pack2, choisi d'apres le numero de SERIE
# du cable (jamais d'apres l'ordre d'enumeration), que le conteneur utilise.
#
# Ce script ne fait RIEN quand tout va bien, et n'ecrit une ligne dans le
# journal systeme (tag evcs-releve) que lorsqu'il agit. Idempotent.
# Ne fait RIEN quand tout va bien ; n'ecrit dans le journal systeme (tag
# evcs-releve) que lorsqu'il agit. Idempotent.
PORT=/dev/ttyUSB0
# Numero de serie du cable FTDI -> nom stable. Un 2e cable (pack 1, US5000)
# s'ajoutera ici, avec son propre service dans compose.yaml.
CABLES="A78KC4LE:pylontech-pack2"
CONTENEUR=evcs-releve-cellules
DOCKER=/usr/local/bin/docker
DIR=$(dirname "$0")
DIR=$(cd "$(dirname "$0")" && pwd)
ETAT=/run/evcs-releve-port.cible
dire() { logger -t evcs-releve "$*"; echo "$*"; }
# Pilotes decharges (ne devrait arriver qu'apres une MAJ DSM) : on les recharge.
@@ -23,19 +29,46 @@ if ! /sbin/lsmod | grep -q "^ftdi_sio "; then
/bin/sh "$DIR/charger-pilotes.sh" >/dev/null 2>&1
fi
# Cable debranche : rien a faire, le conteneur ne peut de toute facon pas
# demarrer sans son device. On attend le rebranchement.
[ -e "$PORT" ] || exit 0
# Numero de serie du peripherique USB qui porte un ttyUSBn.
serie_de() { cat "$(dirname "$(dirname "$(readlink -f "/sys/bus/usb-serial/devices/$1")")")/serial" 2>/dev/null; }
# Droits perdus au rebranchement : on les rouvre.
if [ "$(stat -c %a "$PORT")" != "666" ]; then
chmod 666 "$PORT"
dire "droits de $PORT rouverts (cable rebranche ?)"
cible=""
for entree in $CABLES; do
sn=${entree%%:*}; nom=${entree#*:}
tty=""
for t in /sys/bus/usb-serial/devices/ttyUSB*; do
[ -e "$t" ] || continue
n=$(basename "$t")
[ "$(serie_de "$n")" = "$sn" ] && tty=$n
done
if [ -z "$tty" ]; then
rm -f "/dev/$nom" # cable absent : pas de lien qui pointe dans le vide
continue
fi
if [ "$(readlink "/dev/$nom")" != "/dev/$tty" ]; then
ln -sf "/dev/$tty" "/dev/$nom"
dire "/dev/$nom -> /dev/$tty (cable $sn)"
fi
if [ "$(stat -c %a "/dev/$tty")" != "666" ]; then
chmod 666 "/dev/$tty"
dire "droits de /dev/$tty rouverts (cable rebranche ?)"
fi
[ "$nom" = pylontech-pack2 ] && cible=$tty
done
# Conteneur arrete : on le relance.
etat=$("$DOCKER" inspect -f '{{.State.Running}}' "$CONTENEUR" 2>/dev/null)
if [ "$etat" != "true" ]; then
# Cable debranche : le conteneur ne peut pas tourner sans son port. On attend.
[ -n "$cible" ] || exit 0
# Le port a change de numero depuis la derniere creation du conteneur : il faut
# le RECREER (Docker resout le lien a la creation). Sinon, simple relance s'il
# est arrete.
if [ "$(cat "$ETAT" 2>/dev/null)" != "$cible" ]; then
(cd "$DIR" && "$DOCKER" compose up -d --force-recreate >/dev/null 2>&1) \
&& { echo "$cible" > "$ETAT"; dire "$CONTENEUR recree sur /dev/$cible"; } \
|| dire "$CONTENEUR : echec de la recreation"
exit 0
fi
if [ "$("$DOCKER" inspect -f '{{.State.Running}}' "$CONTENEUR" 2>/dev/null)" != "true" ]; then
"$DOCKER" start "$CONTENEUR" >/dev/null 2>&1 \
&& dire "$CONTENEUR relance" \
|| dire "$CONTENEUR : echec de la relance"