Utiliser une des LED d’un Raspberry Pi comme témoin d’enregistrement TV

Posté par  (site web personnel) . Édité par Davy Defaud, tisaac, bubar🦥, Benoît Sibaud et Ysabeau 🧶 🧦. Modéré par Davy Defaud. Licence CC By‑SA.
24
20
mai
2020
Audiovisuel

Utilisant un Raspberry Pi comme enregistreur TV-TNT via un adaptateur DVB‑T et Tvheadend, il m’est déjà arrivé de flinguer un enregistrement (en fait introduire un saut temporel dans l’enregistrement) :

  • parce que lors d’une manipulation ou d’un test, j’avais besoin de redémarrer le Pi et que j’ai quelque peu oublié l’enregistrement en cours ;
  • parce qu’en trifouillant des branchements derrière la TV, j’ai eu besoin d’éteindre le Pi et que je l’ai débranché de nouveau en oubliant l’enregistrement en cours.

Même si cela n’arrive pas si souvent, j’ai songé à une solution de reconversion des DEL/LED afin d’éviter cela. Je vous explique dans cette dépêche comment je m’y suis pris.

Sommaire

La solution envisagée

Pour prévenir les redémarrages à distance, on peut imaginer une modification de /etc/motd* ou /etc/issue qui prévienne : « ATTENTION, enregistrement en cours ! » Mais pour les débranchements de câbles, on va utiliser les LED afin de prévenir les personnes aux alentours qu’un enregistrement est en cours.

Par défaut, la LED rouge est allumée (témoin power/PWR), la LED verte, elle, s’allume lorsquNil y a de l’activité sur la carte SD (lecture ou écriture). Il est possible de les éteindre ou de les utiliser pour tout autre chose. C’est ce qu’on va faire ici avec les moyens du bord : utiliser une des LED comme témoin d’enregistrement en cours. Pour ma part, en temps normal, je les désactive au démarrage.

Voir l’état des LED

L’état des LED est dans ces répertoires virtuels (kernel filesystem) :

  • /sys/class/leds/led0/ : verte ;
  • /sys/class/leds/led1/ : rouge.

Ces répertoires contiennent, entre autres, ces deux fichiers :

  • brightness : intensité de la LED (0 pour éteinte, toute autre valeur jusque 255 pour allumée) ;
  • trigger : déclencheur, quand et pourquoi la LED s’allume.
# cat /sys/class/leds/led0/brightness
0

# cat /sys/class/leds/led0/trigger
none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlight gpio cpu cpu0 cpu1 cpu2 cpu3 default-on input panic mmc1 [mmc0] rfkill-any rfkill-none rfkill0

# cat /sys/class/leds/led1/brightness
255

# cat /sys/class/leds/led1/trigger
none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlight gpio cpu cpu0 cpu1 cpu2 cpu3 default-on [input] panic mmc1 mmc0 rfkill-any rfkill-none rfkill0

Les triggers sont entre crochets [mmc0] pour led0 (verte) et [input] pour led1 (rouge). On a donc, la LED verte (led0) qui s’allume en cas d’activité sur la carte SD (mmc0) et la LED rouge allumée lorsqu’il y a de l’alimentation électrique (input).

Éteindre et allumer les LED

Ces fichiers virtuels sont lisibles par tous et modifiables par root uniquement :

-rw-r--r-- 1 root root 4096 avril 24 10:54 /sys/class/leds/led0/brightness
-rw-r--r-- 1 root root 4096 avril 24 15:55 /sys/class/leds/led0/trigger
-rw-r--r-- 1 root root 4096 avril 24 10:54 /sys/class/leds/led1/brightness
-rw-r--r-- 1 root root 4096 avril 24 16:03 /sys/class/leds/led1/trigger

Les commandes suivantes devront donc être exécutées en tant que root.

Extinction via trigger none

Éteindre la LED verte (led0) :
echo none > /sys/class/leds/led0/trigger

Éteindre la LED rouge (led1) :
echo none > /sys/class/leds/led1/trigger

Pourquoi je touche aux fichiers trigger et pas aux fichiers brightness ? Car si le trigger reste mmc0 pour la LED verte, placer un 0 dans /sys/class/leds/led0/brightness va juste éteindre la LED si elle était allumée à cet instant précis, puis la prochaine activité sur mmc0 va la faire clignoter encore.

Allumage permanent via trigger default-on

Allumer la LED verte (led0) :
echo default-on > /sys/class/leds/led0/trigger

Le contenu du fichier brightness correspondant passe à 255 :

# cat /sys/class/leds/led0/brightness
255

Note : placer toute autre valeur que 0 dans brightness allume la LED, mais il n’est pas possible de modifier son intensité.

Faire clignoter une LED

Maintenant qu’on maîtrise l’allumage et l’extinction, faisons clignoter la LED verte.
Un clignotement, c’est :

  1. j’allume la LED verte (j’écris default-on dans le trigger) ;
  2. j’attends une seconde ;
  3. j’éteins la LED verte (j’écris none dans le trigger) ;
  4. j’attends une seconde.

Et je recommence tout ça à l’infini.

On peut le faire via le shell, toujours en tant que root :

while true
do
    echo default-on > /sys/class/leds/led0/trigger
    sleep 1
    echo none > /sys/class/leds/led0/trigger
    sleep 1
done

Ctrl + C pour arrêter le script.

On peut même imaginer d’autres séquences invoquant les deux LED pour faire un chenillard rapide :

while true
do
    echo default-on > /sys/class/leds/led0/trigger
    sleep 0.1
    echo default-on > /sys/class/leds/led1/trigger
    sleep 0.1
    echo none > /sys/class/leds/led0/trigger
    sleep 0.1
    echo none > /sys/class/leds/led1/trigger
    sleep 0.1
done

Ou allumer les LED alternativement à cinq secondes d’intervalle :

while true
do
    echo default-on > /sys/class/leds/led0/trigger
    sleep 5
    echo default-on > /sys/class/leds/led1/trigger
    echo none > /sys/class/leds/led0/trigger
    sleep 5
    echo none > /sys/class/leds/led1/trigger
done

Note — La ou les LED restent dans l’état où elles sont lorsque l’on interrompt le script. Il va nous falloir sauvegarder leur état initial au début du script pour pouvoir restaurer cet état à la fin du script.

Sauvegarder l’état des LED

Afin de nettoyer après notre passage, il sera nécessaire de sauvegarder l’état des LED avant d’y toucher pour les restaurer à la sortie de notre script final.

On peut le faire comme ça : pour chaque LED (0 et 1), lire l’état actuel des fichiers virtuels trigger et brightness, stocker ces états dans un emplacement temporaire.

Restaurer l’état des LED

Pour chaque LED (0 et 1), écrire les triggers avec les infos temporaires précédemment enregistrées.

Autoriser un utilisateur standard à modifier l’état des LED

Jusqu’ici, nos expérimentations étaient exécutées en tant que root pour pouvoir modifier les fichiers /sys directement. Par la suite, nous allons vouloir lancer ce script en tant que l’utilisateur qui effectue les enregistrements ; avec Tvheadend, c’est l’utilisateur hts:hts. Quelles sont les possibilités pour arriver à faire ça en tant qu’utilisateur, leurs inconvénients s’il y en a :

  • lancer le script avec sudo ;
  • changer les permissions des fichiers brightness et trigger au démarrage avec un script appelé via systemd.

J’avais commencé avec la méthode sudo, même si notre script est au final inoffensif, psychologiquement, la méthode ne m’emballait pas. J’ai préféré par la suite modifier les permissions des fichiers virtuels afin que les membres du groupe hts (donc l’utilisateur hts de Tvheadend) puissent les modifier.

Pour chaque fichier (brightness et trigger), changer le propriétaire vers le groupe hts et autoriser le groupe à modifier les fichiers. En shell, ça donne un script ledpermissions à placer dans /usr/local/sbin (car il sera exécuté par root) :

#!/bin/bash
chown :hts /sys/class/leds/led*/{brightness,trigger}
chmod g+w /sys/class/leds/led*/{brightness,trigger}

On le rend exécutable :
chmod +x /usr/local/sbin/ledpermissions

Accompagné par une fichier Unit ledpermissions.service pour systemd qui appellera ce script au démarrage du système :

[Unit]
Description=Set leds permissions

[Service]
Type=oneshot
User=root
ExecStart=/usr/local/sbin/ledpermissions

[Install]
WantedBy=multi-user.target

On vérifie son fonctionnement en affichant les permissions des fichiers avant et après lancement :

$ ls -l /sys/class/leds/led*/{brightness,trigger}`
-rw-r--r-- 1 root root 4096 avril 23 17:40 /sys/class/leds/led0/brightness
-rw-r--r-- 1 root root 4096 avril 23 17:40 /sys/class/leds/led0/trigger
-rw-r--r-- 1 root root 4096 avril 23 17:40 /sys/class/leds/led1/brightness
-rw-r--r-- 1 root root 4096 avril 23 17:40 /sys/class/leds/led1/trigger

Groupe : root, permissions du groupe : r--.

# systemctl start ledpermissions.service
# ls -l /sys/class/leds/led*/{brightness,trigger}
-rw-rw-r-- 1 root hts 4096 avril 23 17:40 /sys/class/leds/led0/brightness
-rw-rw-r-- 1 root hts 4096 avril 23 17:40 /sys/class/leds/led0/trigger
-rw-rw-r-- 1 root hts 4096 avril 23 17:40 /sys/class/leds/led1/brightness
-rw-rw-r-- 1 root hts 4096 avril 23 17:40 /sys/class/leds/led1/trigger

Groupe : hts, permissions du groupe : rw-.

Et l’on active le service pour le prochain démarrage du système :
systemctl enable ledpermissions.service

Créer un script « recording »

Ce que je veux pour ce script :

  • qu’il puisse être lancé par l’utilisateur hts (OK si les permissions des LED sont modifiées) ;
  • qu’il ne se plante pas si on le lance plusieurs fois de suite tout en ne laissant qu’une seule instance s’exécuter ;
  • qu’il puisse être appelé avec start ou stop pour démarrer ou arrêter le clignotement et restaurer l’état initial de la LED ;
  • qu’il fasse clignoter la LED verte comme ça : deux secondes allumée, une seconde éteinte, etc.

Son déroulement :

  • vérifier la syntaxe, recording start ou recording stop, ignorer les paramètres supplémentaires, et en cas d’erreur de syntaxe, afficher la syntaxe correcte et sortir en erreur ;
  • si l’argument est « start », vérifier que le script n’est pas déjà en cours d’exécution via un fichier PID, si ce n’est pas le cas, écrire notre PID dans ce fichier, écrire dans un fichier journal la date de début d’enregistrement et appeler la boucle de clignotement ;
  • boucle de clignotement :
    • sauvegarde dans deux variables des valeurs initiales de brightness et trigger de la LED verte (0),
    • se préparer à mourir « proprement » si l’on reçoit Ctrl + C ou un autre signal, en appelant si ça arrive une fonction cleanup qui restaurera les valeurs initiales de brightness et trigger de la LED verte (0),
    • clignoter selon notre volonté.

/usr/local/bin/recording :

#!/bin/bash

# define PID file
PID_FILE="/run/shm/$(basename $0)"

# define which LED we will use
LED_PATH="/sys/class/leds/led0"

# Define blinking delays in seconds
ON_DELAY=2.0
OFF_DELAY=1.0

do_start() {
# are we already running ?
if [ -f $PID_FILE ]
then
    printf "Already running or badly terminated !\n"
    exit 1
else
    echo $$ > $PID_FILE
    echo [$(date '+%F %H:%M:%S')] start >> ~/recording.log
    blink_loop
fi
}

cleanup() {
    # Restore initial values (BRIGHTNESS and TRIGGER) of the led
    echo $LED_INITIAL_BRIGHTNESS > $LED_PATH/brightness
    echo $LED_INITIAL_TRIGGER > $LED_PATH/trigger
    # Remove pid file if present
    [ -f $PID_FILE ] && rm $PID_FILE
    exit 0
}

blink_loop() {
# Get initial values (BRIGHTNESS and TRIGGER) of the red led to restore it at
# exit time
LED_INITIAL_BRIGHTNESS=$(cat $LED_PATH/brightness)
LED_INITIAL_TRIGGER=$(sed 's/.*\[\(.*\)\].*//' < $LED_PATH/trigger)

trap 'cleanup' EXIT HUP INT QUIT TERM

while true
do
    echo default-on > $LED_PATH/trigger
    sleep $ON_DELAY
    echo none > $LED_PATH/trigger
    sleep $OFF_DELAY
done
}

do_stop() {
    echo [$(date '+%F %H:%M:%S')] stop >> ~/recording.log
    # kill the process otherwise, previous led states are unknown unless writed
    # to a file before blink loop
    pkill -TERM -x $(basename $0)
}

print_syntax() {
    printf "Syntax : $(basename $0) <start|stop>\n"
}

if [ "$#" -ge "1" ]
then
    case ${1,,} in
        start)
            do_start
        ;;
        stop)
            do_stop
        ;;
        *)
            print_syntax
            exit 1
        ;;
    esac
else
    print_syntax
    exit 1
fi

Le rendre exécutable :
chmod +x /usr/local/bin/recording

Tester

recording start doit faire clignoter la LED comme convenu, et Ctrl +  C doit arrêter le script et restaurer l’état initial de la LED utilisée.

Depuis un autre terminal avec le même utilisateur, recording stop doit arrêter le script et restaurer l’état initial de la LED utilisée.

Appeler le script en début et fin d’enregistrement

Via l’interface Web de Tvheadend, dans la configuration des profils d’enregistrement :

Configuration > Recording > Digital Video Recorder Profiles
Choisir le profile DVR behavior
Pre-processor command: /usr/local/bin/recording start
Post-processor command: /usr/local/bin/recording stop
Save

Seulement entrer ça dans les deux champs concernés ne fonctionne pas ; il ne se passe rien.
Après tatonnements, j’en suis arrivé à la conclusion que les commandes entrées dans ces champs n’acceptent pas d’arguments ?!

On contourne donc en créant deux scripts distincts recording-start et recording-stop qui vont appeler le script recording avec les bons arguments :

  1. /usr/local/bin/recording-start :

    #!/bin/bash
    /usr/local/bin/recording start
  2. /usr/local/bin/recording-stop :

    #!/bin/bash
    /usr/local/bin/recording stop

Les rendre exécutables :
chmod +x /usr/local/bin/recording-*

Configuration > Recording > Digital Video Recorder Profiles
Choisir le profile DVR behavior
Pre-processor command: /usr/local/bin/recording-start
Post-processor command: /usr/local/bin/recording-stop
Save

Tests

Enregistrez avec Tvheadend, Ça fonctionne correctement ? Well done! Sinon, il doit manquer une étape quelque part.

Dépannage

Au détour d’un site parlant de Pi, j’ai trouvé ces paramètres à placer dans /boot/config.txt pour controler les LED :

dtparam=act_led_trigger=none
dtparam=act_led_activelow=off
dtparam=pwr_led_trigger=none
dtparam=pwr_led_activelow=off

Seulement, au deuxième redémarrage, la LED rouge est restée allumée ! La méthode via script de démarrage et systemd ci‑dessus fonctionne mieux.

Références

Voir cet article « chez moi » : https://www.sekoya.org/#!blog/raspberrypi-tvheadend-recording-led.md.

Aller plus loin

  • # Perte d'infos ?

    Posté par  . Évalué à 3.

    Très intéressant, ton script. Moi qui envisage de me mettre à Raspberry, je suis toujours amusé/étonné de voir la multitude d'usages qu'on peut en faire.

    Une question, cependant. Quand tu écris :

    Éteindre la LED verte (led0) :
    echo none > /sys/class/leds/led0/trigger

    Tu ne perds pas la liste des triggers disponibles, en faisant ça ?

    • [^] # Re: Perte d'infos ?

      Posté par  (site web personnel, Mastodon) . Évalué à 10.

      C'est parce que tu penses que ce sont des fichiers textes. Beaucoup de fichiers dans Linux ne sont pas des données stockées. Ce sont en fait des interfaces pour communiquer avec le noyau (une API). Tu as peut-être entendu parler de la fameuse philosophie "tout est fichier" avec Linux; cela ne veut pas dire que les logiciels ont des fichiers de configuration en texte ou quelque chose du genre comme certains croient souvent. C'est que les intéractions peuvent se faire aussi simplement que de la manipulation de fichiers. Mais cela reste des interfaces et ce qu'on y écrit peut être différent de ce qu'on y lit. Dans le cas de ce fichier trigger dans l'API "leds", c'est l'équivalent de 2 functions: en lisant le contenu, c'est une fonction de listing des options et aussi qui indique quelle est l'option actuellement sélectionnée; en écriture, c'est une fonction de sélection d'option.

      Petite démo, si je regarde mon ordinateur, qui a un clavier rétro-éclairable:

      $ cat /sys/class/leds/tpacpi::kbd_backlight/trigger
      [none] usb-gadget usb-host rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock AC-online BAT0-charging-or-full BAT0-charging BAT0-full BAT0-charging-blink-full-solid disk-activity disk-read disk-write ide-disk mtd nand-disk panic wacom_battery_0-online rfkill-any rfkill-none audio-mute audio-micmute rfkill0 rfkill1 rfkill2 bluetooth-power hci0-power rfkill3 phy0rx phy0tx phy0assoc phy0radio rfkill4
      $ echo audio-mute >| /sys/class/leds/tpacpi::kbd_backlight/trigger
      $ cat /sys/class/leds/tpacpi::kbd_backlight/trigger
      none usb-gadget usb-host rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock AC-online BAT0-charging-or-full BAT0-charging BAT0-full BAT0-charging-blink-full-solid disk-activity disk-read disk-write ide-disk mtd nand-disk panic wacom_battery_0-online rfkill-any rfkill-none [audio-mute] audio-micmute rfkill0 rfkill1 rfkill2 bluetooth-power hci0-power rfkill3 phy0rx phy0tx phy0assoc phy0radio rfkill4

      Comme tu le vois, quand je lis à nouveau le fichier, il affiche toujours la liste des triggers, par contre maintenant les crochets sont autour de [audio-mute] pour indiquer que c'est l'option sélectionnée. Et maintenant mon clavier s'éclaire quand je coupe le son, ce qui bien entendu ne me sert à rien et je vais donc m'empresser d'annuler cela. Ahahah. 🤣

      En cherchant sur le web, tu peux aisément trouver les diverses interfaces fichiers pour communiquer avec le noyau. Pour l'interface LED par exemple: https://www.kernel.org/doc/Documentation/leds/leds-class.txt

      En dehors de /sys/, Il existe de nombreuses autres interfaces sous Linux, par exemple dans /proc/, tu as notamment l'ensemble des processus qui tournent (si tu veux faire un logiciel comme top ou ps, c'est à ça que tu dois t'intéresser), ou /dev/ pour les périphériques, etc.

      Film d'animation libre en CC by-sa/Art Libre, fait avec GIMP et autre logiciels libres: ZeMarmot [ http://film.zemarmot.net ]

      • [^] # Re: Perte d'infos ?

        Posté par  . Évalué à 6.

        Pour en ajouter une couche :)

        Le paradigme "tout est fichier" commun aux unix est une base sur la quelle les différents unix ce sont construit.

        La base c'est de considérer toutes les entrées/sorties des programmes comme des fichiers (donc la sortie standard aussi, les pipes, les sockets,…).

        Du coup je suis allé vérifier les pseudo systèmes de fichiers sont apparu dans unix himself et là il s'agit d’interagir avec un programme au travers d'un système de fichier. On utilise le fait de voir des dossiers/fichiers et lire le contenu d'un fichier comme les sorties d'un programme et le déplacement dans un dossier, la création de dossier/fichier et l'écriture dans un fichier comme les entrées du programme.

        L'intérêt est évident pour les programme qui ne peuvent que difficilement avoir des interfaces comme le noyau, mais c'est aussi très intéressant car les gestionnaires de fichiers et éditeur de texte peuvent naturellement interagir avec ce type de programme.

        Sur linux tu trouvera 3 pseudo systèmes de fichiers classiques : udev (monté sur /dev et qui représente le hardware de la machine), procfs (monté sur /proc qui représente tous les processus de la machine (c'est une mine d'or d'aller s'y balader quand on ne connait pas)) et cgroup de plus en plus qui représente des limitations et quotas données aux processus. Mais fuse en possède pleins pour faire pleins de choses rigolotes : liste de filesystem de fuse.

        Dernier point hurd avait un concept de translator pour faire ça qui a l'air plutôt cool.

        https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

  • # Heartbeat

    Posté par  . Évalué à 2.

    Très intéressant usecase !

    Au lieu de faire une boucle on-off pour le clignotement, as-tu essayé le mode [heartbeat] ?

    Il est spécialement conçu pour faire clignoter la led. Je l'utilise sur mon BananaPi (pas testé sur RasberryPi mais ça doit être pareil)

    • [^] # Re: Heartbeat

      Posté par  . Évalué à 2.

      Avec heartbeat la led clignote en fonction de la charge cpu.

      Il y a le trigger timer qui permet de configurer une durée (en ms) pour chaque état :

      modprobe ledtrig-timer
      cd /sys/class/leds/la_led_qui_va_bien
      echo timer > trigger
      echo 250 > delay_on
      echo 750 > delay_off

  • # Udev rules

    Posté par  (site web personnel) . Évalué à 0.

    Hello,
    un classique pour modifier les permissions est d'utiliser une règle personnalisée udev.

    La première ligne c'est pour le prochain redémarrage, la deuxième pour le test.

    ACTION=="add", SUBSYSTEM=="leds", RUN+="/bin/sh -c 'chown :plugdev /sys$devpath/trigger /sys$devpath/brightness && chmod g+w /sys$devpath/trigger /sys$devpath/brightness'"
    ACTION=="change", SUBSYSTEM=="leds", RUN+="/bin/sh -c 'chown :plugdev /sys$devpath/trigger /sys$devpath/brightness && chmod g+w /sys$devpath/trigger /sys$devpath/brightness'"
    Et pour tester :

    udevadm control --reload && udevadm trigger /sys/devices/platform/leds
    Comme ça on évite tout systemd :)

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.