Skip to content

Commit 50349e4

Browse files
authored
Register sonos entity services in async_setup (home-assistant#152047)
1 parent 42d0415 commit 50349e4

File tree

5 files changed

+154
-110
lines changed

5 files changed

+154
-110
lines changed

homeassistant/components/sonos/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from .exception import SonosUpdateError
6060
from .favorites import SonosFavorites
6161
from .helpers import SonosConfigEntry, SonosData, sync_get_visible_zones
62+
from .services import async_setup_services
6263
from .speaker import SonosSpeaker
6364

6465
_LOGGER = logging.getLogger(__name__)
@@ -104,6 +105,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
104105
)
105106
)
106107

108+
async_setup_services(hass)
109+
107110
return True
108111

109112

homeassistant/components/sonos/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@
194194
SPEECH_DIALOG_LEVEL = "speech_dialog_level"
195195
ATTR_DIALOG_LEVEL = "dialog_level"
196196
ATTR_DIALOG_LEVEL_ENUM = "dialog_level_enum"
197+
ATTR_QUEUE_POSITION = "queue_position"
197198

198199
AVAILABILITY_CHECK_INTERVAL = datetime.timedelta(minutes=1)
199200
AVAILABILITY_TIMEOUT = AVAILABILITY_CHECK_INTERVAL.total_seconds() * 4.5

homeassistant/components/sonos/media_player.py

Lines changed: 3 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from soco.data_structures import DidlFavorite, DidlMusicTrack
1818
from soco.ms_data_structures import MusicServiceItem
1919
from sonos_websocket.exception import SonosWebsocketError
20-
import voluptuous as vol
2120

2221
from homeassistant.components import media_source, spotify
2322
from homeassistant.components.media_player import (
@@ -40,21 +39,16 @@
4039
)
4140
from homeassistant.components.plex import PLEX_URI_SCHEME
4241
from homeassistant.components.plex.services import process_plex_payload
43-
from homeassistant.const import ATTR_TIME
44-
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse, callback
42+
from homeassistant.core import HomeAssistant, callback
4543
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
46-
from homeassistant.helpers import (
47-
config_validation as cv,
48-
entity_platform,
49-
entity_registry as er,
50-
service,
51-
)
44+
from homeassistant.helpers import entity_registry as er
5245
from homeassistant.helpers.dispatcher import async_dispatcher_connect
5346
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
5447
from homeassistant.helpers.event import async_call_later
5548

5649
from . import media_browser
5750
from .const import (
51+
ATTR_QUEUE_POSITION,
5852
DOMAIN,
5953
MEDIA_TYPE_DIRECTORY,
6054
MEDIA_TYPES_TO_SONOS,
@@ -93,123 +87,24 @@
9387
UPNP_ERRORS_TO_IGNORE = ["701", "711", "712"]
9488
ANNOUNCE_NOT_SUPPORTED_ERRORS: list[str] = ["globalError"]
9589

96-
SERVICE_SNAPSHOT = "snapshot"
97-
SERVICE_RESTORE = "restore"
98-
SERVICE_SET_TIMER = "set_sleep_timer"
99-
SERVICE_CLEAR_TIMER = "clear_sleep_timer"
100-
SERVICE_UPDATE_ALARM = "update_alarm"
101-
SERVICE_PLAY_QUEUE = "play_queue"
102-
SERVICE_REMOVE_FROM_QUEUE = "remove_from_queue"
103-
SERVICE_GET_QUEUE = "get_queue"
104-
105-
ATTR_SLEEP_TIME = "sleep_time"
106-
ATTR_ALARM_ID = "alarm_id"
107-
ATTR_VOLUME = "volume"
108-
ATTR_ENABLED = "enabled"
109-
ATTR_INCLUDE_LINKED_ZONES = "include_linked_zones"
110-
ATTR_MASTER = "master"
111-
ATTR_WITH_GROUP = "with_group"
112-
ATTR_QUEUE_POSITION = "queue_position"
113-
11490

11591
async def async_setup_entry(
11692
hass: HomeAssistant,
11793
config_entry: SonosConfigEntry,
11894
async_add_entities: AddConfigEntryEntitiesCallback,
11995
) -> None:
12096
"""Set up Sonos from a config entry."""
121-
platform = entity_platform.async_get_current_platform()
12297

12398
@callback
12499
def async_create_entities(speaker: SonosSpeaker) -> None:
125100
"""Handle device discovery and create entities."""
126101
_LOGGER.debug("Creating media_player on %s", speaker.zone_name)
127102
async_add_entities([SonosMediaPlayerEntity(speaker, config_entry)])
128103

129-
@service.verify_domain_control(hass, DOMAIN)
130-
async def async_service_handle(service_call: ServiceCall) -> None:
131-
"""Handle dispatched services."""
132-
assert platform is not None
133-
entities = await platform.async_extract_from_service(service_call)
134-
135-
if not entities:
136-
return
137-
138-
speakers = []
139-
for entity in entities:
140-
assert isinstance(entity, SonosMediaPlayerEntity)
141-
speakers.append(entity.speaker)
142-
143-
if service_call.service == SERVICE_SNAPSHOT:
144-
await SonosSpeaker.snapshot_multi(
145-
hass, config_entry, speakers, service_call.data[ATTR_WITH_GROUP]
146-
)
147-
elif service_call.service == SERVICE_RESTORE:
148-
await SonosSpeaker.restore_multi(
149-
hass, config_entry, speakers, service_call.data[ATTR_WITH_GROUP]
150-
)
151-
152104
config_entry.async_on_unload(
153105
async_dispatcher_connect(hass, SONOS_CREATE_MEDIA_PLAYER, async_create_entities)
154106
)
155107

156-
join_unjoin_schema = cv.make_entity_service_schema(
157-
{vol.Optional(ATTR_WITH_GROUP, default=True): cv.boolean}
158-
)
159-
160-
hass.services.async_register(
161-
DOMAIN, SERVICE_SNAPSHOT, async_service_handle, join_unjoin_schema
162-
)
163-
164-
hass.services.async_register(
165-
DOMAIN, SERVICE_RESTORE, async_service_handle, join_unjoin_schema
166-
)
167-
168-
platform.async_register_entity_service(
169-
SERVICE_SET_TIMER,
170-
{
171-
vol.Required(ATTR_SLEEP_TIME): vol.All(
172-
vol.Coerce(int), vol.Range(min=0, max=86399)
173-
)
174-
},
175-
"set_sleep_timer",
176-
)
177-
178-
platform.async_register_entity_service(
179-
SERVICE_CLEAR_TIMER, None, "clear_sleep_timer"
180-
)
181-
182-
platform.async_register_entity_service(
183-
SERVICE_UPDATE_ALARM,
184-
{
185-
vol.Required(ATTR_ALARM_ID): cv.positive_int,
186-
vol.Optional(ATTR_TIME): cv.time,
187-
vol.Optional(ATTR_VOLUME): cv.small_float,
188-
vol.Optional(ATTR_ENABLED): cv.boolean,
189-
vol.Optional(ATTR_INCLUDE_LINKED_ZONES): cv.boolean,
190-
},
191-
"set_alarm",
192-
)
193-
194-
platform.async_register_entity_service(
195-
SERVICE_PLAY_QUEUE,
196-
{vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int},
197-
"play_queue",
198-
)
199-
200-
platform.async_register_entity_service(
201-
SERVICE_REMOVE_FROM_QUEUE,
202-
{vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int},
203-
"remove_from_queue",
204-
)
205-
206-
platform.async_register_entity_service(
207-
SERVICE_GET_QUEUE,
208-
None,
209-
"get_queue",
210-
supports_response=SupportsResponse.ONLY,
211-
)
212-
213108

214109
class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
215110
"""Representation of a Sonos entity."""
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"""Support to interface with Sonos players."""
2+
3+
from __future__ import annotations
4+
5+
import voluptuous as vol
6+
7+
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN
8+
from homeassistant.const import ATTR_TIME
9+
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse, callback
10+
from homeassistant.helpers import config_validation as cv, service
11+
from homeassistant.helpers.entity_platform import DATA_DOMAIN_PLATFORM_ENTITIES
12+
13+
from .const import ATTR_QUEUE_POSITION, DOMAIN
14+
from .media_player import SonosMediaPlayerEntity
15+
from .speaker import SonosSpeaker
16+
17+
SERVICE_SNAPSHOT = "snapshot"
18+
SERVICE_RESTORE = "restore"
19+
SERVICE_SET_TIMER = "set_sleep_timer"
20+
SERVICE_CLEAR_TIMER = "clear_sleep_timer"
21+
SERVICE_UPDATE_ALARM = "update_alarm"
22+
SERVICE_PLAY_QUEUE = "play_queue"
23+
SERVICE_REMOVE_FROM_QUEUE = "remove_from_queue"
24+
SERVICE_GET_QUEUE = "get_queue"
25+
26+
ATTR_SLEEP_TIME = "sleep_time"
27+
ATTR_ALARM_ID = "alarm_id"
28+
ATTR_VOLUME = "volume"
29+
ATTR_ENABLED = "enabled"
30+
ATTR_INCLUDE_LINKED_ZONES = "include_linked_zones"
31+
ATTR_WITH_GROUP = "with_group"
32+
33+
34+
@callback
35+
def async_setup_services(hass: HomeAssistant) -> None:
36+
"""Register Sonos services."""
37+
38+
@service.verify_domain_control(hass, DOMAIN)
39+
async def async_service_handle(service_call: ServiceCall) -> None:
40+
"""Handle dispatched services."""
41+
platform_entities = hass.data.get(DATA_DOMAIN_PLATFORM_ENTITIES, {}).get(
42+
(MEDIA_PLAYER_DOMAIN, DOMAIN), {}
43+
)
44+
45+
entities = await service.async_extract_entities(
46+
hass, platform_entities.values(), service_call
47+
)
48+
49+
if not entities:
50+
return
51+
52+
speakers: list[SonosSpeaker] = []
53+
for entity in entities:
54+
assert isinstance(entity, SonosMediaPlayerEntity)
55+
speakers.append(entity.speaker)
56+
57+
config_entry = speakers[0].config_entry # All speakers share the same entry
58+
59+
if service_call.service == SERVICE_SNAPSHOT:
60+
await SonosSpeaker.snapshot_multi(
61+
hass, config_entry, speakers, service_call.data[ATTR_WITH_GROUP]
62+
)
63+
elif service_call.service == SERVICE_RESTORE:
64+
await SonosSpeaker.restore_multi(
65+
hass, config_entry, speakers, service_call.data[ATTR_WITH_GROUP]
66+
)
67+
68+
join_unjoin_schema = cv.make_entity_service_schema(
69+
{vol.Optional(ATTR_WITH_GROUP, default=True): cv.boolean}
70+
)
71+
72+
hass.services.async_register(
73+
DOMAIN, SERVICE_SNAPSHOT, async_service_handle, join_unjoin_schema
74+
)
75+
76+
hass.services.async_register(
77+
DOMAIN, SERVICE_RESTORE, async_service_handle, join_unjoin_schema
78+
)
79+
80+
service.async_register_platform_entity_service(
81+
hass,
82+
DOMAIN,
83+
SERVICE_SET_TIMER,
84+
entity_domain=MEDIA_PLAYER_DOMAIN,
85+
schema={
86+
vol.Required(ATTR_SLEEP_TIME): vol.All(
87+
vol.Coerce(int), vol.Range(min=0, max=86399)
88+
)
89+
},
90+
func="set_sleep_timer",
91+
)
92+
93+
service.async_register_platform_entity_service(
94+
hass,
95+
DOMAIN,
96+
SERVICE_CLEAR_TIMER,
97+
entity_domain=MEDIA_PLAYER_DOMAIN,
98+
schema=None,
99+
func="clear_sleep_timer",
100+
)
101+
102+
service.async_register_platform_entity_service(
103+
hass,
104+
DOMAIN,
105+
SERVICE_UPDATE_ALARM,
106+
entity_domain=MEDIA_PLAYER_DOMAIN,
107+
schema={
108+
vol.Required(ATTR_ALARM_ID): cv.positive_int,
109+
vol.Optional(ATTR_TIME): cv.time,
110+
vol.Optional(ATTR_VOLUME): cv.small_float,
111+
vol.Optional(ATTR_ENABLED): cv.boolean,
112+
vol.Optional(ATTR_INCLUDE_LINKED_ZONES): cv.boolean,
113+
},
114+
func="set_alarm",
115+
)
116+
117+
service.async_register_platform_entity_service(
118+
hass,
119+
DOMAIN,
120+
SERVICE_PLAY_QUEUE,
121+
entity_domain=MEDIA_PLAYER_DOMAIN,
122+
schema={vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int},
123+
func="play_queue",
124+
)
125+
126+
service.async_register_platform_entity_service(
127+
hass,
128+
DOMAIN,
129+
SERVICE_REMOVE_FROM_QUEUE,
130+
entity_domain=MEDIA_PLAYER_DOMAIN,
131+
schema={vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int},
132+
func="remove_from_queue",
133+
)
134+
135+
service.async_register_platform_entity_service(
136+
hass,
137+
DOMAIN,
138+
SERVICE_GET_QUEUE,
139+
entity_domain=MEDIA_PLAYER_DOMAIN,
140+
schema=None,
141+
func="get_queue",
142+
supports_response=SupportsResponse.ONLY,
143+
)

tests/components/sonos/test_media_player.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,18 @@
3333
SOURCE_TV,
3434
)
3535
from homeassistant.components.sonos.media_player import (
36+
LONG_SERVICE_TIMEOUT,
37+
VOLUME_INCREMENT,
38+
)
39+
from homeassistant.components.sonos.services import (
3640
ATTR_ALARM_ID,
3741
ATTR_ENABLED,
3842
ATTR_INCLUDE_LINKED_ZONES,
3943
ATTR_VOLUME,
40-
LONG_SERVICE_TIMEOUT,
4144
SERVICE_GET_QUEUE,
4245
SERVICE_RESTORE,
4346
SERVICE_SNAPSHOT,
4447
SERVICE_UPDATE_ALARM,
45-
VOLUME_INCREMENT,
4648
)
4749
from homeassistant.const import (
4850
ATTR_ENTITY_ID,

0 commit comments

Comments
 (0)