

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:
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:
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?
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
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:
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:
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:
| Token | Funçã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. |
wm.setMenu({"wifi", "info", "sep", "exit"});
Por que essa escolha?
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
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:
WiFiManagerParameter para criar esses campos.Exemplo básico de parâmetro personalizadoO 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?deviceName) para armazenar o valor.WiFiManagerParameter.addParameter().getValue()."param"), mas isso adiciona navegação extra e mais um botão de SAVE. Como nosso objetivo é simplificar o fluxo, optamos por:"devname").<select> e checkbox usando HTML direto no construtor do WiFiManagerParameter. Uma estratégia muito útil é combinar:<select> ou checkbox visível (que o usuário manipula)// 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:"0" ou "1" via JavaScriptgetValue()String theme = p_themeValue.getValue(); // "default", "dark" ou "light"

Figura 7 - Três tipos de campos: Seleção, Checkbox e Texto
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:
wm.setMenu({"wifi", "info", "sep", "exit"});
Com isso, o item Info aparece no menu e exibe automaticamente: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?/info dentro do próprio servidor do WiFiManager
Figura 8 - Tela Info do WifiManager com o Tema Dark
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:
<head> da página. Isso inclui: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 DefaultO 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?setTitle(). Esse título aparece dentro do <header> do portal e pode conter HTML completo, permitindo:<span style="">)wm.setTitle("<center>⭐ WiFiManager <u>Customizado</u> ⭐</center>");
Esse título será renderizado exatamente como está, incluindo:wm.setTitle( "<center>" "<h2 style='margin:0;color:#4CAF50;'>🌐 Configuração do Dispositivo</h2>" "<small>Portal Customizado</small>" "</center>" );Por que isso é útil?
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áriaO 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 SAVENo portal padrão, cada página tem seu próprio botão de salvar. Isso pode levar o usuário a:
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 principalEm 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.ResultadoO portal final fica:

Figura 9 - Tela com Todas as Definições do Programa
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 internosDurante o portal de configuração, o WiFiManager cria:
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:wm.setWebPortalPort(8080);O portal ficaria acessível em:
http://192.168.4.1:8080Por que não usamos essa alternativa?
stopWebPortal()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: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 |
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:
#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 portasO 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 WiFiO portal de configuração cria um AP temporário e manipula rotas internas. O reboot garante que tudo seja desmontado corretamente.3. Evita comportamentos intermitentesSem reboot, podem ocorrer:wm.setConfigPortalTimeout(300);Define o tempo máximo, em segundos, que o portal de configuração ficará ativo sem interação do usuário.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 é:setConnectTimeout(60), você garante:http://mywm.localIsso 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_BUILTINO LED_BUILTIN é usado como indicador de estado do dispositivo: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:
Figura 10 - Tela da Aplicação AsyncWebServer
//------------------------------------------------------------------------------------------------------------------
// 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);
});
}
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çãoO 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: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:
Neste artigo, vamos explorar como transformar esse portal padrão em uma interface mais completa e amigável, incorporando parâmetros personalizados, página de informações, temas visuais e fluxo simplificado com tudo em uma única página.
Encontre tudo na Loja Eletrogate com frete grátis para compras acima de R$ 200