From 43ff6e6762a671a0498411d832a7a1d413a196a4 Mon Sep 17 00:00:00 2001 From: Sébastien Dailly Date: Mon, 15 Nov 2021 10:28:20 +0100 Subject: Raspberry pi and buildroot --- content/Informatique/2021-11-piaudio.rst | 443 +++++++++++++++++++++++++++++++ content/images/buildroot/bluetooth.png | Bin 0 -> 8997 bytes content/images/buildroot/buildroot.png | Bin 0 -> 65022 bytes content/images/buildroot/connexion.jpeg | Bin 0 -> 50794 bytes content/images/buildroot/pulseaudio.png | Bin 0 -> 25623 bytes 5 files changed, 443 insertions(+) create mode 100644 content/Informatique/2021-11-piaudio.rst create mode 100644 content/images/buildroot/bluetooth.png create mode 100644 content/images/buildroot/buildroot.png create mode 100644 content/images/buildroot/connexion.jpeg create mode 100644 content/images/buildroot/pulseaudio.png diff --git a/content/Informatique/2021-11-piaudio.rst b/content/Informatique/2021-11-piaudio.rst new file mode 100644 index 0000000..b7649a7 --- /dev/null +++ b/content/Informatique/2021-11-piaudio.rst @@ -0,0 +1,443 @@ +.. -*- mode: rst -*- +.. -*- coding: utf-8 -*- + +=========================================== +Découverte de buildroot avec un raspberrypi +=========================================== + +:date: 2021-11-15 +:summary: Comment construire un système complet pour un raspberry pi avec + Buildroot ? Présentation détaillée d’un projet permettant de + construire un serveur de son. + +.. _`configs/config`: https://git.chimrod.com/piaudio.git/tree/configs/config +.. _`build.sh`: https://git.chimrod.com/piaudio.git/tree/build.sh +.. _`post-build.sh`: https://git.chimrod.com/piaudio.git/tree/board/post-build.sh +.. _`post-image.sh`: https://git.chimrod.com/piaudio.git/tree/board/post-image.sh + +Présentation +============ + +Il y a quelque temps, j’avais monté un système audio avec un raspberrypi pi 0, +permettant de brancher la carte à ma chaine hifi et diffuser ainsi la musique +dans le salon. En équipant la carte d’un DAC de qualité, je centralise la +musique qui peut etre jouée depuis n’importe quel PC, et comme la carte est +équipée d’une connexion bluetooth, cela permet aussi d’y connecter les +smartphones. + +Dans mon `montage originel`_, je partais d’une distribution raspbian que +j’avais modifié manuellement. À la suite d’une erreur de manipulation, j’ai dû +refaire le paramétrage une seconde fois, et c’est depuis quelque chose qui me +préoccupait : il faudrait que je puisse disposer d’un système déjà configuré +qui fonctionnerait « out of the box », sans que j’aie à toucher à la +configuration. + +.. _`montage originel`: https://linuxfr.org/users/chimrod/journaux/utiliser-un-pi-zero-comme-serveur-de-son-et-lecteur-bluetooth + +J’ai découvert entre-temps buildroot_ (via le projet showmewebcam_) qui est un +ensemble de scripts permettant de construire un système complet. Il prend en +charge un grand nombre d’architectures différentes, et possède une +configuration de base pour construire les applications les plus courantes. Il +ne reste plus qu’à l’utiliser pour reconstruire un client pulseaudio sur une +carte raspberrypi 0, qui offre un service via le wifi et le bluetooth. + +.. _buildroot: https://buildroot.org/ +.. _showmewebcam: https://github.com/showmewebcam/showmewebcam/ + +Je vous propose ici de suivre le détail des modifications apportées pour +construire le système (le projet est disponible sur mon `dépôt git`_ – mais pas +l’image finale). + + +.. _`dépôt git`: https://git.chimrod.com/piaudio.git/about/ + +.. admonition:: Article avancé + :class: warning + + Je ne pensais pas qu'un jour j'écrirais ce genre d'en tête, mais il s'agit ici + d'un article avancé, qui nécessite de connaître le fonctionnement du système + linux. + + Nous allons en effet construire un système de zéro, pour un usage spécifique, + ce qui oblige à sélectionner les services dont nous avons besoin (et savoir + lesquels sélectionner), et comment les configurer pour obtenir le système + attendu. + + Aucune interface graphique ne sera installée sur la carte, et toute la + configuration devra être anticipée pour que la carte démarre dès son + branchement. + +Construction du système +======================= + +Initialisation +-------------- + +Je fournis un script `build.sh`_ qui prends en paramètres : + +1. une architecture +2. une commande buildroot (`all` pour lancer la création complète du système) + +pour l’instant, seules deux architectures ont été testées : un raspberrypi3 +(modèle A), et un raspberrypi0w : + +.. code:: bash + + ./build.sh + usage: BUILDROOT_DIR=../buildroot ./build.sh {boardname} all + boardname: raspberrypi0w, raspberrypi3 + +Lors de l’exécution de la commande, le système va fusionner le paramétrage par +défaut de la carte, qui est chargé dans la configuration de buildroot, ainsi +qu’une liste de paquets spécifiques qui sont propres à notre installation, et +listés dans le fichier `configs/config`_ + +Sélection des paquets +--------------------- + +Cette configuration par défaut inclue les paquets nécessaires pour faire +fonctionner le réseau (wifi), pulseaudio, et le bluetooth (ainsi que les +dépendances pour mettre toute cette architecture en place). Si l’on souhaite +modifier un paquet, on peut regarder ceux disponibles en lançant une interface +de sélection : + +.. code:: bash + + ./build.sh raspberrypi3 menuconfig + +.. image:: {static}/images/buildroot/buildroot.png + :alt: L’interface menuconfig + :align: center + +Toutefois, je n’aime pas cette manière de procéder : la configuration ainsi +générée se retrouve spécifique à une carte donnée, et l’on perd la possibilité +d’appliquer cette même configuration sur un matériel différent. + +Aussi, et pour être sûr d’avoir un système qui soit reproductible, je préfère +noter les paquets dont j’ai besoin, et je les reporte dans le fichier +`configs/config`_ qui sera utilisé lors de la prochaine construction. + +Configuration du système +------------------------ + +En parallèle de la construction du système, nous avons à nous occuper de sa +configuration initiale. + +Cela est réalisé à travers deux scripts, le premier, `post-build.sh`_, est +exécuté après l’installation des paquets, le second, `post-image.sh`_ après la +finalisation de l’image finale. Dans le premier nous allons modifier les +fichiers de configuration du système, alors que dans le second nous traiterons +les paramètres donnés lors du boot. + +Je présente ci-dessous quelques modifications apportées au système pour le +faire correspondre exactement au besoin : + +Carte en lecture seule +~~~~~~~~~~~~~~~~~~~~~~ + +Étant donné que nous n’aurons pas d’interface pour nous connecter à la carte, +elle va etre éteinte « sauvagement » régulièrement. Je préfère donc la basculer +en lecture seule, ce qui permet aussi d’etre sûr que tout le système restera +immuable sur la durée. + + +.. code-block:: bash + + sed -ie '/^\/dev\/root/ s/rw 0 1/ro 0 0/' "${TARGET_DIR}/etc/fstab" + +La modification est faite avec `sed`, et permet de suite de voir un peu comment +on va modifier le système : + +* Chaque modification sera faite à l’aide des outils disponibles dans bash + (sed, cp, cat, …), il faut donc déjà etre à l’aise avec le terminal avant + d’aller modifier la configuration +* Chaque command nécessite de savoir comment sont construits les fichiers que + l’on veut modifier +* Dans le cas où nous ajoutons des nouvelles entrées dans un fichier, il faudra + contrôler *avant* que cette entrée n’est pas déjà présente +* Et enfin, nous allons avoir du boulot pour modifier chaque élément un à un… + +La variable `${TARGET_DIR}` est fournie par buildroot et permet de cibler +l’image que nous sommes en train de construire (par opposition au système hote +dans lequel nous compilons le système). Ici, nous modifions bien le fichier +`/etc/fstab` final, et non pas notre système linux ! + +.. admonition:: Bonne pratique + :class: hint + + Au vu des risques en cas d’erreur de saisie, il est bon de rajouter cette + ligne en début de script : + + .. code-block:: bash + + # Traite les variables non définies comme des erreurs lors de la substitution. + set -u + + Nous serons ainsi sûrs de ne pas modifier notre système par erreur (ce qui ne + devrait pas arriver, car nous ne travaillons pas sous `root`) + +Ajout du wifi +~~~~~~~~~~~~~ + +Pour activer le wifi, nous allons laisser systemd faire le travail, +`wpa_supplicant` propose en effet un service paramétré, qui active l’interface +spécifiée dans le nom (ici wlan0), il suffit donc de créer un lien symbolique +pour que celui-ci soit activé au démarrage : + +.. code-block:: bash + + # Create the link to interface wlan0 directly in the system configuration + ln -sf /usr/lib/systemd/system/wpa_supplicant@.service "${TARGET_DIR}/usr/lib/systemd/system/multi-user.target.wants/wpa_supplicant@wlan0.service" + +(par contre, cela implique d’installer systemd sur la carte, ce qui surcharge +les dépendances, mais cela simplifie notre travail finalement…) + +La configuration de la carte est comme d’habitude dans un fichier +`wpa_supplicant.conf` qui n’est pas versionné, mais qui sera copié s’il est +présent dans le répertoire. + +.. code-block:: bash + + if [ -f "$BR2_EXTERNAL_PIAUDIO_PATH/wpa_supplicant.conf" ]; then + cat "$BR2_EXTERNAL_PIAUDIO_PATH/wpa_supplicant.conf" > "${TARGET_DIR}/etc/wpa_supplicant/wpa_supplicant-wlan0.conf" + … + fi + +La variable `$BR2_EXTERNAL_PIAUDIO_PATH` correspond au répertoire de base du +système, c’est-à-dire notre répertoire racine. Il s’agit également d’une autre +variable d’environnement fournie par buildroot qui nous permet de ne pas avoir +à nous soucier de l’emplacement du script quand nous lançons notre compilation. + +Pulseaudio +~~~~~~~~~~ + +Vient ensuite pulseaudio. Nous n’avons pas à nous occuper de le démarrer avec +le système car cela est fourni par un paquet ayant été sélectionné. + +Par contre, puisque le système est en lecture seule, nous avons quelques +modifications à réaliser pour que le service fonctionne : nous allons créer un +répertoire, et le monter en mémoire au démarrage afin que pulseaudio puisse y +stocker ses fichiers : + +.. code-block:: bash + + create_missing_dir "/var/lib/pulse" + if ! grep -qE '/var/lib/pulse' "${TARGET_DIR}/etc/fstab"; then + cat << __EOF__ >> "${TARGET_DIR}/etc/fstab" + tmpfs /var/lib/pulse tmpfs rw 0 0 + __EOF__ + fi + +Ensuite, nous allons modifier le fichier `/etc/pulse/system.pa` afin d’y +ajouter les modules bluetooth, et la connexion distante : + +.. code-block:: bash + + if ! grep -qE '^load-module module-native-protocol-tcp' "${TARGET_DIR}/etc/pulse/system.pa"; then + cat << __EOF__ >> "${TARGET_DIR}/etc/pulse/system.pa" + load-module module-bluetooth-policy + load-module module-bluetooth-discover + load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1;192.168.0.0/24;2a01:e35:8ac8:0e00::/64 auth-anonymous=1 + __EOF__ + fi + +Bluetooth +~~~~~~~~~ + +D’abord, pour activer le bluetooth, nous allons indiquer au noyau de le charger +au démarrage : + +.. code-block:: bash + + cat << __EOF__ >> "${BINARIES_DIR}/rpi-firmware/config.txt" + dtparam=krnbt=on + __EOF__ + +Cette option ajoutée récemment dans les raspberrypi va nous éviter d’avoir à +lancer les commande `bt-attach` ou `hciattach`. le bluetooth est +automatiquement branché sur le port uart, à la vitesse maximale prise en charge +par la carte. Cela résoud en une ligne énormément de problèmes rencontrés lors +de la configuration du bluetooth si l’on avait voulu faire cela manuellement. + +Voir la liste des options possibles sur la page suivante : https://github.com/raspberrypi/firmware/tree/master/boot/overlays + +Par contre, il faut libérer pour cela la connexion série, qui est configurée +par défaut sur le port `ttyAMA0`. Il nous faut supprimer la ligne suivante dans +le fichier `cmdline.txt` : `console=ttyAMA0,115200` + +Ensuite, comme précédemment, nous allons également créer un répertoire en +mémoire pour pallier le système en lecture seule de la carte : + +.. code-block:: bash + + create_missing_dir "/var/lib/bluetooth/" + if ! grep -qE '/var/lib/bluetooth' "${TARGET_DIR}/etc/fstab"; then + cat << __EOF__ >> "${TARGET_DIR}/etc/fstab" + tmpfs /var/lib/bluetooth tmpfs rw 0 0 + __EOF__ + fi + +La configuration du bluetooth est lue dans le fichier +`/etc/bluetooth/main.conf`, c’est donc là que nous allons y stocker les +paramètres génériques : classe de l’appareil et timeouts : + +.. code-block:: ini + + [General] + + Class = 200428 + DiscoverableTimeout = 0 + PairableTimeout = 0 + + [Policy] + AutoEnable=true + +Enfin, nous avons besoin d’autoriser les connexions externes, et ce, sans aucun +accès au système. Cela s’effectue par le biais d’un *agent* qui demande la +confirmation d’un code auprès des deux parties (celui qui veut se connecter, et +celui qui accepte les connexions) pour éviter les erreurs. Ici, nous faire fi +de la sécurité, et ouvrir les connexions à n’importe qui. Les risques sont +limités (le système est en lecture seule, aucune des applications de base +permettant de détourner l’ordinateur n’est installée). + +C’est un problème que j’avais rencontré dans mon système d’origine, et qui +nécessitait de se connecter sur le raspberrypi pour autoriser le périphérique. +J’ai finalement trouvé une solution (qui se résume en quelques lignes, mais qui +m’a occupé quelques heures avant d’en arriver là). + +On va se créer un service *systemd* (puisqu’il est là…) qui lance un agent +perpétuel chargé d’accepter toutes les connexions : + +.. code-block:: bash + + cat << __EOF__ > "${TARGET_DIR}/etc/systemd/system/bt-agent.service" + [Unit] + Description=Bluetooth Agent + After=bt-audio.service + Requires=bt-audio.service + + [Service] + Type=simple + ExecStartPre=bt-adapter --set Discoverable 1 + ExecStart=bt-agent -c NoInputNoOutput + RestartSec=5 + Restart=always + KillSignal=SIGUSR1 + + [Install] + WantedBy=bluetooth.target + __EOF__ + +Au passage, ce service déclare le bluetooth en mode « découvrable », ce qui rend +vraiment le service ouvert à tous. + +Connexion série +~~~~~~~~~~~~~~~ + +Enfin, il faut que nous puissions nous connecter à la carte pour contrôler +qu’elle démarre correctement. Or, nous n’avons installé aucun serveur ssh : +celle-ci est donc vérouillée. Par contre, le raspberrypi 0 nous offre une +chance, puisqu’il est possible de s’y connecter en le branchant directement en +usb, mais encore faut-il activer cette option au démarrage… + +.. note:: + + Cela fonctionne également avec un raspberrypi 4 ou un raspberrypi 3 A, mais + pour ce dernier, il faut trouver un cable USB male/male pour faire la + connexion. Dans mes tests avec ce dernier, j’ai triché et installé dropbear + qui est un serveur ssh ! + +Il suffit d’ajouter une ligne dans le fichier `cmdline.txt` et le tour est joué +: + +.. code-block:: bash + + if ! grep -qE 'modules-load=dwc2,g_serial' "${BINARIES_DIR}/rpi-firmware/cmdline.txt"; then + sed '/^root=/ s/$/ modules-load=dwc2,g_serial/' -i "${BINARIES_DIR}/rpi-firmware/cmdline.txt" + fi + +Build +----- + +Il ne nous reste plus qu’à lancer la compilation du système, et attendre le +résultat : + +.. code:: bash + + ./build.sh raspberrypi0w all + +Une fois terminé, on peut copier l’image générée et la brancher dans le +raspberry pi + +.. code:: bash + + sudo dd if=output/raspberrypi0w/images/sdcard.img of=/dev/XXX bs=4k + +Démarrage et premier lancement +============================== + +.. image:: {static}/images/buildroot/connexion.jpeg + :alt: La carte connectée en USB + :align: center + + +Une fois la carte branchée, on peut se connecter dessus à l’aide de la commande +suivante : + +.. code:: bash + + screen /dev/ttyACM0 + +Récupérer le nom de la connexion pulseaudio +------------------------------------------- + +.. code:: console + + # su -s /usr/bin/sh pulse + $ pactl info + … + Default Sink: alsa_output.0.stereo-fallback + +C'est cette valeur qui est à utiliser dans le tunnel que nous allons mettre en +place. Sur notre pc de bureau, nous allons modifier le fichier `/etc/pulse/default.pa` et ajouter cette ligne : + +.. code:: bash + + load-module module-tunnel-sink sink_name=rpi_tunnel server=tcp:${IP}:4713 sink=alsa_output.0.stereo-fallback + +Comme notre raspberrypi se connecte en DHCP, son adresse IP est fournie par le +routeur. Je conseille de lui associer une IP fixe afin que la configuration ne +change pas :) + +.. image:: {static}/images/buildroot/pulseaudio.png + :alt: La connexion pulseaudio + :align: center + + +Nommage du service bluetooth +---------------------------- + +Un dernier point que je n’ai pas réussi à comprendre, le nom du service +bluetooth est `Bluez` au lieu du nom de la carte (`piaudio`). Cela semble être +du au fait que la carte soit en lecture seule. La solution consiste à basculer +la carte en écriture, relancer le service bluetooth, et redémarrer : + +.. code:: bash + + mount -o remount,rw / + systemctl restart bluetooth + reboot + +Cette opération est à faire une fois seulement, il n’est pas nécessaire d’y +revenir ensuite. + +Se connecter en bluetooth +------------------------- + +.. image:: {static}/images/buildroot/bluetooth.png + :alt: La connexion bluetooth + :align: center + +Comme nous avons tout automatisé, c’est encore plus simple ! en quelques clics +l’association est faite ! + diff --git a/content/images/buildroot/bluetooth.png b/content/images/buildroot/bluetooth.png new file mode 100644 index 0000000..1614169 Binary files /dev/null and b/content/images/buildroot/bluetooth.png differ diff --git a/content/images/buildroot/buildroot.png b/content/images/buildroot/buildroot.png new file mode 100644 index 0000000..15b8a05 Binary files /dev/null and b/content/images/buildroot/buildroot.png differ diff --git a/content/images/buildroot/connexion.jpeg b/content/images/buildroot/connexion.jpeg new file mode 100644 index 0000000..6655e46 Binary files /dev/null and b/content/images/buildroot/connexion.jpeg differ diff --git a/content/images/buildroot/pulseaudio.png b/content/images/buildroot/pulseaudio.png new file mode 100644 index 0000000..d5e59d2 Binary files /dev/null and b/content/images/buildroot/pulseaudio.png differ -- cgit v1.2.3