From 6610bbe7bba488a1f51f3f17793f53ae33771f13 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Tue, 23 Jun 2020 11:03:43 -0500 Subject: [PATCH] Add service to trigger roku search (#37014) --- homeassistant/components/roku/const.py | 4 ++++ homeassistant/components/roku/media_player.py | 18 +++++++++++++++++- homeassistant/components/roku/services.yaml | 9 +++++++++ tests/components/roku/__init__.py | 4 ++++ tests/components/roku/test_media_player.py | 17 +++++++++++++++++ tests/components/roku/test_remote.py | 2 +- 6 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/roku/services.yaml diff --git a/homeassistant/components/roku/const.py b/homeassistant/components/roku/const.py index dc51e5d6f9b..4abbd9e109a 100644 --- a/homeassistant/components/roku/const.py +++ b/homeassistant/components/roku/const.py @@ -3,9 +3,13 @@ DOMAIN = "roku" # Attributes ATTR_IDENTIFIERS = "identifiers" +ATTR_KEYWORD = "keyword" ATTR_MANUFACTURER = "manufacturer" ATTR_MODEL = "model" ATTR_SOFTWARE_VERSION = "sw_version" # Default Values DEFAULT_PORT = 8060 + +# Services +SERVICE_SEARCH = "search" diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 463a77a1e55..9a46d189486 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -2,6 +2,8 @@ import logging from typing import List +import voluptuous as vol + from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_APP, @@ -24,9 +26,10 @@ from homeassistant.const import ( STATE_PLAYING, STATE_STANDBY, ) +from homeassistant.helpers import entity_platform from . import RokuDataUpdateCoordinator, RokuEntity, roku_exception_handler -from .const import DOMAIN +from .const import ATTR_KEYWORD, DOMAIN, SERVICE_SEARCH _LOGGER = logging.getLogger(__name__) @@ -43,6 +46,8 @@ SUPPORT_ROKU = ( | SUPPORT_TURN_OFF ) +SEARCH_SCHEMA = {vol.Required(ATTR_KEYWORD): str} + async def async_setup_entry(hass, entry, async_add_entities): """Set up the Roku config entry.""" @@ -50,6 +55,12 @@ async def async_setup_entry(hass, entry, async_add_entities): unique_id = coordinator.data.info.serial_number async_add_entities([RokuMediaPlayer(unique_id, coordinator)], True) + platform = entity_platform.current_platform.get() + + platform.async_register_entity_service( + SERVICE_SEARCH, SEARCH_SCHEMA, "search", + ) + class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): """Representation of a Roku media player on the network.""" @@ -170,6 +181,11 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): """List of available input sources.""" return ["Home"] + sorted(app.name for app in self.coordinator.data.apps) + @roku_exception_handler + async def search(self, keyword): + """Emulate opening the search screen and entering the search keyword.""" + await self.coordinator.roku.search(keyword) + @roku_exception_handler async def async_turn_on(self) -> None: """Turn on the Roku.""" diff --git a/homeassistant/components/roku/services.yaml b/homeassistant/components/roku/services.yaml new file mode 100644 index 00000000000..73d635845c9 --- /dev/null +++ b/homeassistant/components/roku/services.yaml @@ -0,0 +1,9 @@ +search: + description: Emulates opening thd search screen and entering the search keyword. + fields: + entity_id: + description: The entities to search on. + example: "media_player.roku" + keyword: + description: The keyword to search for. + example: "Space Jam" diff --git a/tests/components/roku/__init__.py b/tests/components/roku/__init__.py index 695575ddb36..a73e1b7d5aa 100644 --- a/tests/components/roku/__init__.py +++ b/tests/components/roku/__init__.py @@ -104,6 +104,8 @@ def mock_connection( re.compile(f"{roku_url}/launch/.*"), text="OK", ) + aioclient_mock.post(f"{roku_url}/search", text="OK") + def mock_connection_error( aioclient_mock: AiohttpClientMocker, @@ -122,6 +124,7 @@ def mock_connection_error( aioclient_mock.post(re.compile(f"{roku_url}/keypress/.*"), exc=SocketGIAError) aioclient_mock.post(re.compile(f"{roku_url}/launch/.*"), exc=SocketGIAError) + aioclient_mock.post(f"{roku_url}/search", exc=SocketGIAError) def mock_connection_server_error( @@ -141,6 +144,7 @@ def mock_connection_server_error( aioclient_mock.post(re.compile(f"{roku_url}/keypress/.*"), status=500) aioclient_mock.post(re.compile(f"{roku_url}/launch/.*"), status=500) + aioclient_mock.post(f"{roku_url}/search", status=500) async def setup_integration( diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 467e81b957f..a05cde5a596 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -28,6 +28,7 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, ) +from homeassistant.components.roku.const import ATTR_KEYWORD, DOMAIN, SERVICE_SEARCH from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_MEDIA_NEXT_TRACK, @@ -422,3 +423,19 @@ async def test_tv_services( ) tune_mock.assert_called_once_with("55") + + +async def test_integration_services( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker +) -> None: + """Test integration services.""" + await setup_integration(hass, aioclient_mock) + + with patch("homeassistant.components.roku.Roku.search") as search_mock: + await hass.services.async_call( + DOMAIN, + SERVICE_SEARCH, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_KEYWORD: "Space Jam"}, + blocking=True, + ) + search_mock.assert_called_once_with("Space Jam") diff --git a/tests/components/roku/test_remote.py b/tests/components/roku/test_remote.py index 6b50d4362c1..96426e5b10a 100644 --- a/tests/components/roku/test_remote.py +++ b/tests/components/roku/test_remote.py @@ -39,7 +39,7 @@ async def test_unique_id( async def test_main_services( hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker ) -> None: - """Test the different services.""" + """Test platform services.""" await setup_integration(hass, aioclient_mock) with patch("homeassistant.components.roku.Roku.remote") as remote_mock: