Aller au contenu principal

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-cli ou avrdude ne 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 :

  1. leukos-node ouvre le port pour les heartbeats, les mesures et les commandes ;
  2. le wrapper /usr/bin/leukos-arduino-cli arrête le node avant avrdude ;
  3. le wrapper ouvre le descripteur 9 et prend un verrou flock sur le port ;
  4. arduino-cli téléverse le firmware ;
  5. le wrapper doit fermer le descripteur 9 avant 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​

FichierResponsabilité
nodes/os/board/leukos/rpi3plus/rootfs-overlay/usr/bin/leukos-arduino-cliVerrou, arrêt du node, téléversement et reprise.
nodes/os/board/leukos/rpi3plus/rootfs-overlay/etc/init.d/S90leukos-nodeArrêt robuste du vrai processus node et nettoyage du PID stale.
nodes/agent/src/device_bridge.cppOuverture, verrouillage, lecture et fermeture du port côté daemon.
nodes/ui/src/config/domotics_config.cppGénération, compilation, lancement du wrapper et suivi du job.
nodes/ui/qml/pages/domotics/RoomDevicesPage.qmlAssociation métrique/module et fin de l’état pending après commande.
nodes/ui/src/domotics/firmware_generator.cppFirmware 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 9 avant resume_node ;
  • libérer le verrou de processus sur tous les chemins de sortie ;
  • relancer le node avec un vrai restart ;
  • attendre une réponse /status avec "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 :

  1. compilation du firmware avec tous les modules du groupe ;
  2. téléversement à 100 % ;
  3. fermeture du verrou et du descripteur série ;
  4. retour du node avec connected=true ;
  5. présence de toutes les métriques ;
  6. commande SET ON puis OFF du relais ;
  7. 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.