Criar um app da Web para Acesso a Dispositivos

1. Introdução

O programa Acesso a Dispositivos fornece a API Smart Device Management, uma API REST para que os desenvolvedores controlem os dispositivos Google Nest nos apps deles. Os usuários precisam permitir que terceiros acessem os dispositivos Nest.

52f77aa38cda13a6.png

Há três etapas principais para a integração do Acesso a Dispositivos:

  1. Criação de projetos: crie um projeto no Google Cloud Platform e inscreva-se como desenvolvedor no Console do Acesso a Dispositivos.
  2. Vinculação de contas: faça os usuários passarem pelo fluxo de vinculação de contas e receberem um código de acesso. Troque o código por um token de acesso.
  3. Controle do dispositivo: faça solicitações na API Smart Device Management para controlar os dispositivos ao enviar comandos com o token de acesso.

Neste codelab, vamos criar um app da Web que lida com autenticação e chamar a API Smart Device Management para entender melhor como o Acesso a Dispositivos funciona. Também mostraremos como implantar um servidor proxy simples usando o Node.js e Express para encaminhar as solicitações do Acesso a Dispositivos.

Antes de começar, é recomendável recapitular as tecnologias mais comuns da Web que usaremos neste codelab, como a autenticação com OAuth 2.0 ou a criação de um app da Web com Node.js, embora isso não seja um pré-requisito.

Pré-requisitos

  • Node.js 8 ou mais recente
  • Conta do Google com um Nest Thermostat vinculado

O que você vai aprender

  • Como configurar um projeto do Firebase que hospeda páginas da Web estáticas e o Cloud Functions
  • Como emitir solicitações de acesso a dispositivos usando um app da Web baseado em navegador
  • Como criar um servidor proxy com Node.js e Express para encaminhar suas solicitações

2. Criação do projeto

Os desenvolvedores precisam criar um projeto do Google Cloud Platform (GCP) para configurar a integração do Acesso a Dispositivos. Usaremos um ID do cliente e uma chave secreta gerados no projeto do GCP como parte do fluxo do OAuth entre o app do desenvolvedor e o Google Cloud. Os desenvolvedores também precisam acessar o Console do Acesso a Dispositivos para criar um projeto que acessa a API Smart Device Management.

Google Cloud Platform

Acessar o Google Cloud Platform Clique em "Criar um novo projeto" e crie um nome para ele. O ID do projeto [GCP-Project-Id] para o Google Cloud também será exibido. Não perca esse ID, pois ele será usado durante a configuração do Firebase. Chamaremos esse ID de [GCP-Project-Id] neste codelab.

585e926b21994ac9.png

A primeira etapa é ativar a biblioteca de APIs necessária no nosso projeto. Acesse APIs e Serviços > Biblioteca e procure a API Smart Device Management. Você precisa ativar essa API para autorizar seu projeto a fazer solicitações para chamadas de API do Acesso a Dispositivos.

14e7eabc422c7fda.png

Antes de prosseguirmos para criar as credenciais do OAuth, precisamos configurar a tela de permissão OAuth do nosso projeto. Acesse APIs e Serviços > Tela de permissão OAuth Em Tipo de usuário, escolha externo. Para concluir a primeira tela, forneça um nome e um e-mail de suporte para o app, além de dados de contato do desenvolvedor. Na solicitação de Usuários de teste, forneça o endereço de e-mail com os dispositivos vinculados nessa etapa.

Depois de configurar a tela de permissão OAuth, acesse APIs e Serviços > Credenciais. Clique em +Criar credenciais e selecione ID do cliente OAuth. No tipo de app, selecione app da Web.

5de534212d44fce7.png

Dê um nome ao cliente e clique em CRIAR. Adicionaremos uma origem de JavaScript autorizada e um URI de redirecionamento autorizado mais tarde. Ao concluir esse processo, o [Client-Id] e a [Client-Secret] associados a esse cliente do OAuth 2.0 serão exibidos.

e6a670da18952f08.png

Console do Acesso a Dispositivos

Acesse o Console do Acesso a Dispositivos. Se você nunca usou o Console do Acesso a Dispositivos, o contrato dos Termos de Serviço será exibido e haverá uma taxa de registro de US$ 5.

Crie um projeto e dê um nome a ele. Na próxima janela, forneça o [Client-Id] que você recebeu do GCP na etapa anterior.

f8a3f27354bc2625.png

Ao ativar os eventos e concluir as etapas de criação do projeto, a página inicial do seu projeto será aberta. O [Project-Id] será exibido abaixo do nome dado ao projeto.

db7ba33d8b707148.png

Anote o [Project-Id]: ele será usado para enviar solicitações à API Smart Device Management.

3. Configuração do Firebase

O Firebase oferece aos desenvolvedores uma maneira rápida e fácil de implantar apps da Web. Nós vamos desenvolver um app da Web no lado do cliente para a integração do Acesso a Dispositivos usando o Firebase.

Criar um projeto do Firebase

Acesse o Console do Firebase. Clique em Adicionar projeto e selecione o que você criou na etapa Criação do projeto. O projeto será criado no Firebase e vinculado ao seu projeto do GCP [GCP-Project-Id].

Depois que o projeto do Firebase for criado, aparecerá a seguinte tela:

dbb02bbacac093f5.png

Instalar as ferramentas do Firebase

O Firebase oferece um conjunto de ferramentas de CLI para criar e implantar seu app. Para instalar essas ferramentas, abra uma nova janela do terminal e execute o comando a seguir. As ferramentas do Firebase serão instaladas globalmente.

$ npm i -g firebase-tools

Para verificar se as ferramentas do Firebase estão instaladas corretamente, confira as informações da versão.

$ firebase --version

Você pode fazer login nas ferramentas da CLI do Firebase com sua Conta do Google usando o comando de login.

$ firebase login

Inicializar um projeto de hospedagem

Depois de fazer login, a próxima etapa é inicializar um projeto de hospedagem para o app da Web. No terminal, navegue até a pasta onde você quer criar o projeto e execute o seguinte comando:

$ firebase init hosting

O Firebase fará algumas perguntas para começar a criar o projeto de hospedagem:

  1. Selecione uma opção: Usar um dos projetos
  2. Selecione um projeto padrão do Firebase para esse diretório: selecione ***[GCP-Project-Id]***.
  3. O que você quer usar como diretório público? Público
  4. Configurar como app de página única? Sim
  5. Configurar versões e implantações automáticas com o GitHub? Não

Depois que o projeto for inicializado, você poderá implantá-lo no Firebase com o seguinte comando:

$ firebase deploy

O Firebase vai verificar seu projeto e implantar os arquivos necessários na hospedagem em nuvem.

fe15cf75e985e9a1.png

Ao abrir o URL do Hosting em um navegador, você verá a página que acabou de implantar:

e40871238c22ebe2.png

Agora que você já sabe o básico sobre como implantar uma página da Web com o Firebase, vamos implantar o exemplo do codelab.

4. Exemplo de codelab

Use o comando abaixo para clonar o repositório do codelab hospedado no GitHub:

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

Nesse repositório, fornecemos exemplos em duas pastas diferentes. A pasta codelab-start tem os arquivos necessários para você começar do ponto atual deste codelab. A pasta codelab-done contém uma versão completa deste codelab, com o cliente totalmente funcional e o servidor Node.js.

Usaremos os arquivos da pasta codelab-start para este codelab. Se em algum momento você não souber o que fazer, consulte também a versão codelab-done.

Arquivos de exemplo do codelab

A estrutura de arquivos da pasta codelab-start é a seguinte:

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

A pasta pública contém páginas estáticas do nosso app. O firebase.json é responsável por rotear solicitações da Web para nosso app. Na versão codelab-done, você também verá um diretório functions que contém a lógica para que nosso servidor proxy (Express) seja implantado no Cloud Functions.

Implantar exemplo do codelab

Copie os arquivos do codelab-start para o diretório do projeto.

$ firebase deploy

Depois que o Firebase for implantado, você verá o app do codelab:

e84c1049eb4cca92.png

A inicialização do fluxo de autenticação exige credenciais de parceiro, o que será abordado na próxima seção.

5. Processar o OAuth

OAuth é o padrão da Web para delegação de acesso, comumente usado para que os usuários concedam a apps de terceiros acesso às informações da conta sem compartilhar senhas. Usamos o OAuth 2.0 para permitir que os desenvolvedores acessem os dispositivos dos usuários pelo Acesso a Dispositivos.

7ee31f5d9c37f699.png

Especificar o URI de redirecionamento

A primeira etapa do fluxo do OAuth envolve a transmissão de um conjunto de parâmetros para o endpoint do Google OAuth 2.0. Depois de receber o consentimento do usuário, os servidores do Google OAuth emitirão uma solicitação com um código de autorização para o URI de redirecionamento.

Atualize a constante SERVER_URI (linha 19) com seu próprio URL do Hosting no scripts.js:

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

A reimplantação do app com essa mudança atualizará o URI de redirecionamento usado para seu projeto.

$ firebase deploy

Ativar o URI de redirecionamento

Depois de atualizar o URI de redirecionamento no arquivo de scripts, também é preciso adicioná-lo à lista de URIs de redirecionamento permitidos para o ID do cliente criado para o projeto. Acesse a página Credenciais no Google Cloud Platform para ver as credenciais criadas para seu projeto:

1a07b624b5e548da.png

Na lista de IDs do cliente OAuth 2.0, selecione o ID criado na etapa Criação de projeto. Adicione o URI de redirecionamento do seu app à lista de URIs de redirecionamento autorizados do seu projeto.

6d65b298e1f005e2.png

Testar o login

Acesse o URL do Hosting que você configurou com o Firebase, insira suas credenciais de parceiro e clique no botão LOGIN. O ID e a chave secreta do cliente são as credenciais que você recebeu do Google Cloud Platform. O ID do projeto é do Console do Acesso a Dispositivos.

78b48906a2dd7c05.png

O botão LOGIN vai direcionar os usuários pelo fluxo do OAuth da sua empresa, começando com a tela de login na Conta do Google. Depois de fazer login, os usuários devem fornecer permissões para que seu projeto acesse os dispositivos Nest deles.

e9b7887c4ca420.png

Como esse é um app fictício, o Google emitirá um aviso antes do redirecionamento.

b227d510cb1df073.png

Clique em "Avançado" e selecione "Acessar web.app (não seguro)" para concluir o redirecionamento para seu app.

673a4fd217e24dad.png

Isso fornece um código OAuth como parte da solicitação GET de entrada, que o app trocará por um token de acesso e um token de atualização.

6. Controle do dispositivo

O app de exemplo Acesso a Dispositivos usa chamadas da API REST Smart Device Management para controlar dispositivos Google Nest. Essas chamadas envolvem a transmissão do token de acesso no cabeçalho de uma solicitação GET ou POST, assim como um payload necessário para determinados comandos.

Nós escrevemos uma função de solicitação de acesso genérica para lidar com essas chamadas. No entanto, você deve fornecer o endpoint correto e, quando necessário, o objeto de payload para essa função.

function deviceAccessRequest(method, call, localpath, payload = null) {...}
  • method: tipo de solicitação HTTP (GET ou POST))
  • call: uma string que representa nossa chamada de API, usada para encaminhar respostas (listDevices, thermostatMode, temperatureSetpoint)
  • localpath: endpoint para o qual a solicitação é feita, contendo o ID do projeto e o ID do dispositivo (incluídos após https://smartdevicemanagement.googleapis.com/v1)
  • payload (*): outros dados necessários para a chamada de API, como um valor numérico que representa uma temperatura programada

Criaremos controles de IU de exemplo (Listar dispositivos, Definir modo, Definir temperatura) para controlar um Nest Thermostat:

86f8a193aa397421.png

Esses controles de IU chamarão as funções correspondentes (listDevices(), postThermostatMode(), postTemperatureSetpoint()) em scripts.js. Eles ficarão em branco para você implementar. O objetivo é escolher o método/caminho correto e transmitir o payload para a função deviceAccessRequest(...).

Listar dispositivos

A chamada do Acesso a Dispositivos mais simples é a listDevices. Ela usa uma solicitação GET e não requer payload. O endpoint precisa ser estruturado usando o projectId. Conclua a função listDevices() da seguinte maneira:

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

Salve as mudanças e implante o projeto do Firebase novamente com o comando a seguir:

$ firebase deploy

Após a implantação da nova versão do app, atualize a página e clique em LISTAR DISPOSITIVOS. A lista exibida em "Controle do dispositivo" será preenchida, e você verá o ID do termostato:

b64a198673ed289f.png

A seleção de dispositivos na lista atualizará o campo deviceId no arquivo scripts.js. Nos próximos dois controles, precisamos especificar o deviceId para o dispositivo que queremos controlar.

Controle do termostato

Há duas características do controle básico de um Nest Thermostat na API Smart Device Management: ThermostatMode e TemperatureSetpoint. A ThermostatMode define o modo do Nest Thermostat como um dos quatro modos possíveis: {Off, Heat, Cool, HeatCool}. Em seguida, precisamos fornecer o modo selecionado como parte do payload.

Substitua a função postThermostatMode() no scripts.js pelo seguinte:

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

A próxima função, postTemperatureSetpoint(), cuida da definição da temperatura (em Celsius) do Nest Thermostat. É possível definir dois parâmetros no payload, heatCelsius e coolCelsius, dependendo do modo de termostato selecionado.

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)

Parabéns, Você criou um app da Web do lado do cliente que pode fazer solicitações da API Smart Device Management em um navegador. Se quiser criar do lado do servidor, ajudaremos com um servidor proxy que pode redirecionar suas solicitações do navegador.

Para esse servidor proxy, usaremos o Cloud Functions do Firebase, o Node.js e o Express.

Inicializar o Cloud Functions

Abra uma nova janela do terminal, navegue até o diretório do projeto e execute o seguinte:

$ firebase init functions

O Firebase fará algumas perguntas para inicializar o Cloud Functions:

  1. Qual linguagem você quer usar para programar o Cloud Functions? JavaScript
  2. Você quer usar o ESlint para identificar prováveis bugs e aplicar o estilo? Não
  3. Você quer instalar dependências com o NPM agora? Sim

Isso inicializará uma pasta functions no seu projeto, além de instalar as dependências necessárias. Você verá que a pasta do projeto contém um diretório de funções com um arquivo index.js para definir o Cloud Functions, um package.json para definir as configurações e um node_modules que contém as dependências.

Usaremos duas bibliotecas npm para criar a funcionalidade do lado do servidor: express e xmlhttprequest. Você precisará adicionar as seguintes entradas à lista de dependências no arquivo package.json:

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

Em seguida, ao executar a instalação do npm a partir do diretório de funções, as dependências do projeto serão instaladas:

$ npm install

Caso o npm tenha algum problema com o download dos pacotes, salve explicitamente o xmlhttprequest e o Express com o seguinte comando:

$ npm install express xmlhttprequest --save

Fazer upgrade para o plano Blaze

Para usar o comando firebase deploy, será preciso fazer upgrade para o plano Blaze, que exige a inclusão de uma forma de pagamento à sua conta. Acesse Visão geral do projeto > Uso e faturamento e selecione o plano Blaze.

c6a5e5a21397bef6.png

Criar o servidor Express

Um servidor Express segue um framework simples para responder às solicitações GET e POST de entrada. Criamos um servlet que ouve solicitações POST, as transmite para um URL de destino especificado no payload e responde com a resposta recebida da transferência.

Modifique o arquivo index.js no diretório de funções para que ele fique assim:

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 encaminhar solicitações ao nosso servidor, precisamos ajustar as substituições do firebase.json da seguinte forma:

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

Isso encaminhará os URLs que começam com /proxy para nosso servidor Express, enquanto o restante continuará acessando index.html.

Chamadas de API do proxy

Agora que nosso servidor está pronto, vamos definir um URI de proxy no scripts.js para que o navegador envie solicitações a este endereço:

const PROXY_URI = SERVER_URI + "/proxy";

Em seguida, adicione uma função proxyRequest no scripts.js, que tem a mesma assinatura da função deviceAccessRequest(...), para chamadas indiretas do Acesso 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();
}

A última etapa é substituir as chamadas deviceAccessRequest(...) pela função proxyRequest(...) nas funções postThermostatMode() e postTemperatureSetpoint() dentro do scripts.js.

Execute firebase deploy para atualizar o app.

$ firebase deploy

Agora você tem um servidor proxy Node.js em execução usando o Express no Cloud Functions.

Fornecer permissões da função do Cloud

A última etapa é verificar as permissões de acesso do Cloud Functions e se o app do lado do cliente poderá chamá-las.

No Google Cloud Platform, acesse a guia Cloud Functions no menu e selecione a função do Cloud:

461e9bae74227fc1.png

Clique em Permissões e em Adicionar membro. Escreva allUsers no novo campo de membros e selecione o papel Cloud Functions > Invocador do Cloud Functions. Clique em "Salvar" para exibir a seguinte mensagem de aviso:

3adb01644217578c.png

Selecione "Permitir acesso público" para que o app do lado do cliente possa usar sua Função do Cloud.

Parabéns! Você concluiu todas as etapas. Agora você pode acessar seu app da Web e usar os controles do dispositivo roteados pelo servidor proxy.

Próximas etapas

Quer ampliar seus conhecimentos sobre o Acesso a Dispositivos? Confira a documentação de características para saber mais sobre como controlar outros dispositivos Nest e o processo de certificação para aprender as etapas de lançamento do seu produto.

Desenvolva ainda mais suas habilidades com o app de exemplo da Web do Acesso a Dispositivos. Nele, você poderá aprofundar a experiência do codelab e implantar um app da Web em funcionamento para controlar câmeras, campainhas e termostatos Nest.