SystemD - démarrer une application python automatiquement au démarrage du Pi

Mr Orlans partage avec nous ses découvertes Raspberry. Il est vrai que l'apparition de systemD à déjà fait grincé des dents (voir aussi cet article). J'y ai trouvé des informations intéressante comme:
  • le stockage en mémoire (/dev/shm = shared memory)
  • le lancement d'un sous processus
  • l'ajout de message dans les logs systèmes.
Un tout grand merci :-).

Application python démarrée automatiquement quand on allume le Raspberry Pi
La situation du démarrage automatique d’applications a changé avec l’utilisation de systemd comme initialiseur système en Raspbian (eg: Strech), donc j’ai du implémenter une nouvelle procédure.


Le but, dans cet article, est de systématiquement démarrer une application lorsque l'on met le Pi sous tension.
L’exemple choisi, que j’utilise pratiquement depuis des années avec l'ancien processus d'initialisation, est le démarrage automatique d'une boucle de capture PiCam.

Que fait Raspbian au démarrage ? 
Après avoir initialisé le kernel (voir /var/log/kern.log à partir de la dernière ligne Booting Linux on physical CPU... )

Les différents services sont initialisés en séquence. On trouvera la trace dans /var/log/syslog quelques lignes avant la ligne Started Create Static Device Nodes in /dev

Nous pouvons suivre la progression en consultant les lignes émises par systemd[1] commençant par Started... et Reached target .

C’est par rapport à ces targets que nous situerons le moment auquel systemd démarrera notre application.

Créer un service en python
systemd utilise des fichiers xyz.service pour définir les différents services à démarrer.
Nous appellerons le notre camera_boot_start.service et il contiendra:
[Unit]
Description=python camera application
startup at boot
[Service]
WorkingDirectory=/home/pi/cam
ExecStart=/usr/bin/python3 /home/pi/cam/camera_boot_start.py
User=root
Type=forking
[Install]
Wants=multi-user.target

Vous constaterez que le répertoire de démarrage (WorkingDirectory) est celui où se trouve le script "camera_boot_start.py" qui démarre le programme final (un script python camera_loop.py qui contient une boucle infinie).

La clause Wants= met notre service près de la fin du processus d’initialisation.

Installer le service sur le système:
Pour que systemd démarre notre service, il faut copier le fichier dans /etc/systemd/system

sudo cp camera_boot_start.service /etc/systemd/system/camera_boot_start.service
sudo chown root:root /etc/systemd/system/camera_boot_start.service
sudo chmod 666 /etc/systemd/system/camera_boot_start.service

et on demandera à systemd d'activer le service avec:

sudo systemctl enable camera_boot_start.service

Cette commande placera notre service dans la chaîne d’initialisation (disable pour désactiver le service au prochain boot).

La commande suivante permet de consulter le statut du service:

service camera_boot_start status

Voyez le contenu de syslog en cas de problème.

Le service peut être interrompu avec:

sudo service camera_boot_start stop

Plus d'information:
Le script de démarrage
Le fichier .service lance le script camera_boot_start.py dont la principale tâche sera le démarrage du programme principale (camera_loop.py en l'occurence).

Voici donc le contenu de camera_boot_start.py :

import os, sys, subprocess, syslog
#---------------------------
def Start():
    try:
        syslog.syslog(syslog.LOG_CRIT, 'camera_boot_start cwd=%s pid=%d user=%d' \
               % (os.getcwd(), os.getpid(), os.geteuid()))
        call_args = [sys.executable, '/home/pi/cam/camera_loop.py']
        app = subprocess.Popen(call_args, cwd=os.getcwd())
        syslog.syslog(syslog.LOG_CRIT, \
              'camera_boot_start launched pid=%d' % app.pid)
    except Exception as e:
        syslog.syslog(syslog.LOG_ERR, \
              'camera_boot_start Exception %s' % str(e))
#--------------------------
if __name__ == '__main__':
    Start()

Le script ne fait qu'invoquer l'application (camera_loop.py) comme un sous-processus puis rend la main directement.

Les appels à syslog sont facultatif mais permettent de tracer l'application dans le fichier de log système /var/log/syslog 

Application de capture photo
Comme décrit ci-dessus, le service et script camera_boot_start.py démarre le programme de capture d'image qui s'avère être camera_loop.py .

Sans rentrer dans les détails, camera_loop.py est un script qui stocke des photos "petit format" capturées en continu par une caméra Pi.
Afin de ne pas endommager la carte SD, ces images sont stockées dans un système de fichier en mémoire disponible sous le répertoire /dev/shm .

Le nombre de photos est limité en fonction de la mémoire réservée au système de fichier /dev/shm.

En pratique on prend environ 10 photos par seconde (en lumière de jour) et on conserve les 600 dernières photos. En faible lumière, on peut avoir des poses jusqu’à 6 secondes.

Un fichier de configuration
En utilisant un dictionnaire de paramètre (json ou pickle) il est possible de couvrir facilement les différentes possibilités de la caméra: résolution, sensibilité, rotation, vitesse, photo ou vidéo, paramètres de prise de vue, nom de fichier, etc.

Le fichier de configuration peut également prévoir un paramétrage de sauvegarde permanent vers un répertoire de la carte SD.

Distribution des photos
Pratiquement, il est possible de démarrer un serveur TCP/IP acceptant des requêtes réseau et renvoyant la dernière capture.
Un tel serveur peut être réaliser avec Flask (un PHP like écrit en Python) ou avec la classe Python SimpleHTTPServer: