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
This commit is contained in:
Josh Bendavid 2020-01-22 19:06:08 +01:00 committed by Martin Hjelmare
parent e5365779fe
commit fae74f7ed7
6 changed files with 52 additions and 68 deletions

View file

@ -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():

View file

@ -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"]
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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()