Cela fait 5 ans que mon objectif de geek est de réaliser un assistant personnel à qui l'on puisse parler en français et qui puisse réaliser toutes les actions possibles, d'éteindre la lumière à dire la quantité de fuel qu'il reste dans la cuve ou le temps qu'il fera demain. Il y a pléthore de projets commerciaux (amazon echo par exemple) ou pas (gladys, jarvis, ...). Mais aucun ne répondais à mes attentes. Pendant les 5 ans de gestation (en fait j'ai d'autres activités), j'ai appris à maitriser : - raspberry pi et linux - python - les modules esp8266 et lua - node-red et javascript - plein de capteurs - mqtt - ..... Pour la gestion de la parole et de l'analyse linguistique je cherchais des solution en locale pour ne pas être dépendant des gros acteurs du web. Mais les avancées du Deep Learning ces dernières années et les mises à disposition souvent gratuitement d'API très puissantes font qu'il est difficile de trouver des solutions alternative à google et compagnie. J'ai donc abdiqué et dans le projet ci-dessous, j'utilise les ressources du web. Dans ce tuto, je détaillerais la création de l'assistant en lui même, pour ce qui est des modules spécifiques, reportez vous aux tutos http://ouiaremakers.com/les-croquettes-du-chat-en-iot/ http://ouiaremakers.com/mes-refrigerateur-congelateur-en-mode-connecte/ et aux exemples ici https://github.com/FredThx/nodemcu_iot/ Comme l'assistant est écrit en python, on va retrouver l'ensemble des fichiers ici : https://pypi.python.org/pypi/FSTA
Budget : Non défini
Il n'est pas naturel du tout de parler dans le vide (ou alors à soit même) sinon c'est une maladie a soigner aux électrochocs. Je pense qu'il faut mieux matérialiser l'assistant et lui donner un aspect plus ou moins humanoïde.
Pour ma part, j'ai mis une bouche, un nez, des oreilles et des cheveux. Bon la bouche sert de micro et les oreilles de haut parleurs, mais c'est pas gênant!
J'ai mis les tous les fichiers nécessaires à l'impression de la boite ici https://pypi.python.org/pypi/FSTA . Voir ressources/3D.
Avec une imprimante 3D imprimer les 3 pièces de la boite.
Placez tous les élements dans la boite :
- la raspberry pi en bas
- les deux yeux (8x8 led MAX7219) qui vont communiquer en SPI
- le microphone (eye sony ps3) que l'on va mettre tête en bas (ou plutôt pied en haut!)
- le petit ampli pam8403
- les deux haut-parleurs à encastrer dans du placo
comme le eye sony ps3 est livré avec une belle longueur de cable usb : ça va nous prendre toute la place
<strong>Le branchement</strong>
On branche l'oeil gauche sur le raspberry pi :
Vcc : 5V
GND : 0V
DIN : GPIO10 (MOSI)
CS : GPIO08 (SPI CSO)
CLK : GPIO11 (SPI CLK)
Et l'oeil droite en cascade sur l'oeil gauche.
<strong>Logitiel</strong>
Les lib python :
sudo pip install max7219
avec sudo raspi-config, activer SPI
Pour les curieux du code : voir FSTA/eyes.py
Branchement en usb.
Le gros avantage de ce micro est d'être relativement économique et fait pour lui parler de loin.
En plus la caméra fait office de nez!
L’intérêt de ces HP est d'être bien finis et facile à monter.
Il existe plusieurs couleurs...
Leur défaut : le prix
Nous ne sommes pas sur de la HI-FI : c'est juste pour parler. On va donc utiliser la prise jack comme sortie audio et envoyer le signal sur un petit ampli de 3W (PAM8403) pour sortir vers les deux HP.
Branchement du APM8403
5V et GND : sur le 5V et GND du raspberry pi
IN : GND, L et R : sur une prise jack mâle branchée au raspberry
OUT : sur chacun des haut parleur (le fil noir sur le -, le fil rouge sur le +)
Attention, il ne faut pas relier les - au GND!
Maintenant que tout la matériel est installé, on met le jus et ça marche.
En fait non, il y a juste quelques détails à mettre en place.....
Flasher la carte SD avec une image de la version la plus récente de raspian (version lite, c'est à dire sans interface graphique), avec raspi-config, francisez la bête, activez le SPI, SSH, changer le mot de passe et le nom d'hôte, ... (il existe des tonne de tuto la dessus).
Mise à jour :
sudo apt-get update
sudo apt-getupgrade
L'audio
sudo apt-get install python-pyaudio python3-pyaudiosox
pip install pyaudio
sudo apt-get installlibatlas-base-dev
Les librairies python
sudo pip install max7219
sudo pip install FSTA
L'assistant est activé par un mot clef (chez moi, c'est "maison", mais vous pouvez mettre "Jarvis", "ok google" ou tout ce que vous voulez.
Pour la détection, on va utiliser snowboy hotword detection : https://snowboy.kitt.ai/
Ensuite, l'assistant enregistre votre ordre en audio avec https://pypi.python.org/pypi/SpeechRecognition
Puis c'est google qui nous transforme ça en texte (on peut aussi faire ça ailleurs)
Là on va chercher dans une liste de phrases type (qu'il vous faudra paramétrer) laquelle ressemble le plus à celle dictée. Pour cela on va utiliser http://www.cortical.io/
Quand le scénario est trouvé, l'assistant exécuté une action (qu'il faudra aussi paramétrer).
Les actions sont soient écrites en python, soit il s'agit juste d'un envoie de message mqtt. Dans ce dernier cas, une bonne solution est d'utiliser red-node (https://nodered.org/) pour gérer les actions et intelligence.
Si vous opter pour la solution mqtt, il faudra installer un broket (ex : https://mosquitto.org/)
Au choix, vous pouvez tout installer sur la même raspberry pi ou placer broker mqtt et/ou node-red ailleurs. Sur une raspberry pi 3 avec tout dessus ça fonctionne bien, Tout séparer sur plusieurs serveurs ça marche aussi.
Le ou les hotwords.
J'ai prévu que l'on puisse avoir plusieurs hotword pour invoquer tel ou tel groupe de scénarios.
Exemple :
- "télévision" : "éteint toi"/"allume toi"/"met arte" ....
- "lumière" : "éteint toi"/"allume toi"
Ce n'est pas ce que j'utilise chez moi, mais c'est une option qui peut-être intéressante. Chez moi, j'invoque juste "maison".
Pour créer un hotword, vous allez chez https://snowboy.kitt.ai/, vous créer un compte et soit vous choisissez un mot existant dans votre langue, soit vous créer votre propre mot. A noter que sauf quelques rares exceptions, il vous faudra enregistrer une diction du mot pour pouvoir télécharger un fichier hotword.
Vous télécharger le fichier hotword (ex : maison.pmdl) et le placez dans /opt/FSTA/ressources/hotwords
Dans le fichier /opt/FSTA/config.py
- mqtt_host : l'IP de votre broker mqtt (si c'est sur la même machine : 127.0.0.1)
- mqtt_base_topic : utilisé pour envoie de messages automatiques
- si vous n'avez qu'un oeil : eyes = eye(1), si vous n'avez pas d'oeil : eyes = None
J'ai laissé mes API-key google et cortical dans les sources. Mais si tout le monde les utilise, je pense que ça risque de ne pas allez (google limite le nombre d'utilisation de leur API pour les comptes gratuit).
=> aller chez google (https://console.developers.google.com) et cortical (http://www.cortical.io/resources_apikey.html) pour créer des apikey et remplacer les.
On peut aussi changer
- language = 'fr-FR' par defaut
- listen_timeout = 5 par defaut
- mqtt_port = 1883 par defaut
L'assistant est composé de
- une installation (que nous venons de décrire ci-dessus)
- des groupes qui correspondent à des hotwords
- des scénarios (exemple : allumer la lumière)
- des phrases (ex : "allume la lumière", "on n'y voit rien ici")
- des actions (ex: envoyer un message pour que la lumière s'allume, dire "que la lumière soit")
Les scénarios et groupes sont décrit dans des plugins dans le répertoire /opt/FSTA/ressources/rules
Plutôt qu'un long discours, je vous laisse regarder les exemples ... C'est du python
(si c'est pas clair, demandez et je clarifierais)
Ce sont des plugins : on peut les changer quand le programme tourne (par contre pour le moment, la mise à jour ne se fait qu'après avoir dictée une phrase)
C'est là que je me suis le plus amusé : trouver un algorithme que permette de réaliser de la reconnaisance syntaxique.
Le principe est que les scénarios proposent une liste de pharses types qu'il faut comparer à la phrase dictée. Après avoir essayé mes propres solution (qui avaient tendances à faire mouliner sérieusement la framboise), j'ai trouvé une API qui fait ça tout seul : http://www.cortical.io/
.
On envoie la phrase dictée et toutes les pharses types et il nous rentourne une liste de distances entre les pharses type et la phrase dictée : il n'y a plus qu'a trier.
Pour plus d'info : aller voir le code https://pypi.python.org/pypi/FSTA
Vous pouvez coder toutes vos actions directement en python.
Une autre solution que j'utilise beaucoup est d'utiliser le couple MQTT - Node Red pour réaliser les actions.
Dans le plugin, on envoie juste un message MQTT.
Dans Node Red, on récupère le message et l'on traite. Si besoin de faire parler l'assistant, on envoie un message MQTT à l'assistant
Quelques une de mes flow node-red :
Pour tester : copier le texte ci-dessous et dans node-red : menu/import/clipboard/...
C'est ma config : faudra surement importer quelques modules et adapter un peu.
[{"id":"2a6ede4e.100272","type":"mqtt in","z":"c624c89c.f72d88","name":"","topic":"T-HOME/QUESTION","qos":"0","broker":"a4c87fe8.0452d","x":130,"y":122,"wires":[["fcf5475.6af85b8"]]},{"id":"fcf5475.6af85b8","type":"switch","z":"c624c89c.f72d88","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"HEURE","vt":"str"},{"t":"eq","v":"DATE","vt":"str"},{"t":"eq","v":"FUEL","vt":"str"},{"t":"eq","v":"CAVA","vt":"str"},{"t":"eq","v":"METEO","vt":"str"},{"t":"eq","v":"CHUCK","vt":"str"}],"checkall":"true","outputs":6,"x":340,"y":122,"wires":[["fe897337.fb3ac"],["de3650d9.3ce33"],["e2eab9b1.787fe"],["a0dc3e60.581ca8"],["a9351f97.dd1218"],["93b7b7c4.22c77"]]},{"id":"fe897337.fb3ac","type":"function","z":"c624c89c.f72d88","name":"Heure","func":"var moment = global.get('moment');\nmoment.locale('fr');\nmsg.payload = \"Il est \" + moment().format('H[ heure ]m');\nreturn msg;","outputs":1,"noerr":0,"x":550,"y":62,"wires":[["b8baeb81.201858"]]},{"id":"b8baeb81.201858","type":"mqtt out","z":"c624c89c.f72d88","name":"","topic":"T-HOME/PI-SALON/SPEAK","qos":"0","retain":"false","broker":"a4c87fe8.0452d","x":1000,"y":122,"wires":[]},{"id":"de3650d9.3ce33","type":"function","z":"c624c89c.f72d88","name":"Date","func":"var moment = global.get('moment');\nmoment.locale('fr');\nmsg.payload = \"Nous sommes le \" + moment().format(\"dddd Do MMMM YYYY\")\nmsg.payload = msg.payload + \" et il est \" + moment().format('H[ heure ]m');\nreturn msg;","outputs":1,"noerr":0,"x":550,"y":102,"wires":[["b8baeb81.201858"]]},{"id":"e2eab9b1.787fe","type":"function","z":"c624c89c.f72d88","name":"Fuel","func":"if (context.global.VALEURS.CuveFuel){\n msg.payload = \"Il y a \" + Math.round(context.global.VALEURS.CuveFuel.payload) + \" litres de fuel dans la cuve.\";\n}else{\n msg.payload = \"Je n'en ai aucune idée.\"\n}\nreturn msg;","outputs":1,"noerr":0,"x":550,"y":142,"wires":[["b8baeb81.201858"]]},{"id":"a0dc3e60.581ca8","type":"function","z":"c624c89c.f72d88","name":"CAVA","func":"msg.payload = \"Ouais, ça boume grave.\"\nreturn msg;","outputs":1,"noerr":0,"x":550,"y":182,"wires":[["b8baeb81.201858"]]},{"id":"a9351f97.dd1218","type":"change","z":"c624c89c.f72d88","name":"Météo","rules":[{"t":"set","p":"payload","pt":"msg","to":"VALEURS['T-HOME/METEO'].payload","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":222,"wires":[["b8baeb81.201858"]]},{"id":"1682b644.fed61a","type":"function","z":"c624c89c.f72d88","name":"set VALEURS","func":"if (!context.global.VALEURS) {\n context.global.VALEURS={};\n}\nif (!context.global.VALEURS[msg.topic]){\n context.global.VALEURS[msg.topic]={};\n}\ncontext.global.VALEURS[msg.topic].date = new Date();\ncontext.global.VALEURS[msg.topic].payload = msg.payload;\n","outputs":"0","noerr":0,"x":921,"y":674,"wires":[]},{"id":"345a1e7c.9b23d2","type":"openweathermap","z":"c624c89c.f72d88","name":"","wtype":"forecast","lon":"","lat":"","city":"Epinal","country":"France","language":"fr","x":469,"y":673,"wires":[["81398159.08b5e","36dca140.e6bebe"]]},{"id":"fbb6b79e.632db8","type":"inject","z":"c624c89c.f72d88","name":"","topic":"T-HOME/METEO","payload":"","payloadType":"date","repeat":"3600","crontab":"","once":true,"x":170,"y":673,"wires":[["345a1e7c.9b23d2"]]},{"id":"81398159.08b5e","type":"function","z":"c624c89c.f72d88","name":"","func":"var text = \"Météo à Epinal. \";\ntext += \"Aujourd'hui, le temps est \" + msg.payload[0].weather[0].description + \". \";\ntext += \"La température est comprise entre \" + Math.round(msg.payload[0].temp.min)+\" et \"+Math.round(msg.payload[0].temp.max) +\" degrés. \";\ntext += \"Demain, le temps sera \" + msg.payload[1].weather[0].description + \". \";\ntext += \"La température sera comprise entre \" + Math.round(msg.payload[1].temp.min)+\" et \" + Math.round(msg.payload[0].temp.max) + \"degrés.\";\ntext += \"Après demain, le temps sera \"+msg.payload[2].weather[0].description+\".\";\ntext += \"La température sera comprise entre \"+Math.round(msg.payload[2].temp.min)+\" et \"+Math.round(msg.payload[0].temp.max)+\"degrés.\";\nmsg.payload = text;\nreturn msg;","outputs":1,"noerr":0,"x":696,"y":673,"wires":[["1682b644.fed61a"]]},{"id":"93b7b7c4.22c77","type":"http request","z":"c624c89c.f72d88","name":"www.chucknorrisfacts.fr","method":"GET","ret":"obj","url":"https://www.chucknorrisfacts.fr/api/get?data=tri:alea;nb:1","tls":"","x":610,"y":262,"wires":[["5b97c488.9a6b3c"]]},{"id":"5b97c488.9a6b3c","type":"change","z":"c624c89c.f72d88","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[0].fact","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":990,"y":242,"wires":[["b8baeb81.201858"]]},{"id":"9954b98f.5f7e98","type":"http request","z":"c624c89c.f72d88","name":"blague","method":"POST","ret":"txt","url":"http://192.168.10.10/blague.html","tls":"","x":390,"y":302,"wires":[["9f3abbd4.5492b8"]]},{"id":"f3f66570.1b218","type":"inject","z":"c624c89c.f72d88","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":171,"y":253,"wires":[["93b7b7c4.22c77"]]},{"id":"9f3abbd4.5492b8","type":"debug","z":"c624c89c.f72d88","name":"","active":true,"console":"false","complete":"true","x":792,"y":296,"wires":[]},{"id":"32b79850.183af8","type":"function","z":"c624c89c.f72d88","name":"","func":"var iconv = global.get(\"iconvlite\");\nvar str = iconv.decode(msg.payload, 'latin1');\nmsg.payload = str.toString('utf8');\nreturn str;","outputs":1,"noerr":0,"x":770,"y":202,"wires":[[]]},{"id":"36dca140.e6bebe","type":"debug","z":"c624c89c.f72d88","name":"","active":true,"console":"false","complete":"true","x":622.5,"y":783.2999954223633,"wires":[]},{"id":"22c36520.aafd52","type":"mqtt in","z":"c624c89c.f72d88","name":"","topic":"T-HOME/SALON/LISTEN/maison/before","qos":"2","broker":"a4c87fe8.0452d","x":217,"y":390,"wires":[["8c0ee5c8.b97268"]]},{"id":"8c0ee5c8.b97268","type":"change","z":"c624c89c.f72d88","name":"mute","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"volume\":\"mute\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":474,"y":390,"wires":[["76666a4f.e3ec44"]]},{"id":"d8c80995.547e38","type":"mqtt in","z":"c624c89c.f72d88","name":"","topic":"T-HOME/SALON/LISTEN/maison/before_s2t","qos":"2","broker":"a4c87fe8.0452d","x":228,"y":452,"wires":[["3986a72a.956d4"]]},{"id":"3986a72a.956d4","type":"change","z":"c624c89c.f72d88","name":"unmute","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"volume\":\"unmute\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":487,"y":451,"wires":[["76666a4f.e3ec44"]]},{"id":"76666a4f.e3ec44","type":"sonos-control","z":"c624c89c.f72d88","playnode":"5aeb3072.549be8","name":"","mode":"","track":"","volume":"","volume_value":"","x":676.5,"y":413,"wires":[]},{"id":"a4c87fe8.0452d","type":"mqtt-broker","z":"","broker":"192.168.0.15","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":""},{"id":"5aeb3072.549be8","type":"sonos-config","z":"","name":"SALON","ipaddress":"192.168.0.14","port":""}]
Mon assistant tourne depuis plusieurs mois.
Je l'utilise tous les jours pour la musique, la lumière. Par contre toute la famille n'a pas réussit à adopter l'usage...
Il existe encore pas mal de réticences à parler tout haut à une machine.
Le seul point vraiment agaçant : quand l'assistant écoute la TV il a tendance à se déclancher intempestivement. J'ai trouver la solution : plus de TV!
Ensuite, ce qu'il manque c'est une analyse syntaxique avec des variables. Exemple : "Maison, Peux tu mettre la musique des {Wampas | Justin Bieber | Elvis}?" ou "Peux tu me donner la température du {salon | exterieure | chambre-...}"
Il faudrait que j'y passe encore quelques heures....
Petit démo
https://pypi.python.org/pypi/SpeechRecognition https://snowboy.kitt.ai https://github.com/FredThx/nodemcu_iot/ https://pypi.python.org/pypi/retinasdk
Vues: 3292
J'aime: 3