Skip to content

Commit ab709ae

Browse files
authored
Add Get Queue HEOS entity service (#141150)
1 parent f3bcb96 commit ab709ae

File tree

9 files changed

+120
-8
lines changed

9 files changed

+120
-8
lines changed

homeassistant/components/heos/const.py

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
ATTR_USERNAME = "username"
55
DOMAIN = "heos"
66
ENTRY_TITLE = "HEOS System"
7+
SERVICE_GET_QUEUE = "get_queue"
78
SERVICE_GROUP_VOLUME_SET = "group_volume_set"
89
SERVICE_GROUP_VOLUME_DOWN = "group_volume_down"
910
SERVICE_GROUP_VOLUME_UP = "group_volume_up"

homeassistant/components/heos/icons.json

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{
22
"services": {
3+
"get_queue": {
4+
"service": "mdi:playlist-music"
5+
},
36
"group_volume_set": {
47
"service": "mdi:volume-medium"
58
},

homeassistant/components/heos/media_player.py

+27-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from collections.abc import Awaitable, Callable, Coroutine, Sequence
66
from contextlib import suppress
7+
import dataclasses
78
from datetime import datetime
89
from functools import reduce, wraps
910
import logging
@@ -42,7 +43,12 @@
4243
)
4344
from homeassistant.components.media_source import BrowseMediaSource
4445
from homeassistant.const import Platform
45-
from homeassistant.core import HomeAssistant, callback
46+
from homeassistant.core import (
47+
HomeAssistant,
48+
ServiceResponse,
49+
SupportsResponse,
50+
callback,
51+
)
4652
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
4753
from homeassistant.helpers import (
4854
config_validation as cv,
@@ -56,6 +62,7 @@
5662

5763
from .const import (
5864
DOMAIN as HEOS_DOMAIN,
65+
SERVICE_GET_QUEUE,
5966
SERVICE_GROUP_VOLUME_DOWN,
6067
SERVICE_GROUP_VOLUME_SET,
6168
SERVICE_GROUP_VOLUME_UP,
@@ -132,6 +139,12 @@ async def async_setup_entry(
132139
"""Add media players for a config entry."""
133140
# Register custom entity services
134141
platform = entity_platform.async_get_current_platform()
142+
platform.async_register_entity_service(
143+
SERVICE_GET_QUEUE,
144+
None,
145+
"async_get_queue",
146+
supports_response=SupportsResponse.ONLY,
147+
)
135148
platform.async_register_entity_service(
136149
SERVICE_GROUP_VOLUME_SET,
137150
{vol.Required(ATTR_MEDIA_VOLUME_LEVEL): cv.small_float},
@@ -155,20 +168,20 @@ def add_entities_callback(players: Sequence[HeosPlayer]) -> None:
155168
add_entities_callback(list(coordinator.heos.players.values()))
156169

157170

158-
type _FuncType[**_P] = Callable[_P, Awaitable[Any]]
159-
type _ReturnFuncType[**_P] = Callable[_P, Coroutine[Any, Any, None]]
171+
type _FuncType[**_P, _R] = Callable[_P, Awaitable[_R]]
172+
type _ReturnFuncType[**_P, _R] = Callable[_P, Coroutine[Any, Any, _R]]
160173

161174

162-
def catch_action_error[**_P](
175+
def catch_action_error[**_P, _R](
163176
action: str,
164-
) -> Callable[[_FuncType[_P]], _ReturnFuncType[_P]]:
177+
) -> Callable[[_FuncType[_P, _R]], _ReturnFuncType[_P, _R]]:
165178
"""Return decorator that catches errors and raises HomeAssistantError."""
166179

167-
def decorator(func: _FuncType[_P]) -> _ReturnFuncType[_P]:
180+
def decorator(func: _FuncType[_P, _R]) -> _ReturnFuncType[_P, _R]:
168181
@wraps(func)
169-
async def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> None:
182+
async def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
170183
try:
171-
await func(*args, **kwargs)
184+
return await func(*args, **kwargs)
172185
except (HeosError, ValueError) as ex:
173186
raise HomeAssistantError(
174187
translation_domain=HEOS_DOMAIN,
@@ -268,6 +281,12 @@ async def async_added_to_hass(self) -> None:
268281
self.async_on_remove(self._player.add_on_player_event(self._player_update))
269282
await super().async_added_to_hass()
270283

284+
@catch_action_error("get queue")
285+
async def async_get_queue(self) -> ServiceResponse:
286+
"""Get the queue for the current player."""
287+
queue = await self._player.get_queue()
288+
return {"queue": [dataclasses.asdict(item) for item in queue]}
289+
271290
@catch_action_error("clear playlist")
272291
async def async_clear_playlist(self) -> None:
273292
"""Clear players playlist."""

homeassistant/components/heos/services.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
get_queue:
2+
target:
3+
entity:
4+
integration: heos
5+
domain: media_player
6+
17
group_volume_set:
28
target:
39
entity:

homeassistant/components/heos/strings.json

+4
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@
8686
}
8787
}
8888
},
89+
"get_queue": {
90+
"name": "Get queue",
91+
"description": "Retrieves the queue of the media player."
92+
},
8993
"group_volume_down": {
9094
"name": "Turn down group volume",
9195
"description": "Turns down the group volume."

tests/components/heos/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def __init__(self, options: HeosOptions) -> None:
3737
self.play_preset_station: AsyncMock = AsyncMock()
3838
self.play_url: AsyncMock = AsyncMock()
3939
self.player_clear_queue: AsyncMock = AsyncMock()
40+
self.player_get_queue: AsyncMock = AsyncMock()
4041
self.player_get_quick_selects: AsyncMock = AsyncMock()
4142
self.player_play_next: AsyncMock = AsyncMock()
4243
self.player_play_previous: AsyncMock = AsyncMock()

tests/components/heos/conftest.py

+26
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
NetworkType,
2121
PlayerUpdateResult,
2222
PlayState,
23+
QueueItem,
2324
RepeatType,
2425
const,
2526
)
@@ -359,3 +360,28 @@ def change_data_fixture() -> PlayerUpdateResult:
359360
def change_data_mapped_ids_fixture() -> PlayerUpdateResult:
360361
"""Create player change data for testing."""
361362
return PlayerUpdateResult(updated_player_ids={1: 101})
363+
364+
365+
@pytest.fixture(name="queue")
366+
def queue_fixture() -> list[QueueItem]:
367+
"""Create a queue fixture."""
368+
return [
369+
QueueItem(
370+
queue_id=1,
371+
song="Espresso",
372+
album="Espresso",
373+
artist="Sabrina Carpenter",
374+
image_url="http://resources.wimpmusic.com/images/e4f2d75f/a69e/4b8a/b800/e18546b1ad4c/640x640.jpg",
375+
media_id="356276483",
376+
album_id="356276481",
377+
),
378+
QueueItem(
379+
queue_id=2,
380+
song="A Bar Song (Tipsy)",
381+
album="A Bar Song (Tipsy)",
382+
artist="Shaboozey",
383+
image_url="http://resources.wimpmusic.com/images/d05b8da3/4fae/45ff/ac1b/7ab7caab3523/640x640.jpg",
384+
media_id="354365598",
385+
album_id="354365596",
386+
),
387+
]

tests/components/heos/snapshots/test_media_player.ambr

+26
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,32 @@
159159
'title': 'Music Sources',
160160
})
161161
# ---
162+
# name: test_get_queue
163+
dict({
164+
'media_player.test_player': dict({
165+
'queue': list([
166+
dict({
167+
'album': 'Espresso',
168+
'album_id': '356276481',
169+
'artist': 'Sabrina Carpenter',
170+
'image_url': 'http://resources.wimpmusic.com/images/e4f2d75f/a69e/4b8a/b800/e18546b1ad4c/640x640.jpg',
171+
'media_id': '356276483',
172+
'queue_id': 1,
173+
'song': 'Espresso',
174+
}),
175+
dict({
176+
'album': 'A Bar Song (Tipsy)',
177+
'album_id': '354365596',
178+
'artist': 'Shaboozey',
179+
'image_url': 'http://resources.wimpmusic.com/images/d05b8da3/4fae/45ff/ac1b/7ab7caab3523/640x640.jpg',
180+
'media_id': '354365598',
181+
'queue_id': 2,
182+
'song': 'A Bar Song (Tipsy)',
183+
}),
184+
]),
185+
}),
186+
})
187+
# ---
162188
# name: test_state_attributes
163189
StateSnapshot({
164190
'attributes': ReadOnlyDict({

tests/components/heos/test_media_player.py

+26
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
MediaType as HeosMediaType,
1616
PlayerUpdateResult,
1717
PlayState,
18+
QueueItem,
1819
RepeatType,
1920
SignalHeosEvent,
2021
SignalType,
@@ -27,6 +28,7 @@
2728

2829
from homeassistant.components.heos.const import (
2930
DOMAIN,
31+
SERVICE_GET_QUEUE,
3032
SERVICE_GROUP_VOLUME_DOWN,
3133
SERVICE_GROUP_VOLUME_SET,
3234
SERVICE_GROUP_VOLUME_UP,
@@ -1696,3 +1698,27 @@ async def test_media_player_group_fails_wrong_integration(
16961698
blocking=True,
16971699
)
16981700
controller.set_group.assert_not_called()
1701+
1702+
1703+
async def test_get_queue(
1704+
hass: HomeAssistant,
1705+
config_entry: MockConfigEntry,
1706+
controller: MockHeos,
1707+
queue: list[QueueItem],
1708+
snapshot: SnapshotAssertion,
1709+
) -> None:
1710+
"""Test the get queue service."""
1711+
config_entry.add_to_hass(hass)
1712+
await hass.config_entries.async_setup(config_entry.entry_id)
1713+
controller.player_get_queue.return_value = queue
1714+
response = await hass.services.async_call(
1715+
DOMAIN,
1716+
SERVICE_GET_QUEUE,
1717+
{
1718+
ATTR_ENTITY_ID: "media_player.test_player",
1719+
},
1720+
blocking=True,
1721+
return_response=True,
1722+
)
1723+
controller.player_get_queue.assert_called_once_with(1, None, None)
1724+
assert response == snapshot

0 commit comments

Comments
 (0)