MCU & FPGA protocolos mDNS no ESP32 — tornando dispositivos IoT fáceis de encontrar na rede local

mDNS no ESP32 — tornando dispositivos IoT fáceis de encontrar na rede local


Preparando o ESP-IDF para usar mDNS no ESP32

Antes de escrever o código, precisamos entender um detalhe importante: nas versões mais recentes do ESP-IDF, especialmente a partir da linha 5.x, o mDNS deixou de ficar diretamente dentro do repositório principal do ESP-IDF e passou a ser distribuído como um componente separado. A própria documentação da Espressif informa que o componente mDNS foi movido para um repositório separado desde o ESP-IDF v5.0, e que ele pode ser adicionado ao projeto com o comando idf.py add-dependency espressif/mdns. (Espressif Systems)

Isso significa que, em um projeto novo, o caminho mais direto é abrir o terminal dentro da pasta do projeto ESP-IDF e executar:

idf.py add-dependency espressif/mdns

Esse comando adiciona a dependência ao projeto usando o ESP Component Manager. No momento desta consulta, o componente espressif/mdns aparece no registro oficial da Espressif na versão 1.11.1, descrito como um serviço UDP multicast usado para descoberta local de hosts e serviços. (Espressif Components)

Depois disso, é comum executar:

idf.py reconfigure
idf.py build

Em muitos casos, o próprio build já baixa o componente automaticamente, mas o reconfigure ajuda quando o projeto acabou de receber uma nova dependência. O resultado esperado é que o projeto passe a reconhecer o cabeçalho:

#include "mdns.h"

A estrutura mínima do projeto pode continuar simples:

meu_projeto_mdns/
├── CMakeLists.txt
├── main/
│   ├── CMakeLists.txt
│   └── main.c
└── idf_component.yml

O arquivo idf_component.yml, gerado ou atualizado pelo gerenciador de componentes, deve conter algo semelhante a:

dependencies:
  espressif/mdns: "*"

Em projetos mais controlados, especialmente quando buscamos reprodutibilidade, é melhor fixar uma faixa de versão em vez de usar "*". Por exemplo:

dependencies:
  espressif/mdns: "^1.11.1"

Essa escolha evita que uma mudança futura do componente altere o comportamento do projeto sem percebermos. Em sistemas embarcados, esse cuidado é importante porque uma dependência atualizada automaticamente pode introduzir mudança de API, alteração de consumo de memória ou comportamento diferente em rede.

Agora podemos pensar na sequência lógica do firmware. O mDNS não deve ser inicializado antes da pilha de rede estar pronta. Primeiro inicializamos NVS, Wi-Fi, esp_netif, event loop e aguardamos a conexão. Depois que o ESP32 recebe IP, inicializamos o mDNS e registramos o hostname.

A sequência conceitual fica assim:

Inicializar NVS
Inicializar esp_netif
Criar event loop
Conectar ao Wi-Fi
Aguardar IP
Inicializar mDNS
Definir hostname
Definir nome da instância
Registrar serviços opcionais

Um exemplo mínimo de função para inicializar o mDNS seria:

#include "esp_log.h"
#include "mdns.h"

static const char *TAG = "MDNS_APP";

/**
 * @brief Inicializa o serviço mDNS do ESP32.
 *
 * Esta função configura o nome local do dispositivo e o nome amigável
 * que pode aparecer em ferramentas de descoberta de serviço.
 *
 * Após esta configuração, o dispositivo poderá ser acessado como:
 *
 * http://esp32-sensor.local
 */
static esp_err_t app_mdns_init(void)
{
    esp_err_t err;

    err = mdns_init();
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Falha ao inicializar mDNS: %s", esp_err_to_name(err));
        return err;
    }

    err = mdns_hostname_set("esp32-sensor");
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Falha ao definir hostname mDNS: %s", esp_err_to_name(err));
        return err;
    }

    err = mdns_instance_name_set("ESP32 Sensor Ambiental");
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Falha ao definir nome da instância mDNS: %s", esp_err_to_name(err));
        return err;
    }

    ESP_LOGI(TAG, "mDNS iniciado: esp32-sensor.local");

    return ESP_OK;
}

Neste exemplo, a chamada:

mdns_hostname_set("esp32-sensor");

define o nome técnico do host. Portanto, dentro da rede local, o dispositivo poderá responder por:

esp32-sensor.local

Já a chamada:

mdns_instance_name_set("ESP32 Sensor Ambiental");

define um nome mais amigável para identificação humana. Esse nome pode aparecer em ferramentas de descoberta, aplicativos ou navegadores de serviços mDNS.

Um erro comum é tentar acessar esp32-sensor.local antes do ESP32 estar realmente conectado ao Wi-Fi. O mDNS depende da interface de rede ativa. Por isso, em uma aplicação bem organizada, a chamada app_mdns_init() deve ser feita depois do evento IP_EVENT_STA_GOT_IP, isto é, depois que o ESP32 recebeu um endereço IP.

De forma simplificada, dentro do fluxo principal do programa, teríamos algo como:

void app_main(void)
{
    /*
     * 1. Inicializar NVS.
     * 2. Inicializar rede.
     * 3. Conectar ao Wi-Fi.
     * 4. Aguardar obtenção de IP.
     */

    ESP_ERROR_CHECK(app_wifi_init_sta());

    /*
     * Neste ponto, assumimos que app_wifi_init_sta()
     * só retorna depois que o ESP32 conectou e recebeu IP.
     */
    ESP_ERROR_CHECK(app_mdns_init());

    /*
     * A partir daqui, outros serviços podem ser iniciados,
     * como servidor HTTP, MQTT local, WebSocket ou API REST.
     */
}

Do ponto de vista arquitetural, essa separação é saudável. A função de conexão Wi-Fi cuida da rede. A função app_mdns_init() cuida apenas da descoberta local. O servidor HTTP, quando existir, deve ficar em outro módulo. Essa organização segue uma prática importante em firmware: separar responsabilidades para facilitar teste, manutenção e expansão.

Assim, o mDNS entra como uma camada pequena, mas muito útil, entre a conectividade Wi-Fi e os serviços da aplicação. O ESP32 continua executando sua função principal, seja ler sensores, acionar relés, controlar motores ou hospedar uma API local, mas agora ele se torna mais fácil de encontrar na rede.

Exemplo prático: ESP32 com servidor HTTP anunciado via mDNS

Agora que já entendemos o conceito, podemos ligar o mDNS a um caso real: um ESP32 executando um servidor HTTP local. Esse é um dos usos mais comuns em projetos IoT, porque permite acessar uma interface web embarcada sem precisar descobrir o IP do dispositivo.

A ideia prática será esta: o ESP32 conecta ao Wi-Fi, recebe um endereço IP por DHCP, inicializa o mDNS, define o nome esp32-sensor.local, inicia um servidor HTTP na porta 80 e anuncia esse serviço como _http._tcp. A documentação da Espressif mostra que o hostname configurado no mDNS passa a responder no formato nome.local; por exemplo, my-esp32 passa a resolver como my-esp32.local. Ela também diferencia o hostname técnico do nome amigável da instância, que serve para identificar melhor o dispositivo em ferramentas de descoberta. (Espressif Systems)

Na prática, teremos este comportamento esperado:

http://esp32-sensor.local

Ao acessar esse endereço no navegador, o usuário deverá receber uma página simples servida diretamente pelo ESP32. Além disso, ao anunciar o serviço _http._tcp, outros programas compatíveis com descoberta de serviços poderão identificar que existe um servidor HTTP disponível naquele dispositivo.

O primeiro passo é incluir os cabeçalhos necessários. Em um projeto real, o código de Wi-Fi normalmente ficaria separado em outro arquivo, mas aqui vamos focar no servidor HTTP e no mDNS para manter a seção objetiva.

#include <stdio.h>
#include <string.h>

#include "esp_err.h"
#include "esp_log.h"
#include "esp_http_server.h"

#include "mdns.h"

static const char *TAG = "APP_MDNS_HTTP";

Agora criamos o manipulador da rota principal /. Esse handler será chamado quando o navegador acessar a raiz do servidor.

/**
 * @brief Manipula requisições HTTP GET para a rota raiz.
 *
 * Esta função responde uma página HTML simples, permitindo testar
 * se o servidor HTTP do ESP32 está ativo e acessível pela rede.
 *
 * @param req Ponteiro para a requisição HTTP recebida.
 * @return ESP_OK quando a resposta é enviada corretamente.
 */
static esp_err_t root_get_handler(httpd_req_t *req)
{
    const char *html =
        "<!DOCTYPE html>"
        "<html>"
        "<head>"
        "<meta charset=\"UTF-8\">"
        "<title>ESP32 mDNS</title>"
        "</head>"
        "<body>"
        "<h1>ESP32 encontrado via mDNS</h1>"
        "<p>Este servidor HTTP está rodando no ESP32.</p>"
        "<p>Acesse este dispositivo por: <strong>esp32-sensor.local</strong></p>"
        "</body>"
        "</html>";

    httpd_resp_set_type(req, "text/html");
    httpd_resp_send(req, html, HTTPD_RESP_USE_STRLEN);

    return ESP_OK;
}

Em seguida, registramos a rota / dentro do servidor HTTP:

/**
 * @brief Inicia o servidor HTTP embarcado no ESP32.
 *
 * O servidor usa a configuração padrão do ESP-IDF e registra
 * uma rota GET na raiz do endereço.
 *
 * @return Handle do servidor HTTP, ou NULL em caso de falha.
 */
static httpd_handle_t app_http_server_start(void)
{
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    httpd_handle_t server = NULL;

    httpd_uri_t root_uri = {
        .uri = "/",
        .method = HTTP_GET,
        .handler = root_get_handler,
        .user_ctx = NULL
    };

    esp_err_t err = httpd_start(&server, &config);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Falha ao iniciar servidor HTTP: %s", esp_err_to_name(err));
        return NULL;
    }

    err = httpd_register_uri_handler(server, &root_uri);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Falha ao registrar rota /: %s", esp_err_to_name(err));
        httpd_stop(server);
        return NULL;
    }

    ESP_LOGI(TAG, "Servidor HTTP iniciado na porta %d", config.server_port);

    return server;
}

Agora vem a parte mais importante para esta seção: a configuração do mDNS com anúncio do serviço HTTP. A documentação mais recente do componente mdns da Espressif informa que definir um hostname próprio é pré-requisito obrigatório para anunciar serviços. Ela também mostra o uso de mdns_service_add() para adicionar serviços e propriedades. (Espressif Systems)

/**
 * @brief Inicializa mDNS e anuncia o servidor HTTP local.
 *
 * Após esta função, o ESP32 poderá responder por:
 *
 * http://esp32-sensor.local
 *
 * E também anunciará um serviço HTTP no padrão:
 *
 * _http._tcp
 *
 * @return ESP_OK em caso de sucesso.
 */
static esp_err_t app_mdns_start_http_service(void)
{
    esp_err_t err;

    err = mdns_init();
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Falha ao inicializar mDNS: %s", esp_err_to_name(err));
        return err;
    }

    err = mdns_hostname_set("esp32-sensor");
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Falha ao definir hostname: %s", esp_err_to_name(err));
        return err;
    }

    err = mdns_instance_name_set("ESP32 Sensor Ambiental");
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Falha ao definir nome da instância: %s", esp_err_to_name(err));
        return err;
    }

    err = mdns_service_add(
        "ESP32 Web Server",
        "_http",
        "_tcp",
        80,
        NULL,
        0
    );

    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Falha ao anunciar serviço HTTP: %s", esp_err_to_name(err));
        return err;
    }

    ESP_LOGI(TAG, "mDNS ativo: http://esp32-sensor.local");
    ESP_LOGI(TAG, "Serviço anunciado: _http._tcp na porta 80");

    return ESP_OK;
}

Observe com atenção os parâmetros de mdns_service_add():

mdns_service_add(
    "ESP32 Web Server",
    "_http",
    "_tcp",
    80,
    NULL,
    0
);

O primeiro argumento é o nome amigável da instância do serviço. O segundo é o tipo do serviço, neste caso _http. O terceiro é o protocolo de transporte, neste caso _tcp. O quarto é a porta, aqui 80. Os dois últimos argumentos permitem adicionar registros TXT, que são pequenos metadados associados ao serviço. Como ainda não precisamos deles, usamos NULL e 0.

Em seguida, depois que o ESP32 já estiver conectado ao Wi-Fi e tiver recebido IP, podemos iniciar o HTTP e o mDNS:

void app_main(void)
{
    /*
     * Em uma aplicação completa, antes deste ponto devem ocorrer:
     *
     * 1. Inicialização da NVS.
     * 2. Inicialização do esp_netif.
     * 3. Criação do event loop.
     * 4. Configuração e conexão Wi-Fi.
     * 5. Espera pelo evento IP_EVENT_STA_GOT_IP.
     *
     * Aqui assumimos que a função app_wifi_init_sta()
     * executa essas etapas e só retorna após obter IP.
     */

    ESP_ERROR_CHECK(app_wifi_init_sta());

    ESP_ERROR_CHECK(app_mdns_start_http_service());

    httpd_handle_t server = app_http_server_start();
    if (server == NULL) {
        ESP_LOGE(TAG, "Servidor HTTP não foi iniciado");
        return;
    }

    ESP_LOGI(TAG, "Aplicação pronta");
}

Existe uma pequena decisão de ordem aqui: podemos iniciar primeiro o mDNS e depois o servidor HTTP, ou iniciar primeiro o servidor HTTP e depois anunciar o serviço. Do ponto de vista conceitual, o ideal é anunciar apenas depois que o serviço real já estiver funcionando. Assim, em uma versão mais rigorosa, a sequência ficaria:

ESP_ERROR_CHECK(app_wifi_init_sta());

httpd_handle_t server = app_http_server_start();
if (server == NULL) {
    ESP_LOGE(TAG, "Servidor HTTP não foi iniciado");
    return;
}

ESP_ERROR_CHECK(app_mdns_start_http_service());

Essa segunda forma é mais coerente para produto final, porque evita anunciar um serviço antes de ele estar realmente disponível. Em protótipos, a diferença pode passar despercebida, mas em sistemas distribuídos essa ordem reduz inconsistências.

Depois de gravar o firmware, conectar o ESP32 ao Wi-Fi e iniciar a aplicação, o teste básico pode ser feito no navegador:

http://esp32-sensor.local

Também podemos testar pelo terminal de um computador na mesma rede:

ping esp32-sensor.local

ou, em sistemas Linux com Avahi:

avahi-browse -a

No macOS, geralmente é possível usar ferramentas associadas ao Bonjour, como:

dns-sd -B _http._tcp

É importante lembrar que o mDNS depende de multicast local. Portanto, se o roteador estiver com isolamento de clientes Wi-Fi, se o computador estiver em outra VLAN ou se a rede bloquear multicast, o nome .local pode não resolver mesmo que o ESP32 esteja funcionando corretamente.

Com essa estrutura, o ESP32 deixa de ser apenas “um IP perdido na rede” e passa a ser um nó identificável e consultável. Para um produto IoT, isso melhora a instalação. Para bancada, facilita depuração. Para ensino, torna o comportamento da rede mais visível. E para arquiteturas distribuídas, abre caminho para descoberta automática de serviços.

0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

Guia Completo de Barramentos USB, RS-232, RS-485, CAN, I²C, SPI, I²S e OneWire: Distâncias, Integridade do Sinal e InterferênciasGuia Completo de Barramentos USB, RS-232, RS-485, CAN, I²C, SPI, I²S e OneWire: Distâncias, Integridade do Sinal e Interferências

Este artigo apresenta uma explicação detalhada e didática sobre os principais barramentos utilizados em eletrônica embarcada — USB, RS-232, RS-485, CAN, I²C, SPI, I²S e OneWire — esclarecendo suas limitações

0
Adoraria saber sua opinião, comente.x