mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 01:38:02 +00:00
Add beolink_join source_id parameter to Bang & Olufsen (#132377)
* Add source as parameter to beolink join service * Add beolink join source and responses * Improve comment Add translation * Remove result from beolink join custom action * Cleanup * Use options selector instead of string for source ID Fix test docstring * Update options * Use translation dict for source ids Add input validation Add tests for invalid sources Improve source id description * Use list instead of translation dict Remove platform prefixes Add test for Beolink Converter source * Fix source_id naming and order
This commit is contained in:
parent
8fd64d2ca4
commit
d4546c94b0
@ -210,3 +210,20 @@ BANG_OLUFSEN_WEBSOCKET_EVENT: Final[str] = f"{DOMAIN}_websocket_event"
|
||||
|
||||
|
||||
CONNECTION_STATUS: Final[str] = "CONNECTION_STATUS"
|
||||
|
||||
# Beolink Converter NL/ML sources need to be transformed to upper case
|
||||
BEOLINK_JOIN_SOURCES_TO_UPPER = (
|
||||
"aux_a",
|
||||
"cd",
|
||||
"ph",
|
||||
"radio",
|
||||
"tp1",
|
||||
"tp2",
|
||||
)
|
||||
BEOLINK_JOIN_SOURCES = (
|
||||
*BEOLINK_JOIN_SOURCES_TO_UPPER,
|
||||
"beoradio",
|
||||
"deezer",
|
||||
"spotify",
|
||||
"tidal",
|
||||
)
|
||||
|
@ -74,6 +74,8 @@ from .const import (
|
||||
BANG_OLUFSEN_REPEAT_FROM_HA,
|
||||
BANG_OLUFSEN_REPEAT_TO_HA,
|
||||
BANG_OLUFSEN_STATES,
|
||||
BEOLINK_JOIN_SOURCES,
|
||||
BEOLINK_JOIN_SOURCES_TO_UPPER,
|
||||
CONF_BEOLINK_JID,
|
||||
CONNECTION_STATUS,
|
||||
DOMAIN,
|
||||
@ -135,7 +137,10 @@ async def async_setup_entry(
|
||||
|
||||
platform.async_register_entity_service(
|
||||
name="beolink_join",
|
||||
schema={vol.Optional("beolink_jid"): jid_regex},
|
||||
schema={
|
||||
vol.Optional("beolink_jid"): jid_regex,
|
||||
vol.Optional("source_id"): vol.In(BEOLINK_JOIN_SOURCES),
|
||||
},
|
||||
func="async_beolink_join",
|
||||
)
|
||||
|
||||
@ -985,12 +990,23 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
||||
await self.async_beolink_leave()
|
||||
|
||||
# Custom actions:
|
||||
async def async_beolink_join(self, beolink_jid: str | None = None) -> None:
|
||||
async def async_beolink_join(
|
||||
self, beolink_jid: str | None = None, source_id: str | None = None
|
||||
) -> None:
|
||||
"""Join a Beolink multi-room experience."""
|
||||
# Touch to join
|
||||
if beolink_jid is None:
|
||||
await self._client.join_latest_beolink_experience()
|
||||
else:
|
||||
# Join a peer
|
||||
elif beolink_jid and source_id is None:
|
||||
await self._client.join_beolink_peer(jid=beolink_jid)
|
||||
# Join a peer and select specific source
|
||||
elif beolink_jid and source_id:
|
||||
# Beolink Converter NL/ML sources need to be in upper case
|
||||
if source_id in BEOLINK_JOIN_SOURCES_TO_UPPER:
|
||||
source_id = source_id.upper()
|
||||
|
||||
await self._client.join_beolink_peer(jid=beolink_jid, source=source_id)
|
||||
|
||||
async def async_beolink_expand(
|
||||
self, beolink_jids: list[str] | None = None, all_discovered: bool = False
|
||||
|
@ -48,6 +48,23 @@ beolink_join:
|
||||
example: 1111.2222222.33333333@products.bang-olufsen.com
|
||||
selector:
|
||||
text:
|
||||
source_id:
|
||||
required: false
|
||||
example: tidal
|
||||
selector:
|
||||
select:
|
||||
translation_key: "source_ids"
|
||||
options:
|
||||
- beoradio
|
||||
- deezer
|
||||
- spotify
|
||||
- tidal
|
||||
- radio
|
||||
- tp1
|
||||
- tp2
|
||||
- cd
|
||||
- aux_a
|
||||
- ph
|
||||
|
||||
beolink_leave:
|
||||
target:
|
||||
|
@ -29,6 +29,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"source_ids": {
|
||||
"options": {
|
||||
"beoradio": "ASE Beoradio",
|
||||
"deezer": "ASE / Mozart Deezer",
|
||||
"spotify": "ASE / Mozart Spotify",
|
||||
"tidal": "Mozart Tidal",
|
||||
"aux_a": "Beolink Converter NL/ML AUX_A",
|
||||
"cd": "Beolink Converter NL/ML CD",
|
||||
"ph": "Beolink Converter NL/ML PH",
|
||||
"radio": "Beolink Converter NL/ML RADIO",
|
||||
"tp1": "Beolink Converter NL/ML TP1",
|
||||
"tp2": "Beolink Converter NL/ML TP2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"beolink_allstandby": {
|
||||
"name": "Beolink all standby",
|
||||
@ -61,6 +77,10 @@
|
||||
"beolink_jid": {
|
||||
"name": "Beolink JID",
|
||||
"description": "Manually specify Beolink JID to join."
|
||||
},
|
||||
"source_id": {
|
||||
"name": "Source",
|
||||
"description": "Specify which source to join, behavior varies between hardware platforms. Source names prefaced by a platform name can only be used when connecting to that platform. For example \"ASE Beoradio\" can only be used when joining an ASE device, while ”ASE / Mozart Deezer” can be used with ASE or Mozart devices. A defined Beolink JID is required."
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
|
@ -243,7 +243,7 @@
|
||||
'state': 'playing',
|
||||
})
|
||||
# ---
|
||||
# name: test_async_beolink_join
|
||||
# name: test_async_beolink_join[service_parameters0-method_parameters0]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'beolink': dict({
|
||||
@ -291,6 +291,240 @@
|
||||
'state': 'playing',
|
||||
})
|
||||
# ---
|
||||
# name: test_async_beolink_join[service_parameters1-method_parameters1]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'beolink': dict({
|
||||
'listeners': dict({
|
||||
'Lego room Balance': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'peers': dict({
|
||||
'Lego room Balance': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'self': dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
}),
|
||||
}),
|
||||
'device_class': 'speaker',
|
||||
'entity_picture_local': None,
|
||||
'friendly_name': 'Living room Balance',
|
||||
'group_members': list([
|
||||
'media_player.beosound_balance_11111111',
|
||||
'listener_not_in_hass-1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'listener_not_in_hass-1111.1111111.44444444@products.bang-olufsen.com',
|
||||
]),
|
||||
'media_content_type': <MediaType.MUSIC: 'music'>,
|
||||
'repeat': <RepeatMode.OFF: 'off'>,
|
||||
'shuffle': False,
|
||||
'sound_mode': 'Test Listening Mode (123)',
|
||||
'sound_mode_list': list([
|
||||
'Test Listening Mode (123)',
|
||||
'Test Listening Mode (234)',
|
||||
'Test Listening Mode 2 (345)',
|
||||
]),
|
||||
'source_list': list([
|
||||
'Tidal',
|
||||
'Line-In',
|
||||
'HDMI A',
|
||||
]),
|
||||
'supported_features': <MediaPlayerEntityFeature: 2095933>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'media_player.beosound_balance_11111111',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'playing',
|
||||
})
|
||||
# ---
|
||||
# name: test_async_beolink_join[service_parameters2-method_parameters2]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'beolink': dict({
|
||||
'listeners': dict({
|
||||
'Lego room Balance': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'peers': dict({
|
||||
'Lego room Balance': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'self': dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
}),
|
||||
}),
|
||||
'device_class': 'speaker',
|
||||
'entity_picture_local': None,
|
||||
'friendly_name': 'Living room Balance',
|
||||
'group_members': list([
|
||||
'media_player.beosound_balance_11111111',
|
||||
'listener_not_in_hass-1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'listener_not_in_hass-1111.1111111.44444444@products.bang-olufsen.com',
|
||||
]),
|
||||
'media_content_type': <MediaType.MUSIC: 'music'>,
|
||||
'repeat': <RepeatMode.OFF: 'off'>,
|
||||
'shuffle': False,
|
||||
'sound_mode': 'Test Listening Mode (123)',
|
||||
'sound_mode_list': list([
|
||||
'Test Listening Mode (123)',
|
||||
'Test Listening Mode (234)',
|
||||
'Test Listening Mode 2 (345)',
|
||||
]),
|
||||
'source_list': list([
|
||||
'Tidal',
|
||||
'Line-In',
|
||||
'HDMI A',
|
||||
]),
|
||||
'supported_features': <MediaPlayerEntityFeature: 2095933>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'media_player.beosound_balance_11111111',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'playing',
|
||||
})
|
||||
# ---
|
||||
# name: test_async_beolink_join_invalid[service_parameters0-expected_result0]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'beolink': dict({
|
||||
'listeners': dict({
|
||||
'Lego room Balance': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'peers': dict({
|
||||
'Lego room Balance': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'self': dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
}),
|
||||
}),
|
||||
'device_class': 'speaker',
|
||||
'entity_picture_local': None,
|
||||
'friendly_name': 'Living room Balance',
|
||||
'group_members': list([
|
||||
'media_player.beosound_balance_11111111',
|
||||
'listener_not_in_hass-1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'listener_not_in_hass-1111.1111111.44444444@products.bang-olufsen.com',
|
||||
]),
|
||||
'media_content_type': <MediaType.MUSIC: 'music'>,
|
||||
'sound_mode': 'Test Listening Mode (123)',
|
||||
'sound_mode_list': list([
|
||||
'Test Listening Mode (123)',
|
||||
'Test Listening Mode (234)',
|
||||
'Test Listening Mode 2 (345)',
|
||||
]),
|
||||
'source_list': list([
|
||||
'Tidal',
|
||||
'Line-In',
|
||||
'HDMI A',
|
||||
]),
|
||||
'supported_features': <MediaPlayerEntityFeature: 2095933>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'media_player.beosound_balance_11111111',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'playing',
|
||||
})
|
||||
# ---
|
||||
# name: test_async_beolink_join_invalid[service_parameters1-expected_result1]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'beolink': dict({
|
||||
'listeners': dict({
|
||||
'Lego room Balance': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'peers': dict({
|
||||
'Lego room Balance': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'self': dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
}),
|
||||
}),
|
||||
'device_class': 'speaker',
|
||||
'entity_picture_local': None,
|
||||
'friendly_name': 'Living room Balance',
|
||||
'group_members': list([
|
||||
'media_player.beosound_balance_11111111',
|
||||
'listener_not_in_hass-1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'listener_not_in_hass-1111.1111111.44444444@products.bang-olufsen.com',
|
||||
]),
|
||||
'media_content_type': <MediaType.MUSIC: 'music'>,
|
||||
'sound_mode': 'Test Listening Mode (123)',
|
||||
'sound_mode_list': list([
|
||||
'Test Listening Mode (123)',
|
||||
'Test Listening Mode (234)',
|
||||
'Test Listening Mode 2 (345)',
|
||||
]),
|
||||
'source_list': list([
|
||||
'Tidal',
|
||||
'Line-In',
|
||||
'HDMI A',
|
||||
]),
|
||||
'supported_features': <MediaPlayerEntityFeature: 2095933>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'media_player.beosound_balance_11111111',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'playing',
|
||||
})
|
||||
# ---
|
||||
# name: test_async_beolink_join_invalid[service_parameters2-expected_result2]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'beolink': dict({
|
||||
'listeners': dict({
|
||||
'Lego room Balance': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'peers': dict({
|
||||
'Lego room Balance': '1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'Lounge room Balance': '1111.1111111.44444444@products.bang-olufsen.com',
|
||||
}),
|
||||
'self': dict({
|
||||
'Living room Balance': '1111.1111111.11111111@products.bang-olufsen.com',
|
||||
}),
|
||||
}),
|
||||
'device_class': 'speaker',
|
||||
'entity_picture_local': None,
|
||||
'friendly_name': 'Living room Balance',
|
||||
'group_members': list([
|
||||
'media_player.beosound_balance_11111111',
|
||||
'listener_not_in_hass-1111.1111111.33333333@products.bang-olufsen.com',
|
||||
'listener_not_in_hass-1111.1111111.44444444@products.bang-olufsen.com',
|
||||
]),
|
||||
'media_content_type': <MediaType.MUSIC: 'music'>,
|
||||
'sound_mode': 'Test Listening Mode (123)',
|
||||
'sound_mode_list': list([
|
||||
'Test Listening Mode (123)',
|
||||
'Test Listening Mode (234)',
|
||||
'Test Listening Mode 2 (345)',
|
||||
]),
|
||||
'source_list': list([
|
||||
'Tidal',
|
||||
'Line-In',
|
||||
'HDMI A',
|
||||
]),
|
||||
'supported_features': <MediaPlayerEntityFeature: 2095933>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'media_player.beosound_balance_11111111',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'playing',
|
||||
})
|
||||
# ---
|
||||
# name: test_async_beolink_unexpand
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
|
@ -18,6 +18,7 @@ from mozart_api.models import (
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
from syrupy.filters import props
|
||||
from voluptuous import Invalid, MultipleInvalid
|
||||
|
||||
from homeassistant.components.bang_olufsen.const import (
|
||||
BANG_OLUFSEN_REPEAT_FROM_HA,
|
||||
@ -1523,13 +1524,38 @@ async def test_async_unjoin_player(
|
||||
assert states == snapshot(exclude=props("media_position_updated_at"))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"service_parameters",
|
||||
"method_parameters",
|
||||
),
|
||||
[
|
||||
# Defined JID
|
||||
(
|
||||
{"beolink_jid": TEST_JID_2},
|
||||
{"jid": TEST_JID_2},
|
||||
),
|
||||
# Defined JID and source
|
||||
(
|
||||
{"beolink_jid": TEST_JID_2, "source_id": TEST_SOURCE.id},
|
||||
{"jid": TEST_JID_2, "source": TEST_SOURCE.id},
|
||||
),
|
||||
# Defined JID and Beolink Converter NL/ML source
|
||||
(
|
||||
{"beolink_jid": TEST_JID_2, "source_id": "cd"},
|
||||
{"jid": TEST_JID_2, "source": "CD"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_async_beolink_join(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_mozart_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
service_parameters: dict[str, str],
|
||||
method_parameters: dict[str, str],
|
||||
) -> None:
|
||||
"""Test async_beolink_join with defined JID."""
|
||||
"""Test async_beolink_join with defined JID and JID and source."""
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
@ -1537,14 +1563,61 @@ async def test_async_beolink_join(
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
"beolink_join",
|
||||
{
|
||||
ATTR_ENTITY_ID: TEST_MEDIA_PLAYER_ENTITY_ID,
|
||||
"beolink_jid": TEST_JID_2,
|
||||
},
|
||||
{ATTR_ENTITY_ID: TEST_MEDIA_PLAYER_ENTITY_ID, **service_parameters},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_mozart_client.join_beolink_peer.assert_called_once_with(jid=TEST_JID_2)
|
||||
mock_mozart_client.join_beolink_peer.assert_called_once_with(**method_parameters)
|
||||
|
||||
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
||||
assert states == snapshot(exclude=props("media_position_updated_at"))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"service_parameters",
|
||||
"expected_result",
|
||||
),
|
||||
[
|
||||
# Defined invalid JID
|
||||
(
|
||||
{"beolink_jid": "not_a_jid"},
|
||||
pytest.raises(Invalid),
|
||||
),
|
||||
# Defined invalid source
|
||||
(
|
||||
{"source_id": "invalid_source"},
|
||||
pytest.raises(MultipleInvalid),
|
||||
),
|
||||
# Defined invalid JID and invalid source
|
||||
(
|
||||
{"beolink_jid": "not_a_jid", "source_id": "invalid_source"},
|
||||
pytest.raises(MultipleInvalid),
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_async_beolink_join_invalid(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_mozart_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
service_parameters: dict[str, str],
|
||||
expected_result: AbstractContextManager,
|
||||
) -> None:
|
||||
"""Test invalid async_beolink_join calls with defined JID or source ID."""
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
||||
with expected_result:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
"beolink_join",
|
||||
{ATTR_ENTITY_ID: TEST_MEDIA_PLAYER_ENTITY_ID, **service_parameters},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_mozart_client.join_beolink_peer.assert_not_called()
|
||||
|
||||
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
||||
assert states == snapshot(exclude=props("media_position_updated_at"))
|
||||
|
Loading…
x
Reference in New Issue
Block a user