dlna_dmr won't support devices that don't provide all DMR services (#58374)
parent
a9a74e0415
commit
44aa1fdc66
|
@ -216,6 +216,19 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
if _is_ignored_device(discovery_info):
|
||||
return self.async_abort(reason="alternative_integration")
|
||||
|
||||
# Abort if the device doesn't support all services required for a DmrDevice.
|
||||
# Use the discovery_info instead of DmrDevice.is_profile_device to avoid
|
||||
# contacting the device again.
|
||||
discovery_service_list = discovery_info.get(ssdp.ATTR_UPNP_SERVICE_LIST)
|
||||
if not discovery_service_list:
|
||||
return self.async_abort(reason="not_dmr")
|
||||
discovery_service_ids = {
|
||||
service.get("serviceId")
|
||||
for service in discovery_service_list.get("service") or []
|
||||
}
|
||||
if not DmrDevice.SERVICE_IDS.issubset(discovery_service_ids):
|
||||
return self.async_abort(reason="not_dmr")
|
||||
|
||||
# Abort if a migration flow for the device's location is in progress
|
||||
for progress in self._async_in_progress(include_uninitialized=True):
|
||||
if progress["context"].get("unique_id") == self._location:
|
||||
|
@ -277,10 +290,10 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
except UpnpError as err:
|
||||
raise ConnectError("cannot_connect") from err
|
||||
|
||||
try:
|
||||
if not DmrDevice.is_profile_device(device):
|
||||
raise ConnectError("not_dmr")
|
||||
|
||||
device = find_device_of_type(device, DmrDevice.DEVICE_TYPES)
|
||||
except UpnpError as err:
|
||||
raise ConnectError("not_dmr") from err
|
||||
|
||||
if not self._udn:
|
||||
self._udn = device.udn
|
||||
|
|
|
@ -30,11 +30,11 @@
|
|||
"discovery_error": "Failed to discover a matching DLNA device",
|
||||
"incomplete_config": "Configuration is missing a required variable",
|
||||
"non_unique_id": "Multiple devices found with the same unique ID",
|
||||
"not_dmr": "Device is not a Digital Media Renderer"
|
||||
"not_dmr": "Device is not a supported Digital Media Renderer"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"not_dmr": "Device is not a Digital Media Renderer"
|
||||
"not_dmr": "Device is not a supported Digital Media Renderer"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
"discovery_error": "Failed to discover a matching DLNA device",
|
||||
"incomplete_config": "Configuration is missing a required variable",
|
||||
"non_unique_id": "Multiple devices found with the same unique ID",
|
||||
"not_dmr": "Device is not a Digital Media Renderer"
|
||||
"not_dmr": "Device is not a supported Digital Media Renderer"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect",
|
||||
"could_not_connect": "Failed to connect to DLNA device",
|
||||
"not_dmr": "Device is not a Digital Media Renderer"
|
||||
"not_dmr": "Device is not a supported Digital Media Renderer"
|
||||
},
|
||||
"flow_title": "{name}",
|
||||
"step": {
|
||||
|
|
|
@ -51,6 +51,7 @@ ATTR_UPNP_MODEL_NAME = "modelName"
|
|||
ATTR_UPNP_MODEL_NUMBER = "modelNumber"
|
||||
ATTR_UPNP_MODEL_URL = "modelURL"
|
||||
ATTR_UPNP_SERIAL = "serialNumber"
|
||||
ATTR_UPNP_SERVICE_LIST = "serviceList"
|
||||
ATTR_UPNP_UDN = "UDN"
|
||||
ATTR_UPNP_UPC = "UPC"
|
||||
ATTR_UPNP_PRESENTATION_URL = "presentationURL"
|
||||
|
|
|
@ -5,7 +5,7 @@ from collections.abc import Iterable
|
|||
from socket import AddressFamily # pylint: disable=no-name-in-module
|
||||
from unittest.mock import Mock, create_autospec, patch, seal
|
||||
|
||||
from async_upnp_client import UpnpDevice, UpnpFactory
|
||||
from async_upnp_client import UpnpDevice, UpnpFactory, UpnpService
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.dlna_dmr.const import DOMAIN as DLNA_DOMAIN
|
||||
|
@ -49,6 +49,26 @@ def domain_data_mock(hass: HomeAssistant) -> Iterable[Mock]:
|
|||
upnp_device.parent_device = None
|
||||
upnp_device.root_device = upnp_device
|
||||
upnp_device.all_devices = [upnp_device]
|
||||
upnp_device.services = {
|
||||
"urn:schemas-upnp-org:service:AVTransport:1": create_autospec(
|
||||
UpnpService,
|
||||
instance=True,
|
||||
service_type="urn:schemas-upnp-org:service:AVTransport:1",
|
||||
service_id="urn:upnp-org:serviceId:AVTransport",
|
||||
),
|
||||
"urn:schemas-upnp-org:service:ConnectionManager:1": create_autospec(
|
||||
UpnpService,
|
||||
instance=True,
|
||||
service_type="urn:schemas-upnp-org:service:ConnectionManager:1",
|
||||
service_id="urn:upnp-org:serviceId:ConnectionManager",
|
||||
),
|
||||
"urn:schemas-upnp-org:service:RenderingControl:1": create_autospec(
|
||||
UpnpService,
|
||||
instance=True,
|
||||
service_type="urn:schemas-upnp-org:service:RenderingControl:1",
|
||||
service_id="urn:upnp-org:serviceId:RenderingControl",
|
||||
),
|
||||
}
|
||||
seal(upnp_device)
|
||||
domain_data.upnp_factory.async_create_device.return_value = upnp_device
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ from homeassistant.const import (
|
|||
CONF_URL,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||
|
||||
from .conftest import (
|
||||
MOCK_DEVICE_LOCATION,
|
||||
|
@ -51,13 +52,38 @@ MOCK_CONFIG_IMPORT_DATA = {
|
|||
|
||||
MOCK_ROOT_DEVICE_UDN = "ROOT_DEVICE"
|
||||
|
||||
MOCK_DISCOVERY = {
|
||||
MOCK_DISCOVERY: DiscoveryInfoType = {
|
||||
ssdp.ATTR_SSDP_LOCATION: MOCK_DEVICE_LOCATION,
|
||||
ssdp.ATTR_SSDP_UDN: MOCK_DEVICE_UDN,
|
||||
ssdp.ATTR_SSDP_ST: MOCK_DEVICE_TYPE,
|
||||
ssdp.ATTR_UPNP_UDN: MOCK_ROOT_DEVICE_UDN,
|
||||
ssdp.ATTR_UPNP_DEVICE_TYPE: MOCK_DEVICE_TYPE,
|
||||
ssdp.ATTR_UPNP_FRIENDLY_NAME: MOCK_DEVICE_NAME,
|
||||
ssdp.ATTR_UPNP_SERVICE_LIST: {
|
||||
"service": [
|
||||
{
|
||||
"SCPDURL": "/AVTransport/scpd.xml",
|
||||
"controlURL": "/AVTransport/control.xml",
|
||||
"eventSubURL": "/AVTransport/event.xml",
|
||||
"serviceId": "urn:upnp-org:serviceId:AVTransport",
|
||||
"serviceType": "urn:schemas-upnp-org:service:AVTransport:1",
|
||||
},
|
||||
{
|
||||
"SCPDURL": "/ConnectionManager/scpd.xml",
|
||||
"controlURL": "/ConnectionManager/control.xml",
|
||||
"eventSubURL": "/ConnectionManager/event.xml",
|
||||
"serviceId": "urn:upnp-org:serviceId:ConnectionManager",
|
||||
"serviceType": "urn:schemas-upnp-org:service:ConnectionManager:1",
|
||||
},
|
||||
{
|
||||
"SCPDURL": "/RenderingControl/scpd.xml",
|
||||
"controlURL": "/RenderingControl/control.xml",
|
||||
"eventSubURL": "/RenderingControl/event.xml",
|
||||
"serviceId": "urn:upnp-org:serviceId:RenderingControl",
|
||||
"serviceType": "urn:schemas-upnp-org:service:RenderingControl:1",
|
||||
},
|
||||
]
|
||||
},
|
||||
ssdp.ATTR_HA_MATCHING_DOMAINS: {DLNA_DOMAIN},
|
||||
}
|
||||
|
||||
|
@ -197,6 +223,8 @@ async def test_user_flow_embedded_st(
|
|||
embedded_device.udn = MOCK_DEVICE_UDN
|
||||
embedded_device.device_type = MOCK_DEVICE_TYPE
|
||||
embedded_device.name = MOCK_DEVICE_NAME
|
||||
embedded_device.services = upnp_device.services
|
||||
upnp_device.services = {}
|
||||
upnp_device.all_devices.append(embedded_device)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
@ -552,9 +580,38 @@ async def test_ssdp_flow_upnp_udn(
|
|||
assert config_entry_mock.data[CONF_URL] == NEW_DEVICE_LOCATION
|
||||
|
||||
|
||||
async def test_ssdp_missing_services(hass: HomeAssistant) -> None:
|
||||
"""Test SSDP ignores devices that are missing required services."""
|
||||
# No services defined at all
|
||||
discovery = dict(MOCK_DISCOVERY)
|
||||
del discovery[ssdp.ATTR_UPNP_SERVICE_LIST]
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DLNA_DOMAIN,
|
||||
context={"source": config_entries.SOURCE_SSDP},
|
||||
data=discovery,
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "not_dmr"
|
||||
|
||||
# AVTransport service is missing
|
||||
discovery = dict(MOCK_DISCOVERY)
|
||||
discovery[ssdp.ATTR_UPNP_SERVICE_LIST] = {
|
||||
"service": [
|
||||
service
|
||||
for service in discovery[ssdp.ATTR_UPNP_SERVICE_LIST]["service"]
|
||||
if service.get("serviceId") != "urn:upnp-org:serviceId:AVTransport"
|
||||
]
|
||||
}
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DLNA_DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=discovery
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "not_dmr"
|
||||
|
||||
|
||||
async def test_ssdp_ignore_device(hass: HomeAssistant) -> None:
|
||||
"""Test SSDP discovery ignores certain devices."""
|
||||
discovery = MOCK_DISCOVERY.copy()
|
||||
discovery = dict(MOCK_DISCOVERY)
|
||||
discovery[ssdp.ATTR_HA_MATCHING_DOMAINS] = {DLNA_DOMAIN, "other_domain"}
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DLNA_DOMAIN,
|
||||
|
@ -564,7 +621,7 @@ async def test_ssdp_ignore_device(hass: HomeAssistant) -> None:
|
|||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "alternative_integration"
|
||||
|
||||
discovery = MOCK_DISCOVERY.copy()
|
||||
discovery = dict(MOCK_DISCOVERY)
|
||||
discovery[ssdp.ATTR_UPNP_DEVICE_TYPE] = "urn:schemas-upnp-org:device:ZonePlayer:1"
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DLNA_DOMAIN,
|
||||
|
|
Loading…
Reference in New Issue