From f7d25396a463fcfb8afbb50199b3128413fefd31 Mon Sep 17 00:00:00 2001
From: Eugenio Panadero <eugenio.panadero@gmail.com>
Date: Wed, 17 May 2017 14:42:47 +0200
Subject: [PATCH] Kodi specific service to call Kodi API methods (#7603)

* Kodi specific services to call Kodi API methods

 - new service: `kodi_execute_addon` to run a Kodi Addon with optional parameters. Results of the Kodi API call, if any, are redirected in a Home Assistant event: `kodi_execute_addon_result`.
 - new service: `kodi_run_method` to run a Kodi JSONRPC API method with optional parameters. Results of the Kodi API call are redirected in a Home Assistant event: `kodi_run_method_result`.
 - Add descriptions in services.yaml.
 - Add `timeout` parameter to yaml config (needed to make slow queries to the JSONRPC API, default timeout is set to 5s).
 - Trigger events with the results of the Kodi API calls, with:
 ```
 event_data = {
   'result': api_call_results,
   'result_ok': boolean,
   'input': api_call_parameters,
   'entity_id': 'media_player.kodi'}
```

* no need to clean OrderedDicts; no need for the `kodi_execute_addon` service

* no need for the `kodi_execute_addon` service

* unused import

* naming changes
---
 homeassistant/components/media_player/kodi.py | 44 +++++++++++++++++--
 .../components/media_player/services.yaml     | 11 +++++
 2 files changed, 51 insertions(+), 4 deletions(-)

diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py
index 18c01c396ac..9861887df89 100644
--- a/homeassistant/components/media_player/kodi.py
+++ b/homeassistant/components/media_player/kodi.py
@@ -24,7 +24,7 @@ from homeassistant.components.media_player import (
 from homeassistant.const import (
     STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_HOST, CONF_NAME,
     CONF_PORT, CONF_SSL, CONF_PROXY_SSL, CONF_USERNAME, CONF_PASSWORD,
-    EVENT_HOMEASSISTANT_STOP)
+    CONF_TIMEOUT, EVENT_HOMEASSISTANT_STOP)
 from homeassistant.core import callback
 from homeassistant.helpers.aiohttp_client import async_get_clientsession
 import homeassistant.helpers.config_validation as cv
@@ -34,6 +34,8 @@ REQUIREMENTS = ['jsonrpc-async==0.6', 'jsonrpc-websocket==0.5']
 
 _LOGGER = logging.getLogger(__name__)
 
+EVENT_KODI_CALL_METHOD_RESULT = 'kodi_call_method_result'
+
 CONF_TCP_PORT = 'tcp_port'
 CONF_TURN_OFF_ACTION = 'turn_off_action'
 CONF_ENABLE_WEBSOCKET = 'enable_websocket'
@@ -74,6 +76,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
     vol.Optional(CONF_TCP_PORT, default=DEFAULT_TCP_PORT): cv.port,
     vol.Optional(CONF_PROXY_SSL, default=DEFAULT_PROXY_SSL): cv.boolean,
     vol.Optional(CONF_TURN_OFF_ACTION, default=None): vol.In(TURN_OFF_ACTION),
+    vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
     vol.Inclusive(CONF_USERNAME, 'auth'): cv.string,
     vol.Inclusive(CONF_PASSWORD, 'auth'): cv.string,
     vol.Optional(CONF_ENABLE_WEBSOCKET, default=DEFAULT_ENABLE_WEBSOCKET):
@@ -81,6 +84,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
 })
 
 SERVICE_ADD_MEDIA = 'kodi_add_to_playlist'
+SERVICE_CALL_METHOD = 'kodi_call_method'
 
 DATA_KODI = 'kodi'
 
@@ -88,6 +92,7 @@ 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_ADD_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({
     vol.Required(ATTR_MEDIA_TYPE): cv.string,
@@ -95,11 +100,17 @@ MEDIA_PLAYER_ADD_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({
     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},
 }
 
 
@@ -127,7 +138,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
         host=host, port=port, tcp_port=tcp_port, encryption=encryption,
         username=config.get(CONF_USERNAME),
         password=config.get(CONF_PASSWORD),
-        turn_off_action=config.get(CONF_TURN_OFF_ACTION), websocket=websocket)
+        turn_off_action=config.get(CONF_TURN_OFF_ACTION),
+        timeout=config.get(CONF_TIMEOUT), websocket=websocket)
 
     hass.data[DATA_KODI].append(entity)
     async_add_devices([entity], update_before_add=True)
@@ -199,7 +211,7 @@ class KodiDevice(MediaPlayerDevice):
 
     def __init__(self, hass, name, host, port, tcp_port, encryption=False,
                  username=None, password=None, turn_off_action=None,
-                 websocket=True):
+                 timeout=DEFAULT_TIMEOUT, websocket=True):
         """Initialize the Kodi device."""
         import jsonrpc_async
         import jsonrpc_websocket
@@ -207,7 +219,7 @@ class KodiDevice(MediaPlayerDevice):
         self._name = name
 
         kwargs = {
-            'timeout': DEFAULT_TIMEOUT,
+            'timeout': timeout,
             'session': async_get_clientsession(hass),
         }
 
@@ -678,6 +690,30 @@ class KodiDevice(MediaPlayerDevice):
         yield from self.server.Player.SetShuffle(
             {"playerid": self._players[0]['playerid'], "shuffle": shuffle})
 
+    @asyncio.coroutine
+    def async_call_method(self, method, **kwargs):
+        """Run Kodi JSONRPC API method with params."""
+        import jsonrpc_base
+        _LOGGER.debug('Run API method "%s", kwargs=%s', method, kwargs)
+        result_ok = False
+        try:
+            result = yield from getattr(self.server, method)(**kwargs)
+            result_ok = True
+        except jsonrpc_base.jsonrpc.ProtocolError as exc:
+            result = exc.args[2]['error']
+            _LOGGER.error('Run API method %s.%s(%s) error: %s',
+                          self.entity_id, method, kwargs, result)
+
+        if isinstance(result, dict):
+            event_data = {'entity_id': self.entity_id,
+                          'result': result,
+                          'result_ok': result_ok,
+                          'input': {'method': method, 'params': kwargs}}
+            _LOGGER.debug('EVENT kodi_call_method_result: %s', event_data)
+            self.hass.bus.async_fire(EVENT_KODI_CALL_METHOD_RESULT,
+                                     event_data=event_data)
+        return result
+
     @asyncio.coroutine
     def async_add_media_to_playlist(
             self, media_type, media_id=None, media_name='ALL', artist_name=''):
diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml
index 4d5f85c05eb..00ce0987fd9 100644
--- a/homeassistant/components/media_player/services.yaml
+++ b/homeassistant/components/media_player/services.yaml
@@ -289,3 +289,14 @@ kodi_add_to_playlist:
     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'