Olá, neste artigo iremos explorar um projeto desenvolvido com o devkit Franzininho WiFi – LAB01¹ do Projeto Franzininho, que utiliza a Integração MQTT³ do projeto Home Assistant.
Franzininho é um projeto de hardware e software aberto que visa empoderar brasileiros a se tornarem protagonistas no desenvolvimento de projetos eletrônicos e de sistemas embarcados. O projeto Franzininho possui diferentes opções de hardware e placas de desenvolvimento, abrangendo diversos MCUs. No nosso caso, nós utilizaremos a placa Franzininho WiFi ⁴ baseada no SoC ESP32-S2⁵. Caso você queira conhecer mais sobre o projeto Franzininho, acesse sua página oficial² e documentação⁶. O foco do projeto é espalhar a cultura maker entre os brasileiros, então sua documentação encontra-se primeiramente em português. Caso queira contribuir com o mesmo, acesse sua página no github⁷.
Este projeto foi desenvolvido com o ESP-IDF⁸, que é o framework de desenvolvimento oficial para a família de MCUs ESP32. Para aprender como começar a utilizar o ESP-IDF com o Franzininho WiFi, veja este⁹ tutorial.
Este artigo assume que você possui uma instalação do Home Assistant configurada com um broker MQTT, portanto, irá focar somente na aplicação do MCU. Em um próximo artigo, descreverei como subir um ambiente Home Assistant configurado com um broker MQTT.
Apresentação do Hardware
A Franzininho WiFi LAB01 provê ao desenvolvedor diversos periféricos prontos para uso. Neste projeto, contudo, utilizaremos somente os push buttons BT1 – BT6, o LED vermelho e o display OLED SSD1306. Além disso, também utilizaremos a interface WiFi do ESP32-S2 para nos conectarmos ao broker MQTT.
Apresentação do Software
O código-fonte do projeto encontra-se disponível em https://github.com/vinRocha/homeassistant-control. Você pode obtê-lo rodando o seguinte comando:
git clone https://github.com/vinRocha/homeassistant-control -b article! (O comando acima irá clonar a revisão de código na qual este artigo foi baseado)
Dentro do diretório raiz do projeto, temos a seguinte estrutura de arquivos:
O projeto consiste em dois componentes, mqtt_manager e ha_switch; a aplicação principal, implementada em main/app_main.cpp; e um header file, main/images.h, contendo os bitmaps desenhados no display. Além disso, usamos 2 componentes externos definidos em main/idf_component.yml: protocol_examples_common, da Espressif, para gerenciar o stack de rede e a conexão WiFi, e o nopnop2002/ssd1306 ¹⁰ como driver do display OLED.
Se algum dia você precisar de um driver para trabalhar com o display SSD1306 no ESP-IDF, eu recomendo verificar o driver nopnop2002/ssd1306 ¹⁰. Ele é simples de se usar e ainda muito versátil, implementando diferentes funções para um fácil desenvolvimento de interface de usuário.
Para aqueles que não são familiarizados com ESP-IDF, componentes são apenas uma maneira de se compartilhar código entre diferentes projetos, assim como bibliotecas funcionam em projetos fora do ESP-IDF.
Agora vamos estudar o código-fonte em main/app_main/cpp.
app_main.cpp
O ESP-IDF é um framework escrito quase totalmente em C, e aplicações desenvolvidas com ele são normalmente escritas em C. Contudo, a toolchain do ESP-IDF também dá suporte à linguagem C++. Para tirarmos proveito dos novos recursos introduzidos com essa linguagem, nesse projeto utilizamos o C++, portanto, ele serve como referência de como utilizar C++ com o ESP-IDF.
Primeiramente, vamos revisar os #includes:
- <cstdint>: Este header é incluído para podermos utilizar tipos inteiros de tamanhos específicos, como uint8_t, int16_t e etc .
- “freertos/FreeRTOS.h” e “freertos/task.h”: Estes são headers padrões quando se trabalha com o FreeRTOS. O ESP-IDF utiliza o FreeRTOS como seu sistema operacional e, portanto, podemos utilizar os recursos disponíveis no FreeRTOS para desenvolvermos nossa aplicação. Caso você queira aprender sobre os recursos e APIs mais utilizados no FreeRTOS, veja o meu artigo anterior disponível aqui¹¹.
- “nvs_flash.h”, “esp_netif.h”, “esp_event.h” e protocol_examples_common.h”: Estes headers são utilizados para configurarmos a conexão WiFi.
- “soc/gpio_reg.h”, “driver/gpio.h” e “esp_intr_alloc.h”: Estes headers são utilizados para configuração do GPIO e para sub-rotina de interrupção.
- “mqtt_manager.h” e “ha_switch.h”: Estes são os headers dos componentes desenvolvidos neste projeto. Irei discuti-los com mais detalhes no meu próximo artigo.
- “esp_err.h” e “esp_log.h”: Estes são os header necessários para gerenciamento de erro e log.
- “ssd1306.h” e “images.h”: E estes são os headers para interagirmos com o display OLED.
Depois dos includes, temos a definição de duas constantes, c_led_gpio e c_buttons_gpios, que nos permitirão configurar e interagir com os botões e o LED vermelho de uma maneira simples. Na Franzininho WiFi LAB01, o LED vermelho encontra-se conectado com o GPIO14 e, portanto, definimos c_led_gpio = GPIO_NUM14. As {} presentes na definição encontrada no código são somente outra maneira de inicializar uma variável introduzida com o C++ 11.
Já para o c_buttons_gpios, descrevemos cada botão como um bitmask em uma variável do tipo uint64_t, no qual cada bit corresponde a um único pino de IO. Por exemplo, o bit0 corresponde ao GPIO0, o bit1 ao GPIO1 e assim por diante. Como os botões 1 – 6 encontram-se conectados nos GPIOs 7 – 2 respectivamente, nós iniciamos c_buttons_gpios com bitwise OR entre os respectivos bits.
A seguir, definimos uma estrutura chamada app_ctx que contém dados de contexto ligados à configuração da nossa aplicação. Note que, como estamos trabalhando com C++, não há necessidade de se utilizar o typedef para posteriormente se referir à estrutura por nome. É como se o C++ adicionasse o typedef implicitamente para nós. Na verdade, app_ctx é uma classe que contém somente dados como seus membros, portanto, se comporta exatamente como uma estrutura em C.
Por último, instanciamos duas variáveis globais e estáticas: s_TAG, para ser utilizada com a biblioteca de log, e s_app_cfg, que carrega a configuração/contexto da nossa aplicação.
Aqui temos as declarações das funções utilizadas por nossa aplicação. Estas funções estão definidas mais adiante no mesmo arquivo-fonte.
- s_BoardInit(): é responsável por inicializar o hardware na ordem correta.
- s_DrawBaseGui(): é responsável por setar a UI em um estado pré-definido.
- s_PlayAnimation(): é responsável pela animação de “Luzes Ligadas” e “Luzes Desligadas”.
- s_led_cb(): é a função de callback que registramos para sincronizar o estado do switch virtual com o LED vermelho na placa.
Agora vamos verificar a implementação em alto nível da nossa aplicação. Primeiro, criamos uma instância de SSD1306_t e inicializamos a estrutura s_app_cfg. Então, inicializamos o hardware utilizando a macro ESP_ERROR_CHECK() para verificar o valor de retorno de s_BoardInit(). Em caso de falha na inicialização do hardware, a aplicação irá reiniciar automaticamente e tentar a execução novamente.
Após concluída a inicialização do hardware, nós criamos 6 objetos da classe HaSwitch (Home Assistant Switch), e chamamos o método Connect() para cada um deles. Esses objetos são os nossos 6 switches virtuais conectados à nossa instalação do Home Assistant através do protocolo MQTT. Abaixo está o protótipo do construtor da classe HaSwitch, que pode ser encontrado em components/ha_switch/include/ha_switch.h:
HaSwitch(bool gui_switch = 0, user_cb user_callback = nullptr);Para os primeiros 5 switches, nós nos aproveitamos do recurso de argumentos padrão do C++ para passar implicitamente nullptr para o parâmetro user_callback do construtor. Para esses switches, nós passamos false para o parâmetro gui_switch do construtor. Com isso, o Home Assistant não irá criar um switch gráfico em sua interface de usuário. Por outro lado, para o último elemento, passamos true para o parâmetro gui_switch de modo que o Home Assistant irá disponibilizar um switch em sua interface gráfica para podermos interagir com o mesmo. O motivo para tal configuração ficará aparente na próxima sessão com a demonstração do projeto. Além disso, também passamos s_led_cb para o parâmetro user_callback no último switch, para podermos sincronizar o estado do switch virtual com o LED vermelho.
Por último, antes de entrar no loop principal da aplicação, nós resetamos o switch associado com o LED vermelho e inicializamos a UI no display:
A UI do aplicativo é bem simples, na verdade, é uma interface de texto na qual podemos mover uma seta para cima e para baixo para escolhermos um switch e alternar o seu estado, conforme ilustrado na imagem a seguir:

A lógica do loop principal da aplicação também é muito simples. Ela aguarda por uma notificação de tarefa que será enviada pela sub-rotina de interrupção assim que um botão for pressionado. A tarefa então verifica qual botão foi pressionado e executa a ação necessária. Por último, a tarefa volta a aguardar por uma nova notificação.
Eu vou deixar o resto do código-fonte em app_main.cpp para o leitor verificar por si só, porque eu quero discutir recursos do ESP32 utilizados neste projeto, como a configuração de GPIO e a sub-rotina de interrupção, em um próximo artigo. Contudo, o código presente no restante do arquivo é de fácil compreensão para um leitor familiarizado com a linguagem de programação C ou C++.
Um último comentário antes de verificarmos a compilação e demonstração do projeto. A função de entrada de uma aplicação desenvolvida com ESP-IDF é void app_main(void) em C. O código que faz a inicialização de baixo nível do hardware e do ambiente de execução do SW espera chamar app_main(void) em C, e se esta função não estiver presente no projeto, o linker irá falhar ao criar o binário final para gravação. Para utilizarmos a função app_main() definida em main/app_main.cpp (C++), precisamos informar o compilador que essa função será utilizada/chamada por um programa escrito em C. A especificação extern “C” realiza exatamente isso para nós.
Compilação e Demonstração
Nesta etapa, eu assumo que você tenha em mãos o hardware da Franzininho WiFi LAB01 e o framework do ESP-IDF configurado em seu ambiente de trabalho. Este exemplo deve funcionar com outras placas ESP32 contanto que você modifique adequadamente as configurações para outro hardware. Contudo, isto está fora do escopo deste artigo, mas não deixe de usar a área dos comentários abaixo, caso você tenha alguma dúvida ou precise de ajuda, para portar este projeto para outro dispositivo ESP32.
Caso você precise de ajuda para configurar o ambiente de trabalho com o ESP-IDF, siga o tutorial da referência [9]: Primeiros Passos com ESP-IDF.
Configurando, Compilando e Gravando o Projeto
Com o ambiente do ESP-IDF configurado e a Franzininho WiFi LAB01 em mãos, acesse o diretório raiz do projeto e execute o seguinte comando para realizarmos as configurações necessárias:
idf.py menuconfigA seguinte tela deverá surgir:
Navegue até a opção Example Connection Configuration e insira as credenciais da rede WiFi em WiFi SSID e WiFi Password.
Pressione ESC para acessar o menu anterior e navegue para Component config —> e depois MQTT Manager —> que será a última entrada da lista:
Em MQTT Manager entre com as credenciais do broker MQTT que encontra-se conectado à sua instalação do Home Assistant.
Pressione Q e Y para sair e salvar as configurações feitas, e rode o seguinte comando para compilar, gravar o projeto e monitorar os logs via USB CDC:
idf.py -p <COM_DEVICE> build flash monitor
Substitua <COM_DEVICE> no comando acima com a porta serial correspondente a do Franzininho WiFi conectado ao seu sistema e assista à mágica acontecer.
Configuração no Home Assistant e Demonstração
Após a aplicação subir na Franzininho WiFi LAB01, já devemos observar uma entrada do tipo Interruptor com nome Franzininho-WiFi s_6 na página Visão Geral do Home Assistant:
Este interruptor nos permite interagir com o LED vermelho da placa LAB01 utilizando a interface do Home Assistant.
Para os outros switches, s_1 – s_5, precisamos configurar quais dispositivos desejamos controlar com eles. Na minha instalação do Home Assistant, possuo 5 lâmpadas smart, então eu mapeei um switch virtual para cada lâmpada.
Essa configuração pode ser feita na página Automações e Cenas, acessada via Configurações -> Automações e Cenas:
No vídeo abaixo¹² eu demonstro como criar a automação para mapear um switch virtual deste projeto com algum dispositivo existente no Home Assistant, e aproveito para demonstrar o projeto em execução.
Conclusões
Neste artigo verificamos o funcionamento em alto nível de um projeto com ESP32-S2 que utiliza o protocolo MQTT para se integrar com uma instalação do Home Assistant. Este projeto serve como base para o desenvolvimento de sistemas de automação e controle avançados integrados ao Home Assistant. Nos próximos artigos irei explorar o código-fonte dos componentes mqtt_manager e ha_switch que possibilitam essa integração.
Caso você tenha dúvidas, questões, comentários ou sugestões, não deixe de usar a sessão dos comentários abaixo, e sinta-se a vontade para contribuir com este projeto via github.
Referências
- https://docs.franzininho.com.br/docs/franzininho-wifi-lab01
- https://franzininho.com.br/
- https://www.home-assistant.io/integrations/mqtt/
- https://docs.franzininho.com.br/docs/franzininho-wifi/franzininho-wifi
- https://www.espressif.com/en/products/socs/esp32-s2
- https://docs.franzininho.com.br/docs
- https://github.com/Franzininho
- https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/index.html
- https://docs.franzininho.com.br/docs/franzininho-wifi/exemplos-espidf/primeiros-passos
- https://github.com/nopnop2002/esp-idf-ssd1306.git
- https://embarcados.com.br/desenvolvendo-uma-aplicacao-iot-com-freertos-e-coremqtt-com-arduino-e-esp01/
- https://youtu.be/CLayiMC3tYo




