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:
- 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.
- 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:
- Os usuários podem ler, alterar ou remover um dispositivo no qual estão listados como proprietário
- Os usuários não podem criar uma nova entrada de dispositivo (isso será tratado pelo provisionamento de dispositivo)
- 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.