11"""Support for interfacing to the Logitech SqueezeBox API."""
2- import asyncio
32import logging
43import socket
54
2524)
2625from 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)
3533from homeassistant .exceptions import PlatformNotReady
34+ from homeassistant .helpers import config_validation as cv , entity_platform
3635from homeassistant .helpers .aiohttp_client import async_get_clientsession
37- import homeassistant .helpers .config_validation as cv
3836from 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
5965 | SUPPORT_CLEAR_PLAYLIST
6066)
6167
62- MEDIA_PLAYER_SCHEMA = vol .Schema ({ATTR_ENTITY_ID : cv .comp_entity_ids })
63-
6468PLATFORM_SCHEMA = PLATFORM_SCHEMA .extend (
6569 {
6670 vol .Required (CONF_HOST ): cv .string ,
7680
7781ATTR_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
9691async 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 ()
0 commit comments