Qualcomm DragonBoard: Utilize o Bluetooth Low Energy nas suas aplicações!

Olá caro leitor, como vai? Ganhei há alguns meses um combo contendo uma Qualcomm DragonBoard, fornecido pela Arrow, contendo a poderosa (mas mal explorada) SoC SnapDragon 400. Uma das características chave (porém nem tão inovadora) dessa pequena placa de desenvolvimento é a presença de uma SoC Bluetooth low energy perfeita para fazer dela um cliente BLE e por consequência um gateway para... IoT! Nesse artigo pretendemos ir além da linha de comando e vamos realmente fazer uma aplicação em C para acessar os serviços e características de um periférico qualquer.

 

 

Revisitando a pilha Bluetooth Low Energy

 

Antes de qualquer coisa, temos que dar aquela revisada nos componentes principais da pilha bluetooth low energy, que chamarei de BLE daqui pra frente. De modo geral ela é bem parecida com o já conhecido bluetooth classic, a sua distinção ocorre nas camadas superiores, principalmente em uma que não existe no bluetooth classic, o GATT. A camada do GATT é responsável por descrever os diversos serviços e as características existentes dentro destes funcionando como endpoint para troca de dados. Abaixo vamos ver a arquitetura simplificada da pilha BLE:

 

DragonBoard: bluetooth low energy
Figura 1 - Arquitetura simplificada da pilha BLE

 

Além do GATT, a pilha BLE também possui distinções de como os dispositivos devem operar dentro de uma conexão. Esse "modus operandi" do dispositivo fica na camada GAP (Generic Access Profile), e basicamente define o dispositivo como sendo servidor, ou cliente do ponto de visa de conexão. Para simplificar as coisas, um dispositivo servidor é tipicamente um periférico, podendo ser um vestível ou um beacon. Na nossa aplicação, vamos utilizar um Bosch XDK para fazer esse trabalho.

 

Já o cliente, costuma ser o smarphone, tablet, ou notebook. Do ponto de vista de conexão o cliente procura por periféricos que estejam indicando presença (através do Advertisement), encontrando o desejado, ele requisita uma conexão, que uma vez estabelecida, permite que o cliente faça a descoberta dos serviços existentes e realize a troca de dados, e é onde colocaremos a DragonBoard, visto que ela possui características típicas de um cliente, gerencia e roteia os dados para um serviço em nuvem. Vejam abaixo uma linha do tempo simplificada de uma conexão BLE:

 

DragonBoard: bluetooth low energy
Figura 2 - Exemplo de conexão BLE

 

Agora que estamos com o modelo de comunicação e troca de dados do BLE, vamos revisitar o dispositivo que fará o papel do cliente, a DragonBoard.

 

 

DragonBoard: Revisitando suas capacidades para BLE

 

Falar de todas as características da DragonBoard foge ao escopo desse artigo, a placa, bem como o SoC possui seus predicados e seus pontos fracos (mais ligados a falta de suporte ou documentação obscura, o mesmo que uma Raspberry ou derivados sofrem), por isso vamos falar do que interessa para esse artigo, o Bluetooth.

 

Uma das coisas legais dessa placa reside no fato da solução inteira ser Qualcomm, ou seja se por um lado você leitor não vai ter nenhum suporte por parte deles, por outro o time de engenharia fez bem o dever de casa deixando o HCI, um dos layers mais baixos da pilha BLE, totalmente portado e funcional, seja rodando Android ou Linux embarcado. Só para refrescar a memória, vejam uma foto dessa placa:

 

DragonBoard: bluetooth low energy
Figura 3 - DragonBoard 410C

 

A PHY já vem com uma antena on-board, e aqui vale um elogio, nos meus testes obtive alcances que foram além dos 50 metros em ambiente com paredes, claro que parte desse desempenho se deve também ao conjunto de recepção do periférico. Do lado do software, tanto do ponto de vista do Android, como pro Linux embarcado, a principal forma de interagir com a PHY é utilizando o a boa e velha pilha bluetooth também desenvolvida pela Qualcomm, o BlueZ.

 

Mas você pode questionar: "Poxa, li até aqui pra ver mais outro tutorial de BlueZ, é sempre a mesma coisa, abre socket RFCOMM, e manda comando HCI". Foi o que eu pensei ao ganhar a placa,e depois de experiências horríveis com o Bluetooth nada documentado da Intel Edison, porém a pilha bluetooth que compõem a imagem da Linaro, sofreu grandes avanços e conseguiu fornecer as primitivas para acesso ao GATT através de um mecanismo de comunicação bem conhecido do Linux, o DBUS. 

 

Mas é fato que usar DBUS requer paciência, esse IPC não é dos mais amigáveis, ainda mais partindo do zero e será no próximo tópico onde vamos explicar como lidar com ele.

 

 

BLE: Preparando o ambiente na DragonBoard

 

Agora vamos preparar a DragonBoard para deixar o BLE acessível, falamos do DBUS alí em cima, porém podemos ficar aliviados, não precisaremos lidar com ele, ao menos não diretamente, para fazer as coisas funcionarem vamos precisar do seguinte:

 

  • DragonBoard 410C carregada com Linux embarcado;
  • BlueZ versão 4.1 ou superior;
  • Gattlib;
  • libbluetooth-dev;
  • libreadline-dev;
  • CMake;

 

A receita de bolo para ter tudo isso funcionando e falando harmoniosamente parece complicada, mas não assusta ninguém, a começar pela imagem Linux a ser carregada na DragonBoard 410C. Gerar essa imagem foge ao escopo desse artigo, clicando aqui você vai ter o melhor guia para te ajudar nessa tarefa, utilizamos para teste a última versão da imagem fornecida pela Linaro. Depois que a placa estiver com a imagem devidamente instalada, é hora de checar o BlueZ, de um modo geral ele vem instalado por default se não for o caso, o passo de instalação deverá ser feito. Para isso, abra um terminal, ou uma sessão por ssh e conecte-se na sua DragonBoard. Não sabe ainda como acessar ela pelo console? Então faça uma visitinha nesse link externo aqui

 

Ao estabelecer a conexão digite:

 

 

Você deve receber algo parecido com isso aqui:

 

 

Se o BlueZ não estiver instalado esse passo vai precisar ser realizado, como instalar o BlueZ foge ao escopo desse artigo não vamos entrar em detalhes de como instalar, se você precisar fazer esse passo, vá logo nesse guia aqui.

 

Checado os dois componentes que deveriam vir por padrão na DragonBoard, vamos resolver os pacotes necessários para instalação da gattlib, para isso instale os pacotes abaixo utilizando o gerenciador de pacotes do Linux, na DragonBoard:

 

 

Agora vamos copilar a gattlib dentro da DragonBoard, certique-se que o CMAKE esteja instalado, caso contrário, instale-o:

 

 

Para enviar os fontes da gattlib para sua máquina existem dois caminhos. Para não ser preciso instalar o git-core dentro da DB, eu clonei o repositorio na minha máquina host:

 

 

Descompactei em um local conhecido e enviei para DragonBoard utilizando a dobradinha SSH com SCP, se o leitor tiver dúvidas, acho que visitar esse local aqui, vai ajudar a entender o que esses comandos significam.

 

Com todos os arquivos na DragonBoard, podemos agora compilar a gattlib, para isso, volte ao console serial, e acesse o local onde você copiou os arquivos da gattlib, e rode o cmake para compilar a lib:

 

 

O build é rápido e vai gerar os binários (shared libraries) necessários para que você utilize a gattlib nas suas aplicações. Ao compilar as suas aplicações com a gattlib, você precisa adcionar a flag -L e passar o local onde está o binário da lib, juntamente com a flag -lgattlib para indicar a lib que deseja linkar ao código final. Adicionalmente você pode utilizar o utilário cpack e gerar um pacote RPM, para isso digite o comando abaixo de dentro do diretório build:

 

 

Em seguida instale o pacote, utilizando o gerenciador de pacotes do Linux:

 

 

De um modo geral, o pacote acaba ficando no mesmo diretório de build de onde o comando cpack foi invocado, fazendo esse passo, você não vai precisar das flags -L e -l, bastando apenas fazer isso aqui pra usar a gattlib (e portanto o BLE) do seu código em C:

 

 

E pronto! Sua DragonBoard está pronta para ter aplicações capazes de invocar o uso do layer GATT do BLE e trocar dados com seus periféricos, agora vamos usar nosso novo módulo!

 

 

BLE: Desenvolvendo com a gattlib na DragonBoard

 

Agora temos um método de acessar o BLE, ou seja, poderemos agora desenvolver nossa aplicação. Para aumentar a produtividade desse texto resolvi compartilhar com vocês o código fonte de um projeto que estava envolvido e que roda em uma DragonBoard, a primeira versão do gateway do BeeInformed um projeto envolvendo monitoramento de colmeias possuía um módulo chamado de gerenciador de sensores, esse módulo utilizava o próprio BLE e fazia algumas coisas interessantes:

 

  • Escanear os sensores próximos periodicamente;
  • Estabeler a conexão;
  • Fazer descoberta de serviços e características;
  • Trocar dados entre sensor e cliente (DragonBoard).

 

Esse pedaço da aplicação foi desenvolvida em C, o código pode ser obtido aqui, o procedimento para copiar o repositório e compilar na DragonBoard é exatamente igual o para compilar a gattlib, basta copiar os arquivos para a DragonBoard, e dentro do diretório digitar o comando make.

 

Embora seja um projeto que possa servir de ponto de partida, o objetivo do uso desse código é mostrar o passo-a-passo para escanear, conectar, descobrir e trocar dados por BLE, para isso vejam o arquivo principal do gerenciador de sensores abaixo, é um pouco extenso mas nos limitaremos apenas a pontos relevantes ao BLE:

 

 

Agora, qual é o processo para efetuar a conexão? Antes de tudo, temos que iniciar o adaptador bluetooth presente da DragonBoard, ele está enumerado como sendo o device hci0. Na linha 533, o usuário vai notar a chamada da função gattlib_adapter_open(), essa função toma dois argumentos, o nome do device ou seja hci0, e um ponteiro onde será depositado uma estrutura de informação dos devices, se a execução dessa função ocorrer com sucesso, a aplicação deterá o controle do bluetooth da DragonBoard, assim podemos passar para o proximo passo.

 

Como dissemos no começo desse artigo, o servidor do ponto de vista do BLE faz o que chamamos de advertisement, ou seja, periodicamente ele envia um pacote indicando presença. O papel do cliente é monitorar a chegada periodica desses pacotes, ou seja, devemos fazer um ciclo de scan, para isso na linha note a chamada gattlib_adapter_scan_enable(), essa função fala para o BLE iniciar o ciclo de escaneamento. O curioso é que diferente do RFCOMM, essa função somente irá considerar dispositivos BLE, essa chamada recebe três parametros, um deles é ponteiro com as informações do adaptador que você passou na chamada anterior, o segundo parametro é uma callback que será chamada toda vez que um dispositivo for encontrado, o terceiro parametro indica quanto tempo o ciclo de scan deve ocorrer.

 

Quando o uso do escaneamento não for mais necessário a aplicação poderá invocar o gattlib_adapter_scan_disable() e gattlib_adapter_close() nessa ordem liberando o uso do scanner para outras aplicações. Agora vamos para a linha 477, que basicamente contém a implementação da callback de dispositivo BLE encontrado, essa função recebe dois parâmetros, sendo o nome do dispositivo e seu MAC, o bom desses parâmetros é que o MAC recebido pode diferenciar um dispositivo do outro, fazendo com que o cliente consiga gerenciar múltiplos sensores. A implementação particular dessa callback, basicamente cria uma pthread (em caso de dúvidas sobre POSIX threads, sugiro a leitura desse excelente artigo aqui) passando como argumento uma estrutura de contexto, contendo nome e MAC.

 

Na linha 362 temos a função que implementa a pthread criada, o primeiro ponto é receber o contexto do device para qual essa thread foi criada, em seguida de posse dessas informações vamos para a linha 397, onde é invocada a função gattlib_connect(), que recebe dentre os parâmetros o MAC do dispositivo que desejamos conectar, o retorno dessa função é um handle, que é guardado em um local seguro, pois usaremos ele para trocar os dados com o dispositivo.

 

Nesse momento temos o dispositivo conectado, mas ainda não sabemos que tipos de serviços ele suporta ou se sabemos como escrever ou ler nas suas caracteristicas, o sensor em questão é um Bosch XDK, que possui um serviço BLE chamado de Exchange Data, com duas características, sendo uma para TX e outra para RX, em cada uma delas é possível escrever ou receber até 20bytes por transação, apesar desse código ter sido direcionado para esse sensor node (que caso o leitor se interesse, temos também mais um post sobre ele aqui), qualquer dispositivo BLE pode ser utilizado como periférico.

 

Se a requisição de conexão for um sucesso, vamos agora para o próximo passo de uma conexão BLE a descoberta dos serviços, para isso vamos para a linha 305, a chamada da função gattlib_discover_primary(), essa função irá devolver os serviços existentes e mais que isso, a quantidade de características disponíveis no dispositivo encontrado que são retornados nos dois argumentos que são passados para essa função.

 

Na linha 319, finalmente faremos a descoberta das características, ou seja, os endpoints onde são efetivamente enviados e recebidos dados por BLE. A função gattlib_discover_char() é responsável por isso, no retorno é importante o usuário verificar quais são os handles das características de TX e RX do serviço Data Exchange, essas constantes estão definidas pro XDK, mas para seu dispositivo e serviço podem ser diferentes, no final do artigo tem um link para o BLE SIG que lista os serviços e características padrão. De posse desses dois handles, resta apenas um passo a fazer.

 

Acertar a forma como o dispositivo responde a solicitação de dados por BLE, esse passo é opcional, pois a transferência de dados é assíncrona, ou seja, precisamos deixar a característica de RX do cliente aguardando por evento de transmissão do servidor, para isso habilitamos a opção de notificação utilizando a função gattlib_write_char_by_handle() da linha 344, embora exista mais uma chamada ela não entra no escopo desse artigo, essa função faz a primeira troca de dados efetivas e envia ao descritor de característica do serviço de Data Exchange uma requisição avisando que o cliente, vai atender os chamados de notificação da característica de RX.

 

Com esse último passo, encerramos o ciclo de conexão em um periférico, agora podemos registrar uma callback para receber dados do BLE pela característica de RX, na linha 348 que chama a função gattlib_register_notification(), na implementação da callback na linha 144, nenhum trabalho precisa ser feito, pois na sua chamada ela vem com o id da característica que notificou, o ponteiro para os dados e o número de bytes recebidos, na minha implementação como fraciono os pacotes de dados, utilizo uma POSIX queue para o processo de remontagem dos pacotes, mas em muitos casos é comum receber menos de 20 bytes em um pacote não sendo necessário qualquer tipo de estratégia de fatiamento de pacotes de dados.

 

E finalmente para enviar dados ao sensor, utilizamos a mesma função gattlib_write_char_by_handle(), vejam a linha 196, nesse caso utilizamos o handle da característica de TX, que obtivemos durante a descoberta de serviços do periférico, os parâmetros seguintes são triviais, basicamente o os dados e o número de bytes enviados nessa transação, no máximo 20, é possível trabalhar com payloads maiores, mas isso exige negociar o MTU para um valor maior durante o processo de conexão.

 

Conclusão

 

Caro leitor, nesse artigo nosso objetivo foi dar um subsídio além das ferramentas de linha de comando para que você consiga desenvolver sua aplicação com suporte BLE na Qualcomm DragonBoard, aproveitando a integração com o módulo Bluetooth existente nela. Optamos pelo caminho da aplicação em C pela facilidade de controlar seu cliente BLE, além da possibilidade de explorar recursos POSIX para efetuar sincronização e "bufferização" de dados, o que não impede o uso de outras linguagens, mas sim possibilita que o processamento dos dados dos sensores seja feito em mais baixo nível, e um Python possa fazer processamento dos dados numa camada superior da aplicação. Os repositórios aqui mencionados estão abertos a contribuições e são livres para uso, adicionalmente o repositório da própria gattlib possuem exemplos que podem servir de ponto de referência para as próximas aplicações envolvendo BLE. Se tiver dúvida, deixe seu comentário, que vamos te ajudar, até a próxima!

 

Referências

 

NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Felipe Neves
Engenheiro de sistemas embarcados apaixonado pelo que faz, já trabalhou em diversos setores de tecnologia nos últimos 14 anos com destaque para Defesa, Automação, Agricultura, Wearables, Robótica e mais recentemente Semicondutores. Possui sangue maker, tendo paixão por construir e compatilhar suas próprias coisas e explorar novos sabores dentro do mundo "embedded". Atualmente trabalha como Engenheiro de Software Senior na Espressif, sim aquela do ESP32 e do ESP8266. Tem interesse em tópicos que envolvam Robótica, Controle de Movimento, DSP e Sistemas de Tempo Real.

Deixe um comentário

avatar
 
  Notificações  
Notificar