mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add Get Queue HEOS entity service (#141150)
This commit is contained in:
parent
f3bcb96b41
commit
ab709aeb46
@ -4,6 +4,7 @@ ATTR_PASSWORD = "password"
|
||||
ATTR_USERNAME = "username"
|
||||
DOMAIN = "heos"
|
||||
ENTRY_TITLE = "HEOS System"
|
||||
SERVICE_GET_QUEUE = "get_queue"
|
||||
SERVICE_GROUP_VOLUME_SET = "group_volume_set"
|
||||
SERVICE_GROUP_VOLUME_DOWN = "group_volume_down"
|
||||
SERVICE_GROUP_VOLUME_UP = "group_volume_up"
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"services": {
|
||||
"get_queue": {
|
||||
"service": "mdi:playlist-music"
|
||||
},
|
||||
"group_volume_set": {
|
||||
"service": "mdi:volume-medium"
|
||||
},
|
||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable, Coroutine, Sequence
|
||||
from contextlib import suppress
|
||||
import dataclasses
|
||||
from datetime import datetime
|
||||
from functools import reduce, wraps
|
||||
import logging
|
||||
@ -42,7 +43,12 @@ from homeassistant.components.media_player import (
|
||||
)
|
||||
from homeassistant.components.media_source import BrowseMediaSource
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
ServiceResponse,
|
||||
SupportsResponse,
|
||||
callback,
|
||||
)
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
@ -56,6 +62,7 @@ from homeassistant.util.dt import utcnow
|
||||
|
||||
from .const import (
|
||||
DOMAIN as HEOS_DOMAIN,
|
||||
SERVICE_GET_QUEUE,
|
||||
SERVICE_GROUP_VOLUME_DOWN,
|
||||
SERVICE_GROUP_VOLUME_SET,
|
||||
SERVICE_GROUP_VOLUME_UP,
|
||||
@ -132,6 +139,12 @@ async def async_setup_entry(
|
||||
"""Add media players for a config entry."""
|
||||
# Register custom entity services
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_GET_QUEUE,
|
||||
None,
|
||||
"async_get_queue",
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_GROUP_VOLUME_SET,
|
||||
{vol.Required(ATTR_MEDIA_VOLUME_LEVEL): cv.small_float},
|
||||
@ -155,20 +168,20 @@ async def async_setup_entry(
|
||||
add_entities_callback(list(coordinator.heos.players.values()))
|
||||
|
||||
|
||||
type _FuncType[**_P] = Callable[_P, Awaitable[Any]]
|
||||
type _ReturnFuncType[**_P] = Callable[_P, Coroutine[Any, Any, None]]
|
||||
type _FuncType[**_P, _R] = Callable[_P, Awaitable[_R]]
|
||||
type _ReturnFuncType[**_P, _R] = Callable[_P, Coroutine[Any, Any, _R]]
|
||||
|
||||
|
||||
def catch_action_error[**_P](
|
||||
def catch_action_error[**_P, _R](
|
||||
action: str,
|
||||
) -> Callable[[_FuncType[_P]], _ReturnFuncType[_P]]:
|
||||
) -> Callable[[_FuncType[_P, _R]], _ReturnFuncType[_P, _R]]:
|
||||
"""Return decorator that catches errors and raises HomeAssistantError."""
|
||||
|
||||
def decorator(func: _FuncType[_P]) -> _ReturnFuncType[_P]:
|
||||
def decorator(func: _FuncType[_P, _R]) -> _ReturnFuncType[_P, _R]:
|
||||
@wraps(func)
|
||||
async def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> None:
|
||||
async def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
|
||||
try:
|
||||
await func(*args, **kwargs)
|
||||
return await func(*args, **kwargs)
|
||||
except (HeosError, ValueError) as ex:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=HEOS_DOMAIN,
|
||||
@ -268,6 +281,12 @@ class HeosMediaPlayer(CoordinatorEntity[HeosCoordinator], MediaPlayerEntity):
|
||||
self.async_on_remove(self._player.add_on_player_event(self._player_update))
|
||||
await super().async_added_to_hass()
|
||||
|
||||
@catch_action_error("get queue")
|
||||
async def async_get_queue(self) -> ServiceResponse:
|
||||
"""Get the queue for the current player."""
|
||||
queue = await self._player.get_queue()
|
||||
return {"queue": [dataclasses.asdict(item) for item in queue]}
|
||||
|
||||
@catch_action_error("clear playlist")
|
||||
async def async_clear_playlist(self) -> None:
|
||||
"""Clear players playlist."""
|
||||
|
@ -1,3 +1,9 @@
|
||||
get_queue:
|
||||
target:
|
||||
entity:
|
||||
integration: heos
|
||||
domain: media_player
|
||||
|
||||
group_volume_set:
|
||||
target:
|
||||
entity:
|
||||
|
@ -86,6 +86,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_queue": {
|
||||
"name": "Get queue",
|
||||
"description": "Retrieves the queue of the media player."
|
||||
},
|
||||
"group_volume_down": {
|
||||
"name": "Turn down group volume",
|
||||
"description": "Turns down the group volume."
|
||||
|
@ -37,6 +37,7 @@ class MockHeos(Heos):
|
||||
self.play_preset_station: AsyncMock = AsyncMock()
|
||||
self.play_url: AsyncMock = AsyncMock()
|
||||
self.player_clear_queue: AsyncMock = AsyncMock()
|
||||
self.player_get_queue: AsyncMock = AsyncMock()
|
||||
self.player_get_quick_selects: AsyncMock = AsyncMock()
|
||||
self.player_play_next: AsyncMock = AsyncMock()
|
||||
self.player_play_previous: AsyncMock = AsyncMock()
|
||||
|
@ -20,6 +20,7 @@ from pyheos import (
|
||||
NetworkType,
|
||||
PlayerUpdateResult,
|
||||
PlayState,
|
||||
QueueItem,
|
||||
RepeatType,
|
||||
const,
|
||||
)
|
||||
@ -359,3 +360,28 @@ def change_data_fixture() -> PlayerUpdateResult:
|
||||
def change_data_mapped_ids_fixture() -> PlayerUpdateResult:
|
||||
"""Create player change data for testing."""
|
||||
return PlayerUpdateResult(updated_player_ids={1: 101})
|
||||
|
||||
|
||||
@pytest.fixture(name="queue")
|
||||
def queue_fixture() -> list[QueueItem]:
|
||||
"""Create a queue fixture."""
|
||||
return [
|
||||
QueueItem(
|
||||
queue_id=1,
|
||||
song="Espresso",
|
||||
album="Espresso",
|
||||
artist="Sabrina Carpenter",
|
||||
image_url="http://resources.wimpmusic.com/images/e4f2d75f/a69e/4b8a/b800/e18546b1ad4c/640x640.jpg",
|
||||
media_id="356276483",
|
||||
album_id="356276481",
|
||||
),
|
||||
QueueItem(
|
||||
queue_id=2,
|
||||
song="A Bar Song (Tipsy)",
|
||||
album="A Bar Song (Tipsy)",
|
||||
artist="Shaboozey",
|
||||
image_url="http://resources.wimpmusic.com/images/d05b8da3/4fae/45ff/ac1b/7ab7caab3523/640x640.jpg",
|
||||
media_id="354365598",
|
||||
album_id="354365596",
|
||||
),
|
||||
]
|
||||
|
@ -159,6 +159,32 @@
|
||||
'title': 'Music Sources',
|
||||
})
|
||||
# ---
|
||||
# name: test_get_queue
|
||||
dict({
|
||||
'media_player.test_player': dict({
|
||||
'queue': list([
|
||||
dict({
|
||||
'album': 'Espresso',
|
||||
'album_id': '356276481',
|
||||
'artist': 'Sabrina Carpenter',
|
||||
'image_url': 'http://resources.wimpmusic.com/images/e4f2d75f/a69e/4b8a/b800/e18546b1ad4c/640x640.jpg',
|
||||
'media_id': '356276483',
|
||||
'queue_id': 1,
|
||||
'song': 'Espresso',
|
||||
}),
|
||||
dict({
|
||||
'album': 'A Bar Song (Tipsy)',
|
||||
'album_id': '354365596',
|
||||
'artist': 'Shaboozey',
|
||||
'image_url': 'http://resources.wimpmusic.com/images/d05b8da3/4fae/45ff/ac1b/7ab7caab3523/640x640.jpg',
|
||||
'media_id': '354365598',
|
||||
'queue_id': 2,
|
||||
'song': 'A Bar Song (Tipsy)',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_state_attributes
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
|
@ -15,6 +15,7 @@ from pyheos import (
|
||||
MediaType as HeosMediaType,
|
||||
PlayerUpdateResult,
|
||||
PlayState,
|
||||
QueueItem,
|
||||
RepeatType,
|
||||
SignalHeosEvent,
|
||||
SignalType,
|
||||
@ -27,6 +28,7 @@ from syrupy.filters import props
|
||||
|
||||
from homeassistant.components.heos.const import (
|
||||
DOMAIN,
|
||||
SERVICE_GET_QUEUE,
|
||||
SERVICE_GROUP_VOLUME_DOWN,
|
||||
SERVICE_GROUP_VOLUME_SET,
|
||||
SERVICE_GROUP_VOLUME_UP,
|
||||
@ -1696,3 +1698,27 @@ async def test_media_player_group_fails_wrong_integration(
|
||||
blocking=True,
|
||||
)
|
||||
controller.set_group.assert_not_called()
|
||||
|
||||
|
||||
async def test_get_queue(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
controller: MockHeos,
|
||||
queue: list[QueueItem],
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the get queue service."""
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
controller.player_get_queue.return_value = queue
|
||||
response = await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_GET_QUEUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.test_player",
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
controller.player_get_queue.assert_called_once_with(1, None, None)
|
||||
assert response == snapshot
|
||||
|
Loading…
x
Reference in New Issue
Block a user