aboutsummaryrefslogtreecommitdiff
path: root/content/Informatique/2021-11-piaudio.rst
blob: b7649a73b35824622588676fe22830da9c629b10 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
.. -*- 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 !