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

Posté par  (site web personnel) . Licence CC By‑SA.
25
25
avr.
2020

Sommaire

J'utilise un Raspberry Pi comme enregistreur TV-TNT via un adaptateur DVB-T et tvheadend.

Il m'est déjà arrivé de faire ce genre de chose alors qu'un enregistrement était en cours :

  • lors d'une manipulation ou d'un test, j'ai besoin de rebooter le Pi et PAF, je viens de flinguer l'enregistrement en cours ! Ou plutôt, j'ai inséré un saut temporel dans l'enregistrement en cours.
  • Je trifouille des branchements derrière la TV et j'ai besoin de powerOff le PI, bah je le débranche et PAF, encore un saut temporel.

Bon, ok, ça n'arrive pas souvent, mais ça m'est arrivé et j'étais assez énervé pour penser à une solution à base de reconversion des LEDs qui me permette d'éviter ça !

Pour prévenir les reboot à distance, on peut imaginer une modif de /etc/motd* ou /etc/issue qui prévienne : ATTENTION, enregistrement en cours !

Pour les débranchements de câbles, on va utiliser les LEDs 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 lorsqu'il 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 LEDs comme témoin d'enregistrement en cours.

Pour ma part, en temps normal, je les désactive au démarrage.

Voir l'état des LEDs

L'état des LEDs 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 2 fichiers :

  • brightness : intensité de la LED 0 = éteinte, toute autre valeur jusque 255 = allumée.
  • trigger : déclencheur Quand / pourquoi la LED s'allume.

/sys/class/leds/led0/brightness

0
/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
/sys/class/leds/led1/brightness

255
/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 éléctrique (input).

Éteindre / allumer les LEDs

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 correpondant passe à 255
cat /sys/class/leds/led0/brightness

255

Note: placer tout 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 maitrise l'allumage et l'extinction, faisont clignoter la LED verte.

Un clignotement, c'est :

  1. j'allume la LED verte (j'écris default-on dans le trigger)
  2. j'attends 1 seconde
  3. j'éteint la LED verte (j'écris none dans le trigger)
  4. j'attends 1 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 2 LEDs 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 LEDs alternativement à 5 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/les LEDs 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 LEDs

Afin de nettoyer après notre passage, il sera nécessaire de sauvegarder l'état des LEDs 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 LEDs

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

Autoriser un utilisateur standard à modifier l'état des LEDs

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 le user hts:hts. Quelles sont les possibilités pour arriver à faire ça en tant que user, leurs inconvénients si il y en a :

  • Lancer le script avec sudo
  • Changer les permissions des fichiers brightness et trigger au boot avec un script appelé via systemd

J'avais commencé avec la méthode sudo, même si notre script est au final innofensif, 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 le user hts de Tvheadend) puissent les modifier.

Pour chaque fichier (brightness trigger), changer le propriétaire vers le groupe hts et autoriser le groupe à modifier les fichiers

En shell ça donne un script à placer dans /usr/local/sbin (car il sera exécuté par root) :

/usr/local/sbin/ledpermissions

#!/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 "Unit" systemd qui appelera ce script au boot

/etc/systemd/system/ledpermissions.service

[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 on active le service pour le prochain boot :
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 LEDs 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 le clignotement ou arrêter le clignottement et restaurer l'état initial de la LED.
  • Qu'il fasse clignoter la LED verte comme ça : 2 secondes allumée, 1 seconde éteinte, etc.

Son déroulement :

  • Vérifier la syntaxe ; recording start / recording stop. Ignorer les paramètres supplémentaires. 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'éxécution via un fichier PID, si ce n'est pas le cas, écrire notre PID dans ce fichier, écrire dans un log la date de début d'enregistrement. 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 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).
    • Clignotter selon notre volonté

/usr/local/bin/recording

#!/bin/bash

PID_FILE="/run/shm/$(basename $0)"

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

cleanup() {
    # Restore initial values (BRIGHTNESS and TRIGGER) of the red led
    echo $LED0_INITIAL_BRIGHTNESS > /sys/class/leds/led0/brightness
    echo $LED0_INITIAL_TRIGGER > /sys/class/leds/led0/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
LED0_INITIAL_BRIGHTNESS=$(cat /sys/class/leds/led0/brightness)
LED0_INITIAL_TRIGGER=$(sed 's/.*\[\(.*\)\].*/\1/' < /sys/class/leds/led0/trigger)

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

trap 'cleanup' EXIT HUP INT QUIT TERM

while true
do
    echo default-on > /sys/class/leds/led0/trigger
    sleep $ON_DELAY
    echo none > /sys/class/leds/led0/trigger
    sleep $OFF_DELAY
done
}

do_stop() {
    echo [$(date '+%Y-%m-%d %H:%M:%S')] stop >> ~/recording.log
    # kill the process otherwise, previous led states are unknown unless writed
    # to a file before blink loop
    pkill -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.
[CTRL] c doit arrêter le script et restaurer l'état initial de la LED utilisée.

recording start doit faire clignoter la LED comme convenu.
Depuis un autre terminal avec le même user, 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'enregistrements :

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 2 scripts distincts recording-start et recording-stop qui vont appeler le script recording avec les bons arguments.

/usr/local/bin/recording-start

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

/usr/local/bin/recording-stop

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

Les rendre exécutable :

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… Ça fonctionne correctement ? tant mieux, sinon, il doit manquer une étape quelque part.

Troubleshooting

Au détour d'un site parlant de PI, j'ai trouvé ces paramètres à placer dans /boot/config.txt pour controler les LEDs.

dtparam=act_led_trigger=none
dtparam=act_led_activelow=off
dtparam=pwr_led_trigger=none
dtparam=pwr_led_activelow=off
Seulement, au 2eme reboot, la LED rouge est restée allumée ! la méthode via script de démarrage / systemd ci-dessus marche mieux.

References

Raspberry Pi : https://www.raspberrypi.org
Raspberry Pi LEDs : https://mlagerberg.gitbooks.io/raspberry-pi/content/5.2-leds.html
DVB-T : https://www.linuxtv.org/wiki/index.php/DVB-T
Tvheadend : https://tvheadend.org

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

  • # Superbe !

    Posté par  . Évalué à 2.

    Je m'en sers pour remonter l'état de RPi headless, c'est très pratique aussi (notamment pour savoir quand je peux vraiment couper l'alimentation sur la RPi).

    J'aime beaucoup ton fignolage, avec l'unit systemd, chez moi c'est bêtement des scripts appelés par d"autres scripts ;)

    Didactique, professionel et pratique !

  • # rpi0 headless

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

    Sur un rpi0, j'utilise la led pour qu'elle me donne qq info sur l'état du rpi.

    Mais je désactive le trigger et utilise brightness à la place.

    # /boot/config.txt
    [...]
    dtparam=act_led_trigger=panic
    dtparam=act_led_activelow=on
    
    # en bash
    on() {
        echo 1 >/sys/class/leds/led0/brightness
    }
    
    off() {
        echo 0 >/sys/class/leds/led0/brightness
    }
    
    blink() {
        on
        sleep 0.5
        off
        sleep 0.5
    }
    
    blink_forever() {
        while true; do
            blink
            sleep ${1}s
        done
    }
    

Suivre le flux des commentaires

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