Remove SSDP discovery from Hue (#85506)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Franck Nijhof <git@frenck.dev>pull/86040/head
parent
1afb4897a8
commit
fe583b7c4a
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
|||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import aiohttp
|
||||
from aiohue import LinkButtonNotPressed, create_app_key
|
||||
|
@ -15,7 +14,7 @@ import slugify as unicode_slug
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import ssdp, zeroconf
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.const import CONF_API_KEY, CONF_HOST
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
|
@ -201,53 +200,6 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
},
|
||||
)
|
||||
|
||||
async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult:
|
||||
"""Handle a discovered Hue bridge.
|
||||
|
||||
This flow is triggered by the SSDP component. It will check if the
|
||||
host is already configured and delegate to the import step if not.
|
||||
"""
|
||||
# Filter out non-Hue bridges #1
|
||||
if (
|
||||
discovery_info.upnp.get(ssdp.ATTR_UPNP_MANUFACTURER_URL)
|
||||
not in HUE_MANUFACTURERURL
|
||||
):
|
||||
return self.async_abort(reason="not_hue_bridge")
|
||||
|
||||
# Filter out non-Hue bridges #2
|
||||
if any(
|
||||
name in discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, "")
|
||||
for name in HUE_IGNORED_BRIDGE_NAMES
|
||||
):
|
||||
return self.async_abort(reason="not_hue_bridge")
|
||||
|
||||
if (
|
||||
not discovery_info.ssdp_location
|
||||
or ssdp.ATTR_UPNP_SERIAL not in discovery_info.upnp
|
||||
):
|
||||
return self.async_abort(reason="not_hue_bridge")
|
||||
|
||||
url = urlparse(discovery_info.ssdp_location)
|
||||
if not url.hostname:
|
||||
return self.async_abort(reason="not_hue_bridge")
|
||||
|
||||
# Ignore if host is IPv6
|
||||
if is_ipv6_address(url.hostname):
|
||||
return self.async_abort(reason="invalid_host")
|
||||
|
||||
# abort if we already have exactly this bridge id/host
|
||||
# reload the integration if the host got updated
|
||||
bridge_id = normalize_bridge_id(discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL])
|
||||
await self.async_set_unique_id(bridge_id)
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: url.hostname}, reload_on_update=True
|
||||
)
|
||||
|
||||
self.bridge = await self._get_bridge(
|
||||
url.hostname, discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL]
|
||||
)
|
||||
return await self.async_step_link()
|
||||
|
||||
async def async_step_zeroconf(
|
||||
self, discovery_info: zeroconf.ZeroconfServiceInfo
|
||||
) -> FlowResult:
|
||||
|
|
|
@ -4,20 +4,6 @@
|
|||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/hue",
|
||||
"requirements": ["aiohue==4.6.1"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Royal Philips Electronics",
|
||||
"modelName": "Philips hue bridge 2012"
|
||||
},
|
||||
{
|
||||
"manufacturer": "Royal Philips Electronics",
|
||||
"modelName": "Philips hue bridge 2015"
|
||||
},
|
||||
{
|
||||
"manufacturer": "Signify",
|
||||
"modelName": "Philips hue bridge 2015"
|
||||
}
|
||||
],
|
||||
"homekit": {
|
||||
"models": ["BSB002"]
|
||||
},
|
||||
|
|
|
@ -155,20 +155,6 @@ SSDP = {
|
|||
"manufacturer": "SOYEA TECHNOLOGY CO., LTD.",
|
||||
},
|
||||
],
|
||||
"hue": [
|
||||
{
|
||||
"manufacturer": "Royal Philips Electronics",
|
||||
"modelName": "Philips hue bridge 2012",
|
||||
},
|
||||
{
|
||||
"manufacturer": "Royal Philips Electronics",
|
||||
"modelName": "Philips hue bridge 2015",
|
||||
},
|
||||
{
|
||||
"manufacturer": "Signify",
|
||||
"modelName": "Philips hue bridge 2015",
|
||||
},
|
||||
],
|
||||
"hyperion": [
|
||||
{
|
||||
"manufacturer": "Hyperion Open Source Ambient Lighting",
|
||||
|
|
|
@ -8,7 +8,7 @@ import pytest
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import ssdp, zeroconf
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.components.hue import config_flow, const
|
||||
from homeassistant.components.hue.errors import CannotConnect
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
@ -327,176 +327,6 @@ async def test_flow_link_cannot_connect(hass):
|
|||
assert result["reason"] == "cannot_connect"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mf_url", config_flow.HUE_MANUFACTURERURL)
|
||||
async def test_bridge_ssdp(hass, mf_url, aioclient_mock):
|
||||
"""Test a bridge being discovered."""
|
||||
create_mock_api_discovery(aioclient_mock, [("0.0.0.0", "1234")])
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=ssdp.SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
ssdp_location="http://0.0.0.0/",
|
||||
upnp={
|
||||
ssdp.ATTR_UPNP_MANUFACTURER_URL: mf_url,
|
||||
ssdp.ATTR_UPNP_SERIAL: "1234",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "link"
|
||||
|
||||
|
||||
async def test_bridge_ssdp_discover_other_bridge(hass):
|
||||
"""Test that discovery ignores other bridges."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=ssdp.SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
upnp={ssdp.ATTR_UPNP_MANUFACTURER_URL: "http://www.notphilips.com"},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "not_hue_bridge"
|
||||
|
||||
|
||||
async def test_bridge_ssdp_emulated_hue(hass):
|
||||
"""Test if discovery info is from an emulated hue instance."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=ssdp.SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
ssdp_location="http://0.0.0.0/",
|
||||
upnp={
|
||||
ssdp.ATTR_UPNP_FRIENDLY_NAME: "Home Assistant Bridge",
|
||||
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
|
||||
ssdp.ATTR_UPNP_SERIAL: "1234",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "not_hue_bridge"
|
||||
|
||||
|
||||
async def test_bridge_ssdp_missing_location(hass):
|
||||
"""Test if discovery info is missing a location attribute."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=ssdp.SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
upnp={
|
||||
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
|
||||
ssdp.ATTR_UPNP_SERIAL: "1234",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "not_hue_bridge"
|
||||
|
||||
|
||||
async def test_bridge_ssdp_missing_serial(hass):
|
||||
"""Test if discovery info is a serial attribute."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=ssdp.SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
ssdp_location="http://0.0.0.0/",
|
||||
upnp={
|
||||
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "not_hue_bridge"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"location,reason",
|
||||
(
|
||||
("http:///", "not_hue_bridge"),
|
||||
("http://[fd00::eeb5:faff:fe84:b17d]/description.xml", "invalid_host"),
|
||||
),
|
||||
)
|
||||
async def test_bridge_ssdp_invalid_location(hass, location, reason):
|
||||
"""Test if discovery info is a serial attribute."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=ssdp.SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
ssdp_location=location,
|
||||
upnp={
|
||||
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
|
||||
ssdp.ATTR_UPNP_SERIAL: "1234",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == reason
|
||||
|
||||
|
||||
async def test_bridge_ssdp_espalexa(hass):
|
||||
"""Test if discovery info is from an Espalexa based device."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=ssdp.SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
ssdp_location="http://0.0.0.0/",
|
||||
upnp={
|
||||
ssdp.ATTR_UPNP_FRIENDLY_NAME: "Espalexa (0.0.0.0)",
|
||||
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
|
||||
ssdp.ATTR_UPNP_SERIAL: "1234",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "not_hue_bridge"
|
||||
|
||||
|
||||
async def test_bridge_ssdp_already_configured(hass, aioclient_mock):
|
||||
"""Test if a discovered bridge has already been configured."""
|
||||
create_mock_api_discovery(aioclient_mock, [("0.0.0.0", "1234")])
|
||||
MockConfigEntry(
|
||||
domain="hue", unique_id="1234", data={"host": "0.0.0.0"}
|
||||
).add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=ssdp.SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
ssdp_location="http://0.0.0.0/",
|
||||
upnp={
|
||||
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
|
||||
ssdp.ATTR_UPNP_SERIAL: "1234",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_import_with_no_config(hass, aioclient_mock):
|
||||
"""Test importing a host without an existing config file."""
|
||||
create_mock_api_discovery(aioclient_mock, [("0.0.0.0", "1234")])
|
||||
|
@ -634,33 +464,6 @@ async def test_bridge_homekit_already_configured(hass, aioclient_mock):
|
|||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_ssdp_discovery_update_configuration(hass, aioclient_mock):
|
||||
"""Test if a discovered bridge is configured and updated with new host."""
|
||||
create_mock_api_discovery(aioclient_mock, [("1.1.1.1", "aabbccddeeff")])
|
||||
entry = MockConfigEntry(
|
||||
domain="hue", unique_id="aabbccddeeff", data={"host": "0.0.0.0"}
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=ssdp.SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
ssdp_location="http://1.1.1.1/",
|
||||
upnp={
|
||||
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0],
|
||||
ssdp.ATTR_UPNP_SERIAL: "aabbccddeeff",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
assert entry.data["host"] == "1.1.1.1"
|
||||
|
||||
|
||||
async def test_options_flow_v1(hass):
|
||||
"""Test options config flow for a V1 bridge."""
|
||||
entry = MockConfigEntry(
|
||||
|
@ -772,7 +575,7 @@ async def test_bridge_zeroconf_already_exists(hass, aioclient_mock):
|
|||
)
|
||||
entry = MockConfigEntry(
|
||||
domain="hue",
|
||||
source=config_entries.SOURCE_SSDP,
|
||||
source=config_entries.SOURCE_HOMEKIT,
|
||||
data={"host": "0.0.0.0"},
|
||||
unique_id="ecb5faabcabc",
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue