Simulador de baixo consumo de energia dente de azul – Uma nova esperança no desenvolvimento da IOT

Vivemos no século XXI, onde os animais podem ainda não falar connosco, mas as coisas estão definitivamente a começar. Estamos andando por aí com nossos centros de controle de missão (também conhecidos como smartphones) que podem lidar com nosso dinheiro, nossa comunicação, nossas notícias, nossas casas inteligentes e qualquer outra solução inteligente que nos rodeia, em vidas curtas.

Treinamento para uma corrida? Você pode fazer isso com um monitor inteligente de oxigênio-músculo Humon. Quer melhorar a qualidade do seu sono? Adquira uma banda inteligente, um smartwatch ou um sleep-tracker (todos eles utilizam Bluetooth Low Energy e comunicam com o seu telefone). Está a tentar chegar a algum lado numa bicicleta? Use a navegação habilitada para BLE para minimizar distrações como notificações, mas ainda assim saiba como chegar onde você quer. Talvez você queira saber quando o café do escritório está pronto? Nós te protegemos!

Mas com o advento da Internet das Coisas e a crescente interconexão do mundo vieram novos desafios para nós, desenvolvedores.

Desenvolver um aplicativo habilitado para BLE

Se você já trabalhou em um aplicativo móvel que se conecta a um dispositivo Bluetooth de baixo consumo de energia, você sabe que não é a tarefa mais fácil. Há muitas telas e estados internos no aplicativo que estão conectados ao periférico e ao seu comportamento. O aplicativo é frequentemente criado junto com o firmware (às vezes até mesmo hardware (!!!)) do dispositivo, a conexão pode ser inconstante, a interface externa instável, buggy de lógica interna. Como resultado, a equipe está perdendo muito tempo no Conhecido Issues™ ao testar suas funcionalidades.

Em geral, há duas maneiras de abordar isso:

  1. Primeiro, há uma maneira fácil e demorada: use um smartphone físico e um dispositivo BLE, passe por todas as dificuldades para conectar e configurar o dispositivo para o estado em que precisamos que ele esteja e recriar as condições de teste.
  2. Então há o caminho mais difícil: abstrair o dispositivo, criar um mock-up simples que pode esconder o manuseio real do BLE. Isto vai permitir que você trabalhe em um emulador/iOS emulador Android, economizando algum tempo depois e permitindo que você execute testes automatizados nos seus CIs. Ao mesmo tempo, aumenta o custo de manutenção e introduz um novo risco ao não testar a comunicação real cada vez que você executar o seu código. Afinal, esse periférico Bluetooth está provavelmente no coração da nossa aplicação e não deve desconectar-se inesperadamente ou comportar-se de forma estranha.

Os nossos amigos do Frontside-Austin-based frontend software de engenharia de software e consultoria de arquitetura- reconheceram a necessidade de uma solução mais eficaz. Eles nos pediram para desenvolver uma solução de código aberto para um desenvolvimento de aplicativos BLE confiável que todos poderiam se beneficiar.

E assim vem…

BLEmulator /pronun.: bleh-mulator/, um simulador de baixo consumo de energia Bluetooth.

BLEmulator está aqui para facilitar a sua vida! Ele lida com todo o código relacionado ao BLE da sua produção e simula o comportamento de um periférico real e da pilha Bluetooth do sistema. É simples e flexível, permitindo-lhe criar tanto o mock básico como uma simulação completa de um dispositivo habilitado para BLE. E a melhor coisa – é de código aberto!

Permite testar o conceito por trás do seu dispositivo sem o custo da prototipagem do hardware. Ele permite que sua equipe móvel avance sem esperar por firmware ou protótipos, apenas com uma especificação. Ele permite que você trabalhe usando apenas o emulador Android ou simulador iOS, permitindo assim uma maior mobilidade, trabalho remoto mais fácil e evitando a disponibilidade limitada dos smartphones físicos. Ele permite que você teste seus aplicativos em testes automatizados executados pelo seu CI.

Currentemente BLEmulator está disponível para Flutter e funciona apenas com o nosso FlutterBleLib.

Como ele funciona

Flutter, como uma estrutura multiplataforma, precisa de dependências nativas para ambas as plataformas. Normalmente, essas seriam uma parte da própria biblioteca, mas aqui usamos uma abordagem diferente. Polidea tem uma biblioteca React Native BLE, chamada React native-ble-plx, um trabalho fantástico dos nossos colegas. Decidimos extrair toda a lógica nativa dela para uma biblioteca separada, conhecida como Multiplatform BLE Adapter. Desta forma, criámos um núcleo comum que é usado tanto pelo plugin react-native-ble-plx como pelo nosso plugin Flutter, FlutterBleLib. Como efeito secundário criámos uma abstracção comum usada na ponte nativa, a BleAdapter, que é um ponto de entrada perfeito para a simulação!

Este é o aspecto do fluxo de dados do FlutterBleLib:

  1. Chame um método num dos objectos do FlutterBleLib (BleManager, Peripheral, Service, Characteristic)
  2. Dart code envia um nome de método e seus parâmetros para uma ponte nativa
  3. Ponte nativa recebe os dados e os deserializa se necessário
  4. Ponte nativa chama um método apropriado na instância BleAdapter do Multiplatform BLE Adapter
  5. BleAdapter chama o método no RxAndroidBle ou no RxBluetoothKit, dependendo da plataforma
  6. RxAndroidBle/RxBluetoothKit chama um método de sistema
  7. System retorna uma resposta ao intermediário
  8. Intermediary retorna a resposta ao BleAdapter
  9. BleAdapter responde à ponte nativa
  10. Native bridge mapeia a resposta e envia-a para Dart
  11. Dart analisa a resposta e devolve-a ao chamador original

Tivemos o nosso trabalho delineado para nós – os pontos 4 a 9 são os pontos de entrada ideais com um contrato externo definido. Injetando uma implementação diferente do BleAdapter você pode ligar a simulação quando quisermos.

Próximo, tivemos que decidir onde a simulação deveria ser feita. Optamos por manter o máximo possível da simulação no Dart, por duas razões principais:

  1. Uma definição para ambas as plataformas

É importante minimizar tanto o número de lugares onde é possível cometer um erro como a quantidade de trabalho necessário para criar um periférico simulado e mantê-lo.

  1. Língua nativa da plataforma

Esta opção tem algumas vantagens. Primeiro, os desenvolvedores trabalharão de forma mais eficiente com uma ferramenta conhecida, portanto devemos evitar a introdução de idiomas adicionais. Segundo, não queríamos limitar as coisas que são possíveis de fazer no periférico simulado. Se você gostaria de solicitar uma resposta de alguns servidores HTTP (talvez com uma simulação mais avançada, rodando o próprio firmware?), você pode fazê-lo sem problemas com o mesmo código que você escreveria para qualquer outra comunicação HTTP em sua aplicação.

A rota de chamada simulada é parecida com esta:

  1. Chame um método num dos objectos do FlutterBleLib (BleManager, Peripheral, Service, Característica)
  2. Código de partida envia o nome do método e seus parâmetros para a ponte nativa
  3. Ponte nativa recebe os dados e os deserializa se necessário
  4. Ponte nativa chama o método apropriado no Adaptador Simulado

    Modificado parte começa agora

  5. BleAdapter – neste caso o do simulador – encaminha a chamada para a ponte nativa do BLEmulator
  6. BLEmulator ou faz a lógica em si (se não envolver o periférico) ou chama o método apropriado no periférico fornecido pelo usuário
  7. Resposta é passada para a ponte nativa do BLEmulator
  8. A ponte nativa doBLEmulator passa a resposta ao Adaptador Simulado
  9. A Adaptador Simulado responde à ponte nativa

    Back to original flow

  10. Ponte nativa mapeia a resposta e envia-a para Dart
  11. Dart analisa a resposta e devolve-a ao chamador original

Desta forma utiliza todo o seu código de manipulação BLE e trabalha nos tipos fornecidos pela FlutterBleLib, independentemente do backend que estiver a utilizar, seja a verdadeira pilha BT do sistema ou a simulação. Isto também significa que você pode testar a interação com um periférico em testes automáticos no seu CI!

Como usá-lo

Já cobrimos como ele funciona e que possibilidades ele oferece, então vamos agora pular para como usá-lo.

  1. Adicionar dependência para blemulator na sua pubspec.yml
  2. Criar seu próprio periférico simulado usando classes fornecidas por plugins SimulatedPeripheral, SimulatedService e SimulatedCharacteristic (Eu vou cobrir em detalhes na próxima seção)
  3. Adicionar o periférico ao BLEmulator usando Blemulator.addPeripheral(SimulatedPeripheral)
  4. Chamar Blemulator.simulate() antes de chamar BleManager.createClient() de FlutterBleLib

É isso, apenas quatro passos e você está pronto e funcionando! Bem, admito que o passo mais complexo foi meio que pulado, então vamos falar sobre o segundo ponto-definindo o periférico.

Contrato do periférico

Basearei os seguintes exemplos no CC2541 SensorTag da Texas Instruments, focando no sensor de temperatura IR.

Precisamos saber como são os UUIDs do serviço e suas características. Estamos interessados em dois lugares na documentação.

UIDs em que estamos interessados:

  • Serviço de temperatura do IR: F000AA00-0451-4000-B000-000000000000 Este serviço contém todas as características relativas ao sensor de temperatura.
  • Dados de temperatura do ar: F000AA01-0451-4000-B000-000000000000

    Os dados de temperatura que podem ser lidos ou monitorizados. O formato dos dados é ObjectLSB:ObjectMSB:AmbientLSB:AmbientMSB. Ele emitirá uma notificação a cada período configurável enquanto monitorado.

  • Configuração de temperatura do ar: F000AA02-0451-4000-B000-000000000000

    Interruptor de ligar/desligar para o sensor. Existem dois valores válidos para esta característica::

    • 00-sensor colocado a dormir (os dados de temperatura IR serão quatro bytes de zeros)
    • 01-sensor habilitado (os dados de temperatura IR emitirão leituras corretas)
  • Período de temperatura IR: F000AA03-0451-4000-B000-000000000000

    O intervalo entre notificações. O limite inferior é 300 ms, o limite superior é 1000 ms. O valor da característica é multiplicado por 10, portanto os valores suportados estão entre 30 e 100.

É tudo o que procurávamos, então vamos à implementação!

Periférico mais simples

A simulação mais simples aceitará qualquer valor e terá sucesso em todas as operações.

No Dart parece isto:

Curto e conciso, parece quase um JSON.

Aqui está o que acontece: criamos um periférico chamado SensorTag que tem um ID especificado em tempo de execução (qualquer string está bem, mas deve ser único entre os periféricos conhecidos pelo BLEmulator). Enquanto a varredura do periférico é ativada, ela irá anunciar usando informações de varredura padrão a cada 800 milissegundos. Ele contém um serviço, cujo UUID está contido nos dados do anúncio. O serviço contém 3 características, tal como num dispositivo real, e todas elas são legíveis. A primeira das características não pode ser escrita, mas suporta notificações; as outras duas não podem ser monitoradas, mas podem ser escritas. Não há valores inválidos para as características. O argumento convenienceName não é usado de forma alguma pelo BLEmulator, mas torna a definição mais fácil de ler.

IR Temperature Config e IR Temperature Period aceitam e definem qualquer valor passado para ele. A característica Dados de Temperatura IR suporta notificações, mas nunca envia nenhuma, pois não as definimos de nenhuma forma.

BLEmulator fornece o comportamento básico fora da caixa, cuidando do caminho feliz para todas as suas construções, minimizando a quantidade de trabalho necessário. Embora possa ser suficiente para alguns testes e verificações de aderência às especificações básicas, ele não tenta se comportar como um dispositivo real.

Precisamos implementar um comportamento personalizado!

Mantemos simplicidade e flexibilidade ao criar o BLEmulator. Queríamos dar aos desenvolvedores o controle necessário sobre todos os aspectos do periférico criado, enquanto exigíamos o mínimo de trabalho deles para criar uma definição de trabalho. Para atingir este objetivo, decidimos criar a implementação padrão de todos os métodos que podem ser o seu ponto de entrada para o comportamento personalizado e deixá-lo decidir o que sobrescrever.

Agora, vamos adicionar alguma lógica.

Fazer a conexão levar tempo

Um periférico real provavelmente levará algum tempo antes de se conectar. Para conseguir isso, você só precisa substituir um método:

É isso. Agora a conexão leva 200 milissegundos!

Negando conexão

Um caso semelhante. Queremos manter o atraso, mas devolver um erro que a conexão não pôde ser estabelecida. Você poderia anular o método e lançar um SimulatedBleError você mesmo, mas também pode fazer:

 @override Future<bool> onConnectRequest() async { await Future.delayed(Duration(milliseconds: 200)); return false; }

Desconexão inicializada pelo periférico

Vamos dizer que você quer verificar o processo de reconexão ou simular sair do alcance. Pode pedir a um colega para correr para o outro lado do escritório com um periférico real, ou adicionar algum botão de depuração e no seu onPress do:

>

yourPeripheralInstance.onDisconnect();

(Embora a primeira opção pareça ser mais satisfatória.)

Modificar a RSSI na informação de varredura

Muito bem, digamos que queremos ordenar os periféricos pela sua força de sinal percebida e precisamos de testá-la. Criamos alguns periféricos simulados, deixamos um com RSSI estático e depois no outro fazemos:

@overrideScanResult scanResult() { scanInfo.rssi = -20 - Random.nextInt(50); return super.scanResult();}

Desta forma, você pode ter um par de dispositivos com RSSI variável e testar a funcionalidade.

Negociando MTU

BLEmulator faz a maior parte da lógica por si só, limitando assim a MTU ao intervalo suportado de 23 a 512, mas se você precisar limitá-la ainda mais, você deve substituir o método requestMtu():

BLEmulator negociará automaticamente a MTU mais alta suportada no iOS.

Forçar valores para o intervalo suportado

Para limitar valores aceitos por uma característica, você tem que criar uma nova classe estendendo SimulatedCharacteristic.

A característica agora limita o input para 0 ou 1 no primeiro byte, ignora qualquer byte adicional e retorna um erro se o valor exceder o intervalo suportado. Para suportar o retorno de um erro, uma característica tem que retornar uma resposta à operação de escrita, portanto ajuste writableWithoutResponse para falso.

Ligar o sensor

Gostaríamos de saber quando o sensor de temperatura é ligado ou desligado.

Para conseguir isto, vamos criar um novo serviço com UUIDs hardcoded:

A característica que definimos não pode ser monitorizada através da FlutterBleLib, uma vez que falta isNotifiable: true, mas pode ser, para sua conveniência, monitorizada ao nível do BLEmulator. Isto facilita o controle do fluxo total que estamos simulando, simplifica a estrutura e nos permite evitar extensões desnecessárias das classes base.

Emissão de notificações

Ainda estamos faltando notificações de emissão da característica Dados de Temperatura IR. Vamos cuidar disso.

_emitTemperature() é chamado no construtor do Serviço de Temperatura e funciona em um loop infinito. Cada intervalo, especificado pelo valor da característica Período de Temperatura IR, verifica se há um ouvinte (isNotifying). Se houver um, então ele grava dados (zeros ou um valor aleatório, dependendo se o sensor está ligado ou desligado) para a característica Dados de Temperatura IR. SimulatedCharacteristic.write() notifica qualquer ouvinte ativo do novo valor.

Periférico avançado em ação

Você pode encontrar um exemplo completo de um periférico mais avançado no repositório do BLEmulator. Se você gostaria de tentar, basta clonar o repositório e executar seu exemplo.

Usar em testes automatizados

Um grande obrigado, aqui, ao meu colega Polidean Paweł Byszewski por sua pesquisa sobre o uso do BLEmulator em testes automatizados.

Flutter tem um contexto de execução diferente para a aplicação testada e o próprio teste, o que significa que você não pode simplesmente compartilhar um periférico simulado entre os dois e modificar o comportamento do teste. O que você pode fazer é adicionar um manipulador de dados ao driver de teste usando enableFlutterDriverExtension(handler: DataHandler), passar os periféricos simulados para main() da sua aplicação e passar mensagens de string para o manipulador dentro do contexto de execução da aplicação.

Resume-se a:Wrapper para o app

Seu periférico

Seu teste

Pode inicializar o periférico como quiser e chamar qualquer um dos comportamentos pré-definidos dentro do seu dispositivo simulado.

Veja por si mesmo

O melhor de tudo isto é que você não tem que acreditar em mim! Confira você mesmo no GitHub. Use-o, divirta-se com ele, tente quebrá-lo, e deixe-nos saber tudo sobre ele!

Gostaria de vê-lo em outras plataformas também? Alcance-nos e nós faremos isso acontecer! Não deixe de consultar as nossas outras bibliotecas, e não hesite em contactar-nos se quiser que trabalhemos no seu projecto.