From fae74f7ed7ed367f3ee15704d39487b799f381b2 Mon Sep 17 00:00:00 2001 From: Josh Bendavid Date: Wed, 22 Jan 2020 19:06:08 +0100 Subject: [PATCH] Improve state tracking for WebOsTV (#31042) * upgrade to aiopylgtv 0.3.0 and corresponding simplification and cleanup of webostv state tracking * properly handle case where Live TV is not reported in list of apps * fix tests (entity state is no longer linked to source id) * fix pylint checks * avoid unnecessary retrieval of channel list * use only standard home assistant states --- homeassistant/components/webostv/__init__.py | 7 +- .../components/webostv/manifest.json | 2 +- .../components/webostv/media_player.py | 105 ++++++++---------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/webostv/test_media_player.py | 2 - 6 files changed, 52 insertions(+), 68 deletions(-) diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index e03fea68fd7..13f3d9e8f8d 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -21,7 +21,6 @@ DOMAIN = "webostv" CONF_SOURCES = "sources" CONF_ON_ACTION = "turn_on_action" -CONF_STANDBY_CONNECTION = "standby_connection" DEFAULT_NAME = "LG webOS Smart TV" WEBOSTV_CONFIG_FILE = "webostv.conf" @@ -46,9 +45,6 @@ CONFIG_SCHEMA = vol.Schema( vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, - vol.Optional( - CONF_STANDBY_CONNECTION, default=False - ): cv.boolean, vol.Optional(CONF_ICON): cv.string, } ) @@ -100,9 +96,8 @@ async def async_setup_tv(hass, config, conf): host = conf[CONF_HOST] config_file = hass.config.path(WEBOSTV_CONFIG_FILE) - standby_connection = conf[CONF_STANDBY_CONNECTION] - client = WebOsClient(host, config_file, standby_connection=standby_connection) + client = WebOsClient(host, config_file) hass.data[DOMAIN][host] = {"client": client} if client.is_registered(): diff --git a/homeassistant/components/webostv/manifest.json b/homeassistant/components/webostv/manifest.json index ff254e35159..4328ff96b56 100644 --- a/homeassistant/components/webostv/manifest.json +++ b/homeassistant/components/webostv/manifest.json @@ -2,7 +2,7 @@ "domain": "webostv", "name": "LG webOS Smart TV", "documentation": "https://www.home-assistant.io/integrations/webostv", - "requirements": ["aiopylgtv==0.2.7"], + "requirements": ["aiopylgtv==0.3.0"], "dependencies": ["configurator"], "codeowners": ["@bendavid"] } diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index c523c068bcc..c34fb376d31 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -59,6 +59,8 @@ SUPPORT_WEBOSTV = ( MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) +LIVE_TV_APP_ID = "com.webos.app.livetv" + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the LG WebOS TV platform.""" @@ -121,17 +123,8 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): # Assume that the TV is not paused self._paused = False - # Assume that the TV is not muted - self._muted = False - self._volume = 0 self._current_source = None - self._current_source_id = None - self._state = None self._source_list = {} - self._app_list = {} - self._input_list = {} - self._channel = None - self._last_icon = None async def async_added_to_hass(self): """Connect and subscribe to dispatcher signals and state updates.""" @@ -141,10 +134,6 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): self.async_handle_state_update ) - # force state update if needed - if self._state is None: - await self.async_handle_state_update() - async def async_will_remove_from_hass(self): """Call disconnect on removal.""" self._client.unregister_state_update_callback(self.async_handle_state_update) @@ -162,18 +151,6 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): async def async_handle_state_update(self): """Update state from WebOsClient.""" - self._current_source_id = self._client.current_appId - self._muted = self._client.muted - self._volume = self._client.volume - self._channel = self._client.current_channel - self._app_list = self._client.apps - self._input_list = self._client.inputs - - if self._current_source_id == "": - self._state = STATE_OFF - else: - self._state = STATE_ON - self.update_sources() self.async_schedule_update_ha_state(False) @@ -183,8 +160,11 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): self._source_list = {} conf_sources = self._customize[CONF_SOURCES] - for app in self._app_list.values(): - if app["id"] == self._current_source_id: + found_live_tv = False + for app in self._client.apps.values(): + if app["id"] == LIVE_TV_APP_ID: + found_live_tv = True + if app["id"] == self._client.current_appId: self._current_source = app["title"] self._source_list[app["title"]] = app elif ( @@ -195,8 +175,10 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): ): self._source_list[app["title"]] = app - for source in self._input_list.values(): - if source["appId"] == self._current_source_id: + for source in self._client.inputs.values(): + if source["appId"] == LIVE_TV_APP_ID: + found_live_tv = True + if source["appId"] == self._client.current_appId: self._current_source = source["label"] self._source_list[source["label"]] = source elif ( @@ -206,6 +188,20 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): ): self._source_list[source["label"]] = source + # special handling of live tv since this might not appear in the app or input lists in some cases + if not found_live_tv: + app = {"id": LIVE_TV_APP_ID, "title": "Live TV"} + if LIVE_TV_APP_ID == self._client.current_appId: + self._current_source = app["title"] + self._source_list["Live TV"] = app + elif ( + not conf_sources + or app["id"] in conf_sources + or any(word in app["title"] for word in conf_sources) + or any(word in app["id"] for word in conf_sources) + ): + self._source_list["Live TV"] = app + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) async def async_update(self): """Connect.""" @@ -231,17 +227,24 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): @property def state(self): """Return the state of the device.""" - return self._state + client_state = self._client.power_state.get("state") + if client_state in [None, "Power Off", "Suspend", "Active Standby"]: + return STATE_OFF + + return STATE_ON @property def is_volume_muted(self): """Boolean if volume is currently muted.""" - return self._muted + return self._client.muted @property def volume_level(self): """Volume level of the media player (0..1).""" - return self._volume / 100.0 + if self._client.volume is not None: + return self._client.volume / 100.0 + + return None @property def source(self): @@ -256,30 +259,27 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): @property def media_content_type(self): """Content type of current playing media.""" - return MEDIA_TYPE_CHANNEL + if self._client.current_appId == LIVE_TV_APP_ID: + return MEDIA_TYPE_CHANNEL + + return None @property def media_title(self): """Title of current playing media.""" - if (self._channel is not None) and ("channelName" in self._channel): - return self._channel["channelName"] + if (self._client.current_appId == LIVE_TV_APP_ID) and ( + self._client.current_channel is not None + ): + return self._client.current_channel.get("channelName") return None @property def media_image_url(self): """Image url of current playing media.""" - if self._current_source_id in self._app_list: - icon = self._app_list[self._current_source_id]["largeIcon"] + if self._client.current_appId in self._client.apps: + icon = self._client.apps[self._client.current_appId]["largeIcon"] if not icon.startswith("http"): - icon = self._app_list[self._current_source_id]["icon"] - - # 'icon' holds a URL with a transient key. Avoid unnecessary - # updates by returning the same URL until the image changes. - if self._last_icon and ( - icon.split("/")[-1] == self._last_icon.split("/")[-1] - ): - return self._last_icon - self._last_icon = icon + icon = self._client.apps[self._client.current_appId]["icon"] return icon return None @@ -293,22 +293,13 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): @cmd async def async_turn_off(self): """Turn off media player.""" - - # in some situations power_off may cause the TV to switch back on - if self._state != STATE_OFF: - await self._client.power_off() + await self._client.power_off() async def async_turn_on(self): """Turn on the media player.""" - connected = self._client.is_connected() if self._on_script: await self._on_script.async_run() - # if connection was already active - # ensure is still alive - if connected: - await self._client.get_current_app() - @cmd async def async_volume_up(self): """Volume up the media player.""" @@ -360,7 +351,7 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice): partial_match_channel_id = None perfect_match_channel_id = None - for channel in await self._client.get_channels(): + for channel in self._client.channels: if media_id == channel["channelNumber"]: perfect_match_channel_id = channel["channelId"] continue diff --git a/requirements_all.txt b/requirements_all.txt index ef2c85373e6..8b7423ce5a3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -190,7 +190,7 @@ aionotion==1.1.0 aiopvapi==1.6.14 # homeassistant.components.webostv -aiopylgtv==0.2.7 +aiopylgtv==0.3.0 # homeassistant.components.switcher_kis aioswitcher==2019.4.26 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7ed5c31ca49..59e3424b986 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -69,7 +69,7 @@ aiohue==1.10.1 aionotion==1.1.0 # homeassistant.components.webostv -aiopylgtv==0.2.7 +aiopylgtv==0.3.0 # homeassistant.components.switcher_kis aioswitcher==2019.4.26 diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index b0be238f971..e415734bec2 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -21,7 +21,6 @@ from homeassistant.const import ( CONF_HOST, CONF_NAME, SERVICE_VOLUME_MUTE, - STATE_ON, ) from homeassistant.setup import async_setup_component @@ -79,7 +78,6 @@ async def test_select_source_with_empty_source_list(hass, client): await hass.services.async_call(media_player.DOMAIN, SERVICE_SELECT_SOURCE, data) await hass.async_block_till_done() - assert hass.states.is_state(ENTITY_ID, STATE_ON) client.launch_app.assert_not_called() client.set_input.assert_not_called()