Monday, February 08, 2016

Hay una fiesta en la casa de Alexa

Este post fué posteado originalmente en  There's a party at Alexa's place.


Este es mi primer blog del año…así que quería que fuera algo realmente bueno y extenso -:) Ya saben que me encanta la programación en R…pero también me encantan otras tecnologías…así que tomar varias de ellas y interconectarlas es lo que realmente me hace feliz.

Ahora…se estarán preguntándo por el título del post…”Hay una fiesta en la casa de Alexa”…bueno…quería describir el blog de una manera divertida…así que veamos que vamos a construir -;)


Tienen alguna idea? Basicamente…vamos a utilizar Amazon Alexa como nuestro UI…cuando preguntemos un comando…vamos a llamar a un servidor NodeJS en Heroku (Que dicho sea de paso tiene un cliente PhantomJS instalado)…este NodeJS va a llamar a un servidor R en Heroku (Utilizándo un servidor Rook)…y este servidor R va a llamar a HANA Cloud Platform para obtener información de vuelos y generar interesántes gráficos que van a ser retornados al servidor NodeJS el cual va a llamar a nuestro navegador de internet para mostrar los gráficos generados por nuestro servidor de R…por supuesto…al utilizar PhantomJS vamos a leer la página web generada en el navegador y esto va a ser enviado de vuelta a Amazon Alexa para que ella pueda leernos la respuesta…suficientemente intersánte para ustedes? Así lo espero -:) Me tomó más de 2 semanas tener todo esto listo y funcionándo…así que debería gustárles -:P

Así que…veamoslo en algunos cuantos pasos…

OBTENER UNA CUENTA DE HANA CLOUD PLATFORM

Ya deberían tener una…si no…simplemente entre aquí y creen una…

Luego…necesitamos descargar HANA Cloud Platform SDK extraerlo y modificar el archivo tools/neo.sh en la línea 57…

En vez de esto…

javaExe="$JAVA_HOME/bin/$javaCommand"

Debemos utilizar esto…

javaExe="$JAVA_HOME"

Por qué? Bueno…tendrá sentido más adelánte…o quizás tenga sentido ahora -;) Si tienen el cliente de SAP HANA instaládo…de otra manera puedes descargarlo de aquí y tomen en cuenta que vamos a tener que copiar el archivo ngdbc.jar

OBTENIÉNDO LA DATA QUE VAMOS A UTILIZAR

Como siempre…en casi todos mis blogs…vamos a utilizar tablas de modelo de vuelos…el cual por supuesto…no existe en HANA Cloud Platform…

La manera más sencilla (por lo menos para mí) fué acceder a un servidor R/3…y simplemente descargar las tablas como archivos XLS…convertirlos a archivos CSV y subirlos a HCP…

Y dicho sea de paso…por alguna razón mi R/3 no tenía American Airlines listado en la tabla SCARR…así que simplemente lo agregué -;)

Ahora…si no tienen acceso a un servidor de R/3...entonces pueden descargar las tablas en formato CSV en este enlace

CREAR EL SERVIDOR R EN HEROKU

Si no tienen instaládo el Heroku Tool Belt…vayan y consigánlo…

Pasos para instalar R en Heroku con capacidades gráficas
mkdkir myproject && cd myproject
mkdir bin
echo “puts ‘OK’ > config.ru
echo “source ‘http://rubygems.org’\n gem ‘rack’” > Gemfile
#Abre la carpeta de tu proyecto y modifica el archivo Gemfile para reemplazar “\n” con un salto de línea…
bundle install
git init . && git add . && git commit –m “Init”
heroku apps:create myproject –stack=cedar
git push heroku master
#Copia y pega el contenido de installR.sh en la carpeta /bin de tu proyecto
git add . && git commit –am “message” && git push heroku master
heroku ps:scale web=0

installR.sh
#!/bin/bash

function download() {
  if [ ! -f "$2" ]; then
    echo Downloading $2...
    curl $1 -o $2
  else
    echo Got $2...
  fi
}

set -e
 
r_version="${1:-3.2.3}"
r_version_major=${r_version:0:1}
 
if [ -z "$r_version" ]; then
  echo "USAGE: $0 VERSION"
  exit 1
fi
 
basedir="$( cd -P "$( dirname "$0" )" && pwd )"
 
# create output directory
vendordir=/app/vendor
mkdir -p $vendordir

# R
download http://cran.r-project.org/src/base/R-$r_version_major/R-$r_version.tar.gz R-$r_version.tar.gz
tar xzf R-$r_version.tar.gz

# build R
echo ============================================================
echo Building R
echo ============================================================
cd $basedir/R-$r_version/

./configure --prefix=$vendordir/R --with-blas --with-lapack --enable-R-shlib --with-readline=no --with-x=yes
make

cd /app/bin
ln -s R-$r_version/bin/R

rm R-3.2.3.tar.gz
rm -rf erb gem irb rake rdoc ri ruby testrb
rm ruby.exe

cd /app/bin/R-$r_version

rm -rf src
rm Make*
rm -rf doc
rm -rf tests
rm README ChangeLog COPYING INSTALL SVN-REVISION VERSION

Ahora…necesitamos hacer un paso muy importánte -:) Debemos instalar el totalmente asombroso heroku-buildpack-multi de ddollar.

heroku buildpacks:set https://github.com/ddollar/heroku-buildpack-multi.git

Luego de eso…debemos crear un par de archivos…uno llamado .buildpacks y el otro Aptfile.

.buildpacks
https://github.com/ddollar/heroku-buildpack-apt
https://github.com/heroku/heroku-buildpack-ruby
Aptfile
gfortran
libatlas-base-dev
libatlas-dev
liblapack-dev

Ahora viene la parte interesánte…instalar R -;)

Finalmente...instalándo R...
git add . && git commit –am “message” && git push heroku master
heroku ps:scale web=0
heroku run bash
cd bin/
./installR.sh

Con esto listo…vamos a tener todas las librerías que faltaban y que son necesarias para poder compilar R en el nuevo Cedar Stack de Heroku y además…vamos a tener un R finamente instalado y es más...con capacidades gráficas…pero por supuesto…aún no hemos terminado…

Instalándo las librerias de R
#Esto va a abrir R en Heroku…
R
#Esto va a instalar las librerías con sus dependencias correspondientes
install.packages("Rook",dependencies=TRUE)
install.packages("Cairo",dependencies=TRUE)
install.packages("maps",dependencies=TRUE)
install.packages("forecast",dependencies=TRUE)
install.packages("plotrix",dependencies=TRUE)
install.packages("ggplot2",dependencies=TRUE)
install.packages("ggmap",dependencies=TRUE)
install.packages("rJava",dependencies=TRUE)
install.packages("RJDBC",dependencies=TRUE)
q()

Perfecto…estamos casi listos -;) El problema con Heroku es que es de solo lectura…lo cual significa que una vez que te desconectas…vas a perder todo tu trabajo -:(

Así que…necesitamos hacerle un backup y mandarlo a otro lado…yo utilicé mi servidor de R en Amazon WebServices para esto…

Primero…debemos comprimir la carpeta bin así…

tar -cvzf bin.tar.gz bin

y luego debemos grabar el archivo en nuestro servidor externo…

scp -i XXX.pem bin.tar.gz ec2-user@X.X.X.X:~/Blag/bin.tar.gz

y por supuesto luego de eso lo necesitamos en nuestra carpeta de proyecto…así que debemos enviarlo desde nuestro servidor externo de vuelta hacia nuestra carpeta de proyecto, donde simplemente deberemos descomprimirlo…

scp -i XXX.pem ec2-user@X.X.X.X:~/Blag/bin.tar.gz bin.tar.gz

Una vez que esto está listo…debemos hacer algo realmente rápido…

Si todavía siguen en el Heroku bash…simplemente ejecuten

which java

y tomen nota de la ruta…

Ahora…podemos cerrar Heroku y continuar…copiamos el archivo bin.tar.gz en nuestra carpeta de proyecto y lo descomprimimos…luego…creamos los siguientes archivos…

config.ru
`/app/bin/R-3.2.3/bin/R –e “source(‘/app/demo.R’)”`
demo.R
library("Rook")
library("ggmap")
library("maptools")
library("maps")
library("Cairo")
library("RJDBC")

myPort <- as.numeric(Sys.getenv("PORT"))
#myPort <- 8000
myInterface <- "0.0.0.0"
status <- .Call(tools:::startHTTPD, myInterface, myPort)

newapp<-function(env){
  req<-Rook::Request$new(env)
  res<-Rook::Response$new()

  pass<-system('./hcp.sh',intern=T)
  jdbcDriver <- JDBC("com.sap.db.jdbc.Driver","ngdbc.jar", identifier.quote="`")
  conn_server <- dbConnect(jdbcDriver, "jdbc:sap://localhost:30015", "DEV_6O3Q8EAM96G64Q8M0P5KLA1A3",pass[1])
  airports_param<- dbGetQuery(conn_server, "SELECT ID FROM NEO_BLR8NOQVY6TG8KXKQJ003WVOJ.SAIRPORT")
  sairport<-data.frame(IATA=airports_param$ID)
  airports<-read.csv("airports.dat", header = FALSE)
  colnames(airports)<-c("ID", "name", "city", "country", "IATA", "ICAO", "lat", "lon")
  airports<-subset(airports, IATA %in% sairport$IATA)
  keeps <- c("city","country","IATA","lat","lon")
  airports<-airports[keeps]    
  
  if(req$params()$airports != '')
  {
    count_sairport<-nrow(airports)
    mp <- NULL
    mapWorld <- borders("world", colour="gray50")
    mp <- ggplot() + mapWorld
    CairoJPEG(filename = "R_Plot.jpg", width = 680, height = 680)
    mp <- mp+ geom_point(aes(x=airports$lon, y=airports$lat) ,color="red", size=3) 
    show(mp)
    dev.off()
  }else if(req$params()$us_airports != '')
  {
    US_Airports<-airports[airports$country == "United States", ]
    count_sairport<-nrow(US_Airports)
    mp <- NULL
    mapWorld <- borders("state", colour="gray50")
    mp <- ggplot() +  mapWorld
    mp <- mp+geom_point(aes(x=US_Airports$lon, y=US_Airports$lat) ,color="red", size=3)+
           geom_text(data=US_Airports, aes(US_Airports$lon, US_Airports$lat, label = US_Airports$city), size=6)
    CairoJPEG(filename = "R_Plot.jpg", width = 680, height = 680)
    show(mp)
    dev.off()
  }else if(req$params()$carriers != '')
  {
    US_Airports<-airports[airports$country == "United States", ]
    US_Airports<-as.vector(t(US_Airports))
    US_Airports<-paste(shQuote(US_Airports), collapse=", ")
    #query<-paste("SELECT CARRID, DISTANCE FROM NEO_BLR8NOQVY6TG8KXKQJ003WVOJ.SPFLI WHERE AIRPFROM IN (",US_Airports,")")
    query<-paste("SELECT SPFLI.CARRID,CARRNAME,DISTANCE FROM NEO_BLR8NOQVY6TG8KXKQJ003WVOJ.SPFLI 
             INNER JOIN NEO_BLR8NOQVY6TG8KXKQJ003WVOJ.SCARR ON SPFLI.CARRID = SCARR.CARRID
             WHERE AIRPFROM IN (",US_Airports,")")
    result<-dbGetQuery(conn_server,query)
    result$DISTANCE<-sub(",\\d+","",result$DISTANCE)
    result$DISTANCE<-sub("\\.","",result$DISTANCE)
    carriers<-data.frame(CARRID=result$CARRID,CARRNAME=result$CARRNAME,
                         DISTANCE=as.numeric(result$DISTANCE),stringsAsFactors = FALSE)
    carriers_sum<-aggregate(DISTANCE~CARRID + CARRNAME,data=carriers,FUN=sum)
    cols<-c('CARRNAME','DISTANCE')
    data <- apply( carriers_sum[ , cols ] , 1 , paste , collapse = " " )
    count_sairport<-paste(shQuote(data), collapse=", ")
    mp <- NULL
    CairoJPEG(filename = "R_Plot.jpg", width = 680, height = 680)
    mp<-ggplot(carriers_sum,aes(x=CARRID,y=DISTANCE,fill=CARRID))+
        geom_bar(position="dodge",stat="identity")
    show(mp)
    dev.off()
  }
  
  size <- as.integer(system("stat --format %s R_Plot.jpg", intern=T))
  to.read = file("R_Plot.jpg", "rb")
  #x<-readBin(to.read, raw(),n=18674)
  x<-readBin(to.read, raw(),n=size)
  hex<-paste(x, collapse = "")
  hex<-paste(hex,count_sairport,sep = "/")
  close(to.read)
  
  res$write(hex)
  res$finish()
}

unlockBinding("httpdPort", environment(tools:::startDynamicHelp))
assign("httpdPort", myPort, environment(tools:::startDynamicHelp))

server = Rhttpd$new()
server$listenAddr <- myInterface
server$listenPort <- myPort
server$add(app = newapp, name = "summarize")

while(T) {
  Sys.sleep(10000)
}

Así que…hay que tomarnos un tiempo para entender que está pasándo en este código…vamos a crear un servidor Rook…que nos va a permitir crear páginas web en R…luego, vamos a utilizar nuestro script hcp.sh para obtener el password de nuestro HANA Cloud Platform bridge…para que podamos tener una conexión JDBC con la base de datos…de ahí queremos obtener una lista de los aeropuertos y también leer los aeropuertos de archivo que se detalla más adelánte (este archivo de aeropuertos contiene la geolocalización de los aeropuertos). Con esto…queremos filtrar los aeropuertos de HANA con los aeropuertos del archivo…para no tener ninguna información extra…ahora…tenemos tres opciones…aeropuertos, aeropuertos de US o líneas aereas…el primero va a generar un mapa del mundo con todos los aerpuertos como pequeños puntos rojos…el segundo va a generar un mapa de los aeropuertos de Estados Unidos con los aeropuertos como pequeños puntos rojos pero también mostrándo los nombres de las ciudades…el último va a generar un histográma geométrico el detalle de la distancia recorrida por las aerolíneas…más adelánte…vamos a leer la información del gráfico generado para crear una cadenas hexadecimal del gráfico además de información adicional que  Alexa debería recitar…bastante sencillo, no?

Procfile
web: bundle exec rackup config.ru

Queremos que este servidor R pueda acceder a HANA Cloud Platform…así que hagamos eso antes de continuar…

Con la ruta de Java…aplicamos este comándo…

heroku config:set JAVA_HOME='/usr/bin/java'

Ahora…Copia los siguiente archivos en la carpeta de tu proyecto…

carpeta “tools” del HANA Cloud Platform SDK
ngdbc.jar del cliente SAP HANA

Además…crea este pequeño script que nos va a permitir conectárnos a HCP…

hcp.sh
#!/bin/bash -e

user=YourUserName
account=YourAccount
HCP_PASSWORD=YourPassword

json=$(tools/./neo.sh open-db-tunnel -i blaghanax -h hanatrial.ondemand.com -a 
       "$account" -u "$user" -p "$HCP_PASSWORD" --background --output json)
regex='.*"host":"([^"]*)".*"port":([^,]*),.*"instanceNumber":"([^"]*)".
        *"dbUser":"([^"]*)".*"dbUserPassword":"([^"]*)".*"sessionId":"([^"]*)".*'
[[ $json =~ $regex ]]
dbHost=${BASH_REMATCH[1]}
dbPort=${BASH_REMATCH[2]}
dbInstance=${BASH_REMATCH[3]}
dbUser=${BASH_REMATCH[4]}
dbPassword=${BASH_REMATCH[5]}
tunnelSessionId=${BASH_REMATCH[6]}

echo $dbPassword

Impresionante…ya casi estamos listos…por favor descarguen este archivo de aeropuertos y guardénlo en su carpeta de proyecto…

Finalmente…con todos los archivos en su lugar podemos enviar todo de vuelta a heroku…así que hagan login a su Heroku tool belt y escriban lo siguiente…

Poniéndo todo en su lugar
git add .
git commit –am “message”
git push heroku master
heroku ps:scale web=1

R debería estar instalado y listo para funcionar -;) Así que deberíamos seguir adelánte y continuar con nuestro servidor NodeJS en Heroku…

Creándo NodeJS en Heroku con PhamtomJS

Creamos un folder llamado mynodeproject y dentro creamos los siguientes archivos…

package.json
{
  "dependencies": {
    "ws": "1.0.1",
    "request": "2.67.0",
    "express": "4.13.3",
    "phantomjs-prebuilt": "2.1.3"
  },
  "engines": {
    "node": "0.12.7"
  }
}
Procfile
web: node index.js
index.js
var WebSocketServer = require("ws").Server
  , http = require("http")
  , express = require("express")
  , request = require('request')
  , fs = require('fs')
  , app = express()
  , arr = []
  , msg = ""
  , port = process.env.PORT || 5000
  , childProcess = require('child_process')
  , phantomjs = require('phantomjs-prebuilt')
  , path = require('path')
  , binPath = phantomjs.path;

app.use(express.static(__dirname + "/"))

var server = http.createServer(app)
server.listen(port)

var wss = new WebSocketServer({server: server})

var childArgs = [path.join(__dirname, 'phantom.js')]
var childStats = [path.join(__dirname, 'readphantom.js')]

app.get('/path', function (req, res) {
 if(req.query.command == 'map'){
  URL = "http://blagrookheroku.herokuapp.com/custom/summarize?airports=xyz&us_airports=&carriers=";
  request(URL, function (error, response, body) {
   if (!error) {
    arr = body.split("/");
    msg = "There are " + arr[1] + " airports around the world";
    var bitmap = new Buffer(arr[0], 'hex');
    var jpeg = new Buffer(bitmap,'base64');
    fs.writeFileSync('Graph.jpg', jpeg);
    res.redirect('/');
   };
  });
 }else if(req.query.command == 'usmap'){
  URL = "http://blagrookheroku.herokuapp.com/custom/summarize?airports=&us_airports=xyz&carriers=";
  request(URL, function (error, response, body) {
   if (!error) {
    arr = body.split("/");
    msg = "There are " + arr[1] + " airports in the US";
    var bitmap = new Buffer(arr[0], 'hex');
    var jpeg = new Buffer(bitmap,'base64');
    fs.writeFileSync('Graph.jpg', jpeg);
    res.redirect('/');
   };
  });
 }else if(req.query.command == 'carriers'){
  URL = "http://blagrookheroku.herokuapp.com/custom/summarize?airports=&us_airports=&carriers=xyz";
  request(URL, function (error, response, body) {
   if (!error) {
    arr = body.split("/");
    msg = "" + arr[1];
    var bitmap = new Buffer(arr[0], 'hex');
    var jpeg = new Buffer(bitmap,'base64');
    fs.writeFileSync('Graph.jpg', jpeg);
    res.redirect('/');
   };
  });
 }else if(req.query.command == 'stat') {
  childProcess.execFile(binPath, childArgs, function(err, stdout, stderr){
   if(!err){
    res.redirect('/');
   };
  });
 }else if(req.query.command == 'readstat') {
  childProcess.execFile(binPath, childStats, function(err, stdout, stderr){
   if(!err){
    res.write(stdout);
    res.end();
   };
  });  
 }else if(req.query.command == 'bye'){
  if(fs.existsSync('Graph.jpg')){
   fs.unlink('Graph.jpg');
  }
  res.redirect('/');
 }
});

wss.on("connection", function(ws) {
  var id = setInterval(function() {
   
  fs.readFile('Graph.jpg', function(err, data) {
    if(!err){
    ws.send(JSON.stringify("Graph.jpg/" + msg), function() {  })
 }else{
    ws.send(JSON.stringify("Gandalf.jpg/No problem...I'm crunching your data..."), function() {  })
 }
  });
  }, 3000)

  ws.on("close", function() {
    clearInterval(id)
  })
})

Vamos a explicar un poco el código y creánme…estoy muy lejos de ser un experto en NodeJS…esta es la primera vez que lo uso para desarrollar algo tan complejo…y me tomó mucho tiempo y muchísima investigación…así que por favor traten de no criticarme mucho -:(

Vamos a crear una aplicación express que va a utilizar Web Sockets para poder refrescar el navegador y así poder mostrar los gráficos generados por el servidor R…también va a llamar a PhantomJS tánto para crear como para leer la página web generada para que así se la podamos mandar de vuelta a Alexa…

Aquí…tenemos seis opciones…map, usmap y líneas aereas…las primeras tres van a llamar a nuestro servidor R pasándole todos los parámetros pero dejándo vacios lo que no necesitamos…y solo pasándo “xyz” como parámetros…

Cuando recibimos la respuesta de R va a ser una cadena larga separada por “/”…lo cual va a ser la cadenas hexadecimal para el gráfico junto con el texto destinado a Alexa…Node va a leer el gráfico…generárlo y luego refrescar el navegador para poder mostrarlo en la pantalla…

La opción stats va a llamar a nuestro script PhantomJS para simplemente leer la página y crear un nuevo archivo con el JavaScript ya ejecutado…el readstat va a leer esta información y extraer el texto que necesitamos para Alexa…finálmente…bye va a borrar el gráfico y el web socket va a llamar al gráfico principal para que sea mostrado en pantalla.

Finálmente…el web socket va a ser constántemente revisado…cada 3 segundos para ver si hay un gráfico o no…y así poder mostrar la imagen correspondiente…

index.html
<html>
  <head>
 <title>I'm a NodeJS Page!</title> 
 <div id="container" align="center"/>
    <script>
      var host = location.origin.replace(/^http/, 'ws')
      var ws = new WebSocket(host);
      ws.onmessage = function (event) {
    var container = document.getElementById('container');
          var data = JSON.parse(event.data);
          data = data.split("/");
          var url = data[0];
          var msg = data[1];
          
          container.innerHTML = '<img src="' + url + '"></br><p><b>' + msg + '</b></p>';
      };
    </script>
  </head>
  <body>
  </body>
</html>

Esta va a ser llamado por nuestra aplicación express y simplemente va a llamar al web socket para determinar que necesitas mostrar…tiene un poco de Javascript…es por eso que necesitamos PhantomJS para que pueda interactuar con el código…

phantom.js
var page = require('webpage').create();
var fs = require('fs');
page.open('http://blagnodeheroku.herokuapp.com/', function () {
 window.setTimeout(function () {
    page.evaluate(function(){

    });

    fs.write('stats.html', page.content, 'w');
    phantom.exit();
},4000);
});

No es el mejor y más descriptivo nombre…pero a quien le importa -:P En fín…este script va a cargar la página…esto es, la aplicación express…esperar por 4 segundos para que el Javascript sea generado y luego crear una página llamada stats.html


readphantom.js
var page = require('webpage').create(),
    address = "stats.html";

    page.open(address, function (status) {
        if (status == 'success') {
                var results = page.evaluate(function() {
                    return document.querySelector('p').innerText.trim();
                });
 console.log(results);
 phantom.exit();
 }
    });

Este script simplemente va a leer la página stats.html y retornar el texto que está localizado dentro del tag “p”…totalmente simple…

CONFIGURÁNDO A ALEXA

Creándo la función Lambda


Ahora…necesitamos configurar a Alexa…para que podamos controlar todo con comándos de voz -:)

Primero…debemos ir a Amazon Lambda e ingresar si ya tenemos una cuenta…de otro modo…por favor creen una…y asegurénse de estar en la región de West Virginia…

En esta lista de funciones…busca color…


Escoje NodeJS…Python ha sido incluído también…pero no lo estaba cuando comencé a trabajar en este blog -:)


Aquí, simplemente presionamos next....


Yo ya creé la función…pero ustedes no deberías tener problemas...


Basic execution role es más que suficiente…


Esto va a llamar a una ventana pop up…simplemente presionamos el botón “Allow” y luego  “Create function”…vamos a incluir el código fuente más adelánte…pero hay que tomar en cuenta el número ARN generado…porque vamos a necesitarlo en el siguiente paso…

Creándo el Skill

Vayan a http://developer.amazon.com e ingresen…luego escojan Apps & Services --> Alexa --> Alexa Skills Set



Escogemos Alexa Skills Kit y llenámos los espacios en blanco...


Apenas presionamos el botón next un número de aplicación será generado en un nuevo campo llmado Application ID. Debemos tomar este número puesto que lo vamos a necesitar cuando hagamos el código de nuestra aplicación.

La sección de Interaction Model es muy importánte puesto que vamos definir el “Intent Schema” y “Sample Utterances”…el primero va a definir los parámetros que vamos a enviar a Alexa y el segundo es como vamos a llamar a nuestra aplicación.

Intent Schema
{
  "intents": [
    {
      "intent": "GetFlightsIntent",
      "slots": [
        {
          "name": "command",
          "type": "LITERAL"
        }
      ]
    },
    {
      "intent": "HelpIntent",
      "slots": []
    }
  ]
}

Nuestra variable se va a llamar “command” y va a ser de tipo LITERAL…otros tipos son NUMBER, DATE, TIME y DURATION. El intent es el método que vamos a llamar en nuestro código…

Sample Utterances
GetFlightsIntent airports from {around the world|command}
GetFlightsIntent airports from {united states|command}
GetFlightsIntent flight distance from {carriers|command}
GetFlightsIntent {thank you|command}

La sección test nos ayuda a definir comándos y ver como Alexa responde…pero no vamos a hacer esto aquí…vamos a probarlo usándo un dispositivo Alexa real -;)

Olvidense de la sección Publishing Information a menos que realmente quieran publicar su aplicación…

Creamos un carpeta llamada Flights…Alexa_Party…o cualquier cosa que se les ocurra…luego creamos un carpeta llamada src y copiamos este archivo ahí…llamándolo AlexaSkills.js

Vamos a necesitar instalar solamente una librería….”request”…

sudo npm install --prefix=~/Flights/src request

Esto va a crear una carpeta llamada “node_modules” con el paquete en nuestra carpeta de proyecto…luego debemos crear un archivo llamado “index.js” y copiar y pegar el siguiente código…

index.js
var request = require("request")
  , AlexaSkill = require('./AlexaSkill')
  , APP_ID     = 'amzn1.echo-sdk-ams.app.8c0bd993-723f-4ab2-80b5-84402a7a59ce';

var error = function (err, response, body) {
    console.log('ERROR [%s]', err);
};

var getJsonFromFlights = function(command, callback){
 var msg = "";
 if(command == "thank you"){
 request("http://blagnodeheroku.herokuapp.com/path/?command=bye", function (error, response, body) {
  if (!error) {
   console.log("Done");
  };
 });
 setTimeout(function() {
  callback("thank you");
 },2000);
}else if (command == "around the world"){
 request("http://blagnodeheroku.herokuapp.com/path/?command=bye", function (error, response, body) {
 if (!error) {
 request("http://blagnodeheroku.herokuapp.com/path/?command=map", function (error, response, body) {
 if (!error) {
 request("http://blagnodeheroku.herokuapp.com/path/?command=stat", function (error, response, body) {
 if (!error) {
 request("http://blagnodeheroku.herokuapp.com/path/?command=readstat", function (error, response, body) {
 if (!error) {
 msg = body;
 };
 });
 };
 }); 
 };
 });
 }
 }); 
 setTimeout(function() {
  callback(msg.trim());
 },15000);
}else if (command == "united states"){
 request("http://blagnodeheroku.herokuapp.com/path/?command=bye", function (error, response, body) {
 if (!error) {
 request("http://blagnodeheroku.herokuapp.com/path/?command=usmap", function (error, response, body) {
 if (!error) {
 request("http://blagnodeheroku.herokuapp.com/path/?command=stat", function (error, response, body) {
 if (!error) {
 request("http://blagnodeheroku.herokuapp.com/path/?command=readstat", function (error, response, body) {
 if (!error) {
 msg = body;
 };
 });
 };
 }); 
 };
 });
 }
 }); 
 setTimeout(function() {
  callback(msg.trim());
 },15000);
}else if (command == "carriers"){
 request("http://blagnodeheroku.herokuapp.com/path/?command=bye", function (error, response, body) {
 if (!error) {
 request("http://blagnodeheroku.herokuapp.com/path/?command=carriers", function (error, response, body) {
 if (!error) {
 request("http://blagnodeheroku.herokuapp.com/path/?command=stat", function (error, response, body) {
 if (!error) {
 request("http://blagnodeheroku.herokuapp.com/path/?command=readstat", function (error, response, body) {
 if (!error) {
 msg = body;
 };
 });
 };
 }); 
 };
 });
 }
 }); 
 setTimeout(function() {
  callback(msg.trim());
 },15000);
 }
};
var handleFlightsRequest = function(intent, session, response){
  getJsonFromFlights(intent.slots.command.value, function(data){
 if(data != "thank you"){
  var text = data;
  var reprompt = 'Please say a command?';
  response.ask(text, reprompt);
 }else{
  response.tell("You're welcome");
 }
  });
};

var Flights = function(){
  AlexaSkill.call(this, APP_ID);
};

Flights.prototype = Object.create(AlexaSkill.prototype);
Flights.prototype.constructor = Flights;

Flights.prototype.eventHandlers.onSessionStarted = function(sessionStartedRequest, session){
  console.log("onSessionStarted requestId: " + sessionStartedRequest.requestId
      + ", sessionId: " + session.sessionId);
};

Flights.prototype.eventHandlers.onLaunch = function(launchRequest, session, response){
  // This is when they launch the skill but don't specify what they want.
  var output = 'Welcome to Flights. ' +
    'Please, say a command.';

  var reprompt = 'Please, say a command?';

  response.ask(output, reprompt);

  console.log("onLaunch requestId: " + launchRequest.requestId
      + ", sessionId: " + session.sessionId);
};

Flights.prototype.intentHandlers = {
  GetFlightsIntent: function(intent, session, response){
    handleFlightsRequest(intent, session, response);
  },

  HelpIntent: function(intent, session, response){
    var speechOutput = 'Get the information for airports and flights. ' +
      'Please say a command?';
    response.ask(speechOutput);
  }
};

exports.handler = function(event, context) {
    var skill = new Flights();
    skill.execute(event, context);
};

Hora de explicar que es lo que querido hacer aquí -:P

El método handleFlightsRequest va a manejar la respuesta que Alexa va a leernos…y dentro de este método podemos encontrar el método getJsonFromFlights el cual va a tomar el comando definido en nuestro Intent Schema. Esta función va a llamar a nuestro servidor NodeJS  para los siguientes comando…”thank you” simplemente llamará al comándo bye….”around the world” va a llamar a los comandos bye, map, stat y readstats…”united states” va a llamar a los comandos bye, usmap, stat y readstat…finalmente carriers va a llamar a los comandos bye, carriers, stat y readstat…

Luego de 15 segúndos (Sip…yo sé que es bastante tiempo pero hay muchos procesos funcionándo) Alexa obtendrá el mensaje de respuesta y simplemente nos lo leerá -;)

Eso es basicamente lo que hace…ahora…puedo enseñarles algunas imágenes antes de que saltemos al video…





Ok…suficiente…veamos esto en plena acción ;)





Saludos,

Blag.
Development Culture.

No comments: