From 5f2a13fec6ae026ae8b786a7723f8296d9d48fea Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 18 Dec 2023 15:52:48 +0100 Subject: [PATCH] Add DHCP discovery to Tailwind (#105981) --- .../components/tailwind/config_flow.py | 14 ++++++ .../components/tailwind/manifest.json | 5 ++ .../components/tailwind/strings.json | 1 + homeassistant/generated/dhcp.py | 4 ++ tests/components/tailwind/test_config_flow.py | 50 ++++++++++++++++++- 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tailwind/config_flow.py b/homeassistant/components/tailwind/config_flow.py index ae63bb1a5e2..97515f17f3f 100644 --- a/homeassistant/components/tailwind/config_flow.py +++ b/homeassistant/components/tailwind/config_flow.py @@ -15,6 +15,7 @@ from gotailwind import ( import voluptuous as vol from homeassistant.components import zeroconf +from homeassistant.components.dhcp import DhcpServiceInfo from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.data_entry_flow import AbortFlow, FlowResult @@ -182,6 +183,19 @@ class TailwindFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors, ) + async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult: + """Handle dhcp discovery to update existing entries. + + This flow is triggered only by DHCP discovery of known devices. + """ + await self.async_set_unique_id(format_mac(discovery_info.macaddress)) + self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip}) + + # This situation should never happen, as Home Assistant will only + # send updates for existing entries. In case it does, we'll just + # abort the flow with an unknown error. + return self.async_abort(reason="unknown") + async def _async_step_create_entry(self, *, host: str, token: str) -> FlowResult: """Create entry.""" tailwind = Tailwind( diff --git a/homeassistant/components/tailwind/manifest.json b/homeassistant/components/tailwind/manifest.json index b604a8b4886..5121c8408f0 100644 --- a/homeassistant/components/tailwind/manifest.json +++ b/homeassistant/components/tailwind/manifest.json @@ -3,6 +3,11 @@ "name": "Tailwind", "codeowners": ["@frenck"], "config_flow": true, + "dhcp": [ + { + "registered_devices": true + } + ], "documentation": "https://www.home-assistant.io/integrations/tailwind", "integration_type": "device", "iot_class": "local_polling", diff --git a/homeassistant/components/tailwind/strings.json b/homeassistant/components/tailwind/strings.json index 01a254ca0dc..bc765efa8d1 100644 --- a/homeassistant/components/tailwind/strings.json +++ b/homeassistant/components/tailwind/strings.json @@ -41,6 +41,7 @@ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "no_device_id": "The discovered Tailwind device did not provide a device ID.", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", + "unknown": "[%key:common::config_flow::error::unknown%]", "unsupported_firmware": "The firmware of your Tailwind device is not supported. Please update your Tailwind device to the latest firmware version using the Tailwind app." } }, diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 6d04d7602f2..33d069c5663 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -571,6 +571,10 @@ DHCP: list[dict[str, str | bool]] = [ "domain": "tado", "hostname": "tado*", }, + { + "domain": "tailwind", + "registered_devices": True, + }, { "domain": "tesla_wall_connector", "hostname": "teslawallconnector_*", diff --git a/tests/components/tailwind/test_config_flow.py b/tests/components/tailwind/test_config_flow.py index c6afc6e7aec..6d35ccea85a 100644 --- a/tests/components/tailwind/test_config_flow.py +++ b/tests/components/tailwind/test_config_flow.py @@ -11,8 +11,14 @@ import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components import zeroconf +from homeassistant.components.dhcp import DhcpServiceInfo from homeassistant.components.tailwind.const import DOMAIN -from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER, SOURCE_ZEROCONF +from homeassistant.config_entries import ( + SOURCE_DHCP, + SOURCE_REAUTH, + SOURCE_USER, + SOURCE_ZEROCONF, +) from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -378,3 +384,45 @@ async def test_reauth_flow_errors( assert result3.get("type") == FlowResultType.ABORT assert result3.get("reason") == "reauth_successful" + + +async def test_dhcp_discovery_updates_entry( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """Test DHCP discovery updates config entries.""" + mock_config_entry.add_to_hass(hass) + assert mock_config_entry.data[CONF_HOST] == "127.0.0.127" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DHCP}, + data=DhcpServiceInfo( + hostname="tailwind-3ce90e6d2184.local.", + ip="127.0.0.1", + macaddress="3c:e9:0e:6d:21:84", + ), + ) + + assert result.get("type") == FlowResultType.ABORT + assert result.get("reason") == "already_configured" + assert mock_config_entry.data[CONF_HOST] == "127.0.0.1" + + +async def test_dhcp_discovery_ignores_unknown(hass: HomeAssistant) -> None: + """Test DHCP discovery is only used for updates. + + Anything else will just abort the flow. + """ + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DHCP}, + data=DhcpServiceInfo( + hostname="tailwind-3ce90e6d2184.local.", + ip="127.0.0.1", + macaddress="3c:e9:0e:6d:21:84", + ), + ) + + assert result.get("type") == FlowResultType.ABORT + assert result.get("reason") == "unknown"