From 68ee8286741f970feefdbf5c9b874bd54c6973f0 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Sat, 10 Aug 2019 14:31:04 -0700 Subject: [PATCH] Move Kodi services from 'media_player' domain to 'kodi' (#25753) * Create const.py * Register services to 'kodi' domain, not 'media_player' * Add const.py to .coveragerc * 'DATA_KODI' -> 'DOMAIN' * Move the Kodi services descriptions to the Kodi component * Register Kodi services in __init__.py * Finish registering Kodi services in __init__.py * Remove logging statement intended only for testing * Combine homeassistant.const imports * Add __init__.py to .coveragerc --- .coveragerc | 2 + homeassistant/components/kodi/__init__.py | 90 ++++++++++++++++++ homeassistant/components/kodi/const.py | 2 + homeassistant/components/kodi/media_player.py | 91 ++----------------- homeassistant/components/kodi/services.yaml | 30 ++++++ .../components/media_player/services.yaml | 29 ------ 6 files changed, 131 insertions(+), 113 deletions(-) create mode 100644 homeassistant/components/kodi/const.py diff --git a/.coveragerc b/.coveragerc index 24844630f4a..445d3e163ee 100644 --- a/.coveragerc +++ b/.coveragerc @@ -317,6 +317,8 @@ 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 homeassistant/components/konnected/* diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index cbe20384103..5bbffc5df1d 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -1 +1,91 @@ """The kodi component.""" + +import asyncio +import voluptuous as vol + +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 + + +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_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): + """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 + 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/const.py b/homeassistant/components/kodi/const.py new file mode 100644 index 00000000000..7cb93f0d283 --- /dev/null +++ b/homeassistant/components/kodi/const.py @@ -0,0 +1,2 @@ +"""Constants for the Kodi platform.""" +DOMAIN = "kodi" diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index de50a2ef4de..14ef0292ecc 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,9 +9,10 @@ import urllib 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 ( - DOMAIN, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, @@ -34,7 +34,6 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PASSWORD, @@ -134,42 +133,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( } ) -SERVICE_ADD_MEDIA = "kodi_add_to_playlist" -SERVICE_CALL_METHOD = "kodi_call_method" - -DATA_KODI = "kodi" - -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.""" @@ -205,8 +168,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 +194,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,49 +221,9 @@ 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): - """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[DATA_KODI].values() - if player.entity_id in entity_ids - ] - else: - target_players = hass.data[DATA_KODI].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.""" diff --git a/homeassistant/components/kodi/services.yaml b/homeassistant/components/kodi/services.yaml index e69de29bb2d..01dde6a249c 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 d7f636d070a..5421085c308 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: