1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-16 00:33:54 +01:00
Files
esphome/esphome/components/mdns/__init__.py
J. Nick Koston f33d9a77f3 bot comments
2025-10-07 22:22:12 -10:00

170 lines
5.4 KiB
Python

import esphome.codegen as cg
from esphome.components.esp32 import add_idf_component
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_DISABLED,
CONF_ID,
CONF_PORT,
CONF_PROTOCOL,
CONF_SERVICE,
CONF_SERVICES,
PlatformFramework,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.coroutine import CoroPriority
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"]
# Components that create mDNS services at runtime
# IMPORTANT: If you add a new component here, you must also update the corresponding
# #ifdef blocks in mdns_component.cpp compile_records_() method
COMPONENTS_WITH_MDNS_SERVICES = ("api", "prometheus", "web_server")
mdns_ns = cg.esphome_ns.namespace("mdns")
MDNSComponent = mdns_ns.class_("MDNSComponent", cg.Component)
MDNSTXTRecord = mdns_ns.struct("MDNSTXTRecord")
MDNSService = mdns_ns.struct("MDNSService")
def _remove_id_if_disabled(value):
value = value.copy()
if value[CONF_DISABLED]:
value.pop(CONF_ID)
return value
CONF_TXT = "txt"
SERVICE_SCHEMA = cv.Schema(
{
cv.Required(CONF_SERVICE): cv.string,
cv.Required(CONF_PROTOCOL): cv.string,
cv.Optional(CONF_PORT, default=0): cv.templatable(cv.Any(0, cv.port)),
cv.Optional(CONF_TXT, default={}): {cv.string: cv.templatable(cv.string)},
}
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(MDNSComponent),
cv.Optional(CONF_DISABLED, default=False): cv.boolean,
cv.Optional(CONF_SERVICES, default=[]): cv.ensure_list(SERVICE_SCHEMA),
}
),
_remove_id_if_disabled,
)
def mdns_service(
service: str, proto: str, port: int, txt_records: list[dict[str, str]]
):
return cg.StructInitializer(
MDNSService,
("service_type", cg.RawExpression(f"MDNS_STR({cg.safe_exp(service)})")),
("proto", cg.RawExpression(f"MDNS_STR({cg.safe_exp(proto)})")),
("port", port),
("txt_records", txt_records),
)
@coroutine_with_priority(CoroPriority.NETWORK_SERVICES)
async def to_code(config):
if config[CONF_DISABLED] is True:
return
if CORE.using_arduino:
if CORE.is_esp32:
cg.add_library("ESPmDNS", None)
elif CORE.is_esp8266:
cg.add_library("ESP8266mDNS", None)
elif CORE.is_rp2040:
cg.add_library("LEAmDNS", None)
if CORE.using_esp_idf:
add_idf_component(name="espressif/mdns", ref="1.8.2")
cg.add_define("USE_MDNS")
# Calculate compile-time service count
service_count = sum(
1 for key in COMPONENTS_WITH_MDNS_SERVICES if key in CORE.config
) + len(config[CONF_SERVICES])
if config[CONF_SERVICES]:
cg.add_define("USE_MDNS_EXTRA_SERVICES")
# Ensure at least 1 service (fallback service)
cg.add_define("MDNS_SERVICE_COUNT", max(1, service_count))
# Calculate compile-time dynamic TXT value count
# Dynamic values are those that cannot be stored in flash at compile time
dynamic_txt_count = 0
if "api" in CORE.config:
# Always: get_mac_address()
dynamic_txt_count += 1
# User-provided templatable TXT values (only lambdas, not static strings)
dynamic_txt_count += sum(
1
for service in config[CONF_SERVICES]
for txt_value in service[CONF_TXT].values()
if cg.is_template(txt_value)
)
# Ensure at least 1 to avoid zero-size array
cg.add_define("MDNS_DYNAMIC_TXT_COUNT", max(1, dynamic_txt_count))
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
for service in config[CONF_SERVICES]:
# Build the txt records list for the service
txt_records = []
for txt_key, txt_value in service[CONF_TXT].items():
if cg.is_template(txt_value):
# It's a lambda - evaluate and store using helper
templated_value = await cg.templatable(txt_value, [], cg.std_string)
safe_key = cg.safe_exp(txt_key)
dynamic_call = f"{var}->add_dynamic_txt_value(({templated_value})())"
txt_records.append(
cg.RawExpression(
f"{{MDNS_STR({safe_key}), MDNS_STR({dynamic_call})}}"
)
)
else:
# It's a static string - use directly in flash, no need to store in vector
txt_records.append(
cg.RawExpression(
f"{{MDNS_STR({cg.safe_exp(txt_key)}), MDNS_STR({cg.safe_exp(txt_value)})}}"
)
)
exp = mdns_service(
service[CONF_SERVICE],
service[CONF_PROTOCOL],
await cg.templatable(service[CONF_PORT], [], cg.uint16),
txt_records,
)
cg.add(var.add_extra_service(exp))
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"mdns_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"mdns_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"mdns_host.cpp": {PlatformFramework.HOST_NATIVE},
"mdns_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"mdns_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
}
)