From 6dd836589c9704eb83ecd4edd15c2635fbe80e33 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Tue, 21 Apr 2020 23:06:23 -0500 Subject: [PATCH] Improve Roku (#34431) --- homeassistant/components/roku/media_player.py | 42 ++++++-------- tests/components/roku/test_media_player.py | 57 ++++++++++++++++++- 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 71e3ad86808..950946831a8 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -10,7 +10,6 @@ from roku import RokuException from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, @@ -66,12 +65,7 @@ class RokuDevice(MediaPlayerDevice): self._power_state = self.roku.power_state self.ip_address = self.roku.host self.channels = self.get_source_list() - - if self.roku.current_app is not None: - self.current_app = self.roku.current_app - else: - self.current_app = None - + self.current_app = self.roku.current_app self._available = True except (RequestsConnectionError, RequestsReadTimeout, RokuException): self._available = False @@ -90,6 +84,7 @@ class RokuDevice(MediaPlayerDevice): """Return the name of the device.""" if self._device_info.user_device_name: return self._device_info.user_device_name + return f"Roku {self._device_info.serial_num}" @property @@ -103,8 +98,10 @@ class RokuDevice(MediaPlayerDevice): if self.current_app.name == "Power Saver" or self.current_app.is_screensaver: return STATE_IDLE + if self.current_app.name == "Roku": return STATE_HOME + if self.current_app.name is not None: return STATE_PLAYING @@ -139,23 +136,17 @@ class RokuDevice(MediaPlayerDevice): @property def media_content_type(self): """Content type of current playing media.""" - if self.current_app is None: + if self.current_app is None or self.current_app.name in ("Power Saver", "Roku"): return None - if self.current_app.name == "Power Saver": - return None - if self.current_app.name == "Roku": - return None - return MEDIA_TYPE_MOVIE + + return MEDIA_TYPE_CHANNEL @property def media_image_url(self): """Image url of current playing media.""" - if self.current_app is None: - return None - if self.current_app.name == "Roku": - return None - if self.current_app.name == "Power Saver": + if self.current_app is None or self.current_app.name in ("Power Saver", "Roku"): return None + if self.current_app.id is None: return None @@ -233,14 +224,17 @@ class RokuDevice(MediaPlayerDevice): MEDIA_TYPE_CHANNEL, ) return + if self.current_app is not None: self.roku.launch(self.roku["tvinput.dtv"], {"ch": media_id}) def select_source(self, source): """Select input source.""" - if self.current_app is not None: - if source == "Home": - self.roku.home() - else: - channel = self.roku[source] - channel.launch() + if self.current_app is None: + return + + if source == "Home": + self.roku.home() + else: + channel = self.roku[source] + channel.launch() diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 759175b68b7..4965207a5b8 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -1,6 +1,13 @@ """Tests for the Roku Media Player platform.""" +from datetime import timedelta + from asynctest import PropertyMock, patch +from requests.exceptions import ( + ConnectionError as RequestsConnectionError, + ReadTimeout as RequestsReadTimeout, +) from requests_mock import Mocker +from roku import RokuException from homeassistant.components.media_player.const import ( ATTR_INPUT_SOURCE, @@ -9,7 +16,6 @@ from homeassistant.components.media_player.const import ( ATTR_MEDIA_VOLUME_MUTED, DOMAIN as MP_DOMAIN, MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_MOVIE, SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOURCE, SUPPORT_NEXT_TRACK, @@ -32,11 +38,15 @@ from homeassistant.const import ( SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, SERVICE_VOLUME_UP, + STATE_HOME, STATE_PLAYING, STATE_STANDBY, + STATE_UNAVAILABLE, ) from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util import dt as dt_util +from tests.common import async_fire_time_changed from tests.components.roku import UPNP_SERIAL, setup_integration MAIN_ENTITY_ID = f"{MP_DOMAIN}.my_roku_3" @@ -87,6 +97,47 @@ async def test_tv_setup(hass: HomeAssistantType, requests_mock: Mocker) -> None: assert tv.unique_id == TV_SERIAL +async def test_availability(hass: HomeAssistantType, requests_mock: Mocker) -> None: + """Test entity availability.""" + now = dt_util.utcnow() + future = now + timedelta(minutes=1) + + with patch("homeassistant.util.dt.utcnow", return_value=now): + await setup_integration(hass, requests_mock) + + with patch("roku.Roku._get", side_effect=RokuException,), patch( + "homeassistant.util.dt.utcnow", return_value=future + ): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert hass.states.get(MAIN_ENTITY_ID).state == STATE_UNAVAILABLE + + future += timedelta(minutes=1) + + with patch("roku.Roku._get", side_effect=RequestsConnectionError,), patch( + "homeassistant.util.dt.utcnow", return_value=future + ): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert hass.states.get(MAIN_ENTITY_ID).state == STATE_UNAVAILABLE + + future += timedelta(minutes=1) + + with patch("roku.Roku._get", side_effect=RequestsReadTimeout,), patch( + "homeassistant.util.dt.utcnow", return_value=future + ): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert hass.states.get(MAIN_ENTITY_ID).state == STATE_UNAVAILABLE + + future += timedelta(minutes=1) + + with patch("homeassistant.util.dt.utcnow", return_value=future): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert hass.states.get(MAIN_ENTITY_ID).state == STATE_HOME + + async def test_supported_features( hass: HomeAssistantType, requests_mock: Mocker ) -> None: @@ -142,7 +193,7 @@ async def test_attributes(hass: HomeAssistantType, requests_mock: Mocker) -> Non await setup_integration(hass, requests_mock) state = hass.states.get(MAIN_ENTITY_ID) - assert state.state == "home" + assert state.state == STATE_HOME assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) is None assert state.attributes.get(ATTR_INPUT_SOURCE) == "Roku" @@ -162,7 +213,7 @@ async def test_tv_attributes(hass: HomeAssistantType, requests_mock: Mocker) -> state = hass.states.get(TV_ENTITY_ID) assert state.state == STATE_PLAYING - assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == MEDIA_TYPE_MOVIE + assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == MEDIA_TYPE_CHANNEL assert state.attributes.get(ATTR_INPUT_SOURCE) == "Antenna TV"