From 4badc291d9fecdf966627953fec8c769a07b45b0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 23 Mar 2026 12:03:52 -1000 Subject: [PATCH] Don't update ESPHome host when device is already connected (#166084) --- .../components/esphome/config_flow.py | 6 ++ tests/components/esphome/test_config_flow.py | 75 ++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index 7b6cf23e3ca..fd6803db85c 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -28,6 +28,7 @@ from homeassistant.config_entries import ( SOURCE_REAUTH, SOURCE_RECONFIGURE, ConfigEntry, + ConfigEntryState, ConfigFlow, ConfigFlowResult, FlowType, @@ -363,6 +364,11 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): # Don't probe to verify the mac is correct since # the host matches (and port matches if provided). raise AbortFlow("already_configured") + # If the entry is loaded and the device is currently connected, + # don't update the host. This prevents transient mDNS announcements + # (e.g., during WiFi mesh roaming) from overwriting a working connection. + if entry.state is ConfigEntryState.LOADED and entry.runtime_data.available: + raise AbortFlow("already_configured") configured_psk: str | None = entry.data.get(CONF_NOISE_PSK) await self._fetch_device_info(host, port or configured_port, configured_psk) updates: dict[str, Any] = {} diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py index 750b3f9af00..1d32ada5cbc 100644 --- a/tests/components/esphome/test_config_flow.py +++ b/tests/components/esphome/test_config_flow.py @@ -43,7 +43,7 @@ from homeassistant.helpers.service_info.mqtt import MqttServiceInfo from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo from . import VALID_NOISE_PSK -from .conftest import MockGenericDeviceEntryType +from .conftest import MockESPHomeDeviceType, MockGenericDeviceEntryType from tests.common import MockConfigEntry @@ -865,6 +865,79 @@ async def test_discovery_already_configured(hass: HomeAssistant) -> None: } +@pytest.mark.usefixtures("mock_zeroconf") +async def test_discovery_does_not_update_host_when_device_is_connected( + hass: HomeAssistant, + mock_client: APIClient, + mock_esphome_device: MockESPHomeDeviceType, +) -> None: + """Test zeroconf discovery does not update host when device is connected.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "192.168.1.2", + CONF_PORT: 6053, + CONF_PASSWORD: "", + }, + unique_id="11:22:33:44:55:aa", + ) + entry.add_to_hass(hass) + await mock_esphome_device(mock_client=mock_client, entry=entry) + + service_info = ZeroconfServiceInfo( + ip_address=ip_address("192.168.1.99"), + ip_addresses=[ip_address("192.168.1.99")], + hostname="test.local.", + name="mock_name", + port=6053, + properties={"mac": "1122334455aa"}, + type="mock_type", + ) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + # Host should NOT be updated since the device is currently connected + assert entry.data[CONF_HOST] == "192.168.1.2" + + +@pytest.mark.usefixtures("mock_zeroconf") +async def test_discovery_does_not_update_host_when_device_is_connected_dhcp( + hass: HomeAssistant, + mock_client: APIClient, + mock_esphome_device: MockESPHomeDeviceType, +) -> None: + """Test DHCP discovery does not update host when device is connected.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "192.168.1.2", + CONF_PORT: 6053, + CONF_PASSWORD: "", + }, + unique_id="11:22:33:44:55:aa", + ) + entry.add_to_hass(hass) + await mock_esphome_device(mock_client=mock_client, entry=entry) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=DhcpServiceInfo( + ip="192.168.1.99", + macaddress="1122334455aa", + hostname="test", + ), + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + # Host should NOT be updated since the device is currently connected + assert entry.data[CONF_HOST] == "192.168.1.2" + + @pytest.mark.usefixtures("mock_client", "mock_setup_entry", "mock_zeroconf") async def test_discovery_ignored(hass: HomeAssistant) -> None: """Test discovery does not probe and ignored entry."""