Créer une application Web Device Access

1. Introduction

L'API Smart Device Management est disponible via Device Access. Il s'agit d'une API REST qui permet aux développeurs de contrôler des appareils Google Nest depuis leurs applications. Les utilisateurs doivent également autoriser les tiers à accéder à leurs appareils Nest.

52f77aa38cda13a6.png

L'intégration de Device Access se décline en trois grandes étapes :

  1. Création du projet : créez un projet dans Google Cloud Platform, et inscrivez-vous en tant que développeur dans la console Device Access.
  2. Association du compte : guidez les utilisateurs tout au long de la procédure à suivre pour associer le compte et récupérez un code d'accès. Échangez le code contre un jeton d'accès.
  3. Commandes de contrôle des appareils : exécutez des requêtes API Smart Device Management pour contrôler les appareils en envoyant des commandes à l'aide du jeton d'accès.

Dans cet atelier de programmation, nous allons explorer en détail le fonctionnement de Device Access en créant une application Web qui gère l'authentification et effectue les appels de l'API Smart Device Management. Nous verrons également comment déployer un simple serveur proxy à l'aide de Node.js et Express pour acheminer les requêtes Device Access.

Avant de commencer, nous vous conseillons de revoir les technologies Web courantes que nous utiliserons dans cet atelier de programmation, comme l'authentification avec OAuth 2.0 ou la création d'une application Web avec Node.js. Mais ce n'est pas une obligation.

Ce dont vous avez besoin

  • Node.js 8 ou version ultérieure
  • Un compte Google avec un thermostat Nest associé

Objectifs de l'atelier

  • Configurer un projet Firebase hébergeant des pages Web statiques et des fonctions Cloud
  • Émettre des demandes d'accès aux appareils via une application Web basée sur un navigateur
  • Créer un serveur proxy avec Node.js et Express pour acheminer vos requêtes

2. Créer le projet

Les développeurs doivent créer un projet Google Cloud Platform (GCP) pour configurer l'intégration de Device Access. Entre l'application du développeur et Google Cloud, le flux OAuth utilisera un ID client et un code secret de client générés dans le projet GCP. Les développeurs doivent également accéder à la console Device Access pour créer un projet et accéder à l'API Smart Device Management.

Google Cloud Platform

Accédez à Google Cloud Platform. Cliquez sur "Créer un projet", puis indiquez un nom. Un ID de projet [GCP-Project-Id] pour Google Cloud s'affiche aussi. Veuillez le noter, car nous en aurons besoin pour configurer Firebase. Nous appellerons cet ID [GCP-Project-Id] tout au long de cet atelier de programmation.

585e926b21994ac9.png

La première étape consiste à activer la bibliothèque d'API nécessaire pour notre projet. Accédez à API et services > Bibliothèque et recherchez l'API Smart Device Management. Vous devez activer cette API pour autoriser votre projet à envoyer des requêtes aux appels de l'API Device Access.

14e7eabc422c7fda.png

Avant de créer des identifiants OAuth, nous devons configurer l'écran de consentement OAuth pour notre projet. Accédez à API et services > Écran de consentement OAuth. Dans le champ Type d'utilisateur, sélectionnez externe. Renseignez les champs du premier écran en indiquant un nom et une adresse e-mail d'assistance pour votre application, ainsi que les coordonnées du développeur. Lorsque vous êtes invité à spécifier des Utilisateurs tests, veillez à fournir l'adresse e-mail des appareils associés.

Une fois que vous avez configuré l'écran de consentement OAuth, accédez à API et services > Identifiants. Cliquez sur Créer des identifiants, puis sélectionnez ID client OAuth. Sélectionnez Application Web comme type d'application.

5de534212d44fce7.png

Indiquez un nom pour votre client, puis cliquez sur CRÉER. Par la suite, nous ajouterons une origine JavaScript autorisée et un URI de redirection autorisé. Une fois ce processus terminé, le [Client-Id] et le [Client-Secret] associés au client OAuth 2.0 s'affichent.

e6a670da18952f08.png

Console Device Access

Accédez à la Console Device Access. Si vous ne l'avez jamais utilisée, un accord sur les conditions d'utilisation s'affiche et vous devez vous acquitter des frais d'inscription, qui s'élèvent à 5 $.

Créez un projet et nommez-le. Dans la fenêtre suivante, indiquez le [Client-Id] que vous avez reçu de GCP à l'étape précédente.

f8a3f27354bc2625.png

Une fois les événements activés et le projet créé, la page d'accueil de celui-ci s'affiche. Le [Project-Id] se trouve sous le nom que vous avez donné à votre projet.

db7ba33d8b707148.png

Notez le [Project-Id], car nous en aurons besoin pour envoyer des requêtes à l'API Smart Device Management.

3. Configurer Firebase

Firebase permet aux développeurs de déployer facilement et rapidement des applications Web. Nous allons développer une application Web côté client pour notre intégration de Device Access à l'aide de Firebase.

Créer un projet Firebase

Accédez à la Console Firebase. Cliquez sur Ajouter un projet, puis sélectionnez le projet que vous avez créé à l'étape Créer le projet. Cette opération va créer un projet Firebase, qui sera associé à votre projet GCP [GCP-Project-Id].

Une fois le projet Firebase créé, l'écran suivant doit s'afficher :

dbb02bbacac093f5.png

Installer les outils Firebase

Firebase fournit un ensemble d'outils de CLI pour créer et déployer votre application. Pour les installer, ouvrez une nouvelle fenêtre de terminal et exécutez la commande ci-dessous. Cette opération va installer les outils Firebase à l'échelle globale.

$ npm i -g firebase-tools

Pour savoir si les outils Firebase sont correctement installés, vérifiez les informations sur la version.

$ firebase --version

Vous pouvez vous connecter aux outils de la CLI Firebase à l'aide de votre compte Google via la commande de connexion.

$ firebase login

Initialiser le projet d'hébergement

Une fois que vous pouvez vous connecter, vous devez initialiser un projet d'hébergement pour votre application Web. Depuis le terminal, accédez au dossier dans lequel vous souhaitez créer votre projet, puis exécutez la commande suivante :

$ firebase init hosting

Firebase va vous poser une série de questions pour vous aider à démarrer le projet d'hébergement :

  1. Veuillez sélectionner une option : Utiliser un projet existant
  2. Sélectionnez un projet Firebase par défaut pour ce répertoire : choisissez ***[GCP-Project-Id]***
  3. Que voulez-vous utiliser comme répertoire public ? — Public
  4. Configurer en tant qu'appli à page unique ? — Oui
  5. Configurer les builds et les déploiements automatiques avec GitHub ? — Non

Une fois votre projet initialisé, vous pouvez le déployer sur Firebase à l'aide de la commande suivante :

$ firebase deploy

Firebase analyse votre projet et déploie les fichiers nécessaires à l'emplacement d'hébergement dans le cloud.

fe15cf75e985e9a1.png

Lorsque vous ouvrez l'URL d'hébergement dans un navigateur, la page que vous venez de déployer doit s'afficher :

e40871238c22ebe2.png

Maintenant que vous connaissez les principes de base du déploiement d'une page Web avec Firebase, passons au déploiement de l'exemple de notre atelier de programmation.

4. Exemple d'atelier de programmation

Vous pouvez cloner le dépôt de l'atelier de programmation hébergé sur GitHub à l'aide de la commande ci-dessous :

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

Ce dépôt contient des exemples dans deux dossiers distincts. Le dossier codelab-start inclut les fichiers nécessaires pour vous aider à démarrer à partir de cette étape de l'atelier de programmation. Dans le dossier codelab-done, vous trouverez une version complète de cet atelier de programmation, avec le client et le serveur Node.js entièrement fonctionnels.

Nous utiliserons les fichiers du dossier codelab-start tout au long de cet atelier de programmation. Cependant, si à un moment donné vous êtes bloqué, n'hésitez pas à vous reporter à la version codelab-done.

Fichiers d'exemple de l'atelier de programmation

La structure de fichiers du dossier codelab-start est la suivante :

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

Le dossier public contient les pages statiques de notre application. firebase.json est responsable de l'acheminement des requêtes Web jusqu'à notre application. Dans la version codelab-done, vous trouverez également le répertoire functions, qui contient la logique de notre serveur proxy (Express) à déployer sur des fonctions Google Cloud.

Déployer l'exemple de l'atelier de programmation

Copiez les fichiers de codelab-start dans le répertoire de votre projet.

$ firebase deploy

Une fois Firebase déployé, l'application de l'atelier de programmation doit s'afficher :

e84c1049eb4cca92.png

La procédure d'authentification nécessite des identifiants de partenaire, que nous aborderons à la section suivante.

5. Gérer OAuth

OAuth est la norme Web de délégation d'accès, couramment utilisée pour autoriser les applications tierces à accéder aux informations de compte sans partager les mots de passe. Nous utilisons OAuth 2.0 pour permettre aux développeurs d'accéder aux appareils des utilisateurs via Device Access.

7ee31f5d9c37f699.png

Spécifier l'URI de redirection

La première étape du flux OAuth consiste à transmettre un ensemble de paramètres au point de terminaison Google OAuth 2.0. Une fois l'accord de l'utilisateur obtenu, les serveurs Google OAuth envoient une requête avec un code d'autorisation à votre URI de redirection.

Mettez à jour la constante SERVER_URI (ligne 19) avec votre propre URL d'hébergement dans scripts.js :

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

En redéployant l'application avec cette modification, vous mettrez à jour l'URI de redirection utilisé pour votre projet.

$ firebase deploy

Activer l'URI de redirection

Une fois l'URI de redirection mis à jour dans le fichier de scripts, vous devez aussi l'ajouter à la liste des URI de redirection autorisés pour l'ID client que vous avez créé pour votre projet. Accédez à la page Identifiants de Google Cloud Platform. Vous y trouverez la liste des identifiants créés pour votre projet :

1a07b624b5e548da.png

Sous la liste des ID client OAuth 2.0, sélectionnez l'ID client que vous avez créé à l'étape Créer le projet. Ajoutez l'URI de redirection de votre application à la liste des URI de redirection autorisés pour votre projet.

6d65b298e1f005e2.png

Essayer de se connecter

Accédez à l'URL d'hébergement que vous avez configuré avec Firebase, saisissez vos identifiants de partenaire, puis cliquez sur le bouton SE CONNECTER. L'ID client et le code secret du client sont ceux fournis par Google Cloud Platform. L'ID de projet est attribué par la console Device Access.

78b48906a2dd7c05.png

Lorsqu'ils cliqueront sur le bouton SE CONNECTER, les utilisateurs seront redirigés vers le flux OAuth de votre entreprise et verront d'abord l'écran de connexion à leur compte Google. Une fois connectés, ils seront invités à autoriser votre projet à accéder à leurs appareils Nest.

e9b7887c4ca420.png

Comme il s'agit d'une application de simulation, Google va afficher un avertissement avant toute redirection.

b227d510cb1df073.png

Cliquez sur "Paramètres avancés", puis sélectionnez "Accéder à web.app (non sécurisé)" pour rediriger les utilisateurs vers votre application.

673a4fd217e24dad.png

Ce processus fournira un code OAuth pour la demande GET entrante, que l'application échangera ensuite contre un jeton d'accès et un jeton d'actualisation.

6. Commandes de contrôle des appareils

L'application exemple Device Access utilise des appels d'API REST Smart Device Management pour contrôler les appareils Google Nest. Ces appels impliquent de transmettre le jeton d'accès dans l'en-tête d'une demande GET ou d'une requête POST, ainsi que la charge utile requise pour certaines commandes.

Nous avons écrit une fonction de demande d'accès générique pour gérer ces appels. Cependant, vous devrez fournir le bon point de terminaison à cette fonction, ainsi que l'objet de charge utile, le cas échéant.

function deviceAccessRequest(method, call, localpath, payload = null) {...}
  • method (méthode) : type de requête HTTP (GET ou POST))
  • call (appel) : chaîne représentant notre appel d'API, utilisé pour acheminer les réponses (listDevices, thermostatMode, temperatureSetpoint)
  • localpath (chemin d'accès local) : point de terminaison auquel la requête est envoyée, contenant l'ID du projet et l'ID de l'appareil (ajoutés à https://smartdevicemanagement.googleapis.com/v1)
  • payload (charge utile) (*) : données supplémentaires requises pour l'appel d'API (par exemple, une valeur numérique représentant la température pour une température mémorisée)

Nous allons créer des exemples de commandes d'interface utilisateur (afficher la liste des appareils, définir le mode et régler la température) pour contrôler un thermostat Nest :

86f8a193aa397421.png

Ces commandes d'interface utilisateur appellent les fonctions correspondantes (listDevices(), postThermostatMode() et postTemperatureSetpoint()) depuis scripts.js. Elles sont vides pour que vous puissiez les implémenter vous-même. L'objectif est de choisir la méthode ou le chemin d'accès appropriés, et de transmettre la charge utile à la fonction deviceAccessRequest(...).

Afficher la liste des appareils

L'appel Device Access le plus simple est listDevices. Il utilise une demande GET et ne nécessite aucune charge utile. Le point de terminaison doit être structuré à l'aide du projectId. Complétez votre fonction listDevices() comme suit :

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

Enregistrez les modifications et déployez à nouveau votre projet Firebase en exécutant la commande suivante :

$ firebase deploy

Une fois la nouvelle version de l'application déployée, actualisez la page et cliquez sur RÉPERTORIER LES APPAREILS. Les champs de la section "Contrôle des appareils" devraient alors se remplir et vous devriez pouvoir y trouver l'ID de votre thermostat :

b64a198673ed289f.png

Si vous sélectionnez des appareils dans la liste, le champ deviceId se met à jour dans le fichier scripts.js. Pour les deux commandes suivantes, nous devons indiquer le deviceId de l'appareil que vous souhaitez contrôler.

Régler le thermostat

L'API Smart Device Management propose deux commandes de base pour un thermostat Nest. ThermostatMode et TemperatureSetpoint. ThermostatMode règle le mode de votre thermostat Nest sur l'un des quatre modes disponibles : {Off, Heat, Cool, HeatCool}. Nous devons ensuite fournir le mode sélectionné dans la charge utile.

Remplacez votre fonction postThermostatMode() dans scripts.js par le code suivant :

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 fonction ci-dessous, postTemperatureSetpoint(), gère le réglage de la température (en degrés Celsius) de votre thermostat Nest. Vous pouvez définir deux paramètres dans la charge utile, heatCelsius et coolCelsius, en fonction du mode sélectionné pour le thermostat.

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. Serveur Node.js (facultatif)

Félicitations ! Vous avez créé une application Web côté client qui peut envoyer des requêtes API Smart Device Management depuis un navigateur. Si vous souhaitez créer une application côté serveur, nous allons vous aider à vous lancer et créer un serveur proxy capable de rediriger vos requêtes à partir du navigateur.

Pour ce serveur proxy, nous allons utiliser les fonctions Cloud de Firebase, Node.js et Express.

Initialiser les fonctions Cloud

Ouvrez une nouvelle fenêtre de terminal, accédez au répertoire de votre projet et exécutez la commande suivante :

$ firebase init functions

Firebase va vous poser une série de questions pour initialiser les fonctions Cloud :

  1. Quel langage souhaitez-vous utiliser pour écrire des fonctions Cloud ? — JavaScript
  2. Voulez-vous utiliser ESLint pour détecter les bugs potentiels et appliquer le style ? — Non
  3. Voulez-vous installer des dépendances avec npm maintenant ? — Oui

Cette opération initialise un dossier functions dans votre projet et installe les dépendances nécessaires. Vous constaterez que le dossier de votre projet contient un répertoire de fonctions. Le fichier index.js permet de spécifier les fonctions Cloud, le fichier package.json permet de définir les paramètres et le répertoire node_modules contient les dépendances.

Nous allons utiliser deux bibliothèques npm pour créer la fonctionnalité côté serveur : Express et xmlhttprequest. Vous devez ajouter les entrées suivantes à la liste des dépendances dans le fichier package.json :

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

Ensuite, l'exécution de npm install à partir du répertoire des fonctions devrait installer les dépendances de votre projet :

$ npm install

Si npm rencontre un problème lors du téléchargement des packages, vous pouvez essayer d'enregistrer xmlhttprequest et Express explicitement à l'aide de la commande suivante :

$ npm install express xmlhttprequest --save

Passer au forfait Blaze

Pour utiliser la commande firebase deploy, vous devez passer au forfait Blaze, ce qui vous oblige à ajouter un mode de paiement à votre compte. Accédez à Vue d'ensemble du projet > Utilisation et facturation, puis veillez à sélectionner la formule Blaze pour votre projet.

c6a5e5a21397bef6.png

Créer un serveur Express

Un serveur Express suit un framework simple pour répondre aux requêtes GET et POST entrantes. Nous avons créé un servlet qui écoute les requêtes POST, nous le transmettons à une URL de destination spécifiée dans la charge utile et nous renvoyons la réponse reçue du transfert.

Modifiez votre fichier index.js dans le répertoire des fonctions pour qu'il se présente comme suit :

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);

Pour envoyer les requêtes vers notre serveur, nous devons ajuster les réécritures depuis firebase.json comme suit :

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

Cela permet d'acheminer les URL commençant par /proxy jusqu'à notre serveur Express. Les autres continueront d'être renvoyées vers notre index.html.

Appels à l'API du proxy

Maintenant que le serveur est prêt, définissez un URI de proxy dans scripts.js pour que le navigateur envoie les requêtes à l'adresse suivante :

const PROXY_URI = SERVER_URI + "/proxy";

Ajoutez ensuite une fonction proxyRequest dans scripts.js, qui a la même signature que la fonction deviceAccessRequest(...) pour les appels Device Access indirects.

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();
}

La dernière étape consiste à remplacer les appels deviceAccessRequest(...) par la fonction proxyRequest(...), dans les fonctions postThermostatMode() et postTemperatureSetpoint() de scripts.js.

Exécutez firebase deploy pour mettre à jour l'application.

$ firebase deploy

Vous disposez désormais d'un serveur proxy Node.js opérationnel créé à l'aide d'Express sur Cloud Functions.

Accorder des autorisations à une fonction Cloud

La dernière étape consiste à examiner les autorisations d'accès pour vos fonctions Cloud et à vérifier que votre application côté client pourra les appeler.

Dans Google Cloud Platform, accédez à l'onglet Cloud Functions depuis le menu, puis sélectionnez votre fonction Cloud :

461e9bae74227fc1.png

Cliquez sur Autorisations, puis sur Ajouter un membre. Saisissez allUsers dans le champ du nouveau membre, puis sélectionnez le rôle Cloud Functions > Demandeur Cloud Functions. Cliquez sur "Enregistrer". Un message d'avertissement s'affiche alors :

3adb01644217578c.png

Si vous sélectionnez "Autoriser l'accès public", votre application côté client sera en mesure d'utiliser votre fonction Cloud.

Félicitations ! Vous avez terminé toutes les étapes. Vous pouvez à présent accéder à votre application Web et tester les commandes de contrôle des appareils acheminées via votre serveur proxy.

Étapes suivantes

Vous souhaitez approfondir votre expertise de Device Access ? Consultez la documentation sur les caractéristiques pour en savoir plus sur le contrôle d'autres appareils Nest, ainsi que ce guide sur le processus de certification pour savoir comment lancer votre produit à l'internationale.

Approfondissez vos connaissances grâce à l'application Web exemple Device Access. Elle vous permettra de consolider ce que vous avez appris dans l'atelier de programmation et de déployer une application Web opérationnelle pour contrôler des caméras, des sonnettes et des thermostats Nest.