Skip to content

Commit

Permalink
LinkPlay multiroom support (home-assistant#127862)
Browse files Browse the repository at this point in the history
  • Loading branch information
silamon authored Oct 25, 2024
1 parent 66ca424 commit 3734fa9
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 4 deletions.
17 changes: 16 additions & 1 deletion homeassistant/components/linkplay/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from aiohttp import ClientSession
from linkplay.bridge import LinkPlayBridge
from linkplay.controller import LinkPlayController
from linkplay.discovery import linkplay_factory_httpapi_bridge
from linkplay.exceptions import LinkPlayRequestException

Expand All @@ -12,7 +13,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady

from .const import PLATFORMS
from .const import CONTROLLER, CONTROLLER_KEY, DOMAIN, PLATFORMS
from .utils import async_get_client_session


Expand All @@ -32,13 +33,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: LinkPlayConfigEntry) ->
session: ClientSession = await async_get_client_session(hass)
bridge: LinkPlayBridge | None = None

# try create a bridge
try:
bridge = await linkplay_factory_httpapi_bridge(entry.data[CONF_HOST], session)
except LinkPlayRequestException as exception:
raise ConfigEntryNotReady(
f"Failed to connect to LinkPlay device at {entry.data[CONF_HOST]}"
) 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)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/linkplay/const.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"""LinkPlay constants."""

from linkplay.controller import LinkPlayController

from homeassistant.const import Platform
from homeassistant.util.hass_dict import HassKey

DOMAIN = "linkplay"
CONTROLLER = "controller"
CONTROLLER_KEY: HassKey[LinkPlayController] = HassKey(CONTROLLER)
PLATFORMS = [Platform.MEDIA_PLAYER]
DATA_SESSION = "session"
76 changes: 73 additions & 3 deletions homeassistant/components/linkplay/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from linkplay.bridge import LinkPlayBridge
from linkplay.consts import EqualizerMode, LoopMode, PlayingMode, PlayingStatus
from linkplay.controller import LinkPlayController, LinkPlayMultiroom
from linkplay.exceptions import LinkPlayException, LinkPlayRequestException
import voluptuous as vol

Expand All @@ -22,18 +23,20 @@
RepeatMode,
async_process_play_media_url,
)
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
entity_platform,
entity_registry as er,
)
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.dt import utcnow

from . import LinkPlayConfigEntry
from .const import DOMAIN
from . import LinkPlayConfigEntry, LinkPlayData
from .const import CONTROLLER_KEY, DOMAIN
from .utils import MANUFACTURER_GENERIC, get_info_from_project

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -290,6 +293,73 @@ async def async_play_preset(self, preset_number: int) -> None:
"""Play 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:
"""Update the properties of the media player."""
self._attr_available = True
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/linkplay/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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?"
}
}
}

0 comments on commit 3734fa9

Please sign in to comment.