Hola a todos, en este segundo post de la serie meRobot , quisiera enseñarles como lograr que nuestro robot «pueda hablar», reconocer comando de voz y ademas traducirlos. Pero antes de empezar es necesario que previamente hayamos instalado nodejs sobre nuestra raspberry, les comparto entonces un pequeño vídeo donde explico paso a paso como hacerlo, como crear una carpeta compartida usando samba y como acceder dicha carpeta desde visual studio code en nuestra maquina de desarrollo de forma remota:
Si siguieron los pasos en el vídeo, ya podemos entonces desde visual studio code acceder al folder compartido: Nota: es importante que tanto la raspberry como su equipo estén conectados a la misma red wifi (estar dentro de la misma red) para que haya visibilidad entre ambos.
- en visual studio code, vaya al menú file, open folder y en la siguiente ventana siga las instrucciones a continuación:
- escriba en el cuadro de dirección, dígite \\raspi o el \\ el hostname que le haya asignado a su raspberrypi y presione la tecla enter, notará que le aparecerán todas las carpetas compartidas que haya configurado con samba en el explorador de windows,
- seguidamente acceda a la carpeta de la lista que usted desee y cree dentro de esta una que se llame meRobot, luego selecciónela y haga click sobre el boton select folder para que se abrá en visual studio code, allí pondremos todos los archivos de código fuente .
Hora de codificar
Dado que nuestra carpeta físicamente no esta en nuestra maquina de desarrollo, debemos acceder via ssh a nuestra raspberry para poder ejecutar nuestra app y instalar sus dependencias, vía node y npm respectivamente. Para ello usaremos putty.
Hecho lo anterior, quisiera antes de continuar, aclarar un poco como sera el modo de trabajo, nuestra aplicación la codificaremos remotamente desde visual studio code (en nuestra maquina). pero cada vez que hagamos un cambio y queramos probar como va todo, debemos desde la terminal ssh ejecutar el comando:
node –harmony-async-await index.js
Para habilitar la funcionalidad de «tts» o Text To Speech en nuestra raspberry, instale el modulo Pico TTS(Google Android TTS engine) desde la terminal(putty):
sudo apt-get install libttspico-utils
Luego conecta a la raspberry unos auriculares o parlantes y prueba, ejecutando el siguiente comando:
pico2wave -w speak.wav -l es-ES «Hola a todos mis seguidores» && omxplayer speak.wav
Luego de haber instalado correctamente el paquete pico2wave sobre nuestra raspberry, y haber realizado la prueba anterior, estamos listos para empezar a programar nuestro robot, empecemos instalando los siguientes paquetes vía npm (recuerden desde la terminal en putty):
«dependencies»: {«axios»: «^0.15.3»,«bluebird»: «^3.5.0»,«boom»: «^4.3.0»,«guid»: «0.0.12»,«hapi»: «^16.1.0»,«inert»: «^4.1.0»,«ngrok»: «^2.2.6»,«querystring»: «^0.2.0»}
npm i –save axios bluebird boom hapi inert ngrok querystring guid
Luego agregue los siguientes archivos:
Archivo common.js: la función getToken, nos permite obtener el token de autenticacion para podernos conectar al api de bing speech recognition
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const axios = require("axios"); | |
module.exports = { | |
getToken : async(apikey)=>{ | |
var config = { | |
headers: {'Ocp-Apim-Subscription-Key': apikey} | |
}; | |
return axios.post("https://api.cognitive.microsoft.com/sts/v1.0/issueToken",{}, config); | |
} | |
} |
Archivo tts.js (Text to Speech):
Esta función recibe como parámetro una cadena que corresponde al texto que queremos que nuestro robot ¨repita¨ y como segundo parámetro el idioma en el que queremos que lo haga(esto es posible gracias al cliente Text 2 Speech pico2wave, el cual en paso anteriores explique su instalación sobre la raspberrypi)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var exec = require('child_process').exec; | |
var util = require("util"); | |
var talk = (message, lang) =>{ | |
lang = lang === "es" ? (' -l es-ES ') : (' -l en-US '); | |
const cmd = util.format("pico2wave -w speak.wav %s '%s' && sox speak.wav -r 48k speak.mp3 pitch -200 && omxplayer speak.mp3", lang, message) | |
console.log(cmd); | |
exec(cmd, function(error, stdout, stderr) { | |
if(error) | |
console.error(error); | |
}); | |
} | |
module.exports = { talk : talk }//export the talk functions to Robot.js file, This will allow him to speak |
Archivo stt.js (Speech to Text):
la función stt, recibe como parámetro el archivo .wav(audio) en bytes, que contiene el comando de voz que queremos que nuestro robot reconozca (el cual hemos capturado usando nuestro micrófono), y luego lo envía por post al api de bing speech recognition, para que lo convierta a Texto. antes usamos la función getToken, para obtener el token de autenticacion y poder realizar la petición.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const util = require("util"); | |
const Guid = require("guid"); | |
const querystring = require('querystring'); | |
const path = require("path"); | |
const bluebird = require("bluebird"); | |
const axios = require("axios"); | |
const fs = bluebird.promisifyAll(require("fs")); | |
const {getToken} = require("./common"); | |
const stt = async(BING_SPEECH_API_KEY, audioAsBInary)=>{ | |
var authToken = await getToken(BING_SPEECH_API_KEY), params = ""; | |
/* URI Params. Refer to the README file for more information. */ | |
var params = { | |
"scenarios":"smd", | |
"appid": "D4D52672-91D7-4C74-8AD8-42B1D98141A5", | |
"locale" :"es-ES", | |
"device.os": "wp7", | |
"version":"3.0", | |
"format": "json", | |
"instanceid":"565D69FF-E928-4B7E-87DA-9A750B96D9E3", | |
"requestid" : Guid.raw() | |
}; | |
var uri = "https://speech.platform.bing.com/recognize?"+querystring.stringify(params); | |
var config = { | |
headers:{ | |
"Authorization" : "Bearer " + authToken.data, | |
"content-type" : "audio/wav; codec=\"audio/pcm\"; samplerate=16000" | |
} | |
} | |
/*var audioAsBInary = await fs.readFileAsync("./speak.wav"); */ return await axios.post(uri,audioAsBInary,config ); //just making a request | |
} | |
module.exports = stt;//we need to export the function, because the idea is invoke the function from the index file |
Archivo Robot.js
Este archivo es una clase en la cual iremos agregando las funciones de nuestro Robot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const tts = require("./tts");//Text to Speech | |
const stt = require("./stt");//Speech to Text | |
class Robot{ | |
constructor(name, config){ | |
this.name = name; | |
this.config = config; | |
} | |
//just the phrase that you want the robot repeat for you | |
textToSpeech(phrase){ | |
tts.talk("Hi all from raspberry pi"); | |
} | |
//.wav file as binary, it comming from web interface | |
//we could be more strict and put all the code inside a try/ catch block, we are just | |
//assuming that everything'll be perfect :), is just a robot | |
async speechToText(audioAsBinary){ | |
var response = await stt(this.config.BING_SPEECH_API_KEY, audioAsBinary); return response.data.results; | |
} | |
} | |
module.exports = Robot; //we are exporting the class Robot, to be used at the index file |
Archivo config.js:
En el archivo config.js, iremos agregando los keys de las Apis que iremos usando en el transcurso de los tutoriales. para obtenerlos, vasta con seguir la documentación de la pagina de Microsoft cognitive services o el tutorial : Microsoft Cognitive Services Notes: Emotion API (Node.js)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"BING_SPEECH_API_KEY" : "<PUT YOUR BING SPEECH API>"//from https://www.microsoft.com/cognitive-services/en-US/subscriptions | |
} |
Archivo index.js:
Este es quizás el mas importante aquí definimos nuestro servidor hapi, el cual servirá como interfaz de comunicación entre nuestro robot y el mundo:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//const robot = require("./Robot")("Cyg"); | |
const hapi = require("hapi"); | |
const boom = require("boom"); | |
const path = require("path"); | |
const ngrok = require('ngrok'); | |
const bluebird = require("bluebird"); | |
const fs = require("fs"); | |
const Robot = require("./Robot"); | |
const config = JSON.parse(fs.readFileSync("./config.json","utf8")); | |
const meRobot = new Robot("Cyg", config); | |
const port = 8080; | |
const server = new hapi.Server(); | |
server.connection({ port: port, host: '0.0.0.0' }); | |
async function setup() { | |
await server.register(require("inert")); | |
} | |
ngrok.connect(port,function (err, url) { | |
setup().then(() => { | |
server.route({ | |
path: "/stt", | |
method: "POST", | |
config: { | |
payload: { | |
output: 'stream', | |
maxBytes: 1048576 * 10, /*10MB*/ | |
parse: true, | |
allow: 'multipart/form-data' | |
} | |
}, | |
handler: (request,reply)=>{ | |
if(request.payload.audio){ | |
var audioAsBinary = request.payload.audio._data; | |
//we order to the robot that please, convert the audio into Text | |
//for guessing the command that the user is sending, for example take a picture | |
meRobot.speechToText(audioAsBinary) | |
//handler to sucess request | |
.then((data)=>{ return reply(data)}) | |
//hanlder to error request | |
.catch((err)=> { console.log(err); return reply(err)}); | |
} | |
} | |
}); | |
//the route just send to user the index.html file where all happen | |
server.route({ | |
path: "/robot", | |
method: "GET", | |
handler: { | |
file: { | |
path: "./views/index.html" | |
} | |
} | |
}); | |
//once ngrok have generated the uri, the using it | |
//we redirect the user at /robot route | |
server.route({ | |
path: "/", | |
method: "GET", | |
handler: (request,reply)=>{ | |
return reply().redirect(`${url}/robot`); | |
} | |
}); | |
//this route let us handler the static content as | |
//jquery, axios and others frontend component (css, images etc.) | |
server.route({ | |
path: "/assets/{path*}", | |
method: "GET", | |
handler: { | |
directory: { | |
path: "./public" | |
} | |
} | |
}); | |
server.start(() => console.log(`the port is running at ${server.info.port}`)); | |
}) | |
}); | |
//process.on('SIGINT', function() { ngrok.disconnect(); ngrok.kill(); /* kills ngrok process */ }); |
El archivo app.js en el cual esta la lógica para acceder al micrófono, generar el archivo .wav y enviarlo al servidor y el resto de código, están en el repositorio en github:
https://github.com/haruiz/meRobot/
Esto es todo por ahora, cualquier inquietud o problema que tengan, no duden en contactarme
Saludos