diff --git a/homeassistant/components/guardian/config_flow.py b/homeassistant/components/guardian/config_flow.py index 5e7834f4a3f..05be79da344 100644 --- a/homeassistant/components/guardian/config_flow.py +++ b/homeassistant/components/guardian/config_flow.py @@ -4,13 +4,19 @@ from aioguardian.errors import GuardianError import voluptuous as vol from homeassistant import config_entries, core +from homeassistant.components.dhcp import IP_ADDRESS from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT from homeassistant.core import callback from .const import CONF_UID, DOMAIN, LOGGER +DEFAULT_PORT = 7777 + DATA_SCHEMA = vol.Schema( - {vol.Required(CONF_IP_ADDRESS): str, vol.Required(CONF_PORT, default=7777): int} + { + vol.Required(CONF_IP_ADDRESS): str, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, + } ) UNIQUE_ID = "guardian_{0}" @@ -53,7 +59,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_set_unique_id(self, pin): """Set the config entry's unique ID (based on the device's 4-digit PIN).""" await self.async_set_unique_id(UNIQUE_ID.format(pin)) - self._abort_if_unique_id_configured() + if self.discovery_info: + self._abort_if_unique_id_configured( + updates={CONF_IP_ADDRESS: self.discovery_info[CONF_IP_ADDRESS]} + ) + else: + self._abort_if_unique_id_configured() async def async_step_user(self, user_input=None): """Handle configuration via the UI.""" @@ -79,31 +90,38 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): title=info[CONF_UID], data={CONF_UID: info["uid"], **user_input} ) + async def async_step_dhcp(self, discovery_info): + """Handle the configuration via dhcp.""" + self.discovery_info = { + CONF_IP_ADDRESS: discovery_info[IP_ADDRESS], + CONF_PORT: DEFAULT_PORT, + } + return await self._async_handle_discovery() + async def async_step_zeroconf(self, discovery_info): """Handle the configuration via zeroconf.""" - if discovery_info is None: - return self.async_abort(reason="cannot_connect") - - pin = async_get_pin_from_discovery_hostname(discovery_info["hostname"]) - await self._async_set_unique_id(pin) - - self.context[CONF_IP_ADDRESS] = discovery_info["host"] - - if any( - discovery_info["host"] == flow["context"][CONF_IP_ADDRESS] - for flow in self._async_in_progress() - ): - return self.async_abort(reason="already_in_progress") - self.discovery_info = { CONF_IP_ADDRESS: discovery_info["host"], CONF_PORT: discovery_info["port"], } + pin = async_get_pin_from_discovery_hostname(discovery_info["hostname"]) + await self._async_set_unique_id(pin) + return await self._async_handle_discovery() - return await self.async_step_zeroconf_confirm() + async def _async_handle_discovery(self): + """Handle any discovery.""" + self.context[CONF_IP_ADDRESS] = self.discovery_info[CONF_IP_ADDRESS] + if any( + self.context[CONF_IP_ADDRESS] == flow["context"][CONF_IP_ADDRESS] + for flow in self._async_in_progress() + ): + return self.async_abort(reason="already_in_progress") - async def async_step_zeroconf_confirm(self, user_input=None): - """Finish the configuration via zeroconf.""" + return await self.async_step_discovery_confirm() + + async def async_step_discovery_confirm(self, user_input=None): + """Finish the configuration via any discovery.""" if user_input is None: - return self.async_show_form(step_id="zeroconf_confirm") + self._set_confirm_only() + return self.async_show_form(step_id="discovery_confirm") return await self.async_step_user(self.discovery_info) diff --git a/homeassistant/components/guardian/manifest.json b/homeassistant/components/guardian/manifest.json index 28f46a9bf14..60411c5292b 100644 --- a/homeassistant/components/guardian/manifest.json +++ b/homeassistant/components/guardian/manifest.json @@ -6,5 +6,15 @@ "requirements": ["aioguardian==1.0.4"], "zeroconf": ["_api._udp.local."], "codeowners": ["@bachya"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "dhcp": [ + { + "hostname": "gvc*", + "macaddress": "30AEA4*" + }, + { + "hostname": "guardian*", + "macaddress": "30AEA4*" + } + ] } diff --git a/homeassistant/components/guardian/strings.json b/homeassistant/components/guardian/strings.json index 59c838c7e2d..4c60bfe4572 100644 --- a/homeassistant/components/guardian/strings.json +++ b/homeassistant/components/guardian/strings.json @@ -8,7 +8,7 @@ "port": "[%key:common::config_flow::data::port%]" } }, - "zeroconf_confirm": { + "discovery_confirm": { "description": "Do you want to set up this Guardian device?" } }, diff --git a/homeassistant/components/guardian/translations/en.json b/homeassistant/components/guardian/translations/en.json index 3b038935662..310f550bcc1 100644 --- a/homeassistant/components/guardian/translations/en.json +++ b/homeassistant/components/guardian/translations/en.json @@ -6,15 +6,15 @@ "cannot_connect": "Failed to connect" }, "step": { + "discovery_confirm": { + "description": "Do you want to set up this Guardian device?" + }, "user": { "data": { "ip_address": "IP Address", "port": "Port" }, "description": "Configure a local Elexa Guardian device." - }, - "zeroconf_confirm": { - "description": "Do you want to set up this Guardian device?" } } } diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index d7b97dd29e9..368d0189ab4 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -66,6 +66,16 @@ DHCP = [ "domain": "flume", "hostname": "flume-gw-*" }, + { + "domain": "guardian", + "hostname": "gvc*", + "macaddress": "30AEA4*" + }, + { + "domain": "guardian", + "hostname": "guardian*", + "macaddress": "30AEA4*" + }, { "domain": "hunterdouglas_powerview", "hostname": "hunter*", diff --git a/tests/components/guardian/test_config_flow.py b/tests/components/guardian/test_config_flow.py index e9b53b4e629..4fbff7d7e48 100644 --- a/tests/components/guardian/test_config_flow.py +++ b/tests/components/guardian/test_config_flow.py @@ -4,12 +4,13 @@ from unittest.mock import patch from aioguardian.errors import GuardianError from homeassistant import data_entry_flow +from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS, MAC_ADDRESS from homeassistant.components.guardian import CONF_UID, DOMAIN from homeassistant.components.guardian.config_flow import ( async_get_pin_from_discovery_hostname, async_get_pin_from_uid, ) -from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF +from homeassistant.config_entries import SOURCE_DHCP, SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT from tests.common import MockConfigEntry @@ -95,7 +96,7 @@ async def test_step_zeroconf(hass, ping_client): DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf_data ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "zeroconf_confirm" + assert result["step_id"] == "discovery_confirm" result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -124,7 +125,7 @@ async def test_step_zeroconf_already_in_progress(hass): DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf_data ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "zeroconf_confirm" + assert result["step_id"] == "discovery_confirm" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf_data @@ -133,10 +134,48 @@ async def test_step_zeroconf_already_in_progress(hass): assert result["reason"] == "already_in_progress" -async def test_step_zeroconf_no_discovery_info(hass): - """Test the zeroconf step aborting because no discovery info came along.""" +async def test_step_dhcp(hass, ping_client): + """Test the dhcp step.""" + dhcp_data = { + IP_ADDRESS: "192.168.1.100", + HOSTNAME: "GVC1-ABCD.local.", + MAC_ADDRESS: "aa:bb:cc:dd:ee:ff", + } + result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_ZEROCONF} + DOMAIN, context={"source": SOURCE_DHCP}, data=dhcp_data ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "cannot_connect" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "discovery_confirm" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "ABCDEF123456" + assert result["data"] == { + CONF_IP_ADDRESS: "192.168.1.100", + CONF_PORT: 7777, + CONF_UID: "ABCDEF123456", + } + + +async def test_step_dhcp_already_in_progress(hass): + """Test the zeroconf step aborting because it's already in progress.""" + dhcp_data = { + IP_ADDRESS: "192.168.1.100", + HOSTNAME: "GVC1-ABCD.local.", + MAC_ADDRESS: "aa:bb:cc:dd:ee:ff", + } + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_DHCP}, data=dhcp_data + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "discovery_confirm" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_DHCP}, data=dhcp_data + ) + assert result["type"] == "abort" + assert result["reason"] == "already_in_progress"