Google Assistant. Desarrollo de aplicaciones IoT para Arduino y ESP8266

Tekst
Loe katkendit
Märgi loetuks
Kuidas lugeda raamatut pärast ostmist
Šrift:Väiksem АаSuurem Aa

6.4.1 Conceptos previos al desarrollo de un cumplimiento

El desarrollo de un cumplimiento (fulfillment) se realiza en JavaScript, pero, para poder trabajar en este lenguaje de programación, además de conocer su sintaxis, es necesario entender la tecnología que hay detrás de su ejecución. Como sabe, se trata de una función que se ejecuta en la nube (preferentemente, en Firebase Cloud Functions), a la que se invoca como un webhook utilizando el protocolo HTTP. Por ese motivo, se requiere tener conocimientos básicos de dicho protocolo y de la tecnología webhook, así como del formato de datos JSON, ya que la información que viaja entre Dialogflow y Firebase Cloud Functions lo hace en dicho formato.


Si nunca ha programado en JavaScript, al final del libro, encontrará un anexo que lo ayudará a conocer los aspectos básicos de este lenguaje, que le permitirá seguir las prácticas propuestas.

Evidentemente, el empleo de HTTP, Webhook y JSON requerirá de librerías que oculten la complejidad de esta comunicación entre plataformas. Conocerá sus clases principales, cuyo uso le permitirá desarrollar cualquier cumplimiento, sea cual sea su complejidad.

Para terminar de entender toda esta teoría y asentar conceptos, realizará un par de prácticas que harán que el desarrollo de cumplimientos deje de tener algún misterio para usted.

6.4.1.1 Protocolo HTTP

Internet forma ya parte inseparable de nuestras vidas. No hay día en el que, por un motivo u otro, abramos nuestro navegador y surquemos por la red hasta encontrar lo que buscamos. Pero, a nivel técnico, ¿cómo funciona esta comunicación? Para entenderlo, lo primero que tiene que saber es que, en las comunicaciones web, intervienen tres elementos clave: un cliente que solicita y recibe documentos, un canal de comunicación y un servidor que almacena y sirve los documentos a los clientes que se lo soliciten.


El software cliente es el navegador, el canal de comunicación es Internet y el servidor es el sitio web que contiene los documentos visualizados en el navegador. Dichos documentos se conocen como páginas web y están escritas en HTML (HyperText Markup Language - lenguaje hipertexto de marcas).

Para que un navegador web pueda visualizar una página, debe solicitarla a un servidor, pero antes debe saber dónde está. ¿Cómo localizarla? Mediante su dirección o URL (Uniform Resource Locator - localizador uniforme de recursos), que identifica de forma exclusiva un recurso (por ejemplo, una página web) en Internet. El URL más conocido seguro es http://www.google.com/. Un URL se compone de una serie de elementos, el primero de los cuales hace referencia al protocolo utilizado para la comunicación entre el cliente y el servidor (en este caso, HTTP), seguido del nombre o la dirección IP del recurso al que se accede (continuando con este mismo ejemplo, la página HTML contenida en la dirección www.google.com). El protocolo más utilizado en Internet es HTTP (HyperText Transfer Protocol - protocolo de transferencia de hipertextos), que es la base de la comunicación entre clientes y servidores web. Este protocolo define el formato de los mensajes y cómo deben ser transmitidos, para que dicha comunicación sea posible, independientemente del hardware, sistema operativo o lenguaje en el que se hayan desarrollado clientes y servidores.


Un servidor HTTP utiliza, por defecto, el puerto 80 para escuchar las peticiones de los clientes. Si fuera otro, debería indicarse en el URL. A continuación, iría el recurso solicitado. Si no se estableciera ninguno (como en el ejemplo utilizado), se mostraría la página por defecto (principal o index).

Como se acaba de indicar, el protocolo HTTP es el que permite la comunicación entre clientes y servidores web a nivel de aplicación. Pero un cliente no tiene por qué ser un navegador ni un servidor tiene que servir únicamente páginas web. En el caso que nos ocupa, el cliente será Dialogflow, cuyas peticiones no tendrán como objetivo la obtención de una página HTML, sino el resultado de la ejecución de una función en la nube. El proceso es el siguiente:

1. Dialogflow realiza una petición HTTP a un servidor web solicitando la ejecución de una función HTTP (el cumplimiento).

2. El servidor recibe dicha petición.

3. El servidor ejecuta la función solicitada y devuelve una respuesta HTTP a Dialogflow con la información generada.

4. Dialogflow recibe dicha respuesta.


Con HTTP la información viaja en claro y, por lo tanto, susceptible de ser vista por cualquiera con capacidad para escuchar el tráfico que va por la red. Como en la comunicación entre Dialogflow y Firebase se transmitirá información confidencial, esta deberá ser cifrada para ocultarla a miradas indiscretas. Por ese motivo, se empleará HTTPS, que utiliza TLS (Transport Layer Security - seguridad de la capa de transporte) para encriptar la comunicación.

En la comunicación HTTP hay dos tipos de mensajes: el utilizado por los clientes para realizar las peticiones al servidor web y el de respuesta con la información solicitada. El protocolo HTTP establece el formato de ambos tipos de mensaje (válido también para HTTPS). Aunque el uso de las librerías que estudiará más adelante ocultará la estructura de dichos mensajes, conviene conocerlo (aunque sea a alto nivel) para entender mejor cómo se realiza este tipo de comunicaciones. Veamos, por lo tanto, cuál es la estructura de los mensajes HTTP de petición y respuesta.

5.4.1.1.1. Peticiones HTTP

Las peticiones HTTP tienen la siguiente estructura:

• Línea de solicitud

• Cabeceras

• Cuerpo de mensaje


La línea de solicitud indica el tipo de petición empleado (Dialogflow usa POST), la ruta del servidor a la que se realiza la solicitud (habitualmente, sería la de una página HTML, pero, en este caso, es la del cumplimiento) y el protocolo utilizado (HTTP/1.1).

Las cabeceras contienen información sobre el contenido que quiere obtenerse o sobre el cliente. Está formado por una serie de líneas, cada una de las cuales consta de una clave y un valor separados por el carácter «:». Una de ellas es Host, cuyo valor es la dirección IP o el nombre del servidor web al que se realiza la petición.

El cuerpo del mensaje contendrá cualquier tipo de información asociada a la petición. Puesto que el contenido del cuerpo de un mensaje puede ser de cualquier tipo y tamaño, para ayudar a interpretar la información que contiene se utilizan las siguientes cabeceras:

• Content-Type: especifica el formato del contenido del cuerpo de la petición. Si dicho contenido fuera una página HTML, su valor sería text/html. En las comunicaciones entre Dialogflow y Firebase es application/json, formato de datos que estudiará más adelante.

• Content-Length: es el tamaño, en bytes, del cuerpo de la petición.


La cabecera Content-Type toma como valor un tipo MIME (Multipurpose Internet Mail Extensions - extensiones multipropósito de correo Internet), que es una forma estandarizada de indicar el formato de un documento. La estructura de un tipo MIME está formada por un tipo y un subtipo separadas por el carácter «/». El tipo representa la categoría (por ejemplo, text, image, video, application, etc.). El subtipo es específico para cada tipo (por ejemplo, plain o html para text, jpeg o png para image, json para application, etc.).

5.4.1.1.2 Respuestas HTTP

Las respuestas HTTP tienen una estructura similar a la de las peticiones:

• Línea de estado

• Cabeceras

• Cuerpo de mensaje


La línea de estado indica qué protocolo está hablando el servidor (HTTP/1.1), que debe coincidir con el utilizado por el cliente (presente en la línea de solicitud de la petición). Luego, contiene un código de estado numérico y un mensaje corto descriptivo, que indica lo que ha sucedido durante la comunicación. Por ejemplo, la línea de estado «HTTP / 1.1 200 OK» señala que la solicitud realizada por un cliente se ha atendido correctamente.

Las cabeceras permiten al servidor enviar información adicional sobre la respuesta o el propio servidor. De forma similar a lo que sucedía en las peticiones, la cabecera más común es Content-Type que, en las comunicaciones entre Firebase y Dialogflow, es de tipo application/json, ya que el contenido del mensaje HTTP de respuesta también viaja en formato JSON.

 

6.4.1.2 Tecnología webhook

El protocolo HTTP está basado en un modelo cliente-servidor, en el que el cliente realiza una petición y el servidor le devuelve una respuesta. Con la tecnología webhook, se va a invertir el escenario, ya que será el cliente quien proporcione un URL al servidor para que, cuando se produzca un evento, sea este quien lo invoque.

Las ventajas de esta tecnología son enormes en aplicaciones cliente que deben estar pendientes de que se produzca algún cambio en el estado del servidor. Sin este modelo de trabajo, dichas aplicaciones tendrían que estar continuamente haciendo consultas al servidor, para saber si se han producido los cambios en los que están interesadas. Por el contrario, utilizando esta técnica, el servidor invocará los webhooks que provocarán la ejecución inmediata de las acciones necesarias en el momento de producirse; sin necesidad de realizar consultas continuas, que además causaría una demora en la ejecución de dichas acciones entre el tiempo en el que se produjo el cambio y el que se realizó la consulta.

En concreto, Dialogflow será el que llame al webhook residente en Firebase Cloud Functions (contiene el código del cumplimiento) cuando se cumpla una intención que tenga habilitada la opción «Enable webhook call for this intent». La forma de invocarlo será mediante el envío de un mensaje de petición HTTP al URL del cumplimiento (también conocido como extremo del webhook), lo que provocará su ejecución. En el cuerpo del mensaje de petición HTTP viajará, en formato JSON, la información necesaria para su ejecución. El contenido del mensaje HTTP de respuesta también vendrá en formato JSON. Gráficamente, se puede representar de la forma mostrada en la imagen.



La utilización del término webhook es muy amplia ya que, en unos casos, se emplea para hacer referencia a la propia tecnología; en otros, al recurso que se invoca con ella (en este caso, la función JavaScript que contiene el código del cumplimiento) y, con frecuencia, al URL del recurso invocado.

6.4.1.3 Formato de datos JSON

Con la creciente popularidad de los servicios web, JSON se ha convertido en uno de los formatos de intercambio de datos más utilizados en Internet. Su acrónimo procede del inglés JavaScript Object Notation, delatando a JavaScript como el lenguaje de programación del que procede, que lo utiliza para el intercambio de objetos entre un navegador y el servidor. Dicho formato ha sido posteriormente adaptado para un uso independiente del lenguaje, de ahí su enorme expansión. Propuesto por Douglas Crockford a principios de la década de los años 2000, fue finalmente estandarizado por primera vez en 2013. Entre las bazas que juegan a su favor, están su ligereza (una característica muy valorada en IoT) y gran simplicidad.

JSON está constituido por dos estructuras de datos fundamentales:

• Objetos: son colecciones no ordenadas de pares atributo-valor. La clave va entre comillas y se separa del valor por el carácter «:». Los pares clave-valor se separan por comas y se enmarcan entre llaves ({}).

Arrays: simboliza una lista ordenada de cero o más valores, que pueden ser de cualquier tipo. Los valores se separan por comas y se enmarcan entre corchetes ([]).

Los valores pueden ser, a su vez:

• Números: pueden ser positivos o negativos, enteros o decimales.

• Cadenas de caracteres: contienen cero o más caracteres entrecomillados.

• Booleanos: representan los valores true y false.

• null: es el valor nulo.

Arrays: los valores de las claves de un objeto o los elementos de un array pueden ser, a su vez, arrays.

• Objetos: los valores de las claves de un objeto o los elementos de un array pueden ser otros objetos.

Para entender mejor la sencillez de uso de este formato de datos, pero, a la vez, la potencia expresiva que tiene, se va a utilizar JSON para realizar un modelo lógico de una placa Arduino. En primer lugar, considere que cada Arduino tiene un identificador único (que lo distingue de los demás) y que está dedicado a un determinado uso. Además, como ya conoce, existen dos grandes grupos de pines: los analógicos y los digitales. En formato JSON, dicho Arduino podría representarse como un objeto con la siguiente estructura:


Como puede observar, el objeto Arduino tiene cuatro pares clave-valor. El valor de la clave «Identificador» sirve para distinguir cada Arduino del resto. La clave «Descripción» tiene como valor un texto que hace referencia al uso que se está haciendo de él. La clave «pines analógicos» contiene un array de String, que identifica todos los pines analógicos. La última clave [«pines digitales»] también es un array de String que, en este caso, identifican los pines digitales.


A lo largo de este libro, se podrá hacer referencia a las claves de los objetos como atributos o propiedades.

Siga completando este modelo de Arduino. Ahora considere que cada pin se caracteriza no solo por su identificador, sino también por su valor de entrada y/o salida. De esta forma, lo que antes era un valor primitivo (una cadena de caracteres), ahora es, a su vez, un objeto JSON. Por ejemplo, si el pin A0 tuviera un valor de 255, esta información podría especificarse en JSON como:


En el caso de los pines digitales, además de su identificador y un valor, también se podría indicar si están configurados como salida digital, salida analógica (PWM) o entrada. Esta última característica sería una nueva clave («Configuracion»), que podría tomar los valores «ENTRADA», «SALIDA» o «PWM». Al igual que con los pines analógicos, lo que antes era un valor primitivo se ha convertido en otro objeto JSON. Por ejemplo, si el pin D3 estuviera configurado como una salida analógica cuyo valor fuera 255, la representación JSON de dicho pin sería:


Juntándolo todo, cada Arduino se podría describir como un objeto JSON con una estructura similar a la siguiente:


Los caracteres «…» no forman parte de JSON. Se utilizan para indicar que allí deberían estar los objetos que representan a cada uno de los pines analógicos y digitales que faltan, ya que solo se han especificado dos por simplicidad.

Al igual que se ha hecho con Arduino en este ejemplo, todos los componentes de Dialogflow (intenciones, entidades, contextos, etc.) se representan como objetos JSON. La información que viaje en el cuerpo de los mensajes HTTP de petición y respuesta entre Dialogflow y Firebase Cloud Functions irá en este formato, de ahí la importancia de su conocimiento.

6.4.2 Librerías de desarrollo

El código JavaScript de un cumplimiento debe ejecutarse en la nube, por lo que es necesaria una infraestructura que, en su caso, será proporcionada por el servicio Firebase Cloud Functions (no Dialogflow). Dicho servicio ofrecerá todo lo necesario para su ejecución en un entorno Node.js. Para poder hacer uso de esta infraestructura, se proporciona la librería Firebase Functions.

Además, y puesto que Cloud Functions y Dialogflow son servicios independientes, deberá establecerse una comunicación HTTP entre ellos. Para realizar dicha comunicación sin tener que trabajar directamente con el protocolo HTTP ni manejar las estructuras de datos JSON contenidas en los mensajes de petición y respuesta que se intercambian, tendrá que aprender a usar la librería Actions on Google.


En el contexto de Node.js, Firebase Functions y Actions on Google son, en realidad, módulos (packages), término que podría asimilarse con el concepto de librería utilizado en Arduino.

La primera vez que vea el contenido de estas librerías puede que lo encuentre confuso y no entienda para qué sirven algunos de los atributos o métodos de las clases que contienen. Seguramente, tampoco sepa cómo utilizarlos. Esta desorientación inicial es normal. Será la práctica la que termine de aclarar y asentar estos nuevos conceptos. Por eso, una vez que realice los ejercicios que se proponen a continuación, vuelva a leer de nuevo estos apartados de carácter teórico. Será, en ese momento, cuando termine de disipar sus dudas y entender la utilidad de lo que le ofrecen estas librerías.

6.4.2.1 Librería Firebase Functions

Esta librería proporciona el SDK para el desarrollo de funciones en Firebase Cloud Functions. Dichas funciones se ejecutarán cuando se produzcan eventos generados por otros servicios de Firebase o la llegada de peticiones HTTP al URL con el que fueron desplegadas. A las funciones que se invocan por este último tipo de eventos se las conoce como «funciones HTTP». En concreto, un cumplimiento es una función HTTP invocada por las peticiones enviadas desde Dialogflow cuando se activa una intención que tiene habilitada la opción «Enable webhook call for this intent».


SDK (Software Development Kit — kit de desarrollo de software) es un término que hace referencia a un grupo de herramientas que permiten la programación de aplicaciones; en este caso, de funciones que se ejecutan en la nube (cloud functions).

En Firebase Cloud Functions, las funciones se pueden desarrollar en diversos lenguajes, pero usted utilizará únicamente JavaScript, ejecutándolas en un entorno Node.js gestionado por el propio servicio. Además, como Firebase Cloud Functions forma parte de la plataforma Firebase, proporciona todo tipo de facilidades para la integración con el resto de los servicios que la componen (como su base de datos, que más adelante aprenderá a utilizar). La referencia a dicha librería la tiene en https://github.com/firebase/firebase-functions.


Las funciones que desarrolle en Firebase Cloud Function se aprovisionan con un certificado TLS, por lo que se invocarán a través de una conexión segura utilizando HTTPS.

El código JavaScript de un cumplimiento se escribe en un fichero llamado «index.js». Puesto que dicho código se ejecutará en el servicio Firebase Cloud Functions, lo primero que deberá hacer en él es importar la librería Firebase Functions mediante la función require(). Esta función devuelve un objeto a través del que podrá acceder al contenido exportado por dicho módulo, como el de la siguiente sentencia de ejemplo, que sería functions:

const functions = require(‘firebase-functions’);


El método require() no forma parte del API JavaScript estándar. En Node. js, se utiliza para la carga de módulos. Dicha función sería equivalente a la directiva #include de Arduino, utilizada para la importación de librerías. Pero, a diferencia de esta última, en la que todo lo que se importa puede utilizarse directamente desde cualquier parte del programa Arduino, la función require() devuelve un objeto, al que habrá que acceder para usarlo.

 

La función de un cumplimiento desarrollado en Firebase tiene una estructura dividida en tres partes: el nombre de la función, el desencadenador asociado y el código que se ejecuta:


Si utiliza el editor Inline de Dialogflow, el nombre de la función que contendrá el código del cumplimiento será, por defecto, dialogflowFirebaseFulfillment. Para que pueda ser invocada externamente, deberá usarse en una expresión que utilice la variable exports de Node.js.

El desencadenador será el método de un objeto que se ejecutará cuando se produzca el evento generado por la llegada de una solicitud HTTP. En concreto, se empleará el método onRequest() del objeto https (obtenido de la librería Firebase Functions), que se invocará a la llegada de una solicitud HTTP enviada desde Dialogflow.

Utilizando la sentencia del ejemplo anterior en la que se importaba el módulo firebase-functions, para acceder al objeto https, tendrá que extraerlo previamente del objeto functions que lo contiene mediante la expresión functions.https. Por lo tanto, si quisiera invocar el método onRequest() de dicho objeto, la expresión empleada sería:

objetoHTTPS = functions.https;

objetoHTTPS.onRequest();

O su forma abreviada:

functions.https.onRequest();

El argumento del método onRequest() es el que realmente contiene el código del cumplimiento. Como puede observar, en JavaScript es posible pasar una función como argumento de otra función. Incluso se la puede declarar en la posición que ocupa dicho argumento, que es lo que se va a hacer en este caso.

La función contenida en el argumento del método onRequest() se crea declarándola con una expresión de función flecha. De esta forma, si en JavaScript habitualmente una función se crea de la siguiente forma:


Utilizando la notación flecha, quedaría así:



Para entender las expresiones de función, consulte el apartado correspondiente del anexo.


Si quiere conocer más sobre el método onRequest() en particular y las funciones http en general, visite la página https://firebase.google.com/docs/reference/functions/providers_https_#onrequest.

En resumen, la función HTTP que contiene el código de un cumplimiento, se declara con la siguiente sentencia:


Olete lõpetanud tasuta lõigu lugemise. Kas soovite edasi lugeda?