Conectar dispositivos de casa inteligente ao Google Assistente

1. Antes de começar

Como desenvolvedor da Internet das Coisas (IoT), é possível criar integrações entre nuvens que permitem que os usuários controlem os dispositivos por meio dos controles por toque no app Google Home e nos comandos de voz com o Google Assistente.

79266e5f45e6ae20.gif

As integrações entre nuvens dependem do Home Graph para fornecer dados de contexto sobre a casa e os dispositivos, criando um mapa lógico da casa. Esse contexto permite que o Google Assistente entenda melhor as solicitações do usuário em relação à localização na casa. Por exemplo, o Home Graph pode armazenar o conceito de uma sala de estar que contém vários tipos de dispositivos de diferentes fabricantes, como termostato, luminária, ventilador e aspirador.

d009cef0f903d284.jpeg

Pré-requisitos

O que você vai criar

Neste codelab, você vai publicar um serviço de nuvem que gerencia uma máquina de lavar virtual inteligente e criar uma integração entre nuvens e conectá-la ao Google Assistente.

O que você vai aprender

  • Como implantar um serviço de casa inteligente em nuvem
  • Como conectar seu serviço ao Google Assistente
  • Como publicar alterações de estado do dispositivo no Google

O que é necessário

2. Primeiros passos

Ativar os Controles de atividade

Para usar o Google Assistente, você precisa compartilhar determinados dados de atividade com o Google. O Google Assistente precisa desses dados para funcionar corretamente. No entanto, esse requisito não é específico do SDK. Para compartilhar esses dados, crie uma Conta do Google, caso ainda não tenha uma. Você pode usar qualquer Conta do Google. Ela não precisa ser sua conta de desenvolvedor.

Abra a página Controles de atividade da Conta do Google que você quer usar com o Google Assistente.

Verifique se os botões a seguir estão ativados:

  • Atividade na Web e de apps: também marque a caixa de seleção Incluir o histórico do Chrome e a atividade em sites, apps e dispositivos que usam serviços do Google.
  • Informações do dispositivo
  • Atividade de voz e áudio

Criar um projeto de integração entre nuvens

  1. Acesse o console do desenvolvedor.
  2. Clique em Criar projeto, insira um nome e clique em Criar projeto.

Nome do projeto

Selecionar a integração entre nuvens

Na página Início do projeto do console do desenvolvedor, selecione Adicionar integração entre nuvens em Entre nuvens.

Adicionar a integração entre nuvens

Instalar a CLI do Firebase

A interface de linha de comando (CLI, na sigla em inglês) do Firebase permite veicular seus apps da Web localmente e implantá-los no Firebase Hosting.

Para instalar a CLI, execute o seguinte comando NPM no terminal:

npm install -g firebase-tools

Para verificar se a CLI foi instalada corretamente, execute o seguinte:

firebase --version

Autorize a CLI do Firebase com sua Conta do Google executando o seguinte comando:

firebase login

3. Executar o app inicial

Após configurar o ambiente para desenvolvedores, implante o projeto inicial para verificar se a configuração está correta.

Faça o download do código-fonte

Clique neste link para fazer o download do exemplo deste codelab na máquina de desenvolvimento:

Também é possível clonar o repositório do GitHub na linha de comando:

git clone https://github.com/google-home/smarthome-washer.git

Sobre o projeto

O projeto inicial contém os seguintes subdiretórios:

  • public: Uma IU de front-end para controlar e monitorar o estado da lavadora inteligente.
  • functions: Um serviço de nuvem totalmente implementado que gerencia a lavadora inteligente com o Cloud Functions para Firebase e o Firebase Realtime Database.

Criar um projeto do Firebase

  1. Acesse o Firebase.
  2. Clique em Criar um projeto e insira o nome do projeto.
  3. Marque a caixa de seleção do contrato e clique em Continuar. Se não houver uma caixa de seleção de acordo, pule esta etapa.
    Criar projeto do Firebase
  4. Depois que o projeto do Firebase for criado, encontre o ID dele. Acesse Visão geral do projeto e clique no ícone de configurações > Configurações do projeto.
    Abrir as configurações do projeto
  5. Seu projeto está listado na guia Geral.
    Configurações gerais do projeto

Conectar-se ao Firebase

Acesse o diretório washer-start e configure a CLI do Firebase com seu projeto de integração:

cd washer-start
firebase use <firebase-project-id>

Configurar o projeto do Firebase

Inicialize um projeto do Firebase.

firebase init

Selecione os recursos da CLI, Realtime Database, Functions e Hosting, que inclui o Firebase Hosting.

? Which Firebase CLI features do you want to set up for this directory? Press Space to select features, then
 Enter to confirm your choices.
❯◉ Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance
 ◯ Firestore: Configure security rules and indexes files for Firestore
 ◉ Functions: Configure a Cloud Functions directory and its files
 ◉ Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
 ◯ Hosting: Set up GitHub Action deploys
 ◯ Storage: Configure a security rules file for Cloud Storage
 ◯ Emulators: Set up local emulators for Firebase products
 ◯ Remote Config: Configure a template file for Remote Config
 ◯ Extensions: Set up an empty Extensions manifest

Isso inicializará as APIs e os recursos necessários para seu projeto.

Quando solicitado, inicialize o Realtime Database. É possível usar o local padrão para a instância do banco de dados.

? It seems like you haven't initialized Realtime Database in your project yet. Do you want to set it up?
Yes

? Please choose the location for your default Realtime Database instance:
us-central1

Como você está usando o código do projeto inicial, escolha o arquivo padrão para as regras de segurança e tome cuidado para não substituir o arquivo de regras do banco de dados.

? File database.rules.json already exists. Do you want to overwrite it with the Realtime Database Security Rules for <project-ID>-default-rtdb from the Firebase Console?
No

Se você estiver reinizializando o projeto, selecione Substituir quando for solicitado que você inicialize ou substitua uma base de código.

? Would you like to initialize a new codebase, or overwrite an existing one?
Overwrite

Ao configurar as funções, use os arquivos padrão e tome cuidado para não substituir os arquivos index.js e package.json da amostra do projeto.

? What language would you like to use to write Cloud Functions?
JavaScript

? Do you want to use ESLint to catch probable bugs and enforce style?
No

? File functions/package.json already exists. Overwrite?
No

? File functions/index.js already exists. Overwrite?
No

Se você estiver reduzindo o projeto, selecione Não quando for solicitado que você inicialize ou substitua o arquivo functions/.gitignore.

? File functions/.gitignore already exists. Overwrite?
No
? Do you want to install dependencies with npm now?
Yes

Por fim, defina a configuração do Hosting para usar o diretório public no código do projeto e use o arquivo index.html. Selecione Não quando for solicitado usar o ESLint.

? What do you want to use as your public directory?
public

? Configure as a single-page app (rewrite all urls to /index.html)?
Yes

? Set up automatic builds and deploys with GitHub?
No

? File public/index.html already exists. Overwrite?
 No

Se o ESLint foi ativado acidentalmente, há dois métodos disponíveis para desativá-lo:

  1. Usando a GUI, acesse a pasta ../functions no projeto, selecione o arquivo oculto .eslintrc.js e exclua-o. Não confunda com .eslintrc.json, que tem um nome parecido.
  2. Usando a linha de comando:
    cd functions
    rm .eslintrc.js
    

No arquivo washer-done/firebase.json, complete o código com:

{
  "database": {
    "rules": "database.rules.json"
  },
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
    "headers": [{
      "source" : "**/*.@(js|html)",
      "headers" : [ {
        "key" : "Cache-Control",
        "value" : "max-age=0"
      } ]
    }],
  "functions": [
    {
      "source": "functions",
      "codebase": "default",
      "ignore": [
        "node_modules",
        ".git",
        "firebase-debug.log",
        "firebase-debug.*.log",
        "*.local"
      ]
    }
  ]
}

Implantar no Firebase

Após instalar as dependências e configurar o projeto, você já pode executar o app pela primeira vez.

firebase deploy

Esta é a resposta do console que você deverá ver:

...

✔ Deploy complete!

Project Console: https://console.firebase.google.com/project/<firebase-project-id>/overview
Hosting URL: https://<firebase-project-id>.web.app

O comando implanta um app da Web, além de várias Cloud Functions para Firebase.

Abra o URL de hospedagem no navegador (https://.web.app) para ver o app da Web. Aparecerá a seguinte interface:

5845443e94705557.png

Essa IU da Web representa uma plataforma de terceiros para exibir ou modificar estados do dispositivo. Para começar a preencher seu banco de dados com informações do dispositivo, clique em ATUALIZAR. Você não verá mudanças na página, mas o estado atual da lavadora será armazenado no banco de dados.

Agora, conecte o serviço em nuvem que você implantou ao Google Assistente usando o console do desenvolvedor do Google Home.

Configurar seu projeto do console para desenvolvedores

Na guia Develop, adicione um nome de exibição para a interação. Esse nome aparecerá no app Google Home.

Adicionar um nome de exibição

Em Marca do app, faça upload de um arquivo png para o ícone do app, com tamanho de 144 x 144 px e nome .png.

Adicionar um ícone de app

Para ativar a vinculação de contas, use estas configurações:

ID do cliente

ABC123

Chave secreta do cliente

DEF456

URL de autorização

https://us-central1-
.cloudfunctions.net/fakeauth

URL do token

https://us-central1-
.cloudfunctions.net/faketoken

Atualizar URLs de vinculação de conta

Em URL de fulfillment do Cloud, insira o URL da função do Cloud que oferece fulfillment para as intents de casa inteligente.

https://us-central1--cloudfunctions.net/smarthome

Adicionar o URL da função do Cloud

Clique em Salvar para salvar a configuração do projeto e, depois, em Próxima: Testar para ativar os testes no projeto.

Testar a integração entre nuvens

Agora, você pode começar a implementar os webhooks necessários para conectar o estado do dispositivo ao Google Assistente.

4. Criar uma lavadora

Agora que você configurou a integração, pode adicionar dispositivos e enviar dados. Seu serviço em nuvem precisa processar as seguintes intents:

  • A intent SYNC ocorre quando o Google Assistente quer saber a quais dispositivos o usuário está conectado. Ela é enviada ao seu serviço quando o usuário vincula uma conta. Você deve responder com um payload JSON de todos os dispositivos e recursos do usuário.
  • A intent QUERY ocorre quando o Google Assistente quer saber o estado atual ou o status de um dispositivo. Você deve responder com um payload JSON com o estado de cada dispositivo solicitado.
  • A intent EXECUTE ocorre quando o Google Assistente quer controlar um dispositivo em nome do usuário. Você deve responder com um payload JSON com o status de execução de cada dispositivo solicitado.
  • A intent DISCONNECT ocorre quando o usuário desvincula a conta do Google Assistente. Você deve parar de enviar eventos dos dispositivos deste usuário para o Google Assistente.

Você atualizará as funções implantadas para manipular as intents nas seções a seguir.

Atualizar resposta sincronizada

Abra functions/index.js, que contém o código para responder a solicitações do Google Assistente.

Você precisará processar uma intent SYNC. Para isso, retorne os metadados e os recursos do dispositivo. Atualize o JSON na matriz onSync para incluir as informações do dispositivo e os traços recomendados para uma lavadora de roupas.

index.js

app.onSync((body) => {
  return {
    requestId: body.requestId,
    payload: {
      agentUserId: USER_ID,
      devices: [{
        id: 'washer',
        type: 'action.devices.types.WASHER',
        traits: [
          'action.devices.traits.OnOff',
          'action.devices.traits.StartStop',
          'action.devices.traits.RunCycle',
        ],
        name: {
          defaultNames: ['My Washer'],
          name: 'Washer',
          nicknames: ['Washer'],
        },
        deviceInfo: {
          manufacturer: 'Acme Co',
          model: 'acme-washer',
          hwVersion: '1.0',
          swVersion: '1.0.1',
        },
        willReportState: true,
        attributes: {
          pausable: true,
        },
      }],
    },
  };
});

Implantar no Firebase

Implante o fulfillment da nuvem atualizado usando a CLI do Firebase:

firebase deploy --only functions

Para testar a integração entre nuvens, você precisa vincular seu projeto a uma Conta do Google. Isso permite realizar testes usando as plataformas do Google Assistente e o app Google Home, desde que estejam conectados à mesma conta.

  1. No smartphone, abra as configurações do Google Assistente. Você precisa fazer login na mesma conta que no console.
  2. Acesse Google Assistente > Configurações > Automação residencial (em Google Assistente).
  3. Clique no ícone de pesquisa no canto superior direito.
  4. Pesquise o app de teste usando o prefixo [test] para encontrar o app de teste específico.
  5. Selecione esse item. O Google Assistente será autenticado com seu serviço e enviará uma solicitação SYNC para que o serviço forneça uma lista de dispositivos para o usuário.

Abra o app Google Home e verifique se aparece o dispositivo da lavadora.

ae252220753726f6.png

5. Processar comandos e consultas

Agora que seu serviço de nuvem informa corretamente o dispositivo da lavadora ao Google, adicione o recurso para solicitar o estado do dispositivo e enviar comandos.

Processar intents de CONSULTA

Uma intent QUERY inclui um conjunto de dispositivos. Você deve responder com o estado atual de cada dispositivo.

Em functions/index.js, edite o gerenciador QUERY para processar a lista de dispositivos de destino na solicitação de intent.

index.js

app.onQuery(async (body) => {
  const {requestId} = body;
  const payload = {
    devices: {},
  };
  const queryPromises = [];
  const intent = body.inputs[0];
  for (const device of intent.payload.devices) {
    const deviceId = device.id;
    queryPromises.push(queryDevice(deviceId)
        .then((data) => {
        // Add response to device payload
          payload.devices[deviceId] = data;
        }
        ));
  }
  // Wait for all promises to resolve
  await Promise.all(queryPromises);
  return {
    requestId: requestId,
    payload: payload,
  };
});

Retorne o estado atual armazenado no Realtime Database de cada dispositivo da solicitação. Atualize as funções queryFirebase e queryDevice para retornar os dados de estado da lavadora.

index.js

const queryFirebase = async (deviceId) => {
  const snapshot = await firebaseRef.child(deviceId).once('value');
  const snapshotVal = snapshot.val();
  return {
    on: snapshotVal.OnOff.on,
    isPaused: snapshotVal.StartStop.isPaused,
    isRunning: snapshotVal.StartStop.isRunning,
  };
};

const queryDevice = async (deviceId) => {
  const data = await queryFirebase(deviceId);
  return {
    on: data.on,
    isPaused: data.isPaused,
    isRunning: data.isRunning,
    currentRunCycle: [{
      currentCycle: 'rinse',
      nextCycle: 'spin',
      lang: 'en',
    }],
    currentTotalRemainingTime: 1212,
    currentCycleRemainingTime: 301,
  };
};

Processar intents de EXECUÇÃO

A intent EXECUTE processa comandos para atualizar o estado do dispositivo. A resposta retorna o status de cada comando, como SUCCESS, ERROR ou PENDING, e o novo estado do dispositivo.

Em functions/index.js, edite o gerenciador EXECUTE para processar a lista de traços que precisam de atualizações e o conjunto de dispositivos de destino de cada comando:

index.js

app.onExecute(async (body) => {
  const {requestId} = body;
  // Execution results are grouped by status
  const result = {
    ids: [],
    status: 'SUCCESS',
    states: {
      online: true,
    },
  };

  const executePromises = [];
  const intent = body.inputs[0];
  for (const command of intent.payload.commands) {
    for (const device of command.devices) {
      for (const execution of command.execution) {
        executePromises.push(
            updateDevice(execution, device.id)
                .then((data) => {
                  result.ids.push(device.id);
                  Object.assign(result.states, data);
                })
                .catch(() => functions.logger.error('EXECUTE', device.id)));
      }
    }
  }

  await Promise.all(executePromises);
  return {
    requestId: requestId,
    payload: {
      commands: [result],
    },
  };
});

Atualize os valores no Realtime Database que correspondem ao traço solicitado de cada comando e dispositivo de destino. Modifique a função updateDevice para atualizar a referência apropriada do Firebase e retornar o estado atualizado do dispositivo.

index.js

const updateDevice = async (execution, deviceId) => {
  const {params, command} = execution;
  let state; let ref;
  switch (command) {
    case 'action.devices.commands.OnOff':
      state = {on: params.on};
      ref = firebaseRef.child(deviceId).child('OnOff');
      break;
    case 'action.devices.commands.StartStop':
      state = params.start
      ? {isRunning: true, isPaused: false}
      : {isRunning: false, isPaused: false};
      ref = firebaseRef.child(deviceId).child('StartStop');
      break;
    case 'action.devices.commands.PauseUnpause':
      const data = await queryDevice(deviceId);
      state = (data.isPaused === false && data.isRunning === false)
        ? {isRunning: false, isPaused: false}
        : {isRunning: !params.pause, isPaused: params.pause};
      ref = firebaseRef.child(deviceId).child('StartStop');
      break;
  }

  return ref.update(state)
      .then(() => state);
};

6. Testar a integração

Depois de implementar as três intents, é possível testar se a integração controla a lavadora.

Implantar no Firebase

Implante o fulfillment da nuvem atualizado usando a CLI do Firebase:

firebase deploy --only functions

Testar a lavadora

Agora você pode ver a mudança no valor ao tentar usar um dos seguintes comandos de voz no seu smartphone:

"Ok Google, ligar minha lavadora."

"Ok Google, pausar minha lavadora."

"Ok Google, parar minha lavadora."

Também é possível fazer perguntas para ver o estado atual da sua lavadora.

"Ok Google, minha lavadora está ligada?"

"Ok Google, minha lavadora está funcionando?"

"Ok Google, em que ciclo está a lavadora?"

É possível conferir essas consultas e comandos nos registros que aparecem na função na seção Funções do Console do Firebase. Saiba mais sobre os registros do Firebase em Gravar e conferir registros.

Também é possível encontrar essas consultas e comandos no console do Google Cloud, em Registros > Explorador de registros. Saiba mais sobre a geração de registros do Google Cloud em Acessar registros de eventos com o Cloud Logging.

7. Enviar atualizações ao Google

Você integrou totalmente seu serviço de nuvem com as intents de casa inteligente. Assim, os usuários podem controlar e conferir o estado atual dos dispositivos. No entanto, a implementação ainda não permite que o serviço envie informações do evento (como alterações de estado ou presença do dispositivo) para o Google Assistente por conta própria.

Com a sincronização de solicitações, você pode acionar uma nova solicitação de sincronização quando os usuários adicionarem ou removerem dispositivos ou quando os recursos do dispositivo mudarem. Com o Estado do relatório, o serviço de nuvem pode enviar o estado de um dispositivo para o Home Graph de forma proativa quando os usuários mudam fisicamente o estado de um dispositivo (por exemplo, ao acender uma luz) ou mudam o estado usando outro serviço.

Nesta seção, você vai adicionar código para chamar esses métodos do app da Web de front-end.

Ativar a API HomeGraph

A API HomeGraph permite armazenar e consultar dispositivos e os estados deles no Home Graph de um usuário. Para usar essa API, primeiro abra o console do Google Cloud e ative a API HomeGraph.

No console do Google Cloud, selecione o projeto que corresponde à sua integração <project-id>.. Em seguida, na tela da biblioteca da API HomeGraph, clique em Ativar.

ee198858a6eac112.png

Ativar estado do relatório

As gravações no Realtime Database acionam a função reportstate no projeto inicial. Atualize a função reportstate em functions/index.js para capturar os dados gravados no banco de dados e postá-los no Home Graph usando o estado do relatório.

index.js

exports.reportstate = functions.database.ref('{deviceId}').onWrite(
    async (change, context) => {
      functions.logger.info('Firebase write event triggered Report State');
      const snapshot = change.after.val();

      const requestBody = {
        requestId: 'ff36a3cc', /* Any unique ID */
        agentUserId: USER_ID,
        payload: {
          devices: {
            states: {
              /* Report the current state of our washer */
              [context.params.deviceId]: {
                on: snapshot.OnOff.on,
                isPaused: snapshot.StartStop.isPaused,
                isRunning: snapshot.StartStop.isRunning,
              },
            },
          },
        },
      };

      const res = await homegraph.devices.reportStateAndNotification({
        requestBody,
      });
      functions.logger.info('Report state response:', res.status, res.data);
    });

Ativar a sincronização de solicitações

A atualização do ícone na IU da Web do front-end aciona a função requestsync no projeto inicial. Implemente a função requestsync em functions/index.js para chamar a API HomeGraph.

index.js

exports.requestsync = functions.https.onRequest(async (request, response) => {
  response.set('Access-Control-Allow-Origin', '*');
  functions.logger.info(`Request SYNC for user ${USER_ID}`);
  try {
    const res = await homegraph.devices.requestSync({
      requestBody: {
        agentUserId: USER_ID,
      },
    });
    functions.logger.info('Request sync response:', res.status, res.data);
    response.json(res.data);
  } catch (err) {
    functions.logger.error(err);
    response.status(500).send(`Error requesting sync: ${err}`);
  }
});

Implantar no Firebase

Implante o código atualizado usando a CLI do Firebase:

firebase deploy --only functions

Como testar a implementação

Clique no botão Atualizar ae8d3b25777a5e30.png na interface da Web e verifique se aparece uma solicitação de sincronização no registro do Console do Firebase.

Em seguida, ajuste os atributos do dispositivo da lavadora na IU da Web do front-end e clique em Atualizar. Verifique se é possível ver a alteração de estado informada para o Google nos registros do console do Firebase.

8. Parabéns

674c4f4392e98c1.png

Parabéns! Você integrou o Google Assistente a um serviço de nuvem do dispositivo usando integrações entre nuvens.

Saiba mais

Veja algumas ideias que você pode implementar para ir mais a fundo:

Você também pode saber mais sobre como testar e enviar uma integração para análise, incluindo o processo de certificação para publicar a integração aos usuários.