Skip to content

Commit dbf383f

Browse files
authored
Squeezebox add query and sync (#31748)
* Add query and sync * Update description of call_query * Remove backup files accidentally committed to repo * Update after pysqueezebox refactor * Use entity service helper * Implement suggested changes * Fix linter error in services.yaml * Fix long lines in services.yaml
1 parent 7f2c6b4 commit dbf383f

File tree

4 files changed

+136
-55
lines changed

4 files changed

+136
-55
lines changed

homeassistant/components/squeezebox/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
"name": "Logitech Squeezebox",
44
"documentation": "https://www.home-assistant.io/integrations/squeezebox",
55
"codeowners": ["@rajlaud"],
6-
"requirements": ["pysqueezebox==0.1.2"]
6+
"requirements": ["pysqueezebox==0.1.4"]
77
}

homeassistant/components/squeezebox/media_player.py

Lines changed: 103 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""Support for interfacing to the Logitech SqueezeBox API."""
2-
import asyncio
32
import logging
43
import socket
54

@@ -25,19 +24,26 @@
2524
)
2625
from homeassistant.const import (
2726
ATTR_COMMAND,
28-
ATTR_ENTITY_ID,
2927
CONF_HOST,
3028
CONF_PASSWORD,
3129
CONF_PORT,
3230
CONF_USERNAME,
3331
STATE_OFF,
3432
)
3533
from homeassistant.exceptions import PlatformNotReady
34+
from homeassistant.helpers import config_validation as cv, entity_platform
3635
from homeassistant.helpers.aiohttp_client import async_get_clientsession
37-
import homeassistant.helpers.config_validation as cv
3836
from homeassistant.util.dt import utcnow
3937

40-
from .const import DOMAIN, SERVICE_CALL_METHOD, SQUEEZEBOX_MODE
38+
from .const import SQUEEZEBOX_MODE
39+
40+
SERVICE_CALL_METHOD = "call_method"
41+
SERVICE_CALL_QUERY = "call_query"
42+
SERVICE_SYNC = "sync"
43+
SERVICE_UNSYNC = "unsync"
44+
45+
ATTR_QUERY_RESULT = "query_result"
46+
ATTR_SYNC_GROUP = "sync_group"
4147

4248
_LOGGER = logging.getLogger(__name__)
4349

@@ -59,8 +65,6 @@
5965
| SUPPORT_CLEAR_PLAYLIST
6066
)
6167

62-
MEDIA_PLAYER_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.comp_entity_ids})
63-
6468
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
6569
{
6670
vol.Required(CONF_HOST): cv.string,
@@ -76,21 +80,12 @@
7680

7781
ATTR_PARAMETERS = "parameters"
7882

79-
SQUEEZEBOX_CALL_METHOD_SCHEMA = MEDIA_PLAYER_SCHEMA.extend(
80-
{
81-
vol.Required(ATTR_COMMAND): cv.string,
82-
vol.Optional(ATTR_PARAMETERS): vol.All(
83-
cv.ensure_list, vol.Length(min=1), [cv.string]
84-
),
85-
}
86-
)
83+
ATTR_OTHER_PLAYER = "other_player"
8784

88-
SERVICE_TO_METHOD = {
89-
SERVICE_CALL_METHOD: {
90-
"method": "async_call_method",
91-
"schema": SQUEEZEBOX_CALL_METHOD_SCHEMA,
92-
}
93-
}
85+
ATTR_TO_PROPERTY = [
86+
ATTR_QUERY_RESULT,
87+
ATTR_SYNC_GROUP,
88+
]
9489

9590

9691
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
@@ -141,38 +136,35 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
141136
hass.data[DATA_SQUEEZEBOX].extend(media_players)
142137
async_add_entities(media_players)
143138

144-
async def async_service_handler(service):
145-
"""Map services to methods on MediaPlayerEntity."""
146-
method = SERVICE_TO_METHOD.get(service.service)
147-
if not method:
148-
return
149-
150-
params = {
151-
key: value for key, value in service.data.items() if key != "entity_id"
152-
}
153-
entity_ids = service.data.get("entity_id")
154-
if entity_ids:
155-
target_players = [
156-
player
157-
for player in hass.data[DATA_SQUEEZEBOX]
158-
if player.entity_id in entity_ids
159-
]
160-
else:
161-
target_players = hass.data[DATA_SQUEEZEBOX]
162-
163-
update_tasks = []
164-
for player in target_players:
165-
await getattr(player, method["method"])(**params)
166-
update_tasks.append(player.async_update_ha_state(True))
167-
168-
if update_tasks:
169-
await asyncio.wait(update_tasks)
170-
171-
for service in SERVICE_TO_METHOD:
172-
schema = SERVICE_TO_METHOD[service]["schema"]
173-
hass.services.async_register(
174-
DOMAIN, service, async_service_handler, schema=schema
175-
)
139+
platform = entity_platform.current_platform.get()
140+
141+
platform.async_register_entity_service(
142+
SERVICE_CALL_METHOD,
143+
{
144+
vol.Required(ATTR_COMMAND): cv.string,
145+
vol.Optional(ATTR_PARAMETERS): vol.All(
146+
cv.ensure_list, vol.Length(min=1), [cv.string]
147+
),
148+
},
149+
"async_call_method",
150+
)
151+
152+
platform.async_register_entity_service(
153+
SERVICE_CALL_QUERY,
154+
{
155+
vol.Required(ATTR_COMMAND): cv.string,
156+
vol.Optional(ATTR_PARAMETERS): vol.All(
157+
cv.ensure_list, vol.Length(min=1), [cv.string]
158+
),
159+
},
160+
"async_call_query",
161+
)
162+
163+
platform.async_register_entity_service(
164+
SERVICE_SYNC, {vol.Required(ATTR_OTHER_PLAYER): cv.string}, "async_sync",
165+
)
166+
167+
platform.async_register_entity_service(SERVICE_UNSYNC, None, "async_unsync")
176168

177169
return True
178170

@@ -188,6 +180,18 @@ def __init__(self, player):
188180
"""Initialize the SqueezeBox device."""
189181
self._player = player
190182
self._last_update = None
183+
self._query_result = {}
184+
185+
@property
186+
def device_state_attributes(self):
187+
"""Return device-specific attributes."""
188+
squeezebox_attr = {
189+
attr: getattr(self, attr)
190+
for attr in ATTR_TO_PROPERTY
191+
if getattr(self, attr) is not None
192+
}
193+
194+
return squeezebox_attr
191195

192196
@property
193197
def name(self):
@@ -284,6 +288,21 @@ def supported_features(self):
284288
"""Flag media player features that are supported."""
285289
return SUPPORT_SQUEEZEBOX
286290

291+
@property
292+
def sync_group(self):
293+
"""List players we are synced with."""
294+
player_ids = {p.unique_id: p.entity_id for p in self.hass.data[DATA_SQUEEZEBOX]}
295+
sync_group = []
296+
for player in self._player.sync_group:
297+
if player in player_ids:
298+
sync_group.append(player_ids[player])
299+
return sync_group
300+
301+
@property
302+
def query_result(self):
303+
"""Return the result from the call_query service."""
304+
return self._query_result
305+
287306
async def async_turn_off(self):
288307
"""Turn off media player."""
289308
await self._player.async_set_power(False)
@@ -366,3 +385,35 @@ async def async_call_method(self, command, parameters=None):
366385
for parameter in parameters:
367386
all_params.append(parameter)
368387
await self._player.async_query(*all_params)
388+
389+
async def async_call_query(self, command, parameters=None):
390+
"""
391+
Call Squeezebox JSON/RPC method where we care about the result.
392+
393+
Additional parameters are added to the command to form the list of
394+
positional parameters (p0, p1..., pN) passed to JSON/RPC server.
395+
"""
396+
all_params = [command]
397+
if parameters:
398+
for parameter in parameters:
399+
all_params.append(parameter)
400+
self._query_result = await self._player.async_query(*all_params)
401+
_LOGGER.debug("call_query got result %s", self._query_result)
402+
403+
async def async_sync(self, other_player):
404+
"""
405+
Add another Squeezebox player to this player's sync group.
406+
407+
If the other player is a member of a sync group, it will leave the current sync group
408+
without asking.
409+
"""
410+
player_ids = {p.entity_id: p.unique_id for p in self.hass.data[DATA_SQUEEZEBOX]}
411+
other_player_id = player_ids.get(other_player)
412+
if other_player_id:
413+
await self._player.async_sync(other_player_id)
414+
else:
415+
_LOGGER.info("Could not find player_id for %s. Not syncing.", other_player)
416+
417+
async def async_unsync(self):
418+
"""Unsync this Squeezebox player."""
419+
await self._player.async_unsync()

homeassistant/components/squeezebox/services.yaml

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,35 @@ call_method:
88
description: Command to pass to Logitech Media Server (p0 in the CLI documentation).
99
example: "playlist"
1010
parameters:
11-
description: Array of additional parameters to pass to Logitech Media Server (p1, ..., pN in the CLI documentation).
11+
description: >
12+
Array of additional parameters to pass to Logitech Media Server (p1, ..., pN in the CLI documentation).
1213
example: ["loadtracks", "album.titlesearch="]
14+
call_query:
15+
description: >
16+
Call a custom Squeezebox JSONRPC API. Result will be stored in 'query_result' attribute of the Squeezebox entity.
17+
fields:
18+
entity_id:
19+
description: Name(s) of the Squeezebox entities where to run the API method.
20+
example: 'media_player.squeezebox_radio'
21+
command:
22+
description: Command to pass to Logitech Media Server (p0 in the CLI documentation).
23+
example: 'albums'
24+
parameters:
25+
description: >
26+
Array of additional parameters to pass to Logitech Media Server (p1, ..., pN in the CLI documentation).
27+
example: ["0", "20", "Revolver"]
28+
sync:
29+
description: >
30+
Add another player to this player's sync group. If the other player is already in a sync group, it will leave it.
31+
fields:
32+
entity_id:
33+
description: Name of the Squeezebox entity where to run the API method.
34+
example: "media_player.bedroom"
35+
other_player:
36+
description: Name of the other Squeezebox player to link.
37+
example: "media_player.living_room"
38+
unsync:
39+
description: Remove this player from its sync group.
40+
fields:
41+
entity_id:
42+
description: Name of the Squeezebox entity to unsync.

requirements_all.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1590,7 +1590,7 @@ pysonos==0.0.25
15901590
pyspcwebgw==0.4.0
15911591

15921592
# homeassistant.components.squeezebox
1593-
pysqueezebox==0.1.2
1593+
pysqueezebox==0.1.4
15941594

15951595
# homeassistant.components.stiebel_eltron
15961596
pystiebeleltron==0.0.1.dev2

0 commit comments

Comments
 (0)