Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions homeassistant/components/motionmount/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,21 @@ async def async_step_zeroconf(
# so we can avoid probing the device if its already
# configured or ignored
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured(
updates={CONF_HOST: host, CONF_PORT: port}
existing_entry = self._async_current_entries()
existing_host = next(
(
e.data.get(CONF_HOST, "")
for e in existing_entry
if e.unique_id == unique_id
),
"",
)
if not existing_host or existing_host.endswith(".local"):
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normalize the hostname before checking for an mDNS .local suffix (zeroconf hostnames in this integration include a trailing dot, e.g. MMF8A55F.local., so endswith(".local") will not match and may incorrectly treat an mDNS host as “static”).

Suggested change
if not existing_host or existing_host.endswith(".local"):
if not existing_host or existing_host.removesuffix(".").endswith(".local"):

Copilot uses AI. Check for mistakes.
host_updates = {CONF_HOST: host, CONF_PORT: port}
else:
host_updates = {CONF_PORT: port}
self.connection_data[CONF_HOST] = existing_host
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the self.connection_data[CONF_HOST] = existing_host assignment (this branch only runs when an entry with the same unique_id already exists and the flow immediately aborts, so mutating connection_data here is misleading and has no effect).

Suggested change
self.connection_data[CONF_HOST] = existing_host

Copilot uses AI. Check for mistakes.
self._abort_if_unique_id_configured(updates=host_updates)
else:
# Avoid probing devices that already have an entry
self._async_abort_entries_match({CONF_HOST: host})
Expand All @@ -131,9 +143,21 @@ async def async_step_zeroconf(

if unique_id:
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured(
updates={CONF_HOST: host, CONF_PORT: port}
existing_entry = self._async_current_entries()
existing_host = next(
(
e.data.get(CONF_HOST, "")
for e in existing_entry
if e.unique_id == unique_id
),
"",
)
host_updates = (
{CONF_HOST: host, CONF_PORT: port}
if not existing_host or existing_host.endswith(".local")
else {CONF_PORT: port}
)
self._abort_if_unique_id_configured(updates=host_updates)
Comment on lines +146 to +160
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apply the same mDNS-suffix normalization here as well (e.g., handle .local. with a trailing dot and case-insensitivity) so existing mDNS hosts aren’t misclassified as “static” and skipped for updates unintentionally.

Copilot uses AI. Check for mistakes.
else:
await self._async_handle_discovery_without_unique_id()

Expand Down
28 changes: 28 additions & 0 deletions tests/components/motionmount/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,34 @@ async def test_zeroconf_device_exists_abort(
assert result["reason"] == "already_configured"


async def test_zeroconf_does_not_overwrite_static_ip(
hass: HomeAssistant,
mock_motionmount: MagicMock,
) -> None:
"""Test zeroconf discovery does not overwrite a manually configured static IP."""
mock_config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=ZEROCONF_MAC,
data={
CONF_HOST: HOST,
CONF_PORT: PORT,
},
)
mock_config_entry.add_to_hass(hass)

discovery_info = dataclasses.replace(MOCK_ZEROCONF_TVM_SERVICE_INFO_V2)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=discovery_info,
)

assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
assert mock_config_entry.data[CONF_HOST] == HOST
assert mock_config_entry.data[CONF_HOST] != ZEROCONF_HOSTNAME


async def test_zeroconf_authentication_needed(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
Expand Down