Un routeur pour la radio par internet

Gazette Linux n°166 — Septembre 2009

Pascal Mazon

Adaptation française  

Gaël Montreuil

Relecture de la version française  

Article paru dans le n°166 de la Gazette Linux d'/de Septembre 2009.

Cet article est publié selon les termes de la Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.


Table des matières

Un routeur pour la radio par internet
Hardware
Logiciel système
Interface utilisateur
Changer les stations
Références

Un routeur pour la radio par internet

Le point d'accès DSL de ma famille se trouve dans la cuisine, et on distribue l'accès internet dans la maison à l'aide d'un routeur sans-fil, mais pour le moment, on ne peut pas écouter la radio par internet dans la cuisine. Bien sûr je pourrais acheter une radio internet commerciale toute prête, mais en quoi serait ce amusant ? D'un autre côté, je pourrais acheter un routeur sans-fil basé sur Linux équipé USB, y brancher une carte son USB, et m'amuser à faire marcher tout le tremblement de façon (plus ou moins) conviviale, afin que ce soit également utilisable par les autres membres de la famille. Pour rendre ce projet assez léger et réalisable durant des vacances d'été, j'ai décidé d'éviter les modifications hardware (et pourtant ça doit être amusant à faire) ou les installations de gros paquets logiciels comme perl sur le routeur. La quasi-totalité de la programmation sera faite avec des scripts shell.

Avant de commencer, je ferai remarquer qu'écouter un flux radio par internet à 128kbits/sec représente 1,4Go de trafic sur le réseau en 24 heures. Il peut être bon de s'en souvenir, si vous n'avez pas un abonnement forfaitaire mais que vous devez payer en proportion du volume de vos accès Internet. Cela étant dit, tournons-nous vers les détails d'implantation, à commencer par le hardware.

Hardware

J'ai acheté ce qui devait sûrement être le dernier ASUS WL-500g Premium disponible, avec deux connecteurs USB2 à l'arrière. N'importe quel autre routeur fera probablement l'affaire, s'il a au moins un port USB et s'il est possible de remplacer son système d'exploitation (firmware) par un système basé sur Linux qui supporte aussi l'USB. J'ai choisi OpenWRT [1] qui est une distribution dérivée de Debian pour les appareils embarquées, et qui fournit les pilotes et supports pour de nombreux gadgets, en particulier la carte son USB pas chère et sans nom que j'ai achetée (la commande lsusb renvoie Od8c:000c C-Media Electronics, Inc. Audio Adapter). Afin de pouvoir entendre la musique, j'ai connecté la sortie de la carte son à des enceintes avec alimentation qui traînaient par là. Des écouteurs standards devraient aussi pouvoir marcher, mais j'évite d'utiliser des périphériques sans alimentation, parce que pour la carte son USB l'alimentation électrique se fait via le port USB, et des périphériques d'écoute sans alimentation pourraient surcharger le port USB au delà du niveau critique.

Logiciel système

L'étape suivante est l'installation sur le routeur d'un nouveau système d'exploitation basé sur Linux. J'applique les instructions d'installation (obtenues depuis cette page) pour OpenWRT 8.09.1 (Kamikaze) [2] et je re-flashe le firmware du routeur. Je peux alors me connecter sur le routeur pour la première fois. Immédiatement après avoir flashé, le routeur a l'adresse IP par défaut 192.168.1.1, et je peux connecter un ordinateur à l'un des ports LAN du routeur. Par défaut un serveur DHCP est activé sur le routeur. Ainsi n'importe quel ordinateur réclamant une adresse IP en DHCP peut se connecter. Ensuite j'utilise telnet pour me connecter sur le routeur en tant que root. Aucun mot de passe n'est nécessaire tant que le mot de passe root n'est pas défini grâce à la commande « passwd ». Une fois qu'il est défini, il faut utiliser ssh pour avoir un accès par terminal. Pour les tests et le développement, je préfère l'accès par terminal, mais les tâches administratives telles que l'ajustement des paramètres réseau ou l'installation de logiciels supplémentaires peuvent se faire plus facilement avec l'interface orienté-web LuCI. On peut accéder à cette dernière à l'aide du navigateur, en lui indiquant le routeur, qui possède toujours l'adresse IP 192.168.1.1, à moins que vous ne l'ayez changé. Il faut s'identifier en tant que root avec le mot de passe préalablement défini. On peut désormais changer tous les paramètres du routeur et installer des pilotes et des applications supplémentaires. Pour avoir un accès total à toutes les options de configuration, sélectionnez le mode « administration », en haut à droite sur l'interface LuCI.

Avant de pouvoir installer plus de logiciels, nous devons connecter le routeur à internet. Cette opération est simplifiée grâce à la configuration automatique des ports WAN du routeur, qui demande par défaut une adresse IP à tout périphérique auquel il est connecté. J'ai relié le port WAN de mon ASUS à l'un des ports LAN de mon vieux routeur DSL dans la cuisine. De cette façon, mon ASUS a accès à internet, et peut contacter les dépôts logiciels.

L'installation par défaut d'OpenWRT ne supporte pas les périphériques USB, on doit donc installer les pilotes pour activer l'utilisation des clés-USB et de la carte son USB. Dans LuCI, la page d'installation de logiciels se trouve dans l'onglet « System/Software ». Vous devez en premier mettre à jour les listes de paquets en sélectionnant le lien en haut à gauche de la page Logiciels. Cela à pour effet de recharger la page avec un grand nombre de paquets disponibles. Installez des paquets en cochant la case dans la ligne adéquate, et en cliquant sur le bouton « Perform Actions » en bas de la page. Il y a une fonction filtre disponible; très pratique car elle facilite la recherche de paquets.

Pour le support des clés USB, j'ai suivi ce lien [3] et installé les modules du noyau kmod-usb-uhci, kmod-usb-ohci, kmod-usb2 pour le support d'USB1 et USB2 générique. Étant donné que la plupart des clés USB utilisent le système de fichier FAT, j'ai aussi installé kmod-fs-vfat. En installant depuis l'interface web LuCI, tous les modules importants sont déjà chargés, donc pas besoin de faire des commandes insmod. Le module du noyau requis pour la carte son est kmod-usb-audio, comme l'indique ce lien [4]. J'ai aussi installé le paquet madplay (lecteur mp3). Le paquet usbutils, qui contient la commande lsusb, est utile pour les débogages.

Après avoir installé tous les paquets nécessaires, je me suis enfin connecté sur le routeur via ssh pour accéder aux commandes terminal, et j'ai suivi le lien précédent pour lancer une station de radio par internet depuis le terminal, en tapant :

      wget -q -O – http://94.23.29.150:11022 | madplay -

À ma plus grande surprise, tout a marché du premier coup : félicitations aux développeurs d'OpenWRT. Dans la commande plus haut, wget récupère le flux radio mp3 à partir de l'IP et du numéro de port http://94.23.29.150:11022 et le dirige vers la sortie standard. C'est là que madplay le reçoit et utilise le périphérique de son par défaut /dev/dsp comme sortie, et le son sortira par les haut-parleurs. On notera que cela constitue le cœur du lecteur radio internet. Cependant, se connecter au routeur pour le lancer n'est pas des plus pratique. De plus, il n'y a qu'un bouton utile sur le routeur, ce qui rend difficile la création d'une interface utilisateur sans accès direct à un ordinateur. Un peu plus loin, je décris mon interface utilisateur pour mon routeur radio par internet.

Juste un mot de circonstance sur la recherche d'adresses de stations de radio internet. D'habitude je trouve les stations en les sélectionnant sur http://www.shoutcast.com et en les lançant dans xmms sur mon ordinateur. Dans xmms, en sélectionnant « voir les informations du fichier » dans le menu fichier (ou tapez Ctrl+3), on affiche l'adresse de la station.

Interface utilisateur

À cause de l'unique bouton disponible pour l'interface utilisateur, on est obligé de réfléchir sur quelle fonctionnalité est essentielle, et sur la fréquence des différentes activités. Par exemple allumer/éteindre la radio ou changer de station arrivera fréquemment. Il faudrait que ce soit possible sans ordinateur supplémentaire, en utilisant simplement le seul bouton disponible. Par conséquent, je vais programmer le bouton afin qu'il effectue un cycle entre cinq stations radios pré-sélectionnées et l'extinction pour le sixième état. La sélection des stations est complémentée par un court fichier audio, décrivant le numéro de chaîne, joué avant la sélection de la nouvelle station. Le changement des stations pré-sélectionnées arrive beaucoup moins souvent, et nécessitera l'utilisation du serveur web, y compris des scripts cgi, disponibles sur le routeur qui propose normalement l'interface LuCI.

Commençons par programmer le bouton. Il y a en réalité deux boutons à l'arrière du routeur : l'un est rouge et est en relief, et est nommé « EzSetup ». L'autre est nommé « Restore » et requiert un ustensile pointu tel qu'un stylo pour l'activer. Je vais donc utiliser uniquement le bouton « EzSetup ». Il s'avère que c'est assez simple au final. Toutes les actions suivantes sont exécutées en étant connecté en ssh au routeur et en utilisant l'éditeur vi installé dessus. Le bouton est déclenché de façon asynchrone pour d'autres actions du routeur, et est donc sous contrôle d'un démon « hotplug » dont les fichiers de configuration se trouvent sur /etc/hotplug.d/. Dans ce répertoire, il faut tout d'abord créer un sous-répertoire /etc/hotplug.d/button/, et dedans créer un script handler qui s'exécute quand on appuie sur un bouton. On utilise également cette fonctionnalité pour allumer/éteindre le réseau sans-fil, voir ce lien [5] et celui-ci [6]. Pour des informations spécifiques concernant le routeur ASUS, voir ce lien.

Avant d'aborder le script, il faut le faire connaître au système. Toute information de configuration est stockée dans le sous-répertoire /etc/config/, où je crée un fichier radio pour contenir toute information persistante, conservée après redémarrage du routeur. Voici une copie de ce fichier :

      # fichier: /etc/config/radio
      config 'radio'
      option 'button' 'ses'
      option 'state' '0'
      option 'ch1' 'http://216.155.137.150:10000'
      option 'ch2' 'http://scfire-dtc-aa07.stream.aol.com:80/stream/1075'
      option 'ch3' 'http://scfire-mtc-aa04.stream.aol.com:80/stream/1006'
      option 'ch4' 'http://94.23.17.224:8396'
      option 'ch5' 'http://94.23.29.150:11022'
    

La première ligne qui ne soit pas un commentaire définit le nom de paramètre, ici radio, suivi des options. La plus importante est le bouton assigné ses. J'ai aussi ajouté une variable  « state » pour se souvenir de la station qu'on vient d'écouter, et aussi pour dire laquelle est la suivante. Les paramètres nommés chX stockent les adresses des stations radio pré-sélectionnées. En ligne de commande ou par des scripts, on peut lire les paramètres en utilisant la commande uci, avec la syntaxe suivante : uci get radio.@radio[0].state, et les écrire avec uci set radio.@radio[0].state=3. Ici la variable state n'était qu'un exemple. On peut changer les autres paramètres de la même façon. Le programme uci est une interface pratique d'OpenWRT pour accéder aux paramètres de configuration. Cependant il ne manipule que des copies en mémoire des paramètres, pas sur le disque. Les paramètres actuels peuvent être écrits dans le fichier de configuration en utilisant la commande uci commit radio, qui va rendre les valeurs de nouveau persistantes. On va utiliser cette fonctionnalité pour changer les stations radio pré-sélectionnées.

Maintenant qu'on a rendu disponibles les variables de configuration qui mémorisent la station actuelle et les adresses des stations, on peut enfin parler des actions effectuées quand on appuie sur le bouton situé à l'arrière. Rappelez-vous, ceci est stocké dans le fichier /etc/hotplug.d/button/handler. Voici une petite partie du fichier :

      #!/bin/sh
      # fichier : /etc/hotplug.d/button/handler
      # logger button handler: $BUTTON $ACTION $SEEN

      STATE=$(uci get radio.@radio[0].state)

      CH1=$(uci get radio.@radio[0].ch1) CH2=$(uci get radio.@radio[0].ch2)
      CH3=$(uci get radio.@radio[0].ch3) CH4=$(uci get radio.@radio[0].ch4)
      CH5=$(uci get radio.@radio[0].ch5)

      if [ $BUTTON = "ses" ]
      then
      if [ $ACTION = "released" ]
      then
      killall wget
      if [ $SEEN -gt "0" ]
      then
      STATE=-1
      fi

      case $STATE in
      0 ) madplay /root/radio/ch1.mp3 wget -q -O - $CH1 |\
      madplay - & uci set radio.@radio[0].state=1 ;;
      1 ) madplay /root/radio/ch2.mp3 wget -q -O - $CH2 |\
      madplay - & uci set radio.@radio[0].state=2 ;;
      * ) uci set radio.@radio[0].state=0 madplay /root/radio/off.mp3 ;;
      esac
      fi
      fi
    

On trouvera une copie complète ici. L'exécution de ce fichier est déclenchée par l'appui sur le bouton de l'arrière du routeur. Le système fournit aussi trois variables d'environnement en cours d'exécution : $BUTTON, $ACTION et $SEEN. La variable $BUTTON peut avoir pour valeur ses ou reset, selon qu'on a appuyé sur le bouton rouge en relief EzSetup, ou sur l'autre. La variable $ACTION peut avoir pour valeur pressed (appuyé) ou released (relâché), selon la position du bouton. La variable $SEEN contient le nombre de secondes depuis le dernier événement lié au bouton. Nous l'utiliserons pour différencier un appui long d'un appui court sur le bouton. Ces variables vont jouer un rôle prépondérant dans le script que nous allons décrire succinctement maintenant. En haut du script handler la variable « state » est lue, celle qui mémorise la station. On a aussi l'affectation de l'adresse de la station aux variables CHX. Ensuite, on vérifie que le bouton EzSetup nommé « ses » a été activé; la partie qui suit ne s'exécute que si le bouton est relâché. Si l'on n'a pas vérifié l' « appuyé » ou « relâché », le script s'écécuterait deux fois, une fois quand on appuie sur le bouton, et l'autre fois quand on le relâche. En vérifiant l'évènement de relâchement, on peut différencier l'appui court de l'appui long, puisque la variable $SEEN est remise à zéro pour chacun des deux évènements. Si $SEEN est zéro, moins d'une seconde s'est écoulée entre l'appui et le relâchement du bouton. Quand on est entré dans la condition if, on interrompt tout processus wget actif, puisque l'on ne peut écouter qu'une seule station à la fois.

Le processus madplay associé est également interrompu, parce que son processus père n'existe plus. Ensuite on vérifie la variable $SEEN, et on définit la variable $STATE à une certaine valeur qui va causer l'extinction de la radio par l'instruction case définie plus loin. La structure case est utilisée pour faire des cycles entre les stations : si state est « 0 », ça va lancer la station 1, et ainsi de suite. Dans chaque cas on commence par la lecture d'un court fichier audio pour identifier la station. Sur mon ordinateur, je me suis enregistré en disant « Chaîne 1 », et j'ai converti l'enregistrement en un fichier mp3 nommé ch1.mp3. Puis j'ai copié les fichiers via « scp » vers le routeur, mais bon, vous pouvez mettre n'importe quoi d'autre. Vous pourriez, par exemple, extraire le jingle de la station radio et le mettre dans un fichier. Cet identifiant sonore est juste un petit plus pour savoir quelle station radio a été sélectionnée. S'ensuit la construction avec wget encapsulé dans madplay, dont j'ai parlé plus haut, et la variable state s'incrémente de un. J'utilise cinq stations pour faire le tour, et la dernière partie me dit juste que la radio est coupée et ne reçoit pas de données Internet.

La mise en place avec le script handler fournit des fonctionnalités basiques pour passer d'une radio pré-sélectionnée à une autre, mais changer ce set de stations va demander une connexion sur le routeur et l'édition des fichiers de configuration à la main. Nous allons remédier à ce problème dans la partie suivante.

Changer les stations

Le routeur contient déjà un serveur Web pour gérer l'interface de configuration LuCI. Tous les fichiers du serveur sont situés dans le répertoire /www/, et même le support des scripts cgi-bin est supporté, si l'on met les exécutables dans le répertoire /www/cgi-bin/. En pratique, on peut utiliser un navigateur Web pour lancer des programmes sur le routeur, pas mal, hein ? Soyez juste sûr que le routeur n'est accessible que sur un réseau de confiance.

En premier malgré tout, il faut préparer une page Web qui nous dira quelles sont les 5 stations radios présélectionnées. C'est ce que fait le script suivant :

    
    #!/bin/sh
    # fichier : /www/cgi-bin/radio.cgi

    echo 'HTTP/1.0 200 OK'
    echo 'Server: Netscape-Communications/3.0'
    echo 'Content-type: text/html'
    echo

    ch1=$(uci get radio.@radio[0].ch1)
    ch2=$(uci get radio.@radio[0].ch2)
    ch3=$(uci get radio.@radio[0].ch3)
    ch4=$(uci get radio.@radio[0].ch4)
    ch5=$(uci get radio.@radio[0].ch5)

    header='<HTML><HEAD><TITLE>Canaux Radio</TITLE></HEAD><BODY>
    <H1 ALIGN=CENTER>Sélection de canal radio</H1>'

    echo $header 
    
    echo '<FORM action="setchannel.sh" method="get">'
    echo -n 'Canal 1 : <input type="text" name="ch1" size="80" value="'
    echo -n $ch1; echo '"> '
    echo '<input type="submit" value="Submit">'
    echo '</FORM>'

                          :

    echo '<FORM action="setchannel.sh" method="get">'
    echo -n 'Canal 5: <input type="text" name="ch5" size="80" value="'
    echo -n $ch5; echo '"> '
    echo '<input type="submit" value="Submit">'
    echo '</FORM>'

    echo '<FORM action="commit.sh" method="get">'
    echo '<input type="submit" value="Sauvegarder dans le routeur">'
    echo '</FORM>'

    echo '</BODY></HTML>' 
    
    

Je n'ai pas mis les quelques lignes du formulaire FORM pour les stations 2, 3 et 4, mais le script complet est disponible ici. Le script doit être rendu exécutable (chmod + x radio.cgi) et placé dans /www/cgi-bin/. Renseigner l'adresse 192.168.1.1/cgi-bin/radio.cgi dans le navigateur Web de votre ordinateur vous affichera la page Web contenant cinq lignes, où les stations radios sont indiquées. Chaque ligne a un bouton avec « envoyer » écrit dessus (voir l'aperçu écran, plus bas).

L'idée, c'est de changer l'adresse de la station radio dans le champ de saisie, et d'appuyer sur le bouton d'envoi pour mettre à jour les paramètres du routeur. Plus précisément c'est une partie des fonctionnalités du script radio.cgi. Vous pouvez remarquer que le script se trouve exécuté dans cgi-bin, ce qui fait que toutes les données en sortie du script, normalement écrites sur la sortie standard, doivent être redirigées vers le navigateur Web qui appelle le script. Du coup dans le script, toutes les sorties sont générées à l'aide de la commande echo. Les premières lignes préparent l'en-tête standard que tout navigateur Web attend. Puis les variables chX sont assignées avec les adresses des stations. Enfin, les informations d'en-tête HTML sont définies et écrites par une commande echo.

Les cinq lignes suivantes définissent un formulaire contenant le champ de texte avec les adresses des stations. Il est déjà pré-rempli avec les adresses actuelles. S'ensuit la définition du bouton d'envoi. À la première ligne de la définition du FORM, la partie action="setchannel.s" définit un programme qui doit se situer dans le répertoire cgi-bin du routeur, et qui sert à récupérer le contenu du champ de texte – l'adresse de la station – à l'appui sur le bouton d'envoi. Vous pouvez consulter n'importe quel livre sur le HTML à propos des formulaires HTML et des scripts cgi-bin, pour plus de détails, par exemple ce lien [7] et ce lien [8]. Les trois dernières lignes pour le formulaire servent à exécuter le script commit.sh du serveur, pour que les variables persistent malgré les redémarrages du serveur.

La partie qui reçoit les champs du formulaire, c'est le script setchannel.sh, qui doit être rendu exécutable (chmod +x setchannel.sh) et comme précédemment, placé dans /www/cgi-bin/. En voici une copie :

    #!/bin/sh
    # fichier : /www/cgi-bin/setchannel.sh
    decode(){
    echo $QUERY_STRING |\
       sed 's/+/ /g'| sed 's/\%0[dD]//g' |\
       awk '/%/{while(match($0,/\%[0-9a-fA-F][0-9a-fA-F]/))\
       {$0=substr($0,1,RSTART-1)sprintf("%c",0+("0x"substr(\
         $0,RSTART+1,2)))substr($0,RSTART+3);}}{print}'
    }
    TMP=$( decode )
    CHAN=${TMP%=http*}
    URL=${TMP:4}
    uci set radio.@radio[0].$CHAN=$URL
    echo "Association du Canal $CHAN à la station $URL"
    

Une copie pour le téléchargement est disponible ici. Dans ce script nous définissons tout d'abord une fonction pour enlever l'encodage URL obligatoire pour les variables envoyées par le formulaire dans le script radio.cgi. J'ai adapté un script que j'ai trouvé sur le lien [9] pour décoder le QUERY_STRING et la suite est un petit script shell pour extraire la station et l'URL. Une commande finale uci set attribue les variables du routeur. Vous remarquerez que les stations radios sont enregistrées en mémoire du routeur uniquement. Afin de les rendre persistantes, nous devons exécuter une commande uci commit radio à partir du script commit.sh, dont le code est situé ci-après :

    #!/bin/sh
    # fichier : /www/cgi-bin/commit.sh
    uci set radio.@radio[0].state=0
    uci commit radio
    echo 'Canaux sauvegardés dans le routeur'
    

Nous pouvons voir que ça ne fait qu'initialiser les variables d'état à la valeur initiale zéro, et qu'ensuite ça exécute le script commit, pour finalement faire un echo de ce qui a été fait vers le navigateur Web qui appelle le script.

En utilisant l'interface présentée, il est possible de changer les adresses des stations radio que l'on souhaite, et de les rendre persistantes. Il est facile de sélectionner l'une des cinq stations ou d'éteindre la radio en appuyant sur le bouton rouge, sur le côté du routeur. Notre routeur est maintenant placé définitivement dans la cuisine, et est utilisé pour écouter de la musique depuis des stations de radios Internet.

Bien sûr, un routeur avec une connectivité USB est un superbe appareil pour d'autres fonctionnalités. On pourrait par exemple, envisager d'attacher un support Bluetooth (voir le lien [10]), et utiliser un téléphone acceptant le Bluetooth pour contrôler la sélection des stations radio. J'explique comment faire sur le téléphone sur le lien [11]. Inclure les fonctionnalités de contrôle de radio internet dans l'interface LuCI serait aussi un projet sympa. Mais vous, très cher lecteur, allez certainement inventer d'autres manières amusantes d'utiliser une boîte magique telle qu'un routeur avec USB.

Références

[7] R. Darnell, et al., HTML4 Unleashed, SAMS Publishing, 1999.

[8] http://de.selfhtml.org/ (malheureusement disponible uniquement en allemand)

[11] http://linuxgazette.net/153/ziemann.html, Linux Gazette 153, Août 2008.

Volker Ziemann vit à Uppsala, en Suède. Il est physicien et travaille sur des projets utilisant les accélérateurs de particules, au CERN en Suisse et au DESY en Allemagne. Sa thèse effectuée dans les années 80 l'a poussé à utiliser des ordinateurs, qu'il a continué à utiliser avec un enthousiasme sans cesse croissant. Il utilise Linux activement depuis 1995.