Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ omit =
homeassistant/components/smarthab/*
homeassistant/components/sms/*
homeassistant/components/smtp/notify.py
homeassistant/components/snapcast/media_player.py
homeassistant/components/snapcast/*
homeassistant/components/snmp/*
homeassistant/components/sochain/sensor.py
homeassistant/components/socialblade/sensor.py
Expand Down
49 changes: 0 additions & 49 deletions homeassistant/components/snapcast/__init__.py
Original file line number Diff line number Diff line change
@@ -1,50 +1 @@
"""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
17 changes: 17 additions & 0 deletions homeassistant/components/snapcast/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Constants for Snapcast."""

DATA_KEY = "snapcast"

GROUP_PREFIX = "snapcast_group_"
GROUP_SUFFIX = "Snapcast Group"
CLIENT_PREFIX = "snapcast_client_"
CLIENT_SUFFIX = "Snapcast Client"

SERVICE_SNAPSHOT = "snapshot"
SERVICE_RESTORE = "restore"
SERVICE_JOIN = "join"
SERVICE_UNJOIN = "unjoin"
SERVICE_SET_LATENCY = "set_latency"

ATTR_MASTER = "master"
ATTR_LATENCY = "latency"
112 changes: 68 additions & 44 deletions homeassistant/components/snapcast/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
SUPPORT_VOLUME_SET,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_HOST,
CONF_PORT,
STATE_IDLE,
Expand All @@ -22,34 +21,32 @@
STATE_PLAYING,
STATE_UNKNOWN,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers import config_validation as cv, entity_platform

from . import (
from .const import (
ATTR_LATENCY,
ATTR_MASTER,
DOMAIN,
CLIENT_PREFIX,
CLIENT_SUFFIX,
DATA_KEY,
GROUP_PREFIX,
GROUP_SUFFIX,
SERVICE_JOIN,
SERVICE_RESTORE,
SERVICE_SET_LATENCY,
SERVICE_SNAPSHOT,
SERVICE_UNJOIN,
)

_LOGGER = logging.getLogger(__name__)

DATA_KEY = "snapcast"

SUPPORT_SNAPCAST_CLIENT = (
SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | SUPPORT_SELECT_SOURCE
)
SUPPORT_SNAPCAST_GROUP = (
SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | SUPPORT_SELECT_SOURCE
)

GROUP_PREFIX = "snapcast_group_"
GROUP_SUFFIX = "Snapcast Group"
CLIENT_PREFIX = "snapcast_client_"
CLIENT_SUFFIX = "Snapcast Client"

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
Comment thread
MartinHjelmare marked this conversation as resolved.
{vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_PORT): cv.port}
)
Expand All @@ -61,33 +58,18 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
host = config.get(CONF_HOST)
port = config.get(CONF_PORT, CONTROL_PORT)

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_SNAPSHOT:
device.snapshot()
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()

service_event.set()

async_dispatcher_connect(hass, DOMAIN, async_service_handle)
platform = entity_platform.current_platform.get()
platform.async_register_entity_service(SERVICE_SNAPSHOT, {}, "snapshot")
platform.async_register_entity_service(SERVICE_RESTORE, {}, "async_restore")
platform.async_register_entity_service(
SERVICE_JOIN, {vol.Required(ATTR_MASTER): cv.entity_id}, handle_async_join
)
platform.async_register_entity_service(SERVICE_UNJOIN, {}, handle_async_unjoin)
platform.async_register_entity_service(
SERVICE_SET_LATENCY,
{vol.Required(ATTR_LATENCY): cv.positive_int},
handle_set_latency,
)

try:
server = await snapcast.control.create_server(
Expand All @@ -107,6 +89,27 @@ async def async_service_handle(service_event, service, data):
async_add_entities(devices)


async def handle_async_join(entity, service_call):
"""Handle the entity service join."""
if not isinstance(entity, SnapcastClientDevice):
raise ValueError("Entity is not a client. Can only join clients.")
await entity.async_join(service_call.data[ATTR_MASTER])


async def handle_async_unjoin(entity, service_call):
"""Handle the entity service unjoin."""
if not isinstance(entity, SnapcastClientDevice):
raise ValueError("Entity is not a client. Can only unjoin clients.")
await entity.async_unjoin()


async def handle_set_latency(entity, service_call):
"""Handle the entity service set_latency."""
if not isinstance(entity, SnapcastClientDevice):
raise ValueError("Latency can only be set for a Snapcast client.")
await entity.async_set_latency(service_call.data[ATTR_LATENCY])


class SnapcastGroupDevice(MediaPlayerDevice):
"""Representation of a Snapcast group device."""

Expand Down Expand Up @@ -260,14 +263,23 @@ def state(self):
@property
def device_state_attributes(self):
"""Return the state attributes."""
state_attrs = {}
if self.latency is not None:
state_attrs["latency"] = self.latency
Comment thread
MartinHjelmare marked this conversation as resolved.
name = f"{self._client.friendly_name} {CLIENT_SUFFIX}"
return {"friendly_name": name}
state_attrs["friendly_name"] = name
return state_attrs

@property
def should_poll(self):
"""Do not poll for state."""
return False

@property
def latency(self):
"""Latency for Client."""
return self._client.latency

async def async_select_source(self, source):
"""Set input source."""
streams = self._client.group.streams_by_name()
Expand All @@ -287,12 +299,19 @@ async def async_set_volume_level(self, volume):

async def async_join(self, master):
"""Join the group of the master player."""
master_group = [

master_entity = next(
entity for entity in self.hass.data[DATA_KEY] if entity.entity_id == master
)
if not isinstance(master_entity, SnapcastClientDevice):
raise ValueError("Master is not a client device. Can only join clients.")

master_group = next(
group
for group in self._client.groups_available()
if master.identifier in group.clients
]
await master_group[0].add_client(self._client.identifier)
if master_entity.identifier in group.clients
)
await master_group.add_client(self._client.identifier)
self.async_write_ha_state()

async def async_unjoin(self):
Expand All @@ -307,3 +326,8 @@ def snapshot(self):
async def async_restore(self):
"""Restore the client state."""
await self._client.restore()

async def async_set_latency(self, latency):
"""Set the latency of the client."""
await self._client.set_latency(latency)
self.async_write_ha_state()
9 changes: 9 additions & 0 deletions homeassistant/components/snapcast/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,12 @@ restore:
entity_id:
description: Name(s) of entities that will be restored. Platform dependent.
example: "media_player.living_room"

set_latency:
description: Set client set_latency
fields:
entity_id:
description: Name of entities that will have adjusted latency
latency:
description: Latency in master
example: 14