mirror of
https://github.com/home-assistant/core.git
synced 2025-07-07 21:37:07 +00:00
Fix Kodi specific services registry and add descriptions (#7551)
* Fix Kodi specific services, add descriptions, add more handled exceptions - Fixes issue #7528 - Add descriptions for Kodi specific services in services.yaml. - Error handling in Kodi API errors. - Make compatible the existent specific service `media_player.kodi_set_shuffle` with the general `media_player.shuffle_set` service (both use the same method but with different named parameter, I think the Kodi specific service should be eliminated, since it is not) * fix line too long * removed new services (for another PR); removed `kodi_set_shuffle` service * requested changes - Removed `kodi_set_shuffle` service. - Optional `media_name` and `artist_name` parameters. `media_name` defaults to 'ALL'. - Guard clause to check if the services are already registered.
This commit is contained in:
parent
ad15844cf4
commit
4cdf0b4969
@ -9,16 +9,18 @@ from functools import wraps
|
|||||||
import logging
|
import logging
|
||||||
import urllib
|
import urllib
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config import load_yaml_config_file
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
|
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
|
||||||
SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_STOP,
|
SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_STOP,
|
||||||
SUPPORT_TURN_OFF, SUPPORT_PLAY, SUPPORT_VOLUME_STEP, MediaPlayerDevice,
|
SUPPORT_TURN_OFF, SUPPORT_PLAY, SUPPORT_VOLUME_STEP, SUPPORT_SHUFFLE_SET,
|
||||||
PLATFORM_SCHEMA, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO,
|
MediaPlayerDevice, PLATFORM_SCHEMA, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
|
||||||
MEDIA_TYPE_PLAYLIST, MEDIA_PLAYER_SCHEMA, DOMAIN)
|
MEDIA_TYPE_VIDEO, MEDIA_TYPE_PLAYLIST, MEDIA_PLAYER_SCHEMA, DOMAIN)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_HOST, CONF_NAME,
|
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_HOST, CONF_NAME,
|
||||||
CONF_PORT, CONF_SSL, CONF_PROXY_SSL, CONF_USERNAME, CONF_PASSWORD,
|
CONF_PORT, CONF_SSL, CONF_PROXY_SSL, CONF_USERNAME, CONF_PASSWORD,
|
||||||
@ -61,8 +63,9 @@ MEDIA_TYPES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \
|
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \
|
||||||
SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_PLAY | SUPPORT_VOLUME_STEP
|
SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_SHUFFLE_SET | \
|
||||||
|
SUPPORT_PLAY | SUPPORT_VOLUME_STEP
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_HOST): cv.string,
|
vol.Required(CONF_HOST): cv.string,
|
||||||
@ -78,17 +81,14 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
SERVICE_ADD_MEDIA = 'kodi_add_to_playlist'
|
SERVICE_ADD_MEDIA = 'kodi_add_to_playlist'
|
||||||
SERVICE_SET_SHUFFLE = 'kodi_set_shuffle'
|
|
||||||
|
DATA_KODI = 'kodi'
|
||||||
|
|
||||||
ATTR_MEDIA_TYPE = 'media_type'
|
ATTR_MEDIA_TYPE = 'media_type'
|
||||||
ATTR_MEDIA_NAME = 'media_name'
|
ATTR_MEDIA_NAME = 'media_name'
|
||||||
ATTR_MEDIA_ARTIST_NAME = 'artist_name'
|
ATTR_MEDIA_ARTIST_NAME = 'artist_name'
|
||||||
ATTR_MEDIA_ID = 'media_id'
|
ATTR_MEDIA_ID = 'media_id'
|
||||||
|
|
||||||
MEDIA_PLAYER_SET_SHUFFLE_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({
|
|
||||||
vol.Required('shuffle_on'): cv.boolean,
|
|
||||||
})
|
|
||||||
|
|
||||||
MEDIA_PLAYER_ADD_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({
|
MEDIA_PLAYER_ADD_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({
|
||||||
vol.Required(ATTR_MEDIA_TYPE): cv.string,
|
vol.Required(ATTR_MEDIA_TYPE): cv.string,
|
||||||
vol.Optional(ATTR_MEDIA_ID): cv.string,
|
vol.Optional(ATTR_MEDIA_ID): cv.string,
|
||||||
@ -100,15 +100,14 @@ SERVICE_TO_METHOD = {
|
|||||||
SERVICE_ADD_MEDIA: {
|
SERVICE_ADD_MEDIA: {
|
||||||
'method': 'async_add_media_to_playlist',
|
'method': 'async_add_media_to_playlist',
|
||||||
'schema': MEDIA_PLAYER_ADD_MEDIA_SCHEMA},
|
'schema': MEDIA_PLAYER_ADD_MEDIA_SCHEMA},
|
||||||
SERVICE_SET_SHUFFLE: {
|
|
||||||
'method': 'async_set_shuffle',
|
|
||||||
'schema': MEDIA_PLAYER_SET_SHUFFLE_SCHEMA},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
"""Set up the Kodi platform."""
|
"""Set up the Kodi platform."""
|
||||||
|
if DATA_KODI not in hass.data:
|
||||||
|
hass.data[DATA_KODI] = []
|
||||||
host = config.get(CONF_HOST)
|
host = config.get(CONF_HOST)
|
||||||
port = config.get(CONF_PORT)
|
port = config.get(CONF_PORT)
|
||||||
tcp_port = config.get(CONF_TCP_PORT)
|
tcp_port = config.get(CONF_TCP_PORT)
|
||||||
@ -130,6 +129,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|||||||
password=config.get(CONF_PASSWORD),
|
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), websocket=websocket)
|
||||||
|
|
||||||
|
hass.data[DATA_KODI].append(entity)
|
||||||
async_add_devices([entity], update_before_add=True)
|
async_add_devices([entity], update_before_add=True)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@ -141,23 +141,37 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|||||||
|
|
||||||
params = {key: value for key, value in service.data.items()
|
params = {key: value for key, value in service.data.items()
|
||||||
if key != 'entity_id'}
|
if key != 'entity_id'}
|
||||||
|
entity_ids = service.data.get('entity_id')
|
||||||
yield from getattr(entity, method['method'])(**params)
|
if entity_ids:
|
||||||
|
target_players = [player for player in hass.data[DATA_KODI]
|
||||||
|
if player.entity_id in entity_ids]
|
||||||
|
else:
|
||||||
|
target_players = hass.data[DATA_KODI]
|
||||||
|
|
||||||
update_tasks = []
|
update_tasks = []
|
||||||
if entity.should_poll:
|
for player in target_players:
|
||||||
update_coro = entity.async_update_ha_state(True)
|
yield from getattr(player, method['method'])(**params)
|
||||||
update_tasks.append(update_coro)
|
|
||||||
|
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:
|
if update_tasks:
|
||||||
yield from asyncio.wait(update_tasks, loop=hass.loop)
|
yield from asyncio.wait(update_tasks, loop=hass.loop)
|
||||||
|
|
||||||
|
if hass.services.has_service(DOMAIN, SERVICE_ADD_MEDIA):
|
||||||
|
return
|
||||||
|
|
||||||
|
descriptions = yield from hass.loop.run_in_executor(
|
||||||
|
None, load_yaml_config_file, os.path.join(
|
||||||
|
os.path.dirname(__file__), 'services.yaml'))
|
||||||
|
|
||||||
for service in SERVICE_TO_METHOD:
|
for service in SERVICE_TO_METHOD:
|
||||||
schema = SERVICE_TO_METHOD[service].get(
|
schema = SERVICE_TO_METHOD[service]['schema']
|
||||||
'schema', MEDIA_PLAYER_SCHEMA)
|
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
DOMAIN, service, async_service_handler,
|
DOMAIN, service, async_service_handler,
|
||||||
description=None, schema=schema)
|
description=descriptions.get(service), schema=schema)
|
||||||
|
|
||||||
|
|
||||||
def cmd(func):
|
def cmd(func):
|
||||||
@ -657,16 +671,16 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
{"item": {"file": str(media_id)}})
|
{"item": {"file": str(media_id)}})
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_set_shuffle(self, shuffle_on):
|
def async_set_shuffle(self, shuffle):
|
||||||
"""Set shuffle mode, for the first player."""
|
"""Set shuffle mode, for the first player."""
|
||||||
if len(self._players) < 1:
|
if len(self._players) < 1:
|
||||||
raise RuntimeError("Error: No active player.")
|
raise RuntimeError("Error: No active player.")
|
||||||
yield from self.server.Player.SetShuffle(
|
yield from self.server.Player.SetShuffle(
|
||||||
{"playerid": self._players[0]['playerid'], "shuffle": shuffle_on})
|
{"playerid": self._players[0]['playerid'], "shuffle": shuffle})
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_add_media_to_playlist(
|
def async_add_media_to_playlist(
|
||||||
self, media_type, media_id=None, media_name='', artist_name=''):
|
self, media_type, media_id=None, media_name='ALL', artist_name=''):
|
||||||
"""Add a media to default playlist (i.e. playlistid=0).
|
"""Add a media to default playlist (i.e. playlistid=0).
|
||||||
|
|
||||||
First the media type must be selected, then
|
First the media type must be selected, then
|
||||||
@ -675,13 +689,14 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
All the albums of an artist can be added with
|
All the albums of an artist can be added with
|
||||||
media_name="ALL"
|
media_name="ALL"
|
||||||
"""
|
"""
|
||||||
|
import jsonrpc_base
|
||||||
|
params = {"playlistid": 0}
|
||||||
if media_type == "SONG":
|
if media_type == "SONG":
|
||||||
if media_id is None:
|
if media_id is None:
|
||||||
media_id = yield from self.async_find_song(
|
media_id = yield from self.async_find_song(
|
||||||
media_name, artist_name)
|
media_name, artist_name)
|
||||||
|
if media_id:
|
||||||
yield from self.server.Playlist.Add(
|
params["item"] = {"songid": int(media_id)}
|
||||||
{"playlistid": 0, "item": {"songid": int(media_id)}})
|
|
||||||
|
|
||||||
elif media_type == "ALBUM":
|
elif media_type == "ALBUM":
|
||||||
if media_id is None:
|
if media_id is None:
|
||||||
@ -691,12 +706,22 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
|
|
||||||
media_id = yield from self.async_find_album(
|
media_id = yield from self.async_find_album(
|
||||||
media_name, artist_name)
|
media_name, artist_name)
|
||||||
|
if media_id:
|
||||||
|
params["item"] = {"albumid": int(media_id)}
|
||||||
|
|
||||||
yield from self.server.Playlist.Add(
|
|
||||||
{"playlistid": 0, "item": {"albumid": int(media_id)}})
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Unrecognized media type.")
|
raise RuntimeError("Unrecognized media type.")
|
||||||
|
|
||||||
|
if media_id is not None:
|
||||||
|
try:
|
||||||
|
yield from self.server.Playlist.Add(params)
|
||||||
|
except jsonrpc_base.jsonrpc.ProtocolError as exc:
|
||||||
|
result = exc.args[2]['error']
|
||||||
|
_LOGGER.error('Run API method %s.Playlist.Add(%s) error: %s',
|
||||||
|
self.entity_id, media_type, result)
|
||||||
|
else:
|
||||||
|
_LOGGER.warning('No media detected for Playlist.Add')
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_add_all_albums(self, artist_name):
|
def async_add_all_albums(self, artist_name):
|
||||||
"""Add all albums of an artist to default playlist (i.e. playlistid=0).
|
"""Add all albums of an artist to default playlist (i.e. playlistid=0).
|
||||||
@ -734,9 +759,13 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
def async_find_artist(self, artist_name):
|
def async_find_artist(self, artist_name):
|
||||||
"""Find artist by name."""
|
"""Find artist by name."""
|
||||||
artists = yield from self.async_get_artists()
|
artists = yield from self.async_get_artists()
|
||||||
out = self._find(
|
try:
|
||||||
artist_name, [a['artist'] for a in artists['artists']])
|
out = self._find(
|
||||||
return artists['artists'][out[0][0]]['artistid']
|
artist_name, [a['artist'] for a in artists['artists']])
|
||||||
|
return artists['artists'][out[0][0]]['artistid']
|
||||||
|
except KeyError:
|
||||||
|
_LOGGER.warning('No artists were found: %s', artist_name)
|
||||||
|
return None
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_get_songs(self, artist_id=None):
|
def async_get_songs(self, artist_id=None):
|
||||||
@ -769,8 +798,14 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
artist_id = yield from self.async_find_artist(artist_name)
|
artist_id = yield from self.async_find_artist(artist_name)
|
||||||
|
|
||||||
albums = yield from self.async_get_albums(artist_id)
|
albums = yield from self.async_get_albums(artist_id)
|
||||||
out = self._find(album_name, [a['label'] for a in albums['albums']])
|
try:
|
||||||
return albums['albums'][out[0][0]]['albumid']
|
out = self._find(
|
||||||
|
album_name, [a['label'] for a in albums['albums']])
|
||||||
|
return albums['albums'][out[0][0]]['albumid']
|
||||||
|
except KeyError:
|
||||||
|
_LOGGER.warning('No albums were found with artist: %s, album: %s',
|
||||||
|
artist_name, album_name)
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _find(key_word, words):
|
def _find(key_word, words):
|
||||||
|
@ -269,3 +269,23 @@ soundtouch_remove_zone_slave:
|
|||||||
slaves:
|
slaves:
|
||||||
description: Name of slaves entities to remove from the existing zone
|
description: Name of slaves entities to remove from the existing zone
|
||||||
example: 'media_player.soundtouch_bedroom'
|
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'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user