diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index c9da38d3657..c641eda1a49 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -185,73 +185,6 @@ snapcast_restore: description: Name(s) of entities that will be restored. Platform dependent. example: 'media_player.living_room' -sonos_join: - description: Group player together. - fields: - master: - description: Entity ID of the player that should become the coordinator of the group. - example: 'media_player.living_room_sonos' - entity_id: - description: Name(s) of entities that will coordinate the grouping. Platform dependent. - example: 'media_player.living_room_sonos' - -sonos_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.living_room_sonos' - -sonos_snapshot: - description: Take a snapshot of the media player. - fields: - entity_id: - description: Name(s) of entities that will be snapshot. Platform dependent. - example: 'media_player.living_room_sonos' - with_group: - description: True (default) or False. Snapshot with all group attributes. - example: 'true' - -sonos_restore: - description: Restore a snapshot of the media player. - fields: - entity_id: - description: Name(s) of entities that will be restored. Platform dependent. - example: 'media_player.living_room_sonos' - with_group: - description: True (default) or False. Restore with all group attributes. - example: 'true' - -sonos_set_sleep_timer: - description: Set a Sonos timer. - fields: - entity_id: - description: Name(s) of entities that will have a timer set. - example: 'media_player.living_room_sonos' - sleep_time: - description: Number of seconds to set the timer. - example: '900' - -sonos_clear_sleep_timer: - description: Clear a Sonos timer. - fields: - entity_id: - description: Name(s) of entities that will have the timer cleared. - example: 'media_player.living_room_sonos' - -sonos_set_option: - description: Set Sonos sound options. - fields: - entity_id: - description: Name(s) of entities that will have options set. - example: 'media_player.living_room_sonos' - night_sound: - description: Enable Night Sound mode - example: 'true' - speech_enhance: - description: Enable Speech Enhancement mode - example: 'true' - channels_seek_forward: description: Seek forward by a set number of seconds. fields: diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index d68e87914ec..d0e467f6964 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -3,8 +3,9 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components.media_player import DOMAIN as MP_DOMAIN -from homeassistant.const import CONF_HOSTS +from homeassistant.const import CONF_HOSTS, ATTR_ENTITY_ID, ATTR_TIME from homeassistant.helpers import config_entry_flow, config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send DOMAIN = 'sonos' @@ -21,6 +22,64 @@ CONFIG_SCHEMA = vol.Schema({ }), }, extra=vol.ALLOW_EXTRA) +SERVICE_JOIN = 'join' +SERVICE_UNJOIN = 'unjoin' +SERVICE_SNAPSHOT = 'snapshot' +SERVICE_RESTORE = 'restore' +SERVICE_SET_TIMER = 'set_sleep_timer' +SERVICE_CLEAR_TIMER = 'clear_sleep_timer' +SERVICE_UPDATE_ALARM = 'update_alarm' +SERVICE_SET_OPTION = 'set_option' + +ATTR_SLEEP_TIME = 'sleep_time' +ATTR_ALARM_ID = 'alarm_id' +ATTR_VOLUME = 'volume' +ATTR_ENABLED = 'enabled' +ATTR_INCLUDE_LINKED_ZONES = 'include_linked_zones' +ATTR_MASTER = 'master' +ATTR_WITH_GROUP = 'with_group' +ATTR_NIGHT_SOUND = 'night_sound' +ATTR_SPEECH_ENHANCE = 'speech_enhance' + +SONOS_JOIN_SCHEMA = vol.Schema({ + vol.Required(ATTR_MASTER): cv.entity_id, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, +}) + +SONOS_UNJOIN_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, +}) + +SONOS_STATES_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, + vol.Optional(ATTR_WITH_GROUP, default=True): cv.boolean, +}) + +SONOS_SET_TIMER_SCHEMA = vol.Schema({ + vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids, + vol.Required(ATTR_SLEEP_TIME): + vol.All(vol.Coerce(int), vol.Range(min=0, max=86399)) +}) + +SONOS_CLEAR_TIMER_SCHEMA = vol.Schema({ + vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids, +}) + +SONOS_UPDATE_ALARM_SCHEMA = vol.Schema({ + vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids, + vol.Required(ATTR_ALARM_ID): cv.positive_int, + vol.Optional(ATTR_TIME): cv.time, + vol.Optional(ATTR_VOLUME): cv.small_float, + vol.Optional(ATTR_ENABLED): cv.boolean, + vol.Optional(ATTR_INCLUDE_LINKED_ZONES): cv.boolean, +}) + +SONOS_SET_OPTION_SCHEMA = vol.Schema({ + vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids, + vol.Optional(ATTR_NIGHT_SOUND): cv.boolean, + vol.Optional(ATTR_SPEECH_ENHANCE): cv.boolean, +}) + async def async_setup(hass, config): """Set up the Sonos component.""" @@ -32,6 +91,42 @@ async def async_setup(hass, config): hass.async_create_task(hass.config_entries.flow.async_init( DOMAIN, context={'source': config_entries.SOURCE_IMPORT})) + async def service_handle(service): + """Dispatch a service call.""" + async_dispatcher_send(hass, DOMAIN, service.service, service.data) + + hass.services.async_register( + DOMAIN, SERVICE_JOIN, service_handle, + schema=SONOS_JOIN_SCHEMA) + + hass.services.async_register( + DOMAIN, SERVICE_UNJOIN, service_handle, + schema=SONOS_UNJOIN_SCHEMA) + + hass.services.async_register( + DOMAIN, SERVICE_SNAPSHOT, service_handle, + schema=SONOS_STATES_SCHEMA) + + hass.services.async_register( + DOMAIN, SERVICE_RESTORE, service_handle, + schema=SONOS_STATES_SCHEMA) + + hass.services.async_register( + DOMAIN, SERVICE_SET_TIMER, service_handle, + schema=SONOS_SET_TIMER_SCHEMA) + + hass.services.async_register( + DOMAIN, SERVICE_CLEAR_TIMER, service_handle, + schema=SONOS_CLEAR_TIMER_SCHEMA) + + hass.services.async_register( + DOMAIN, SERVICE_UPDATE_ALARM, service_handle, + schema=SONOS_UPDATE_ALARM_SCHEMA) + + hass.services.async_register( + DOMAIN, SERVICE_SET_OPTION, service_handle, + schema=SONOS_SET_OPTION_SCHEMA) + return True diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 4aea88c6657..524353f8c54 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -8,29 +8,29 @@ import time import urllib import async_timeout -import voluptuous as vol - import pysonos import pysonos.snapshot from pysonos.exceptions import SoCoUPnPException, SoCoException from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( - ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST, + ATTR_MEDIA_ENQUEUE, MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_TIME, STATE_IDLE, STATE_OFF, STATE_PAUSED, - STATE_PLAYING) -import homeassistant.helpers.config_validation as cv + ENTITY_MATCH_ALL, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.dt import utcnow from . import ( CONF_ADVERTISE_ADDR, CONF_HOSTS, CONF_INTERFACE_ADDR, - DOMAIN as SONOS_DOMAIN) - -DEPENDENCIES = ('sonos',) + DOMAIN as SONOS_DOMAIN, + ATTR_ALARM_ID, ATTR_ENABLED, ATTR_INCLUDE_LINKED_ZONES, ATTR_MASTER, + ATTR_NIGHT_SOUND, ATTR_SLEEP_TIME, ATTR_SPEECH_ENHANCE, ATTR_TIME, + ATTR_VOLUME, ATTR_WITH_GROUP, + SERVICE_CLEAR_TIMER, SERVICE_JOIN, SERVICE_RESTORE, SERVICE_SET_OPTION, + SERVICE_SET_TIMER, SERVICE_SNAPSHOT, SERVICE_UNJOIN, SERVICE_UPDATE_ALARM) _LOGGER = logging.getLogger(__name__) @@ -47,65 +47,15 @@ SUPPORT_SONOS = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\ SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK |\ SUPPORT_PLAY_MEDIA | SUPPORT_SHUFFLE_SET | SUPPORT_CLEAR_PLAYLIST -SERVICE_JOIN = 'sonos_join' -SERVICE_UNJOIN = 'sonos_unjoin' -SERVICE_SNAPSHOT = 'sonos_snapshot' -SERVICE_RESTORE = 'sonos_restore' -SERVICE_SET_TIMER = 'sonos_set_sleep_timer' -SERVICE_CLEAR_TIMER = 'sonos_clear_sleep_timer' -SERVICE_UPDATE_ALARM = 'sonos_update_alarm' -SERVICE_SET_OPTION = 'sonos_set_option' - DATA_SONOS = 'sonos_media_player' SOURCE_LINEIN = 'Line-in' SOURCE_TV = 'TV' -# Service call validation schemas -ATTR_SLEEP_TIME = 'sleep_time' -ATTR_ALARM_ID = 'alarm_id' -ATTR_VOLUME = 'volume' -ATTR_ENABLED = 'enabled' -ATTR_INCLUDE_LINKED_ZONES = 'include_linked_zones' -ATTR_MASTER = 'master' -ATTR_WITH_GROUP = 'with_group' -ATTR_NIGHT_SOUND = 'night_sound' -ATTR_SPEECH_ENHANCE = 'speech_enhance' - ATTR_SONOS_GROUP = 'sonos_group' UPNP_ERRORS_TO_IGNORE = ['701', '711', '712'] -SONOS_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) - -SONOS_JOIN_SCHEMA = SONOS_SCHEMA.extend({ - vol.Required(ATTR_MASTER): cv.entity_id, -}) - -SONOS_STATES_SCHEMA = SONOS_SCHEMA.extend({ - vol.Optional(ATTR_WITH_GROUP, default=True): cv.boolean, -}) - -SONOS_SET_TIMER_SCHEMA = SONOS_SCHEMA.extend({ - vol.Required(ATTR_SLEEP_TIME): - vol.All(vol.Coerce(int), vol.Range(min=0, max=86399)) -}) - -SONOS_UPDATE_ALARM_SCHEMA = SONOS_SCHEMA.extend({ - vol.Required(ATTR_ALARM_ID): cv.positive_int, - vol.Optional(ATTR_TIME): cv.time, - vol.Optional(ATTR_VOLUME): cv.small_float, - vol.Optional(ATTR_ENABLED): cv.boolean, - vol.Optional(ATTR_INCLUDE_LINKED_ZONES): cv.boolean, -}) - -SONOS_SET_OPTION_SCHEMA = SONOS_SCHEMA.extend({ - vol.Optional(ATTR_NIGHT_SOUND): cv.boolean, - vol.Optional(ATTR_SPEECH_ENHANCE): cv.boolean, -}) - class SonosData: """Storage class for platform global data.""" @@ -172,77 +122,40 @@ async def async_setup_entry(hass, config_entry, async_add_entities): hass.async_add_executor_job(_discovery) - def _service_to_entities(service): - """Extract and return entities from service call.""" - entity_ids = service.data.get('entity_id') - + async def async_service_handle(service, data): + """Handle dispatched services.""" + entity_ids = data.get('entity_id') entities = hass.data[DATA_SONOS].entities - if entity_ids: + if entity_ids and entity_ids != ENTITY_MATCH_ALL: entities = [e for e in entities if e.entity_id in entity_ids] - return entities - - async def async_service_handle(service): - """Handle async services.""" - entities = _service_to_entities(service) - - if service.service == SERVICE_JOIN: + if service == SERVICE_JOIN: master = [e for e in hass.data[DATA_SONOS].entities - if e.entity_id == service.data[ATTR_MASTER]] + if e.entity_id == data[ATTR_MASTER]] if master: await SonosEntity.join_multi(hass, master[0], entities) - elif service.service == SERVICE_UNJOIN: + elif service == SERVICE_UNJOIN: await SonosEntity.unjoin_multi(hass, entities) - elif service.service == SERVICE_SNAPSHOT: + elif service == SERVICE_SNAPSHOT: await SonosEntity.snapshot_multi( - hass, entities, service.data[ATTR_WITH_GROUP]) - elif service.service == SERVICE_RESTORE: + hass, entities, data[ATTR_WITH_GROUP]) + elif service == SERVICE_RESTORE: await SonosEntity.restore_multi( - hass, entities, service.data[ATTR_WITH_GROUP]) + hass, entities, data[ATTR_WITH_GROUP]) + else: + for entity in entities: + if service == SERVICE_SET_TIMER: + call = entity.set_sleep_timer + elif service == SERVICE_CLEAR_TIMER: + call = entity.clear_sleep_timer + elif service == SERVICE_UPDATE_ALARM: + call = entity.set_alarm + elif service == SERVICE_SET_OPTION: + call = entity.set_option - hass.services.async_register( - DOMAIN, SERVICE_JOIN, async_service_handle, - schema=SONOS_JOIN_SCHEMA) + hass.async_add_executor_job(call, data) - hass.services.async_register( - DOMAIN, SERVICE_UNJOIN, async_service_handle, - schema=SONOS_SCHEMA) - - hass.services.async_register( - DOMAIN, SERVICE_SNAPSHOT, async_service_handle, - schema=SONOS_STATES_SCHEMA) - - hass.services.async_register( - DOMAIN, SERVICE_RESTORE, async_service_handle, - schema=SONOS_STATES_SCHEMA) - - def service_handle(service): - """Handle sync services.""" - for entity in _service_to_entities(service): - if service.service == SERVICE_SET_TIMER: - entity.set_sleep_timer(service.data[ATTR_SLEEP_TIME]) - elif service.service == SERVICE_CLEAR_TIMER: - entity.clear_sleep_timer() - elif service.service == SERVICE_UPDATE_ALARM: - entity.set_alarm(**service.data) - elif service.service == SERVICE_SET_OPTION: - entity.set_option(**service.data) - - hass.services.async_register( - DOMAIN, SERVICE_SET_TIMER, service_handle, - schema=SONOS_SET_TIMER_SCHEMA) - - hass.services.async_register( - DOMAIN, SERVICE_CLEAR_TIMER, service_handle, - schema=SONOS_SCHEMA) - - hass.services.async_register( - DOMAIN, SERVICE_UPDATE_ALARM, service_handle, - schema=SONOS_UPDATE_ALARM_SCHEMA) - - hass.services.async_register( - DOMAIN, SERVICE_SET_OPTION, service_handle, - schema=SONOS_SET_OPTION_SCHEMA) + async_dispatcher_connect(hass, SONOS_DOMAIN, async_service_handle) class _ProcessSonosEventQueue: @@ -1126,19 +1039,19 @@ class SonosEntity(MediaPlayerDevice): @soco_error() @soco_coordinator - def set_sleep_timer(self, sleep_time): + def set_sleep_timer(self, data): """Set the timer on the player.""" - self.soco.set_sleep_timer(sleep_time) + self.soco.set_sleep_timer(data[ATTR_SLEEP_TIME]) @soco_error() @soco_coordinator - def clear_sleep_timer(self): + def clear_sleep_timer(self, data): """Clear the timer on the player.""" self.soco.set_sleep_timer(None) @soco_error() @soco_coordinator - def set_alarm(self, **data): + def set_alarm(self, data): """Set the alarm clock on the player.""" from pysonos import alarms alarm = None @@ -1161,7 +1074,7 @@ class SonosEntity(MediaPlayerDevice): alarm.save() @soco_error() - def set_option(self, **data): + def set_option(self, data): """Modify playback options.""" if ATTR_NIGHT_SOUND in data and self._night_sound is not None: self.soco.night_mode = data[ATTR_NIGHT_SOUND] diff --git a/homeassistant/components/sonos/services.yaml b/homeassistant/components/sonos/services.yaml index e69de29bb2d..98f53ff8d37 100644 --- a/homeassistant/components/sonos/services.yaml +++ b/homeassistant/components/sonos/services.yaml @@ -0,0 +1,67 @@ +join: + description: Group player together. + fields: + master: + description: Entity ID of the player that should become the coordinator of the group. + example: 'media_player.living_room_sonos' + entity_id: + description: Name(s) of entities that will join the master. + example: 'media_player.living_room_sonos' + +unjoin: + description: Unjoin the player from a group. + fields: + entity_id: + description: Name(s) of entities that will be unjoined from their group. + example: 'media_player.living_room_sonos' + +snapshot: + description: Take a snapshot of the media player. + fields: + entity_id: + description: Name(s) of entities that will be snapshot. + example: 'media_player.living_room_sonos' + with_group: + description: True (default) or False. Also snapshot the group layout. + example: 'true' + +restore: + description: Restore a snapshot of the media player. + fields: + entity_id: + description: Name(s) of entities that will be restored. + example: 'media_player.living_room_sonos' + with_group: + description: True (default) or False. Also restore the group layout. + example: 'true' + +set_sleep_timer: + description: Set a Sonos timer. + fields: + entity_id: + description: Name(s) of entities that will have a timer set. + example: 'media_player.living_room_sonos' + sleep_time: + description: Number of seconds to set the timer. + example: '900' + +clear_sleep_timer: + description: Clear a Sonos timer. + fields: + entity_id: + description: Name(s) of entities that will have the timer cleared. + example: 'media_player.living_room_sonos' + +set_option: + description: Set Sonos sound options. + fields: + entity_id: + description: Name(s) of entities that will have options set. + example: 'media_player.living_room_sonos' + night_sound: + description: Enable Night Sound mode + example: 'true' + speech_enhance: + description: Enable Speech Enhancement mode + example: 'true' +