Cómo compilar una aplicación web con Acceso a dispositivos

1. Introducción

El programa Acceso a dispositivos brinda la API de Smart Device Management, una API de REST que permite a los desarrolladores administrar los dispositivos Google Nest desde sus aplicaciones. Los usuarios deben dar su consentimiento para el acceso de terceros a sus dispositivos Nest.

52f77aa38cda13a6.png

Hay tres pasos clave para lograr una integración correcta en Acceso a dispositivos:

  1. Creación de proyectos: Crea un proyecto en Google Cloud Platform y regístrate como desarrollador en la Consola de Acceso a dispositivos.
  2. Vinculación de cuentas: Logra que los usuarios accedan al flujo de vinculación de cuentas y recupera un código de acceso. Intercambia el código por un token de acceso.
  3. Control de dispositivos: Crea solicitudes a la API de Smart Device Management para controlar dispositivos mediante el envío de comandos con el token de acceso.

En este Codelab, analizaremos en profundidad el funcionamiento de Acceso a dispositivos. Para ello, crearemos una aplicación web que administre la autenticación y haga llamadas a la API de Smart Device Management. También exploraremos la implementación de un servidor proxy sencillo usando Node.js y Express para enrutar las solicitudes de Acceso a dispositivos.

Antes de comenzar, sería conveniente repasar las tecnologías web comunes que utilizaremos en este Codelab, como autenticar con OAuth 2.0 o crear una aplicación web con Node.js, aunque no son requisitos previos.

Requisitos

  • Node.js 8 o superior
  • Cuenta de Google con Nest Thermostat vinculado

Qué aprenderás

  • Configurar un proyecto de Firebase que aloje páginas web estáticas y funciones en la nube
  • Emitir solicitudes de acceso a dispositivos mediante una aplicación web basada en el navegador
  • Compilar un servidor proxy con Node.js y Express para enrutar las solicitudes

2. Creación de proyectos

Los desarrolladores deben crear un proyecto de Google Cloud Platform (GCP) para configurar la integración de Acceso a dispositivos. Se usará un ID de cliente y un Secreto del cliente generados dentro del proyecto de GCP como parte del flujo de OAuth entre la aplicación del desarrollador y Google Cloud. Los desarrolladores también deben visitar la Consola de Acceso a dispositivos para crear un proyecto que permita acceder a la API de Smart Device Management.

Google Cloud

Ve a Google Cloud Platform. Haz clic en crear un nuevo proyecto y proporciona un nombre de proyecto. También se mostrará un ID del proyecto [GCP-Project-Id] para Google Cloud. Regístralo porque lo usaremos durante la configuración de Firebase. (Nos referiremos a este ID como [GCP-Project-Id] durante este Codelab).

585e926b21994ac9.png

El primer paso es habilitar la biblioteca de API necesaria en nuestro proyecto. Dirígete a API y servicios > Biblioteca y busca API de Smart Device Management. Debes habilitar esta API para autorizar a tu proyecto a realizar solicitudes a las llamadas a la API de Acceso a dispositivos.

14e7eabc422c7fda.png

Antes de continuar con la creación de credenciales de OAuth, debemos configurar la pantalla de consentimiento de OAuth para nuestro proyecto. Dirígete a API y servicios > Pantalla de consentimiento de OAuth. En Tipo de usuario, selecciona externo. Proporciona un nombre y un correo electrónico de asistencia para tu app, así como la información de contacto del desarrollador para completar la primera pantalla. Cuando se te solicite Usuarios de prueba, asegúrate de proporcionar la dirección de correo electrónico con dispositivos vinculados en este paso.

Después de configurar tu pantalla de consentimiento de OAuth, dirígete a API y servicios > Credenciales. Haz clic en +Crear credenciales y selecciona ID de cliente de OAuth. Para el tipo de aplicación, selecciona Aplicación web.

5de534212d44fce7.png

Indica un nombre para tu cliente y haz clic en CREAR. Más adelante, agregaremos un origen de JavaScript autorizado y un URI de redireccionamiento autorizado. Cuando se completa este proceso, aparecerán el [ID de cliente] y el [Secreto del cliente] asociados con este cliente de OAuth 2.0.

e6a670da18952f08.png

Consola de Acceso a dispositivos

Dirígete a la Consola de Acceso a dispositivos. Si nunca antes utilizaste la Consola de Acceso a dispositivos, recibirás un acuerdo de Condiciones del Servicio y una cuota de registro de USD 5.

Crea un proyecto nuevo y asígnale un nombre. En la siguiente ventana, proporciona el [ID de cliente] que recibiste de GCP en el paso anterior.

f8a3f27354bc2625.png

Cuando habilites los eventos y finalices los pasos para la creación del proyecto, se te dirigirá a la página principal del proyecto. Se mostrará el [ID del proyecto] con el nombre que le hayas dado a tu proyecto.

db7ba33d8b707148.png

Ten en cuenta tu [ID del proyecto], ya que lo usaremos cuando se envíen solicitudes a la API de Smart Device Management

3. Configuración de Firebase

Firebase ofrece a los desarrolladores una manera rápida y fácil de implementar aplicaciones web. Desarrollaremos una aplicación web del cliente para nuestra integración de Acceso a dispositivos utilizando Firebase.

Crea un proyecto de Firebase

Ve a Firebase Console. Haz clic en Agregar proyecto y, luego, selecciona el proyecto que creaste en el paso Creación del proyecto. Se creará un proyecto de Firebase que se vinculará a tu proyecto de GCP [ID del proyecto de GCP].

Una vez que hayas creado el proyecto de Firebase correctamente, deberías ver la siguiente pantalla:

dbb02bbacac093f5.png

Instala las herramientas de Firebase:

Firebase proporciona un conjunto de herramientas de CLI para compilar e implementar tu app. Para instalar estas herramientas, abre una ventana nueva de terminal y ejecuta el siguiente comando. Esto instalará las herramientas de Firebase a nivel global.

$ npm i -g firebase-tools

Para verificar que las herramientas de Firebase estén instaladas correctamente, comprueba la información de la versión.

$ firebase --version

Con el comando de acceso, puedes acceder a las herramientas de Firebase CLI con tu Cuenta de Google.

$ firebase login

Inicializa el proyecto de Hosting

Una vez que puedas acceder, el siguiente paso es inicializar un proyecto de hosting para tu aplicación web. Desde la terminal, ve a la carpeta en la que deseas crear tu proyecto y ejecuta el siguiente comando:

$ firebase init hosting

Firebase te hará una serie de preguntas para comenzar a utilizar un proyecto de hosting:

  1. Selecciona una opción: Usar un proyecto existente
  2. Selecciona un proyecto de Firebase predeterminado para este directorio: Elige ***[ID del proyecto de GCP]***
  3. ¿Qué quieres usar como directorio público? Público
  4. ¿Deseas configurar como una app de una sola página? Sí
  5. ¿Deseas configurar implementaciones y compilaciones automáticas con GitHub? No

Una vez que se haya inicializado tu proyecto, puedes implementarlo en Firebase con el siguiente comando:

$ firebase deploy

Firebase analizará tu proyecto y, luego, implementará los archivos necesarios en el hosting en la nube.

fe15cf75e985e9a1.png

Cuando abras la URL de Hosting en un navegador, deberías ver la página que acabas de implementar:

e40871238c22ebe2.png

Ahora que conoces los conceptos básicos sobre cómo implementar una página web con Firebase, vamos a implementar nuestro ejemplo de Codelab.

4. Muestra de Codelab

Puedes clonar el repositorio de codelab alojado en GitHub con el siguiente comando:

$ git clone https://github.com/google/device-access-codelab-web-app.git

En este repositorio proporcionamos ejemplos en dos carpetas separadas. La carpeta codelab-start tiene los archivos necesarios para comenzar desde el punto actual de este Codelab. La carpeta codelab-done contiene una versión completa de este Codelab, con el cliente y el servidor node.js totalmente funcionales.

Utilizaremos los archivos de la carpeta codelab-start a lo largo de este codelab, aunque si te sientes que no ves progreso en algún momento, no dudes en consultar también la versión del codelab realizado.

Archivos de ejemplo de Codelab

La estructura de archivos de la carpeta codelab-start es la siguiente:

public
├───index.html
├───scripts.js
├───style.css
firebase.json

La carpeta pública contiene páginas estáticas de nuestra aplicación. firebase.json es responsable de enrutar las solicitudes web a nuestra app. En la versión codelab-done también verás un directorio functions, que contiene la lógica para que nuestro servidor proxy (Express) se implemente en las funciones de Google Cloud Functions.

Implementa una prueba de Codelab

Copia los archivos del codelab-start al directorio del proyecto.

$ firebase deploy

Cuando Firebase termine de implementarse, deberías ver la aplicación de Codelab:

e84c1049eb4cca92.png

Para el inicio del flujo de autenticación, se requieren credenciales de socio, que analizaremos en la próxima sección.

5. Manejo de OAuth

OAuth es el estándar web para la delegación de acceso, comúnmente utilizado para que los usuarios concedan a las aplicaciones de terceros el acceso a la información de su cuenta sin compartir las contraseñas. Utilizamos OAuth 2.0 para permitir que los desarrolladores accedan a los dispositivos de los usuarios por medio de Acceso a dispositivos.

7ee31f5d9c37f699.png

Especifica el URI de redireccionamiento

El primer paso del flujo de OAuth implica pasar un conjunto de parámetros al extremo de Google OAuth 2.0. Después de obtener el consentimiento del usuario, los servidores de Google OAuth emitirán una solicitud con un código de autorización al URI de redireccionamiento.

Actualiza la SERVER_URI constante (línea 19) con tu propia URL de Hosting en scripts.js:

const SERVER_URI = "https://[GCP-Project-Id].web.app";

Cuando vuelvas a implementar la app con este cambio, se actualizará el URI de redireccionamiento utilizado para tu proyecto.

$ firebase deploy

Habilita el URI de redireccionamiento

Una vez que actualices el URI de redireccionamiento en el archivo de secuencias de comandos, también debes agregarlo a la lista de URI de redireccionamiento permitidos para el ID de cliente que creaste para tu proyecto. Ve a la página Credenciales de Google Cloud Platform, en la que se mostrarán todas las credenciales que se crearon para tu proyecto.

1a07b624b5e548da.png

En la lista ID de cliente de OAuth 2.0, elige el ID de cliente que creaste en el paso Creación del proyecto. Agrega el URI de redireccionamiento de tu app a la lista de URI de redireccionamiento autorizados de tu proyecto.

6d65b298e1f005e2.png

Intenta acceder

Dirígete a la URL de Hosting que configuraste con Firebase, ingresa tus credenciales de socio y haz clic en el botón ACCEDER. El ID de cliente y el Secreto del cliente son las credenciales que obtuviste de Google Cloud Platform, mientras que el ID del proyecto es de la Consola de Acceso a dispositivos.

78b48906a2dd7c05.png

El botón ACCEDER llevará a tus usuarios a través del flujo de OAuth para tu empresa, empezando por la pantalla de acceso a tu Cuenta de Google. Después de acceder, se les pedirá a los usuarios que otorguen permisos para que tu proyecto acceda a los dispositivos Nest.

e9b7887c4ca420.png

Dado que se trata de una app falsa, Google enviará una advertencia antes de emitir una redireccionamiento.

b227d510cb1df073.png

Haz clic en "Avanzado" y, luego, selecciona la opción para ir a aplicación web (no seguro) a fin de completar el redireccionamiento a tu app.

673a4fd217e24dad.png

De este modo, se proporcionará un código OAuth como parte de la solicitud GET entrante, que la app intercambiará por un token de acceso y un token de actualización.

6. Control de dispositivos

La app de prueba de Acceso a dispositivos usa llamadas a la API de REST de Smart Device Management para controlar los dispositivos de Google Nest. Estas llamadas implican pasar el token de acceso en el encabezado de una solicitud GET o POST, junto con una carga útil necesaria para algunos comandos.

Escribimos una función genérica de solicitud de acceso para manejar estas llamadas. Sin embargo, debes proporcionar el extremo correcto a esta función, así como el objeto de carga útil cuando sea necesario.

function deviceAccessRequest(method, call, localpath, payload = null) {...}
  • método: tipo de solicitud HTTP (GET o POST))
  • llamada: una string que representa nuestra llamada a la API y se usa para enrutar respuestas (listDevices, thermostatMode, temperatureSetpoint)
  • localpath: el extremo al cual se realiza la solicitud, con el ID del proyecto y el ID del dispositivo (agregado después de https://smartdevicemanagement.googleapis.com/v1)
  • carga útil (*): datos adicionales obligatorios para la llamada a la API (por ejemplo, un valor numérico que representa la temperatura de un punto de ajuste)

Crearemos ejemplos de controles de la IU (enumerar los dispositivos, establecer modo, definir temperatura) para controlar un Nest Thermostat:

86f8a193aa397421.png

Estos controles de la IU llamarán a las funciones correspondientes (listDevices(), postThermostatMode(), postTemperatureSetpoint()) de scripts.js. Se dejan en blanco para que las implementes. El objetivo es elegir el método o la ruta correctos y pasar la carga útil a la función deviceAccessRequest(...).

Enumeración de dispositivos

La llamada más sencilla de Acceso a dispositivos es listDevices. Usa una solicitud GET y no requiere carga útil. El extremo se debe estructurar utilizando el projectId. Completa la función listDevices() de la siguiente manera:

function listDevices() {
  var endpoint = "/enterprises/" + projectId + "/devices";
  deviceAccessRequest('GET', 'listDevices', endpoint);
}

Guarda los cambios y vuelve a implementar tu proyecto de Firebase con el siguiente comando:

$ firebase deploy

Una vez que se implemente la nueva versión de la app, intenta volver a cargar la página y haz clic en ENUMERAR DISPOSITIVOS. De este modo, se debería rellenar la lista en "Controles de dispositivos", en la que deberías ver el ID de tu termostato:

b64a198673ed289f.png

Si seleccionas dispositivos de la lista, se actualizará el campo deviceId en el archivo scripts.js. Para los dos controles siguientes, debemos especificar el deviceId del dispositivo específico que queremos controlar.

Control de termostatos

Existen dos características para el control básico de un Nest Thermostat en la API de Smart Device Management ThermostatMode y TemperatureSetpoint. ThermostatMode establece el modo para tu Nest Thermostat en uno de los cuatro modos posibles: {Off, Heat, Cool, HeatCool}. Luego, debemos proporcionar el modo seleccionado como parte de la carga útil.

Reemplaza la función postThermostatMode() en scripts.js por lo siguiente:

function postThermostatMode() {
  var endpoint = "/enterprises/" + projectId + "/devices/" + deviceId + ":executeCommand";
  var tempMode = id("tempMode").value;
  var payload = {
    "command": "sdm.devices.commands.ThermostatMode.SetMode",
    "params": {
      "mode": tempMode
    }
  };
  deviceAccessRequest('POST', 'thermostatMode', endpoint, payload);
}

La siguiente función, postTemperatureSetpoint(), se encarga de establecer la temperatura (en grados Celsius) para tu Nest Thermostat. Existen dos parámetros que se pueden establecer en la carga útil, heatCelsius y coolCelsius, según el modo del termostato seleccionado.

function postTemperatureSetpoint() {
  var endpoint = "/enterprises/" + projectId + "/devices/" + deviceId + ":executeCommand";
  var heatCelsius = parseFloat(id("heatCelsius").value);
  var coolCelsius = parseFloat(id("coolCelsius").value);

  var payload = {
    "command": "",
    "params": {}
  };
  
  if ("HEAT" === id("tempMode").value) {
    payload.command = "sdm.devices.commands.ThermostatTemperatureSetpoint.SetHeat";
    payload.params["heatCelsius"] = heatCelsius;
  }
  else if ("COOL" === id("tempMode").value) {
    payload.command = "sdm.devices.commands.ThermostatTemperatureSetpoint.SetCool";
    payload.params["coolCelsius"] = coolCelsius;
  }
  else if ("HEATCOOL" === id("tempMode").value) {
    payload.command = "sdm.devices.commands.ThermostatTemperatureSetpoint.SetRange";
    payload.params["heatCelsius"] = heatCelsius;
    payload.params["coolCelsius"] = coolCelsius;
  } else {
    console.log("Off and Eco mode don't allow this function");
    return;
  }
  deviceAccessRequest('POST', 'temperatureSetpoint', endpoint, payload);
}

7. Servidor Node.js (opcional)

¡Felicitaciones! Creaste una aplicación web del cliente que puede realizar solicitudes de la API de Smart Device Management desde un navegador. Para aquellos que quieren compilar desde el servidor, deseamos poner en marcha el esfuerzo con un servidor proxy que pueda redirigir las solicitudes desde el navegador.

Para este servidor proxy, usaremos Cloud Functions de Firebase, Node.js y Express.

Inicializa Cloud Functions

Abre una ventana nueva de terminal, ve al directorio del proyecto y ejecuta lo siguiente:

$ firebase init functions

Firebase te hará una serie de preguntas para inicializar las funciones de la nube:

  1. ¿Qué lenguaje te gustaría utilizar para escribir Cloud Functions? JavaScript
  2. ¿Deseas usar ESLint para detectar posibles errores y aplicar estilo? No
  3. ¿Quieres instalar ahora dependencias con Administración de socios de red? Sí

De este modo, se inicializará una carpeta functions en tu proyecto y se instalarán las dependencias necesarias. Verás que la carpeta de tu proyecto contiene un directorio de funciones, con un archivo index.js para definir nuestras Cloud Functions, package.json para definir la configuración y un directorio node_modules para contener las dependencias.

Usaremos dos bibliotecas npm para compilar la funcionalidad del servidor: express y xmlhttprequest. Debes agregar las siguientes entradas a la lista de dependencias en el archivo package.json:

"xmlhttprequest": "^1.8.0",
"express": "^4.17.0"

Luego, si ejecutas npm install desde el directorio de funciones se deberían instalar dependencias para tu proyecto:

$ npm install

Si npm experimenta un problema para descargar paquetes, puedes intentar guardar xmlhttprequest y expresarlo de manera explícita con el siguiente comando:

$ npm install express xmlhttprequest --save

Actualiza al plan Blaze

Si usas el comando firebase deploy, deberás actualizar al plan Blaze, que requiere que agregues una forma de pago a tu cuenta. Ve a Descripción general del proyecto > Uso y facturación y asegúrate de seleccionar el plan Blaze para tu proyecto.

c6a5e5a21397bef6.png

Crea un servidor Express

Un servidor Express sigue un marco de trabajo simple para responder a las entradas GET y solicitudes POST. Construimos un servlet que escucha las solicitudesPOST, las transmite a una URL de destino especificada en la carga útil y contesta con la respuesta recibida de la transferencia.

Modifica tu archivo index.js en el directorio de funciones para que se vea de la siguiente manera:

const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
const functions = require('firebase-functions');
const express = require('express');
const http = require('http');

const app = express();
app.use(express.json());


//***** Device Access - Proxy Server *****//

// Serving Get Requests (Not used) 
app.get('*', (request, response) => {
  response.status(200).send("Hello World!");
});
// Serving Post Requests
app.post('*', (request, response) => {
  
  setTimeout(() => {
    // Read the destination address from payload:
    var destination = request.body.address;
    
    // Create a new proxy post request:
    var xhr = new XMLHttpRequest();
    xhr.open('POST', destination);
    
    // Add original headers to proxy request:
    for (var key in request.headers) {
            var value = request.headers[key];
      xhr.setRequestHeader(key, value);
    }
    
    // Add command/parameters to proxy request:
    var newBody = {};
    newBody.command = request.body.command;
    newBody.params = request.body.params;
    
    // Respond to original request with the response coming
    // back from proxy request (to Device Access Endpoint)
    xhr.onload = function () {
      response.status(200).send(xhr.responseText);
    };
    
    // Send the proxy request!
    xhr.send(JSON.stringify(newBody));
  }, 1000);
});

// Export our app to firebase functions:
exports.app = functions.https.onRequest(app);

Para enrutar solicitudes a nuestro servidor, debemos ajustar las reescrituras desde firebase.json de la siguiente manera:

{
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [{
        "source": "/proxy**",
        "function": "app"
      },{
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

Esto dirigirá las URLs que comiencen con /proxy a nuestro servidor Express y el resto seguirá yendo a nuestro servidor index.html.

Llamadas a la API del proxy

Ahora que tenemos nuestro servidor listo, definamos un URI del proxy en scripts.js para que nuestro navegador envíe solicitudes a esta dirección:

const PROXY_URI = SERVER_URI + "/proxy";

Luego, agrega una función proxyRequest en scripts.js, que tiene la misma firma que la función deviceAccessRequest(...), para las llamadas indirectas de Acceso a dispositivos.

function proxyRequest(method, call, localpath, payload = null) {
    var xhr = new XMLHttpRequest();
    
    // We are doing our post request to our proxy server:
    xhr.open(method, PROXY_URI);
    xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
    xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
    xhr.onload = function () {
      // Response is passed to deviceAccessResponse function:
      deviceAccessResponse(call, xhr.response);
    };
    
    // We are passing the device access endpoint in address field of the payload:
    payload.address = "https://smartdevicemanagement.googleapis.com/v1" + localpath;
    if ('POST' === method && payload)
        xhr.send(JSON.stringify(payload));
    else
        xhr.send();
}

El último paso consiste en reemplazar las llamadas deviceAccessRequest(...) por la función proxyRequest(...), en las funciones postThermostatMode() y postTemperatureSetpoint() dentro de scripts.js.

Ejecuta firebase deploy para actualizar la app.

$ firebase deploy

Ahora, tienes un servidor proxy Node.js en ejecución que usa Express en Cloud Functions.

Proporciona permisos de Cloud Functions

El último paso consiste en revisar los permisos de acceso de tus Cloud Functions y asegurarte de que la aplicación del cliente pueda llamarlos.

En Google Cloud Platform, ve a la pestaña Cloud Functions del menú y, luego, selecciona tu Cloud Function:

461e9bae74227fc1.png

Haz clic en Permisos y, luego, en Agregar miembro. Escribe allUsers en el campo de miembro nuevo y selecciona Cloud Functions > Invocador de Cloud Functions como el rol. Si haces clic en "Guardar", se mostrará un mensaje de advertencia:

3adb01644217578c.png

Si seleccionas "Permitir acceso público", la aplicación del cliente podrá usar tu Cloud Function.

Felicitaciones. Completaste todos los pasos. Ahora, puedes ir a tu aplicación web y probar los controles de dispositivos que se enrutan con tu servidor proxy.

Próximos pasos

¿Buscas formas de ampliar tu conocimiento sobre Acceso a dispositivos? Consulta la documentación de las características para saber más sobre el control de otros dispositivos Nest y el proceso de certificación para conocer los pasos que debes seguir y lanzar tu producto al mundo.

Mejora aún más tus habilidades con la app de ejemplo de aplicación web de Acceso a dispositivos, donde ampliarás tu experiencia de Codelab y, luego, implementarás una aplicación web en funcionamiento para controlar cámaras, timbres y termostatos Nest.