ESPHome 2026.8.0-dev
Loading...
Searching...
No Matches
mdns_component.cpp
Go to the documentation of this file.
2#ifdef USE_MDNS
4#include "esphome/core/log.h"
6#include "mdns_component.h"
7
8#ifdef USE_ESP8266
9#include <pgmspace.h>
10// Macro to define strings in PROGMEM on ESP8266, regular memory on other platforms
11#define MDNS_STATIC_CONST_CHAR(name, value) static const char name[] PROGMEM = value
12#else
13// On non-ESP8266 platforms, use regular const char*
14#define MDNS_STATIC_CONST_CHAR(name, value) static constexpr const char name[] = value
15#endif
16
17#ifdef USE_API
19#endif
20#ifdef USE_DASHBOARD_IMPORT
22#endif
23
24namespace esphome::mdns {
25
26static const char *const TAG = "mdns";
27
28#ifndef USE_WEBSERVER_PORT
29#define USE_WEBSERVER_PORT 80 // NOLINT
30#endif
31
32#ifndef USE_SENDSPIN_PORT
33#define USE_SENDSPIN_PORT 8928 // NOLINT
34#endif
35
36// Define all constant strings using the macro
37MDNS_STATIC_CONST_CHAR(SERVICE_TCP, "_tcp");
38
39// Wrap build-time defines into flash storage
40MDNS_STATIC_CONST_CHAR(VALUE_VERSION, ESPHOME_VERSION);
41
43#ifdef USE_MDNS_STORE_SERVICES
44 auto &services = this->services_;
45#else
47 auto &services = services_storage;
48#endif
49
50#ifdef USE_MDNS_DEVICE_INFO_TXT
51#ifdef USE_MDNS_STORE_SERVICES
53 char *mac_ptr = this->mac_address_;
55 char *cfg_ptr = this->config_hash_str_;
56#else
57 char mac_address[MAC_ADDRESS_BUFFER_SIZE];
58 char config_hash_str[CONFIG_HASH_STR_SIZE];
59 get_mac_address_into_buffer(mac_address);
60 format_hex_to(config_hash_str, App.get_config_hash());
61 char *mac_ptr = mac_address;
62 char *cfg_ptr = config_hash_str;
63#endif
64#else
65 char *mac_ptr = nullptr;
66 char *cfg_ptr = nullptr;
67#endif
68
69 this->compile_records_(services, mac_ptr, cfg_ptr);
70 platform_register(this, services);
71}
72
74 const char *mac_address_buf, const char *config_hash_buf) {
75 // IMPORTANT: The #ifdef blocks below must match COMPONENTS_WITH_MDNS_SERVICES
76 // in mdns/__init__.py. If you add a new service here, update both locations.
77
78#ifdef USE_MDNS_DEVICE_INFO_TXT
79 MDNS_STATIC_CONST_CHAR(TXT_VERSION, "version");
80 MDNS_STATIC_CONST_CHAR(TXT_MAC, "mac");
81 MDNS_STATIC_CONST_CHAR(TXT_CONFIG_HASH, "config_hash");
82#endif
83
84#ifdef USE_API
85 MDNS_STATIC_CONST_CHAR(SERVICE_ESPHOMELIB, "_esphomelib");
86 MDNS_STATIC_CONST_CHAR(TXT_FRIENDLY_NAME, "friendly_name");
87 MDNS_STATIC_CONST_CHAR(TXT_PLATFORM, "platform");
88 MDNS_STATIC_CONST_CHAR(TXT_BOARD, "board");
89 MDNS_STATIC_CONST_CHAR(TXT_NETWORK, "network");
90 MDNS_STATIC_CONST_CHAR(VALUE_BOARD, ESPHOME_BOARD);
91
92 if (api::global_api_server != nullptr) {
93 auto &service = services.emplace_next();
94 service.service_type = MDNS_STR(SERVICE_ESPHOMELIB);
95 service.proto = MDNS_STR(SERVICE_TCP);
96 service.port = []() -> uint16_t { return api::global_api_server->get_port(); };
97
98 const auto &friendly_name = App.get_friendly_name();
99 bool friendly_name_empty = friendly_name.empty();
100
101 // Calculate exact capacity for txt_records
102 size_t txt_count = 4; // version, config_hash, mac, board (always present)
103 if (!friendly_name_empty) {
104 txt_count++; // friendly_name
105 }
106#if defined(USE_ESP8266) || defined(USE_ESP32) || defined(USE_RP2) || defined(USE_LIBRETINY) || defined(USE_NRF52)
107 txt_count++; // platform
108#endif
109#if defined(USE_WIFI) || defined(USE_ETHERNET) || defined(USE_OPENTHREAD)
110 txt_count++; // network
111#endif
112#ifdef USE_API_NOISE
113 const bool api_has_psk = api::global_api_server->get_noise_ctx().has_psk();
114 txt_count++; // api_encryption or api_encryption_supported
115#ifndef USE_API_NOISE_PSK_FROM_YAML
116 if (!api_has_psk) {
117 txt_count++; // api_provisioning
118 }
119#endif
120#endif
121#ifdef ESPHOME_PROJECT_NAME
122 txt_count += 2; // project_name and project_version
123#endif
124#ifdef USE_DASHBOARD_IMPORT
125 txt_count++; // package_import_url
126#endif
127
128 auto &txt_records = service.txt_records;
129 txt_records.init(txt_count);
130
131 if (!friendly_name_empty) {
132 txt_records.push_back({MDNS_STR(TXT_FRIENDLY_NAME), MDNS_STR(friendly_name.c_str())});
133 }
134 txt_records.push_back({MDNS_STR(TXT_VERSION), MDNS_STR(VALUE_VERSION)});
135
136 // Config hash: passed from caller (either member buffer or stack buffer depending on USE_MDNS_STORE_SERVICES)
137 txt_records.push_back({MDNS_STR(TXT_CONFIG_HASH), MDNS_STR(config_hash_buf)});
138
139 // MAC address: passed from caller (either member buffer or stack buffer depending on USE_MDNS_STORE_SERVICES)
140 txt_records.push_back({MDNS_STR(TXT_MAC), MDNS_STR(mac_address_buf)});
141
142#ifdef USE_ESP8266
143 MDNS_STATIC_CONST_CHAR(PLATFORM_ESP8266, "ESP8266");
144 txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_ESP8266)});
145#elif defined(USE_ESP32)
146 MDNS_STATIC_CONST_CHAR(PLATFORM_ESP32, "ESP32");
147 txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_ESP32)});
148#elif defined(USE_RP2)
149 MDNS_STATIC_CONST_CHAR(PLATFORM_RP2, "RP2");
150 txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_RP2)});
151#elif defined(USE_LIBRETINY)
152 txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(lt_cpu_get_model_name())});
153#elif defined(USE_NRF52)
154 MDNS_STATIC_CONST_CHAR(PLATFORM_NRF52, "nRF52");
155 txt_records.push_back({MDNS_STR(TXT_PLATFORM), MDNS_STR(PLATFORM_NRF52)});
156#endif
157
158 txt_records.push_back({MDNS_STR(TXT_BOARD), MDNS_STR(VALUE_BOARD)});
159
160#if defined(USE_WIFI)
161 MDNS_STATIC_CONST_CHAR(NETWORK_WIFI, "wifi");
162 txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_WIFI)});
163#elif defined(USE_ETHERNET)
164 MDNS_STATIC_CONST_CHAR(NETWORK_ETHERNET, "ethernet");
165 txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_ETHERNET)});
166#elif defined(USE_OPENTHREAD)
167 MDNS_STATIC_CONST_CHAR(NETWORK_THREAD, "thread");
168 txt_records.push_back({MDNS_STR(TXT_NETWORK), MDNS_STR(NETWORK_THREAD)});
169#endif
170
171#ifdef USE_API_NOISE
172 MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION, "api_encryption");
173 MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION_SUPPORTED, "api_encryption_supported");
174 MDNS_STATIC_CONST_CHAR(NOISE_ENCRYPTION, "Noise_NNpsk0_25519_ChaChaPoly_SHA256");
175 const char *encryption_key = api_has_psk ? TXT_API_ENCRYPTION : TXT_API_ENCRYPTION_SUPPORTED;
176 txt_records.push_back({MDNS_STR(encryption_key), MDNS_STR(NOISE_ENCRYPTION)});
177#ifndef USE_API_NOISE_PSK_FROM_YAML
178 if (!api_has_psk) {
179 // Unprovisioned device without a YAML key: advertise that the encryption
180 // key can be provisioned over a zero-PSK Noise connection. Gated on the
181 // YAML define so this survives the plaintext removal in 2027.2.0.
182 MDNS_STATIC_CONST_CHAR(TXT_API_PROVISIONING, "api_provisioning");
183 MDNS_STATIC_CONST_CHAR(VALUE_ZERO_PSK, "zero-psk");
184 txt_records.push_back({MDNS_STR(TXT_API_PROVISIONING), MDNS_STR(VALUE_ZERO_PSK)});
185 }
186#endif
187#endif
188
189#ifdef ESPHOME_PROJECT_NAME
190 MDNS_STATIC_CONST_CHAR(TXT_PROJECT_NAME, "project_name");
191 MDNS_STATIC_CONST_CHAR(TXT_PROJECT_VERSION, "project_version");
192 MDNS_STATIC_CONST_CHAR(VALUE_PROJECT_NAME, ESPHOME_PROJECT_NAME);
193 MDNS_STATIC_CONST_CHAR(VALUE_PROJECT_VERSION, ESPHOME_PROJECT_VERSION);
194 txt_records.push_back({MDNS_STR(TXT_PROJECT_NAME), MDNS_STR(VALUE_PROJECT_NAME)});
195 txt_records.push_back({MDNS_STR(TXT_PROJECT_VERSION), MDNS_STR(VALUE_PROJECT_VERSION)});
196#endif // ESPHOME_PROJECT_NAME
197
198#ifdef USE_DASHBOARD_IMPORT
199 MDNS_STATIC_CONST_CHAR(TXT_PACKAGE_IMPORT_URL, "package_import_url");
200 txt_records.push_back({MDNS_STR(TXT_PACKAGE_IMPORT_URL), MDNS_STR(dashboard_import::get_package_import_url())});
201#endif
202 }
203#endif // USE_API
204
205#ifdef USE_PROMETHEUS
206 MDNS_STATIC_CONST_CHAR(SERVICE_PROMETHEUS, "_prometheus-http");
207
208 auto &prom_service = services.emplace_next();
209 prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS);
210 prom_service.proto = MDNS_STR(SERVICE_TCP);
211 prom_service.port = []() -> uint16_t { return USE_WEBSERVER_PORT; };
212#endif
213
214#ifdef USE_SENDSPIN
215 MDNS_STATIC_CONST_CHAR(SERVICE_SENDSPIN, "_sendspin");
216 MDNS_STATIC_CONST_CHAR(TXT_SENDSPIN_PATH, "path");
217 MDNS_STATIC_CONST_CHAR(VALUE_SENDSPIN_PATH, "/sendspin");
218
219 auto &sendspin_service = services.emplace_next();
220 sendspin_service.service_type = MDNS_STR(SERVICE_SENDSPIN);
221 sendspin_service.proto = MDNS_STR(SERVICE_TCP);
222 sendspin_service.port = []() -> uint16_t { return USE_SENDSPIN_PORT; };
223 sendspin_service.txt_records = {{MDNS_STR(TXT_SENDSPIN_PATH), MDNS_STR(VALUE_SENDSPIN_PATH)}};
224#endif
225
226#ifdef USE_WEBSERVER
227 MDNS_STATIC_CONST_CHAR(SERVICE_HTTP, "_http");
228
229 auto &web_service = services.emplace_next();
230 web_service.service_type = MDNS_STR(SERVICE_HTTP);
231 web_service.proto = MDNS_STR(SERVICE_TCP);
232 web_service.port = []() -> uint16_t { return USE_WEBSERVER_PORT; };
233#ifndef USE_API
234 // Without the native API there is no _esphomelib service, so publish the
235 // device info here for the device builder to discover.
236 web_service.txt_records = {{MDNS_STR(TXT_VERSION), MDNS_STR(VALUE_VERSION)},
237 {MDNS_STR(TXT_MAC), MDNS_STR(mac_address_buf)},
238 {MDNS_STR(TXT_CONFIG_HASH), MDNS_STR(config_hash_buf)}};
239#endif
240#endif
241
242#if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_SENDSPIN) && !defined(USE_WEBSERVER) && \
243 !defined(USE_MDNS_EXTRA_SERVICES)
244 MDNS_STATIC_CONST_CHAR(SERVICE_HTTP, "_http");
245
246 // Publish "http" service if not using native API or any other services
247 // This is just to have *some* mDNS service so that .local resolution works
248 auto &fallback_service = services.emplace_next();
249 fallback_service.service_type = MDNS_STR(SERVICE_HTTP);
250 fallback_service.proto = MDNS_STR(SERVICE_TCP);
251 fallback_service.port = []() -> uint16_t { return USE_WEBSERVER_PORT; };
252 fallback_service.txt_records = {{MDNS_STR(TXT_VERSION), MDNS_STR(VALUE_VERSION)},
253 {MDNS_STR(TXT_MAC), MDNS_STR(mac_address_buf)},
254 {MDNS_STR(TXT_CONFIG_HASH), MDNS_STR(config_hash_buf)}};
255#endif
256}
257
259 ESP_LOGCONFIG(TAG,
260 "mDNS:\n"
261 " Hostname: %s",
262 App.get_name().c_str());
263#ifdef USE_MDNS_STORE_SERVICES
264 ESP_LOGV(TAG, " Services:");
265 for (const auto &service : this->services_) {
266 ESP_LOGV(TAG, " - %s, %s, %d", MDNS_STR_ARG(service.service_type), MDNS_STR_ARG(service.proto),
267 service.port.value());
268 for (const auto &record : service.txt_records) {
269 ESP_LOGV(TAG, " TXT: %s = %s", MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
270 }
271 }
272#endif
273}
274
275} // namespace esphome::mdns
276#endif
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
uint32_t get_config_hash()
Get the config hash as a 32-bit integer.
const StringRef & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:222
constexpr const char * c_str() const
Definition string_ref.h:73
constexpr bool empty() const
Definition string_ref.h:76
APINoiseContext & get_noise_ctx()
Definition api_server.h:79
uint16_t get_port() const
char config_hash_str_[CONFIG_HASH_STR_SIZE]
Fixed buffer for config hash hex string (only needed when services are stored)
static constexpr size_t CONFIG_HASH_STR_SIZE
Size of buffer required for config hash hex string (8 hex chars + null terminator)
void compile_records_(StaticVector< MDNSService, MDNS_SERVICE_COUNT > &services, const char *mac_address_buf, const char *config_hash_buf)
void setup_buffers_and_register_(PlatformRegisterFn platform_register)
char mac_address_[MAC_ADDRESS_BUFFER_SIZE]
Fixed buffer for MAC address (only needed when services are stored)
void(*)(MDNSComponent *, StaticVector< MDNSService, MDNS_SERVICE_COUNT > &) PlatformRegisterFn
Helper to set up services and MAC buffers, then call platform-specific registration.
StaticVector< MDNSService, MDNS_SERVICE_COUNT > services_
APIServer * global_api_server
MDNS_STATIC_CONST_CHAR(SERVICE_TCP, "_tcp")
void get_mac_address_into_buffer(std::span< char, MAC_ADDRESS_BUFFER_SIZE > buf)
Get the device MAC address into the given buffer, in lowercase hex notation.
Definition helpers.cpp:744
char * format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length)
Format byte array as lowercase hex to buffer (base implementation).
Definition helpers.cpp:334
Application App
Global storage of Application pointer - only one Application can exist.