mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Multiroom support for snapcast (#24061)
* Multiroom support for snapcast * Moving services to the snapcast domain * Passing asyncio event via dispatcher instead of hass.data * Fixing lint
This commit is contained in:
parent
290d32267e
commit
caa7a3a3d6
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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": []
|
||||
|
@ -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()
|
||||
|
@ -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'
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user