Criando um serviço Smart Home Cloud com o Google

Dave Smith em Google Developers Follow Mar 4 · 9 min ler

Recentemente, meu colega Dan Myers escreveu um ótimo artigo sobre o IoT e o Assistente do Google , apresentando os principais conceitos da API de ações do Smart Home. Essa API permite que os desenvolvedores relatem o estado do dispositivo ao gráfico inicial a partir de sua infraestrutura existente de serviços na nuvem e executem comandos enviados de dispositivos habilitados para o Assistente.

No artigo, Dan menciona que, para integrar seus dispositivos com o Google Assistente, você, como criador do dispositivo, desenvolve seu próprio serviço em nuvem, que inclui seu próprio painel, registro de dispositivo e gerenciamento de dispositivo que funciona independentemente do Assistente. "

Figura 1: Fluxo de Comunicação do Assistente

Neste post, gostaria de explorar como seria esse serviço de nuvem se você fosse um desenvolvedor que ainda não investiu tempo e recursos na criação de sua própria nuvem de dispositivos ou simplesmente não queira gerenciar a nuvem infra-estrutura você mesmo como sua frota de dispositivos dimensiona. Talvez você esteja apenas procurando conectar seus produtos existentes e se perguntando o que é necessário para criar um serviço de nuvem para a casa inteligente.

Vamos discutir um exemplo prático de uma nuvem doméstica inteligente usando componentes do Firebase e do Google Cloud Platform. Você verá como conectar e provisionar dispositivos com segurança, criar um modelo de dispositivo autenticado pelo usuário que permita o fácil desenvolvimento de aplicativos de usuário front-end para dispositivos móveis e a Web e configurar a estrutura necessária para integrar-se ao Google Assistente.

Você pode encontrar o código de exemplo descrito neste post no GitHub .

Dentro da caixa azul

Expandindo um pouco a aparência de uma nuvem de dispositivo doméstico inteligente, aqui estão alguns requisitos típicos que você pode ter:

  • Envie comandos para dispositivos da nuvem
  • Estado do relatório de dispositivos para a nuvem
  • Detectar quando os dispositivos estão off-line
  • Provisionar dispositivos para usuários individuais
  • Fornecer aos usuários acesso para gerenciar seus dispositivos

Sem mencionar que você quer que tudo isso aconteça de forma segura à medida que os dados trafegam entre os usuários, a nuvem e os dispositivos. Podemos satisfazer todos esses requisitos usando os serviços do Google Cloud Platform e do Firebase para criar uma solução escalonável sem servidor para dispositivos domésticos.

Figura 2: Arquitetura do Serviço Cloud

O Cloud IoT Core é um serviço totalmente gerenciado para conectar e gerenciar com segurança dispositivos IoT. Usando a ponte MQTT ou HTTP, os dispositivos de IoT podem se conectar ao Google Cloud usando autenticação de chave pública / privada por dispositivo e dados de troca. Os dados do dispositivo de entrada são publicados em um fluxo de eventos do Cloud Pub / Sub .

O Cloud Firestore é um banco de dados de nuvem NoSQL escalável e flexível para armazenar e sincronizar dados para o desenvolvimento do lado do cliente e do servidor. Ele mantém seus dados em sincronia entre aplicativos clientes por meio de ouvintes em tempo real e oferece suporte off-line para dispositivos móveis e Web por meio de seus SDKs nativos. O Firestore também é compatível com o Firebase Authentication para controlar o acesso aos dados por meio de regras de segurança incorporadas.

As Funções da nuvem permitem que o código de back-end seja executado em resposta a eventos acionados pelos recursos do Firebase e do Google Cloud. Podemos usar isso para organizar os dados do dispositivo entre o IoT Core e o Firestore. Usando essa arquitetura, o Cloud Firestore se torna o hub de dados central e a origem da verdade para o estado de todos os dispositivos conectados e expõe esse estado a aplicativos clientes autenticados.

Modelo do dispositivo

O modelo Firestore representa dados como pares de valor-chave armazenados em documentos , que são então organizados em coleções . Vamos representar cada dispositivo doméstico inteligente no Firestore usando dois documentos:

  1. Documento em uma coleção de dispositivos que contém os metadados do dispositivo, o status on-line e o estado mais recente relatado pelo dispositivo.
  2. Documento dentro de uma coleção de configurações de dispositivos com o estado solicitado mais recente do usuário (por exemplo, “defina o termostato para 75 graus”).
 dispositivos / luz-123abc 
nome: "Cozinha Light"
proprietário:
$ user-id
tipo: "light"
online: true
Estado: {
em: true
brilho: 100
}
device-configs / light-123abc
proprietário:
$ user-id
valor: {
em: true
brilho: 100
}
dispositivos / termostato-123abc
nome: "Termostato Corredor"
proprietário:
$ user-id
tipo: "termostato"
online: true
Estado: {
modo: calor
corrente: 70
setpoint: 72
}
device-configs / termostato-123abc
proprietário:
$ user-id
valor: {
em: true
setpoint: 72
}

Alterações no documento de configuração do dispositivo acionam uma função de nuvem para atualizar o dispositivo. Ao separar a configuração e o estado em dois documentos, garantimos que as atualizações bem-sucedidas recebidas do dispositivo não acionem a mesma lógica. Isso simplifica a lógica da função e cria uma boa separação dos fluxos de dados de saída e entrada.

Comunicação de dispositivos

Precisamos mapear mudanças no estado para mensagens que podemos trocar com o dispositivo. O Cloud IoT Core suporta os seguintes tipos de mensagens:

  • Configuração: Enviado da nuvem para o dispositivo, até uma mensagem por segundo. As mensagens de configuração são garantidas para serem entregues ao dispositivo.
  • Comandos: Envie até 100 mensagens por segundo da nuvem para o dispositivo. Os comandos só são entregues se o dispositivo estiver online.
  • Estado: enviado do dispositivo para a nuvem, até uma mensagem por segundo. As atualizações de estado são entregues aos assinantes do Pub / Sub ativos e as atualizações recentes são mantidas no Cloud IoT Core.
  • Telemetria: Envie até 100 mensagens por segundo do dispositivo para a nuvem. Os eventos de telemetria são entregues apenas para assinantes ativos do Pub / Sub.

Figura 3: Tipos de mensagens do IoT em nuvem

Queremos que os comandos do usuário acendam as luzes ou ajustem a temperatura para garantir a chegada ao dispositivo, mesmo que esse dispositivo esteja off-line. Com as mensagens de configuração, isso é feito para nós pelo IoT Core. O dispositivo receberá a configuração mais recente sempre que se conectar ao gateway, eliminando a necessidade da lógica da nossa aplicação lidar com isso.

Usando as funções da nuvem, podemos implantar o código que é acionado quando um documento de configuração do dispositivo é alterado no Firestore e publicá-lo no dispositivo correspondente no IoT Core. O caminho do documento usado como um acionador contém um curinga para o ID do dispositivo. Isso permite que a função seja acionada para cada dispositivo e captura o valor do ID do dispositivo no contexto da função.

 funções.firestore.document ( 'device-configs / {device}') .onWrite ( 
assíncrono (mudança, contexto) => {
const deviceId = context.params.device;
const config = change.after.data ();
... // Criar um novo cliente do Cloud IoT
cliente const = google.cloudiot ({
versão: 'v1',
auth: auth
});
// Atualizar configuração do IoT Core
pai / mãe
'projetos / meu-projeto / localizações / us-central1';
const devicePath = `$ {parent} / registries / my-registry`;
pedido const = {
nome: `$ {devicePath} / devices / $ {deviceId}`,
binaryData: Buffer.from (JSON.stringify (config))
.toString ("base64"),
};

aguardar client.projects.locations.registries.devices
.modifyCloudToDeviceConfig (solicitação);

});

Quando o dispositivo relata seu estado de volta ao IoT Core, a mensagem é publicada em um tópico do Cloud Pub / Sub que escolhemos. Podemos então usar outra função para capturar essas mensagens e gravar o estado atualizado no Firestore.

 functions.pubsub.topic ('device-events') .onPublish ( 
async (mensagem) => {
const deviceId = message.attributes.deviceId;
// Escreva o estado do dispositivo no Firestore
const deviceRef = firestore.doc (`devices / $ {deviceId}`);
experimentar {

aguardar deviceRef.update ({
'estado': message.json,
'online': true
});

console.log (estado `atualizado para $ {deviceId}`);
} pegar (erro) {
console.error (erro);
}
});

Nossos dispositivos poderiam publicar esses dados como atualizações de estado ou eventos de telemetria, e o código se comportaria da mesma maneira. No entanto, como o Firestore faz o trabalho de persistir o estado do dispositivo em nosso serviço de nuvem, não precisamos do IoT Core para fazer o mesmo. Portanto, faz mais sentido que o dispositivo envie dados usando eventos de telemetria.

NOTA: O projeto de dispositivo de amostra no GitHub implementa um dispositivo virtual usando o Node.js, que se conecta ao gateway MQTT, assina as alterações de configuração e publica as atualizações de volta como eventos de telemetria.

Construindo os Clientes

Como todos os dados do dispositivo residem no Firestore, a criação de aplicativos de usuário para a Web e dispositivos móveis é bastante simples, usando os SDKs nativos do Firebase. Para tornar as coisas ainda mais simples, a equipe do Firebase fornece bibliotecas adicionais que integram esses SDKs com estruturas populares.

Este exemplo usa Angular e AngularFire para a interface da web. A aplicação móvel foi construída em Flutter com FlutterFire , que tem a vantagem de ser multi-plataforma entre dispositivos iOS e Android.

Figura 4: Aplicativos do Cliente

As regras de segurança definem o controle de acesso para os dados armazenados no Firestore, garantindo que somente usuários autorizados tenham permissão para visualizar e gerenciar os dispositivos que possuem. A regra definida abaixo define os seguintes controles:

  1. Os usuários podem ler, alterar ou remover um dispositivo no qual estão listados como proprietário
  2. Os usuários não podem criar uma nova entrada de dispositivo (isso será tratado pelo provisionamento de dispositivo)
  3. Os usuários podem criar e modificar uma entrada de dispositivo pendente (parte do provisionamento de dispositivo)
 serviço cloud.firestore { 
match / databases / {database} / documents {
match / devices / {deviceid} {
permitir ler, atualizar, excluir:
if request.auth.uid == resource.data.owner;
}

match / device-configs / {deviceid} {
permitir ler, atualizar, excluir:
if request.auth.uid == resource.data.owner;
}

correspondência / pendente / {deviceid} {
permitir ler, escrever:
if request.auth.uid == resource.data.owner;
permitir criar:
if request.auth.uid! = null;
}
}
}

Como o Firestore representa a origem da verdade para os dados do dispositivo, os aplicativos clientes precisam apenas interagir com o SDK do Firebase para autenticar usuários e gerenciar dispositivos. Os usuários podem visualizar seus dispositivos listando os documentos na coleção de dispositivos em que sua conta corresponde ao campo do proprietário . O seguinte código Dart lista os documentos para os dispositivos do usuário atual usando o plugin FlutterFire.

 Firestore.instance.collection ('devices') 
.where ('owner', isEqualTo: user.uid)
.snapshots ()

Cada comando do usuário para alterar o estado do dispositivo atualiza o documento correspondente na coleção config do dispositivo . Isso aciona uma atualização da configuração IoT Core do dispositivo usando a função de nuvem descrita na seção anterior.

 Firestore.instance.collection ('device-configs') 
.document (device.id)
.updateData ({
'valor': ...
});

Registrar um novo dispositivo na conta do usuário requer algumas etapas adicionais para fins de segurança, portanto, vamos analisar com mais detalhes esse processo.

Provisionamento de dispositivos

O Cloud IoT Core usa pares de chaves pública / privada para autenticar dispositivos. Não discutiremos o processo em detalhes aqui, mas veja a segurança do dispositivo para mais informações sobre como ele funciona. Para dispositivos de consumo, dividiremos o processo de provisionamento em dois estágios: fornecimento de fábrica e provisionamento de usuário final.

Figura 5: Processo de provisionamento de dispositivos

A fábrica atribui uma chave privada ao dispositivo físico e registra a chave pública e o identificador de dispositivo correspondentes com o Cloud IoT Core. Ao fazer isso na fábrica, garantimos que apenas os dispositivos que fabricamos tenham credenciais para acessar a nuvem. O dispositivo é enviado ao usuário final com uma cópia da chave pública, que o aplicativo de usuário deve fornecer para registrar o dispositivo em sua conta.

Em nosso exemplo, a chave pública e os metadados do dispositivo estão vinculados a um código QR que o usuário pode verificar em seu dispositivo móvel durante o processo de registro do dispositivo. O código QR de exemplo contém as seguintes informações como um blob JSON:

 { 
"type": "light",
"serial_number": "abcdef123456",
"public_key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4InTLsvDq9Km ..."
}

Figura 6: exemplo de código de registro de dispositivo

O aplicativo de usuário grava esses dados no Firestore como um registro de dispositivo pendente, que aciona uma função de nuvem para verificar se a chave pública do dispositivo físico corresponde ao valor provisionado no Cloud IoT Core para o identificador de dispositivo correspondente e se o dispositivo ainda não está registrado para uma conta diferente.

 functions.firestore.document ('pendente / {device}'). onWrite ( 
assíncrono (mudança, contexto) => {
const deviceId = context.params.device;
const pendente = change.after.data ();

...
// Cria um novo cliente do Cloud IoT
cliente const = google.cloudiot ({
versão: 'v1',
auth: auth
});
pai constante = 'projetos / meu-projeto / locais / us-central1';
const devicePath = `$ {parent} / registries / my-registry`;
pedido const = {
nome: `$ {devicePath} / devices / $ {deviceId}`
}
experimentar {
// Verificar se o dispositivo ainda não existe no Firestore
const deviceRef = firestore.doc (`devices / $ {deviceId}`);
const deviceDoc = aguardar deviceRef.get ();
if (deviceDoc.exists)
throw `$ {deviceId} já está registrado para outro usuário`;
// Verificar se o dispositivo existe no IoT Core
// Lança um erro se o ID do dispositivo não existir
const result = espera client.projects.locations.registries
.devices.get (request);
// Verifique se a chave pública do dispositivo corresponde
const deviceKey = result.credentials [0] .publicKey.key
.aparar();
const pendingKey = pendente.public_key;
if (deviceKey! == pendingKey) throw 'Incompatibilidade de chave pública';
... } catch (error) {
console.error (erro);
}
});

Depois que os dados do dispositivo forem verificados, o servidor poderá criar a nova entrada de dispositivo para o usuário e remover a entrada pendente do Firestore.

Qual é o próximo?

Nesta postagem, exploramos o que é preciso para começar a criar um serviço de nuvem para dispositivos domésticos inteligentes usando o Google Cloud Platform e o Firebase e como esses serviços facilitam o desenvolvimento de aplicativos front-end, mantendo tudo seguro de ponta a ponta .

No próximo post desta série, você verá como o trabalho básico que estabelecemos nos permite adicionar rapidamente a vinculação de contas e o preenchimento de intenção necessários para conectar sua nova nuvem de dispositivos com o Smart Home Actions e o Assistente do Google.