mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
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:
parent
e5365779fe
commit
fae74f7ed7
@ -21,7 +21,6 @@ DOMAIN = "webostv"
|
|||||||
|
|
||||||
CONF_SOURCES = "sources"
|
CONF_SOURCES = "sources"
|
||||||
CONF_ON_ACTION = "turn_on_action"
|
CONF_ON_ACTION = "turn_on_action"
|
||||||
CONF_STANDBY_CONNECTION = "standby_connection"
|
|
||||||
DEFAULT_NAME = "LG webOS Smart TV"
|
DEFAULT_NAME = "LG webOS Smart TV"
|
||||||
WEBOSTV_CONFIG_FILE = "webostv.conf"
|
WEBOSTV_CONFIG_FILE = "webostv.conf"
|
||||||
|
|
||||||
@ -46,9 +45,6 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
vol.Required(CONF_HOST): cv.string,
|
vol.Required(CONF_HOST): cv.string,
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(
|
|
||||||
CONF_STANDBY_CONNECTION, default=False
|
|
||||||
): cv.boolean,
|
|
||||||
vol.Optional(CONF_ICON): cv.string,
|
vol.Optional(CONF_ICON): cv.string,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -100,9 +96,8 @@ async def async_setup_tv(hass, config, conf):
|
|||||||
|
|
||||||
host = conf[CONF_HOST]
|
host = conf[CONF_HOST]
|
||||||
config_file = hass.config.path(WEBOSTV_CONFIG_FILE)
|
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}
|
hass.data[DOMAIN][host] = {"client": client}
|
||||||
|
|
||||||
if client.is_registered():
|
if client.is_registered():
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "webostv",
|
"domain": "webostv",
|
||||||
"name": "LG webOS Smart TV",
|
"name": "LG webOS Smart TV",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/webostv",
|
"documentation": "https://www.home-assistant.io/integrations/webostv",
|
||||||
"requirements": ["aiopylgtv==0.2.7"],
|
"requirements": ["aiopylgtv==0.3.0"],
|
||||||
"dependencies": ["configurator"],
|
"dependencies": ["configurator"],
|
||||||
"codeowners": ["@bendavid"]
|
"codeowners": ["@bendavid"]
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,8 @@ SUPPORT_WEBOSTV = (
|
|||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
|
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):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Set up the LG WebOS TV platform."""
|
"""Set up the LG WebOS TV platform."""
|
||||||
@ -121,17 +123,8 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice):
|
|||||||
# Assume that the TV is not paused
|
# Assume that the TV is not paused
|
||||||
self._paused = False
|
self._paused = False
|
||||||
|
|
||||||
# Assume that the TV is not muted
|
|
||||||
self._muted = False
|
|
||||||
self._volume = 0
|
|
||||||
self._current_source = None
|
self._current_source = None
|
||||||
self._current_source_id = None
|
|
||||||
self._state = None
|
|
||||||
self._source_list = {}
|
self._source_list = {}
|
||||||
self._app_list = {}
|
|
||||||
self._input_list = {}
|
|
||||||
self._channel = None
|
|
||||||
self._last_icon = None
|
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Connect and subscribe to dispatcher signals and state updates."""
|
"""Connect and subscribe to dispatcher signals and state updates."""
|
||||||
@ -141,10 +134,6 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice):
|
|||||||
self.async_handle_state_update
|
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):
|
async def async_will_remove_from_hass(self):
|
||||||
"""Call disconnect on removal."""
|
"""Call disconnect on removal."""
|
||||||
self._client.unregister_state_update_callback(self.async_handle_state_update)
|
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):
|
async def async_handle_state_update(self):
|
||||||
"""Update state from WebOsClient."""
|
"""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.update_sources()
|
||||||
|
|
||||||
self.async_schedule_update_ha_state(False)
|
self.async_schedule_update_ha_state(False)
|
||||||
@ -183,8 +160,11 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice):
|
|||||||
self._source_list = {}
|
self._source_list = {}
|
||||||
conf_sources = self._customize[CONF_SOURCES]
|
conf_sources = self._customize[CONF_SOURCES]
|
||||||
|
|
||||||
for app in self._app_list.values():
|
found_live_tv = False
|
||||||
if app["id"] == self._current_source_id:
|
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._current_source = app["title"]
|
||||||
self._source_list[app["title"]] = app
|
self._source_list[app["title"]] = app
|
||||||
elif (
|
elif (
|
||||||
@ -195,8 +175,10 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice):
|
|||||||
):
|
):
|
||||||
self._source_list[app["title"]] = app
|
self._source_list[app["title"]] = app
|
||||||
|
|
||||||
for source in self._input_list.values():
|
for source in self._client.inputs.values():
|
||||||
if source["appId"] == self._current_source_id:
|
if source["appId"] == LIVE_TV_APP_ID:
|
||||||
|
found_live_tv = True
|
||||||
|
if source["appId"] == self._client.current_appId:
|
||||||
self._current_source = source["label"]
|
self._current_source = source["label"]
|
||||||
self._source_list[source["label"]] = source
|
self._source_list[source["label"]] = source
|
||||||
elif (
|
elif (
|
||||||
@ -206,6 +188,20 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice):
|
|||||||
):
|
):
|
||||||
self._source_list[source["label"]] = source
|
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)
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Connect."""
|
"""Connect."""
|
||||||
@ -231,17 +227,24 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the device."""
|
"""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
|
@property
|
||||||
def is_volume_muted(self):
|
def is_volume_muted(self):
|
||||||
"""Boolean if volume is currently muted."""
|
"""Boolean if volume is currently muted."""
|
||||||
return self._muted
|
return self._client.muted
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def volume_level(self):
|
def volume_level(self):
|
||||||
"""Volume level of the media player (0..1)."""
|
"""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
|
@property
|
||||||
def source(self):
|
def source(self):
|
||||||
@ -256,30 +259,27 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def media_content_type(self):
|
def media_content_type(self):
|
||||||
"""Content type of current playing media."""
|
"""Content type of current playing media."""
|
||||||
|
if self._client.current_appId == LIVE_TV_APP_ID:
|
||||||
return MEDIA_TYPE_CHANNEL
|
return MEDIA_TYPE_CHANNEL
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_title(self):
|
def media_title(self):
|
||||||
"""Title of current playing media."""
|
"""Title of current playing media."""
|
||||||
if (self._channel is not None) and ("channelName" in self._channel):
|
if (self._client.current_appId == LIVE_TV_APP_ID) and (
|
||||||
return self._channel["channelName"]
|
self._client.current_channel is not None
|
||||||
|
):
|
||||||
|
return self._client.current_channel.get("channelName")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_image_url(self):
|
def media_image_url(self):
|
||||||
"""Image url of current playing media."""
|
"""Image url of current playing media."""
|
||||||
if self._current_source_id in self._app_list:
|
if self._client.current_appId in self._client.apps:
|
||||||
icon = self._app_list[self._current_source_id]["largeIcon"]
|
icon = self._client.apps[self._client.current_appId]["largeIcon"]
|
||||||
if not icon.startswith("http"):
|
if not icon.startswith("http"):
|
||||||
icon = self._app_list[self._current_source_id]["icon"]
|
icon = self._client.apps[self._client.current_appId]["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
|
|
||||||
return icon
|
return icon
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -293,22 +293,13 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice):
|
|||||||
@cmd
|
@cmd
|
||||||
async def async_turn_off(self):
|
async def async_turn_off(self):
|
||||||
"""Turn off media player."""
|
"""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):
|
async def async_turn_on(self):
|
||||||
"""Turn on the media player."""
|
"""Turn on the media player."""
|
||||||
connected = self._client.is_connected()
|
|
||||||
if self._on_script:
|
if self._on_script:
|
||||||
await self._on_script.async_run()
|
await self._on_script.async_run()
|
||||||
|
|
||||||
# if connection was already active
|
|
||||||
# ensure is still alive
|
|
||||||
if connected:
|
|
||||||
await self._client.get_current_app()
|
|
||||||
|
|
||||||
@cmd
|
@cmd
|
||||||
async def async_volume_up(self):
|
async def async_volume_up(self):
|
||||||
"""Volume up the media player."""
|
"""Volume up the media player."""
|
||||||
@ -360,7 +351,7 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice):
|
|||||||
partial_match_channel_id = None
|
partial_match_channel_id = None
|
||||||
perfect_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"]:
|
if media_id == channel["channelNumber"]:
|
||||||
perfect_match_channel_id = channel["channelId"]
|
perfect_match_channel_id = channel["channelId"]
|
||||||
continue
|
continue
|
||||||
|
@ -190,7 +190,7 @@ aionotion==1.1.0
|
|||||||
aiopvapi==1.6.14
|
aiopvapi==1.6.14
|
||||||
|
|
||||||
# homeassistant.components.webostv
|
# homeassistant.components.webostv
|
||||||
aiopylgtv==0.2.7
|
aiopylgtv==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.switcher_kis
|
# homeassistant.components.switcher_kis
|
||||||
aioswitcher==2019.4.26
|
aioswitcher==2019.4.26
|
||||||
|
@ -69,7 +69,7 @@ aiohue==1.10.1
|
|||||||
aionotion==1.1.0
|
aionotion==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.webostv
|
# homeassistant.components.webostv
|
||||||
aiopylgtv==0.2.7
|
aiopylgtv==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.switcher_kis
|
# homeassistant.components.switcher_kis
|
||||||
aioswitcher==2019.4.26
|
aioswitcher==2019.4.26
|
||||||
|
@ -21,7 +21,6 @@ from homeassistant.const import (
|
|||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
SERVICE_VOLUME_MUTE,
|
SERVICE_VOLUME_MUTE,
|
||||||
STATE_ON,
|
|
||||||
)
|
)
|
||||||
from homeassistant.setup import async_setup_component
|
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.services.async_call(media_player.DOMAIN, SERVICE_SELECT_SOURCE, data)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.is_state(ENTITY_ID, STATE_ON)
|
|
||||||
client.launch_app.assert_not_called()
|
client.launch_app.assert_not_called()
|
||||||
client.set_input.assert_not_called()
|
client.set_input.assert_not_called()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user