mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
More features for the Bluesound component (#11450)
* Added support for join and unjoin * Added support for sleep functionality * Fixed supported features * Removed long lines and fixed documentation strings * Fixed D401, imperative mood * Added shuffle support * Removed unnecessary log row * Removed model, modelname and brand * Removed descriptions * Removed polling command on method run. This change is not needed * Fixed merge errors * Removed unused usings * Pylint fixes * Hound fixes * Remove attr Sleep and removed white space in services.xml
This commit is contained in:
parent
72fa170265
commit
1143499301
@ -16,14 +16,16 @@ import async_timeout
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_CLEAR_PLAYLIST,
|
ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA,
|
||||||
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA,
|
SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
|
||||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, SUPPORT_STOP,
|
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
|
||||||
|
SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP,
|
||||||
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP,
|
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP,
|
||||||
MediaPlayerDevice)
|
MediaPlayerDevice)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST, CONF_HOSTS, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_START,
|
ATTR_ENTITY_ID, CONF_HOST, CONF_HOSTS, CONF_NAME, CONF_PORT,
|
||||||
EVENT_HOMEASSISTANT_STOP, STATE_IDLE, STATE_PAUSED, STATE_PLAYING)
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, STATE_IDLE,
|
||||||
|
STATE_OFF, STATE_PAUSED, STATE_PLAYING)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@ -35,10 +37,14 @@ REQUIREMENTS = ['xmltodict==0.11.0']
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
STATE_OFFLINE = 'offline'
|
STATE_GROUPED = 'grouped'
|
||||||
ATTR_MODEL = 'model'
|
|
||||||
ATTR_MODEL_NAME = 'model_name'
|
ATTR_MASTER = 'master'
|
||||||
ATTR_BRAND = 'brand'
|
|
||||||
|
SERVICE_JOIN = 'bluesound_join'
|
||||||
|
SERVICE_UNJOIN = 'bluesound_unjoin'
|
||||||
|
SERVICE_SET_TIMER = 'bluesound_set_sleep_timer'
|
||||||
|
SERVICE_CLEAR_TIMER = 'bluesound_clear_sleep_timer'
|
||||||
|
|
||||||
DATA_BLUESOUND = 'bluesound'
|
DATA_BLUESOUND = 'bluesound'
|
||||||
DEFAULT_PORT = 11000
|
DEFAULT_PORT = 11000
|
||||||
@ -58,6 +64,29 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
}])
|
}])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
BS_SCHEMA = vol.Schema({
|
||||||
|
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||||
|
})
|
||||||
|
|
||||||
|
BS_JOIN_SCHEMA = BS_SCHEMA.extend({
|
||||||
|
vol.Required(ATTR_MASTER): cv.entity_id,
|
||||||
|
})
|
||||||
|
|
||||||
|
SERVICE_TO_METHOD = {
|
||||||
|
SERVICE_JOIN: {
|
||||||
|
'method': 'async_join',
|
||||||
|
'schema': BS_JOIN_SCHEMA},
|
||||||
|
SERVICE_UNJOIN: {
|
||||||
|
'method': 'async_unjoin',
|
||||||
|
'schema': BS_SCHEMA},
|
||||||
|
SERVICE_SET_TIMER: {
|
||||||
|
'method': 'async_increase_timer',
|
||||||
|
'schema': BS_SCHEMA},
|
||||||
|
SERVICE_CLEAR_TIMER: {
|
||||||
|
'method': 'async_clear_timer',
|
||||||
|
'schema': BS_SCHEMA}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _add_player(hass, async_add_devices, host, port=None, name=None):
|
def _add_player(hass, async_add_devices, host, port=None, name=None):
|
||||||
"""Add Bluesound players."""
|
"""Add Bluesound players."""
|
||||||
@ -120,6 +149,30 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|||||||
hass, async_add_devices, host.get(CONF_HOST),
|
hass, async_add_devices, host.get(CONF_HOST),
|
||||||
host.get(CONF_PORT), host.get(CONF_NAME))
|
host.get(CONF_PORT), host.get(CONF_NAME))
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_service_handler(service):
|
||||||
|
"""Map services to method of Bluesound devices."""
|
||||||
|
method = SERVICE_TO_METHOD.get(service.service)
|
||||||
|
if not method:
|
||||||
|
return
|
||||||
|
|
||||||
|
params = {key: value for key, value in service.data.items()
|
||||||
|
if key != ATTR_ENTITY_ID}
|
||||||
|
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||||
|
if entity_ids:
|
||||||
|
target_players = [player for player in hass.data[DATA_BLUESOUND]
|
||||||
|
if player.entity_id in entity_ids]
|
||||||
|
else:
|
||||||
|
target_players = hass.data[DATA_BLUESOUND]
|
||||||
|
|
||||||
|
for player in target_players:
|
||||||
|
yield from getattr(player, method['method'])(**params)
|
||||||
|
|
||||||
|
for service in SERVICE_TO_METHOD:
|
||||||
|
schema = SERVICE_TO_METHOD[service]['schema']
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, service, async_service_handler, schema=schema)
|
||||||
|
|
||||||
|
|
||||||
class BluesoundPlayer(MediaPlayerDevice):
|
class BluesoundPlayer(MediaPlayerDevice):
|
||||||
"""Representation of a Bluesound Player."""
|
"""Representation of a Bluesound Player."""
|
||||||
@ -128,13 +181,10 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
"""Initialize the media player."""
|
"""Initialize the media player."""
|
||||||
self.host = host
|
self.host = host
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._port = port
|
self.port = port
|
||||||
self._polling_session = async_get_clientsession(hass)
|
self._polling_session = async_get_clientsession(hass)
|
||||||
self._polling_task = None # The actual polling task.
|
self._polling_task = None # The actual polling task.
|
||||||
self._name = name
|
self._name = name
|
||||||
self._brand = None
|
|
||||||
self._model = None
|
|
||||||
self._model_name = None
|
|
||||||
self._icon = None
|
self._icon = None
|
||||||
self._capture_items = []
|
self._capture_items = []
|
||||||
self._services_items = []
|
self._services_items = []
|
||||||
@ -145,9 +195,13 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
self._is_online = False
|
self._is_online = False
|
||||||
self._retry_remove = None
|
self._retry_remove = None
|
||||||
self._lastvol = None
|
self._lastvol = None
|
||||||
|
self._master = None
|
||||||
|
self._is_master = False
|
||||||
|
self._group_name = None
|
||||||
|
|
||||||
self._init_callback = init_callback
|
self._init_callback = init_callback
|
||||||
if self._port is None:
|
if self.port is None:
|
||||||
self._port = DEFAULT_PORT
|
self.port = DEFAULT_PORT
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _try_get_index(string, search_string):
|
def _try_get_index(string, search_string):
|
||||||
@ -158,7 +212,7 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
return -1
|
return -1
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def _internal_update_sync_status(
|
def force_update_sync_status(
|
||||||
self, on_updated_cb=None, raise_timeout=False):
|
self, on_updated_cb=None, raise_timeout=False):
|
||||||
"""Update the internal status."""
|
"""Update the internal status."""
|
||||||
resp = None
|
resp = None
|
||||||
@ -174,14 +228,27 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
|
|
||||||
if not self._name:
|
if not self._name:
|
||||||
self._name = self._sync_status.get('@name', self.host)
|
self._name = self._sync_status.get('@name', self.host)
|
||||||
if not self._brand:
|
|
||||||
self._brand = self._sync_status.get('@brand', self.host)
|
|
||||||
if not self._model:
|
|
||||||
self._model = self._sync_status.get('@model', self.host)
|
|
||||||
if not self._icon:
|
if not self._icon:
|
||||||
self._icon = self._sync_status.get('@icon', self.host)
|
self._icon = self._sync_status.get('@icon', self.host)
|
||||||
if not self._model_name:
|
|
||||||
self._model_name = self._sync_status.get('@modelName', self.host)
|
master = self._sync_status.get('master', None)
|
||||||
|
if master is not None:
|
||||||
|
self._is_master = False
|
||||||
|
master_host = master.get('#text')
|
||||||
|
master_device = [device for device in
|
||||||
|
self._hass.data[DATA_BLUESOUND]
|
||||||
|
if device.host == master_host]
|
||||||
|
|
||||||
|
if master_device and master_host != self.host:
|
||||||
|
self._master = master_device[0]
|
||||||
|
else:
|
||||||
|
self._master = None
|
||||||
|
_LOGGER.error("Master not found %s", master_host)
|
||||||
|
else:
|
||||||
|
if self._master is not None:
|
||||||
|
self._master = None
|
||||||
|
slaves = self._sync_status.get('slave', None)
|
||||||
|
self._is_master = slaves is not None
|
||||||
|
|
||||||
if on_updated_cb:
|
if on_updated_cb:
|
||||||
on_updated_cb()
|
on_updated_cb()
|
||||||
@ -223,7 +290,7 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
self._retry_remove()
|
self._retry_remove()
|
||||||
self._retry_remove = None
|
self._retry_remove = None
|
||||||
|
|
||||||
yield from self._internal_update_sync_status(
|
yield from self.force_update_sync_status(
|
||||||
self._init_callback, True)
|
self._init_callback, True)
|
||||||
except (asyncio.TimeoutError, ClientError):
|
except (asyncio.TimeoutError, ClientError):
|
||||||
_LOGGER.info("Node %s is offline, retrying later", self.host)
|
_LOGGER.info("Node %s is offline, retrying later", self.host)
|
||||||
@ -256,7 +323,7 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
|
|
||||||
if method[0] == '/':
|
if method[0] == '/':
|
||||||
method = method[1:]
|
method = method[1:]
|
||||||
url = "http://{}:{}/{}".format(self.host, self._port, method)
|
url = "http://{}:{}/{}".format(self.host, self.port, method)
|
||||||
|
|
||||||
_LOGGER.debug("Calling URL: %s", url)
|
_LOGGER.debug("Calling URL: %s", url)
|
||||||
response = None
|
response = None
|
||||||
@ -297,26 +364,47 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
etag = self._status.get('@etag', '')
|
etag = self._status.get('@etag', '')
|
||||||
|
|
||||||
if etag != '':
|
if etag != '':
|
||||||
url = 'Status?etag={}&timeout=60.0'.format(etag)
|
url = 'Status?etag={}&timeout=120.0'.format(etag)
|
||||||
url = "http://{}:{}/{}".format(self.host, self._port, url)
|
url = "http://{}:{}/{}".format(self.host, self.port, url)
|
||||||
|
|
||||||
_LOGGER.debug("Calling URL: %s", url)
|
_LOGGER.debug("Calling URL: %s", url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
with async_timeout.timeout(65, loop=self._hass.loop):
|
with async_timeout.timeout(125, loop=self._hass.loop):
|
||||||
response = yield from self._polling_session.get(
|
response = yield from self._polling_session.get(
|
||||||
url,
|
url,
|
||||||
headers={CONNECTION: KEEP_ALIVE})
|
headers={CONNECTION: KEEP_ALIVE})
|
||||||
|
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
_LOGGER.error("Error %s on %s", response.status, url)
|
_LOGGER.error("Error %s on %s. Trying one more time.",
|
||||||
|
response.status, url)
|
||||||
|
else:
|
||||||
|
result = yield from response.text()
|
||||||
|
self._is_online = True
|
||||||
|
self._last_status_update = dt_util.utcnow()
|
||||||
|
self._status = xmltodict.parse(result)['status'].copy()
|
||||||
|
|
||||||
result = yield from response.text()
|
group_name = self._status.get('groupName', None)
|
||||||
self._is_online = True
|
if group_name != self._group_name:
|
||||||
self._last_status_update = dt_util.utcnow()
|
_LOGGER.debug('Group name change detected on device: %s',
|
||||||
self._status = xmltodict.parse(result)['status'].copy()
|
self.host)
|
||||||
self.schedule_update_ha_state()
|
self._group_name = group_name
|
||||||
|
# the sleep is needed to make sure that the
|
||||||
|
# devices is synced
|
||||||
|
yield from asyncio.sleep(1, loop=self._hass.loop)
|
||||||
|
yield from self.async_trigger_sync_on_all()
|
||||||
|
elif self.is_grouped:
|
||||||
|
# when player is grouped we need to fetch volume from
|
||||||
|
# sync_status. We will force an update if the player is
|
||||||
|
# grouped this isn't a foolproof solution. A better
|
||||||
|
# solution would be to fetch sync_status more often when
|
||||||
|
# the device is playing. This would solve alot of
|
||||||
|
# problems. This change will be done when the
|
||||||
|
# communication is moved to a separate library
|
||||||
|
yield from self.force_update_sync_status()
|
||||||
|
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
except (asyncio.TimeoutError, ClientError):
|
except (asyncio.TimeoutError, ClientError):
|
||||||
self._is_online = False
|
self._is_online = False
|
||||||
@ -327,12 +415,20 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
self._name)
|
self._name)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_trigger_sync_on_all(self):
|
||||||
|
"""Trigger sync status update on all devices."""
|
||||||
|
_LOGGER.debug("Trigger sync status on all devices")
|
||||||
|
|
||||||
|
for player in self._hass.data[DATA_BLUESOUND]:
|
||||||
|
yield from player.force_update_sync_status()
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@Throttle(SYNC_STATUS_INTERVAL)
|
@Throttle(SYNC_STATUS_INTERVAL)
|
||||||
def async_update_sync_status(self, on_updated_cb=None,
|
def async_update_sync_status(self, on_updated_cb=None,
|
||||||
raise_timeout=False):
|
raise_timeout=False):
|
||||||
"""Update sync status."""
|
"""Update sync status."""
|
||||||
yield from self._internal_update_sync_status(
|
yield from self.force_update_sync_status(
|
||||||
on_updated_cb, raise_timeout=False)
|
on_updated_cb, raise_timeout=False)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@ -433,7 +529,10 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
if self._status is None:
|
if self._status is None:
|
||||||
return STATE_OFFLINE
|
return STATE_OFF
|
||||||
|
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return STATE_GROUPED
|
||||||
|
|
||||||
status = self._status.get('state', None)
|
status = self._status.get('state', None)
|
||||||
if status == 'pause' or status == 'stop':
|
if status == 'pause' or status == 'stop':
|
||||||
@ -445,7 +544,8 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def media_title(self):
|
def media_title(self):
|
||||||
"""Title of current playing media."""
|
"""Title of current playing media."""
|
||||||
if self._status is None:
|
if (self._status is None or
|
||||||
|
(self.is_grouped and not self.is_master)):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self._status.get('title1', None)
|
return self._status.get('title1', None)
|
||||||
@ -456,6 +556,9 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
if self._status is None:
|
if self._status is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return self._group_name
|
||||||
|
|
||||||
artist = self._status.get('artist', None)
|
artist = self._status.get('artist', None)
|
||||||
if not artist:
|
if not artist:
|
||||||
artist = self._status.get('title2', None)
|
artist = self._status.get('title2', None)
|
||||||
@ -464,7 +567,8 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def media_album_name(self):
|
def media_album_name(self):
|
||||||
"""Artist of current playing media (Music track only)."""
|
"""Artist of current playing media (Music track only)."""
|
||||||
if self._status is None:
|
if (self._status is None or
|
||||||
|
(self.is_grouped and not self.is_master)):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
album = self._status.get('album', None)
|
album = self._status.get('album', None)
|
||||||
@ -475,21 +579,23 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def media_image_url(self):
|
def media_image_url(self):
|
||||||
"""Image url of current playing media."""
|
"""Image url of current playing media."""
|
||||||
if self._status is None:
|
if (self._status is None or
|
||||||
|
(self.is_grouped and not self.is_master)):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
url = self._status.get('image', None)
|
url = self._status.get('image', None)
|
||||||
if not url:
|
if not url:
|
||||||
return
|
return
|
||||||
if url[0] == '/':
|
if url[0] == '/':
|
||||||
url = "http://{}:{}{}".format(self.host, self._port, url)
|
url = "http://{}:{}{}".format(self.host, self.port, url)
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_position(self):
|
def media_position(self):
|
||||||
"""Position of current playing media in seconds."""
|
"""Position of current playing media in seconds."""
|
||||||
if self._status is None:
|
if (self._status is None or
|
||||||
|
(self.is_grouped and not self.is_master)):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mediastate = self.state
|
mediastate = self.state
|
||||||
@ -510,7 +616,8 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def media_duration(self):
|
def media_duration(self):
|
||||||
"""Duration of current playing media in seconds."""
|
"""Duration of current playing media in seconds."""
|
||||||
if self._status is None:
|
if (self._status is None or
|
||||||
|
(self.is_grouped and not self.is_master)):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
duration = self._status.get('totlen', None)
|
duration = self._status.get('totlen', None)
|
||||||
@ -526,10 +633,10 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def volume_level(self):
|
def volume_level(self):
|
||||||
"""Volume level of the media player (0..1)."""
|
"""Volume level of the media player (0..1)."""
|
||||||
if self._status is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
volume = self._status.get('volume', None)
|
volume = self._status.get('volume', None)
|
||||||
|
if self.is_grouped:
|
||||||
|
volume = self._sync_status.get('@volume', None)
|
||||||
|
|
||||||
if volume is not None:
|
if volume is not None:
|
||||||
return int(volume) / 100
|
return int(volume) / 100
|
||||||
return None
|
return None
|
||||||
@ -537,9 +644,6 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def is_volume_muted(self):
|
def is_volume_muted(self):
|
||||||
"""Boolean if volume is currently muted."""
|
"""Boolean if volume is currently muted."""
|
||||||
if not self._status:
|
|
||||||
return None
|
|
||||||
|
|
||||||
volume = self.volume_level
|
volume = self.volume_level
|
||||||
if not volume:
|
if not volume:
|
||||||
return None
|
return None
|
||||||
@ -558,7 +662,8 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def source_list(self):
|
def source_list(self):
|
||||||
"""List of available input sources."""
|
"""List of available input sources."""
|
||||||
if self._status is None:
|
if (self._status is None or
|
||||||
|
(self.is_grouped and not self.is_master)):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sources = []
|
sources = []
|
||||||
@ -581,7 +686,8 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
"""Name of the current input source."""
|
"""Name of the current input source."""
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
|
|
||||||
if self._status is None:
|
if (self._status is None or
|
||||||
|
(self.is_grouped and not self.is_master)):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
current_service = self._status.get('service', '')
|
current_service = self._status.get('service', '')
|
||||||
@ -649,12 +755,17 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
if self._status is None:
|
if self._status is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_SET | \
|
||||||
|
SUPPORT_VOLUME_MUTE
|
||||||
|
|
||||||
supported = SUPPORT_CLEAR_PLAYLIST
|
supported = SUPPORT_CLEAR_PLAYLIST
|
||||||
|
|
||||||
if self._status.get('indexing', '0') == '0':
|
if self._status.get('indexing', '0') == '0':
|
||||||
supported = supported | SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | \
|
supported = supported | SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | \
|
||||||
SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA | \
|
SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA | \
|
||||||
SUPPORT_STOP | SUPPORT_PLAY | SUPPORT_SELECT_SOURCE
|
SUPPORT_STOP | SUPPORT_PLAY | SUPPORT_SELECT_SOURCE | \
|
||||||
|
SUPPORT_SHUFFLE_SET
|
||||||
|
|
||||||
current_vol = self.volume_level
|
current_vol = self.volume_level
|
||||||
if current_vol is not None and current_vol >= 0:
|
if current_vol is not None and current_vol >= 0:
|
||||||
@ -667,17 +778,87 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
return supported
|
return supported
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def is_master(self):
|
||||||
"""Return the state attributes."""
|
"""Return true if player is a coordinator."""
|
||||||
return {
|
return self._is_master
|
||||||
ATTR_MODEL: self._model,
|
|
||||||
ATTR_MODEL_NAME: self._model_name,
|
@property
|
||||||
ATTR_BRAND: self._brand,
|
def is_grouped(self):
|
||||||
}
|
"""Return true if player is a coordinator."""
|
||||||
|
return self._master is not None or self._is_master
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shuffle(self):
|
||||||
|
"""Return true if shuffle is active."""
|
||||||
|
return True if self._status.get('shuffle', '0') == '1' else False
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_join(self, master):
|
||||||
|
"""Join the player to a group."""
|
||||||
|
master_device = [device for device in self.hass.data[DATA_BLUESOUND]
|
||||||
|
if device.entity_id == master]
|
||||||
|
|
||||||
|
if master_device:
|
||||||
|
_LOGGER.debug("Trying to join player: %s to master: %s",
|
||||||
|
self.host, master_device[0].host)
|
||||||
|
|
||||||
|
yield from master_device[0].async_add_slave(self)
|
||||||
|
else:
|
||||||
|
_LOGGER.error("Master not found %s", master_device)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_unjoin(self):
|
||||||
|
"""Unjoin the player from a group."""
|
||||||
|
if self._master is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.debug("Trying to unjoin player: %s", self.host)
|
||||||
|
yield from self._master.async_remove_slave(self)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_add_slave(self, slave_device):
|
||||||
|
"""Add slave to master."""
|
||||||
|
return self.send_bluesound_command('/AddSlave?slave={}&port={}'
|
||||||
|
.format(slave_device.host,
|
||||||
|
slave_device.port))
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_remove_slave(self, slave_device):
|
||||||
|
"""Remove slave to master."""
|
||||||
|
return self.send_bluesound_command('/RemoveSlave?slave={}&port={}'
|
||||||
|
.format(slave_device.host,
|
||||||
|
slave_device.port))
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_increase_timer(self):
|
||||||
|
"""Increase sleep time on player."""
|
||||||
|
sleep_time = yield from self.send_bluesound_command('/Sleep')
|
||||||
|
if sleep_time is None:
|
||||||
|
_LOGGER.error('Error while increasing sleep time on player: %s',
|
||||||
|
self.host)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return int(sleep_time.get('sleep', '0'))
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_clear_timer(self):
|
||||||
|
"""Clear sleep timer on player."""
|
||||||
|
sleep = 1
|
||||||
|
while sleep > 0:
|
||||||
|
sleep = yield from self.async_increase_timer()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_set_shuffle(self, shuffle):
|
||||||
|
"""Enable or disable shuffle mode."""
|
||||||
|
return self.send_bluesound_command('/Shuffle?state={}'
|
||||||
|
.format('1' if shuffle else '0'))
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_select_source(self, source):
|
def async_select_source(self, source):
|
||||||
"""Select input source."""
|
"""Select input source."""
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return
|
||||||
|
|
||||||
items = [x for x in self._preset_items if x['title'] == source]
|
items = [x for x in self._preset_items if x['title'] == source]
|
||||||
|
|
||||||
if len(items) < 1:
|
if len(items) < 1:
|
||||||
@ -700,11 +881,17 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_clear_playlist(self):
|
def async_clear_playlist(self):
|
||||||
"""Clear players playlist."""
|
"""Clear players playlist."""
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return
|
||||||
|
|
||||||
return self.send_bluesound_command('Clear')
|
return self.send_bluesound_command('Clear')
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_media_next_track(self):
|
def async_media_next_track(self):
|
||||||
"""Send media_next command to media player."""
|
"""Send media_next command to media player."""
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return
|
||||||
|
|
||||||
cmd = 'Skip'
|
cmd = 'Skip'
|
||||||
if self._status and 'actions' in self._status:
|
if self._status and 'actions' in self._status:
|
||||||
for action in self._status['actions']['action']:
|
for action in self._status['actions']['action']:
|
||||||
@ -717,6 +904,9 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_media_previous_track(self):
|
def async_media_previous_track(self):
|
||||||
"""Send media_previous command to media player."""
|
"""Send media_previous command to media player."""
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return
|
||||||
|
|
||||||
cmd = 'Back'
|
cmd = 'Back'
|
||||||
if self._status and 'actions' in self._status:
|
if self._status and 'actions' in self._status:
|
||||||
for action in self._status['actions']['action']:
|
for action in self._status['actions']['action']:
|
||||||
@ -729,23 +919,52 @@ class BluesoundPlayer(MediaPlayerDevice):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_media_play(self):
|
def async_media_play(self):
|
||||||
"""Send media_play command to media player."""
|
"""Send media_play command to media player."""
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return
|
||||||
|
|
||||||
return self.send_bluesound_command('Play')
|
return self.send_bluesound_command('Play')
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_media_pause(self):
|
def async_media_pause(self):
|
||||||
"""Send media_pause command to media player."""
|
"""Send media_pause command to media player."""
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return
|
||||||
|
|
||||||
return self.send_bluesound_command('Pause')
|
return self.send_bluesound_command('Pause')
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_media_stop(self):
|
def async_media_stop(self):
|
||||||
"""Send stop command."""
|
"""Send stop command."""
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return
|
||||||
|
|
||||||
return self.send_bluesound_command('Pause')
|
return self.send_bluesound_command('Pause')
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_media_seek(self, position):
|
def async_media_seek(self, position):
|
||||||
"""Send media_seek command to media player."""
|
"""Send media_seek command to media player."""
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return
|
||||||
|
|
||||||
return self.send_bluesound_command('Play?seek=' + str(float(position)))
|
return self.send_bluesound_command('Play?seek=' + str(float(position)))
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_play_media(self, media_type, media_id, **kwargs):
|
||||||
|
"""
|
||||||
|
Send the play_media command to the media player.
|
||||||
|
|
||||||
|
If ATTR_MEDIA_ENQUEUE is True, add `media_id` to the queue.
|
||||||
|
"""
|
||||||
|
if self.is_grouped and not self.is_master:
|
||||||
|
return
|
||||||
|
|
||||||
|
url = 'Play?url={}'.format(media_id)
|
||||||
|
|
||||||
|
if kwargs.get(ATTR_MEDIA_ENQUEUE):
|
||||||
|
return self.send_bluesound_command(url)
|
||||||
|
|
||||||
|
return self.send_bluesound_command(url)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_volume_up(self):
|
def async_volume_up(self):
|
||||||
"""Volume up the media player."""
|
"""Volume up the media player."""
|
||||||
|
@ -323,7 +323,6 @@ squeezebox_call_method:
|
|||||||
|
|
||||||
yamaha_enable_output:
|
yamaha_enable_output:
|
||||||
description: Enable or disable an output port
|
description: Enable or disable an output port
|
||||||
|
|
||||||
fields:
|
fields:
|
||||||
entity_id:
|
entity_id:
|
||||||
description: Name(s) of entites to enable/disable port on.
|
description: Name(s) of entites to enable/disable port on.
|
||||||
@ -334,3 +333,34 @@ yamaha_enable_output:
|
|||||||
enabled:
|
enabled:
|
||||||
description: Boolean indicating if port should be enabled or not.
|
description: Boolean indicating if port should be enabled or not.
|
||||||
example: true
|
example: true
|
||||||
|
|
||||||
|
bluesound_join:
|
||||||
|
description: Group player together.
|
||||||
|
fields:
|
||||||
|
master:
|
||||||
|
description: Entity ID of the player that should become the master of the group.
|
||||||
|
example: 'media_player.bluesound_livingroom'
|
||||||
|
entity_id:
|
||||||
|
description: Name(s) of entities that will coordinate the grouping. Platform dependent.
|
||||||
|
example: 'media_player.bluesound_livingroom'
|
||||||
|
|
||||||
|
bluesound_unjoin:
|
||||||
|
description: Unjoin the player from a group.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name(s) of entities that will be unjoined from their group. Platform dependent.
|
||||||
|
example: 'media_player.bluesound_livingroom'
|
||||||
|
|
||||||
|
bluesound_set_sleep_timer:
|
||||||
|
description: "Set a Bluesound timer. It will increase timer in steps: 15, 30, 45, 60, 90, 0"
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name(s) of entities that will have a timer set.
|
||||||
|
example: 'media_player.bluesound_livingroom'
|
||||||
|
|
||||||
|
bluesound_clear_sleep_timer:
|
||||||
|
description: Clear a Bluesound timer.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name(s) of entities that will have the timer cleared.
|
||||||
|
example: 'media_player.bluesound_livingroom'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user