From 1c653ec83726e1c3a77c99a975d14773107a195b Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Wed, 7 Aug 2019 08:03:59 -0700 Subject: [PATCH 01/10] Create const.py --- homeassistant/components/kodi/const.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 homeassistant/components/kodi/const.py diff --git a/homeassistant/components/kodi/const.py b/homeassistant/components/kodi/const.py new file mode 100644 index 00000000000000..7cb93f0d2830e3 --- /dev/null +++ b/homeassistant/components/kodi/const.py @@ -0,0 +1,2 @@ +"""Constants for the Kodi platform.""" +DOMAIN = "kodi" From 5bf5016dc579dea4fb890cf7f44119cf27f78159 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Wed, 7 Aug 2019 08:11:33 -0700 Subject: [PATCH 02/10] Register services to 'kodi' domain, not 'media_player' --- homeassistant/components/kodi/media_player.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index de50a2ef4de637..f12e4cd5e98400 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -10,9 +10,9 @@ import aiohttp import voluptuous as vol +from homeassistant.components.kodi.const import DOMAIN from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( - DOMAIN, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, @@ -134,8 +134,8 @@ } ) -SERVICE_ADD_MEDIA = "kodi_add_to_playlist" -SERVICE_CALL_METHOD = "kodi_call_method" +SERVICE_ADD_MEDIA = "add_to_playlist" +SERVICE_CALL_METHOD = "call_method" DATA_KODI = "kodi" From 42f95f27a5827c8e1d5479de88d5b304e6f93162 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Wed, 7 Aug 2019 09:35:30 -0700 Subject: [PATCH 03/10] Add const.py to .coveragerc --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 75b97e8f5e1127..a90e80e606cb56 100644 --- a/.coveragerc +++ b/.coveragerc @@ -317,6 +317,7 @@ omit = homeassistant/components/knx/* homeassistant/components/knx/climate.py homeassistant/components/knx/cover.py + homeassistant/components/kodi/const.py homeassistant/components/kodi/media_player.py homeassistant/components/kodi/notify.py homeassistant/components/konnected/* From 0983cf1d21a2a9c07eb486152417b28691b23d8a Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Wed, 7 Aug 2019 10:53:41 -0700 Subject: [PATCH 04/10] 'DATA_KODI' -> 'DOMAIN' --- homeassistant/components/kodi/media_player.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index f12e4cd5e98400..b2965b18736509 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -137,8 +137,6 @@ SERVICE_ADD_MEDIA = "add_to_playlist" SERVICE_CALL_METHOD = "call_method" -DATA_KODI = "kodi" - ATTR_MEDIA_TYPE = "media_type" ATTR_MEDIA_NAME = "media_name" ATTR_MEDIA_ARTIST_NAME = "artist_name" @@ -205,8 +203,8 @@ def _check_deprecated_turn_off(hass, turn_off_action): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Kodi platform.""" - if DATA_KODI not in hass.data: - hass.data[DATA_KODI] = dict() + if DOMAIN not in hass.data: + hass.data[DOMAIN] = dict() unique_id = None # Is this a manual configuration? @@ -231,14 +229,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= # Only add a device once, so discovered devices do not override manual # config. ip_addr = socket.gethostbyname(host) - if ip_addr in hass.data[DATA_KODI]: + if ip_addr in hass.data[DOMAIN]: return # If we got an unique id, check that it does not exist already. # This is necessary as netdisco does not deterministally return the same # advertisement when the service is offered over multiple IP addresses. if unique_id is not None: - for device in hass.data[DATA_KODI].values(): + for device in hass.data[DOMAIN].values(): if device.unique_id == unique_id: return @@ -258,7 +256,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= unique_id=unique_id, ) - hass.data[DATA_KODI][ip_addr] = entity + hass.data[DOMAIN][ip_addr] = entity async_add_entities([entity], update_before_add=True) async def async_service_handler(service): @@ -274,11 +272,11 @@ async def async_service_handler(service): if entity_ids: target_players = [ player - for player in hass.data[DATA_KODI].values() + for player in hass.data[DOMAIN].values() if player.entity_id in entity_ids ] else: - target_players = hass.data[DATA_KODI].values() + target_players = hass.data[DOMAIN].values() update_tasks = [] for player in target_players: From 4629bbb457aedcda886c8c7f848d45370f2ccdaf Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Thu, 8 Aug 2019 04:08:35 +0000 Subject: [PATCH 05/10] Move the Kodi services descriptions to the Kodi component --- homeassistant/components/kodi/services.yaml | 30 +++++++++++++++++++ .../components/media_player/services.yaml | 29 ------------------ 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/kodi/services.yaml b/homeassistant/components/kodi/services.yaml index e69de29bb2d1d6..01dde6a249cc0a 100644 --- a/homeassistant/components/kodi/services.yaml +++ b/homeassistant/components/kodi/services.yaml @@ -0,0 +1,30 @@ +# Describes the format for available Kodi services + +add_to_playlist: + description: Add music to the default playlist (i.e. playlistid=0). + fields: + entity_id: + description: Name(s) of the Kodi entities where to add the media. + example: 'media_player.living_room_kodi' + media_type: + description: Media type identifier. It must be one of SONG or ALBUM. + example: ALBUM + media_id: + description: Unique Id of the media entry to add (`songid` or albumid`). If not defined, `media_name` and `artist_name` are needed to search the Kodi music library. + example: 123456 + media_name: + description: Optional media name for filtering media. Can be 'ALL' when `media_type` is 'ALBUM' and `artist_name` is specified, to add all songs from one artist. + example: 'Highway to Hell' + artist_name: + description: Optional artist name for filtering media. + example: 'AC/DC' + +call_method: + description: 'Call a Kodi JSONRPC API method with optional parameters. Results of the Kodi API call will be redirected in a Home Assistant event: `kodi_call_method_result`.' + fields: + entity_id: + description: Name(s) of the Kodi entities where to run the API method. + example: 'media_player.living_room_kodi' + method: + description: Name of the Kodi JSONRPC API method to be called. + example: 'VideoLibrary.GetRecentlyAddedEpisodes' diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index d7f636d070a6cb..5421085c30804e 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -232,35 +232,6 @@ soundtouch_remove_zone_slave: description: Name of slaves entities to remove from the existing zone. example: 'media_player.soundtouch_bedroom' -kodi_add_to_playlist: - description: Add music to the default playlist (i.e. playlistid=0). - fields: - entity_id: - description: Name(s) of the Kodi entities where to add the media. - example: 'media_player.living_room_kodi' - media_type: - description: Media type identifier. It must be one of SONG or ALBUM. - example: ALBUM - media_id: - description: Unique Id of the media entry to add (`songid` or albumid`). If not defined, `media_name` and `artist_name` are needed to search the Kodi music library. - example: 123456 - media_name: - description: Optional media name for filtering media. Can be 'ALL' when `media_type` is 'ALBUM' and `artist_name` is specified, to add all songs from one artist. - example: 'Highway to Hell' - artist_name: - description: Optional artist name for filtering media. - example: 'AC/DC' - -kodi_call_method: - description: 'Call a Kodi JSONRPC API method with optional parameters. Results of the Kodi API call will be redirected in a Home Assistant event: `kodi_call_method_result`.' - fields: - entity_id: - description: Name(s) of the Kodi entities where to run the API method. - example: 'media_player.living_room_kodi' - method: - description: Name of the Kodi JSONRPC API method to be called. - example: 'VideoLibrary.GetRecentlyAddedEpisodes' - squeezebox_call_method: description: 'Call a Squeezebox JSON/RPC API method.' fields: From de44c9a9672073eaedfe0f8eec150e5498dae341 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Sat, 10 Aug 2019 17:18:56 +0000 Subject: [PATCH 06/10] Register Kodi services in __init__.py --- homeassistant/components/kodi/__init__.py | 57 +++++++++++++++++++ homeassistant/components/kodi/media_player.py | 34 ----------- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index cbe203841036e4..4a42db85e5d24a 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -1 +1,58 @@ """The kodi component.""" + +import asyncio +import logging + +from homeassistant.const import CONF_PLATFORM +from homeassistant.components.kodi.const import DOMAIN +from homeassistant.components.media_player.const import DOMAIN as MP_DOMAIN + + +_LOGGER = logging.getLogger(__name__) + + +SERVICE_ADD_MEDIA = "add_to_playlist" +SERVICE_CALL_METHOD = "call_method" + +ATTR_MEDIA_TYPE = "media_type" +ATTR_MEDIA_NAME = "media_name" +ATTR_MEDIA_ARTIST_NAME = "artist_name" +ATTR_MEDIA_ID = "media_id" +ATTR_METHOD = "method" + +MEDIA_PLAYER_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.comp_entity_ids}) + +KODI_ADD_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend( + { + vol.Required(ATTR_MEDIA_TYPE): cv.string, + vol.Optional(ATTR_MEDIA_ID): cv.string, + vol.Optional(ATTR_MEDIA_NAME): cv.string, + vol.Optional(ATTR_MEDIA_ARTIST_NAME): cv.string, + } +) +KODI_PLAYER_CALL_METHOD_SCHEMA = MEDIA_PLAYER_SCHEMA.extend( + {vol.Required(ATTR_METHOD): cv.string}, extra=vol.ALLOW_EXTRA +) + +SERVICE_TO_METHOD = { + SERVICE_ADD_MEDIA: { + "method": "async_add_media_to_playlist", + "schema": KODI_ADD_MEDIA_SCHEMA, + }, + SERVICE_CALL_METHOD: { + "method": "async_call_method", + "schema": KODI_CALL_METHOD_SCHEMA, + }, +} + + +async def async_setup(hass, config): + """Setup the Kodi integration.""" + if any( + ((CONF_PLATFORM, DOMAIN) in cfg.items() for cfg in config.get(MP_DOMAIN, [])) + ): + # Register the Kodi media_player services + _LOGGER.critical("Has Kodi media_player") + + # Return boolean to indicate that initialization was successful. + return True diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index b2965b18736509..484500f7b2091c 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -134,40 +134,6 @@ } ) -SERVICE_ADD_MEDIA = "add_to_playlist" -SERVICE_CALL_METHOD = "call_method" - -ATTR_MEDIA_TYPE = "media_type" -ATTR_MEDIA_NAME = "media_name" -ATTR_MEDIA_ARTIST_NAME = "artist_name" -ATTR_MEDIA_ID = "media_id" -ATTR_METHOD = "method" - -MEDIA_PLAYER_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.comp_entity_ids}) - -MEDIA_PLAYER_ADD_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend( - { - vol.Required(ATTR_MEDIA_TYPE): cv.string, - vol.Optional(ATTR_MEDIA_ID): cv.string, - vol.Optional(ATTR_MEDIA_NAME): cv.string, - vol.Optional(ATTR_MEDIA_ARTIST_NAME): cv.string, - } -) -MEDIA_PLAYER_CALL_METHOD_SCHEMA = MEDIA_PLAYER_SCHEMA.extend( - {vol.Required(ATTR_METHOD): cv.string}, extra=vol.ALLOW_EXTRA -) - -SERVICE_TO_METHOD = { - SERVICE_ADD_MEDIA: { - "method": "async_add_media_to_playlist", - "schema": MEDIA_PLAYER_ADD_MEDIA_SCHEMA, - }, - SERVICE_CALL_METHOD: { - "method": "async_call_method", - "schema": MEDIA_PLAYER_CALL_METHOD_SCHEMA, - }, -} - def _check_deprecated_turn_off(hass, turn_off_action): """Create an equivalent script for old turn off actions.""" From d3f616837d85fa881ffc9c28322882c1d1ba2773 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Sat, 10 Aug 2019 17:41:21 +0000 Subject: [PATCH 07/10] Finish registering Kodi services in __init__.py --- homeassistant/components/kodi/__init__.py | 44 ++++++++++++++++++- homeassistant/components/kodi/media_player.py | 43 +----------------- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index 4a42db85e5d24a..341ed571cdf2dd 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -2,11 +2,14 @@ import asyncio import logging +import voluptuous as vol from homeassistant.const import CONF_PLATFORM +from homeassistant.helpers import config_validation as cv from homeassistant.components.kodi.const import DOMAIN from homeassistant.components.media_player.const import DOMAIN as MP_DOMAIN +from homeassistant.const import ATTR_ENTITY_ID _LOGGER = logging.getLogger(__name__) @@ -30,7 +33,7 @@ vol.Optional(ATTR_MEDIA_ARTIST_NAME): cv.string, } ) -KODI_PLAYER_CALL_METHOD_SCHEMA = MEDIA_PLAYER_SCHEMA.extend( +KODI_CALL_METHOD_SCHEMA = MEDIA_PLAYER_SCHEMA.extend( {vol.Required(ATTR_METHOD): cv.string}, extra=vol.ALLOW_EXTRA ) @@ -47,12 +50,49 @@ async def async_setup(hass, config): - """Setup the Kodi integration.""" + """Set up the Kodi integration.""" if any( ((CONF_PLATFORM, DOMAIN) in cfg.items() for cfg in config.get(MP_DOMAIN, [])) ): # Register the Kodi media_player services _LOGGER.critical("Has Kodi media_player") + async def async_service_handler(service): + """Map services to methods on MediaPlayerDevice.""" + method = SERVICE_TO_METHOD.get(service.service) + if not method: + return + + params = { + key: value for key, value in service.data.items() if key != "entity_id" + } + entity_ids = service.data.get("entity_id") + if entity_ids: + target_players = [ + player + for player in hass.data[DOMAIN].values() + if player.entity_id in entity_ids + ] + else: + target_players = hass.data[DOMAIN].values() + + update_tasks = [] + for player in target_players: + await getattr(player, method["method"])(**params) + + for player in target_players: + if player.should_poll: + update_coro = player.async_update_ha_state(True) + update_tasks.append(update_coro) + + if update_tasks: + await asyncio.wait(update_tasks) + + for service in SERVICE_TO_METHOD: + schema = SERVICE_TO_METHOD[service]["schema"] + hass.services.async_register( + DOMAIN, service, async_service_handler, schema=schema + ) + # Return boolean to indicate that initialization was successful. return True diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 484500f7b2091c..14ef0292eccfc6 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -1,5 +1,4 @@ """Support for interfacing with the XBMC/Kodi JSON-RPC API.""" -import asyncio from collections import OrderedDict from functools import wraps import logging @@ -10,6 +9,7 @@ import aiohttp import voluptuous as vol +from homeassistant.components.kodi import SERVICE_CALL_METHOD from homeassistant.components.kodi.const import DOMAIN from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA from homeassistant.components.media_player.const import ( @@ -34,7 +34,6 @@ SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PASSWORD, @@ -225,46 +224,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= hass.data[DOMAIN][ip_addr] = entity async_add_entities([entity], update_before_add=True) - async def async_service_handler(service): - """Map services to methods on MediaPlayerDevice.""" - method = SERVICE_TO_METHOD.get(service.service) - if not method: - return - - params = { - key: value for key, value in service.data.items() if key != "entity_id" - } - entity_ids = service.data.get("entity_id") - if entity_ids: - target_players = [ - player - for player in hass.data[DOMAIN].values() - if player.entity_id in entity_ids - ] - else: - target_players = hass.data[DOMAIN].values() - - update_tasks = [] - for player in target_players: - await getattr(player, method["method"])(**params) - - for player in target_players: - if player.should_poll: - update_coro = player.async_update_ha_state(True) - update_tasks.append(update_coro) - - if update_tasks: - await asyncio.wait(update_tasks) - - if hass.services.has_service(DOMAIN, SERVICE_ADD_MEDIA): - return - - for service in SERVICE_TO_METHOD: - schema = SERVICE_TO_METHOD[service]["schema"] - hass.services.async_register( - DOMAIN, service, async_service_handler, schema=schema - ) - def cmd(func): """Catch command exceptions.""" From 5dce3868cf1707e78ded9bd3acc3e9d144144b11 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Sat, 10 Aug 2019 17:53:32 +0000 Subject: [PATCH 08/10] Remove logging statement intended only for testing --- homeassistant/components/kodi/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index 341ed571cdf2dd..f370dc5e3464ca 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -1,7 +1,6 @@ """The kodi component.""" import asyncio -import logging import voluptuous as vol from homeassistant.const import CONF_PLATFORM @@ -11,8 +10,6 @@ from homeassistant.const import ATTR_ENTITY_ID -_LOGGER = logging.getLogger(__name__) - SERVICE_ADD_MEDIA = "add_to_playlist" SERVICE_CALL_METHOD = "call_method" @@ -55,8 +52,6 @@ async def async_setup(hass, config): ((CONF_PLATFORM, DOMAIN) in cfg.items() for cfg in config.get(MP_DOMAIN, [])) ): # Register the Kodi media_player services - _LOGGER.critical("Has Kodi media_player") - async def async_service_handler(service): """Map services to methods on MediaPlayerDevice.""" method = SERVICE_TO_METHOD.get(service.service) From 57393ab9c324e8ec73378d12dea4d6776e6cf804 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Sat, 10 Aug 2019 18:27:20 +0000 Subject: [PATCH 09/10] Combine homeassistant.const imports --- homeassistant/components/kodi/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index f370dc5e3464ca..5bbffc5df1de5e 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -3,13 +3,11 @@ import asyncio import voluptuous as vol -from homeassistant.const import CONF_PLATFORM +from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM from homeassistant.helpers import config_validation as cv from homeassistant.components.kodi.const import DOMAIN from homeassistant.components.media_player.const import DOMAIN as MP_DOMAIN -from homeassistant.const import ATTR_ENTITY_ID - SERVICE_ADD_MEDIA = "add_to_playlist" SERVICE_CALL_METHOD = "call_method" From b7a29bdb06db3d991763ae6684cadbb9500c83e3 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Sat, 10 Aug 2019 19:11:06 +0000 Subject: [PATCH 10/10] Add __init__.py to .coveragerc --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index a90e80e606cb56..88705fcb5037f4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -317,6 +317,7 @@ omit = homeassistant/components/knx/* homeassistant/components/knx/climate.py homeassistant/components/knx/cover.py + homeassistant/components/kodi/__init__.py homeassistant/components/kodi/const.py homeassistant/components/kodi/media_player.py homeassistant/components/kodi/notify.py