From ba241c9440ec28d6d0c3d98b11028c01c6252ddf Mon Sep 17 00:00:00 2001 From: mvn23 Date: Tue, 22 Sep 2020 22:30:54 -0700 Subject: [PATCH 1/4] Fix 'Unavailable' state on HA startup if Kodi is not running --- homeassistant/components/kodi/__init__.py | 14 ++++++++++---- homeassistant/components/kodi/media_player.py | 6 +++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index f3cb15a17eb4e6..da69364c840b03 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -16,6 +16,7 @@ ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( @@ -48,13 +49,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry.data[CONF_SSL], session=async_get_clientsession(hass), ) + + kodi = Kodi(conn) + try: await conn.connect() - kodi = Kodi(conn) - await kodi.ping() raw_version = (await kodi.get_application_properties(["version"]))["version"] + version = f"{raw_version['major']}.{raw_version['minor']}" except CannotConnectError as error: - raise ConfigEntryNotReady from error + dr = await device_registry.async_get_registry(hass) + device = dr.async_get_device({(DOMAIN, entry.entry_id)}, []) + if not device: + raise ConfigEntryNotReady from error + version = device.sw_version except InvalidAuthError as error: _LOGGER.error( "Login to %s failed: [%s]", @@ -68,7 +75,6 @@ async def _close(event): remove_stop_listener = hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _close) - version = f"{raw_version['major']}.{raw_version['minor']}" hass.data[DOMAIN][entry.entry_id] = { DATA_CONNECTION: conn, DATA_KODI: kodi, diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 68809559cbf40a..ad268dae9c9395 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -432,7 +432,11 @@ async def async_update(self): self._reset_state() return - self._players = await self._kodi.get_players() + try: + self._players = await self._kodi.get_players() + except jsonrpc_base.jsonrpc.TransportError: + self._reset_state() + return if self._kodi_is_off: self._reset_state() From fb3e69b7b4cb872e889d5f2757c9368fa238dfe4 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Mon, 12 Oct 2020 14:30:35 +0200 Subject: [PATCH 2/4] Delay websocket watchdog until after Home Assistant has finished starting, reduce watchdog timeout --- homeassistant/components/kodi/__init__.py | 4 +-- homeassistant/components/kodi/media_player.py | 26 +++++++++++++------ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index da69364c840b03..00cd27a73dd331 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -57,8 +57,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): raw_version = (await kodi.get_application_properties(["version"]))["version"] version = f"{raw_version['major']}.{raw_version['minor']}" except CannotConnectError as error: - dr = await device_registry.async_get_registry(hass) - device = dr.async_get_device({(DOMAIN, entry.entry_id)}, []) + dev_reg = await device_registry.async_get_registry(hass) + device = dev_reg.async_get_device({(DOMAIN, entry.entry_id)}, []) if not device: raise ConfigEntryNotReady from error version = device.sw_version diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index ad268dae9c9395..8d5d84372faec0 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -48,12 +48,13 @@ CONF_SSL, CONF_TIMEOUT, CONF_USERNAME, + EVENT_HOMEASSISTANT_STARTED, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, ) -from homeassistant.core import callback +from homeassistant.core import CoreState, callback from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.event import async_track_time_interval import homeassistant.util.dt as dt_util @@ -91,7 +92,7 @@ "shutdown": "System.Shutdown", } -WEBSOCKET_WATCHDOG_INTERVAL = timedelta(minutes=3) +WEBSOCKET_WATCHDOG_INTERVAL = timedelta(seconds=10) # https://github.com/xbmc/xbmc/blob/master/xbmc/media/MediaType.h MEDIA_TYPES = { @@ -372,13 +373,22 @@ async def async_added_to_hass(self): if self._connection.connected: self._on_ws_connected() - self.async_on_remove( - async_track_time_interval( - self.hass, - self._async_connect_websocket_if_disconnected, - WEBSOCKET_WATCHDOG_INTERVAL, + def start_watchdog(event=None): + """Start websocket watchdog.""" + self.async_on_remove( + async_track_time_interval( + self.hass, + self._async_connect_websocket_if_disconnected, + WEBSOCKET_WATCHDOG_INTERVAL, + ) ) - ) + + # If Home Assistant is already in a running state, start the watchdog + # immediately, else trigger it after Home Assistant has finished starting. + if self.hass.state == CoreState.running: + start_watchdog() + else: + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, start_watchdog) @callback def _on_ws_connected(self): From 5d98ded03d006088c41e4441ad104acd10d79366 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Tue, 13 Oct 2020 12:22:39 +0200 Subject: [PATCH 3/4] Move all sw_version related code to _on_ws_connected() --- homeassistant/components/kodi/__init__.py | 14 ++------ homeassistant/components/kodi/const.py | 1 - homeassistant/components/kodi/media_player.py | 35 ++++++++++--------- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index 00cd27a73dd331..4dcb25b3ea9a00 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -15,8 +15,6 @@ EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import device_registry from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( @@ -24,7 +22,6 @@ DATA_CONNECTION, DATA_KODI, DATA_REMOVE_LISTENER, - DATA_VERSION, DOMAIN, ) @@ -54,14 +51,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): try: await conn.connect() - raw_version = (await kodi.get_application_properties(["version"]))["version"] - version = f"{raw_version['major']}.{raw_version['minor']}" - except CannotConnectError as error: - dev_reg = await device_registry.async_get_registry(hass) - device = dev_reg.async_get_device({(DOMAIN, entry.entry_id)}, []) - if not device: - raise ConfigEntryNotReady from error - version = device.sw_version + except CannotConnectError: + pass except InvalidAuthError as error: _LOGGER.error( "Login to %s failed: [%s]", @@ -79,7 +70,6 @@ async def _close(event): DATA_CONNECTION: conn, DATA_KODI: kodi, DATA_REMOVE_LISTENER: remove_stop_listener, - DATA_VERSION: version, } for component in PLATFORMS: diff --git a/homeassistant/components/kodi/const.py b/homeassistant/components/kodi/const.py index 26677f99e5ec3b..8f0ae5de737006 100644 --- a/homeassistant/components/kodi/const.py +++ b/homeassistant/components/kodi/const.py @@ -6,7 +6,6 @@ DATA_CONNECTION = "connection" DATA_KODI = "kodi" DATA_REMOVE_LISTENER = "remove_listener" -DATA_VERSION = "version" DEFAULT_PORT = 8080 DEFAULT_SSL = False diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 8d5d84372faec0..0c8a3c3cb015cb 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -55,7 +55,11 @@ STATE_PLAYING, ) from homeassistant.core import CoreState, callback -from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.helpers import ( + config_validation as cv, + device_registry, + entity_platform, +) from homeassistant.helpers.event import async_track_time_interval import homeassistant.util.dt as dt_util @@ -64,7 +68,6 @@ CONF_WS_PORT, DATA_CONNECTION, DATA_KODI, - DATA_VERSION, DEFAULT_PORT, DEFAULT_SSL, DEFAULT_TIMEOUT, @@ -230,14 +233,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities): data = hass.data[DOMAIN][config_entry.entry_id] connection = data[DATA_CONNECTION] - version = data[DATA_VERSION] kodi = data[DATA_KODI] name = config_entry.data[CONF_NAME] uid = config_entry.unique_id if uid is None: uid = config_entry.entry_id - entity = KodiEntity(connection, kodi, name, uid, version) + entity = KodiEntity(connection, kodi, name, uid) async_add_entities([entity]) @@ -265,13 +267,12 @@ async def wrapper(obj, *args, **kwargs): class KodiEntity(MediaPlayerEntity): """Representation of a XBMC/Kodi device.""" - def __init__(self, connection, kodi, name, uid, version): + def __init__(self, connection, kodi, name, uid): """Initialize the Kodi entity.""" self._connection = connection self._kodi = kodi self._name = name self._unique_id = uid - self._version = version self._players = None self._properties = {} self._item = {} @@ -348,7 +349,6 @@ def device_info(self): "identifiers": {(DOMAIN, self.unique_id)}, "name": self.name, "manufacturer": "Kodi", - "sw_version": self._version, } @property @@ -371,7 +371,7 @@ async def async_added_to_hass(self): return if self._connection.connected: - self._on_ws_connected() + await self._on_ws_connected() def start_watchdog(event=None): """Start websocket watchdog.""" @@ -390,17 +390,23 @@ def start_watchdog(event=None): else: self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, start_watchdog) - @callback - def _on_ws_connected(self): + async def _on_ws_connected(self): """Call after ws is connected.""" self._register_ws_callbacks() + + version = (await self._kodi.get_application_properties(["version"]))["version"] + sw_version = f"{version['major']}.{version['minor']}" + dev_reg = await device_registry.async_get_registry(self.hass) + device = dev_reg.async_get_device({(DOMAIN, self.unique_id)}, []) + dev_reg.async_update_device(device.id, sw_version=sw_version) + self.async_schedule_update_ha_state(True) async def _async_ws_connect(self): """Connect to Kodi via websocket protocol.""" try: await self._connection.connect() - self._on_ws_connected() + await self._on_ws_connected() except (jsonrpc_base.jsonrpc.TransportError, CannotConnectError): _LOGGER.debug("Unable to connect to Kodi via websocket", exc_info=True) await self._clear_connection(False) @@ -436,17 +442,14 @@ def _register_ws_callbacks(self): self._connection.server.System.OnRestart = self.async_on_quit self._connection.server.System.OnSleep = self.async_on_quit + @cmd async def async_update(self): """Retrieve latest state.""" if not self._connection.connected: self._reset_state() return - try: - self._players = await self._kodi.get_players() - except jsonrpc_base.jsonrpc.TransportError: - self._reset_state() - return + self._players = await self._kodi.get_players() if self._kodi_is_off: self._reset_state() From 8f796832662f05983fdc5372c9785d14fdeb5392 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Tue, 17 Nov 2020 13:11:09 +0100 Subject: [PATCH 4/4] Attempt to connect websocket without delay --- homeassistant/components/kodi/media_player.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 0c8a3c3cb015cb..dfe3af4b11e491 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -373,8 +373,9 @@ async def async_added_to_hass(self): if self._connection.connected: await self._on_ws_connected() - def start_watchdog(event=None): + async def start_watchdog(event=None): """Start websocket watchdog.""" + await self._async_connect_websocket_if_disconnected() self.async_on_remove( async_track_time_interval( self.hass, @@ -386,7 +387,7 @@ def start_watchdog(event=None): # If Home Assistant is already in a running state, start the watchdog # immediately, else trigger it after Home Assistant has finished starting. if self.hass.state == CoreState.running: - start_watchdog() + await start_watchdog() else: self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, start_watchdog)