Add ssdp discovery to Onkyo (#131066)

This commit is contained in:
Tomer Shemesh
2024-12-18 04:21:37 -05:00
committed by GitHub
parent 5fb5e933e2
commit 39d781905d
5 changed files with 272 additions and 4 deletions

View File

@ -1066,8 +1066,8 @@ build.json @home-assistant/supervisor
/tests/components/ondilo_ico/ @JeromeHXP
/homeassistant/components/onewire/ @garbled1 @epenet
/tests/components/onewire/ @garbled1 @epenet
/homeassistant/components/onkyo/ @arturpragacz
/tests/components/onkyo/ @arturpragacz
/homeassistant/components/onkyo/ @arturpragacz @eclair4151
/tests/components/onkyo/ @arturpragacz @eclair4151
/homeassistant/components/onvif/ @hunterjm
/tests/components/onvif/ @hunterjm
/homeassistant/components/open_meteo/ @frenck

View File

@ -4,7 +4,9 @@ import logging
from typing import Any
import voluptuous as vol
from yarl import URL
from homeassistant.components import ssdp
from homeassistant.config_entries import (
SOURCE_RECONFIGURE,
ConfigEntry,
@ -165,6 +167,49 @@ class OnkyoConfigFlow(ConfigFlow, domain=DOMAIN):
),
)
async def async_step_ssdp(
self, discovery_info: ssdp.SsdpServiceInfo
) -> ConfigFlowResult:
"""Handle flow initialized by SSDP discovery."""
_LOGGER.debug("Config flow start ssdp: %s", discovery_info)
if udn := discovery_info.ssdp_udn:
udn_parts = udn.split(":")
if len(udn_parts) == 2:
uuid = udn_parts[1]
last_uuid_section = uuid.split("-")[-1].upper()
await self.async_set_unique_id(last_uuid_section)
self._abort_if_unique_id_configured()
if discovery_info.ssdp_location is None:
_LOGGER.error("SSDP location is None")
return self.async_abort(reason="unknown")
host = URL(discovery_info.ssdp_location).host
if host is None:
_LOGGER.error("SSDP host is None")
return self.async_abort(reason="unknown")
try:
info = await async_interview(host)
except OSError:
_LOGGER.exception("Unexpected exception interviewing host %s", host)
return self.async_abort(reason="unknown")
if info is None:
_LOGGER.debug("SSDP eiscp is None: %s", host)
return self.async_abort(reason="cannot_connect")
await self.async_set_unique_id(info.identifier)
self._abort_if_unique_id_configured(updates={CONF_HOST: info.host})
self._receiver_info = info
title_string = f"{info.model_name} ({info.host})"
self.context["title_placeholders"] = {"name": title_string}
return await self.async_step_configure_receiver()
async def async_step_configure_receiver(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:

View File

@ -1,11 +1,49 @@
{
"domain": "onkyo",
"name": "Onkyo",
"codeowners": ["@arturpragacz"],
"codeowners": ["@arturpragacz", "@eclair4151"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/onkyo",
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["pyeiscp"],
"requirements": ["pyeiscp==0.0.7"]
"requirements": ["pyeiscp==0.0.7"],
"ssdp": [
{
"manufacturer": "ONKYO",
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1"
},
{
"manufacturer": "ONKYO",
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:2"
},
{
"manufacturer": "ONKYO",
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:3"
},
{
"manufacturer": "Onkyo & Pioneer Corporation",
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1"
},
{
"manufacturer": "Onkyo & Pioneer Corporation",
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:2"
},
{
"manufacturer": "Onkyo & Pioneer Corporation",
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:3"
},
{
"manufacturer": "Pioneer",
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1"
},
{
"manufacturer": "Pioneer",
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:2"
},
{
"manufacturer": "Pioneer",
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:3"
}
]
}

View File

@ -224,6 +224,44 @@ SSDP = {
"manufacturer": "The OctoPrint Project",
},
],
"onkyo": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
"manufacturer": "ONKYO",
},
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:2",
"manufacturer": "ONKYO",
},
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:3",
"manufacturer": "ONKYO",
},
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
"manufacturer": "Onkyo & Pioneer Corporation",
},
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:2",
"manufacturer": "Onkyo & Pioneer Corporation",
},
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:3",
"manufacturer": "Onkyo & Pioneer Corporation",
},
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
"manufacturer": "Pioneer",
},
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:2",
"manufacturer": "Pioneer",
},
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:3",
"manufacturer": "Pioneer",
},
],
"openhome": [
{
"st": "urn:av-openhome-org:service:Product:1",

View File

@ -6,6 +6,7 @@ from unittest.mock import patch
import pytest
from homeassistant import config_entries
from homeassistant.components import ssdp
from homeassistant.components.onkyo import InputSource
from homeassistant.components.onkyo.config_flow import OnkyoConfigFlow
from homeassistant.components.onkyo.const import (
@ -83,6 +84,35 @@ async def test_manual_invalid_host(hass: HomeAssistant, stub_mock_discovery) ->
assert host_result["errors"]["base"] == "cannot_connect"
async def test_ssdp_discovery_already_configured(
hass: HomeAssistant, default_mock_discovery
) -> None:
"""Test SSDP discovery with already configured device."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_HOST: "192.168.1.100"},
unique_id="id1",
)
config_entry.add_to_hass(hass)
discovery_info = ssdp.SsdpServiceInfo(
ssdp_location="http://192.168.1.100:8080",
upnp={ssdp.ATTR_UPNP_FRIENDLY_NAME: "Onkyo Receiver"},
ssdp_usn="uuid:mock_usn",
ssdp_udn="uuid:00000000-0000-0000-0000-000000000000",
ssdp_st="mock_st",
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=discovery_info,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_manual_valid_host_unexpected_error(
hass: HomeAssistant, empty_mock_discovery
) -> None:
@ -198,6 +228,123 @@ async def test_discovery_with_one_selected(hass: HomeAssistant) -> None:
assert select_result["description_placeholders"]["name"] == "type 42 (host 42)"
async def test_ssdp_discovery_success(
hass: HomeAssistant, default_mock_discovery
) -> None:
"""Test SSDP discovery with valid host."""
discovery_info = ssdp.SsdpServiceInfo(
ssdp_location="http://192.168.1.100:8080",
upnp={ssdp.ATTR_UPNP_FRIENDLY_NAME: "Onkyo Receiver"},
ssdp_usn="uuid:mock_usn",
ssdp_udn="uuid:00000000-0000-0000-0000-000000000000",
ssdp_st="mock_st",
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=discovery_info,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_receiver"
select_result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={"volume_resolution": 200, "input_sources": ["TV"]},
)
assert select_result["type"] is FlowResultType.CREATE_ENTRY
assert select_result["data"]["host"] == "192.168.1.100"
assert select_result["result"].unique_id == "id1"
async def test_ssdp_discovery_host_info_error(hass: HomeAssistant) -> None:
"""Test SSDP discovery with host info error."""
discovery_info = ssdp.SsdpServiceInfo(
ssdp_location="http://192.168.1.100:8080",
upnp={ssdp.ATTR_UPNP_FRIENDLY_NAME: "Onkyo Receiver"},
ssdp_usn="uuid:mock_usn",
ssdp_st="mock_st",
)
with patch(
"homeassistant.components.onkyo.receiver.pyeiscp.Connection.discover",
side_effect=OSError,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=discovery_info,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unknown"
async def test_ssdp_discovery_host_none_info(
hass: HomeAssistant, stub_mock_discovery
) -> None:
"""Test SSDP discovery with host info error."""
discovery_info = ssdp.SsdpServiceInfo(
ssdp_location="http://192.168.1.100:8080",
upnp={ssdp.ATTR_UPNP_FRIENDLY_NAME: "Onkyo Receiver"},
ssdp_usn="uuid:mock_usn",
ssdp_st="mock_st",
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=discovery_info,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "cannot_connect"
async def test_ssdp_discovery_no_location(
hass: HomeAssistant, default_mock_discovery
) -> None:
"""Test SSDP discovery with no location."""
discovery_info = ssdp.SsdpServiceInfo(
ssdp_location=None,
upnp={ssdp.ATTR_UPNP_FRIENDLY_NAME: "Onkyo Receiver"},
ssdp_usn="uuid:mock_usn",
ssdp_st="mock_st",
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=discovery_info,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unknown"
async def test_ssdp_discovery_no_host(
hass: HomeAssistant, default_mock_discovery
) -> None:
"""Test SSDP discovery with no host."""
discovery_info = ssdp.SsdpServiceInfo(
ssdp_location="http://",
upnp={ssdp.ATTR_UPNP_FRIENDLY_NAME: "Onkyo Receiver"},
ssdp_usn="uuid:mock_usn",
ssdp_st="mock_st",
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_SSDP},
data=discovery_info,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unknown"
async def test_configure_empty_source_list(
hass: HomeAssistant, default_mock_discovery
) -> None: