fix(config_flow): bekanntes Geraet auch an der verkuerzten mDNS-MAC erkennen

Die Firmware wirbt im TXT-Satz nur mit den letzten vier Hex-Stellen der
MAC. Daraus wurde bisher direkt eine Kennung gebaut - die auf keinen
Eintrag passte. Folge: Home Assistant bot nach jedem Neustart dieselben,
laengst eingerichteten Strips erneut an, und beim Hinzufuegen kam
'bereits konfiguriert'.

Vier Stellen sind keine Kennung, aber ein Hinweis: endet genau ein
Eintrag so, ist er gemeint. Bei mehreren bleibt es unentschieden und der
Ablauf fragt nach - vier Stellen sind nicht eindeutig genug, um einen
fremden Eintrag stillschweigend umzuschreiben.

Die Tests sahen das nicht, weil ihr mDNS-Fixture die volle MAC eintrug -
eine Welt, die es am Geraet nicht gibt.
This commit is contained in:
Michael Sauer
2026-09-26 01:05:25 +02:00
parent 616515ef77
commit d1db07f522
2 changed files with 70 additions and 2 deletions
+27 -2
View File
@@ -97,13 +97,38 @@ class NinstripConfigFlow(ConfigFlow, domain=DOMAIN):
errors=errors, errors=errors,
) )
def _kennung_aus_mdns(self, roh: str) -> str | None:
"""Die Kennung zu dem, was im TXT-Satz unter "mac" steht.
Eine volle MAC ist die Kennung. Die Firmware wirbt aber nur mit den
letzten vier Hex-Stellen (`nl_wifi_mac4()`, siehe nl_mdns.c) - daraus
laesst sich keine bauen. Als *Hinweis* taugen sie trotzdem: endet
genau ein eingerichteter Eintrag so, ist er gemeint.
Bei mehreren Treffern bleibt es unentschieden. Dann lieber nachfragen
als den falschen Eintrag stillschweigend auf eine fremde Adresse
umzuschreiben - vier Stellen sind nicht eindeutig.
"""
kennung = format_mac(roh)
if len(kennung) == 17:
return kennung
kurz = roh.replace(":", "").replace("-", "").lower()
if len(kurz) != 4:
return None
treffer = [
eintrag.unique_id
for eintrag in self._async_current_entries(include_ignore=False)
if eintrag.unique_id and eintrag.unique_id.replace(":", "").endswith(kurz)
]
return treffer[0] if len(treffer) == 1 else None
async def async_step_zeroconf(self, discovery_info: ZeroconfServiceInfo) -> ConfigFlowResult: async def async_step_zeroconf(self, discovery_info: ZeroconfServiceInfo) -> ConfigFlowResult:
"""Gefunden ueber _ninstrip._tcp.""" """Gefunden ueber _ninstrip._tcp."""
self._host = discovery_info.host self._host = discovery_info.host
props = discovery_info.properties or {} props = discovery_info.properties or {}
mac = props.get("mac") mac = props.get("mac")
if mac: if mac and (kennung := self._kennung_aus_mdns(str(mac))):
await self.async_set_unique_id(format_mac(str(mac))) await self.async_set_unique_id(kennung)
self._abort_if_unique_id_configured(updates={CONF_HOST: self._host}) self._abort_if_unique_id_configured(updates={CONF_HOST: self._host})
self._discovered_name = str(props.get("name") or discovery_info.name.split(".")[0]) self._discovered_name = str(props.get("name") or discovery_info.name.split(".")[0])
self.context["title_placeholders"] = {"name": self._discovered_name} self.context["title_placeholders"] = {"name": self._discovered_name}
+43
View File
@@ -296,3 +296,46 @@ async def test_neu_konfigurieren_lehnt_ein_anderes_geraet_ab(
assert ergebnis["type"] is FlowResultType.ABORT assert ergebnis["type"] is FlowResultType.ABORT
assert ergebnis["reason"] == "wrong_device" assert ergebnis["reason"] == "wrong_device"
assert eintrag.data[CONF_HOST] == HOST_K1Y assert eintrag.data[CONF_HOST] == HOST_K1Y
async def test_mdns_mit_verkuerzter_mac_erkennt_das_bekannte_geraet(
hass: HomeAssistant, mock_client: MagicMock
) -> None:
"""Die Firmware wirbt nur mit den letzten vier Hex-Stellen der MAC.
Genau so steht es im TXT-Satz am Geraet (`mac=ad4c`). Wird daraus eine
Kennung gebaut, passt sie auf keinen Eintrag - und Home Assistant bietet
nach jedem Neustart dasselbe, laengst eingerichtete Geraet erneut an.
"""
eintrag = bestehender_eintrag(hass)
ergebnis = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf_info(mac="ad4c")
)
assert ergebnis["type"] is FlowResultType.ABORT
assert ergebnis["reason"] == "already_configured"
assert eintrag.unique_id == MAC_K1Y
async def test_mdns_mit_verkuerzter_mac_bleibt_bei_zwei_treffern_unentschieden(
hass: HomeAssistant, mock_client: MagicMock
) -> None:
"""Vier Hex-Stellen sind nicht eindeutig.
Enden zwei eingerichtete Geraete gleich, darf keines davon stillschweigend
auf die neue Adresse umgeschrieben werden - dann wird gefragt.
"""
erster = bestehender_eintrag(hass, "192.168.2.5")
zweiter = MockConfigEntry(
domain=DOMAIN,
title="Zwilling",
unique_id="aa:bb:cc:dd:ad:4c",
data={CONF_HOST: "192.168.2.6", CONF_TOKEN: TOKEN},
)
zweiter.add_to_hass(hass)
ergebnis = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf_info(mac="ad4c")
)
assert ergebnis["type"] is FlowResultType.FORM
assert erster.data[CONF_HOST] == "192.168.2.5"
assert zweiter.data[CONF_HOST] == "192.168.2.6"