Este repositório contém os arquivos e especificações do projeto final da disciplina de Internet das Coisas (IOT). O projeto escolhido foi: "Desenvolvimento de aplicação utilizando Bluetooth". As seguintes funcionalidades devem ser implementadas:
- Conexão Wi-Fi:
- Atualização com biblioteca OTA (via Wi-Fi).
- Conexão bluetooth:
- A conexão Wi-Fi deve ser ativada pelo bluetooth (economia de energia);
- O usuário deve ser capaz de comandar o LED a ligar/desligar (via bluetooth);
- Dados de umidade devem ser enviados com periodicidade (via bluetooth).
- Ler dados de umidade do sensor.
O módulo a ser utilizado será o ESP32, pois possui capacidades de conexão via Wi-Fi e bluetooth sem que seja necessária a adição de outros componentes.
Segue o planejamento quanto à adiação de funcionalidades:
[X] Documentar a especificação da aplicação;
[X] Documentar o ambiente de desenvolvimento;
[X] Adicionar e testar as bibliotecas de conexão Wi-Fi e bluetooth;
[X] Documentar o uso das bibliotecas Wi-Fi e bluetooth;
[X] Enviar e receber mensagens (bytes) via bluetooth (visualização na saída serial);
[X] Adicionar capacidades de atualização via Wi-Fi;
[X] Documentar integração com sensor e atuador (LED);
[ ] Preparar um vídeo demonstrativo das funcionalidades;
[X] Adicionar e testar integração com sensor e atuador;
[x] Preprara slides de apresentação.
A aplicação consiste de um servidor BLE (Bluetooth Low Energy) sempre aguardando ("ouvindo") novas conexões até estabelecer uma. Após essa conexão ser estabelecidade pelo cliente, o servidor para de ouvir.
Enquanto o cliente estiver conectado, ele receberá notificações sobre a humidade atual coletada diretamente do sensor em intervalos de 2 segundos cada. Além disso, o cliente poderá paralelamente enviar os seguintes comandos ao módulo:
- A: liga o LED
- B: desliga o LED
- U: habilita o Wi-Fi e OTA (desenvolvimento)
- D: desabilita o Wi-Fi e OTA (desenvolvimento)
- R: reseta as configuração de Wi-Fi (desenvolvimento)
Estes comandos devem ser enviados pelo terminal no aplicativo BLE scanner instalado, por exemplo, o aplicativo de celular nRF Connect for Mobile [1]. O comando "U" é destinado a desenvolvedores que desejem atualizar o firmware do módulo via Wi-Fi utilizando WiFiManager e ArduinoOTA.
Para configurar o ambiente de desenvolvimento deste projeto, incialmente é necessário instalar o git [1]. Em sistemas operacionais Linux e Mac Os recomenda-se a instalação pelo package manager disponível. Já no sistema operacional Windows, recomenda-se a instalação do GitBash [2]. Em seguinda, clone o repositório:
git clone https://github.com/matheuskknd/bcc_internet_of_things
cd bcc_internet_of_things
Logo depois instale a IDE do Arduino [3], abra o programa e permita quaisquer regras através do firewall (evita problemas futuros). Então adicione um repositório de bibliotecas adicionais da seguite forma:
- Navegue até: Arquivo -> Preferências;
- No campo "URLs Adicionais para Gerenciadores de Placas" copie essa URL:
https://dl.espressif.com/dl/package_esp32_index.json
e clique em "OK";
Em seguida, baixe todas as dependências:
- Navegue até: Ferramentas -> Placa: "XXX" -> Gerenciador de Placas...;
- Pesquise por "esp32", selecione a última versão e clique em "Instalar";
- Volte a janela principal, e navegue até: Ferramentas -> Gerenciar Bibliotecas...;
- Pesquise "tzapu" -> Instale a biblioteca WiFiManager (by tablatronix);
- Pesquise "NimBLE" -> Instale a biblioteca NimBLE-Arduino (by h2zero);
- Pesquise "DHT sensor" -> Instale a biblioteca DHT sensor library (by Adafruit);
- Pesquise "Adafruit Unified Sensor" -> Instale a biblioteca Adafruit Unified Sensor (by Adafruit).
NOTA: para utilizar a funcionalidad do OTA pela IDE, é necessário ter o Python [4] instalado na máquina. Em sistemas operacionais Linux e Mac Os recomenda-se a instalação pelo package manager disponível. Já no sistema operacional Windows 10 ou superior, recomenda-se baixar a versão mais atulizada do Python 3 pela Microsoft Store.
Por fim, selecione o módulo ESP32 na IDE e comece a trabalhar:
- Navegue até: Ferramentas -> Placa: "XXX" -> ESP32 Arduino -> ESP32 Dev Module;
- Conecte seu ESP32 na porta USB da sua escolha (uma luz deve ficar acesa enquanto conectado);
- Navegue até: Ferramentas -> Portas e selecione seu dispositivo;
- Se for a primeira vez conectando o disposivo, pode ser que o Windows não consiga detectar os drivers. Neste caso, siga o passo a passo "Troubleshooting ESP32 drivers.";
- Caso seu dispositivo não apareça na IDE (mesmo que a luz esteja acesa), o cabo pode ser o problema. Recomenda-se testar com outro cabo. Alguns cabos não possuem fios dedicados a passagem de dados, apenas os de carga e, por isso, jamais vão funcionar para este fim.
- Feito isso, já deve ser possível compilar e carregar o programa como firmware no módulo.
Ademais, basta realizar as modificações pretendidas no código fonte e utilizar o GIT para controle de versionamento.
O Windows pode não detectar os drivers automaticamente caso seja a primeira vez que um dispositivo é conectado. Para permitir que o Windows consiga se comunicar com o módulo pode ser necessário instalar os drivers manualmente. Veja as instruções:
- Neste site:
https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers
navegue até a aba "Downloads"; - Baixe o arquivo "CP210x Universal Windows Driver" e descompacteo com "Extrair para";
- Entre na pasta recém descompactada, clique com o botão direito do mouse no arquivo "silabser.inf" e clique em "Instalar";
- Feito isso, deve bastar reconectar o dispositivo à porta USB para que ele apareça na IDE;
- Os arquivos baixados e extraídos podem ser apagados, pois não serão mais necessários.
Nota: este troubleshooting funciona quando USDB to UART (não é necessário USB to TTL standalone nesta placa) for o CP210x, quando for o CH340 olhar este site para obter informações: https://learn.sparkfun.com/tutorials/how-to-install-ch340-drivers/all
.
Neste capítulo são explicadas as dependências (includes) do projeto. Todas as depências são instaladas conforme explicado no capítulo anterior. A biblioteca ArduinoOTA.h
vem junto com o kit de desenvolvimento da placa. Já Arduino.h
e cstdint
são bibliotecas padrão que toda instalação do compilador possuem.
Essa biblioteca padrão contém declarações e algumas definições de funções, variáveis e constantes utilizadas para desenvolmento cotidiano em microcontroladores NodeMCU. Algumas destas são: o tipo String
; as funções pinMode
e digitalWrite
etc.
NOTAS: essa biblioteca sempre é incluída implicitamente quando o arquivo sendo compilado tem a extensão .ino
. Caso possua uma extensão diferente, é necessário a inclusão explícita.
Essa é uma biblioteca padrão do C++ que se origina da biblioteca stdint.h
da linguagem C. De acordo com a documentação [15]:
Este cabeçalho pertence originalmente à biblioteca padrão do C como
stdint.h
.
Este cabeçalho é parte da biblioteca de suporte à tipos, provendo tipos inteiros de tamanho fixo e partes da interface de limites numéricos da linguagem C.
Essa biblioteca provê uma implementação alternativa para as classes e funções do Bluetooth Low Energy (BLE) [8], que é, segundo os autores [9]:
Um fork da stack NimBLE refatorada para compilação na IDE Arduino [3]
...
Esta biblioteca reduz significativamente o uso de recursos computacionais e melhora a eficiência de aplicações BLE para ESP32 comparada à biblioteca base
bluedroid
. O intuito é manter, tanto quanto seja razoável, compatibilidade com a biblioteca original, porém refatorada, para uso com a stack NimBLE. Além disso, esta biblioteca deve ser mais ativamente desenvolvida e mantida para prover capacidades e estabilidade melhoradas em relação à original.
...
De fato, os autores estão corretos em afirmar que há redução significativa de uso de recursos computacionais quando se usa sua biblioteca em relação à original (que vem junto ao kit de desenvolvimento da placa). Por exemplo: é impossível compilar este projeto com a biblioteca original, pois o tamanho do arquivo gerado não cabe na memória flash da placa reservada para programas. No entanto, utilizando esta bilioteca, ainda sobra cerca de 20% do referido espaço.
Além da redução de uso de recursos, esta biblioteca possui algumas funções auxiliares que reduzem o tamanho do código do cliente como: NimBLEServer::advertiseOnDisconnect
etc. Por isso seu uso é sempre recomendado em relação à biblioteca original.
Esta biblioteca forcene classes e funções utilizadas para permitir ao código cliente configurar e consultar de maneira simples a leitura de dados de sensores DHT. Apenas instanciado-se um objeto DHT
passando o tipo de sensor (hardware) e o pino de sinal, e chamando duas outras funções já é possível ler, em tempo real, dados do sensor de humidade e temperatura DHT.
NOTAS: a biblioteca "Adafruit Unified Sensor" (Adafruit_Sensor.h
) é um dependência desta, por isso não há descrições sobre ela neste documento nem inclusões no código cliente.
Esta biblioteca forcene classes e funções utilizadas para permitir ao código cliente implementar de maneira simples a funcionalidade OTA (Over The Air). Esta, por sua vez, permite realizar autlizações de firmware através de rede. No caso do ESP32, através da conexão Wi-Fi.
Esta biblioteca forcene classes e funções utilizadas para permitir ao código cliente implementar de maneira simples conexões sucessivas e novas conexões Wi-Fi. Ao utilizá-la, é possível configurar os SSIDs e suas respectivas senhas em tempo de execução geralmente logo após o boot na primeira execução, excluindo a necessidade de ter SSID e senha hardcoded (gravados em tempo de compilação).
Este capítulo possui uma breve descrição sobre a implementação de cada classe. Inicialmente, supõe-se que o leitor já possua alguma afinidade com organização de projetos Arduino, C++ e POO.
Todas as classes possuem métodos setup
e loop
, que devem ser chamados quando é necessário realizar a inicialização do objeto (setup
) e posteriormente para sua operação ao longo do tempo (loop
). A implementação foi separada em classes e arquivos:
-
app.ino
:- Arquivo principal;
- Necessário para compilação na IDE Arduino;
- Contém as definições das funções globais
setup
eloop
; - Responsável por instanciar um objeto BluetoothController e chamar seus métodos
setup
eloop
; - Inicializa a saída serial.
-
BluetoothController:
- Classe principal;
- Implementa a conexão bluetooth;
- Contrói objetos das outras classes;
- Chama os métodos de inicialização e operação nos objetos de outras classes conforme aplicável.
-
LedController:
- Classe responsável por controlar o atuador (LED);
- Implementa a configuração e operação (escrita) do LED sobre o pino de sinal D5.
-
SensorController:
- Classe responsável por controlar o sensor (DHT11);
- Implementa a configuração e operação (leitura) do sensor (DHT11) sobre o pino de sinal D4.
-
UpdateController:
- Classe responsável por controlar a atualização via OTA e, consequentemente, a conexão Wi-Fi;
- Implementa configuração e operação de conexão Wi-Fi;
- Implementa configuração e operação de conexão OTA;
- Mantém a conexão Wi-Fi e OTA, quando aplicável.
NOTAS ESPECIAIS: a utilização das macros F
e PSTR
se deram necessárias para reduzir o espaço que o programa ocupa na memória RAM. Pois estas macros "movem" a string que recebem como parâmetro para a memória flash da placa. A única diferença entre elas é que a F
é uma composição da PSTR
seguida de um c-style cast como: (__FlashStringHelper*)
. Este tipo ajuda o compilador escolher a sobrecarga de função correta para processar tais strings, já que elas não podem ser lidas como strings comuns. Por exemplo, o seguinte código é necessário para trazê-las para a memória principal:
constexpr static auto FLASH_STR = PSTR("string armazena em flash")
...
char buffer[64];
strcpy_P((char *)&buffer, FLASH_STR);
funcao_que_espera_char_pointer((char *)&buffer);
Depende da biblioteca NimBLEDevice.h
.
Responsável por contruir objetos das outras classes.
Responsável por destruir explicitamente os objetos alocados dinamicamente (com operador new
).
Responsável por:
- Inicializar os objetos das classes SensorController e LedController, respectivamente;
- Inicializar o dispositivo BLE;
- Criar um servidor BLE;
- Criar um serviço BLE (com UUID: "8bfb95e0-412a-4a9a-985b-7e291c51f459" [13]);
- Cria a característica BLE "sensor de humidade" (UUID número: 10863 (Humidity) [12]), com capacidades de leitura e notificação;
- Adicionar um descritor BLE à característica "sensor de humidade" (UUID número: 10500 (Characteristic Presentation Format) [12]), descrevendo a variável de 4 bytes com formato ponto fluante IEEE-754 32-bit e percentual;
- Cria a característica BLE "linha de comando" (UUID número: 11046 (IDD Command Data) [12]), com capacidade de escrita apenas;
- Configurar os callbacks utilizados para processar os comandos escritos na característica "linha de comando";
- Inicializar o serviço BLE;
- Configurar e inicializa a propagação o serviço (advertisement).
Responsável por:
- Chamar o método
loop
nos objetos das classes UpdateController, SensorController e LedController, respectivamente; - Gerenciar o estado de conexão de possíveis clientes;
- Notificar o cliente, caso conectado, sobre alterações siginficativas na humidade percentual (ao menos 0.5% de diferença).
Depende da biblioteca Arduino.h
.
Responsável por configurar o pino de sinal do atuador (LED), neste caso: D5.
Não faz nada.
Responsável por:
- Configurar o pino de sinal D5 como saída;
- Configurar o estado do pino como
LOW
(desativado).
Não faz nada.
Responsável por configurar o estado do pino como LOW
(desativado) ou HIGH
(ativado) dependendo do parâmetro booleano.
Depende da biblioteca DHT.h
.
Responsável por contruir o objeto da classe DHT
passando o pino de sinal como D4 e o tipo do sensor com DHT11.
Responsável por destruir explicitamente os objetos alocados dinamicamente (com operador new
).
Responsável por inicializar a operação do objeto DHT
.
Responsável por:
- Tentar ler dados do sensor a cada 2 segundos;
- Caso leia os dados com sucesso, atualizar o estado interno.
Retorna o estado interno: última leitura com sucesso dos dados de humidade do sensor.
Depende das bibliotecas ArduinoOTA.h
e WiFiManager.h
.
Responsável por:
- Contruir os da classe
WiFiManager
eWiFiClient
; - Dar um nome único ao dispositivo para reconhecemo-lo na rede.
Responsável por destruir explicitamente os objetos alocados dinamicamente (com operador new
).
Responsável por:
- Configurar o hostname (nome do host na rede) com o ID único;
- Chamar a função
initWiFi
(da própria classe). - Chamar a função
initOTA
(da própria classe). - Configurar o estado interno como habilitado.
Responsável por:
- Não fazer nada caso o estado interno esteja em "dormindo" ou desabilitado;
- Se naõ, garantir que o Wi-Fi esteja conectado chamando a função
ensureWiFi
(da própria classe); - Caso o Wi-Fi esteja conectado, faz o keep-alive da conexão OTA.
Responsável por:
- Desabilitar a conexão OTA e desfazer toda sua configuração;
- Desconectar a conexão Wi-Fi;
- Configurar o estado interno como desabilitado.
Responsável por deletar todos os SSIDs e senhas de redes Wi-Fi conhecidas.
Retorna uma cópia do nome único do dispositivo.
Responsável por:
- Chamar o método
WiFiManager::autoConnect
, que conecta a uma rede já conhecida; ou - Faz um fallback em que o dispositivo se tona um AP (Access Point) com uma página Web de configuração para se conectar alguma rede Wi-Fi.
Responsável por:
- Configurar o hostname OTA como o nome único do dispositivo;
- Configurar a senha de conexão OTA (hardcoded);
- Configurar os callbacks chamados nos diversos eventos do OTA;
- Habilita a conexão OTA no dispositivo.
Responsável por:
- Verificar o estado da conexão Wi-Fi; e
- Caso esteja conectado, apenas retorna verdadeiro;
- Caso não esteja conectado, faz o seguinte:
- Caso o contador não esteja inicializado, inicializa-o em
MAX_ATTEMPT_CONNECTION + 1
; - Caso o contador já esteja inicializado e não seja
1
, apenas decrementa-o em 1; - Caso o contador esteja em 1, chama a função
initWiFi
(da própria classe) configura o contador em 0; - Configura o estado interno como "dormindo" por 3 segundos.
- Caso o contador não esteja inicializado, inicializa-o em
Este capítulo possui uma breve descrição sobre os componentes de hardware e suas configurações (integrações) para funcionarem com a aplicação desenvolvida.
Lâmpada LED de 5 volts simples que já contém um resistor de 10 k ohms embutido. Permanece acesa se o sinal do pino digital D5 estiver em HIGH
, e apagada caso esteja em LOW
.
NOTAS: por padrão o pino D5 emite um sinal HIGH
durante o boot. E volta para LOW
ao fim dele.
Sensor de humidade e temperatura da família de sensores DHT. O sensor utilizado não possui resistor embutido. O DHT11 possui as seguintes características:
DHT11 | |
---|---|
Alcance de temperatura | 0 a 50 ºC |
Alcance de humidade | 20 to 90% |
Resolução | Humidade: 1% Temperatura: 1ºC |
Voltagem de operação | 3 – 5.5 V DC |
Corrente (força) | 0.5 – 2.5 mA |
Tempo entre medições | 1 second |
Preço médio | R$5 a R$25 |
Operador através da biblioteca DHT.h
. Basta configurar o pino como D4 e seguir a esquemática apresentada a seguir.
Segue a representação esquemática da integração entre os componentes:
Diagramado no Fritzing [5].
O projeto foi feito a partir de exemplos com especificações encontradas no side da Arduino IDE (LED) [6] e Random Nerd Tutorials (DHT) [14]. A pinagem da placa sendo utilizada pode ser encontrada no repositório AchimPieters/esp32-homekit-camera no GitHub [7].
- https://github.com
- https://git-scm.com/download/win
- https://www.arduino.cc/en/software
- https://www.python.org/downloads/
- https://fritzing.org/
- https://www.arduino.cc/en/Tutorial/BuiltInExamples/Blink
- https://github.com/AchimPieters/esp32-homekit-camera/blob/master/Images/ESP32-30Pin-devboard.pdf
- https://randomnerdtutorials.com/esp32-bluetooth-low-energy-ble-arduino-ide
- https://github.com/h2zero/NimBLE-Arduino
- https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp
- https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html
- https://www.bluetooth.com/specifications/assigned-numbers
- https://www.uuidgenerator.net
- https://randomnerdtutorials.com/esp32-dht11-dht22-temperature-humidity-sensor-arduino-ide
- https://en.cppreference.com/w/cpp/header/cstdint