Desenvolver aplicações conectadas em sistemas embarcados como o ESP32 frequentemente esbarra em um problema clássico: gerenciamento de memória RAM e fragmentação de heap.
Ao consumir APIs REST tradicionais no ecossistema Arduino/ESP-IDF, o fluxo padrão quase sempre envolve:
- Instanciar o
HTTPClientnativo; - Realizar a requisição e baixar o payload inteiro em uma
Stringdinâmica viahttp.getString(); - Alocar um documento na heap via
DynamicJsonDocument(ArduinoJson); - Desserializar a árvore JSON completa na memória;
- Extrair manualmente campo por campo;
- Torcer para o heap não fragmentar após centenas de requisições.
Em projetos maiores com Bluetooth, Wi-Fi, displays e múltiplos sensores concorrentes, essa abordagem facilmente causa crashes silenciosos ou reinicializações por falta de memória livre.
Pensando em resolver esse gargalo no meu próprio fluxo de trabalho, decidi construir o ESP32-HTTP-Client. O objetivo inicial era puramente técnico: eliminar intermediários de memória e simplificar a ergonomia do código em C++.
Hoje, a biblioteca já conta com usuários de vários lugares do mundo usando em projetos reais, e quero compartilhar aqui as decisões de arquitetura que tornaram isso possível e os aprendizados desse processo.
1. O gargalo do modelo tradicional (DOM vs Stream)
A maioria das soluções populares consome APIs via modelo baseado em DOM (Document Object Model): elas lêem a resposta completa da rede, guardam em buffer na memória e depois montam uma árvore de objetos em memória para você consultar.
Em um microcontrolador com poucas dezenas de kilobytes livres, isso é um desperdício crítico.
O que mudamos na arquitetura:
-
Parsing em Streaming direto do Socket: Em vez de armazenar o corpo da resposta em um buffer intermediário, o parser processa os bytes conforme eles chegam do socket de rede (com suporte nativo a
Transfer-Encoding: chunked). -
Direct Memory Binding (Injeção Direta): O valor do JSON é escrito diretamente no endereço de memória da variável C/C++ de destino (
int,float,char[], oustruct), sem alocar árvores nem nós temporários. - Conexão Persistente (Keep-Alive TLS): Reutiliza a sessão e o socket TLS aberto, evitando o custo computacional e de latência do handshake TLS a cada requisição.
O resultado no código final fica em uma única linha fluente:
#include <WiFi.h>
#include "ESP32HTTPClient.h"
ESP32HTTPClient client("https://jsonplaceholder.typicode.com");
void setup() {
Serial.begin(115200);
WiFi.begin("SSID", "SENHA");
while (WiFi.status() != WL_CONNECTED) delay(100);
int userId = 0;
// O valor vai direto para o endereço de userId sem buffer intermediário
client.get("/todos/1").getBody("userId", &userId);
Serial.printf("User ID obtido: %d\n", userId);
}
2. Benchmark de Performance e Uso de Heap
Para testar a hipótese na prática, executei um benchmark de 100 requisições consecutivas (GET /users do JSONPlaceholder) em um ESP32 padrão, comparando a abordagem padrão (HTTPClient + ArduinoJson) contra o ESP32-HTTP-Client:
| Métrica | Padrão (HTTPClient + ArduinoJson) | ESP32-HTTP-Client | Diferença |
|---|---|---|---|
| Heap alocada por req | ~58.2 KB | ~15 bytes | -99.9% de RAM temporária |
| Heap Livre Mínimo | 114.3 KB | 128.6 KB | Mais margem de segurança |
| Tempo Médio por Request | ~750 ms | ~59 ms | ~12x mais rápido (Keep-Alive) |
| Linhas de Boilerplate | ~15 linhas | 1 linha fluente | Alta manutenibilidade |
Além de evitar a fragmentação do heap, o tempo de resposta caiu drasticamente porque o ESP32 não precisa renegociar certificados criptográficos TLS em toda chamada.
3. Ergonomia da API: Fluent Interface em C++ Embarcado
Uma das prioridades era garantir que a sintaxe fosse natural para quem vem tanto do C++ quanto de linguagens modernas como TypeScript/Python:
Query e Path Parameters
client.get("/users/{id}")
.path("id", 42)
.query("filter", "active");
POST com múltiplos tipos de payload
int novoId = 0;
client.post("/dispositivos")
.body("nome", "Sensor-01")
.body("temperatura", 24.5)
.body("ativo", true)
.getBody("id", &novoId);
Navegação em objetos aninhados e arrays
char cidade[32];
// Navega no caminho "address.city" direto para o buffer
client.get("/users/1").getBody("address.city", cidade, sizeof(cidade));
4. A experiência com usuários reais pelo mundo
Quando publicamos um projeto open source, muitas vezes esperamos que ele resolva apenas o nosso problema isolado.
Porém, conforme a lib foi catalogada no PlatformIO Registry e no Arduino Library Manager, começaram a surgir issues, dúvidas e discussões no GitHub vindas de desenvolvedores de vários países (EUA, Belorrusia, Belgica, França, e claro o Brasil).
Alguns aprendizados marcantes dessa troca:
- Casos de borda em redes industriais: Desenvolvedores usando roteadores com MTU customizada ou servidores corporativos com headers HTTP fora do padrão convencional me forçaram a refinar o parser de stream para ser ainda mais resiliente a timeouts parciais.
- Ambientes com pouquíssima RAM: Projetos rodando ESP32-CAM ou stacks pesadas de BLE relataram que a lib foi o que permitiu manter a conectividade HTTP sem causar estouro de pilha (stack overflow) no FreeRTOS.
- Importância de documentação viva: Manter uma documentação clara em inglês e português com exemplos práticos reduziu o número de dúvidas básicas e aumentou o nível das contribuições técnicas.
5. Código e Documentação
O projeto é 100% open source (MIT) e está disponível nos gerenciadores de pacote:
- Repositório no GitHub: PedroFnseca/esp32-http-client
- Documentação Oficial: pedrofnseca.github.io/esp32-http-client
-
Instalação via PlatformIO:
lib_deps = PedroFnseca/ESP32-HTTP-Client@^1.4.0 -
Instalação via Arduino IDE: Buscar por
ESP32-HTTP-Clientno Library Manager.
Gostaria de abrir espaço para feedbacks de quem trabalha com sistemas embarcados e IoT aqui na comunidade: que estratégias vocês têm adotado para gerenciar payloads JSON e conexões seguras em microcontroladores com recursos escassos?
Top comments (0)