LinkPlay multiroom support (#127862)

This commit is contained in:
Simon Lamon 2024-10-25 20:12:42 +02:00 committed by GitHub
parent 66ca424d3a
commit 3734fa948f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 99 additions and 4 deletions

View File

@ -4,6 +4,7 @@ from dataclasses import dataclass
from aiohttp import ClientSession from aiohttp import ClientSession
from linkplay.bridge import LinkPlayBridge from linkplay.bridge import LinkPlayBridge
from linkplay.controller import LinkPlayController
from linkplay.discovery import linkplay_factory_httpapi_bridge from linkplay.discovery import linkplay_factory_httpapi_bridge
from linkplay.exceptions import LinkPlayRequestException from linkplay.exceptions import LinkPlayRequestException
@ -12,7 +13,7 @@ from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from .const import PLATFORMS from .const import CONTROLLER, CONTROLLER_KEY, DOMAIN, PLATFORMS
from .utils import async_get_client_session from .utils import async_get_client_session
@ -32,6 +33,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LinkPlayConfigEntry) ->
session: ClientSession = await async_get_client_session(hass) session: ClientSession = await async_get_client_session(hass)
bridge: LinkPlayBridge | None = None bridge: LinkPlayBridge | None = None
# try create a bridge
try: try:
bridge = await linkplay_factory_httpapi_bridge(entry.data[CONF_HOST], session) bridge = await linkplay_factory_httpapi_bridge(entry.data[CONF_HOST], session)
except LinkPlayRequestException as exception: except LinkPlayRequestException as exception:
@ -39,6 +41,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: LinkPlayConfigEntry) ->
f"Failed to connect to LinkPlay device at {entry.data[CONF_HOST]}" f"Failed to connect to LinkPlay device at {entry.data[CONF_HOST]}"
) from exception ) from exception
# setup the controller and discover multirooms
controller: LinkPlayController | None = None
hass.data.setdefault(DOMAIN, {})
if CONTROLLER not in hass.data[DOMAIN]:
controller = LinkPlayController(session)
hass.data[DOMAIN][CONTROLLER_KEY] = controller
else:
controller = hass.data[DOMAIN][CONTROLLER_KEY]
await controller.add_bridge(bridge)
await controller.discover_multirooms()
# forward to platforms
entry.runtime_data = LinkPlayData(bridge=bridge) entry.runtime_data = LinkPlayData(bridge=bridge)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

View File

@ -1,7 +1,12 @@
"""LinkPlay constants.""" """LinkPlay constants."""
from linkplay.controller import LinkPlayController
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.util.hass_dict import HassKey
DOMAIN = "linkplay" DOMAIN = "linkplay"
CONTROLLER = "controller"
CONTROLLER_KEY: HassKey[LinkPlayController] = HassKey(CONTROLLER)
PLATFORMS = [Platform.MEDIA_PLAYER] PLATFORMS = [Platform.MEDIA_PLAYER]
DATA_SESSION = "session" DATA_SESSION = "session"

View File

@ -8,6 +8,7 @@ from typing import Any, Concatenate
from linkplay.bridge import LinkPlayBridge from linkplay.bridge import LinkPlayBridge
from linkplay.consts import EqualizerMode, LoopMode, PlayingMode, PlayingStatus from linkplay.consts import EqualizerMode, LoopMode, PlayingMode, PlayingStatus
from linkplay.controller import LinkPlayController, LinkPlayMultiroom
from linkplay.exceptions import LinkPlayException, LinkPlayRequestException from linkplay.exceptions import LinkPlayException, LinkPlayRequestException
import voluptuous as vol import voluptuous as vol
@ -22,18 +23,20 @@ from homeassistant.components.media_player import (
RepeatMode, RepeatMode,
async_process_play_media_url, async_process_play_media_url,
) )
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import ( from homeassistant.helpers import (
config_validation as cv, config_validation as cv,
device_registry as dr, device_registry as dr,
entity_platform, entity_platform,
entity_registry as er,
) )
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
from . import LinkPlayConfigEntry from . import LinkPlayConfigEntry, LinkPlayData
from .const import DOMAIN from .const import CONTROLLER_KEY, DOMAIN
from .utils import MANUFACTURER_GENERIC, get_info_from_project from .utils import MANUFACTURER_GENERIC, get_info_from_project
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -290,6 +293,73 @@ class LinkPlayMediaPlayerEntity(MediaPlayerEntity):
"""Play preset number.""" """Play preset number."""
await self._bridge.player.play_preset(preset_number) await self._bridge.player.play_preset(preset_number)
@exception_wrap
async def async_join_players(self, group_members: list[str]) -> None:
"""Join `group_members` as a player group with the current player."""
controller: LinkPlayController = self.hass.data[DOMAIN][CONTROLLER_KEY]
multiroom = self._bridge.multiroom
if multiroom is None:
multiroom = LinkPlayMultiroom(self._bridge)
for group_member in group_members:
bridge = self._get_linkplay_bridge(group_member)
if bridge:
await multiroom.add_follower(bridge)
await controller.discover_multirooms()
def _get_linkplay_bridge(self, entity_id: str) -> LinkPlayBridge:
"""Get linkplay bridge from entity_id."""
entity_registry = er.async_get(self.hass)
# Check for valid linkplay media_player entity
entity_entry = entity_registry.async_get(entity_id)
if (
entity_entry is None
or entity_entry.domain != Platform.MEDIA_PLAYER
or entity_entry.platform != DOMAIN
or entity_entry.config_entry_id is None
):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_grouping_entity",
translation_placeholders={"entity_id": entity_id},
)
config_entry = self.hass.config_entries.async_get_entry(
entity_entry.config_entry_id
)
assert config_entry
# Return bridge
data: LinkPlayData = config_entry.runtime_data
return data.bridge
@property
def group_members(self) -> list[str]:
"""List of players which are grouped together."""
multiroom = self._bridge.multiroom
if multiroom is not None:
return [multiroom.leader.device.uuid] + [
follower.device.uuid for follower in multiroom.followers
]
return []
@exception_wrap
async def async_unjoin_player(self) -> None:
"""Remove this player from any group."""
controller: LinkPlayController = self.hass.data[DOMAIN][CONTROLLER_KEY]
multiroom = self._bridge.multiroom
if multiroom is not None:
await multiroom.remove_follower(self._bridge)
await controller.discover_multirooms()
def _update_properties(self) -> None: def _update_properties(self) -> None:
"""Update the properties of the media player.""" """Update the properties of the media player."""
self._attr_available = True self._attr_available = True

View File

@ -34,5 +34,10 @@
} }
} }
} }
},
"exceptions": {
"invalid_grouping_entity": {
"message": "Entity with id {entity_id} can't be added to the LinkPlay multiroom. Is the entity a LinkPlay mediaplayer?"
}
} }
} }