ESP32

ESP32 com MQTT, servidor web e sistema de arquivos

Eis mais um artigo super elaborado! E trata-se de um projeto para um produto, onde deve-se rodar um ESP32 com MQTT, webserver e, nesse caso, fundamentalmente um sistema de arquivos. Explico.

Placa de desenvolvimento para ESP32

Antes de começar, gostaria de citar que essa placa de desenvolvimento é “slotável”, esse ESP32 está apenas encaixado sobre ela e assim desenvolvo projetos em paralelo, apenas trocando o processador.

Essa placa está disponível no parceiro CurtoCircuito (único lugar que encontrei para venda). Tem outros modelos também, é só dar uma chegada e escolher o seu.

Utilizando o sistema de arquivos SPIFFS

Já escrevi diversos artigos falando sobre o uso do SPIFFS, particionamento da memória flash e exemplos para ESP8266 e ESP32.

Nesse projeto, a ideia inicial era fazer a gravação dos parâmetros de configuração em uma memória EEPROM externa. Oras, tendo um sistema operacional com um sistema de arquivos rodando no ESP32, não dá pra ter dúvidas de qual utilizar, certo?

Para utilizar o sistema de arquivos SPIFFS é muito simples; basta declarar as funções para leitura, escrita, renomeação, adição e exclusão dos arquivos criados. Vou dispor o código para utilização do SPIFFS, primeiramente.

Já coloquei acima alguns outros recursos que são relacionados. Agora explico.

writeFile(fs::FS &fs, const char * path, const char * message)

O filesystem passado como primeiro parâmetro é o SPIFFS. O segundo parâmetro é o caminho absoluto do arquivo que será gravado. No caso, o arquivo de versão. Por último, passamos o conteúdo para o arquivo.

readFile(fs::FS &fs, const char * path)

Essa função recebe apenas os parâmetros filesystem caminho absoluto. Dentro da função está disposta a leitura. Em todas as funções estão alguns tratamentos de exceção, por exemplo, a verificação da existência do arquivo, o acesso ao arquivo etc. É fundamental tratar as exceções para evitar que aconteçam reinicializações. Quanto mais código, menos trabalho, acredite.

deleteFile(fs::FS &fs, const char * path)

Do mesmo modo, passa-se o filesystemcaminho absoluto. As exceções são tratadas e então o arquivo (se existir) é removido.

appendFile(fs::FS &fs, const char * path, const char * message)

Um arquivo pode ser incrementado, como é o caso dos logs. Resolvi criar um logger para ter parâmetros para uma análise em caso de problemas. Como um arquivo de log pode ser acessado inúmeras vezes, é fundamental que seja possível adicionar dados a ele, mas esse arquivo não deve crescer de forma indefinida, por isso limitei o tamanho a 2048 Bytes. Poderia renomeá-lo ao chegar no tamanho limite e criar então sua sequência, mas pense no que aconteceria se o produto estivesse no cliente e gerando logs continuamente. Em algum momento o sistema de arquivos seria preenchido e então a solução se transformaria em um bug.

check_if_ap_file_exists(char *path)

Essa função já faz uso dos recursos anteriores.

O ESP32 poderá em dado momento trabalhar em modo AP e o modo STA precisa ser desligado. Bem, quando se usa a função WiFi.disconnect(true)supostamente haveria de desconfigurar a rede. O problema é que se estiver iniciado o MQTT, não basta pará-lo para interromper o processo, pois tem também um socket alocado. Desconectar o socket mantém o objeto, por isso seria necessário abrir um novo para uma nova conexão. Para que fique disponível de forma global, esse socket é criado no começo do programa, então, como mudar do modo station para o modo access point? A melhor solução que encontrei foi criar um arquivo .ini e fazer um softrestart. O softrestart é feito com a chamada ESP.restart(). Para trocar entre STA e AP (lembrando que quando rodando em modo AP, não deve executar o MQTT), criei uma função para fazer a configuração.

O último caso é ter iniciada a configuração em modo STA mas estar com o MQTT parado. Nesse caso, não precisa reiniciar.

loadConfig()

Essa função tem uma sacada legal. Se temos uma struct, normalmente chamados item a item para alimentá-los com dados. Mas e se temos os valores ordenados? Chato fazer linha a linha, não? Ainda mais que os valores vem do arquivo de configuração, portanto, invés de alimentar chamando cada um dos ítens, preferi fazer um loop na struct. E como fazer um loop na struct? Simples!

Gosto bastante de ponteiros, mas não domino amplamente o recurso. Mas nesse caso eu já tinha estudado a respeito dessa forma de acesso ao endereço de memória e, depois de anos, essa é a primeira vez que achei aplicação em um caso real.

Esse comando acima é um ponteiro para o primeiro endereço do primeiro item da estrutura wifiConfig. Como defini o tamanho de todas as variáveis do mesmo tamanho, fica fácil caminhar pelo endereço de memória invés de acessá-los através da struct.

Logo após ter aberto o arquivo na função loadConfig, inicio um loop enquanto houver dados para leitura, lendo linha a linha, delimitado pelo terminador CR ou até o tamanho máximo de 50 Bytes. Depois, mudo para o próximo endereço de memória, que é o incremento do endereço atual acrescido de 50 Bytes. Como o tipo é char, a alocação é de 1 Byte por caractere.

Esses são os tratamentos essenciais relacionados à manipulação de arquivo. Se quiser uma leitura exclusiva sobre o SPIFFS, sugiro esse artigo, com uma explicação mais simples. Tenho também um artigo de acesso ao sistema de arquivos com Sming e com MicroPython, além de um artigo para reparar o sistema de arquivos.

Webserver com ESP32

Um webserver nada mais é do que um socket aberto enviando dados formatados. Usar o lwip para fazer uma comunicação socket é terrível, como você pode ver nesse artigo. Mas tem uma maneira fácil demais de implementar um socket server:

Esse socket receberá conexões externas. E para mandar o dado de volta? Lá no loop, cria-se um client para  fazer a transação:

E em seguida vem todo o tratamento que disporei no código completo, mais abaixo. Basicamente é isso, essas duas instâncias são as principais partes do código do servidor web!

Uma última chamada necessária é a server.begin(), que você verá onde ela está sendo chamada no código completo e entenderá o porquê.

MQTT com ESP32

Essa foi a pior parte. Experimentei diversas bibliotecas até chegar à conclusão que “tinha” que ser essa.

Vamos primeiro pensar um pouco a respeito; todos os exemplos das bibliotecas são exclusivamente da biblioteca, logo, dificilmente haverá por aí algum artigo que demonstre a utilização de múltiplos serviços. E acho que principalmente no caso de utilizar um servidor web com MQTT, porque ambos precisam um loop e não devem colidir; um não deve interromper o outro. Como resolver? – digo logo, é fácil com ESP32! Deixei o código HTML (que encontrei em um exemplo) rodando na função loop(), então criei uma task para o MQTT! Processamento assíncrono, um em cada núcleo.

Para iniciar o MQTT, deve-se estar em modo STA. A conexão, a carga das variáveis e a inicialização do MQTT acontecem em uma interrupção de um push button, monitorada por outra task, que faz polling em 4 pinos de IO. Quando o pino de IO relacionado ao MQTT é interrompido, uma task é iniciada, verifica se o MQTT já está rodando (para não chamar de novo), exclui a task que mantém a execução e a inicializa outra vez. Mas antes, faz a carga das variáveis de ambiente (do preLoad), e depois de iniciada a task principal do MQTT, se exclui. Vou mostrar a porção de código relacionada a essas duas tasks:

Mas ainda tenho que remover o IPAddres estático daí, esse foi pra teste. O restante fica igual. Em setup(), duas tasks apenas são iniciadas juntas ao sistema; a que faz a carga do arquivo e a que faz polling nos pinos de serviço:

Código completo

Algumas outras funções adicionais foram criadas para suprir a negociação entre as execuções. Também tem uma função de log, que criei para auxiliar na depuração, como citei anteriormente. Dela, ainda falta a função para leitura e entrará mais um pino de interrupção para sua leitura.

O código completo no estado atual é esse:

Espero que tenha gostado e, vou tentar gravar um video em um ou dois dias. Já tenho outro pendente aqui, aproveito pra subir ambos. Não deixe de se inscrever no nosso canal DobitAoByteBrasil no Youtube e clique no sininho pra receber notificações. Só subo vídeos que realmente considero importantes, você não será incomodado por pouco, ok?