Support homekit discovery for roku (#44625)
* support homekit discovery for roku * Update config_flow.py * Update config_flow.py * Update test_config_flow.py * Update __init__.py * Update __init__.py * Update strings.json * Update manifest.json * Update __init__.py * Update test_config_flow.py * Update __init__.py * Update manifest.json * Update config_flow.py * Update config_flow.py * Update __init__.py * Update test_config_flow.py * Update __init__.py * Update manifest.json * Update __init__.py * Update zeroconf.py * Update config_flow.py * Update test_config_flow.py * Update config_flow.py * Update test_config_flow.py * Update __init__.py * Update config_flow.py * Update test_config_flow.py * Update manifest.json * Update zeroconf.pypull/44641/head
parent
35a19a4d02
commit
12aa537eb9
|
@ -85,6 +85,36 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
|
||||
return self.async_create_entry(title=info["title"], data=user_input)
|
||||
|
||||
async def async_step_homekit(self, discovery_info):
|
||||
"""Handle a flow initialized by homekit discovery."""
|
||||
|
||||
# If we already have the host configured do
|
||||
# not open connections to it if we can avoid it.
|
||||
if self._host_already_configured(discovery_info[CONF_HOST]):
|
||||
return self.async_abort(reason="already_configured")
|
||||
|
||||
self.discovery_info.update({CONF_HOST: discovery_info[CONF_HOST]})
|
||||
|
||||
try:
|
||||
info = await validate_input(self.hass, self.discovery_info)
|
||||
except RokuError:
|
||||
_LOGGER.debug("Roku Error", exc_info=True)
|
||||
return self.async_abort(reason=ERROR_CANNOT_CONNECT)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unknown error trying to connect")
|
||||
return self.async_abort(reason=ERROR_UNKNOWN)
|
||||
|
||||
await self.async_set_unique_id(info["serial_number"])
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: discovery_info[CONF_HOST]},
|
||||
)
|
||||
|
||||
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
|
||||
self.context.update({"title_placeholders": {"name": info["title"]}})
|
||||
self.discovery_info.update({CONF_NAME: info["title"]})
|
||||
|
||||
return await self.async_step_discovery_confirm()
|
||||
|
||||
async def async_step_ssdp(
|
||||
self, discovery_info: Optional[Dict] = None
|
||||
) -> Dict[str, Any]:
|
||||
|
@ -110,16 +140,16 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
_LOGGER.exception("Unknown error trying to connect")
|
||||
return self.async_abort(reason=ERROR_UNKNOWN)
|
||||
|
||||
return await self.async_step_ssdp_confirm()
|
||||
return await self.async_step_discovery_confirm()
|
||||
|
||||
async def async_step_ssdp_confirm(
|
||||
async def async_step_discovery_confirm(
|
||||
self, user_input: Optional[Dict] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Handle user-confirmation of discovered device."""
|
||||
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="ssdp_confirm",
|
||||
step_id="discovery_confirm",
|
||||
description_placeholders={"name": self.discovery_info[CONF_NAME]},
|
||||
errors={},
|
||||
)
|
||||
|
@ -128,3 +158,12 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
title=self.discovery_info[CONF_NAME],
|
||||
data=self.discovery_info,
|
||||
)
|
||||
|
||||
def _host_already_configured(self, host):
|
||||
"""See if we already have a hub with the host address configured."""
|
||||
existing_hosts = {
|
||||
entry.data[CONF_HOST]
|
||||
for entry in self._async_current_entries()
|
||||
if CONF_HOST in entry.data
|
||||
}
|
||||
return host in existing_hosts
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
"name": "Roku",
|
||||
"documentation": "https://www.home-assistant.io/integrations/roku",
|
||||
"requirements": ["rokuecp==0.6.0"],
|
||||
"homekit": {
|
||||
"models": [
|
||||
"3810X",
|
||||
"4660X",
|
||||
"7820X",
|
||||
"C105X"
|
||||
]
|
||||
},
|
||||
"ssdp": [
|
||||
{
|
||||
"st": "roku:ecp",
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"host": "[%key:common::config_flow::data::host%]"
|
||||
}
|
||||
},
|
||||
"ssdp_confirm": {
|
||||
"discovery_confirm": {
|
||||
"title": "Roku",
|
||||
"description": "Do you want to set up {name}?",
|
||||
"data": {}
|
||||
|
|
|
@ -157,10 +157,14 @@ ZEROCONF = {
|
|||
}
|
||||
|
||||
HOMEKIT = {
|
||||
"3810X": "roku",
|
||||
"4660X": "roku",
|
||||
"7820X": "roku",
|
||||
"819LMB": "myq",
|
||||
"AC02": "tado",
|
||||
"Abode": "abode",
|
||||
"BSB002": "hue",
|
||||
"C105X": "roku",
|
||||
"Healty Home Coach": "netatmo",
|
||||
"Iota": "abode",
|
||||
"LIFX": "lifx",
|
||||
|
|
|
@ -8,14 +8,16 @@ from homeassistant.components.ssdp import (
|
|||
ATTR_UPNP_FRIENDLY_NAME,
|
||||
ATTR_UPNP_SERIAL,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.const import CONF_HOST, CONF_ID, CONF_NAME
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
HOST = "192.168.1.160"
|
||||
NAME = "Roku 3"
|
||||
NAME_ROKUTV = '58" Onn Roku TV'
|
||||
|
||||
HOST = "192.168.1.160"
|
||||
SSDP_LOCATION = "http://192.168.1.160/"
|
||||
UPNP_FRIENDLY_NAME = "My Roku 3"
|
||||
UPNP_SERIAL = "1GU48T017973"
|
||||
|
@ -26,6 +28,16 @@ MOCK_SSDP_DISCOVERY_INFO = {
|
|||
ATTR_UPNP_SERIAL: UPNP_SERIAL,
|
||||
}
|
||||
|
||||
HOMEKIT_HOST = "192.168.1.161"
|
||||
|
||||
MOCK_HOMEKIT_DISCOVERY_INFO = {
|
||||
CONF_NAME: "onn._hap._tcp.local.",
|
||||
CONF_HOST: HOMEKIT_HOST,
|
||||
"properties": {
|
||||
CONF_ID: "2d:97:da:ee:dc:99",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def mock_connection(
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Test the Roku config flow."""
|
||||
from homeassistant.components.roku.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_SSDP, SOURCE_USER
|
||||
from homeassistant.config_entries import SOURCE_HOMEKIT, SOURCE_SSDP, SOURCE_USER
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_SOURCE
|
||||
from homeassistant.data_entry_flow import (
|
||||
RESULT_TYPE_ABORT,
|
||||
|
@ -12,8 +12,11 @@ from homeassistant.setup import async_setup_component
|
|||
|
||||
from tests.async_mock import patch
|
||||
from tests.components.roku import (
|
||||
HOMEKIT_HOST,
|
||||
HOST,
|
||||
MOCK_HOMEKIT_DISCOVERY_INFO,
|
||||
MOCK_SSDP_DISCOVERY_INFO,
|
||||
NAME_ROKUTV,
|
||||
UPNP_FRIENDLY_NAME,
|
||||
mock_connection,
|
||||
setup_integration,
|
||||
|
@ -128,6 +131,92 @@ async def test_form_unknown_error(hass: HomeAssistantType) -> None:
|
|||
assert len(mock_validate_input.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_homekit_cannot_connect(
|
||||
hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
"""Test we abort homekit flow on connection error."""
|
||||
mock_connection(
|
||||
aioclient_mock,
|
||||
host=HOMEKIT_HOST,
|
||||
error=True,
|
||||
)
|
||||
|
||||
discovery_info = MOCK_HOMEKIT_DISCOVERY_INFO.copy()
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={CONF_SOURCE: SOURCE_HOMEKIT},
|
||||
data=discovery_info,
|
||||
)
|
||||
|
||||
assert result["type"] == RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "cannot_connect"
|
||||
|
||||
|
||||
async def test_homekit_unknown_error(
|
||||
hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
"""Test we abort homekit flow on unknown error."""
|
||||
discovery_info = MOCK_HOMEKIT_DISCOVERY_INFO.copy()
|
||||
with patch(
|
||||
"homeassistant.components.roku.config_flow.Roku.update",
|
||||
side_effect=Exception,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={CONF_SOURCE: SOURCE_HOMEKIT},
|
||||
data=discovery_info,
|
||||
)
|
||||
|
||||
assert result["type"] == RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "unknown"
|
||||
|
||||
|
||||
async def test_homekit_discovery(
|
||||
hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
"""Test the homekit discovery flow."""
|
||||
mock_connection(aioclient_mock, device="rokutv", host=HOMEKIT_HOST)
|
||||
|
||||
discovery_info = MOCK_HOMEKIT_DISCOVERY_INFO.copy()
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={CONF_SOURCE: SOURCE_HOMEKIT}, data=discovery_info
|
||||
)
|
||||
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "discovery_confirm"
|
||||
assert result["description_placeholders"] == {CONF_NAME: NAME_ROKUTV}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.roku.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.roku.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
flow_id=result["flow_id"], user_input={}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == NAME_ROKUTV
|
||||
|
||||
assert result["data"]
|
||||
assert result["data"][CONF_HOST] == HOMEKIT_HOST
|
||||
assert result["data"][CONF_NAME] == NAME_ROKUTV
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
# test abort on existing host
|
||||
discovery_info = MOCK_HOMEKIT_DISCOVERY_INFO.copy()
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={CONF_SOURCE: SOURCE_HOMEKIT}, data=discovery_info
|
||||
)
|
||||
|
||||
assert result["type"] == RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_ssdp_cannot_connect(
|
||||
hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
|
@ -176,7 +265,7 @@ async def test_ssdp_discovery(
|
|||
)
|
||||
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "ssdp_confirm"
|
||||
assert result["step_id"] == "discovery_confirm"
|
||||
assert result["description_placeholders"] == {CONF_NAME: UPNP_FRIENDLY_NAME}
|
||||
|
||||
with patch(
|
||||
|
|
Loading…
Reference in New Issue