Skip to content

mDNS Module — ESP3D-X

The mDNS module (ESP3DmDNS) makes the pendant visible to other devices on the local network without any manual IP address configuration. After WiFi connects, it registers the device under its hostname (e.g. pendant.local) and advertises all active services — HTTP, WebSocket, WebDAV, socket server — as standard mDNS service records. It also exposes a rich _device-info._tcp record that encodes compile-time and NVS-sourced device metadata useful for discovery tools and integrations. The module additionally provides a scan API so the firmware itself can discover other ESP3D devices on the network.

Build guard: ESP3D_MDNS_FEATURE


begin()

deferred begin()

deferred begin() - after mDNS

mdns_init() / mdns_hostname_set()

registerAnnouncedServices()

registerDeviceInfoService()

conditional

conditional

conditional

conditional

refreshAnnouncements()

mdns_query_ptr()

ESP3DNetwork

network.h

ESP3DNetworkServices

network_services.cpp

ESP3DmDNS

esp3d_mdns.cpp

ESP3Dssdp

ssdp.h

ESP-IDF mdns component

_esp3d._tcp

_device-info._tcp

_http._tcp / _https._tcp

_ws._tcp

_webdav._tcp

_telnet._tcp

ESP Commands

ESP120/121/130/131/160/190/401

servicesScan() / getRecord()


mdns module

optional

optional

optional

optional

optional

optional

esp3d_mdns.h

esp3d_mdns.cpp

esp3d_settings.h

NVS reads at begin

network/esp3d_network.h

getBTMac

filesystem/esp3d_flash.h

flashFs - FS name and size

esp3d_target.h

ESP3D_TARGET_NAME

esp3d_version.h

ESP3D_X_VERSION

customizations/mdns/customizations.h

custom TXT macros

ESP-IDF mdns.h

esp_flash.h

flash size

esp_psram.h

CONFIG_SPIRAM

websocket_server/esp3d_ws_service.h

WS URI and subprotocol

webdav/esp3d_webdav_service.h

WebDAV root path

notifications/esp3d_notifications_service.h

notification type

camera/camera.h

sensors/esp3d_sensor.h

filesystem/esp3d_sd_config.h

SD interface type


mDNS is not started immediately during ESP3DNetworkServices::begin(). It is deferred by ESP3D_NETSERVICE_ANNOUNCE_DEFER_MS milliseconds to allow lwIP and the network stack to settle after WiFi association. The handle() loop fires the actual begin() once the deadline passes.

SSDP (see ssdp.md) starts a further ESP3D_NETSERVICE_SSDP_AFTER_MDNS_MS milliseconds after mDNS completes, sequencing the two announcement protocols to avoid simultaneous heap pressure.

ESP-IDF mdnsESP3DmDNSESP3DNetworkServicesnetworkTaskESP-IDF mdnsESP3DmDNSESP3DNetworkServicesnetworkTaskalt[deadline reached]loop[handle() polling at ~10 ms]On network settings change (ESP commands)On network stopbegin()start auth / time / HTTP / notificationsset _mdns_defer_pending = truedeadline = now + ANNOUNCE_DEFER_MSreturn (services started)handle()begin()mdns_init()mdns_hostname_set(hostname)mdns_instance_name_set(hostname)registerAnnouncedServices()trueschedule SSDP deferrefreshAnnouncements()mdns_service_remove_all()registerAnnouncedServices()end()end()mdns_service_remove_all()mdns_free()

ESP3DmDNS

-bool _started

-mdns_result_t* _scanResults

-mdns_result_t* _currentresult

-uint16_t _count

+begin() : bool

+handle() : void

+end() : void

+refreshAnnouncements() : void

+started() : bool

+getCount() : uint16_t

+servicesScan(service_name, proto) : uint16_t

+getRecord(pos) : mdns_result_t

+freeServiceScan() : void

-registerAnnouncedServices() : bool

-registerDeviceInfoService() : bool

MethodDescription
begin()Initialises the ESP-IDF mDNS stack, sets hostname and instance name, then registers all services. Calls end() first if already started.
end()Removes all registered services, frees the mDNS stack, frees any pending scan results. Safe to call when not started.
handle()No-op placeholder — reserved for future periodic work. Called by ESP3DNetworkServices::handle().
refreshAnnouncements()Calls mdns_service_remove_all() then re-runs registerAnnouncedServices(), re-reading NVS. Falls back to a full end()/begin() cycle if remove_all fails.
servicesScan(service, proto)Queries the network for a given service type (default _esp3d._tcp), blocks up to 3 000 ms, and populates the internal result list. Returns record count.
getRecord(pos)Returns record at index pos, or the next record in sequence when pos == -1 (iterator mode). Returns nullptr if out of range or not started.
freeServiceScan()Releases all scan results via mdns_query_results_free() and resets the iterator.

Conditional - feature + runtime NVS

_http._tcp or _https._tcp

port: http_port from NVS

guard: ESP3D_HTTP_FEATURE + http_on

_ws._tcp

port: http_port

TXT: uri, subprotocol

guard: ESP3D_WS_SERVER_SERVICE_FEATURE + ws_on

_webdav._tcp

port: http_port

TXT: path

guard: ESP3D_WEBDAV_SERVICES_FEATURE + webdav_on

_telnet._tcp

port: socket_port from NVS

guard: ESP3D_SOCKET_SERVER_FEATURE + socket_on

Always registered

_esp3d._tcp

port: ESP3D_MDNS_ESP3D_TCP_PORT default 8080

TXT: firmware, version

_device-info._tcp

port: ESP3D_MDNS_ESP3D_TCP_PORT

TXT: 10 to 21 records

Yes

Yes

No

Yes

No

No

Yes

No

registerAnnouncedServices

mdns_service_add _esp3d._tcp

TXT: firmware + version + custom

registerDeviceInfoService

mdns_service_add _device-info._tcp

TXT: fixed + conditional records

ESP3D_HTTP_FEATURE AND http_on?

mdns_service_add _http._tcp or _https._tcp

ESP3D_WS_SERVER_SERVICE_FEATURE AND ws_on?

mdns_service_add _ws._tcp

ESP3D_WEBDAV_SERVICES_FEATURE AND webdav_on?

mdns_service_add _webdav._tcp

ESP3D_SOCKET_SERVER_FEATURE AND socket_on?

mdns_service_add _telnet._tcp

return true


KeyValueSource
nameDevice hostnameesp3dWifiClient.getHostName()
boardChip architectureCONFIG_IDF_TARGET (e.g. "esp32", "esp32s3")
fwFirmware baseESP3D_CODE_BASE ("ESP3D-X")
verFirmware versionESP3D_X_VERSION (e.g. "2.0.0.a14")
targetCNC firmware targetESP3D_TARGET_NAME (e.g. "FluidNC", "grblHAL")
sdkESP-IDF versionIDF_VER (e.g. "v5.4.3")
flashFlash chip sizeesp_flash_get_size() (e.g. "16.00 MB")
authAuthentication state"enabled" / "disabled" — compile-time ESP3D_AUTHENTICATION_FEATURE
logLog backend/levelcompile-time ESP3D_LOG_BACKEND + ESP3D_LOG (e.g. "serial/debug", "none")
fsFlash FS name + total sizeflashFs.getFileSystemName() + getSpaceInfo() (e.g. "LittleFS/1.44 MB")

Format: "<backend>/<level>" or "none" when logging is fully disabled (ESP3D_LOG == 0).

Backend tokenCompile condition
serialESP3D_LOG_BACKEND_SERIAL (default)
sdESP3D_LOG_BACKEND_SD
uart2ESP3D_LOG_BACKEND_UART2
telnetESP3D_LOG_BACKEND_TELNET
websocketESP3D_LOG_BACKEND_WEBSOCKET
Level tokenCompile condition
allESP3D_LOG >= ESP3D_LOG_LEVEL_ALL (4)
debugESP3D_LOG >= ESP3D_LOG_LEVEL_DEBUG (3)
warningESP3D_LOG >= ESP3D_LOG_LEVEL_WARNING (2)
errorESP3D_LOG >= ESP3D_LOG_LEVEL_ERROR (1)

See esp3d_log.md for the full logging reference.

A record is absent when the feature is not compiled in, or when the NVS setting reports it is inactive.

KeyExample valueGateSource
psram"8.00 MB"CONFIG_SPIRAMesp_psram_get_size()
notification"telegram" / "email" / "pushover" / "ifttt" / "whatsapp" / "homeassistant"ESP3D_NOTIFICATIONS_FEATURE and type ≠ none (NVS)esp3d_notification_type setting
camera"ESP32 Cam"ESP3D_CAMERA_FEATUREesp3d_camera.GetModelString()
bt"pendant/AA:BB:CC:DD:EE:FF"ESP3D_BT_SERIAL_FEATURE or ESP3D_BT_BLE_FEATUREhostname + esp3dNetwork.getBTMac()
sensor"DHT22" / "BMP280" / "BME280" / "ANALOG"ESP3D_SENSOR_FEATUREESP3DSensor::getModelString()
sd-update"ON" / "OFF"ESP3D_UPDATE_FEATURE + ESP3D_SD_CARD_FEATURE (NVS)esp3d_check_update_on_sd
web-update"enabled"ESP3D_UPDATE_FEATURE + ESP3D_HTTP_FEATURE (compile-time)—
time"ntp" / "manual"ESP3D_TIMESTAMP_FEATURE (NVS)esp3d_use_internet_time
sd"SPI" / "SDIO"ESP3D_SD_CARD_FEATURE (compile-time via board config)esp3dSdConfig.interface_type
usb"enabled"ESP3D_USB_SERIAL_FEATURE (compile-time)—
usb-cdc (a14)"enabled"ESP3D_USB_CDC_DEVICE_FEATURE (compile-time)—

usb-cdc uses the same slot as usb: USB_CDC_DEVICE_SERVICE and USB_SERIAL_SERVICE are mutually exclusive (cmake/sanity_check.cmake), so the 21 built-in slots still hold. | ssdp | "enabled" | ESP3D_SSDP_FEATURE (compile-time) | — |

notification: absent when type is none. A missing record means no active notification channel — not that the feature is absent.

bt: Bluetooth and WiFi are mutually exclusive on boards without PSRAM. When BT is active, WiFi is off and mDNS does not run; this record is reserved for future dual-radio capable boards. See Network_&_Web_Services.md for the feature resource model.


CONFIG_SPIRAM

ESP3D_NOTIFICATIONS_FEATURE

ESP3D_CAMERA_FEATURE

ESP3D_BT_FEATURE

ESP3D_SENSOR_FEATURE

UPDATE_FEATURE + SD

UPDATE_FEATURE + HTTP

ESP3D_TIMESTAMP_FEATURE

ESP3D_SD_CARD_FEATURE

ESP3D_USB_SERIAL_FEATURE

ESP3D_SSDP_FEATURE

Yes

No

registerDeviceInfoService called

Stack-allocate value buffers

flash_val fs_val log_val ...

Compute flash size via esp_flash_get_size

Compute fs_val from flashFs

Compute log_val from compile-time constants

Declare txt array - mdns_txt_item_t txt 21 + CUSTOM_MAX

Fill fixed records

name board fw ver target sdk flash auth log fs

Feature guards

append psram

read NVS - append notification

append camera

append bt

append sensor

read NVS - append sd-update

append web-update

read NVS - append time

append sd

append usb

append ssdp

ESP3D_MDNS_CUSTOM_DEVICE_INFO_TXT defined?

Merge custom records

override existing keys or append new

mdns_service_add _device-info._tcp with n records


Board-level and project-level custom TXT records are injected at compile time through customizations/mdns/customizations.h. The same header is present in each board’s customizations/ directory, so board-specific overrides take precedence over the project-level defaults.

MacroApplies toEffect
ESP3D_MDNS_CUSTOM_ESP3D_TXT_esp3d._tcpList of { "key", "value" } entries. Keys already in the built-in list replace their value; new keys are appended.
ESP3D_MDNS_CUSTOM_ESP3D_TXT_MAX_esp3d._tcpStack-allocation size for custom entries. Must equal the number of entries in ESP3D_MDNS_CUSTOM_ESP3D_TXT. Default: 0.
ESP3D_MDNS_CUSTOM_DEVICE_INFO_TXT_device-info._tcpSame merge behaviour — override or append. Built-in keys that can be overridden: name, board, fw, ver, target, sdk, flash, auth, log, fs, psram, notification, camera, bt, sensor, sd-update, web-update, time, sd, usb, ssdp.
ESP3D_MDNS_CUSTOM_DEVICE_INFO_TXT_MAX_device-info._tcpStack-allocation ceiling. Each entry costs 8 bytes on ESP32 (two pointers). Default: 0.
ESP3D_MDNS_ESP3D_TCP_PORTBoth servicesDiscovery port for _esp3d._tcp and _device-info._tcp. Default: 8080. Override at compile time if needed; not tied to the HTTP listen port.
// customizations/mdns/customizations.h
#define ESP3D_MDNS_CUSTOM_DEVICE_INFO_TXT_MAX 2
#define ESP3D_MDNS_CUSTOM_DEVICE_INFO_TXT \
{ "vendor", "MyCompany" }, \
{ "model", "My Custom Board" },

All string values must be compile-time string literals. No heap is allocated during TXT registration; all buffers are stack-local inside registerDeviceInfoService() and are consumed by mdns_service_add() before the function returns.


ESP3DmDNS doubles as a client scanner. Any code can query the local network for other ESP3D devices and iterate over the results.

ESP-IDF mdnsESP3DmDNSCallerESP-IDF mdnsESP3DmDNSCallerloop[while count > 0]servicesScan("_esp3d", "_tcp")freeServiceScan() - release previous resultsmdns_query_ptr(service, proto, 3000 ms, MAX_RESULTS)mdns_result_t* list or NULLcount results - reset _currentresultcountgetRecord(-1) - iterator modemdns_result_t* - advances internal pointerfreeServiceScan()mdns_query_results_free()

getRecord(pos) also supports direct indexed access: pass a non-negative pos to walk the linked list by index.

The scan blocks for up to 3 000 ms while waiting for PTR responses. Callers must not run this from the LVGL task — use a background FreeRTOS task instead.


Several ESP commands call refreshAnnouncements() immediately after persisting a network setting change, so the mDNS advertisement stays consistent without a full reboot.

CommandTrigger
[ESP120]WiFi mode change
[ESP121]HTTP port or HTTP on/off
[ESP130]WiFi SSID / credential change
[ESP131]WiFi password change
[ESP160]Notification type change
[ESP190]Factory reset / hostname change
[ESP401]Generic setting write (hostname, WS, HTTP, etc.)

refreshAnnouncements() is guarded by if (esp3dmDNS.started()) in every call site — it is a no-op when mDNS is not running (e.g. during initial boot before the deferred start fires).


All TXT value buffers are stack-allocated inside registerDeviceInfoService():

BufferSizePurpose
flash_val[16]16 BFlash chip size string
fs_val[32]32 BFS name + size string
log_val[20]20 BLog backend/level string
psram_val[16]16 BPSRAM size (conditional)
bt_val[52]52 BBT hostname + MAC (conditional)
txt[21 + CUSTOM_MAX]variesmdns_txt_item_t pointer pairs

All buffers are consumed by mdns_service_add() inside the same stack frame. The ESP-IDF mDNS stack copies the strings internally before the call returns, so no heap allocation is needed and no dangling-pointer risk exists.

The maximum built-in TXT slot count is 21. This must be incremented in the array declaration when adding new built-in records.


mDNS and SSDP serve the same device-discovery purpose for different protocols (mDNS/DNS-SD for Bonjour/Avahi clients; SSDP/UPnP for Windows and other UPnP clients). They are always started in sequence — mDNS first, then SSDP — to avoid simultaneous heap pressure from two large network initialisations. See Network_&_Web_Services.md for the network services module overview.

Network readyWiFi STA connectednetif upESP3DNetworkServices begin()AuthenticationstartedHTTP serverstartedNotificationsstartedDeferred via handle() loopmDNSANNOUNCE_DEFER_MSafter begin()SSDPSSDP_AFTER_MDNS_MSafter mDNSSocket clientSOCKET_CLIENT_DEFER_MSafter begin()Service startup sequence (approximate)
FileRole
main/modules/mdns/esp3d_mdns.hESP3DmDNS class declaration; ESP3D_MDNS_ESP3D_TCP_PORT default
main/modules/mdns/esp3d_mdns.cppbegin(), end(), refreshAnnouncements(), registerAnnouncedServices(), registerDeviceInfoService(), scan API
customizations/mdns/customizations.hProject-level custom TXT record macros (default: all disabled)
boards/<board>/customizations/mdns/customizations.hBoard-specific overrides — takes precedence over project-level
main/modules/network/esp3d_network_services.cppDeferred start orchestration; calls esp3dmDNS.begin() / end()
main/core/commands/esp120.cpp … esp401.cpprefreshAnnouncements() call sites after setting changes