[mdns] Advertise ota_signed when signed OTA verification is compiled in (#19407)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Keith Burzinski
2026-09-18 17:18:44 -05:00
committed by GitHub
co-authored by Claude Opus 5
parent aba49c3d4f
commit 623aec2984
4 changed files with 48 additions and 0 deletions
@@ -118,6 +118,9 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
}
#endif
#endif
#ifdef USE_OTA_SIGNED_VERIFICATION
txt_count++; // ota_signed
#endif
#ifdef ESPHOME_PROJECT_NAME
txt_count += 2; // project_name and project_version
#endif
@@ -186,6 +189,13 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
#endif
#endif
#ifdef USE_OTA_SIGNED_VERIFICATION
// Signals that an unsigned OTA image is rejected; serial flash is unaffected.
MDNS_STATIC_CONST_CHAR(TXT_OTA_SIGNED, "ota_signed");
MDNS_STATIC_CONST_CHAR(VALUE_TRUE, "1");
txt_records.push_back({MDNS_STR(TXT_OTA_SIGNED), MDNS_STR(VALUE_TRUE)});
#endif
#ifdef ESPHOME_PROJECT_NAME
MDNS_STATIC_CONST_CHAR(TXT_PROJECT_NAME, "project_name");
MDNS_STATIC_CONST_CHAR(TXT_PROJECT_VERSION, "project_version");
+5
View File
@@ -56,6 +56,7 @@ TXT_RECORD_PROJECT_VERSION = b"project_version"
TXT_RECORD_NETWORK = b"network"
TXT_RECORD_FRIENDLY_NAME = b"friendly_name"
TXT_RECORD_VERSION = b"version"
TXT_RECORD_OTA_SIGNED = b"ota_signed"
@dataclass
@@ -76,6 +77,8 @@ class DiscoveredImport:
project_name: str
project_version: str
network: str
# Defaults False so entries persisted before this field still load.
ota_signed: bool = False
class DashboardBrowser(AsyncServiceBrowser):
@@ -169,6 +172,7 @@ class DashboardImportDiscovery:
project_name = info.properties[TXT_RECORD_PROJECT_NAME].decode()
project_version = info.properties[TXT_RECORD_PROJECT_VERSION].decode()
network = info.properties.get(TXT_RECORD_NETWORK, b"wifi").decode()
ota_signed = info.properties.get(TXT_RECORD_OTA_SIGNED) == b"1"
friendly_name = info.properties.get(TXT_RECORD_FRIENDLY_NAME)
if friendly_name is not None:
friendly_name = friendly_name.decode()
@@ -180,6 +184,7 @@ class DashboardImportDiscovery:
project_name=project_name,
project_version=project_version,
network=network,
ota_signed=ota_signed,
)
is_new = name not in self.import_state
self.import_state[name] = discovered
@@ -0,0 +1,15 @@
# Signed OTA plus the native api: the only combination that emits ota_signed.
esp32:
variant: esp32s3
framework:
type: esp-idf
advanced:
signed_ota_verification:
wifi:
ssid: MySSID
password: password1
api:
mdns:
+18
View File
@@ -28,6 +28,7 @@ def _make_service_info(
network: str | None = "wifi",
friendly_name: str | None = "Living Room",
version: str | None = "2025.1.0",
ota_signed: str | None = None,
) -> MagicMock:
"""Build a fake ``AsyncServiceInfo`` with the TXT records we care about.
@@ -48,6 +49,8 @@ def _make_service_info(
properties[b"friendly_name"] = friendly_name.encode()
if version is not None:
properties[b"version"] = version.encode()
if ota_signed is not None:
properties[b"ota_signed"] = ota_signed.encode()
info.properties = properties
info.load_from_cache.return_value = True
return info
@@ -77,6 +80,7 @@ def test_added_service_populates_import_state_and_fires_callback() -> None:
assert entry.project_version == "1.0.0"
assert entry.network == "wifi"
assert entry.friendly_name == "Living Room"
assert entry.ota_signed is False
on_update.assert_called_once_with(name, entry)
@@ -206,6 +210,20 @@ def test_network_defaults_to_wifi_when_txt_absent() -> None:
assert discovery.import_state[name].network == "wifi"
def test_ota_signed_txt_is_parsed() -> None:
"""``ota_signed=1`` marks a device that only accepts signed OTA images.
Lets a consumer check whether it holds a key the device trusts before
an update is attempted, instead of failing at install time.
"""
discovery = DashboardImportDiscovery()
info = _make_service_info(ota_signed="1")
name = f"signed.{ESPHOME_SERVICE_TYPE}"
discovery._process_service_info(name, info)
assert discovery.import_state[name].ota_signed is True
def test_friendly_name_optional() -> None:
"""``friendly_name`` may be ``None`` if the device doesn't broadcast it.