mirror of
https://github.com/home-assistant/core.git
synced 2025-04-28 11:17:53 +00:00
dlna_dmr won't support devices that don't provide all DMR services (#58374)
This commit is contained in:
parent
a9a74e0415
commit
44aa1fdc66
@ -216,6 +216,19 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
if _is_ignored_device(discovery_info):
|
if _is_ignored_device(discovery_info):
|
||||||
return self.async_abort(reason="alternative_integration")
|
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
|
# Abort if a migration flow for the device's location is in progress
|
||||||
for progress in self._async_in_progress(include_uninitialized=True):
|
for progress in self._async_in_progress(include_uninitialized=True):
|
||||||
if progress["context"].get("unique_id") == self._location:
|
if progress["context"].get("unique_id") == self._location:
|
||||||
@ -277,10 +290,10 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
except UpnpError as err:
|
except UpnpError as err:
|
||||||
raise ConnectError("cannot_connect") from 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)
|
device = find_device_of_type(device, DmrDevice.DEVICE_TYPES)
|
||||||
except UpnpError as err:
|
|
||||||
raise ConnectError("not_dmr") from err
|
|
||||||
|
|
||||||
if not self._udn:
|
if not self._udn:
|
||||||
self._udn = device.udn
|
self._udn = device.udn
|
||||||
|
@ -30,11 +30,11 @@
|
|||||||
"discovery_error": "Failed to discover a matching DLNA device",
|
"discovery_error": "Failed to discover a matching DLNA device",
|
||||||
"incomplete_config": "Configuration is missing a required variable",
|
"incomplete_config": "Configuration is missing a required variable",
|
||||||
"non_unique_id": "Multiple devices found with the same unique ID",
|
"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": {
|
"error": {
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"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": {
|
"options": {
|
||||||
|
@ -8,12 +8,12 @@
|
|||||||
"discovery_error": "Failed to discover a matching DLNA device",
|
"discovery_error": "Failed to discover a matching DLNA device",
|
||||||
"incomplete_config": "Configuration is missing a required variable",
|
"incomplete_config": "Configuration is missing a required variable",
|
||||||
"non_unique_id": "Multiple devices found with the same unique ID",
|
"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": {
|
"error": {
|
||||||
"cannot_connect": "Failed to connect",
|
"cannot_connect": "Failed to connect",
|
||||||
"could_not_connect": "Failed to connect to DLNA device",
|
"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}",
|
"flow_title": "{name}",
|
||||||
"step": {
|
"step": {
|
||||||
|
@ -51,6 +51,7 @@ ATTR_UPNP_MODEL_NAME = "modelName"
|
|||||||
ATTR_UPNP_MODEL_NUMBER = "modelNumber"
|
ATTR_UPNP_MODEL_NUMBER = "modelNumber"
|
||||||
ATTR_UPNP_MODEL_URL = "modelURL"
|
ATTR_UPNP_MODEL_URL = "modelURL"
|
||||||
ATTR_UPNP_SERIAL = "serialNumber"
|
ATTR_UPNP_SERIAL = "serialNumber"
|
||||||
|
ATTR_UPNP_SERVICE_LIST = "serviceList"
|
||||||
ATTR_UPNP_UDN = "UDN"
|
ATTR_UPNP_UDN = "UDN"
|
||||||
ATTR_UPNP_UPC = "UPC"
|
ATTR_UPNP_UPC = "UPC"
|
||||||
ATTR_UPNP_PRESENTATION_URL = "presentationURL"
|
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 socket import AddressFamily # pylint: disable=no-name-in-module
|
||||||
from unittest.mock import Mock, create_autospec, patch, seal
|
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
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.dlna_dmr.const import DOMAIN as DLNA_DOMAIN
|
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.parent_device = None
|
||||||
upnp_device.root_device = upnp_device
|
upnp_device.root_device = upnp_device
|
||||||
upnp_device.all_devices = [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)
|
seal(upnp_device)
|
||||||
domain_data.upnp_factory.async_create_device.return_value = upnp_device
|
domain_data.upnp_factory.async_create_device.return_value = upnp_device
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ from homeassistant.const import (
|
|||||||
CONF_URL,
|
CONF_URL,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||||
|
|
||||||
from .conftest import (
|
from .conftest import (
|
||||||
MOCK_DEVICE_LOCATION,
|
MOCK_DEVICE_LOCATION,
|
||||||
@ -51,13 +52,38 @@ MOCK_CONFIG_IMPORT_DATA = {
|
|||||||
|
|
||||||
MOCK_ROOT_DEVICE_UDN = "ROOT_DEVICE"
|
MOCK_ROOT_DEVICE_UDN = "ROOT_DEVICE"
|
||||||
|
|
||||||
MOCK_DISCOVERY = {
|
MOCK_DISCOVERY: DiscoveryInfoType = {
|
||||||
ssdp.ATTR_SSDP_LOCATION: MOCK_DEVICE_LOCATION,
|
ssdp.ATTR_SSDP_LOCATION: MOCK_DEVICE_LOCATION,
|
||||||
ssdp.ATTR_SSDP_UDN: MOCK_DEVICE_UDN,
|
ssdp.ATTR_SSDP_UDN: MOCK_DEVICE_UDN,
|
||||||
ssdp.ATTR_SSDP_ST: MOCK_DEVICE_TYPE,
|
ssdp.ATTR_SSDP_ST: MOCK_DEVICE_TYPE,
|
||||||
ssdp.ATTR_UPNP_UDN: MOCK_ROOT_DEVICE_UDN,
|
ssdp.ATTR_UPNP_UDN: MOCK_ROOT_DEVICE_UDN,
|
||||||
ssdp.ATTR_UPNP_DEVICE_TYPE: MOCK_DEVICE_TYPE,
|
ssdp.ATTR_UPNP_DEVICE_TYPE: MOCK_DEVICE_TYPE,
|
||||||
ssdp.ATTR_UPNP_FRIENDLY_NAME: MOCK_DEVICE_NAME,
|
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},
|
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.udn = MOCK_DEVICE_UDN
|
||||||
embedded_device.device_type = MOCK_DEVICE_TYPE
|
embedded_device.device_type = MOCK_DEVICE_TYPE
|
||||||
embedded_device.name = MOCK_DEVICE_NAME
|
embedded_device.name = MOCK_DEVICE_NAME
|
||||||
|
embedded_device.services = upnp_device.services
|
||||||
|
upnp_device.services = {}
|
||||||
upnp_device.all_devices.append(embedded_device)
|
upnp_device.all_devices.append(embedded_device)
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
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
|
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:
|
async def test_ssdp_ignore_device(hass: HomeAssistant) -> None:
|
||||||
"""Test SSDP discovery ignores certain devices."""
|
"""Test SSDP discovery ignores certain devices."""
|
||||||
discovery = MOCK_DISCOVERY.copy()
|
discovery = dict(MOCK_DISCOVERY)
|
||||||
discovery[ssdp.ATTR_HA_MATCHING_DOMAINS] = {DLNA_DOMAIN, "other_domain"}
|
discovery[ssdp.ATTR_HA_MATCHING_DOMAINS] = {DLNA_DOMAIN, "other_domain"}
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DLNA_DOMAIN,
|
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["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
assert result["reason"] == "alternative_integration"
|
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"
|
discovery[ssdp.ATTR_UPNP_DEVICE_TYPE] = "urn:schemas-upnp-org:device:ZonePlayer:1"
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DLNA_DOMAIN,
|
DLNA_DOMAIN,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user