Arduino CLI — port série après téléversement
Cette page documente l’incident où les capteurs et les relais devenaient indisponibles après un téléversement de firmware.
Symptômes
Après un téléversement réussi :
- l’interface affichait les modules comme indisponibles ;
- l’API du node signalait parfois
serial port reserved; - le microcontrôleur pouvait être visible dans
/dev/ttyACM0, mais aucune métrique n’était publiée ; - un nouveau téléversement pouvait échouer alors qu’aucun processus
arduino-cliouavrdudene tournait.
Le téléversement lui-même pouvait pourtant terminer correctement. Le problème se produisait pendant la reprise du service node.
Cause racine
Deux composants ouvrent le même port série, à des moments différents :
leukos-nodeouvre le port pour les heartbeats, les mesures et les commandes ;- le wrapper
/usr/bin/leukos-arduino-cliarrête le node avantavrdude; - le wrapper ouvre le descripteur
9et prend un verrouflocksur le port ; arduino-clitéléverse le firmware ;- le wrapper doit fermer le descripteur
9avant de relancer le node.
Le défaut venait de l’ordre de reprise : le node pouvait être relancé alors que le
wrapper détenait encore le descripteur de verrou. Le nouveau node échouait alors à
ouvrir le port et exposait serial port reserved.
Un second problème aggravant était le PID stale de /var/run/leukos-node.pid.
Le script d’arrêt pouvait considérer que le service était arrêté alors qu’un ancien
processus leukos-node --config /etc/leukos/node.json détenait toujours le port.
Cycle correct
Le cycle doit être strictement séquentiel :
node actif
-> arrêter le vrai processus node
-> attendre sa disparition complète
-> ouvrir le port et prendre flock
-> compiler si nécessaire
-> téléverser avec Arduino CLI
-> fermer le descripteur flock
-> supprimer le PID stale
-> redémarrer le node
-> attendre /status avec connected=true
-> rendre l’opération terminée dans l’UI
Le port série ne doit jamais être partagé simultanément entre le bridge et avrdude.
Fichiers concernés
| Fichier | Responsabilité |
|---|---|
nodes/os/board/leukos/rpi3plus/rootfs-overlay/usr/bin/leukos-arduino-cli | Verrou, arrêt du node, téléversement et reprise. |
nodes/os/board/leukos/rpi3plus/rootfs-overlay/etc/init.d/S90leukos-node | Arrêt robuste du vrai processus node et nettoyage du PID stale. |
nodes/agent/src/device_bridge.cpp | Ouverture, verrouillage, lecture et fermeture du port côté daemon. |
nodes/ui/src/config/domotics_config.cpp | Génération, compilation, lancement du wrapper et suivi du job. |
nodes/ui/qml/pages/domotics/RoomDevicesPage.qml | Association métrique/module et fin de l’état pending après commande. |
nodes/ui/src/domotics/firmware_generator.cpp | Firmware généré, parsing des commandes SET et rapports de métriques. |
Règles du wrapper upload
Le wrapper doit :
- prendre un verrou de processus pour empêcher deux uploads concurrents ;
- arrêter le node avant d’ouvrir le port ;
- attendre l’absence de tout processus
leukos-node --config; - conserver le verrou pendant toute l’opération
avrdude; - fermer explicitement le descripteur
9avantresume_node; - libérer le verrou de processus sur tous les chemins de sortie ;
- relancer le node avec un vrai
restart; - attendre une réponse
/statusavec"connected":true; - échouer explicitement si le node ne revient pas.
Extrait important :
release_serial_lock() {
if [ "$serial_lock_open" -eq 1 ]; then
exec 9>&-
serial_lock_open=0
fi
}
trap 'release_serial_lock; resume_node; release_upload_lock' EXIT
Fermer le verrou après le redémarrage est trop tard : le node doit être relancé seulement après la fermeture du descripteur.
Règles du script node
S90leukos-node stop ne doit pas se fier uniquement au PID file. Il doit aussi
rechercher le vrai processus et le terminer :
for pid in $(ps | awk '/[l]eukos-node --config/ {print $1}'); do
kill "$pid" 2>/dev/null || true
done
sleep 1
for pid in $(ps | awk '/[l]eukos-node --config/ {print $1}'); do
kill -9 "$pid" 2>/dev/null || true
done
rm -f /var/run/leukos-node.pid
Cette étape évite qu’un ancien descripteur série reste ouvert après une interruption ou un timeout.
Conservation des modules
Un téléversement remplace tout le programme du microcontrôleur. Le groupe envoyé doit contenir tous les modules à conserver.
Exemple pour la salle de bain :
m_5f51407eb172 Détecteur de présence D8
m_2cb4831694f0 Plafonnier D6
Téléverser uniquement le plafonnier efface le détecteur du firmware. Le node ne peut pas afficher une métrique absente du programme installé.
Le générateur doit donc produire une seule liste modules[] complète et publier une
métrique pour chaque module dans report().
Vérification après téléversement
Sur le nano-ordinateur :
ps | grep -E 'leukos-node|arduino-cli|avrdude' | grep -v grep
curl http://127.0.0.1:8080/status
La réponse attendue contient :
arduino_bathroom connected=true
m_2cb4831694f0
m_5f51407eb172
last_error=""
Si le port est réservé :
for pid in $(ps | awk '/[l]eukos-node --config/ {print $1}'); do
kill -9 "$pid"
done
rm -f /var/run/leukos-node.pid
/etc/init.d/S90leukos-node start
Cette récupération est un diagnostic de secours. Le wrapper doit normalement rendre cette intervention inutile.
Validation attendue
Un test complet doit vérifier, dans cet ordre :
- compilation du firmware avec tous les modules du groupe ;
- téléversement à 100 % ;
- fermeture du verrou et du descripteur série ;
- retour du node avec
connected=true; - présence de toutes les métriques ;
- commande
SETON puis OFF du relais ; - absence de timeout dans l’UI.
Exemple de commande API :
curl -X POST \
http://127.0.0.1:8080/module/arduino_bathroom/m_2cb4831694f0/on
Réponse attendue :
{"ok":true}
La valeur réelle doit ensuite être confirmée par /status, et non uniquement par la
réponse immédiate de la commande.