Add sub-device support to Russound RIO (#146763)

This commit is contained in:
Noah Husby 2025-06-20 08:52:34 -04:00 committed by GitHub
parent e28965770e
commit 1b73acc025
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 52 additions and 25 deletions

View File

@ -9,6 +9,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from .const import DOMAIN, RUSSOUND_RIO_EXCEPTIONS
@ -52,6 +54,39 @@ async def async_setup_entry(hass: HomeAssistant, entry: RussoundConfigEntry) ->
) from err
entry.runtime_data = client
device_registry = dr.async_get(hass)
for controller_id, controller in client.controllers.items():
_device_identifier = (
controller.mac_address
or f"{client.controllers[1].mac_address}-{controller_id}"
)
connections = None
via_device = None
configuration_url = None
if controller_id != 1:
assert client.controllers[1].mac_address
via_device = (
DOMAIN,
client.controllers[1].mac_address,
)
else:
assert controller.mac_address
connections = {(CONNECTION_NETWORK_MAC, controller.mac_address)}
if isinstance(client.connection_handler, RussoundTcpConnectionHandler):
configuration_url = f"http://{client.connection_handler.host}"
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, _device_identifier)},
manufacturer="Russound",
name=controller.controller_type,
model=controller.controller_type,
sw_version=controller.firmware_version,
connections=connections,
via_device=via_device,
configuration_url=configuration_url,
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True

View File

@ -4,11 +4,11 @@ from collections.abc import Awaitable, Callable, Coroutine
from functools import wraps
from typing import Any, Concatenate
from aiorussound import Controller, RussoundClient, RussoundTcpConnectionHandler
from aiorussound import Controller, RussoundClient
from aiorussound.models import CallbackType
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity
from .const import DOMAIN, RUSSOUND_RIO_EXCEPTIONS
@ -46,6 +46,7 @@ class RussoundBaseEntity(Entity):
def __init__(
self,
controller: Controller,
zone_id: int | None = None,
) -> None:
"""Initialize the entity."""
self._client = controller.client
@ -57,29 +58,21 @@ class RussoundBaseEntity(Entity):
self._controller.mac_address
or f"{self._primary_mac_address}-{self._controller.controller_id}"
)
if not zone_id:
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._device_identifier)},
)
return
zone = controller.zones[zone_id]
self._attr_device_info = DeviceInfo(
# Use MAC address of Russound device as identifier
identifiers={(DOMAIN, self._device_identifier)},
identifiers={(DOMAIN, f"{self._device_identifier}-{zone_id}")},
name=zone.name,
manufacturer="Russound",
name=controller.controller_type,
model=controller.controller_type,
sw_version=controller.firmware_version,
suggested_area=zone.name,
via_device=(DOMAIN, self._device_identifier),
)
if isinstance(self._client.connection_handler, RussoundTcpConnectionHandler):
self._attr_device_info["configuration_url"] = (
f"http://{self._client.connection_handler.host}"
)
if controller.controller_id != 1:
assert self._client.controllers[1].mac_address
self._attr_device_info["via_device"] = (
DOMAIN,
self._client.controllers[1].mac_address,
)
else:
assert controller.mac_address
self._attr_device_info["connections"] = {
(CONNECTION_NETWORK_MAC, controller.mac_address)
}
async def _state_update_callback(
self, _client: RussoundClient, _callback_type: CallbackType

View File

@ -60,16 +60,16 @@ class RussoundZoneDevice(RussoundBaseEntity, MediaPlayerEntity):
| MediaPlayerEntityFeature.SELECT_SOURCE
| MediaPlayerEntityFeature.SEEK
)
_attr_name = None
def __init__(
self, controller: Controller, zone_id: int, sources: dict[int, Source]
) -> None:
"""Initialize the zone device."""
super().__init__(controller)
super().__init__(controller, zone_id)
self._zone_id = zone_id
_zone = self._zone
self._sources = sources
self._attr_name = _zone.name
self._attr_unique_id = f"{self._primary_mac_address}-{_zone.device_str}"
@property

View File

@ -17,6 +17,5 @@ MOCK_RECONFIGURATION_CONFIG = {
CONF_PORT: 9622,
}
DEVICE_NAME = "mca_c5"
NAME_ZONE_1 = "backyard"
ENTITY_ID_ZONE_1 = f"{MP_DOMAIN}.{DEVICE_NAME}_{NAME_ZONE_1}"
ENTITY_ID_ZONE_1 = f"{MP_DOMAIN}.{NAME_ZONE_1}"

View File

@ -207,7 +207,7 @@ async def test_invalid_source_service(
with pytest.raises(
HomeAssistantError,
match="Error executing async_select_source on entity media_player.mca_c5_backyard",
match="Error executing async_select_source on entity media_player.backyard",
):
await hass.services.async_call(
MP_DOMAIN,