Eloark, mon nouveau robot hybride Raspberry Pi & Arduino écrit en Python
Il y a presque un an je finissais Discovery, mon premier robot autonome. Le passage au hackathon Nao 2013 de la Cité des Sciences a considérablement changé ma vision de la robotique domestique : connexion à Internet, interaction avec son environnement et l’Humain. Construire mon nouveau robot m’a appris plein de choses
J’avais gagné un Arduino-compatible grâce à un concours DFRobot, gracieusement expédié par un revendeur français. Peu après Guillaume m’avait offert un Raspberry Pi B ainsi qu’un boîtier qu’il avait imprimé en 3D avec le matériel de sa boite. Il ne m’a pas fallu très longtemps pour savoir quoi en faire : lisez la suite pour le découvrir !
Définir les grandes lignes d’un projet est le moment le plus excitant : tout est possible. Faire un brainstorming avec soit même prend des semaines, c’est un moment de la vie où les idées fusent et se croisent à tout moment de la journée, surtout quand on n’a rien pour noter. Certains environnements sont plus adaptés que d’autres au développement de l’imagination et l’important est de vous y sentir détendu.
Concept initial
En terme d’objectif, je me suis inspiré de mon expérience sur Nao. À ce jour, j’aimerais beaucoup découvrir le framework libre de Darwin-OP que l’on devrait retrouver sur un dérivé nommé Jimmy. En attendant voici ce que j’ai dégagé pour mon proof of concept :
- pilotage par le réseau à l’aide du protocole HTTP
- interface web de contrôle à l’aide de joysticks virtuels tactiles
- bouton de connexion permettant à un seul utilisateur à la fois de piloter
- retour vidéo d’une webcam embarquée sur une page publique pour tous le monde
Et plus tard :
- une page de statistiques (graphs de températures, uptime, load average, load en fonction du temps, …)
- reconnaissance vidéo à l’aide d’OpenCV (suivi d’objets, de visages, …)
- IA de pilotage automatique par reconnaissance vidéo
- reconnaissance d’empreintes sonores à la Shazaam pour avoir des commandes en jouant des sons depuis un téléphone
- text to speech pour signaler son état (IP, position, objets reconnus)
- speech to text pour avoir des commandes vocales
- amélioration de l’IA pour être capable d’apprentissage
Le code a été pensé dès le départ avec ces impératifs et découpé en modules indépendants exécutés dans des threads. Ce planning est prévisionnel et a encore le temps d’évoluer d’ici que je trouve le temps d’y arriver.
Pour y parvenir, j’ai choisi de placer toute l’intelligence du robot dans un programme exécuté sur le micro ordinateur embarqué. L’Arduino a pour rôle de faire ce qu’on lui dit et reçoit ses ordres du programme via son port USB. Le Raspberry Pi peut ainsi servir pour exécuter bien d’autres fonctionnalités : serveur Web, streaming de webcam, base de données et pourquoi pas une centrale domotique !
Liste de courses
Il faut tout d’abord se construire un châssis avec une plateforme roulante de votre choix. Moi j’ai réutilisé des Meccano de mon enfance mais j’ai aidé des amis à se servir de vieilles voitures radiocommandées. Si vous n’avez rien de tout ça chez vous, pas de panique : soyez inventif, allez sur un site de petites annonces et cherchez des occasions type robot aspirateur. Voici les composants dont je me suis servi :
- Une variante de l’Arduino Leonardo appelée DFRobot Romeo v2. Elle a l’avantage de proposer une multitude d’interfaces pour connecter des servomoteurs, deux borniers moteurs ainsi que deux borniers d’alimentation externe pour moteurs et servos
- une batterie de modélisme à connecter dessus
- un servo moteur pour orienter une webcam
- Un micro-ordinateur type Raspberry Pi B, assez puissant pour faire de l’encodage vidéo (matériellement en H264)
- une batterie de rechargement USB possédant au moins deux ports USB, dont un de 1A et l’autre de 2A, par exemple une RAVPower 14000mAh
- un hub à alimentation externe, par exemple un D-Link DUB H4, pour alimenter les périphériques connectés au Pi
- un cordon USB A mâle vers DC 5.5×2.1mm pour alimenter le hub D-Link avec la batterie sur le port USB 2A
- deux cordons USB vers Micro USB pour relier l’Arduino au Hub et le Raspberry au port 1A de la batterie
- une webcam
Il est important de séparer l’alimentation du circuit informatique de celle du circuit « de puissance » car les moteurs provoqueront des appels de courant, et par conséquent des chutes de tension pouvant amener le circuit de commande à redémarrer aléatoirement. Voici de bons vieux schémas :
Commencer par le début
J’aime beaucoup avancer par petites itérations. Installer le Raspberry Pi et écrire un programme Arduino basique sont les deux premières choses que j’ai faites. Cela m’a permis de tester le bon fonctionnement de mon matériel, batteries et moteurs. Paramétrer le Raspberry Pi en Wifi m’a demandé quelques recherches supplémentaires pour profiter des avantages du roaming et ainsi ne plus avoir à jongler entre mes différents points d’accès.
Puis j’ai commencé à faire des choix techniques. Ayant choisi de faire mon programme en Python, j’ai d’abord planché sur un moyen de piloter la carte Arduino. Cela se fait très simplement via la bibliothèque Nanpy, livrée avec un firmware à téléverser sur l’Arduino. J’ai fait un article séparé pour la présenter et la comparer avec les solutions alternatives utilisant le firmware Firmata.
J’ai ensuite réfléchi à la manière dont piloter la carte Raspberry Pi (qui pilote à son tour l’Arduino, vous suivez ? :D). Ce problème revient à se demander comment transmettre de l’information entre deux ordinateurs modernes, ce qui est relativement trivial. J’avais déjà bien assez de pain sur la planche pour me lancer dans la rédaction de mon propre protocole (même basique) qui aurait impliqué un client spécifique pour communiquer.
Au lieu de ça, j’ai choisi le bon vieux protocole HTTP pour mille raisons : implémentations à foison sur tout type de terminaux, support dans tous les langages, livré avec un langage de balisage et un langage de script qui permettent de créer très rapidement des interfaces, etc.
J’avais besoin d’une bibliothèque légère, très basique, simple à utiliser. J’ai choisi Bottle pour cette tâche. Là encore, j’ai rédigé un article séparé pour comparer les autres solutions trouvées.
Il m’a fallu trouver une base de données, car Bottle ne gère pas le stockage de session. La contrainte de travailler sur un système installé sur une carte SD sont les accès en écriture : ils usent le support et ralentissent le système. J’ai pris Redis principalement pour son stockage volatil en Ram : les écritures se font sur demande ou à la fermeture de la base. Le risque de perdre des données existe mais un robot ne manipule aucune donnée critique. C’est une base NoSQL de type clé/valeur, on y stocke donc des variables (hash compris) et c’est tout !
Pour finir, il me fallait pouvoir contrôler l’API que j’avais l’intention de créer (une simple URL retournant une page vide dont les paramètres transmettent la direction des moteurs à faire appliquer par le serveur) sur tout terminal supportant HTML5, tactile compris. Je me suis orienté vers virtualjoystick.js, une superbe petite bibliothèque de joysticks virtuels qui fonctionne parfaitement sur mon téléphone Firefox OS ainsi que sur PC à l’aide d’une souris. Comme à mon habitude, j’aime prendre le temps de comparer les alternatives, c’est chose faite.
La gestion du streaming vidéo de la webcam a été tranchée trop tardivement pour pouvoir être mise en place dans les temps. Il me fallait quelque chose d’intégrable dans une page web, transportable par HTTP. Peu de formats vidéo répondent à cet impératif. Voici le dilemme : vous avez une application qui s’ouvre dans n’importe quel navigateur, mais aucun standard n’impose le format vidéo:
- OGV, libre mais inefficace en qualité et en poids ?
- WEBM, touché par des brevets, Open Source mais plus gourmand ?
- MP4, criblé de brevets mais souvent supporté matériellement ?
Pour avoir expérimenté avec un flux vidéo en 280p, l’encodage est quelque chose de très consommateur pour un processeur ARM11. Le Raspberry Pi n’en est tout simplement pas capable. En revanche il dispose d’une puce dédiée à ce travail, capable d’encoder/décoder du 1080p en MP4. Reste à trouver une application permettant de le réaliser, pour l’heure à ma connaissance seul le logiciel gstreamer sait le faire (API OpenMax). Hors c’est une telle usine à gaz qu’il m’a été impossible d’en comprendre le principe.
J’ai contourné le problème et me suis rabattu sur le format le plus universel à ce jour : le Motion JPEG (ou mjpeg), un flux d’images jpg indépendantes qui se remplacent les unes les autres dans la balise HTML img. Oui, c’est moche. VLC sait faire cela via son utilitaire en lignes de commande cvlc. L’inconvénient : il me semble que les images sont écrites sur le support de stockage au lieu d’être acheminées directement de la caméra sur le réseau. J’ai rédigé un billet entrant un peu plus loin dans le débat des formats vidéo pour HTML5.
Lancer des programmes externes et communiquer avec eux via un pipe se fait très bien avec le module subprocess
de Python. À terme, OpenCV devrait venir s’intercaler entre la source vidéo et le flux streamé pour déterminer comment diriger le robot, comme l’illustre le schéma ci-dessous :
Des tests au déploiement
On se retrouve vite à vouloir faire exécuter son programme par le Raspberry Pi plutôt que par l’ordinateur qui sert au développement. Plusieurs méthodes pour cela :
- coder directement sur les fichiers présents sur le Raspberry Pi au travers d’SSHFS (leeeent)
- coder depuis un ordinateur, pusher sur GIT, ouvrir un terminal et puller les révisions sur le Raspberry Pi
- coder depuis un ordinateur et utiliser un script « hook » post-push pour déclencher un pull sur le Raspberry Pi
J’ai jusque là exclusivement utilisé la seconde option, la troisième serait bien plus commode mais je ne vois pas comment la mettre en place sachant que l’IP du robot change selon les points d’accès Wifi.
Une fois en place, il ne reste qu’à gérer l’envoi d’un mail contenant l’URL de l’interface web pour faire d’une pierre deux coups : connaître l’IP de la machine et pouvoir tester l’interface web. Python le fait nativement en moins de dix lignes.
Dernière chose à ne pas oublier : automatiser le lancement du programme. Deux écoles : crontab et /etc/init.d/ j’ai choisi le crontab @reboot qui se lance à chaque démarrage du système d’exploitation.
Choix d’une licence
C’est la première fois que je distribue un Logiciel Libre. Libre de droit, libre de mon droit d’auteur. Proposer les sources ouvertes de ce projet est une démarche pédagogique, les proposer avec une licence permet à quiconque de les modifier, de les partager, de les vendre au travers d’une page dans un magazine, d’un blog contenant de la publicité. La licence que j’ai choisi pose quelques conditions à cela dont l’une est de conserver la paternité.
Poser un cadre légal pour quelques lignes de code n’est pas utile à proprement parler. Ce que j’ai créé existe en partie parce que toutes les briques que j’ai utilisé sont libres. Par contre j’aime beaucoup l’idée qu’une communauté puisse se former autour d’un projet, c’est pour cela que je tiens à diffuser la paternité du code : retrouver l’auteur originel pour discuter avec lui n’est possible que si tout le monde joue le jeu en passant le flambeau.
Choisir une licence est quelque chose de difficile, on peut remercier les auteurs de choosealicense et de Veni Vivi Libri. Je voulais quelque chose de permissif et simple à comprendre. La Simplified BSD License, datant d’avant la reconnaissance des brevets logiciels aux US, est une licence non copyleft en opposition à la GPL. J’aimais bien l’idéologie, mais je m’attendais à quelque chose moins brut de décoffrage, de plus sympa.
Je me suis attelé à mettre en place la CeCILL-B en anglais, la FAQ explique cela très bien. C’est une transposition de la philosophie de la Simplified BSD License en droit français, avec une préoccupation plus prononcée pour le respect de la paternité ainsi qu’un engagement du créateur qu’en cas de litige, 30j de délais seront accordés avant résiliation du contrat. Les licences CeCILL proposent aussi une version de la GPL et LGPL en droit français.
La première étape est de vérifier qu’aucune bibliothèque utilisée n’est publiée sous une licence incompatible (ex: une copyleft, qui interdit de sous licencier du code). Bottle, Redis-py et virtualjoystick.js sont sous MIT, une licence quasi identique à la Simplified BSD License. Il faudrait ensuite s’assurer la paternité et l’antériorité de l’œuvre en la protégeant de diverse façon décrites sur cette page. Merci aux auteurs de cette mine d’infos.
Bien que tout soit public sur Internet, ce que nous produisons peut être défendu par la juridiction française dans la mesure où le site causant l’infraction est accessible depuis la France et hébergé dans l’un des États membres de l’UE. En dehors, il appartiendra au pays du site fautif de reconnaître la juridiction française (indiquée par la licence CeCILL). Utiliser une licence garanti donc avant tout que vous concédez la libre exploitation de vos œuvres. Expliciter ses intentions est même une condition sine qua non à toute nouvelle production de Logiciel Libre : les entreprises ont besoin de sécurité.
Présentation des résultats
Voici finalement la raison d’être de cet article : le code source !
Guide d’installation : sur le lien ci-dessus vous trouverez un bouton de téléchargement, dézippez, ouvrez une console, tapez « ./main.py », et voilà Visitez http://127.0.0.1:8080 et admirez. N’ayez pas peur de regarder le code, le Python se lit facilement.
J’ai réalisé trois vidéos durant le développement, à la suite sur cette playlist :
Ce n’était pas facile de filmer et de faire la démo en même temps, d’autre part je n’ai retrouvé chez moi que d’antiques batteries de modélisme qui n’avaient pas été entretenues pendant plus de sept ans. Le résultat n’est pas super réactif j’en conviens !
Pour aller plus loin
Je ne me suis pas tout de suite attelé à la gestion audio du port Jack en Python, mais il serait cool de pouvoir parler dans un micro qui soit émis dans des enceintes branchées sur le robot. J’ai listé en début d’article ce que je pourrais faire par la suite, il faudra patienter un peu.
Il me reste un tas d’applications à essayer, j’attends surtout vos réactions et vos sentiments sur ce projet pour glaner de nouvelles idées. À bientôt !