Les cookies assurent le bon fonctionnement de nos services. En utilisant ces derniers, vous acceptez l'utilisation des cookies. En savoir plus

Un assistant personnel qui réagit à la voie

Sauvegarder: 3 J'aime

Partager:

Difficulté:

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

Matériel :

Budget : 1€

Etape 1 : Une grosse boite pour mettre tout ça

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 


Etape 2 : Les deux yeux

<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


Etape 3 : Le microphone

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!

Etape 4 : Les oreilles : les haut parleurs

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!


Etape 5 : Installation

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



Etape 6 : Principes de fonctionnement

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. 


Etape 7 : Votre paramètrage : le Hotword

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




Etape 8 : Votre paramétrage : config.py

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

Etape 9 : Les plugins

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)

Etape 10 : La reconnaissance vocale

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

Etape 11 : En passant par Node-red c'est plus simple

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":""}]

Etape 12 : Pour allez plus loin

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....


Etape 13 : Démonstration

Petit démo

Sources :

https://pypi.python.org/pypi/SpeechRecognition https://snowboy.kitt.ai https://github.com/FredThx/nodemcu_iot/ https://pypi.python.org/pypi/retinasdk


dave
03 - 12 - 2017 : 00:00

Bonjour, le service google API est payant n'est-ce pas? Merci

Sauvegarder: 3 J'aime

Partager:

Recevez une fois par mois les meilleurs tutoriels Déco dans votre boîte mail


Ces tutoriels devraient vous plaire

Arduino et Porte automatique de poulailler
Un capteur de température/humidité en WIFI avec une page Web
Comment fabriquer son filament pour imprimante 3D