Il a peu, j'ai découvert l'existence de Dancer, un micro-framework web pour perl.
J'ai appris à m'en servir sommairement et en ai profité pour bidouiller une interface vers une instance d'ADECampus en guise de pied de nez à ceux qui pensaient que ça pouvait pas se faire sans module (payant).
Aujourd'hui, je réutilise un peu ce truc dans le but de réaliser une télécommande pour MPD.
Quelques infos
Pour ce qui du système, je tourne sous ArchLinux (à jour forcément) avec perl 5.16.1 et MPD 0.17.1.
Le téléphone Android qui servira de télécommande dans la suite est un Samsung i5700 (Galaxy Spica) rooté avec SL4A d'installé (pas à jour je pense), c'est toutefois largement suffisant pour ce que l'on veut en faire.
Objectifs
On va commencer par comprendre comment s'interfacer avec MPD depuis un script perl, ensuite, nous passerons à l'écriture du micro-serveur pour le controle d'MPD.
Finalement, on regardera de plus près comment utiliser SL4A (Scripting Layer For Android) pour le code de la télécommande elle même (voir screenshot ci-contre).
L'objectif profond n'est pas de fournir une solution parfaite mais une piste. C'est typiquement un proof of concept qui gagnerait à être amélioré mais qui fonctionne ainsi.
Perl The Dancer
Pour la partie tournant sur la machine hôte, on se contentera d'un petit script Perl utilisant Dancer pour la partie web et Net::Telnet pour l'interfaçage avec mpd.
Vous pourrez installer les deux modules via CPAN.
Le squelette du script ressemblera donc à :
use strict;
use warnings;
use Dancer;
use Net::Telnet;
# global params
set 'warning' => 1;
set 'startup_info' => 1;
set 'show_errors' => 1;
set 'logger' => 'console';
set 'log' => 'debug';
# ... faites un truc ici ..
dance;
J'en profite pour préciser que Dancer admet des paramètres globaux via la construction :
set 'param_name' => value;
C'est souvent le cas dans ce genre de micro-framework et peu s'avérer très pratique..
Les commandes de MPD
Par défaut, MPD écoute sur 0.0.0.0:6600 il s'agit d'une socket telnet classique à laquelle on peut se connecter sans soucis depuis un client telnet.
Il semble que le daemon accepte toute une série de commandes pour la configuration et les changements d'états. Voilà les plus courantes :
- play
- démarrer la lecture
- stop
- arrêter la lecture
- pause
- mettre en pause, sortir de la pose
- previous
- passer à la plage précédente
- next
- passer à la plage suivante
Et d'autre que nous n'utiliserons pas cette fois :
- currentsong
- Affiche des infos sur la plage en cours
- state
- Affiche des infos sur le serveur mpd
Normalement, MPD renvoie soit les informations demandées, soit OK MPD <version>, soit ACK [5@0] {} unknown command "<la_commande>".
Et avec Perl
Avec Perl, on va utiliser le module Net::Telnet. Pour simplifier le code des vues ensuite, on va écrire une fonction qui prend une commande MPD en paramètre, se connecte via telnet à MPD, lui envoie la commande, récupère le résultat et se déconnecte en renvoyant le résultat à l'appellant.
Le code de cette fonction n'est pas compliqué, il est tiré principalement de la doc de Net::Telnet :
sub send_command {
my ($command) = @_; # récup de l'argument
my $conn = new Net::Telnet; # création de l'instance telnet
$conn->open( # connexion
host => "localhost",
port => 6600
);
$conn->print($command); # envoi de la commande
my $result = $conn->getline; # récup. du résultat
$conn->close; # fermeture de la connexion
return $result;
}
La capacité de Perl à accepter l'omission de parenthèse permet, dans la plupart des cas d'obtenir un code beaucoup plus propre (même si souvent cryptique).
La structure d'une vue
Avec Dancer, pour répondre à une GET sur une URL on écrit :
get $url => sub {
# do something
}
Ainsi, pour répondre "Hello World" quand une requête arrive sur "/yop", on fera :
get "/yop" => sub {
return "Hello World"
}
Maintenant, on va écrire les 5 fonctions pour les 5 commandes de la première partie de tout à l'heure en utilisant la fonction que nous avons écrit pour et en renvoyant le résultat :
# play/pause/stop
get "/play" => sub { return send_command('play'); };
get "/pause" => sub { return send_command('pause'); };
get "/stop" => sub { return send_command('stop'); };
# prev/next
get "/previous" => sub { return send_command('previous'); };
get "/next" => sub { return send_command('next'); };
On aurait aussi pu le faire en une fois et à grand renforts de regexes.
Lancement
Le script complet (nommé serv.pl par exemple) ressemble à ça :
use strict;
use warnings;
use Dancer;
use Net::Telnet;
# global params
set 'warning' => 1;
set 'startup_info' => 1;
set 'show_errors' => 1;
set 'logger' => 'console';
set 'log' => 'debug';
sub send_command {
my ($command) = @_;
my $conn = new Net::Telnet;
$conn->open(
Host => "localhost",
Port => 6600
);
$conn->print($command);
my $result = $conn->getline;
$conn->close;
return $result;
}
# play/pause/stop
get "/play" => sub { return send_command('play'); };
get "/pause" => sub { return send_command('pause'); };
get "/stop" => sub { return send_command('stop'); };
# prev/next
get "/previous" => sub { return send_command('previous'); };
get "/next" => sub { return send_command('next'); };
dance;
Et il se lance comme tout script perl :
$ perl serv.pl
$ perl serv.pl& # arrière plan
Android
SL4A et python
Depuis le site de SL4A, vous pourrez télécharger l'application et l'interpréteur python, c'est tout ce dont nous aurons besoin.
L'interprèteur python est fourni avec urllib2, soit tout ce qu'il nous faut pour communiquer avec la machine hôte. Enfin, l'API d'Android nous permettra parfaitement de créer un menu comme celui montré en début d'article.
Pour le reste, le code de l'UI android est tiré des exemples de la doc d'SL4A (exemple uilist.py), rien de compliqué :
import android # lib android
import urllib2 # requetes http
import sys # pour exit()
# on initialise l'API
droid = android.Android()
# IP de la machine hote et port
HOST = '192.168.1.14'
PORT = 3000
# Choix de l'action
def getaction():
"get user action"
# on initialise la boite de dialogue avec son titre
droid.dialogCreateAlert("MPD Remote Controler")
# on envoie les items
droid.dialogSetItems([
"Start",
"Pause",
"Stop",
"Previous",
"Next"])
# on affiche
droid.dialogShow()
# on récupère le résultat
result = droid.dialogGetResponse().result
# si un item à été choisi, on le renvoie,
# sinon, on met -1
if result.has_key("item"):
return result["item"]
else:
return -1
# On récupère l'action à faire
action = getaction()
# On vérifie que ce n'est pas -1
# et si besoin on averti l'utilisateur
if action < 0:
droid.makeToast("no item chosen")
droid.close()
sys.exit()
# On remet les bon noms (pour les urls)
real_acts = [
'start',
'pause',
'stop',
'previous',
'next'
]
# on balance la requète et on récupère le résultat pour l'afficher
result = urllib2.urlopen("http://{0}:{1}/{2}".format(
HOST,
PORT,
real_acts[action])
)
# on affiche
toast = '\n'.join(result.readlines())
droid.makeToast(toast.rstrip())
# on quitte
droid.close()
sys.exit()
Voilà donc ledit code, finalement assez simple.
Je ne m'attarderais pas dessus. Retenez simplement que SL4A est un excellent moyen de scripter de petits utilitaires pour Android.
Désormais, si le serveur est lancé, vous pourrez contrôler MPD depuis votre téléphone Android, ce qui est (avouons le) bien symmpathique.
Conclusion
Cet article a donc présenté succintement une interface pour MPD pas forcément très connue.
Nous avons aussi pu toucher à Dancer et Net::Telnet qui permettent d'écrire facilement une passerelle web vers MPD.
Enfin, le script final montre un moyen alternatif de créer de petites applications facilement incluables dans Android.
La piste SL4A est à creuser pour d'autres projets et deviendra peut être une constante chez moi pour les projets demandant une interface mobile rapidement développée.