/
/
Usando o ESP32 como roteador

Usando o ESP32 como roteador

Sumário

Baixe agora a apostila Arduino Iniciante e comece seus projetos

Compartilhe seu conhecimento e receba em dinheiro ou produtos.

Introdução

Este artigo apresenta a construção de um mini-roteador experimental baseado no microcontrolador ESP32. O dispositivo é capaz de criar uma rede local sem fio, conectar-se a uma rede externa e atuar como gateway de acesso. Além disso, o sistema oferece uma interface simples para visualização de métricas como dispositivos conectados, informações de hardware e estado da rede.

O objetivo do projeto não é substituir soluções comerciais de roteamento, mas sim oferecer uma plataforma didática de baixo custo para experimentação prática com conceitos fundamentais de redes de computadores. Dessa forma, estudantes, entusiastas e desenvolvedores podem explorar aspectos do funcionamento de redes reais utilizando um hardware mínimo e amplamente disponível.

Fundamentos de Redes IP

O mini-roteador desenvolvido neste projeto permite observar, de forma prática, alguns dos conceitos fundamentais presentes nas redes IP, como a organização em camadas, o endereçamento IP e o mecanismo de tradução de endereços (Network Address Translation - NAT).

Esses conceitos formam a base para compreender como um dispositivo intermediário, como um roteador, é capaz de conectar uma rede local a uma rede externa e permitir a comunicação entre múltiplos equipamentos.

Modelo de camadas

A comunicação em redes de computadores é tradicionalmente descrita por meio de modelos em camadas, que organizam as diferentes responsabilidades da transmissão de dados em níveis bem definidos.

Um dos modelos mais conhecidos é o modelo OSI (Open Systems Interconnection), que descreve a comunicação em sete camadas responsáveis por diferentes aspectos da transmissão de dados. Embora seja amplamente utilizado como referência conceitual para o estudo de redes, muitas implementações reais utilizam arquiteturas derivadas do modelo TCP/IP, que possui uma organização mais compacta.

Em sistemas embarcados como o ESP32, por exemplo, a pilha de protocolos é implementada pela biblioteca lwIP, baseada nesse modelo. Apesar das diferenças de organização entre os dois modelos, as camadas inferiores responsáveis pela transmissão, enlace e encaminhamento de pacotes desempenham funções essencialmente equivalentes.

Como este projeto se concentra nos mecanismos básicos de comunicação e roteamento, é suficiente considerar as três primeiras camadas responsáveis pela transmissão e encaminhamento dos dados na rede:

  • Camada física, responsável pela transmissão de sinais através do meio de comunicação.
  • Camada de enlace, responsável pela comunicação entre dispositivos dentro de uma mesma rede local.
  • Camada de rede, responsável pelo endereçamento lógico e pelo encaminhamento de pacotes entre diferentes redes.

Em cada uma dessas camadas operam protocolos de comunicação, que definem regras para a troca de dados entre dispositivos, especificando aspectos como formato das informações transmitidas, mecanismos de identificação e procedimentos para envio e recebimento de mensagens.

Endereçamento em redes IP

Em redes baseadas no protocolo IP, cada dispositivo conectado recebe um endereço IP, que funciona como um identificador lógico utilizado para localizar e encaminhar dados entre diferentes pontos da rede. Em ambientes como redes domésticas ou sistemas embarcados conectados, esses dispositivos normalmente fazem parte de uma rede local (LAN), compartilhando uma mesma faixa de endereços IP para possibilitar a comunicação entre si.

No sistema apresentado neste projeto, os dispositivos conectados ao ponto de acesso do ESP32 recebem endereços no intervalo 192.168.4.x, que corresponde à faixa padrão utilizada pelo modo Access Point (softAP) da biblioteca WiFi do ESP32.
Caso seja desejado utilizar outra faixa de rede, é necessário configurá-la explicitamente no código através da função WiFi.softAPConfig().

IPAddress local_ip(10,0,0,1);
IPAddress gateway(10,0,0,1);
IPAddress subnet(255,255,255,0);

WiFi.softAPConfig(local_ip, gateway, subnet);

WiFi.softAP(ap_ssid.c_str(), ap_pass.c_str());


// e depois habilitar o NAT usando o IP escolhido manualmente:

ip_napt_enable(IPAddress(10,0,0,1), 1);

Quando um dispositivo precisa se comunicar com um destino fora da rede local, o tráfego é encaminhado para um gateway, que atua como ponto de saída. Esse dispositivo intermediário é responsável por encaminhar os pacotes para outras redes, permitindo a comunicação com a rede externa (WAN) ou com a internet.

Network Address Translation (NAT)

O gateway substitui o endereço IP de origem do dispositivo pelo seu próprio endereço na rede externa. Esse processo é chamado de tradução de endereços. Quando a resposta retorna, é realizada a operação inversa, encaminhando o pacote de volta ao dispositivo correto dentro da rede local.

Esse mecanismo chamado Network Address Translation (NAT) permite que múltiplos dispositivos de uma rede local compartilhem uma única conexão externa, mantendo seus endereços internos isolados da rede pública.

Hardware e Firmware

O ESP32 possui recursos que permitem utilizá-lo como um elemento ativo na infraestrutura de rede, executando diretamente no firmware funções normalmente associadas a equipamentos dedicados, como repetidores ou roteadores. Essa capacidade decorre da combinação entre seu rádio Wi-Fi integrado e a pilha de protocolos TCP/IP disponível no ambiente de desenvolvimento.

Nesta seção são apresentados os aspectos do hardware e de firmware que permitem utilizar o ESP32 como base para a implementação de um nó de rede programável.

Limitações do hardware

Apesar da flexibilidade oferecida pelo ESP32, algumas características da plataforma impõem limitações quando ele é utilizado para funções de rede.

Uma das principais restrições é a presença de apenas um rádio Wi-Fi. Quando o dispositivo opera simultaneamente como estação (STA) e ponto de acesso (AP), as duas interfaces compartilham o mesmo hardware de rádio. Isso significa que o ESP32 alterna rapidamente entre os dois papéis utilizando multiplexação temporal do canal (time slicing), o que reduz o throughput disponível.

Outra questão relevante é a quantidade restrita de memória RAM disponível no microcontrolador. Como estruturas de rede — como tabelas de conexões, buffers de pacotes e estados de NAT — precisam ser mantidas em memória, o número de conexões simultâneas que podem ser tratadas pelo sistema é naturalmente limitado.

Ambiente de desenvolvimento e firmware

Além das características de hardware, o ambiente de desenvolvimento do ESP32 também influencia a forma como funcionalidades de rede podem ser implementadas.

Uma das formas mais comuns de programar o microcontrolador é através do Arduino Core para ESP32, que fornece uma camada de abstração simples sobre o sistema operacional e as bibliotecas de rede. Essa abordagem facilita o desenvolvimento rápido de protótipos e aplicações didáticas, permitindo configurar interfaces Wi-Fi e utilizar sockets de rede com poucas linhas de código.

Por outro lado, aplicações mais complexas podem utilizar diretamente o ESP-IDF (Espressif IoT Development Framework), que oferece acesso mais completo aos recursos do sistema, incluindo controle detalhado da pilha de rede, gerenciamento de tarefas no sistema operacional FreeRTOS e integração direta com a biblioteca lwIP.

Neste projeto optou-se pela utilização do Arduino Core, principalmente pela simplicidade de configuração e pelo caráter didático da implementação, permitindo explorar os mecanismos fundamentais de funcionamento de uma rede IP como criação de interfaces, encaminhamento de pacotes e tradução de endereços.

O mesmo ambiente ainda permite implementar arquiteturas de rede mais elaboradas. Um exemplo é a construção de redes mesh utilizando módulos ESP, como demonstrado no artigo "Rede Mesh com Módulos ESP-8266, ESP-32 e ESP-01" deste blog.

Por outro lado, funcionalidades mais avançadas de infraestrutura de rede como PPPoE, roteamento entre múltiplas interfaces, VPNs ou mecanismos mais sofisticados de firewall, normalmente exigem acesso mais direto às camadas internas do sistema e, portanto, são desenvolvidas utilizando o ESP-IDF.

Diversos projetos open source exploram essas capacidades de forma mais profunda. Entre eles destacam-se os repositórios mantidos por Martin Ger, que implementam funcionalidades avançadas da pilha de rede do ESP32, disponíveis em:
https://github.com/martin-ger

Arquitetura do Sistema

Para compreender o projeto tanto do ponto de vista do software embarcado quanto dos mecanismos de rede, analisaremos dois aspectos principais: a arquitetura de firmware, responsável pela organização do código e pela interação com o hardware do microcontrolador, e a arquitetura de rede, responsável pela implementação das funções de roteamento, pelo acesso à rede externa e pelo atendimento aos clientes da rede local.

Arquitetura do Firmware

O firmware foi organizado de forma modular, com diferentes componentes responsáveis por funções específicas do roteador. Essa organização permite separar as responsabilidades em unidades independentes, facilitando tanto a manutenção do código quanto a compreensão do funcionamento do dispositivo.

No início do arquivo são incluídos os módulos principais do projeto:

#include "router.h" // configuração do roteamento e integração entre os módulos.
#include "net_analyzer.h" // coleta de informações sobre o estado da rede.
#include "hw_analyzer.h" // monitoramento de recursos do hardware do ESP32.
#include "web_server.h" // interface web de configuração e monitoramento.
Esses módulos são instanciados como objetos globais, permitindo que os diferentes subsistemas compartilhem informações e cooperem na execução do sistema.
Router router;
NetAnalyzer netAnalyzer;
HwAnalyzer hwAnalyzer;
WebServerModule web;

Durante a função setup(), o firmware prepara os subsistemas necessários para a operação do dispositivo. Dependendo do estado de configuração do dispositivo, o firmware pode iniciar no modo de provisionamento ou modo roteador.

No modo de provisionamento, o dispositivo cria um Access Point e inicia um servidor web de configuração. Por meio dessa interface, o usuário pode informar as credenciais da rede externa (WAN) e também definir os parâmetros da rede local criada pelo roteador (AP), como o SSID e a senha da rede sem fio.

Essas informações são armazenadas na memória não volátil do ESP32 utilizando o recurso Preferences, uma interface de armazenamento persistente baseada na memória flash do dispositivo. Dessa forma, as configurações permanecem salvas mesmo após reinicializações ou perda de energia, permitindo que o roteador reconecte automaticamente à rede externa e recrie sua rede local utilizando os parâmetros definidos pelo usuário.

Após o provisionamento, o dispositivo reinicia e passa a operar no modo roteador. Nesse modo, o ESP32 se conecta à rede externa utilizando o modo Station e simultaneamente cria um Access Point para os dispositivos clientes. O firmware então ativa o mecanismo de Network Address Translation (NAT), permitindo que os dispositivos conectados à rede local utilizem o ESP32 como gateway para acessar a rede externa.

Após a inicialização, o dispositivo entra no loop() principal, que atualiza periodicamente o estado da rede, coleta métricas de hardware e mantém o servidor web responsável pela interface de monitoramento. É empregado o mecanismo de temporização baseado em millis(), que permite executar tarefas em intervalos definidos sem interromper o fluxo principal do programa, esse mecanismo mantém a responsividade do servidor web e evita atrasos no processamento de eventos de rede, sem introduzir sobrecarga significativa.

Arquitetura de Rede

Do ponto de vista da rede, o dispositivo atua como um roteador simples entre duas interfaces Wi-Fi. A comunicação entre essas duas redes ocorre por meio de tradução de endereços e portas (Network Address Port Translation – NAPT), uma forma comum de NAT utilizada em roteadores domésticos no qual além do endereço IP, as portas de comunicação também são utilizadas para multiplexar múltiplas conexões através de um único endereço externo.

O fluxo básico de comunicação ocorre da seguinte forma:

Conexão do cliente à rede local: Um dispositivo cliente conecta-se ao Access Point criado pelo ESP32. Durante esse processo são executados os mecanismos usuais de uma rede Wi-Fi, incluindo autenticação, associação e obtenção de endereço IP por meio de DHCP.

Descoberta de endereços na rede local: Quando o cliente deseja enviar um pacote para um destino externo, ele identifica o ESP32 como gateway padrão da rede. Antes de transmitir o pacote, o cliente utiliza o protocolo ARP para descobrir o endereço MAC correspondente ao endereço IP do gateway.

  • Envio do pacote para o roteador: Após resolver o endereço MAC do gateway, o cliente envia o pacote IP para o ESP32 através da interface do ponto de acesso.

  • Tradução de endereços (NAT): Ao receber o pacote, o firmware aplica a tradução de endereços. O endereço IP privado do cliente é substituído pelo endereço IP da interface externa do ESP32, e uma entrada é criada na tabela de tradução para permitir o encaminhamento correto das respostas.

  • Encaminhamento para a rede externa: O pacote traduzido é então encaminhado pela interface Station do ESP32 em direção ao gateway da rede externa.

  • Recebimento da resposta: Quando a resposta retorna da rede externa, ela é enviada ao endereço IP do ESP32. O roteador consulta a tabela de NAT para identificar qual cliente interno originou a conexão.

  • Entrega ao cliente: O firmware desfaz a tradução de endereços, restaura o endereço IP original do cliente e encaminha o pacote para a rede local através da interface Access Point.

Esse processo permite que múltiplos dispositivos da rede local compartilhem um único endereço IP externo, reproduzindo o comportamento básico de um roteador doméstico e permitindo observar, em um ambiente controlado, diversos mecanismos fundamentais das redes IP.


Durante os testes realizados com dispositivos Android, observou-se que em alguns dispositivos o gateway padrão não foi configurado automaticamente via DHCP, sendo necessário defini-lo manualmente nas configurações de rede do cliente.

Interface Web e Monitoramento

Além das funções de roteamento, o sistema também disponibiliza uma interface web de configuração e monitoramento, permitindo que o usuário acompanhe o estado do dispositivo e realize a configuração inicial da rede externa.

Essa interface é fornecida por um servidor HTTP executado diretamente no ESP32 e pode ser acessada por qualquer dispositivo conectado à rede criada pelo microcontrolador.

Dependendo do estado de configuração do dispositivo, o sistema opera no modo de provisionamento, utilizado para a configuração inicial e reconfigurações ou no modo roteador, no qual o dispositivo passa a operar como gateway da rede local e disponibiliza um painel de monitoramento do sistema.

Modo de provisionamento

Quando o dispositivo opera em modo de provisionamento, a interface apresenta um formulário simples onde podem ser informadas tanto as credenciais da rede externa (WAN), utilizada para acesso à internet, quanto os parâmetros da rede local (Access Point) criada pelo próprio roteador.

O formulário inclui os seguintes campos:

  • SSID da rede externa (WAN)
  • Senha de acesso
  • SSID da rede local (LAN)
  • Senha de acesso

Modo roteador

Durante o modo roteador, é criado um dashboard que pode ser acessado por qualquer dispositivo conectado à rede local, utilizando um navegador web. Ao abrir a página inicial do sistema, o usuário encontra uma interface simples composta por um botão de reconfiguração da rede e uma área dinâmica onde são exibidas as informações de estado do dispositivo.

Essas informações são atualizadas automaticamente a cada poucos segundos, sem a necessidade de recarregar a página no navegador. Isso é feito por meio de pequenas consultas periódicas que a própria página envia ao servidor HTTP do ESP32.

No código da interface, um pequeno script em JavaScript executa regularmente uma função responsável por solicitar o estado atual do roteador. Essa função envia uma requisição ao endereço /status, que atualiza a página e o conteúdo recebido é então inserido na área principal do dashboard, substituindo os dados exibidos anteriormente.

function updateStatus(){

fetch("/status")
.then(response => response.text())
.then(data => {
    document.getElementById("status").innerHTML = data;
});


}

setInterval(updateStatus,2000);

Entre os elementos apresentados no dashboard destacam-se:

  • Dispositivos conectados: Clientes atualmente conectados à rede local. Para cada dispositivo são exibidas informações como:

    • endereço MAC do cliente
    • intensidade do sinal (RSSI)
  • Estado da conexão com a internet: Informações relacionadas à conexão externa utilizada pelo roteador.

    • IP público utilizado na conexão com a rede externa
    • Hora atual, sincronizada automaticamente com servidores de tempo na internet por meio do protocolo NTP (Network Time Protocol).
  • Informações de sistema: Métricas do próprio dispositivo, permitindo acompanhar o funcionamento do hardware do ESP32. Entre essas informações estão:

    • Endereço MAC do roteador
    • Tempo de atividade (uptime) do sistema
    • Temperatura interna do microcontrolador
    • Memória heap disponível
  • Reconfiguração da rede: O dashboard também inclui um botão que permite voltar para o modo de provisionamento.

Implementação prática

O sistema apresentado neste artigo não exige circuitos adicionais nem montagem eletrônica específica. Toda a funcionalidade é implementada diretamente no ESP32, utilizando apenas os recursos de rede já disponíveis no microcontrolador, disponível em:  https://www.eletrogate.com/modulo-wifi-esp32-bluetooth-30-pinos

A demonstração prática do sistema consiste essencialmente na observação do comportamento da rede e das interfaces de configuração, que já foram apresentadas ao longo do artigo por meio de diagramas de arquitetura e capturas de tela das interfaces web.

Assim, a implementação pode ser reproduzida diretamente a partir do código do firmware disponibilizado no artigo.

Códigos comentados

firmware.ino

#include <Arduino.h>
#include <WiFi.h>

#include "router.h"
#include "net_analyzer.h"
#include "hw_analyzer.h"
#include "web_server.h"

// =====================================================
//  Instâncias globais dos módulos do sistema
// =====================================================

Router router;
NetAnalyzer netAnalyzer;
HwAnalyzer hwAnalyzer;
WebServerModule web;

// =====================================================
//  Controle de temporização
// =====================================================

unsigned long lastUpdate = 0;
const unsigned long updateInterval = 5000; // 5 segundos

// =====================================================
//  SETUP
// =====================================================

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

    netAnalyzer.begin(&router);
    hwAnalyzer.begin();

    router.begin(&web, &netAnalyzer, &hwAnalyzer);

    printBootReport(&netAnalyzer);
}

// =====================================================
//  LOOP PRINCIPAL
// =====================================================

void loop()
{
    // Mantém servidor web sempre responsivo
    web.update();

    unsigned long now = millis();

    // Atualizações periódicas
    if (now - lastUpdate >= updateInterval)
    {
        lastUpdate = now;

        netAnalyzer.update();
        hwAnalyzer.update();
    }
}

// =====================================================
//  FUNÇÃO DE DEBUG DO BOOT
// =====================================================

void printBootReport(NetAnalyzer* net)
{
    Serial.println("\n====================");
    Serial.println(" ESP32 LAB NODE BOOT ");
    Serial.println("====================\n");

    Serial.print("STA IP: ");
    Serial.println(WiFi.localIP());

    Serial.print("GATEWAY: ");
    Serial.println(WiFi.gatewayIP());

    Serial.print("DNS: ");
    Serial.println(WiFi.dnsIP());

    Serial.print("\nAP IP: ");
    Serial.println(WiFi.softAPIP());

    Serial.print("\nPUBLIC IP: ");
    Serial.println(net->getPublicIP());

    Serial.print("\nTIME (NTP): ");
    Serial.println(net->getTime());

    Serial.println("\n====================\n");
}

router.cpp
#include "router.h"
#include "web_server.h"
#include "net_analyzer.h"
#include "hw_analyzer.h"

#include <Arduino.h>
#include <WiFi.h>
#include <Preferences.h>

extern "C" {
#include "lwip/lwip_napt.h"
#include "lwip/tcpip.h"
}

static NetAnalyzer* netPtr;
static HwAnalyzer* hwPtr;

// =====================================================
// POINTER PARA WEBSERVER
// =====================================================

WebServerModule* webPtr;

// =====================================================
// CONFIG
// =====================================================

// valores padrão caso não exista configuração salva
static String ap_ssid = "ESP32_ROUTER";
static String ap_pass = "12345678";

// NVS storage
Preferences routerPrefs;

// estado NAT
static bool nat_enabled = false;

// credenciais carregadas
static String sta_ssid;
static String sta_pass;

// =====================================================
// NAT CALLBACK
// =====================================================

static void enable_nat(void *arg)
{
    ip_napt_enable(IPAddress(192,168,4,1), 1);
    Serial.println("NAT habilitado");
}

// =====================================================
// STORAGE
// =====================================================

bool Router::configExists()
{
    routerPrefs.begin("router", true);

    sta_ssid = routerPrefs.getString("ssid", "");
    sta_pass = routerPrefs.getString("pass", "");

    ap_ssid = routerPrefs.getString("ap_ssid", "ESP32_ROUTER");
    ap_pass = routerPrefs.getString("ap_pass", "12345678");

    routerPrefs.end();

    return sta_ssid.length() > 0;
}

// =====================================================
// AP
// =====================================================

void Router::startAP()
{
    Serial.println("Iniciando AP...");

    WiFi.mode(WIFI_AP_STA);

    WiFi.softAP(ap_ssid.c_str(), ap_pass.c_str());

    Serial.print("AP SSID: ");
    Serial.println(ap_ssid);

    Serial.print("AP IP: ");
    Serial.println(WiFi.softAPIP());
}

// =====================================================
// STA
// =====================================================

bool Router::connectSTA()
{
    Serial.print("Conectando STA -> ");
    Serial.println(sta_ssid);

    WiFi.begin(sta_ssid.c_str(), sta_pass.c_str());

    int retries = 0;

    while (WiFi.status() != WL_CONNECTED && retries < 20)
    {
        delay(500);
        Serial.print(".");
        retries++;
    }

    if (WiFi.status() == WL_CONNECTED)
    {
        Serial.println("\nSTA conectado");

        Serial.print("IP WAN: ");
        Serial.println(WiFi.localIP());

        return true;
    }

    Serial.println("\nFalha STA");

    return false;
}

// =====================================================
// NAT
// =====================================================

void Router::enableNAT()
{
    if (!nat_enabled)
    {
        delay(2000);
        tcpip_callback(enable_nat, NULL);
        nat_enabled = true;
    }
}

// =====================================================
// PROVISION PORTAL
// =====================================================

void Router::startProvisionPortal()
{
    Serial.println("Modo provisionamento ativo");
    Serial.println("Conecte ao AP e configure o WiFi");

    if (webPtr)
    {
        webPtr->beginProvision();
    }
}

// =====================================================
// ROUTER MONITOR
// =====================================================

void Router::startRouterMonitor()
{
    Serial.println("Router monitor iniciado");

    if (webPtr)
    {
        webPtr->beginRouter(netPtr, hwPtr);
    }
}

// =====================================================
// BEGIN
// =====================================================

void Router::begin(WebServerModule* web, NetAnalyzer* net, HwAnalyzer* hw)
{
    webPtr = web;
    netPtr = net;
    hwPtr = hw;

    Serial.println("Inicializando router");

    WiFi.mode(WIFI_AP_STA);  //  inicializa stack
    delay(100);

    bool hasConfig = configExists();

    startAP();

    if (!hasConfig)
    {
        startProvisionPortal();
        return;
    }

    if (connectSTA())
    {
        enableNAT();
        startRouterMonitor();
    }
    else
    {
        startProvisionPortal();
    }
}

// =====================================================
// CLIENTES
// =====================================================

int Router::getConnectedClients()
{
    return WiFi.softAPgetStationNum();
}
router.h
#ifndef ROUTER_H
#define ROUTER_H

class WebServerModule;
class NetAnalyzer;
class HwAnalyzer;

class Router
{
public:

    void begin(WebServerModule* web, NetAnalyzer* net, HwAnalyzer* hw);

    int getConnectedClients();

private:

    bool configExists();

    void startAP();
    bool connectSTA();
    void enableNAT();

    void startProvisionPortal();
    void startRouterMonitor();
};

#endif

hw_analyzer.cpp
#include "hw_analyzer.h"
#include "system.h"

#include <Arduino.h>
#include <WiFi.h>

// =====================================================
//  HW ANALYZER
// =====================================================

void HwAnalyzer::begin()
{}

// =====================================================
//  Atualiza métricas de hardware
// =====================================================

void HwAnalyzer::update()
{
    // =================================================
    // Captura o MAC do AP apenas quando estiver pronto
    // =================================================

    if (!macCaptured)
    {
        String mac = WiFi.softAPmacAddress();

        if (mac != "00:00:00:00:00:00")
        {
            macAddress = mac;
            macCaptured = true;
        }
    }

    // =================================================
    // Leitura da temperatura interna
    // =================================================

    temperature = temperatureRead();

    // =================================================
    // Heap livre
    // =================================================

    heap = ESP.getFreeHeap();

    // =================================================
    // Uptime (segundos desde boot)
    // =================================================

    uptimeSeconds = millis() / 1000;

#if SERIAL_MONITOR_ENABLED

    LOGLN("---- HARDWARE ----");

    LOG("MAC Router: ");
    LOGLN(macAddress);

    LOG("Temperatura: ");
    LOG(temperature);
    LOGLN(" C");

    LOG("Heap livre: ");
    LOG(heap / 1024);
    LOGLN(" KB");

    LOG("Uptime: ");
    LOGLN(getUptimeString());

#endif
}

// =====================================================
//  GETTERS
// =====================================================

float HwAnalyzer::getTemperature()
{
    return temperature;
}

uint32_t HwAnalyzer::getHeap()
{
    return heap;
}

String HwAnalyzer::getMac()
{
    return macAddress;
}

uint64_t HwAnalyzer::getUptime()
{
    return uptimeSeconds;
}

// =====================================================
//  Uptime formatado (d h m s)
// =====================================================

String HwAnalyzer::getUptimeString()
{
    uint64_t s = uptimeSeconds;

    uint32_t days = s / 86400;
    s %= 86400;

    uint32_t hours = s / 3600;
    s %= 3600;

    uint32_t minutes = s / 60;
    uint32_t seconds = s % 60;

    String out = "";

    if (days > 0)
    {
        out += String(days) + "d ";
    }

    if (hours > 0 || days > 0)
    {
        out += String(hours) + "h ";
    }

    if (minutes > 0 || hours > 0 || days > 0)
    {
        out += String(minutes) + "m ";
    }

    out += String(seconds) + "s";

    return out;
}
hw_analyzer.cpp
#ifndef HW_ANALYZER_H
#define HW_ANALYZER_H

#include <Arduino.h>

class HwAnalyzer
{
public:

    void begin();
    void update();

    float getTemperature();
    uint32_t getHeap();
    String getMac();

    // ===== UPTIME =====
    uint64_t getUptime();
    String getUptimeString();

private:

    float temperature = 0;
    uint32_t heap = 0;

    String macAddress = "";

    bool macCaptured = false;

    // ===== UPTIME =====
    uint64_t uptimeSeconds = 0;
};

#endif

net_analyzer.cpp
#include "net_analyzer.h"
#include "router.h"
#include "system.h"

#include <WiFi.h>
#include <HTTPClient.h>
#include <time.h>

extern "C" {
#include "esp_wifi.h"
}

// =====================================================
// TIME INIT (NTP)
// =====================================================

static void initTime()
{
    configTime(-3 * 3600, 0, "pool.ntp.org");
}

// =====================================================
// BEGIN
// =====================================================

void NetAnalyzer::begin(Router* r)
{
    router = r;
    initTime();
}

// =====================================================
// UPDATE
// =====================================================

void NetAnalyzer::update()
{
    // =================================================
    // WIFI CLIENTS (MAC + RSSI)
    // =================================================

    wifi_sta_list_t sta_list;

    clientCount = 0;

    if (esp_wifi_ap_get_sta_list(&sta_list) == ESP_OK)
    {
        clientCount = sta_list.num;

        for (int i = 0; i < clientCount && i < 10; i++)
        {
            wifi_sta_info_t station = sta_list.sta[i];

            memcpy(clients[i].mac, station.mac, 6);
            clients[i].rssi = station.rssi;
        }
    }

    // =================================================
    // PUBLIC IP
    // =================================================

    static unsigned long lastIPUpdate = 0;

    if (millis() - lastIPUpdate > 60000 || publicIP == "")
    {
        lastIPUpdate = millis();

        HTTPClient http;

        http.begin("http://api.ipify.org");

        int code = http.GET();

        if (code == 200)
        {
            publicIP = http.getString();
        }

        http.end();
    }

#if SERIAL_MONITOR_ENABLED

    LOGLN("---- NET ANALYZER ----");

    LOG("Clientes conectados: ");
    LOGLN(clientCount);

    for (int i = 0; i < clientCount; i++)
    {
        char macStr[18];

        sprintf(macStr,
        "%02X:%02X:%02X:%02X:%02X:%02X",
        clients[i].mac[0],
        clients[i].mac[1],
        clients[i].mac[2],
        clients[i].mac[3],
        clients[i].mac[4],
        clients[i].mac[5]);

        LOG("MAC: ");
        LOGLN(macStr);

        LOG("RSSI: ");
        LOGLN(clients[i].rssi);

        LOGLN("");
    }

    LOG("IP publico: ");
    LOGLN(publicIP);

    LOG("Hora: ");
    LOGLN(getTime());

#endif
}

// =====================================================
// CLIENT API
// =====================================================

int NetAnalyzer::getClientCount()
{
    return clientCount;
}

ClientInfo* NetAnalyzer::getClients()
{
    return clients;
}

// =====================================================
// PUBLIC IP
// =====================================================

String NetAnalyzer::getPublicIP()
{
    return publicIP;
}

// =====================================================
// TIME
// =====================================================

String NetAnalyzer::getTime()
{
    struct tm timeinfo;

    if (!getLocalTime(&timeinfo))
        return "sync...";

    char buffer[64];

    strftime(buffer, sizeof(buffer), "%d/%m/%Y %H:%M:%S", &timeinfo);

    return String(buffer);
}
net_analyzer.h
#ifndef NET_ANALYZER_H
#define NET_ANALYZER_H

#include <Arduino.h>

class Router;

struct ClientInfo
{
    uint8_t mac[6];
    int8_t rssi;
};

class NetAnalyzer {

public:

    void begin(Router* r);
    void update();

    int getClientCount();
    ClientInfo* getClients();

    String getPublicIP();
    String getTime();

private:

    Router* router;

    ClientInfo clients[10];
    int clientCount = 0;

    String publicIP;
    bool timeInitialized = false;

};

#endif

web_server.cpp
#include "web_server.h"
#include "net_analyzer.h"
#include "hw_analyzer.h"

#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>

// =====================================================
// SERVER
// =====================================================

WebServer server(80);

// =====================================================
// MODULE POINTERS
// =====================================================

NetAnalyzer* netPtr;
HwAnalyzer* hwPtr;

Preferences webPrefs;

// =====================================================
// PROVISION PAGE
// =====================================================

void handleProvision()
{
    String html = R"rawliteral(

<html>
<head>
<title>ESP32 Router Setup</title>
</head>

<body>

<h1>Reconfigurar roteador</h1>

<form action="/connect" method="POST">

<b>Rede externa (WAN)</b><br><br>

SSID:<br>
<input name="ssid"><br><br>

Senha:<br>
<input name="pass" type="password"><br><br>

<hr>

<b>Rede do roteador (AP)</b><br><br>

SSID do roteador:<br>
<input name="ap_ssid" value="ESP32_ROUTER"><br><br>

Senha do roteador:<br>
<input name="ap_pass" type="password" value="12345678"><br><br>

<input type="submit" value="Salvar e Conectar">

</form>

</body>
</html>

)rawliteral";

    server.send(200,"text/html",html);
}

// =====================================================
// RECEIVE WIFI CONFIG
// =====================================================

void handleConnect()
{
    String ssid = server.arg("ssid");
    String pass = server.arg("pass");

    String ap_ssid = server.arg("ap_ssid");
    String ap_pass = server.arg("ap_pass");

    // valida senha do AP (mínimo 8 caracteres)
    if(ap_pass.length() < 8)
{
    server.send(200,"text/html",
    "<html>"
    "<head>"
    "<meta http-equiv='refresh' content='3;url=/' />"
    "</head>"
    "<body>"
    "<h3>Senha precisa ter pelo menos 8 caracteres</h3>"
    "<p>Voltando para config...</p>"
    "</body>"
    "</html>");
    return;
}

    webPrefs.begin("router", false);

    webPrefs.putString("ssid", ssid);
    webPrefs.putString("pass", pass);

    webPrefs.putString("ap_ssid", ap_ssid);
    webPrefs.putString("ap_pass", ap_pass);

    webPrefs.end();

    server.send(200,"text/html","Configuracao salva! Reiniciando...");

    delay(1000);
    ESP.restart();
}

// =====================================================
// RESET CONFIG
// =====================================================

void handleReconfigure()
{
    webPrefs.begin("router", false);

    webPrefs.remove("ssid");
    webPrefs.remove("pass");

    webPrefs.end();

    server.send(200,"text/html","Configuracao de Internet apagada. Reiniciando...");

    delay(1000);
    ESP.restart();
}

// =====================================================
// ROOT PAGE (ROUTER UI)
// =====================================================

void handleRoot()
{
    String html = R"rawliteral(

<html>
<head>

<title>ESP32 Router</title>

<script>

function updateStatus(){

fetch("/status")
.then(response => response.text())
.then(data => {

document.getElementById("status").innerHTML = data;

});

}

setInterval(updateStatus,2000);

function reconfigure(){

fetch("/reconfigure");

}

</script>

</head>

<body onload="updateStatus()">

<h1>ESP32 Router</h1>

<button onclick="reconfigure()">
Reconfigurar Roteador
</button>

<hr>

<div id="status">
carregando...
</div>

</body>
</html>

)rawliteral";

    server.send(200,"text/html",html);
}

// =====================================================
// STATUS PAGE
// =====================================================

void handleStatus()
{
    String html;

    int count = netPtr->getClientCount();
    ClientInfo* clients = netPtr->getClients();

    html += "<h2>Clientes conectados: ";
    html += count;
    html += "</h2>";

    for (int i = 0; i < count; i++)
    {
        html += "<b>Cliente ";
        html += i + 1;
        html += "</b><br>";

        char macStr[18];

        sprintf(macStr,
            "%02X:%02X:%02X:%02X:%02X:%02X",
            clients[i].mac[0],
            clients[i].mac[1],
            clients[i].mac[2],
            clients[i].mac[3],
            clients[i].mac[4],
            clients[i].mac[5]
        );

        html += "MAC: ";
        html += macStr;
        html += "<br>";

        html += "RSSI: ";
        html += clients[i].rssi;
        html += "<br><br>";
    }

    html += "<h2>Internet</h2>";
    html += "IP publico: ";
    html += netPtr->getPublicIP();
    html += "<br>";
    html += "Data / Hora: ";
    html += netPtr->getTime();
    html += "<br>";

    html += "<h2>Status do Roteador</h2>";

    html += "MAC Router: ";
    html += hwPtr->getMac();
    html += "<br>";

    html += "Uptime: ";
    html += hwPtr->getUptimeString();
    html += "<br>";

    html += "Temperatura: ";
    html += hwPtr->getTemperature();
    html += " C<br>";

    html += "Heap livre: ";
    html += hwPtr->getHeap() / 1024;
    html += " KB";

    server.send(200,"text/html",html);
}

// =====================================================
// PROVISION MODE
// =====================================================

void WebServerModule::beginProvision()
{
    server.on("/", handleProvision);
    server.on("/connect", HTTP_POST, handleConnect);

    server.begin();
}

// =====================================================
// ROUTER MODE
// =====================================================

void WebServerModule::beginRouter(NetAnalyzer* net, HwAnalyzer* hw)
{
    netPtr = net;
    hwPtr = hw;

    server.on("/", handleRoot);
    server.on("/status", handleStatus);
    server.on("/reconfigure", handleReconfigure);

    server.begin();
}

// =====================================================
// LOOP
// =====================================================

void WebServerModule::update()
{
    server.handleClient();
}
web_server.h
#ifndef WEB_SERVER_MODULE_H
#define WEB_SERVER_MODULE_H

class NetAnalyzer;
class HwAnalyzer;

class WebServerModule
{
public:

    void beginProvision();
    void beginRouter(NetAnalyzer* net, HwAnalyzer* hw);

    void update();
};

#endif

system.h
#ifndef SYSTEM_H
#define SYSTEM_H

#include <Arduino.h>

// =====================================================
//  CONFIGURAÇÃO GLOBAL
// =====================================================

#define SERIAL_MONITOR_ENABLED 1

// =====================================================
//  DEBUG MACROS
// =====================================================

#if SERIAL_MONITOR_ENABLED
  #define LOG(x)   Serial.print(x)
  #define LOGLN(x) Serial.println(x)
#else
  #define LOG(x)
  #define LOGLN(x)
#endif

#endif

Conclusão

O projeto apresentado demonstra que um microcontrolador de baixo custo como o ESP32 pode ser utilizado como base para a construção de um pequeno laboratório experimental de redes. Mesmo com recursos limitados de hardware, é possível implementar funções fundamentais de infraestrutura, como a criação de uma rede local sem fio, o encaminhamento de pacotes entre interfaces e a tradução de endereços por meio de NAT.

Mais do que reproduzir o comportamento de um roteador convencional, o sistema proposto busca tornar visíveis os mecanismos que normalmente permanecem ocultos em equipamentos comerciais. Ao permitir observar diretamente aspectos como a configuração das interfaces de rede, o papel do gateway, o funcionamento do DHCP e o processo de tradução de endereços, o projeto se torna uma ferramenta didática para o estudo prático de redes IP.

Outra característica relevante da abordagem apresentada é a simplicidade do ambiente necessário para sua reprodução. Como toda a lógica é implementada diretamente no firmware do microcontrolador e não requer circuitos adicionais, o sistema pode ser facilmente replicado e modificado por estudantes e desenvolvedores interessados em explorar o funcionamento interno das redes.

A arquitetura modular do firmware também abre espaço para experimentações futuras. O projeto pode servir como ponto de partida para a implementação de novos serviços de rede, mecanismos de monitoramento mais avançados ou integrações com outras plataformas e dispositivos embarcados.

Projetos como este demonstram como plataformas embarcadas modernas podem ser utilizadas não apenas para aplicações de automação ou IoT, mas também como ferramentas acessíveis para o estudo e experimentação da infraestrutura fundamental que sustenta a comunicação na internet.

Referências

TANENBAUM, Andrew S.; FEAMSTER, Nick; WETHERALL, David. Redes de computadores. 6. ed. Porto Alegre: Bookman, 2021.

KUROSE, James F.; ROSS, Keith W. Redes de computadores e a Internet: uma abordagem top-down. 8. ed. São Paulo: Pearson, 2021.

ESPRESSIF SYSTEMS. ESP32 Technical Reference Manual. Disponível em: https://www.espressif.com.

GER, Martin. ESP32 NAT Router. Repositório disponível em: https://github.com/martin-ger/esp32_nat_router.

ARDUINO. Arduino Core for ESP32. Disponível em: https://github.com/espressif/arduino-esp32.

Precisa dos componentes para este projeto?

Encontre tudo na Loja Eletrogate com frete grátis para compras acima de R$ 200