Include all SSDP data in discovery info (#28197)

* Include all SSDP data in discovery info

* Use UPnP device description as discovery info, inject some SSDP attrs

* Clean up attribute names

* Adapt existing SSDP flows to changed attribute names

* Prefix all SSDP UPnP attribute name constants with ATTR_UPNP, tweak a bit
This commit is contained in:
Ville Skyttä 2019-12-19 19:28:03 +02:00 committed by GitHub
parent 0cb468d7b0
commit d236a19139
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 128 additions and 125 deletions

View file

@ -1,5 +1,6 @@
"""Config flow to configure deCONZ component.""" """Config flow to configure deCONZ component."""
import asyncio import asyncio
from urllib.parse import urlparse
import async_timeout import async_timeout
from pydeconz.errors import RequestError, ResponseError from pydeconz.errors import RequestError, ResponseError
@ -7,7 +8,7 @@ from pydeconz.utils import async_discovery, async_get_api_key, async_get_gateway
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_SERIAL from homeassistant.components import ssdp
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
@ -26,7 +27,6 @@ from .const import (
DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de" DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de"
CONF_SERIAL = "serial" CONF_SERIAL = "serial"
ATTR_UUID = "udn"
@callback @callback
@ -170,18 +170,20 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_ssdp(self, discovery_info): async def async_step_ssdp(self, discovery_info):
"""Handle a discovered deCONZ bridge.""" """Handle a discovered deCONZ bridge."""
if discovery_info[ATTR_MANUFACTURERURL] != DECONZ_MANUFACTURERURL: if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != DECONZ_MANUFACTURERURL:
return self.async_abort(reason="not_deconz_bridge") return self.async_abort(reason="not_deconz_bridge")
uuid = discovery_info[ATTR_UUID].replace("uuid:", "") uuid = discovery_info[ssdp.ATTR_UPNP_UDN].replace("uuid:", "")
_LOGGER.debug("deCONZ gateway discovered (%s)", uuid) _LOGGER.debug("deCONZ gateway discovered (%s)", uuid)
parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION])
for entry in self.hass.config_entries.async_entries(DOMAIN): for entry in self.hass.config_entries.async_entries(DOMAIN):
if uuid == entry.data.get(CONF_UUID): if uuid == entry.data.get(CONF_UUID):
return await self._update_entry(entry, discovery_info[CONF_HOST]) return await self._update_entry(entry, parsed_url.hostname)
bridgeid = discovery_info[ATTR_SERIAL] bridgeid = discovery_info[ssdp.ATTR_UPNP_SERIAL]
if any( if any(
bridgeid == flow["context"][CONF_BRIDGEID] bridgeid == flow["context"][CONF_BRIDGEID]
for flow in self._async_in_progress() for flow in self._async_in_progress()
@ -190,11 +192,11 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
self.context[CONF_BRIDGEID] = bridgeid self.context[CONF_BRIDGEID] = bridgeid
self.context["title_placeholders"] = {"host": discovery_info[CONF_HOST]} self.context["title_placeholders"] = {"host": parsed_url.hostname}
self.deconz_config = { self.deconz_config = {
CONF_HOST: discovery_info[CONF_HOST], CONF_HOST: parsed_url.hostname,
CONF_PORT: discovery_info[CONF_PORT], CONF_PORT: parsed_url.port,
} }
return await self.async_step_link() return await self.async_step_link()

View file

@ -1,9 +1,12 @@
"""Config flow to configure Heos.""" """Config flow to configure Heos."""
from urllib.parse import urlparse
from pyheos import Heos, HeosError from pyheos import Heos, HeosError
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.components import ssdp
from homeassistant.const import CONF_HOST
from .const import DATA_DISCOVERED_HOSTS, DOMAIN from .const import DATA_DISCOVERED_HOSTS, DOMAIN
@ -23,11 +26,12 @@ class HeosFlowHandler(config_entries.ConfigFlow):
async def async_step_ssdp(self, discovery_info): async def async_step_ssdp(self, discovery_info):
"""Handle a discovered Heos device.""" """Handle a discovered Heos device."""
# Store discovered host # Store discovered host
hostname = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname
friendly_name = "{} ({})".format( friendly_name = "{} ({})".format(
discovery_info[CONF_NAME], discovery_info[CONF_HOST] discovery_info[ssdp.ATTR_UPNP_FRIENDLY_NAME], hostname
) )
self.hass.data.setdefault(DATA_DISCOVERED_HOSTS, {}) self.hass.data.setdefault(DATA_DISCOVERED_HOSTS, {})
self.hass.data[DATA_DISCOVERED_HOSTS][friendly_name] = discovery_info[CONF_HOST] self.hass.data[DATA_DISCOVERED_HOSTS][friendly_name] = hostname
# Abort if other flows in progress or an entry already exists # Abort if other flows in progress or an entry already exists
if self._async_in_progress() or self._async_current_entries(): if self._async_in_progress() or self._async_current_entries():
return self.async_abort(reason="already_setup") return self.async_abort(reason="already_setup")

View file

@ -3,6 +3,7 @@
from collections import OrderedDict from collections import OrderedDict
import logging import logging
from typing import Optional from typing import Optional
from urllib.parse import urlparse
from huawei_lte_api.AuthorizedConnection import AuthorizedConnection from huawei_lte_api.AuthorizedConnection import AuthorizedConnection
from huawei_lte_api.Client import Client from huawei_lte_api.Client import Client
@ -19,7 +20,7 @@ from url_normalize import url_normalize
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.ssdp import ATTR_HOST, ATTR_NAME, ATTR_PRESENTATIONURL from homeassistant.components import ssdp
from homeassistant.const import CONF_PASSWORD, CONF_RECIPIENT, CONF_URL, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_RECIPIENT, CONF_URL, CONF_USERNAME
from homeassistant.core import callback from homeassistant.core import callback
@ -208,13 +209,14 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle SSDP initiated config flow.""" """Handle SSDP initiated config flow."""
# Attempt to distinguish from other non-LTE Huawei router devices, at least # Attempt to distinguish from other non-LTE Huawei router devices, at least
# some ones we are interested in have "Mobile Wi-Fi" friendlyName. # some ones we are interested in have "Mobile Wi-Fi" friendlyName.
if "mobile" not in discovery_info.get(ATTR_NAME, "").lower(): if "mobile" not in discovery_info.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, "").lower():
return self.async_abort(reason="not_huawei_lte") return self.async_abort(reason="not_huawei_lte")
# https://github.com/PyCQA/pylint/issues/3167 # https://github.com/PyCQA/pylint/issues/3167
url = self.context[CONF_URL] = url_normalize( # pylint: disable=no-member url = self.context[CONF_URL] = url_normalize( # pylint: disable=no-member
discovery_info.get( discovery_info.get(
ATTR_PRESENTATIONURL, f"http://{discovery_info[ATTR_HOST]}/" ssdp.ATTR_UPNP_PRESENTATION_URL,
f"http://{urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname}/",
) )
) )

View file

@ -1,6 +1,7 @@
"""Config flow to configure Philips Hue.""" """Config flow to configure Philips Hue."""
import asyncio import asyncio
from typing import Dict, Optional from typing import Dict, Optional
from urllib.parse import urlparse
import aiohue import aiohue
from aiohue.discovery import discover_nupnp, normalize_bridge_id from aiohue.discovery import discover_nupnp, normalize_bridge_id
@ -8,7 +9,7 @@ import async_timeout
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries, core from homeassistant import config_entries, core
from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_NAME from homeassistant.components import ssdp
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from .bridge import authenticate_bridge from .bridge import authenticate_bridge
@ -147,22 +148,25 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
host is already configured and delegate to the import step if not. host is already configured and delegate to the import step if not.
""" """
# Filter out non-Hue bridges #1 # Filter out non-Hue bridges #1
if discovery_info[ATTR_MANUFACTURERURL] != HUE_MANUFACTURERURL: if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != HUE_MANUFACTURERURL:
return self.async_abort(reason="not_hue_bridge") return self.async_abort(reason="not_hue_bridge")
# Filter out non-Hue bridges #2 # Filter out non-Hue bridges #2
if any( if any(
name in discovery_info.get(ATTR_NAME, "") name in discovery_info.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, "")
for name in HUE_IGNORED_BRIDGE_NAMES for name in HUE_IGNORED_BRIDGE_NAMES
): ):
return self.async_abort(reason="not_hue_bridge") return self.async_abort(reason="not_hue_bridge")
if "host" not in discovery_info or "serial" not in discovery_info: if (
ssdp.ATTR_SSDP_LOCATION not in discovery_info
or ssdp.ATTR_UPNP_SERIAL not in discovery_info
):
return self.async_abort(reason="not_hue_bridge") return self.async_abort(reason="not_hue_bridge")
bridge = self._async_get_bridge( host = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname
discovery_info["host"], discovery_info["serial"]
) bridge = self._async_get_bridge(host, discovery_info[ssdp.ATTR_UPNP_SERIAL])
await self.async_set_unique_id(bridge.id) await self.async_set_unique_id(bridge.id)
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()

View file

@ -2,7 +2,6 @@
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
from urllib.parse import urlparse
import aiohttp import aiohttp
from defusedxml import ElementTree from defusedxml import ElementTree
@ -14,19 +13,19 @@ from homeassistant.helpers.event import async_track_time_interval
DOMAIN = "ssdp" DOMAIN = "ssdp"
SCAN_INTERVAL = timedelta(seconds=60) SCAN_INTERVAL = timedelta(seconds=60)
ATTR_HOST = "host" # Attributes for accessing info from SSDP response
ATTR_PORT = "port" ATTR_SSDP_LOCATION = "ssdp_location"
ATTR_SSDP_DESCRIPTION = "ssdp_description" ATTR_SSDP_ST = "ssdp_st"
ATTR_ST = "ssdp_st" # Attributes for accessing info from retrieved UPnP device description
ATTR_NAME = "name" ATTR_UPNP_DEVICE_TYPE = "deviceType"
ATTR_MODEL_NAME = "model_name" ATTR_UPNP_FRIENDLY_NAME = "friendlyName"
ATTR_MODEL_NUMBER = "model_number" ATTR_UPNP_MANUFACTURER = "manufacturer"
ATTR_SERIAL = "serial_number" ATTR_UPNP_MANUFACTURER_URL = "manufacturerURL"
ATTR_MANUFACTURER = "manufacturer" ATTR_UPNP_MODEL_NAME = "modelName"
ATTR_MANUFACTURERURL = "manufacturerURL" ATTR_UPNP_MODEL_NUMBER = "modelNumber"
ATTR_UDN = "udn" ATTR_UPNP_PRESENTATION_URL = "presentationURL"
ATTR_UPNP_DEVICE_TYPE = "upnp_device_type" ATTR_UPNP_SERIAL = "serialNumber"
ATTR_PRESENTATIONURL = "presentation_url" ATTR_UPNP_UDN = "UDN"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -157,24 +156,12 @@ class Scanner:
def info_from_entry(entry, device_info): def info_from_entry(entry, device_info):
"""Get most important info from an entry.""" """Get info from an entry."""
url = urlparse(entry.location)
info = { info = {
ATTR_HOST: url.hostname, ATTR_SSDP_LOCATION: entry.location,
ATTR_PORT: url.port, ATTR_SSDP_ST: entry.st,
ATTR_SSDP_DESCRIPTION: entry.location,
ATTR_ST: entry.st,
} }
if device_info: if device_info:
info[ATTR_NAME] = device_info.get("friendlyName") info.update(device_info)
info[ATTR_MODEL_NAME] = device_info.get("modelName")
info[ATTR_MODEL_NUMBER] = device_info.get("modelNumber")
info[ATTR_SERIAL] = device_info.get("serialNumber")
info[ATTR_MANUFACTURER] = device_info.get("manufacturer")
info[ATTR_MANUFACTURERURL] = device_info.get("manufacturerURL")
info[ATTR_UDN] = device_info.get("UDN")
info[ATTR_UPNP_DEVICE_TYPE] = device_info.get("deviceType")
info[ATTR_PRESENTATIONURL] = device_info.get("presentationURL")
return info return info

View file

@ -4,8 +4,8 @@ from unittest.mock import Mock, patch
import pydeconz import pydeconz
from homeassistant.components import ssdp
from homeassistant.components.deconz import config_flow from homeassistant.components.deconz import config_flow
from homeassistant.components.ssdp import ATTR_MANUFACTURERURL, ATTR_SERIAL
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -213,11 +213,10 @@ async def test_bridge_ssdp_discovery(hass):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, config_flow.DOMAIN,
data={ data={
config_flow.CONF_HOST: "1.2.3.4", ssdp.ATTR_SSDP_LOCATION: "http://1.2.3.4:80/",
config_flow.CONF_PORT: 80, ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.DECONZ_MANUFACTURERURL,
ATTR_SERIAL: "id", ssdp.ATTR_UPNP_SERIAL: "id",
ATTR_MANUFACTURERURL: config_flow.DECONZ_MANUFACTURERURL, ssdp.ATTR_UPNP_UDN: "uuid:1234",
config_flow.ATTR_UUID: "uuid:1234",
}, },
context={"source": "ssdp"}, context={"source": "ssdp"},
) )
@ -230,7 +229,7 @@ async def test_bridge_ssdp_discovery_not_deconz_bridge(hass):
"""Test a non deconz bridge being discovered over ssdp.""" """Test a non deconz bridge being discovered over ssdp."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, config_flow.DOMAIN,
data={ATTR_MANUFACTURERURL: "not deconz bridge"}, data={ssdp.ATTR_UPNP_MANUFACTURER_URL: "not deconz bridge"},
context={"source": "ssdp"}, context={"source": "ssdp"},
) )
@ -257,10 +256,10 @@ async def test_bridge_discovery_update_existing_entry(hass):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, config_flow.DOMAIN,
data={ data={
config_flow.CONF_HOST: "mock-deconz", ssdp.ATTR_SSDP_LOCATION: "http://mock-deconz/",
ATTR_SERIAL: "123ABC", ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.DECONZ_MANUFACTURERURL,
ATTR_MANUFACTURERURL: config_flow.DECONZ_MANUFACTURERURL, ssdp.ATTR_UPNP_SERIAL: "123ABC",
config_flow.ATTR_UUID: "uuid:456DEF", ssdp.ATTR_UPNP_UDN: "uuid:456DEF",
}, },
context={"source": "ssdp"}, context={"source": "ssdp"},
) )

View file

@ -145,11 +145,10 @@ async def test_update_address(hass):
await hass.config_entries.flow.async_init( await hass.config_entries.flow.async_init(
deconz.config_flow.DOMAIN, deconz.config_flow.DOMAIN,
data={ data={
deconz.config_flow.CONF_HOST: "2.3.4.5", ssdp.ATTR_SSDP_LOCATION: "http://2.3.4.5:80/",
deconz.config_flow.CONF_PORT: 80, ssdp.ATTR_UPNP_MANUFACTURER_URL: deconz.config_flow.DECONZ_MANUFACTURERURL,
ssdp.ATTR_SERIAL: BRIDGEID, ssdp.ATTR_UPNP_SERIAL: BRIDGEID,
ssdp.ATTR_MANUFACTURERURL: deconz.config_flow.DECONZ_MANUFACTURERURL, ssdp.ATTR_UPNP_UDN: "uuid:456DEF",
deconz.config_flow.ATTR_UUID: "uuid:456DEF",
}, },
context={"source": "ssdp"}, context={"source": "ssdp"},
) )

View file

@ -5,6 +5,7 @@ from asynctest.mock import Mock, patch as patch
from pyheos import Dispatcher, Heos, HeosPlayer, HeosSource, InputSource, const from pyheos import Dispatcher, Heos, HeosPlayer, HeosSource, InputSource, const
import pytest import pytest
from homeassistant.components import ssdp
from homeassistant.components.heos import DOMAIN from homeassistant.components.heos import DOMAIN
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
@ -118,16 +119,14 @@ def dispatcher_fixture() -> Dispatcher:
def discovery_data_fixture() -> dict: def discovery_data_fixture() -> dict:
"""Return mock discovery data for testing.""" """Return mock discovery data for testing."""
return { return {
"host": "127.0.0.1", ssdp.ATTR_SSDP_LOCATION: "http://127.0.0.1:60006/upnp/desc/aios_device/aios_device.xml",
"manufacturer": "Denon", ssdp.ATTR_UPNP_DEVICE_TYPE: "urn:schemas-denon-com:device:AiosDevice:1",
"model_name": "HEOS Drive", ssdp.ATTR_UPNP_FRIENDLY_NAME: "Office",
"model_number": "DWSA-10 4.0", ssdp.ATTR_UPNP_MANUFACTURER: "Denon",
"name": "Office", ssdp.ATTR_UPNP_MODEL_NAME: "HEOS Drive",
"port": 60006, ssdp.ATTR_UPNP_MODEL_NUMBER: "DWSA-10 4.0",
"serial": None, ssdp.ATTR_UPNP_SERIAL: None,
"ssdp_description": "http://127.0.0.1:60006/upnp/desc/aios_device/aios_device.xml", ssdp.ATTR_UPNP_UDN: "uuid:e61de70c-2250-1c22-0080-0005cdf512be",
"udn": "uuid:e61de70c-2250-1c22-0080-0005cdf512be",
"upnp_device_type": "urn:schemas-denon-com:device:AiosDevice:1",
} }

View file

@ -1,10 +1,13 @@
"""Tests for the Heos config flow module.""" """Tests for the Heos config flow module."""
from urllib.parse import urlparse
from pyheos import HeosError from pyheos import HeosError
from homeassistant import data_entry_flow from homeassistant import data_entry_flow
from homeassistant.components import ssdp
from homeassistant.components.heos.config_flow import HeosFlowHandler from homeassistant.components.heos.config_flow import HeosFlowHandler
from homeassistant.components.heos.const import DATA_DISCOVERED_HOSTS, DOMAIN from homeassistant.components.heos.const import DATA_DISCOVERED_HOSTS, DOMAIN
from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.const import CONF_HOST
async def test_flow_aborts_already_setup(hass, config_entry): async def test_flow_aborts_already_setup(hass, config_entry):
@ -79,8 +82,9 @@ async def test_discovery_shows_create_form(hass, controller, discovery_data):
assert len(hass.config_entries.flow.async_progress()) == 1 assert len(hass.config_entries.flow.async_progress()) == 1
assert hass.data[DATA_DISCOVERED_HOSTS] == {"Office (127.0.0.1)": "127.0.0.1"} assert hass.data[DATA_DISCOVERED_HOSTS] == {"Office (127.0.0.1)": "127.0.0.1"}
discovery_data[CONF_HOST] = "127.0.0.2" port = urlparse(discovery_data[ssdp.ATTR_SSDP_LOCATION]).port
discovery_data[CONF_NAME] = "Bedroom" discovery_data[ssdp.ATTR_SSDP_LOCATION] = f"http://127.0.0.2:{port}/"
discovery_data[ssdp.ATTR_UPNP_FRIENDLY_NAME] = "Bedroom"
await hass.config_entries.flow.async_init( await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "ssdp"}, data=discovery_data DOMAIN, context={"source": "ssdp"}, data=discovery_data
) )

View file

@ -7,22 +7,9 @@ from requests.exceptions import ConnectionError
from requests_mock import ANY from requests_mock import ANY
from homeassistant import data_entry_flow from homeassistant import data_entry_flow
from homeassistant.components import ssdp
from homeassistant.components.huawei_lte.config_flow import ConfigFlowHandler from homeassistant.components.huawei_lte.config_flow import ConfigFlowHandler
from homeassistant.components.huawei_lte.const import DOMAIN from homeassistant.components.huawei_lte.const import DOMAIN
from homeassistant.components.ssdp import (
ATTR_HOST,
ATTR_MANUFACTURER,
ATTR_MANUFACTURERURL,
ATTR_MODEL_NAME,
ATTR_MODEL_NUMBER,
ATTR_NAME,
ATTR_PORT,
ATTR_PRESENTATIONURL,
ATTR_SERIAL,
ATTR_ST,
ATTR_UDN,
ATTR_UPNP_DEVICE_TYPE,
)
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -156,18 +143,17 @@ async def test_ssdp(flow):
url = "http://192.168.100.1/" url = "http://192.168.100.1/"
result = await flow.async_step_ssdp( result = await flow.async_step_ssdp(
discovery_info={ discovery_info={
ATTR_ST: "upnp:rootdevice", ssdp.ATTR_SSDP_LOCATION: "http://192.168.100.1:60957/rootDesc.xml",
ATTR_PORT: 60957, ssdp.ATTR_SSDP_ST: "upnp:rootdevice",
ATTR_HOST: "192.168.100.1", ssdp.ATTR_UPNP_DEVICE_TYPE: "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
ATTR_MANUFACTURER: "Huawei", ssdp.ATTR_UPNP_FRIENDLY_NAME: "Mobile Wi-Fi",
ATTR_MANUFACTURERURL: "http://www.huawei.com/", ssdp.ATTR_UPNP_MANUFACTURER: "Huawei",
ATTR_MODEL_NAME: "Huawei router", ssdp.ATTR_UPNP_MANUFACTURER_URL: "http://www.huawei.com/",
ATTR_MODEL_NUMBER: "12345678", ssdp.ATTR_UPNP_MODEL_NAME: "Huawei router",
ATTR_NAME: "Mobile Wi-Fi", ssdp.ATTR_UPNP_MODEL_NUMBER: "12345678",
ATTR_PRESENTATIONURL: url, ssdp.ATTR_UPNP_PRESENTATION_URL: url,
ATTR_SERIAL: "00000000", ssdp.ATTR_UPNP_SERIAL: "00000000",
ATTR_UDN: "uuid:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", ssdp.ATTR_UPNP_UDN: "uuid:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
ATTR_UPNP_DEVICE_TYPE: "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
} }
) )

View file

@ -7,6 +7,7 @@ import pytest
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries, data_entry_flow from homeassistant import config_entries, data_entry_flow
from homeassistant.components import ssdp
from homeassistant.components.hue import config_flow, const from homeassistant.components.hue import config_flow, const
from tests.common import MockConfigEntry, mock_coro from tests.common import MockConfigEntry, mock_coro
@ -208,9 +209,9 @@ async def test_bridge_ssdp(hass):
result = await flow.async_step_ssdp( result = await flow.async_step_ssdp(
{ {
"host": "0.0.0.0", ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/",
"serial": "1234", ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL,
"manufacturerURL": config_flow.HUE_MANUFACTURERURL, ssdp.ATTR_UPNP_SERIAL: "1234",
} }
) )
@ -224,7 +225,7 @@ async def test_bridge_ssdp_discover_other_bridge(hass):
flow.hass = hass flow.hass = hass
result = await flow.async_step_ssdp( result = await flow.async_step_ssdp(
{"manufacturerURL": "http://www.notphilips.com"} {ssdp.ATTR_UPNP_MANUFACTURER_URL: "http://www.notphilips.com"}
) )
assert result["type"] == "abort" assert result["type"] == "abort"
@ -238,10 +239,10 @@ async def test_bridge_ssdp_emulated_hue(hass):
result = await flow.async_step_ssdp( result = await flow.async_step_ssdp(
{ {
"name": "HASS Bridge", ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/",
"host": "0.0.0.0", ssdp.ATTR_UPNP_FRIENDLY_NAME: "HASS Bridge",
"serial": "1234", ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL,
"manufacturerURL": config_flow.HUE_MANUFACTURERURL, ssdp.ATTR_UPNP_SERIAL: "1234",
} }
) )
@ -257,10 +258,10 @@ async def test_bridge_ssdp_espalexa(hass):
result = await flow.async_step_ssdp( result = await flow.async_step_ssdp(
{ {
"name": "Espalexa (0.0.0.0)", ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/",
"host": "0.0.0.0", ssdp.ATTR_UPNP_FRIENDLY_NAME: "Espalexa (0.0.0.0)",
"serial": "1234", ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL,
"manufacturerURL": config_flow.HUE_MANUFACTURERURL, ssdp.ATTR_UPNP_SERIAL: "1234",
} }
) )
@ -281,9 +282,9 @@ async def test_bridge_ssdp_already_configured(hass):
with pytest.raises(data_entry_flow.AbortFlow): with pytest.raises(data_entry_flow.AbortFlow):
await flow.async_step_ssdp( await flow.async_step_ssdp(
{ {
"host": "0.0.0.0", ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/",
"serial": "1234", ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL,
"manufacturerURL": config_flow.HUE_MANUFACTURERURL, ssdp.ATTR_UPNP_SERIAL: "1234",
} }
) )

View file

@ -27,7 +27,9 @@ async def test_scan_match_st(hass):
assert mock_init.mock_calls[0][2]["context"] == {"source": "ssdp"} assert mock_init.mock_calls[0][2]["context"] == {"source": "ssdp"}
@pytest.mark.parametrize("key", ("manufacturer", "deviceType")) @pytest.mark.parametrize(
"key", (ssdp.ATTR_UPNP_MANUFACTURER, ssdp.ATTR_UPNP_DEVICE_TYPE)
)
async def test_scan_match_upnp_devicedesc(hass, aioclient_mock, key): async def test_scan_match_upnp_devicedesc(hass, aioclient_mock, key):
"""Test matching based on UPnP device description data.""" """Test matching based on UPnP device description data."""
aioclient_mock.get( aioclient_mock.get(
@ -74,7 +76,14 @@ async def test_scan_not_all_present(hass, aioclient_mock):
return_value=[Mock(st="mock-st", location="http://1.1.1.1")], return_value=[Mock(st="mock-st", location="http://1.1.1.1")],
), patch.dict( ), patch.dict(
gn_ssdp.SSDP, gn_ssdp.SSDP,
{"mock-domain": [{"deviceType": "Paulus", "manufacturer": "Paulus"}]}, {
"mock-domain": [
{
ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus",
ssdp.ATTR_UPNP_MANUFACTURER: "Paulus",
}
]
},
), patch.object( ), patch.object(
hass.config_entries.flow, "async_init", return_value=mock_coro() hass.config_entries.flow, "async_init", return_value=mock_coro()
) as mock_init: ) as mock_init:
@ -103,7 +112,14 @@ async def test_scan_not_all_match(hass, aioclient_mock):
return_value=[Mock(st="mock-st", location="http://1.1.1.1")], return_value=[Mock(st="mock-st", location="http://1.1.1.1")],
), patch.dict( ), patch.dict(
gn_ssdp.SSDP, gn_ssdp.SSDP,
{"mock-domain": [{"deviceType": "Paulus", "manufacturer": "Not-Paulus"}]}, {
"mock-domain": [
{
ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus",
ssdp.ATTR_UPNP_MANUFACTURER: "Not-Paulus",
}
]
},
), patch.object( ), patch.object(
hass.config_entries.flow, "async_init", return_value=mock_coro() hass.config_entries.flow, "async_init", return_value=mock_coro()
) as mock_init: ) as mock_init: