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
- Como ele funciona
- Como usá-lo
- Contrato do periférico
- Periférico mais simples
- Fazer a conexão levar tempo
- Negando conexão
- Desconexão inicializada pelo periférico
- Modificar a RSSI na informação de varredura
- Negociando MTU
- Forçar valores para o intervalo suportado
- Ligar o sensor
- Emissão de notificações
- Periférico avançado em ação
- Usar em testes automatizados
- Veja por si mesmo
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:
- 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.
- 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:
- Chame um método num dos objectos do FlutterBleLib (BleManager, Peripheral, Service, Characteristic)
- Dart code envia um nome de método e seus parâmetros para uma ponte nativa
- Ponte nativa recebe os dados e os deserializa se necessário
- Ponte nativa chama um método apropriado na instância BleAdapter do Multiplatform BLE Adapter
- BleAdapter chama o método no RxAndroidBle ou no RxBluetoothKit, dependendo da plataforma
- RxAndroidBle/RxBluetoothKit chama um método de sistema
- System retorna uma resposta ao intermediário
- Intermediary retorna a resposta ao BleAdapter
- BleAdapter responde à ponte nativa
- Native bridge mapeia a resposta e envia-a para Dart
- 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:
- 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.
- 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:
- Chame um método num dos objectos do FlutterBleLib (BleManager, Peripheral, Service, Característica)
- Código de partida envia o nome do método e seus parâmetros para a ponte nativa
- Ponte nativa recebe os dados e os deserializa se necessário
-
Ponte nativa chama o método apropriado no Adaptador Simulado
Modificado parte começa agora
- BleAdapter – neste caso o do simulador – encaminha a chamada para a ponte nativa do BLEmulator
- 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
- Resposta é passada para a ponte nativa do BLEmulator
- A ponte nativa doBLEmulator passa a resposta ao Adaptador Simulado
-
A Adaptador Simulado responde à ponte nativa
Back to original flow
- Ponte nativa mapeia a resposta e envia-a para Dart
- 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.
- Adicionar dependência para
blemulator
na sua pubspec.yml - Criar seu próprio periférico simulado usando classes fornecidas por plugins
SimulatedPeripheral
,SimulatedService
eSimulatedCharacteristic
(Eu vou cobrir em detalhes na próxima seção) - Adicionar o periférico ao BLEmulator usando
Blemulator.addPeripheral(SimulatedPeripheral)
- Chamar
Blemulator.simulate()
antes de chamarBleManager.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.