diff --git a/homeassistant/components/yeelight/config_flow.py b/homeassistant/components/yeelight/config_flow.py index 73bbcdcfe5f..d59e03c965d 100644 --- a/homeassistant/components/yeelight/config_flow.py +++ b/homeassistant/components/yeelight/config_flow.py @@ -60,6 +60,17 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._discovered_ip = discovery_info[IP_ADDRESS] return await self._async_handle_discovery() + async def async_step_zeroconf(self, discovery_info): + """Handle discovery from zeroconf.""" + self._discovered_ip = discovery_info["host"] + await self.async_set_unique_id( + "{0:#0{1}x}".format(int(discovery_info["name"][-26:-18]), 18) + ) + self._abort_if_unique_id_configured( + updates={CONF_HOST: self._discovered_ip}, reload_on_update=False + ) + return await self._async_handle_discovery() + async def async_step_ssdp(self, discovery_info): """Handle discovery from ssdp.""" self._discovered_ip = urlparse(discovery_info["location"]).hostname diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 163a718c0bb..ca6fe09fe53 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -8,9 +8,12 @@ "dependencies": ["network"], "quality_scale": "platinum", "iot_class": "local_push", - "dhcp": [{ - "hostname": "yeelink-*" - }], + "dhcp": [ + { + "hostname": "yeelink-*" + } + ], + "zeroconf": [{ "type": "_miio._udp.local.", "name": "yeelink-*" }], "homekit": { "models": ["YL*"] } diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index cf94ff03a1c..da7c08df675 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -168,6 +168,10 @@ ZEROCONF = { }, { "domain": "xiaomi_miio" + }, + { + "domain": "yeelight", + "name": "yeelink-*" } ], "_nanoleafapi._tcp.local.": [ diff --git a/tests/components/yeelight/__init__.py b/tests/components/yeelight/__init__.py index 84035f61fdf..4d673dfaa94 100644 --- a/tests/components/yeelight/__init__.py +++ b/tests/components/yeelight/__init__.py @@ -37,6 +37,17 @@ CAPABILITIES = { "name": "", } +ID_DECIMAL = f"{int(ID, 16):08d}" + +ZEROCONF_DATA = { + "host": IP_ADDRESS, + "port": 54321, + "hostname": f"yeelink-light-strip1_miio{ID_DECIMAL}.local.", + "type": "_miio._udp.local.", + "name": f"yeelink-light-strip1_miio{ID_DECIMAL}._miio._udp.local.", + "properties": {"epoch": "1", "mac": "000000000000"}, +} + NAME = "name" SHORT_ID = hex(int("0x000000000015243f", 16)) UNIQUE_NAME = f"yeelight_{MODEL}_{SHORT_ID}" diff --git a/tests/components/yeelight/test_config_flow.py b/tests/components/yeelight/test_config_flow.py index 6bc3ba68275..8d4b7f48543 100644 --- a/tests/components/yeelight/test_config_flow.py +++ b/tests/components/yeelight/test_config_flow.py @@ -33,6 +33,7 @@ from . import ( MODULE_CONFIG_FLOW, NAME, UNIQUE_FRIENDLY_NAME, + ZEROCONF_DATA, _mocked_bulb, _patch_discovery, _patch_discovery_interval, @@ -576,3 +577,67 @@ async def test_discovered_ssdp(hass): assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + + +async def test_discovered_zeroconf(hass): + """Test we can setup when discovered from zeroconf.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + mocked_bulb = _mocked_bulb() + with _patch_discovery(), _patch_discovery_interval(), patch( + f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=ZEROCONF_DATA, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] is None + + with _patch_discovery(), _patch_discovery_interval(), patch( + f"{MODULE}.async_setup", return_value=True + ) as mock_async_setup, patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry: + result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["data"] == { + CONF_HOST: IP_ADDRESS, + CONF_ID: "0x000000000015243f", + CONF_MODEL: MODEL, + } + assert mock_async_setup.called + assert mock_async_setup_entry.called + + mocked_bulb = _mocked_bulb() + with _patch_discovery(), _patch_discovery_interval(), patch( + f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=ZEROCONF_DATA, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + mocked_bulb = _mocked_bulb() + with _patch_discovery(), _patch_discovery_interval(), patch( + f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=CAPABILITIES, + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured"