diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index fbfdca00b41..3fee1445758 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -119,6 +119,32 @@ class PlugwiseConfigFlow(ConfigFlow, domain=DOMAIN): _version = _properties.get("version", "n/a") _name = f"{ZEROCONF_MAP.get(_product, _product)} v{_version}" + # This is an Anna, but we already have config entries. + # Assuming that the user has already configured Adam, aborting discovery. + if self._async_current_entries() and _product == "smile_thermo": + return self.async_abort(reason="anna_with_adam") + + # If we have discovered an Adam or Anna, both might be on the network. + # In that case, we need to cancel the Anna flow, as the Adam should + # be added. + for flow in self._async_in_progress(): + # This is an Anna, and there is already an Adam flow in progress + if ( + _product == "smile_thermo" + and "context" in flow + and flow["context"].get("product") == "smile_open_therm" + ): + return self.async_abort(reason="anna_with_adam") + + # This is an Adam, and there is already an Anna flow in progress + if ( + _product == "smile_open_therm" + and "context" in flow + and flow["context"].get("product") == "smile_thermo" + and "flow_id" in flow + ): + self.hass.config_entries.flow.async_abort(flow["flow_id"]) + self.context.update( { "title_placeholders": { @@ -128,6 +154,7 @@ class PlugwiseConfigFlow(ConfigFlow, domain=DOMAIN): CONF_USERNAME: self._username, }, "configuration_url": f"http://{discovery_info.host}:{discovery_info.port}", + "product": _product, } ) return await self.async_step_user() diff --git a/homeassistant/components/plugwise/strings.json b/homeassistant/components/plugwise/strings.json index 42dcee96196..7278f6c4414 100644 --- a/homeassistant/components/plugwise/strings.json +++ b/homeassistant/components/plugwise/strings.json @@ -19,7 +19,8 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", + "anna_with_adam": "Both Anna and Adam detected. Add your Adam instead of your Anna" } } } diff --git a/tests/components/plugwise/test_config_flow.py b/tests/components/plugwise/test_config_flow.py index 66f0986682e..4dbe1d2615f 100644 --- a/tests/components/plugwise/test_config_flow.py +++ b/tests/components/plugwise/test_config_flow.py @@ -61,6 +61,34 @@ TEST_DISCOVERY2 = ZeroconfServiceInfo( type="mock_type", ) +TEST_DISCOVERY_ANNA = ZeroconfServiceInfo( + host=TEST_HOST, + addresses=[TEST_HOST], + hostname=f"{TEST_HOSTNAME}.local.", + name="mock_name", + port=DEFAULT_PORT, + properties={ + "product": "smile_thermo", + "version": "1.2.3", + "hostname": f"{TEST_HOSTNAME}.local.", + }, + type="mock_type", +) + +TEST_DISCOVERY_ADAM = ZeroconfServiceInfo( + host=TEST_HOST, + addresses=[TEST_HOST], + hostname=f"{TEST_HOSTNAME2}.local.", + name="mock_name", + port=DEFAULT_PORT, + properties={ + "product": "smile_open_therm", + "version": "1.2.3", + "hostname": f"{TEST_HOSTNAME2}.local.", + }, + type="mock_type", +) + @pytest.fixture(name="mock_smile") def mock_smile(): @@ -294,3 +322,61 @@ async def test_flow_errors( assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_smile_config_flow.connect.mock_calls) == 2 + + +async def test_zeroconf_abort_anna_with_existing_config_entries( + hass: HomeAssistant, + mock_smile_adam: MagicMock, + init_integration: MockConfigEntry, +) -> None: + """Test we abort Anna discovery with existing config entries.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_ZEROCONF}, + data=TEST_DISCOVERY_ANNA, + ) + assert result.get("type") == FlowResultType.ABORT + assert result.get("reason") == "anna_with_adam" + + +async def test_zeroconf_abort_anna_with_adam(hass: HomeAssistant) -> None: + """Test we abort Anna discovery when an Adam is also discovered.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_ZEROCONF}, + data=TEST_DISCOVERY_ANNA, + ) + assert result.get("type") == FlowResultType.FORM + assert result.get("step_id") == "user" + + flows_in_progress = hass.config_entries.flow.async_progress() + assert len(flows_in_progress) == 1 + assert flows_in_progress[0]["context"]["product"] == "smile_thermo" + + # Discover Adam, Anna should be aborted and no longer present + result2 = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_ZEROCONF}, + data=TEST_DISCOVERY_ADAM, + ) + + assert result2.get("type") == FlowResultType.FORM + assert result2.get("step_id") == "user" + + flows_in_progress = hass.config_entries.flow.async_progress() + assert len(flows_in_progress) == 1 + assert flows_in_progress[0]["context"]["product"] == "smile_open_therm" + + # Discover Anna again, Anna should be aborted directly + result3 = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_ZEROCONF}, + data=TEST_DISCOVERY_ANNA, + ) + assert result3.get("type") == FlowResultType.ABORT + assert result3.get("reason") == "anna_with_adam" + + # Adam should still be there + flows_in_progress = hass.config_entries.flow.async_progress() + assert len(flows_in_progress) == 1 + assert flows_in_progress[0]["context"]["product"] == "smile_open_therm"