Módulos Wifi

Biblioteca WiFiManager: Recursos Avançados

Dailton.Menezes 3 de abril de 2026

Introdução

Configurar o WiFi de um ESP32 diretamente no código funciona bem durante o desenvolvimento, mas não é prático quando o dispositivo precisa ser instalado em diferentes ambientes ou entregue a usuários finais. Para resolver isso, o WiFiManager cria automaticamente um ponto de acesso temporário e exibe um portal de configuração acessível pelo navegador, permitindo que o usuário selecione a rede WiFi e informe a senha sem necessidade de cabo USB ou recompilação do firmware.

Neste artigo, vamos explorar como transformar esse portal padrão em uma interface mais completa e amigável, incorporando:

  • parâmetros personalizados
  • uma página de informações
  • temas visuais (Dark, Light e Default)
  • um fluxo simplificado com tudo em uma única página

O objetivo é criar um portal de configuração mais profissional, adequado para aplicações reais com ESP32.


Motivação

O portal padrão do WiFiManager cumpre bem o papel de configurar SSID e senha, mas foi projetado com foco técnico. Em aplicações reais, especialmente quando o ESP32 faz parte de um produto, é desejável oferecer uma experiência mais clara e intuitiva para o usuário final.

Queríamos ir além da configuração básica de WiFi e criar um portal que:

  • apresentasse todos os parâmetros relevantes em um único lugar
  • reduzisse a navegação entre telas
  • evitasse múltiplos botões de SAVE
  • permitisse personalização visual
  • incluísse uma página de informações do dispositivo
  • tivesse aparência mais moderna e coerente com o produto

Em outras palavras, buscamos transformar o portal técnico padrão em uma interface mais amigável, organizada e visualmente consistente — algo que qualquer usuário pudesse compreender e utilizar sem instruções adicionais.

Essa necessidade de clareza, simplicidade e personalização motivou a criação de um portal customizado, com temas visuais e um fluxo mais direto, adequado tanto para projetos pessoais quanto para produtos comerciais baseados em ESP32.


Materiais Necessários

  1. Módulo WiFi ESP32 Bluetooth 30 pinos

A rigor, qualquer modelo de ESP32 atende ao projeto. A única atenção seria com relação ao pino do botão LED_BUILTIN que pode mudar por modelo.

 


Entendendo o WiFiManager no ESP32

O WiFiManager simplifica a configuração de redes WiFi no ESP32 criando automaticamente um ponto de acesso temporário e exibindo um portal de configuração acessível pelo navegador. O fluxo padrão é simples e funciona sem qualquer customização adicional.

Abaixo está um exemplo mínimo de uso:

#include <WiFiManager.h>

void setup() 
{
  Serial.begin(115200);

  WiFiManager wm;

  // Inicia o portal caso não consiga conectar ao último WiFi conhecido
  bool ok = wm.autoConnect("Portal-ESP32", "12345678");

  if (!ok) 
  {
    Serial.println("Falha ao conectar. Reiniciando...");
    delay(1000);
    ESP.restart();
  }

  Serial.println("Conectado ao WiFi!");
}

void loop() 
{
  // Código principal da aplicação
}

Como esse código funciona?

  • O ESP32 tenta conectar à última rede salva.
  • Se não conseguir, cria um AP chamado Portal-ESP32 com a senha 12345678.
  • O usuário conecta ao AP e acessa o portal de configuração.
  • Após salvar SSID e senha, o ESP32 tenta conectar novamente.
  • Se tudo der certo, o código segue normalmente.
  • Se falhar, o dispositivo reinicia para tentar novamente.

Esse é o fluxo padrão do WiFiManager — simples, direto e suficiente para muitos projetos.
A partir daqui, começamos a evoluir para um portal mais completo, com parâmetros adicionais, temas visuais e uma experiência mais amigável para o usuário final.

Figura 1 – Reconhecendo o SSID do WifiManager na Rede Wifi

Figura 2 – Confirmação antes da Conexão

Figura 3 – Conectado no SSID do WifiManager


Por que Customizar o Portal?

O WiFiManager já oferece um portal funcional para configurar SSID e senha, mas seu layout padrão foi pensado para uso técnico. Em muitos projetos com ESP32 — especialmente quando o dispositivo faz parte de um produto — é importante oferecer uma experiência mais simples e intuitiva para o usuário final.

Ao analisar o portal padrão, percebemos algumas oportunidades de melhoria:

  • Múltiplas telas e navegação desnecessária → O menu padrão inclui várias páginas separadas (WiFi, Parâmetros, Info, Reset, etc.). Para o usuário comum, isso pode gerar dúvidas sobre onde salvar cada informação.
  • Vários botões de SAVE → Dependendo da estrutura do menu, o usuário pode salvar parte das informações e esquecer de salvar o restante.
  • Visual técnico e pouco amigável → O layout padrão é simples, mas não transmite a sensação de um produto final.
  • Ausência de informações adicionais → Em muitos casos, é útil exibir dados como IP, RSSI, versão do firmware ou nome do dispositivo.
  • Impossibilidade de personalizar a identidade visual → Projetos comerciais geralmente exigem cores, estilos e organização próprios.

Com isso em mente, buscamos criar um portal que:

  • apresentasse tudo em uma única página, evitando navegação desnecessária
  • tivesse um único botão SAVE, reduzindo erros do usuário
  • permitisse parâmetros personalizados além de SSID e senha
  • incluísse uma página de informações clara e organizada
  • oferecesse temas visuais (Default, Dark e Light)
  • tivesse aparência mais moderna e coerente com o produto

O objetivo não era substituir o WiFiManager, mas aproveitar sua base sólida e construir uma interface mais amigável, organizada e visualmente consistente — algo adequado tanto para projetos pessoais quanto para aplicações comerciais.


Estrutura Geral da Solução

Antes de mergulharmos nas customizações, é importante entender como as peças principais do projeto se encaixam. A solução final combina dois componentes fundamentais:

  • WiFiManager → responsável pela configuração inicial do WiFi
  • AsyncWebServer → responsável pelo servidor web principal após a configuração

O WiFiManager entra em ação apenas quando o dispositivo ainda não conhece a rede WiFi ou quando o usuário decide reconfigurá-la. Depois que as credenciais são salvas, o ESP32 passa a operar normalmente, e quem assume o controle é o AsyncWebServer.

A arquitetura geral pode ser resumida assim:

  1. Inicialização do ESP32 → O dispositivo tenta conectar à última rede conhecida.
  2. Portal de Configuração (WiFiManager) → Se não houver rede salva ou se a conexão falhar, o ESP32 cria um ponto de acesso temporário e exibe o portal de configuração.
  3. Portal Customizado → O usuário acessa uma única página contendo:
    • lista de redes disponíveis
    • SSID e senha
    • parâmetros personalizados
    • seleção de tema
    • botão único de SAVE
  4. Reboot Automático → Após salvar as configurações, o dispositivo reinicia para liberar recursos internos e iniciar o servidor principal.
  5. Operação Normal (AsyncWebServer) → Com o WiFi configurado, o ESP32 inicia o servidor web da aplicação, exibe informações do dispositivo e executa suas funções normais.

Essa separação clara entre configuração e operação traz várias vantagens:

  • evita conflitos entre servidores web
  • simplifica o fluxo para o usuário
  • permite personalizar o portal sem afetar a aplicação principal
  • garante estabilidade após o SAVE
  • facilita a manutenção e evolução do código

Ao longo das próximas seções, vamos detalhar cada parte dessa arquitetura, mostrando como o portal foi simplificado, como os parâmetros foram adicionados, como os temas foram aplicados e como o reboot automático garante um funcionamento confiável.


Customizando o Menu com setMenu()

O WiFiManager permite personalizar o menu exibido no portal de configuração através do método setMenu(). Por padrão, o portal inclui várias páginas, mas você pode escolher exatamente quais delas deseja exibir, deixando o portal mais simples e adequado ao usuário final.

A biblioteca define internamente diversos tokens que representam cada item de menu. Aqui estão os principais:

TokenFunção no Portal
"wifi"Página principal de configuração WiFi (lista de redes, SSID, senha).
"wifinoscan"Igual ao "wifi", mas sem escanear redes (útil para ambientes com interferência).
"info"Exibe informações do dispositivo (IP, MAC, RSSI, etc.).
"param"Página separada para parâmetros personalizados.
"close"Fecha o portal sem salvar nada.
"restart"Reinicia o dispositivo diretamente pelo portal.
"exit"Sai do portal e tenta conectar ao WiFi configurado.
"erase"Apaga todas as credenciais WiFi salvas.
"update"Página de atualização OTA (quando habilitada).
"sep"Apenas um separador visual no menu.
"custom"Permite adicionar páginas personalizadas.

Esses tokens permitem montar o menu exatamente como você deseja. Por exemplo, o menu padrão costuma incluir várias dessas opções, o que pode deixar o portal mais técnico do que o necessário.

Como nosso objetivo é simplificar a experiência do usuário, reduzimos o menu para apenas o essencial:

wm.setMenu({"wifi", "info", "sep", "exit"});

Por que essa escolha?

  • wifi → concentra toda a configuração em uma única página
  • info → fornece dados úteis sobre o dispositivo
  • sep → organiza visualmente o menu
  • exit → encerra o portal de forma clara e previsível

Essa simplificação:

  • reduz a navegação entre telas
  • evita múltiplos botões de SAVE
  • deixa o portal mais intuitivo
  • diminui a chance de erro do usuário

Nas próximas seções, vamos ver como adicionar parâmetros personalizados, aplicar temas e consolidar tudo em uma única página.

Figura 4 – Tela Principal do WifiManager sem Customização

Figura 5 – Tela Principal do WifiManager com o Tema Dark

Figura 6 – Tela Principal do WifiManager com o Tema Light


Criando Parâmetros Personalizados

Um dos recursos mais poderosos do WiFiManager é a possibilidade de adicionar parâmetros personalizados ao portal de configuração. Isso permite que o usuário configure não apenas o WiFi, mas também outras informações importantes para o funcionamento do dispositivo — tudo na mesma página.

Esses parâmetros podem ser usados para:

  • nome do dispositivo
  • modo de operação
  • chaves de API
  • URLs de servidor
  • intervalos de leitura
  • opções de tema
  • e qualquer outro dado relevante para a aplicação

O WiFiManager fornece a classe WiFiManagerParameter para criar esses campos.

Exemplo básico de parâmetro personalizado

O exemplo abaixo adiciona um campo para o nome do dispositivo:

#include <WiFiManager.h>

WiFiManager wm;

// Buffer para armazenar o valor do parâmetro
char deviceName[40] = "MeuESP32";

void setup() {
  Serial.begin(115200);

  // Cria o parâmetro (id, label, valor inicial, tamanho máximo)
  WiFiManagerParameter p_deviceName("devname", "Nome do Dispositivo", deviceName, 40);

  // Adiciona o parâmetro ao WiFiManager
  wm.addParameter(&p_deviceName);

  // Inicia o portal de configuração
  bool ok = wm.autoConnect("Portal-ESP32", "12345678");

  if (!ok) {
    Serial.println("Falha ao conectar. Reiniciando...");
    delay(1000);
    ESP.restart();
  }

  // Atualiza o valor salvo
  strcpy(deviceName, p_deviceName.getValue());

  Serial.print("Nome configurado: ");
  Serial.println(deviceName);
}

void loop() {
  // Código principal da aplicação
}

Como esse código funciona?

  • Criamos um buffer (deviceName) para armazenar o valor.
  • Criamos um parâmetro com WiFiManagerParameter.
  • Adicionamos o parâmetro ao WiFiManager com addParameter().
  • O campo aparece automaticamente na página principal do portal.
  • Após o SAVE, recuperamos o valor com getValue().

Simples, direto e extremamente útil.

Por que colocar tudo na mesma página?

O WiFiManager permite criar uma página separada para parâmetros ("param"), mas isso adiciona navegação extra e mais um botão de SAVE. Como nosso objetivo é simplificar o fluxo, optamos por:

  • exibir todos os parâmetros na página principal
  • usar um único botão SAVE
  • evitar múltiplas telas

Isso reduz erros e deixa o portal mais intuitivo.

Dicas importantes ao criar parâmetros

  • Sempre use buffers com tamanho suficiente.
  • Evite parâmetros muito longos (o portal é simples).
  • Prefira nomes curtos para os IDs (ex.: "devname").
  • Use labels claros e amigáveis para o usuário.
  • Após o SAVE, sempre copie o valor para uma variável persistente.

Combinação de campos de <select>, checkbox e campos invisíveis

Além de campos de texto, o WiFiManager permite criar parâmetros que serão renderizados como <select> e checkbox usando HTML direto no construtor do WiFiManagerParameter. Uma estratégia muito útil é combinar:

  • um campo invisível (que guarda o valor real usado no código)
  • com um <select> ou checkbox visível (que o usuário manipula)

Por exemplo, para escolher o tema:

// Campo invisível que guarda o valor efetivo do tema
WiFiManagerParameter p_themeValue("theme", "", "default", 10, "style='display:none;'");

// Campo visível (select) que o usuário enxerga
const char* themeHtml =
  "<label for='theme_sel'>Tema</label>"
  "<select id='theme_sel' onchange=\"document.getElementById('theme').value=this.value;\">"
  "<option value='default'>Default</option>"
  "<option value='dark'>Dark</option>"
  "<option value='light'>Light</option>"
  "</select>";

WiFiManagerParameter p_themeSelect(themeHtml);

// Adiciona ambos
wm.addParameter(&p_themeValue);
wm.addParameter(&p_themeSelect);

Mesma ideia vale para checkbox:

  • checkbox visível
  • campo invisível que recebe "0" ou "1" via JavaScript

Essa abordagem tem duas vantagens:

  • o HTML fica livre para você montar a interface como quiser
  • o código C++ continua simples, lendo apenas o valor do campo invisível com getValue()

Depois do SAVE, você só lê:

String theme = p_themeValue.getValue();  // "default", "dark" ou "light"

Figura 7 – Três tipos de campos: Seleção, Checkbox e Texto


Implementando a Página “Info”

Além da página principal de configuração, o WiFiManager permite adicionar páginas personalizadas ao portal. Uma das mais úteis é a página “Info”, que exibe dados importantes sobre o dispositivo, como:

  • endereço IP
  • MAC address
  • intensidade do sinal (RSSI)
  • versão do firmware
  • uptime
  • nome do dispositivo
  • entre outras informações relevantes

Esses dados ajudam o usuário a entender o estado atual do ESP32 e facilitam diagnósticos, especialmente em ambientes onde o dispositivo será instalado por terceiros.

Habilitando a Página “Info”

O WiFiManager já possui uma página de informações interna, e basta incluí-la no menu:

wm.setMenu({"wifi", "info", "sep", "exit"});

Com isso, o item Info aparece no menu e exibe automaticamente:

  • IP local
  • Gateway
  • Máscara
  • MAC
  • RSSI
  • Versão da biblioteca
  • Tempo de atividade

Essa página é gerada pelo próprio WiFiManager e já atende a maioria dos casos.

Criando uma página “Info” personalizada

Se você quiser exibir informações adicionais — como nome do dispositivo, versão do firmware ou dados específicos da aplicação — pode criar sua própria rota usando setCustomMenuHTML() ou setCustomHeadElement() combinados com setCustomMenuHTML().

Aqui vai um exemplo simples usando uma rota customizada:

wm.setCustomMenuHTML("<a href=\"/info\">Info</a>");

wm.setWebServerCallback([&](WiFiManager *wm) {
  wm->server->on("/info", HTTP_GET, [&]() {
    String page = "<html><body>";
    page += "<h2>Informações do Dispositivo</h2>";
    page += "IP: " + WiFi.localIP().toString() + "<br>";
    page += "MAC: " + WiFi.macAddress() + "<br>";
    page += "RSSI: " + String(WiFi.RSSI()) + " dBm<br>";
    page += "Firmware: 1.0.0<br>";
    page += "</body></html>";
    wm->server->send(200, "text/html", page);
  });
});

O que esse código faz?

  • adiciona um link “Info” ao menu
  • cria uma rota /info dentro do próprio servidor do WiFiManager
  • monta uma página HTML simples com informações do dispositivo
  • exibe dados úteis para o usuário final

Essa abordagem permite personalizar totalmente o conteúdo exibido, mantendo o portal organizado e profissional.

Por que incluir uma página “Info”?

  • ajuda o usuário a verificar se o dispositivo está conectado corretamente
  • facilita diagnósticos em campo
  • permite exibir dados específicos da aplicação
  • deixa o portal mais completo e com aparência de produto final

A página “Info” é um complemento natural ao portal customizado, oferecendo transparência e utilidade sem complicar o fluxo de configuração.

Figura 8 – Tela Info do WifiManager com o Tema Dark


Aplicando Temas (Default, Dark e Light)

O WiFiManager permite inserir elementos HTML e CSS personalizados dentro do portal de configuração usando o método setCustomHeadElement(). Isso abre espaço para criar uma identidade visual própria, deixando o portal mais agradável e coerente com o produto final.

No nosso projeto, implementamos três temas:

  • Default – o tema original do WiFiManager
  • Dark – fundo escuro, contraste suave e visual moderno
  • Light – fundo claro, visual limpo e minimalista

A seleção do tema é feita por meio de um parâmetro personalizado (como vimos na seção anterior), e o CSS correspondente é injetado dinamicamente no portal.

Inserindo CSS customizado

O WiFiManager permite adicionar qualquer conteúdo dentro da tag <head> da página. Isso inclui:

  • estilos CSS
  • fontes
  • scripts
  • meta tags

Para aplicar um tema, basta inserir o CSS desejado:

if (theme == "dark") {
    wm.setCustomHeadElement(
        "<style>"
        "body{background:#222;color:#eee;font-family:Arial;}"
        "input,select{background:#333;color:#eee;border:1px solid #555;}"
        "</style>"
    );
}
else if (theme == "light") {
    wm.setCustomHeadElement(
        "<style>"
        "body{background:#f5f5f5;color:#333;font-family:Arial;}"
        "input,select{background:#fff;color:#333;border:1px solid #ccc;}"
        "</style>"
    );
}

Esse CSS é aplicado somente ao portal do WiFiManager, sem interferir no servidor web principal do dispositivo.

Corrigindo campos invisíveis no tema Default

O tema Default do WiFiManager exibe todos os campos adicionados, inclusive aqueles que deveriam permanecer invisíveis (como os campos ocultos usados para <select> e checkbox). Para resolver isso, os temas Dark e Light incluem regras CSS que garantem que esses campos permaneçam ocultos:

#theme {
    display: none;
}

Isso mantém a interface limpa e evita confusão para o usuário.

Por que usar temas?

  • melhora a experiência visual
  • deixa o portal com aparência mais profissional
  • facilita a leitura em ambientes com pouca ou muita luz
  • permite adaptar o portal à identidade visual do produto
  • ajuda a esconder campos técnicos que não devem aparecer

O resultado é um portal mais moderno, agradável e intuitivo — algo que realmente parece parte de um produto final.

Personalizando o Título do Portal

O WiFiManager permite alterar o título exibido no topo da página de configuração usando o método setTitle(). Esse título aparece dentro do <header> do portal e pode conter HTML completo, permitindo:

  • negrito
  • itálico
  • sublinhado
  • emojis
  • alinhamento
  • cores (via <span style="">)
  • até pequenos ícones SVG

Isso abre espaço para deixar o portal com uma identidade visual própria, reforçando que ele faz parte de um produto final — não apenas de um firmware técnico.

Exemplo de título customizado

wm.setTitle("<center>⭐ WiFiManager <u>Customizado</u> ⭐</center>");

Esse título será renderizado exatamente como está, incluindo:

  • alinhamento central
  • emojis
  • sublinhado
  • espaçamento natural do HTML

Você pode ir além:

wm.setTitle(
  "<center>"
  "<h2 style='margin:0;color:#4CAF50;'>🌐 Configuração do Dispositivo</h2>"
  "<small>Portal Customizado</small>"
  "</center>"
);

Por que isso é útil?

  • reforça a identidade visual do produto
  • deixa o portal mais amigável
  • melhora a percepção de qualidade
  • permite destacar o nome do dispositivo
  • combina perfeitamente com os temas Dark/Light
  • ajuda o usuário a entender que está no lugar certo

 


Tudo em uma Única Página — Simplificando o Fluxo

O WiFiManager, por padrão, organiza suas funcionalidades em várias páginas: WiFi, Parâmetros, Informações, Reset, entre outras. Embora isso faça sentido para uso técnico, para o usuário final essa estrutura pode gerar dúvidas e aumentar a chance de erro.

Ao desenvolver o portal customizado, adotamos uma abordagem diferente: concentrar todas as informações relevantes em uma única página, com um único botão de SAVE. Essa decisão simplifica o fluxo e torna o processo de configuração muito mais intuitivo.

Por que consolidar tudo em uma página?

1. Reduz navegação desnecessária

O usuário não precisa alternar entre telas para configurar WiFi, parâmetros e opções adicionais. Tudo está ali, visível e acessível.

2. Evita múltiplos botões de SAVE

No portal padrão, cada página tem seu próprio botão de salvar. Isso pode levar o usuário a:

  • salvar apenas parte das informações
  • esquecer de salvar parâmetros adicionais
  • acreditar que já concluiu a configuração

Com uma única página, isso simplesmente não acontece.

3. Facilita o entendimento

O usuário vê:

  • SSID
  • senha
  • parâmetros personalizados
  • seleção de tema
  • botão SAVE

Tudo no mesmo contexto, sem surpresas.

4. Melhora a experiência em dispositivos móveis

A maioria dos usuários acessa o portal pelo celular. Uma única página reduz:

  • tempo de navegação
  • carregamento de telas
  • necessidade de voltar/avançar

5. Deixa o portal com “cara de produto”

Uma interface simples, direta e sem distrações transmite profissionalismo e confiança.

Como isso é implementado no WiFiManager?

O segredo está em duas decisões:

1. Remover páginas desnecessárias do menu

Como vimos na seção 4:

wm.setMenu({"wifi", "info", "sep", "exit"});

Isso garante que o usuário só veja o essencial.

2. Adicionar todos os parâmetros na página principal

Em vez de usar a página "param", adicionamos tudo diretamente:

wm.addParameter(&p_deviceName);
wm.addParameter(&p_themeValue);
wm.addParameter(&p_themeSelect);
wm.addParameter(&p_checkboxValue);

O WiFiManager automaticamente renderiza esses campos na página WiFi.

Resultado

O portal final fica:

  • mais limpo
  • mais intuitivo
  • mais rápido de usar
  • mais difícil de usar errado
  • mais adequado para produtos reais

Essa abordagem é especialmente útil quando o ESP32 será configurado por pessoas sem conhecimento técnico — instaladores, clientes finais ou usuários domésticos.

Figura 9 – Tela com Todas as Definições do Programa


Reboot Automático Após o SAVE

Quando usamos o WiFiManager em seu fluxo padrão, ele próprio reinicia o ESP32 após salvar as credenciais. Porém, ao criar um portal customizado — especialmente quando adicionamos parâmetros personalizados, temas e páginas extras — esse comportamento pode mudar. Em alguns casos, o WiFiManager retorna o controle ao código sem reiniciar automaticamente, o que pode gerar inconsistências.

Por isso, adotamos a prática de forçar um reboot após o SAVE, garantindo que o dispositivo inicie em um estado limpo e previsível.

Por que o reboot é necessário?

1. Libera recursos internos

Durante o portal de configuração, o WiFiManager cria:

  • um ponto de acesso temporário
  • um servidor web interno
  • rotas adicionais
  • buffers e estruturas de rede

Após o SAVE, esses recursos não são mais necessários. O reboot garante que tudo seja desmontado corretamente.

2. Evita conflitos com o AsyncWebServer

O servidor principal da aplicação (AsyncWebServer) precisa iniciar com:

  • WiFi já conectado
  • portas livres
  • pilha de rede estável

Sem o reboot, o servidor do WiFiManager pode deixar portas ocupadas ou estados intermediários que causam falhas intermitentes.

3. Garante que os parâmetros personalizados sejam aplicados

Após o SAVE, os valores dos parâmetros:

  • são gravados
  • mas só são usados pelo firmware após reiniciar

O reboot garante que o dispositivo já inicie com todas as configurações ativas.

4. Comportamento mais previsível para o usuário

O usuário aperta SAVE, o dispositivo reinicia e entra em operação normal. Simples, direto e sem ambiguidades.

Como implementar o reboot automático?

O WiFiManager retorna true quando o usuário salva as configurações. Assim, basta verificar esse retorno:

bool ok = wm.autoConnect("Portal-ESP32", "12345678");

if (!ok) {
    Serial.println("Falha ao conectar. Reiniciando...");
    delay(1000);
    ESP.restart();
}

// Se chegou aqui, o SAVE foi bem-sucedido
Serial.println("Configurações salvas. Reiniciando...");
delay(1000);
ESP.restart();

Esse padrão garante que:

  • o portal é encerrado corretamente
  • o WiFi é reconectado
  • o AsyncWebServer inicia limpo
  • todos os parâmetros personalizados são aplicados

Resultado

Com o reboot automático:

  • o fluxo fica mais confiável
  • o servidor principal inicia sem conflitos
  • o dispositivo entra em operação normal imediatamente
  • o usuário percebe um comportamento consistente e profissional

Essa etapa, embora simples, é fundamental para garantir a estabilidade do portal customizado.

Alternativas para Encerrar o Portal do WiFiManager Após o SAVE

Quando o usuário salva as configurações no portal do WiFiManager, o dispositivo precisa liberar a porta 80 para que o AsyncWebServer possa assumir o controle. Existem três maneiras de fazer isso, cada uma com vantagens e desvantagens.

1) Reboot automático após o SAVE (solução adotada)

  • Mais simples
  • Mais robusta
  • Melhor experiência para o usuário final

Essa é a abordagem usada no projeto. Após o SAVE, o ESP32 reinicia, o portal é encerrado automaticamente e a porta 80 fica livre para o AsyncWebServer.

Por que funciona tão bem?

  • o reboot garante que o portal do WiFiManager não está mais rodando
  • a pilha WiFi reinicia limpa
  • a porta 80 é liberada sem risco de conflito
  • o AsyncWebServer inicia de forma previsível
  • o usuário percebe um comportamento natural: “salvou → reiniciou → funcionou”

Essa é a solução mais profissional para produtos reais.

2) Rodar o portal do WiFiManager em outra porta (ex.: 8080)

  • Evita conflito com a porta 80
  • Menos natural para o usuário final
  • Exige digitar “:8080” manualmente

O WiFiManager permite mudar a porta do portal:

wm.setWebPortalPort(8080);

O portal ficaria acessível em:

http://192.168.4.1:8080

Por que não usamos essa alternativa?

  • o usuário precisa digitar a porta manualmente
  • o captive portal pode não funcionar corretamente
  • a experiência deixa de ser intuitiva
  • em produtos comerciais, isso parece “não profissional”

Funciona, mas não é ideal para um fluxo simples e amigável.

3) Encerrar o portal manualmente com stopWebPortal()

  • Evita reboot
  • Exige modo não bloqueante
  • Fluxo mais complexo
  • Pode quebrar o captive portal

O WiFiManager oferece:

wm.stopWebPortal();

Mas isso só funciona quando o portal está rodando em modo não bloqueante:

wm.setConfigPortalBlocking(false);
wm.startConfigPortal("Portal-ESP32", "12345678");

E no loop:

wm.process();  // mantém o portal vivo

if (configuracaoSalva) {
    wm.stopWebPortal();   // libera a porta 80
    server.begin();       // inicia o AsyncWebServer
}

Desvantagens:

  • o fluxo fica mais difícil de implementar
  • o portal pode não aparecer automaticamente no celular
  • há risco de condições de corrida
  • exige mais código e mais cuidado

É uma alternativa válida, mas não tão estável quanto o reboot.

Resumo das Alternativas

Alternativa

Vantagens

Desvantagens

Adequada para

1) Reboot após SAVE

simples, robusta, intuitiva

reinicia o dispositivo

produtos reais, usuários finais

2) Portal na porta 8080

evita conflito sem reboot

menos natural, exige digitar porta

ambientes técnicos, protótipos

3) stopWebPortal()

evita reboot

fluxo complexo, pode quebrar captive portal

usuários avançados, projetos customizados

As três alternativas funcionam, mas cada uma tem um impacto diferente no fluxo e na experiência do usuário. Para um produto real — simples, confiável e intuitivo — a melhor escolha continua sendo:

Reboot automático após o SAVE

E é exatamente essa a abordagem adotada no projeto.

Após o SAVE, o firmware salva os parâmetros na NVS e atualiza as variáveis globais que alimentam o HTML. Em seguida, o reboot garante que o portal do WiFiManager seja encerrado e a porta 80 fique livre para o AsyncWebServer.


Integrando com o AsyncWebServer

Depois que o WiFiManager cumpre seu papel — coletar SSID, senha e parâmetros personalizados — o ESP32 reinicia e entra no modo de operação normal. Nesse momento, quem assume o controle é o AsyncWebServer, que será responsável por:

  • servir páginas HTML
  • exibir status do dispositivo
  • receber comandos
  • fornecer APIs REST
  • ou qualquer outra funcionalidade da aplicação

A integração entre WiFiManager e AsyncWebServer é simples, mas precisa seguir uma ordem correta para evitar conflitos.

Ordem correta de inicialização

Após o reboot:

  1. O ESP32 conecta ao WiFi usando as credenciais salvas.
  2. O firmware lê os parâmetros personalizados.
  3. O AsyncWebServer é iniciado.
  4. As rotas da aplicação são registradas.
  5. O dispositivo entra em operação normal.

Essa ordem garante que:

  • a pilha de rede está estável
  • nenhuma porta está ocupada pelo portal de configuração
  • todos os parâmetros já estão disponíveis
  • o servidor principal inicia limpo

Exemplo básico de integração

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

AsyncWebServer server(80);

void setup() {
  Serial.begin(115200);

  // Conecta ao WiFi com as credenciais salvas pelo WiFiManager
  WiFi.begin();

  Serial.print("Conectando");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nWiFi conectado!");
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());

  // Rota principal
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    String page = "<h2>Status do ESP32</h2>";
    page += "IP: " + WiFi.localIP().toString() + "<br>";
    page += "RSSI: " + String(WiFi.RSSI()) + " dBm<br>";
    request->send(200, "text/html", page);
  });

  // Inicia o servidor
  server.begin();
}

void loop() {
  // Lógica principal da aplicação
}

Por que o AsyncWebServer só deve iniciar após o reboot?

1. Evita conflito de portas

O WiFiManager usa internamente um servidor web próprio. Se o AsyncWebServer for iniciado antes do reboot, ambos podem tentar usar a porta 80.

2. Garante estabilidade da pilha WiFi

O portal de configuração cria um AP temporário e manipula rotas internas. O reboot garante que tudo seja desmontado corretamente.

3. Evita comportamentos intermitentes

Sem reboot, podem ocorrer:

  • páginas que não carregam
  • servidor travando
  • falhas ao registrar rotas
  • perda de conexão WiFi

4. Fluxo mais previsível

O usuário aperta SAVE, o dispositivo reinicia e entra em operação normal. Simples e profissional.

Resultado final

Com essa integração:

  • o WiFiManager cuida da configuração inicial
  • o AsyncWebServer cuida da operação normal
  • o fluxo fica limpo, estável e confiável
  • o portal customizado funciona como parte natural do produto
  • o usuário final tem uma experiência simples e intuitiva

Essa separação clara entre configuração e operação é o que torna o projeto robusto e adequado para aplicações reais.

Configurações Importantes de Timeout no WiFiManager

O WiFiManager oferece dois parâmetros essenciais para controlar o comportamento do portal de configuração e da tentativa de conexão ao roteador. Ajustá‑los corretamente evita que o dispositivo entre no modo AP sem necessidade ou fique preso indefinidamente aguardando interação do usuário.

1) Timeout do Portal de Configuração

wm.setConfigPortalTimeout(300);

Define o tempo máximo, em segundos, que o portal de configuração ficará ativo sem interação do usuário.

  • Se ninguém acessar o portal dentro desse período
  • ou se o usuário abrir o portal mas não clicar em nada
  • ou se o celular se desconectar do AP

→ o WiFiManager encerra o modo AP e retorna ao fluxo normal do programa.

 Por que isso é importante?

Sem esse timeout, o ESP32 pode ficar preso eternamente no modo AP caso o usuário:

  • esqueça de finalizar a configuração
  • se afaste do dispositivo
  • perca a conexão
  • desligue o celular

Com o timeout, o dispositivo sempre retorna ao modo operacional, evitando travamentos.

2) Timeout de Conexão ao Roteador

wm.setConnectTimeout(60);

Define o tempo máximo (em segundos) que o WiFiManager tentará conectar ao roteador depois que as credenciais já estão salvas.

Esse parâmetro é crítico porque, sem ele, o comportamento padrão é:

  • se a conexão demorar mais que alguns segundos
  • ou se o roteador estiver lento
  • ou se o sinal estiver fraco

→ o WiFiManager assume que a conexão falhou e entra no modo AP, mesmo com SSID e senha corretos.

 Por que isso acontece?

Porque o WiFiManager tenta conectar rapidamente. Se o roteador demorar mais que o esperado para autenticar, o ESP32 entende como falha.

O que o timeout resolve?

Com setConnectTimeout(60), você garante:

  • tempo suficiente para autenticação em roteadores lentos
  • estabilidade em redes congestionadas
  • que o dispositivo não entre no modo AP sem necessidade
  • que o fluxo de inicialização seja previsível

Observações sobre a Implementação

    1) Alias e mDNS

    O alias informado no portal do WiFiManager é utilizado para registrar o serviço mDNS, permitindo que o usuário acesse a aplicação através de um endereço amigável, como:

    http://mywm.local
    

    Isso evita que o usuário precise descobrir o IP do dispositivo (por exemplo: http://192.168.18.15) para acessar a interface web.

      2) Indicação visual com o LED_BUILTIN

      O LED_BUILTIN é usado como indicador de estado do dispositivo:

      • aceso continuamente → WiFiManager ativo no modo AP, aguardando configuração
      • piscando → aplicação AsyncWebServer em execução, pronta para receber conexões

      Essa sinalização visual facilita o diagnóstico rápido do estado atual do ESP32.

      3) Persistência dos parâmetros

      Os três parâmetros configurados no portal (Modo, Logs e Alias) são armazenados usando Preferences e recuperados no início da aplicação.

      • “Modo” e “Logs” são exemplos didáticos para demonstrar o uso de campos customizados.
      • “Alias” é utilizado efetivamente na configuração do mDNS.

      Já o SSID e a senha são persistidos automaticamente pelo WiFiManager na NVS (Non‑Volatile Storage) — a memória flash interna do ESP32 usada para armazenar pares chave/valor de forma permanente.

      4) Atualização dinâmica via WebSocket

      A aplicação atualiza dinamicamente, a cada 1000 ms, os campos:

      • UPTIME
      • RSSI
      • FreeRAM

      Esses valores variam constantemente e, por isso, são enviados via WebSocket. Os demais campos são estáticos e carregados apenas na renderização inicial da página.

      5) Botão físico (GPIO 0) para reentrar no modo de configuração

      Durante a operação normal, o dispositivo pode ser colocado novamente no modo de configuração pressionando o botão interno do ESP32 (GPIO 0). Esse recurso é essencial para:

      • reconfigurar a rede WiFi
      • recuperar o dispositivo em campo
      • redefinir parâmetros personalizados
      • corrigir erros de configuração
      • reinstalar o dispositivo em outro local

      A lógica típica é:

      pinMode(0, INPUT_PULLUP);
      
      if (digitalRead(0) == LOW) {   // Botão pressionado
          Serial.println("Botão pressionado. Entrando no modo de configuração...");
          wm.resetSettings();        // Opcional: apaga SSID/senha
          wm.startConfigPortal("Portal-ESP32", "12345678");
      }
      

      Esse mecanismo garante que o usuário sempre tenha uma forma física, simples e confiável de recuperar o dispositivo, mesmo quando:

      • o WiFi não conecta
      • o IP é desconhecido
      • o mDNS não responde
      • o servidor web está inacessível

      Figura 10 – Tela da Aplicação AsyncWebServer


      Código Fonte

      //------------------------------------------------------------------------------------------------------------------
      // Objetivos : . Usar o WifiManager com campos customizados:
      //                 1) Campo de <Select>
      //                 2) [x] Checkbox
      //             . Alterar o tema (dark/light/default) e título do html do WifiManager 
      //             . Salvar/Recuperar os parâmetros do Prefs
      // Observação: LED Acesso   -> Modo de Configuração 
      //             LED Piscando -> WebServer Ativo
      // Autor     : Dailton Menezes - Mar/2026
      //------------------------------------------------------------------------------------------------------------------
      
      //-------------------------------------
      // Definição das Bibliotecas Utilizadas
      //-------------------------------------
      
      #include <Arduino.h>                 
      #include <WiFi.h>
      #include <WiFiManager.h>
      #include <Preferences.h>
      #include <AsyncTCP.h>
      #include <ESPAsyncWebServer.h>
      #include <ArduinoJson.h>
      #include <ESPmDNS.h>
      
      //--------------------------------
      // Definição de Constantes Gerais
      //--------------------------------
      
      #define INTERVALO       1000               // Intervalo em ms para enviar via WebSocket, Cleanup, etc.
      #define SSID_AP         "ESP32_Config_AP"  // SSID do Modo AP
      #define SENHA_AP        "12345678"         // Senha do Modo AP
      #define TIMEOUT_PORTAL  300                // Tempo máximo que o Portal ficará ativo em seg
      #define TIMEOUT_CONEXAO 60                 // Tempo máximo de conexão com o Roteador em seg
      
      //--------------------------------
      // Definição dos Pinos Utilizados
      //--------------------------------
      
      #define RESET_BUTTON_PIN 0                 // Pino do Botão para forçar entrar na configuração
      #define LED_PIN 2                          // Pino do LED_BUILTIN do ESP32 
      
      //---------------------------------------------------------------
      // Seleção de Tema -> Descomente um e deixe os demais comentados
      //---------------------------------------------------------------
      
      // #define TEMA_DEFAULT
      #define TEMA_DARK
      // #define TEMA_LIGHT
      
      //--------------------------------
      // Definição de Variáveis Globais
      //--------------------------------
      
      volatile bool factoryResetRequested = false;
      Preferences prefs;
      char modo_operacao[10] = "";
      char habilitar_log[5] = "";
      char aliasname[30] = "";
      String saved_modo;
      String saved_log;
      String saved_alias;
      unsigned long lastDisplay = 0;
      unsigned long lastCleanup = 0;
      
      //--------------------------------
      // Definição do Serviço Web
      //--------------------------------
      
      AsyncWebServer server(80);
      AsyncWebSocket ws("/ws");
      
      //-------------------------------------
      // Definição dos Campos do WifiManager
      //-------------------------------------
      
      WiFiManager wm;
      WiFiManagerParameter *html_select;
      WiFiManagerParameter *html_checkbox;
      WiFiManagerParameter *modo_param;
      WiFiManagerParameter *log_param;
      WiFiManagerParameter *alias_name;
      bool wifiSalvo = false;
      bool paramsSalvos = false;
      bool portalFoiUsado = false;
      
      //----------------------------------
      // Prototipação de Rotinas e Funções
      //----------------------------------
      
      void IRAM_ATTR onFactoryReset();
      void configModeCallback(WiFiManager *wm);
      void preparaWifiManager();
      void processaWifiManager();
      void setupServer();
      bool setDNSNAME(String nome);
      String formatUptime();
      void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
                     AwsEventType type, void *arg, uint8_t *data, size_t len);
      void notifyClients();
      void aplicaTemaWiFiManager();
      void adicionaPaginaExtra();
      void webServerReadyCallback();
      
      //-------------------------
      // Definição do Tema Dark
      //-------------------------
      
      const char wm_css_dark[] PROGMEM = R"rawliteral(
      <style>
      /* ======== TEMA DARK PARA WIFIMANAGER ======== */
      
      /* Fundo geral */
      body {
          background-color: #121212 !important;
          color: #e0e0e0 !important;
          font-family: Arial, sans-serif;
      }
      
      /* Títulos */
      h1, h2, h3 {
          color: #ffffff !important;
      }
      
      /* Inputs */
      input[type="text"],
      input[type="password"],
      input[type="number"],
      select {
          background-color: #1e1e1e !important;
          color: #ffffff !important;
          border: 1px solid #444 !important;
          padding: 8px !important;
          border-radius: 4px !important;
      }
      
      /* Botões */
      button, input[type="submit"] {
          background-color: #333 !important;
          color: #fff !important;
          border: 1px solid #555 !important;
          padding: 10px 16px !important;
          border-radius: 4px !important;
          cursor: pointer;
      }
      
      button:hover, input[type="submit"]:hover {
          background-color: #444 !important;
      }
      
      /* Links */
      a {
          color: #80bfff !important;
      }
      
      /* Separadores */
      hr {
          border: 1px solid #333 !important;
      }
      
      /* ======== CORREÇÃO DA LISTA DE SSIDs ======== */
      
      #networks,
      .wifi,
      .wifi li,
      .ssid {
          display: block !important;
          visibility: visible !important;
          background-color: #1b1b1b !important;
          color: #ffffff !important;
          border: 1px solid #333 !important;
          padding: 10px !important;
          margin-bottom: 8px !important;
          border-radius: 4px !important;
      }
      
      /* Hover nos SSIDs */
      .wifi li:hover {
          background-color: #2a2a2a !important;
      }
      
      /* Nome da rede */
      .ssid span {
          color: #fff !important;
          font-weight: bold !important;
      }
      
      /* Força exibição da tabela de redes */
      table {
          color: #fff !important;
          background-color: #1b1b1b !important;
          border: 1px solid #333 !important;
      }
      
      table td, table th {
          border: 1px solid #333 !important;
          padding: 6px !important;
      }
      
      /* Corrige o botão de scan */
      #scan {
          background-color: #333 !important;
          color: #fff !important;
          border: 1px solid #555 !important;
      }
      
      /* Corrige o spinner de scan */
      #scanning {
          color: #fff !important;
      }
      
      /* Painéis */
      div {
          background-color: transparent !important;
      }
      /* ======== OCULTA CAMPOS INVISÍVEIS DO WIFIMANAGER ======== */
      input[type="hidden"],
      .wm_hidden,
      #hidden,
      input[name="modo"],
      input[name="log"] {
          display: none !important;
          visibility: hidden !important;
          height: 0 !important;
          margin: 0 !important;
          padding: 0 !important;
          border: none !important;
      }
      </style>
      )rawliteral";  
      
      //-------------------------
      // Definição do Tema Light
      //-------------------------
      
      const char wm_css_light[] PROGMEM = R"rawliteral(
      <style>
      
      /* ======== TEMA LIGHT PARA WIFIMANAGER ======== */
      
      /* Fundo geral */
      body {
          background-color: #f2f2f2 !important;
          color: #000 !important;
          font-family: Arial, sans-serif;
      }
      
      /* Títulos */
      h1, h2, h3 {
          color: #005bbb !important;
      }
      
      /* Inputs */
      input[type="text"],
      input[type="password"],
      input[type="number"],
      select {
          background-color: #ffffff !important;
          color: #000 !important;
          border: 1px solid #005bbb !important;
          padding: 8px !important;
          border-radius: 4px !important;
      }
      
      /* Botões */
      button, input[type="submit"] {
          background-color: #005bbb !important;
          color: #fff !important;
          border: 1px solid #004999 !important;
          padding: 10px 16px !important;
          border-radius: 4px !important;
          cursor: pointer;
      }
      
      button:hover, input[type="submit"]:hover {
          background-color: #0070dd !important;
      }
      
      /* Links */
      a {
          color: #005bbb !important;
      }
      
      /* Separadores */
      hr {
          border: 1px solid #ccc !important;
      }
      
      /* ======== LISTA DE SSIDs (CORRIGIDA) ======== */
      
      #networks,
      .wifi,
      .wifi li,
      .ssid {
          display: block !important;
          visibility: visible !important;
          background-color: #ffffff !important;
          color: #000 !important;
          border: 1px solid #cccccc !important;
          padding: 10px !important;
          margin-bottom: 8px !important;
          border-radius: 4px !important;
      }
      
      /* Hover nos SSIDs */
      .wifi li:hover {
          background-color: #e6f0ff !important;
      }
      
      /* Nome da rede */
      .ssid span {
          color: #000 !important;
          font-weight: bold !important;
      }
      
      /* Tabela de redes */
      table {
          color: #000 !important;
          background-color: #ffffff !important;
          border: 1px solid #ccc !important;
      }
      
      table td, table th {
          border: 1px solid #ccc !important;
          padding: 6px !important;
      }
      
      /* Botão de scan */
      #scan {
          background-color: #005bbb !important;
          color: #fff !important;
          border: 1px solid #004999 !important;
      }
      
      /* Spinner de scan */
      #scanning {
          color: #005bbb !important;
      }
      
      /* Painéis */
      div {
          background-color: transparent !important;
      }
      
      /* ======== OCULTA CAMPOS INVISÍVEIS ======== */
      input[type="hidden"],
      .wm_hidden,
      #hidden,
      input[name="modo"],
      input[name="log"] {
          display: none !important;
          visibility: hidden !important;
          height: 0 !important;
          margin: 0 !important;
          padding: 0 !important;
          border: none !important;
      }
      
      </style>
      )rawliteral";
      
      //----------------------------------
      // HTML Principal
      //----------------------------------
      
      const char status_html[] PROGMEM = R"rawliteral(
      <!DOCTYPE html>
      <html lang='pt-br'>
      <head>
      <meta charset='UTF-8'>
      <meta name='viewport' content='width=device-width, initial-scale=1'>
      <title>Status do ESP32</title>
      
      <style>
        body { background:#121212; color:#fff; font-family:Arial; padding:20px; }
        h1 { color:#4da3ff; }
        .card { background:#1e1e1e; padding:20px; border-radius:10px; margin-bottom:20px; }
        .label { font-weight:bold; color:#4da3ff; }
      </style>
      
      </head>
      <body>
      
      <h1>Status do ESP32</h1>
      
      <div class='card'><span class='label'>Modo de Operação:</span> %MODO%</div>
      <div class='card'><span class='label'>Logs Habilitados:</span> %LOGS%</div>
      <div class='card'><span class='label'>MAC Address:</span> %MAC%</div>
      <div class='card'><span class='label'>IP Local:</span> %IP%</div>
      
      <div class='card'><span class='label'>RSSI (Sinal WiFi):</span> <span id='rssi'>--</span> dBm</div>
      <div class='card'><span class='label'>Free RAM:</span> <span id='ram'>--</span> bytes</div>
      <div class='card'><span class='label'>Uptime:</span> <span id='uptime'>--</span></div>
      
      <div class='card'><span class='label'>Versão Firmware:</span> 1.0</div>
      
      <script>
        var gateway = `ws://${window.location.hostname}/ws`;
        var websocket;
      
        function initWebSocket() {
          websocket = new WebSocket(gateway);
      
          websocket.onopen = () => console.log("WS conectado");
          websocket.onclose = () => { console.log("WS desconectado"); setTimeout(initWebSocket, 2000); };
      
          websocket.onmessage = (event) => {
            let data = JSON.parse(event.data);
            document.getElementById("uptime").innerHTML = data.uptime;
            document.getElementById("ram").innerHTML = data.ram;
            document.getElementById("rssi").innerHTML = data.rssi;
          };
        }
      
        window.addEventListener('load', initWebSocket);
      </script>
      
      </body>
      </html>
      )rawliteral";
      
      //--------------------------------
      // Rotina de Inicialização Geral
      //--------------------------------
      
      void setup() 
      {
        // Inicialza a Serial
      
        Serial.begin(115200);
        delay(1000);
      
        // Hello no Terminal
      
        Serial.println("WifiManager Customizado V1.0 Mar/2026");
      
        // Define o Pino do LED Builtin e apaga
      
        pinMode(LED_PIN, OUTPUT);
        digitalWrite(LED_PIN, LOW);
      
        // Define o Pino do Botão de BOOT interno do ESP32
      
        pinMode(RESET_BUTTON_PIN, INPUT);
        attachInterrupt(digitalPinToInterrupt(RESET_BUTTON_PIN), onFactoryReset, RISING);
      
        // Carregar valores salvos
      
        prefs.begin("my-app", false);
        saved_modo  = prefs.getString("modo", "0");
        saved_log   = prefs.getString("log", "0");
        saved_alias = prefs.getString("alias", "mywm");
        prefs.end();
      
        // Mostra os valores salvos
      
        Serial.printf("Saved Modo: %s\n", saved_modo.c_str());
        Serial.printf("Saved Logs: %s\n", saved_log.c_str());  
        Serial.printf("Saved Alias: %s\n", saved_alias.c_str());  
      
        // Prepara os valores salvos para uso
      
        strncpy(modo_operacao, saved_modo.c_str(), sizeof(modo_operacao));
        strncpy(habilitar_log, saved_log.c_str(), sizeof(habilitar_log));
        strncpy(aliasname, saved_alias.c_str(), sizeof(aliasname));
      
      
        // Processa o WifiManager (Menu, campos, timeout, callback) e chama Portal se necessário
      
         processaWifiManager();
      
        // Apaga o LED informativo de WM Ativo
      
        digitalWrite(LED_PIN, LOW);
      
        // Recupera os parâmetros do WM
      
        int modo = atoi(modo_param->getValue());
        int logs = atoi(log_param->getValue()); // 0 ou 1
        saved_alias = alias_name->getValue();
      
        // Seta o DNSNAME
      
        setDNSNAME(saved_alias);
      
        // Mostra os parâmetros no terminal
      
        Serial.printf("Modo: %d\n", modo);
        Serial.printf("Logs: %d\n", logs);
      
        bool logsEnabled = strcmp(log_param->getValue(), "1") == 0;
        Serial.printf("Logs habilitados: %s\n", logsEnabled ? "Sim" : "Não");
      
        // Salvar valores
      
        prefs.begin("my-app", false);
        prefs.putString("modo", modo_param->getValue());
        prefs.putString("log", log_param->getValue());
        prefs.putString("alias", alias_name->getValue());
        prefs.end();
      
        // Incializa o Server
      
        setupServer();
      
      }
      
      //--------------------------------
      // Loop Principal da Aplicação
      //--------------------------------
      
      void loop() 
      {
        // Pega o atual ciclo
      
        unsigned long now = millis();  
      
        // Verifica se pressionou o botão de entar no modo de configuração
      
        if (factoryResetRequested) 
        {
          wm.resetSettings();
          delay(100);
          ESP.restart();
        }
      
        // Verifica se conectado
      
        if (WiFi.status() == WL_CONNECTED) 
        {
          if (now-lastDisplay > INTERVALO)
          {
             digitalWrite(LED_PIN,!digitalRead(LED_PIN));
             lastDisplay = now;
          }   
        } 
        else 
        {
          Serial.println("Reconectando...");
          WiFi.reconnect();
        }
      
        // Cleanup de conexões Web inativas
      
        if (now-lastCleanup > INTERVALO)
        {
           notifyClients();
           ws.cleanupClients();
           lastCleanup = now;
        }
      
      }
      
      //--------------------------------------------
      // Definição da Rotina de Interrupção do Botão
      //--------------------------------------------
      
      void IRAM_ATTR onFactoryReset() 
      {
        factoryResetRequested = true;
      }
      
      //--------------------------------------
      // Definição do Callback do WifiManager
      //--------------------------------------
      
      void configModeCallback(WiFiManager *wm) 
      {
        Serial.println("Entrou no modo de configuração!");
        digitalWrite(LED_PIN, HIGH); // acende o LED informativo fo modo de configuração
        portalFoiUsado = true;
      }
      
      //--------------------------------------
      // Prepara o ambiente do WifiManager
      //--------------------------------------
      
      void preparaWifiManager()
      {
        // Callbacks e timeouts
        wm.setAPCallback(configModeCallback);
        wm.setConfigPortalTimeout(TIMEOUT_PORTAL);
        wm.setConnectTimeout(TIMEOUT_CONEXAO);
      
        // Menu customizado
        //std::vector<const char*> menu = { "wifi", "param", "info", "sep", "restart" };
        //std::vector<const char*> menu = { "wifi", "wifinoscan", "param", "info", "sep", "restart" };
        std::vector<const char *> menu = {"wifi", "info", "sep", "exit"};
      
        #ifdef TEMA_DEFAULT
        #else
          wm.setMenu(menu);
        #endif
      
        // Callback para adicionar página extra quando o servidor estiver pronto
        wm.setWebServerCallback(webServerReadyCallback);
      
        // Título da página
        #ifdef TEMA_DEFAULT
        #else  
        wm.setTitle("<center>⭐ WifiManager ⭐ <u>Customizado</u></center>");
        #endif  
      
        // Tema
        #ifdef TEMA_DEFAULT
        #else    
        aplicaTemaWiFiManager();
        #endif  
      
        // -----------------------------
        // CAMPOS PERSONALIZADOS
        // -----------------------------
      
        // SELECT
        String html_select_str =
          "<label for='modo_sel'>Modo de Operação:</label>"
          "<select id='modo_sel' onchange=\"document.getElementsByName('modo')[0].value=this.value;\">"
            "<option value='0' " + String(saved_modo == "0" ? "selected" : "") + ">Normal</option>"
            "<option value='1' " + String(saved_modo == "1" ? "selected" : "") + ">Econômico</option>"
            "<option value='2' " + String(saved_modo == "2" ? "selected" : "") + ">Turbo</option>"
          "</select><br>";
      
        char* select_buffer = new char[html_select_str.length() + 1];
        strcpy(select_buffer, html_select_str.c_str());
        html_select = new WiFiManagerParameter(select_buffer);
        wm.addParameter(html_select);
      
        // CHECKBOX
        String html_checkbox_str =
          "<label>"
          "<input type='checkbox' id='log_chk' " +
          String(saved_log == "1" ? "checked" : "") +
          " onchange=\"document.getElementsByName('log')[0].value=this.checked?'1':'0';\">"
          " Habilitar Logs"
          "</label><br>";
      
        char* checkbox_buffer = new char[html_checkbox_str.length() + 1];
        strcpy(checkbox_buffer, html_checkbox_str.c_str());
        html_checkbox = new WiFiManagerParameter(checkbox_buffer);
        wm.addParameter(html_checkbox);
      
        // Campos invisíveis
        modo_param = new WiFiManagerParameter("modo", "", saved_modo.c_str(), 10);
        log_param  = new WiFiManagerParameter("log",  "", saved_log.c_str(), 5);
        wm.addParameter(modo_param);
        wm.addParameter(log_param);
      
        // Alias
        alias_name = new WiFiManagerParameter("alias", "Informe o AliasName para o mDNS", saved_alias.c_str(), 30);
        wm.addParameter(alias_name);
      
        // Sincronismo de parâmetros
        wm.setSaveParamsCallback([]() {
          Serial.println("Parâmetros salvos.");
          paramsSalvos = true;
        });
      
        // Sincronismo de WiFi
        wm.setSaveConfigCallback([]() {
          Serial.println("Config WiFi salva.");
          wifiSalvo = true;
        });
      }
      
      //--------------------------------------
      // Processa o WifiManager
      //--------------------------------------
      
      void processaWifiManager() 
      {
          preparaWifiManager();
      
          Serial.println("Iniciando WiFiManager...");
      
          bool conectado = wm.autoConnect(SSID_AP, SENHA_AP);
      
          if (!conectado) 
          {
              Serial.println("Falha ao conectar. Reiniciando...");
              delay(2000);
              ESP.restart();
          }
      
          // Se o portal foi usado, precisamos salvar os parâmetros e reiniciar
      
          if (portalFoiUsado)
          {
              Serial.println("Portal foi usado. Salvando parâmetros...");
      
              // Salva no Preferences
              Preferences prefs;
              prefs.begin("my-app", false);
      
              prefs.putInt("modo",   atoi(modo_param->getValue()));
              prefs.putInt("logs",   atoi(log_param->getValue()));
              prefs.putString("alias", String(alias_name->getValue()));
      
              prefs.end();
      
              // Atualiza as variáveis globais usadas pelo HTML
              strncpy(modo_operacao, modo_param->getValue(), sizeof(modo_operacao));
              strncpy(habilitar_log, log_param->getValue(), sizeof(habilitar_log));
              strncpy(aliasname,     alias_name->getValue(), sizeof(aliasname));
      
              Serial.println("Parâmetros atualizados e salvos com sucesso!");
              Serial.println("Reiniciando para liberar porta 80...");
      
              delay(1000);
              ESP.restart();
          }
      
          // Se chegou aqui, o portal NÃO foi usado → porta 80 está livre
          Serial.println("WiFiManager finalizado. Iniciando AsyncWebServer...");
      }
      
      //--------------------------------------
      // Inicializa o WebServer
      //--------------------------------------
      
      void setupServer()
      {
        ws.onEvent(onWsEvent);
        server.addHandler(&ws);
      
        server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
      
            String page = status_html;
      
            page.replace("%MODO%", saved_modo);
            page.replace("%LOGS%", saved_log == "1" ? "Sim" : "Não");
            page.replace("%MAC%", WiFi.macAddress());
            page.replace("%IP%", WiFi.localIP().toString());
      
            request->send(200, "text/html; charset=UTF-8", page);
        });
      
        server.begin();
        Serial.printf("Servidor Web iniciado. Acesse http://%s ou http://%s.local\n",WiFi.localIP().toString(),saved_alias.c_str());
      }
      
      //---------------------------------
      // Define o HostName como DNS NAME
      //---------------------------------
      
      bool setDNSNAME(String nome)
      {
         WiFi.setHostname(nome.c_str());
         bool ok = MDNS.begin(nome.c_str());
         if (ok) 
         {
            MDNS.addService("http", "tcp", 80);
            MDNS.setInstanceName(nome.c_str()); // Adicionar o nome da instância      
         }
         return ok;
      }
      
      //--------------------------------------
      // Converte millis() para dias.hh:mm:ss
      //--------------------------------------
      
      String formatUptime() 
      {
        //unsigned long ms = millis();
        uint64_t us = esp_timer_get_time();
        unsigned long ms = us / 1000;
      
        unsigned long seconds = ms / 1000;
        unsigned long minutes = seconds / 60;
        unsigned long hours   = minutes / 60;
        unsigned long days    = hours / 24;
      
        seconds %= 60;
        minutes %= 60;
        hours   %= 24;
      
        char buffer[20];
        if (days > 0) sprintf(buffer, "%lu.%02lu:%02lu:%02lu", days, hours, minutes, seconds);
        else sprintf(buffer, "%02lu:%02lu:%02lu", hours, minutes, seconds); 
      
        return String(buffer);
      }
      
      //--------------------------------------
      // Trata Eventos do WebSocket
      //--------------------------------------
      
      void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,
                     AwsEventType type, void *arg, uint8_t *data, size_t len) 
      {
        if (type == WS_EVT_CONNECT) {
          Serial.printf("WebSocket conectado: %u\n", client->id());
        } 
        else if (type == WS_EVT_DISCONNECT) {
          Serial.printf("WebSocket desconectado: %u\n", client->id());
        }
      }
      
      //--------------------------------------
      // Notifica campos variáveis para o Html
      //--------------------------------------
      
      void notifyClients() 
      {
        StaticJsonDocument<200> doc;
      
        doc["uptime"] = formatUptime();
        doc["ram"] = ESP.getFreeHeap();
        doc["rssi"] = WiFi.RSSI();
      
        String json;
        serializeJson(doc, json);
      
        ws.textAll(json);
      }
      
      //--------------------------------------
      // Aplica o Tema para o WifiManager
      //--------------------------------------
      
      void aplicaTemaWiFiManager() 
      {
        // Tema
        #ifdef TEMA_DARK
            wm.setCustomHeadElement(wm_css_dark);
        #endif
      
        #ifdef TEMA_LIGHT
            wm.setCustomHeadElement(wm_css_light);
        #endif
      
        #ifdef TEMA_DEFAULT
            // Não seta nada → usa o tema nativo do WiFiManager
        #endif
      }
      
      //------------------------------------------------
      // Callback WifiManager Ativo e define Handle /info
      //------------------------------------------------
      
      void webServerReadyCallback() 
      {
          Serial.println("Servidor interno do WiFiManager pronto!");
      
          wm.server->on("/info", HTTP_GET, []() {
      
              String page = "<!DOCTYPE html><html lang='pt-br'><head>";
              page += "<meta charset='UTF-8'>";
              page += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
      
              // injeta o mesmo CSS do tema atual
              #if WM_THEME == WM_THEME_DARK
                      page += wm_css_dark;
              #elif WM_THEME == WM_THEME_LIGHT
                      page += wm_css_light;
              #endif
      
              page += "<title>Informações do Sistema</title>";
              page += "</head><body>";
      
              page += "<div class='wrap'>";
              page += "<h1>Informações Avançadas</h1>";
      
              page += "<div class='card'><b>MAC Address:</b> " + WiFi.macAddress() + "</div>";
              page += "<div class='card'><b>IP Local:</b> " + WiFi.localIP().toString() + "</div>";
              page += "<div class='card'><b>RSSI:</b> " + String(WiFi.RSSI()) + " dBm</div>";
              page += "<div class='card'><b>Free RAM:</b> " + String(ESP.getFreeHeap()) + " bytes</div>";
              page += "<div class='card'><b>Uptime:</b> " + formatUptime() + "</div>";
              page += "<div class='card'><b>Versão Firmware:</b> 1.0</div>";
      
              page += "<br><a class='btn' href='/'>Voltar</a>";
              page += "</div>"; // wrap
      
              page += "</body></html>";
      
              wm.server->send(200, "text/html", page);
          });
      }
      
      

      Diagrama do Circuito

      Embora este projeto seja focado principalmente em software — WiFiManager, parâmetros personalizados, temas e AsyncWebServer — o hardware necessário é extremamente simples: apenas o módulo ESP32. Ainda assim, dois elementos físicos desempenham papéis fundamentais no fluxo de operação: os botões BOOT e RESET.

      A figura abaixo destaca esses dois botões no ESP32 de 30 pinos, indicando claramente suas funções no contexto do projeto.

      Figura 11 – Diagrama do Circuito

      Botão BOOT (GPIO 0) — Entrada no Modo de Configuração

      O botão BOOT, conectado internamente ao GPIO 0, é o elemento-chave para permitir que o usuário reentre no modo de configuração mesmo após o dispositivo já estar operando normalmente com o AsyncWebServer.

      Ele funciona como um “atalho físico” para recuperar o dispositivo em campo, sem depender de:

      • WiFi ativo
      • mDNS funcionando
      • IP conhecido
      • acesso ao portal web

      Ao pressionar o BOOT durante o boot (ou por um tempo específico durante a operação), o firmware detecta o nível lógico baixo no GPIO 0 e inicia o WiFiManager em modo AP, permitindo reconfigurar SSID, senha e parâmetros personalizados.

      Esse recurso é essencial para:

      • reinstalar o dispositivo em outra rede
      • corrigir configurações incorretas
      • recuperar o dispositivo quando o roteador mudou
      • facilitar manutenção em campo

      Em produtos reais, esse botão funciona como uma espécie de “modo de segurança” do ESP32.

      Botão RESET — Reinicialização Completa do Sistema

      O botão RESET reinicia o ESP32 por completo, interrompendo qualquer operação em andamento e reiniciando o firmware desde o início.

      No contexto deste projeto, ele é útil para:

      • aplicar novas configurações após o SAVE
      • reiniciar o AsyncWebServer
      • reinicializar a pilha WiFi
      • recuperar o dispositivo após falhas temporárias

      Embora o firmware já execute um reboot automático após o SAVE (como explicado na Seção 9), o botão RESET continua sendo uma ferramenta prática para testes e manutenção.

      Por que destacar esses botões no diagrama?
      Mesmo em um projeto sem componentes externos, esses dois botões:

      • definem o fluxo de operação
      • permitem recuperação do dispositivo
      • facilitam testes e manutenção
      • são essenciais para o comportamento esperado do WiFiManager

      Ao mostrar o ESP32 com os botões destacados, o leitor entende que o hardware é simples, mas o fluxo de uso é bem pensado e robusto — algo fundamental para quem pretende transformar o projeto em um produto real.


      Conclusão

      Criar um portal de configuração realmente amigável para o ESP32 vai além de simplesmente conectar o dispositivo ao WiFi. Envolve pensar na experiência do usuário final, reduzir passos desnecessários e apresentar as informações de forma clara e organizada.

      Neste artigo, mostramos como transformar o WiFiManager em um portal mais completo e profissional, reunindo:

      • configuração de WiFi
      • parâmetros personalizados
      • página de informações
      • temas visuais (Dark, Light e Default)
      • tudo em uma única página, com um único botão SAVE

      Essa abordagem simplifica o uso, evita confusão e deixa o fluxo mais previsível. Também adotamos um reboot automático após o SAVE, garantindo que o servidor web principal do dispositivo inicie de forma confiável — uma prática importante quando combinamos WiFiManager com AsyncWebServer.

      O resultado é um portal claro, prático e adequado tanto para projetos pessoais quanto para produtos comerciais baseados em ESP32. Mais do que uma customização estética, trata-se de uma melhoria real na usabilidade e na confiabilidade do processo de configuração.


      Dailton.Menezes

      3 de abril de 2026

      Os comentários estão desativados.

      Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!

      Eletrogate Robô

      Assine nossa newsletter e
      receba  10% OFF  na sua
      primeira compra!