diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index c641eda1a49..d7f636d070a 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -171,20 +171,6 @@ shuffle_set: description: True/false for enabling/disabling shuffle. example: true -snapcast_snapshot: - description: Take a snapshot of the media player. - fields: - entity_id: - description: Name(s) of entities that will be snapshotted. Platform dependent. - example: 'media_player.living_room' - -snapcast_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' - channels_seek_forward: description: Seek forward by a set number of seconds. fields: diff --git a/homeassistant/components/snapcast/__init__.py b/homeassistant/components/snapcast/__init__.py index b5279fa3ce0..6d39be4f663 100644 --- a/homeassistant/components/snapcast/__init__.py +++ b/homeassistant/components/snapcast/__init__.py @@ -1 +1,48 @@ """The snapcast component.""" + +import asyncio +import voluptuous as vol + +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send + +DOMAIN = 'snapcast' + +SERVICE_SNAPSHOT = 'snapshot' +SERVICE_RESTORE = 'restore' +SERVICE_JOIN = 'join' +SERVICE_UNJOIN = 'unjoin' + +ATTR_MASTER = 'master' + +SERVICE_SCHEMA = vol.Schema({ + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, +}) + +JOIN_SERVICE_SCHEMA = SERVICE_SCHEMA.extend({ + vol.Required(ATTR_MASTER): cv.entity_id, +}) + + +async def async_setup(hass, config): + """Handle service configuration.""" + service_event = asyncio.Event() + + async def service_handle(service): + """Dispatch a service call.""" + service_event.clear() + async_dispatcher_send(hass, DOMAIN, service_event, service.service, + service.data) + await service_event.wait() + + hass.services.async_register( + DOMAIN, SERVICE_SNAPSHOT, service_handle, schema=SERVICE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_RESTORE, service_handle, schema=SERVICE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_JOIN, service_handle, schema=JOIN_SERVICE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_UNJOIN, service_handle, schema=SERVICE_SCHEMA) + + return True diff --git a/homeassistant/components/snapcast/manifest.json b/homeassistant/components/snapcast/manifest.json index 70c9db7dada..0bb36c1317a 100644 --- a/homeassistant/components/snapcast/manifest.json +++ b/homeassistant/components/snapcast/manifest.json @@ -3,7 +3,7 @@ "name": "Snapcast", "documentation": "https://www.home-assistant.io/components/snapcast", "requirements": [ - "snapcast==2.0.9" + "snapcast==2.0.10" ], "dependencies": [], "codeowners": [] diff --git a/homeassistant/components/snapcast/media_player.py b/homeassistant/components/snapcast/media_player.py index 12ecabd68ea..7924fe960fd 100644 --- a/homeassistant/components/snapcast/media_player.py +++ b/homeassistant/components/snapcast/media_player.py @@ -7,21 +7,24 @@ import voluptuous as vol from homeassistant.components.media_player import ( MediaPlayerDevice, PLATFORM_SCHEMA) from homeassistant.components.media_player.const import ( - DOMAIN, SUPPORT_SELECT_SOURCE, SUPPORT_VOLUME_MUTE, + SUPPORT_SELECT_SOURCE, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_PORT, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PLAYING, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ( + DOMAIN, SERVICE_SNAPSHOT, SERVICE_RESTORE, SERVICE_JOIN, + SERVICE_UNJOIN, ATTR_MASTER) _LOGGER = logging.getLogger(__name__) DATA_KEY = 'snapcast' -SERVICE_SNAPSHOT = 'snapcast_snapshot' -SERVICE_RESTORE = 'snapcast_restore' - -SUPPORT_SNAPCAST_CLIENT = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET +SUPPORT_SNAPCAST_CLIENT = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET |\ + SUPPORT_SELECT_SOURCE SUPPORT_SNAPCAST_GROUP = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET |\ SUPPORT_SELECT_SOURCE @@ -30,10 +33,6 @@ GROUP_SUFFIX = 'Snapcast Group' CLIENT_PREFIX = 'snapcast_client_' CLIENT_SUFFIX = 'Snapcast Client' -SERVICE_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, -}) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_PORT): cv.port, @@ -48,21 +47,29 @@ async def async_setup_platform(hass, config, async_add_entities, host = config.get(CONF_HOST) port = config.get(CONF_PORT, CONTROL_PORT) - async def _handle_service(service): - """Handle services.""" - entity_ids = service.data.get(ATTR_ENTITY_ID) + async def async_service_handle(service_event, service, data): + """Handle dispatched services.""" + entity_ids = data.get(ATTR_ENTITY_ID) devices = [device for device in hass.data[DATA_KEY] if device.entity_id in entity_ids] for device in devices: - if service.service == SERVICE_SNAPSHOT: + if service == SERVICE_SNAPSHOT: device.snapshot() - elif service.service == SERVICE_RESTORE: + elif service == SERVICE_RESTORE: await device.async_restore() + elif service == SERVICE_JOIN: + if isinstance(device, SnapcastClientDevice): + master = [e for e in hass.data[DATA_KEY] + if e.entity_id == data[ATTR_MASTER]] + if isinstance(master[0], SnapcastClientDevice): + await device.async_join(master[0]) + elif service == SERVICE_UNJOIN: + if isinstance(device, SnapcastClientDevice): + await device.async_unjoin() - hass.services.async_register( - DOMAIN, SERVICE_SNAPSHOT, _handle_service, schema=SERVICE_SCHEMA) - hass.services.async_register( - DOMAIN, SERVICE_RESTORE, _handle_service, schema=SERVICE_SCHEMA) + service_event.set() + + async_dispatcher_connect(hass, DOMAIN, async_service_handle) try: server = await snapcast.control.create_server( @@ -194,11 +201,21 @@ class SnapcastClientDevice(MediaPlayerDevice): """ return self._uid + @property + def identifier(self): + """Return the snapcast identifier.""" + return self._client.identifier + @property def name(self): """Return the name of the device.""" return '{}{}'.format(CLIENT_PREFIX, self._client.identifier) + @property + def source(self): + """Return the current input source.""" + return self._client.group.stream + @property def volume_level(self): """Return the volume level.""" @@ -214,6 +231,11 @@ class SnapcastClientDevice(MediaPlayerDevice): """Flag media player features that are supported.""" return SUPPORT_SNAPCAST_CLIENT + @property + def source_list(self): + """List of available input sources.""" + return list(self._client.group.streams_by_name().keys()) + @property def state(self): """Return the state of the player.""" @@ -234,6 +256,13 @@ class SnapcastClientDevice(MediaPlayerDevice): """Do not poll for state.""" return False + async def async_select_source(self, source): + """Set input source.""" + streams = self._client.group.streams_by_name() + if source in streams: + await self._client.group.set_stream(streams[source].identifier) + self.async_schedule_update_ha_state() + async def async_mute_volume(self, mute): """Send the mute command.""" await self._client.set_muted(mute) @@ -244,6 +273,18 @@ class SnapcastClientDevice(MediaPlayerDevice): await self._client.set_volume(round(volume * 100)) self.async_schedule_update_ha_state() + async def async_join(self, master): + """Join the group of the master player.""" + master_group = [group for group in self._client.groups_available() + if master.identifier in group.clients] + await master_group[0].add_client(self._client.identifier) + self.async_schedule_update_ha_state() + + async def async_unjoin(self): + """Unjoin the group the player is currently in.""" + await self._client.group.remove_client(self._client.identifier) + self.async_schedule_update_ha_state() + def snapshot(self): """Snapshot the client state.""" self._client.snapshot() diff --git a/homeassistant/components/snapcast/services.yaml b/homeassistant/components/snapcast/services.yaml index e69de29bb2d..3e5dadbf5c1 100644 --- a/homeassistant/components/snapcast/services.yaml +++ b/homeassistant/components/snapcast/services.yaml @@ -0,0 +1,30 @@ +join: + description: Group players together. + fields: + master: + description: Entity ID of the player to synchronize to. + example: 'media_player.living_room' + entity_id: + description: Entity ID of the players to join to the "master". + example: 'media_player.bedroom' + +unjoin: + description: Unjoin the player from a group. + fields: + entity_id: + description: Entity ID of the player to unjoin. + example: 'media_player.living_room' + +snapshot: + description: Take a snapshot of the media player. + fields: + entity_id: + description: Name(s) of entities that will be snapshotted. Platform dependent. + example: 'media_player.living_room' + +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' diff --git a/requirements_all.txt b/requirements_all.txt index ff42fdccef1..2546dca6c5c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1714,7 +1714,7 @@ smarthab==0.20 smhi-pkg==1.0.10 # homeassistant.components.snapcast -snapcast==2.0.9 +snapcast==2.0.10 # homeassistant.components.socialblade socialbladeclient==0.2