Skip to content

SSDP Service — ESP3D-X Reference

Build guard: ESP3D_SSDP_FEATURE
CMake option: SSDP_SERVICE (default ON)
Global instance: esp3d_ssdp_service


The SSDP (Simple Service Discovery Protocol) module broadcasts the device’s presence on the local network using the UPnP-based SSDP standard over UDP multicast. It enables automatic discovery by Windows Network Explorer, UPnP-aware clients, and any tool that listens on the SSDP multicast address (239.255.255.250:1900).

The module is a thin wrapper around the SSDP_IDF component (a FreeRTOS-native, IDF-compatible SSDP library). ESP3Dssdp configures the library from NVS settings and firmware identity macros, then delegates the UDP multicast loop to a self-contained SSDP_IDF FreeRTOS task. No polling is needed from the application: handle() is a no-op.

SSDP complements mDNS as the second network-announcement service. When both are enabled, SSDP starts after mDNS has finished initializing, with a configurable stagger delay, to spread RAM allocation peaks.


Configuration Sources

SSDP_IDF Component (C library)

ESP3DNetworkServices (WiFi STA/AP)

deferred begin()

completion triggers SSDP defer

ssdp_init + ssdp_start

xTaskCreate

get_schema

XML string

port, friendly_name

serial_number

model / manufacturer fields

UDP multicast

239.255.255.250:1900

ESP3DNetworkServices

ESP3DmDNS

ESP3Dssdp

esp3d_ssdp_service

ssdp_init / ssdp_start / ssdp_stop

ssdp_running_task

(FreeRTOS task)

get_ssdp_schema_str

ESP3DSettings

(NVS - http_port, hostname)

esp3d_hal::getEfuseMac()

Customization Macros

customizations/ssdp/customizations.h

esp3dHttpService

(/description.xml handler)

Network

UDP Multicast


LayerFile(s)LanguageRole
Application wrappermain/modules/ssdp/esp3d_ssdp.h / .cppC++Lifecycle management, config assembly
Protocol librarycomponents/SSDP_IDF/ssdp.c / include/ssdp.hCUDP multicast, NOTIFY, M-SEARCH response
Customizationcustomizations/ssdp/customizations.hC (macros)Board/product identity overrides
Network orchestrationmain/modules/network/esp3d_network_services.cppC++Deferred startup and shutdown sequencing

SSDP is always started in a deferred manner to avoid RAM-allocation contention with other services that initialize earlier (HTTP, auth, mDNS).

NETssdp_running_task"SSDP_IDF (ssdp.c)"ESP3DssdpESP3DmDNSESP3DNetworkServicesNETssdp_running_task"SSDP_IDF (ssdp.c)"ESP3DssdpESP3DmDNSESP3DNetworkServiceshandle() fires afterESP3D_NETSERVICE_ANNOUNCE_DEFER_MSalt[mDNS enabled (ESP3D_MDNS_FEATURE)][mDNS disabled]handle() fires when deadline passesloop[Every interval (default 1200 s)]begin()begin auth, HTTP, notifications...schedule mDNS deferred startbegin()started OKschedule SSDP:+ESP3D_NETSERVICE_SSDP_AFTER_MDNS_MSschedule SSDP:+ESP3D_NETSERVICE_ANNOUNCE_DEFER_MSbegin()ssdp_init()build ssdp_config_t(NVS + macros + eFuse MAC)ssdp_start(&config)xTaskCreate(ssdp_running_task)joins 239.255.255.250 multicast groupsends initial NOTIFY_started = trueNOTIFY (periodic re-announcement)M-SEARCH receivedHTTP/1.1 200 OK (unicast reply)

The ssdp_running_task runs as an independent FreeRTOS task for the entire lifetime of the SSDP service. It:

  1. Creates a UDP multicast socket and joins the SSDP multicast group (239.255.255.250:1900).
  2. Enters a select() loop with a 2-second timeout.
  3. On receiving a datagram, calls onPacket():
    • Parses M-SEARCH requests (device discovery from clients).
    • Replies with a unicast HTTP/1.1 200 OK response containing UPnP device description fields.
    • Respects the MX header by applying a random delay capped at mx_max_delay (default 10 000 ms).
  4. Sends periodic NOTIFY datagrams every interval seconds (default 1 200 s = 20 minutes) to keep the device visible in client caches.
  5. On socket error, closes and re-opens the socket automatically (self-healing loop).

The ssdp_config_t struct maps directly to UPnP device description fields. SDDP_DEFAULT_CONFIG() sets safe defaults; ESP3Dssdp::begin() overrides the fields below.

FieldSourceExample
friendly_nameesp3dWifiClient.getHostName() (NVS hostname)"pendant-001"
serial_numberesp3d_hal::getEfuseMac() as decimal string"3927502938"
portNVS setting esp3d_http_port80
model_nameESP3D_MODEL_NAME macro or TFT_TARGET"pibot_pendant_v1_0"
model_numberESP3D_MODEL_NUMBER macro or ESP3D_X_VERSION"2.0.0.a14"
model_urlESP3D_MODEL_URL macro or ESP3D_X_FW_URLGitHub firmware URL
model_descriptionESP3D_MODEL_DESCRIPTION macro (optional)"ESP32 CNC Pendant"
manufacturer_nameESP3D_MANUFACTURER_NAME macro"Espressif Systems"
manufacturer_urlESP3D_MANUFACTURER_URL macro"https://www.espressif.com"
device_typeHardcoded to "rootdevice""rootdevice"
FieldDefault valueNotes
task_prioritytskIDLE_PRIORITY + 5FreeRTOS task priority
stack_size4096 bytesTask stack size
core_idtskNO_AFFINITYFloats between Core 0 and Core 1
ttl2IP TTL for multicast packets
interval1200 sNOTIFY re-announcement interval
mx_max_delay10000 msMax random M-SEARCH response delay
schema_url"description.xml"URL path served by HTTP service
presentation_url"/"Device UI URL
server_name"SSDPServer/1.0"HTTP Server header value
uuid_root / uuidNULLAuto-generated from MAC by SSDP_IDF
services_descriptionNULLUPnP service list (not used)
icons_descriptionNULLUPnP icon list (not used)

Override macros in customizations/ssdp/customizations.h to change how the device identifies itself to discovery clients. All macros are optional; fallbacks are used when undefined.

// #define ESP3D_MODEL_NAME "My CNC Pendant" // fallback: TFT_TARGET
// #define ESP3D_MODEL_NUMBER "v2.0" // fallback: ESP3D_X_VERSION
// #define ESP3D_MODEL_URL "https://example.com" // fallback: ESP3D_X_FW_URL
// #define ESP3D_MODEL_DESCRIPTION "ESP32 Pendant" // optional, Windows ignores it
#define ESP3D_MANUFACTURER_NAME "Espressif Systems" // already set in default customizations
#define ESP3D_MANUFACTURER_URL "https://www.espressif.com"

Board-specific overrides (e.g. for pibot_pendant_v1_0) are placed in that board’s own customizations/ssdp/customizations.h.


The SSDP_IDF library builds an XML device description string (the UPnP schema) from the ssdp_config_t fields at ssdp_start() time. ESP3Dssdp::get_schema() exposes this string so the HTTP service can serve it at /description.xml when a discovery client fetches the URL carried in the SSDP response’s LOCATION header.

ESP3Dssdp::get_schema()
└── get_ssdp_schema_str() ← C function from SSDP_IDF
└── returns pre-built XML string stored in task config buffer

The URL path ("description.xml") is baked into the SSDP response via the schema_url config field and matches the handler registered in esp3dHttpService.


Defined in CMakeLists.txt:

OPTION(SSDP_SERVICE "SSDP service" ON)

Set by cmake/features.cmake when both WIFI_SERVICE and SSDP_SERVICE are ON:

if(WIFI_SERVICE)
if(SSDP_SERVICE)
add_compile_options(-DESP3D_SSDP_FEATURE=1)
endif()
endif()

SSDP requires WiFi to be present — the define is nested inside the WIFI_SERVICE block and is not set when WiFi is disabled.

Mutual exclusion with SOCKET_CLIENT_SERVICE

Section titled “Mutual exclusion with SOCKET_CLIENT_SERVICE”

cmake/sanity_check.cmake enforces a hard build error if both are enabled simultaneously:

if(WIFI_SERVICE AND SOCKET_CLIENT_SERVICE)
if(SSDP_SERVICE)
message(FATAL_ERROR "SSDP_SERVICE cannot be enabled with SOCKET_CLIENT_SERVICE. ...")
endif()
endif()

Rationale: When SOCKET_CLIENT_SERVICE is ON, WiFi STA is reserved exclusively for the outgoing TCP connection to the CNC controller (FluidNC / grblHAL over TCP). Running SSDP multicast simultaneously creates lwIP stack contention and increases the minimum-free-heap risk. See Network_&_Web_Services.md and the feature resource matrix (docs/features/feature_resource_matrix.md) §2.1 for the full analysis.

ProfileSOCKET_CLIENT_SERVICESSDP_SERVICE
Serial/USB CNC + WiFi remote (default)OFFON
WiFi TCP CNC (socket client)ONOFF (blocked by sanity check)

esp3dHttpServicessdp_running_task"239.255.255.250:1900\n(multicast)""UPnP Client\n(e.g. Windows Explorer)"esp3dHttpServicessdp_running_task"239.255.255.250:1900\n(multicast)""UPnP Client\n(e.g. Windows Explorer)"'Every interval (1200 s default):'"M-SEARCH * HTTP/1.1\nST: ssdp:all MX: 3"receives M-SEARCH on multicast socket"random delay (0..MX s, max mx_max_delay)""HTTP/1.1 200 OK (unicast)\nUSN / ST / LOCATION: http://device-ip/description.xml""GET /description.xml HTTP/1.1""200 OK\n(UPnP XML from get_ssdp_schema_str())""NOTIFY * HTTP/1.1 ssdp:alive (multicast)"

esp3d_ssdp_service is owned and driven by ESP3DNetworkServices. The relevant control flow in esp3d_network_services.cpp:

ESP3DNetworkServices::begin()
├─ ESP3D_MDNS_FEATURE ON → defers mDNS; after mDNS done, defers SSDP by ESP3D_NETSERVICE_SSDP_AFTER_MDNS_MS
└─ ESP3D_MDNS_FEATURE OFF → defers SSDP by ESP3D_NETSERVICE_ANNOUNCE_DEFER_MS
ESP3DNetworkServices::handle() ← called every 10 ms from networkTask
└─ _ssdp_defer_pending && millis() >= deadline → esp3d_ssdp_service.begin()
ESP3DNetworkServices::end()
└─ _ssdp_defer_pending = false ← cancels any pending deferred start
esp3d_ssdp_service.end() ← stops running service if started

The defer mechanism prevents RAM-allocation peaks. Auth, HTTP, mDNS, and SSDP each allocate task stacks and heap buffers. Staggering their starts allows the allocator to service each request from a less-fragmented heap. See docs/guides/esp32_memory_constraints.md for the general fragmentation model.


Declared in main/modules/ssdp/esp3d_ssdp.h.

MethodDescription
bool begin()Initialises SSDP_IDF, assembles ssdp_config_t, starts multicast task. Returns true on success. Calls end() first if already started.
void handle()No-op. The SSDP_IDF task is self-contained; no application-level polling is required.
void end()Calls ssdp_stop(), sets _started = false. Called by the destructor and by ESP3DNetworkServices::end().
const char* get_schema()Returns the pre-built UPnP XML schema string generated by SSDP_IDF. Used by the HTTP service to respond to GET /description.xml.
bool started()Returns _started. Inline accessor.

DocumentRelevance
mdns.mdParallel discovery service; SSDP starts after mDNS when both are enabled
Network_&_Web_Services.mdParent module overview; network service orchestration
network.mdESP3DNetwork — WiFi lifecycle that must be up before SSDP starts
wifi.mdWiFi STA/AP driver layer; provides the network interface SSDP uses

FileRole
main/modules/ssdp/esp3d_ssdp.hESP3Dssdp class declaration; global esp3d_ssdp_service
main/modules/ssdp/esp3d_ssdp.cppbegin() / end() / get_schema() implementation
customizations/ssdp/customizations.hOptional product-identity macros (ESP3D_MODEL_NAME, etc.)
components/SSDP_IDF/include/ssdp.hssdp_config_t, SDDP_DEFAULT_CONFIG(), C API declarations
components/SSDP_IDF/ssdp.cssdp_running_task, multicast socket, NOTIFY / M-SEARCH logic
main/modules/network/esp3d_network_services.cppDeferred start / stop sequencing
cmake/features.cmakeESP3D_SSDP_FEATURE compile define
cmake/sanity_check.cmakeMutual-exclusion check with SOCKET_CLIENT_SERVICE