Android Things + Firebase + Google Home (parte 1)

Como criar uma dispensadora interativa de doces utilizando tecnologias Google. \o/

Quem participou do Capivara.codes ou esteve presente no PHP Experience pode conferir ao vivo o funcionamento dessa máquina. Ela foi utilizada em uma ativação da #firma (Udacity) e apresentada aos participantes do Capivara como um exemplo prático de uso do Firebase com Android Things, demonstrando como IoT (ou Internet das Coisas) pode ser aplicado em casos de uso concretos e com uma resposta em realtime.

A ideia consistia em uma ativação pelo usuário através de um website mobile. A partir daí o usuário deveria inserir um código (ou “token”) que mudava a cada nova ativação confirmando que ele estava presente (ou próximo) da máquina. Assim que informasse o código correto, a máquina liberava confete de chocolate. Os participantes que ativaram a máquina participariam de sorteios ao final da palestra.

Para implementar esse caso de uso foram utilizados os seguintes serviços, que vou detalhar na sequência:

  • Firebase Realtime Database: armazenando o token atual, dados dos usuários cadastrados, total geral e por usuário de ativações da máquina, intervalos para dispensar confete por usuário e tempo de ativação da máquina.
  • Firebase Hosting: utilizado para fornecer o front-end da interface do usuário (e autenticação do mesmo), além da interface do totem que informa o token atualizado. Tudo com SSL ativado.
  • Firebase Authentication: para autenticar usuários no sistema. Foi ativado o registro através de contas Google e o registro por e-mail e senha (que foi utilizado para o Android Things).
  • Firebase Cloud Functions: monitora o uso do token e gera um novo a cada ativação. Insere o registro dos usuários no Realtime Database a cada novo login. Remove o nó do usuário caso ele tenha sido removido do módulo de autenticação. Responde a chamadas do Google Assistant para realizar o sorteio.
  • Android Things: é o controlador da dispensadora que conecta ao Firebase Realtime Database e liberar o confete conforme os parâmetros definidos.
  • Google Assistant: utilizado para realizar o sorteio dos participantes através do Google Home ou do Assistant no Android.

Vou abordar a seguir cada item e detalhar algumas questões importantes sobre a implementação deles.


Firebase Realtime Database

Estrutura do Banco de Dados

A primeira coisa que veio em mente ao desenvolver esse caso de uso seria definir quais eram as peças que seriam integradas e como elas fariam a interação entre elas.

O Realtime Database foi o hub que concentrou toda a regra do “negócio” e fez a ponte entre o dispositivo fixo e as interfaces web. Comecei definindo alguns parâmetros: o “token” que seria utilizado para validar a ativação da máquina e que seria alterado a cada nova ativação; o tempo de liberação da dispensadora, ou seja, ao ser acionada, quanto tempo ela permaneceria liberando confete, sendo possível ajustar o volume de chocolate dispensado; o intervalo entre novas ativações do mesmo usuário; outra coisa que achei importante foi controlar quantas ativações foram realizadas, ao todo e por cada usuário. Além disso, registrar os dados dos usuários que fizeram o cadastro no sistema. Um nó de ativação seria utilizado pelo Android Things para identificar que ele deveria acionar a dispensadora.

Agora começam os problemas… O campo de token só pode ser visualizado pela interface do totem e novos registros de ativação deveriam conter o token atual e o identificador do usuário. O usuário logado poderia ver apenas o intervalo de ativação e seu registro específico no nó de usuários, mas não poderia editá-lo.

Essas colocações apontam a questão mais importante quando você trabalha com o Database do Firebase: SEGURANÇA! Eu já vi alguns projetos utilizando a segurança padrão, que significa que qualquer usuário poderia, com uma linha de código apagar todo o banco de dados. Por isso, vou abordar um tópico só para esta questão.

Segurança & Validação dos Dados

Essa é a coisa mais poderosa do Realtime Database, através da aba de regras (“rules”), você pode definir, nó a nó, quais são as permissões conforme parâmetros de autenticação e até mesmo registros armazenados na sua estrutura de dados e validar quais são as regras para incluir ou alterar registros.

NUNCA DEIXE A REGRA PADRÃO! Pense na sua estrutura de dados e defina as permissões de acordo com sua necessidade.

Uma observação importante: a permissão de criação de dados não se aplica ao Cloud Functions. Você vai acessar seu banco como um “admin” e poderá escrever/excluir qualquer nó como queira.

Para cada nó você pode definir até três padrões: “.read”, “.write” e “.validate”, qualquer elemento definido sem iniciar com “.” será um nó filho daquele nó. Se você quiser utilizar o nó como parâmetro (ou aceitar qualquer elemento para aquele nó), utilize “$nomeDoParâmetro”. Vou explicar os dois nós mais importantes e você poderá conferir os demais no github.

Regras de segurança do nó “activate”

No nó “activate”, utilizado pelo Android Things para ativar a dispensadora, foi definida a permissão de leitura e escrita, apenas para usuários autenticados que tinham um parâmetro “isAdmin” como “true”, ou seja, eu precisava definir este parâmetro para a conta que fosse utilizada para autenticar a dispensadora. Os demais usuários (sem esse parâmetro) não veriam nem acessariam esse nó.

O nó “$newActivation” é criado através do comando “firebase.database().ref(“/activate”).push()” realizado na interface web (do totem e do usuário). Deixei um botão de ativação manual como failsafe do sistema. Como o parâmetro começa com “$”, significa que posso utilizá-lo dentro da minha regra (ou que tanto faz o valor que estiver inserido alí). Lembre-se que se você utilizar o comando “push” será criado um nó pai com um valor aleatório de ID.

Observe que o “.read” está disponível apenas para usuários autenticados (“auth != null”) e somente para o mesmo usuário que está logado (“root.child(‘activate’).child($newActivation).child(‘uid’).val() === auth.uid”). Observe que o $newActivation é utilizado para chegar ao dado que está inserido no banco de dados.

Por que fiz dessa forma? Primeiro porque o dispositivo vai remover todo o nó “activate” assim que ativar a dispensadora. Então é possível criar uma tela de espera para o usuário que ativou a máquina (com uma mensagem de “aguarde…”) enquanto o nó que ele criou continuar existindo. Assim que o nó for removido, você pode mudar a interface dizendo que foi liberado.

Veja que qualquer usuário autenticado pode escrever neste nó, desde que seja um dado novo (“!data.exists() && auth != null”).

Agora entra a parte mais importante: não deixar o usuário inserir um dado novo se o token não corresponder com o token da base de dados e se o intervalo da última ativação for interior ao tempo padrão, também definido na raiz do nosso banco de dados.

O comando “.validate” é acionado quando algum elemento é inserido/atualizado na base. O campo “newData” é utilizado para validar o conteúdo antes da inclusão/alteração. Assim criamos as seguintes regras:

  • O usuário precisa estar autenticado;
  • O parâmetro “token” do dado a ser inserido corresponde ao parâmetro “token da raiz do banco;
  • O novo dado contém dois filhos: “uid” (id do usuário) e “token”; e por fim
  • O usuário precisa ser Admin (isAdmin=true) ou nunca ter ativado antes (lastActivation=null) ou o tempo entre a última ativação e a data/hora atual ser maior que o intervalo (campo “interval” da raiz do banco).

Essas regras me permitem gerar uma ativação manual se necessária, além de não deixar inserir/editar uma nova ativação (mesmo que seja admin), pois o dispositivo ainda não executou esse comando. A interface do usuário pode tratar o erro de permissão e exibir uma mensagem apropriada para cada situação, como por exemplo o caso de uma ativação estiver em andamento ou o token informado for inválido.

Se tiver alguma dúvida sobre como definir as permissões e como verificar os dados existentes no banco, consulte a documentação completa de permissões do Realtime Database.

Firebase Hosting

Eu poderia utilizar um domínio próprio, inclusive com SSL, mas acabei mantendo o domínio que criei para o projeto (https://estudenaudacity.firebaseapp.com/).

Para a hospedagem das interface, lembre-se que criei duas:

  • A interface do usuário: aonde ele faria a autenticação e informaria o código de liberação da máquina
  • A interface do totem: aonde já logado com uma conta marcada como “admin” exibiria o token atualizado e um botão de ativação manual.

Como as permissões do banco definem se você terá acesso direto ao token ou não, fica muito simples o controle de permissão na interface, porque simplesmente você não teria acesso ao token nem aos demais dados.

Interface do Usuário
Interface do Totem

Firebase Authentication

Não vou detalhar muito este tópico, uma vez que utilizei o exemplo disponível no site. Implementei o login do Google e a parte do retorno da autenticação para mudar a interface. Uma vez logado o webapp consultava os dados do nó do usuário logado, inclusive se o parâmetro “sorteado” fosse “true” para exibir a mensagem de sorteio.

Todas as funções criadas para o projeto

Firebase Cloud Functions

Essa foi a parte mais fantástica ao desenvolver essa aplicação com Firebase. Eu ou qualquer outro desenvolvedor que começou a utilizar a plataforma vai sempre se deparar com casos de uso aonde é necessário uma espécie de “backend” que tenha acesso a dados que não podem estar disponíveis na interface comum do usuário.

O primeiro caso que me deparei com essa aplicação foi o fato de necessitar dos dados de cadastro do usuário. Essa informação não fica disponível via API para que seu backend liste os registros de autenticação. Por isso seria interessante ter um “trigger” ou gatilho que criasse um nó do usuário com os dados que precisava consultar.

Ao trabalhar com o Cloud Functions, lembre-se que ele funciona baseado nesses gatilhos, que podem estar vinculados a qualquer um dos sub-produtos do Firebase: por enquanto Authentication, Realtime Database e Storage ou através de um acionamento via URL. Este último importante para implementar a funcionalidade do Assistant (ou qualquer outro webhook).

Eu criei os seguintes gatilhos (por ordem de execução):

  • “createUserStack” (quando um novo usuário entra na base de autenticação): cria um nó com os dados do usuário autenticado dentro do nó “users”. Você também poderia enviar um e-mail de boas-vindas aqui, por exemplo.
  • “generateNewToken” (quando o nó “activate” recebe um novo elemento): vai gerar um novo token aleatório e atualizar o nó “/token”.
  • “activateRemoved” (quando o AndroidThings remove o nó após ativar a dispensadora): vai transferir o elemento excluído para um outro nó chamado “released”. Isso é legal, pois permite você recuperar o dado, por exemplo.
  • “userRemoved” (quando remover algum usuário do Authentication): basicamente ele remove os dados do nó do usuário removido e mantém a base de dados consistente com a base de usuários autenticados.
  • “raffleUser” (quando uma URL/webhook for acionada pelo Google Assistant): vai tratar todas as transações do Assistant e gerar a conversação com ele, incluindo o sorteio do usuário.
  • “updateUsers” (quando uma URL for acionada): vai atualizar a lista de usuários armazenada numa variável do CloudFunctions.

O CloudFunctions funciona baseado em Node.JS, inclusive a parte de importação de módulos. Um caso de uso interessante é que você pode utilizar uma API de Computer Vision, por exemplo, para detectar conteúdo adulto e bloquear/modificar a imagem caso seja necessário esse controle no seu app.

Trigger de Realtime Database — Fonte: documentação Cloud Functions

Se você tem desenvolvido projetos em Firebase há mais tempo como eu, já se deparou com esse caso de uso: notificar um usuário que recebeu um novo seguidor. Não faz muito sentido passar a responsabilidade de gerar a notificação para o usuário seguidor, portando seria necessário criar um backend só pra isso. É possível implementar este caso com poucas linhas de código no Cloud Functions.

Trigger de Realtime Database — Fonte: documentação Cloud Functions

Um outro caso interessante é quando você precisa ajustar um conteúdo adicionado ao banco de dados. Como o exemplo acima que demonstra um ajuste do conteúdo inserido, adaptando o campo de mensagem.

Trigger de Storage — Fonte: documentação Cloud Functions

As vezes temos um caso mais complexo, como acontece em apps que fazem uso de imagens e é necessário criar thumbnails (para reduzir o tráfego de dados). O gatilho é acionado quando uma nova imagem é inserida, faz o download da mensagem em memória e gera a miniatura e rearmazena no Storage ao mesmo tempo que registra no banco de dados.

Trigger de Webhook — Fonte: documentação Cloud Functions

E por fim, quando você precisa disponibilizar uma URL (incluindo SSL) para algum serviço de terceiros para acionar seu sistema em tempo real. Chatbots e o próprio Google Assistant fazem uso desse gatilho.

O Google disponibilizou uma série de exemplos de uso do Cloud Functions, veja mais em https://github.com/firebase/functions-samples.


Em breve…

A segunda parte deste post, explicando a implementação do Android Things e a chamada do Google Assistant, através do Google Home.

[]’s

Show your support

Clapping shows how much you appreciated luis.leao’s story.