aboutsummaryrefslogtreecommitdiff
path: root/content/Perso/2015-03-19-dynamisme.rst
AgeCommit message (Expand)Author
2020-12-04Change files reference to staticSébastien Dailly
2015-03-21Update rst role for literral and emphase wordsSébastien Dailly
2015-03-21New article on the creationSébastien Dailly
href='#n34'>34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
.. -*- 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.
:tags: MPD

.. _`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 !