Add number entity to Russound RIO (#147228)

* Add number entity to Russound RIO

* Fixes

* Fix tests

* Change entity name
This commit is contained in:
Noah Husby 2025-06-22 18:02:16 -04:00 committed by GitHub
parent b47706f360
commit a7290f92cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1123 additions and 7 deletions

View File

@ -14,7 +14,7 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from .const import DOMAIN, RUSSOUND_RIO_EXCEPTIONS
PLATFORMS = [Platform.MEDIA_PLAYER]
PLATFORMS = [Platform.MEDIA_PLAYER, Platform.NUMBER]
_LOGGER = logging.getLogger(__name__)

View File

@ -6,6 +6,7 @@ from typing import Any, Concatenate
from aiorussound import Controller, RussoundClient
from aiorussound.models import CallbackType
from aiorussound.rio import ZoneControlSurface
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
@ -58,6 +59,7 @@ class RussoundBaseEntity(Entity):
self._controller.mac_address
or f"{self._primary_mac_address}-{self._controller.controller_id}"
)
self._zone_id = zone_id
if not zone_id:
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._device_identifier)},
@ -74,6 +76,11 @@ class RussoundBaseEntity(Entity):
via_device=(DOMAIN, self._device_identifier),
)
@property
def _zone(self) -> ZoneControlSurface:
assert self._zone_id
return self._controller.zones[self._zone_id]
async def _state_update_callback(
self, _client: RussoundClient, _callback_type: CallbackType
) -> None:

View File

@ -9,7 +9,6 @@ from typing import TYPE_CHECKING
from aiorussound import Controller
from aiorussound.const import FeatureFlag
from aiorussound.models import PlayStatus, Source
from aiorussound.rio import ZoneControlSurface
from aiorussound.util import is_feature_supported
from homeassistant.components.media_player import (
@ -67,15 +66,10 @@ class RussoundZoneDevice(RussoundBaseEntity, MediaPlayerEntity):
) -> None:
"""Initialize the zone device."""
super().__init__(controller, zone_id)
self._zone_id = zone_id
_zone = self._zone
self._sources = sources
self._attr_unique_id = f"{self._primary_mac_address}-{_zone.device_str}"
@property
def _zone(self) -> ZoneControlSurface:
return self._controller.zones[self._zone_id]
@property
def _source(self) -> Source:
return self._zone.fetch_current_source()

View File

@ -0,0 +1,112 @@
"""Support for Russound number entities."""
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from aiorussound.rio import Controller, ZoneControlSurface
from homeassistant.components.number import NumberEntity, NumberEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import RussoundConfigEntry
from .entity import RussoundBaseEntity, command
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class RussoundZoneNumberEntityDescription(NumberEntityDescription):
"""Describes Russound number entities."""
value_fn: Callable[[ZoneControlSurface], float]
set_value_fn: Callable[[ZoneControlSurface, float], Awaitable[None]]
CONTROL_ENTITIES: tuple[RussoundZoneNumberEntityDescription, ...] = (
RussoundZoneNumberEntityDescription(
key="balance",
translation_key="balance",
native_min_value=-10,
native_max_value=10,
native_step=1,
entity_category=EntityCategory.CONFIG,
value_fn=lambda zone: zone.balance,
set_value_fn=lambda zone, value: zone.set_balance(int(value)),
),
RussoundZoneNumberEntityDescription(
key="bass",
translation_key="bass",
native_min_value=-10,
native_max_value=10,
native_step=1,
entity_category=EntityCategory.CONFIG,
value_fn=lambda zone: zone.bass,
set_value_fn=lambda zone, value: zone.set_bass(int(value)),
),
RussoundZoneNumberEntityDescription(
key="treble",
translation_key="treble",
native_min_value=-10,
native_max_value=10,
native_step=1,
entity_category=EntityCategory.CONFIG,
value_fn=lambda zone: zone.treble,
set_value_fn=lambda zone, value: zone.set_treble(int(value)),
),
RussoundZoneNumberEntityDescription(
key="turn_on_volume",
translation_key="turn_on_volume",
native_min_value=0,
native_max_value=100,
native_step=2,
entity_category=EntityCategory.CONFIG,
value_fn=lambda zone: zone.turn_on_volume * 2,
set_value_fn=lambda zone, value: zone.set_turn_on_volume(int(value / 2)),
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: RussoundConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Russound number entities based on a config entry."""
client = entry.runtime_data
async_add_entities(
RussoundNumberEntity(controller, zone_id, description)
for controller in client.controllers.values()
for zone_id in controller.zones
for description in CONTROL_ENTITIES
)
class RussoundNumberEntity(RussoundBaseEntity, NumberEntity):
"""Defines a Russound number entity."""
entity_description: RussoundZoneNumberEntityDescription
def __init__(
self,
controller: Controller,
zone_id: int,
description: RussoundZoneNumberEntityDescription,
) -> None:
"""Initialize a Russound number entity."""
super().__init__(controller, zone_id)
self.entity_description = description
self._attr_unique_id = (
f"{self._primary_mac_address}-{self._zone.device_str}-{description.key}"
)
@property
def native_value(self) -> float:
"""Return the native value of the entity."""
return float(self.entity_description.value_fn(self._zone))
@command
async def async_set_native_value(self, value: float) -> None:
"""Set the value."""
await self.entity_description.set_value_fn(self._zone, value)

View File

@ -40,6 +40,22 @@
"wrong_device": "This Russound controller does not match the existing device ID. Please make sure you entered the correct IP address."
}
},
"entity": {
"number": {
"balance": {
"name": "Balance"
},
"bass": {
"name": "Bass"
},
"treble": {
"name": "Treble"
},
"turn_on_volume": {
"name": "Turn-on volume"
}
}
},
"exceptions": {
"entry_cannot_connect": {
"message": "Error while connecting to {host}:{port}"

View File

@ -79,6 +79,10 @@ def mock_russound_client() -> Generator[AsyncMock]:
zone.unmute = AsyncMock()
zone.toggle_mute = AsyncMock()
zone.set_seek_time = AsyncMock()
zone.set_balance = AsyncMock()
zone.set_bass = AsyncMock()
zone.set_treble = AsyncMock()
zone.set_turn_on_volume = AsyncMock()
client.controllers = {
1: Controller(

View File

@ -0,0 +1,913 @@
# serializer version: 1
# name: test_all_entities[number.backyard_balance-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.backyard_balance',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Balance',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'balance',
'unique_id': '00:11:22:33:44:55-C[1].Z[1]-balance',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.backyard_balance-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Backyard Balance',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.backyard_balance',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.backyard_bass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.backyard_bass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bass',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bass',
'unique_id': '00:11:22:33:44:55-C[1].Z[1]-bass',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.backyard_bass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Backyard Bass',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.backyard_bass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.backyard_treble-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.backyard_treble',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Treble',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'treble',
'unique_id': '00:11:22:33:44:55-C[1].Z[1]-treble',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.backyard_treble-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Backyard Treble',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.backyard_treble',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.backyard_turn_on_volume-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 2,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.backyard_turn_on_volume',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Turn-on volume',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'turn_on_volume',
'unique_id': '00:11:22:33:44:55-C[1].Z[1]-turn_on_volume',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.backyard_turn_on_volume-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Backyard Turn-on volume',
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 2,
}),
'context': <ANY>,
'entity_id': 'number.backyard_turn_on_volume',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '40.0',
})
# ---
# name: test_all_entities[number.bedroom_balance-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.bedroom_balance',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Balance',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'balance',
'unique_id': '00:11:22:33:44:55-C[1].Z[3]-balance',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.bedroom_balance-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Bedroom Balance',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.bedroom_balance',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.bedroom_bass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.bedroom_bass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bass',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bass',
'unique_id': '00:11:22:33:44:55-C[1].Z[3]-bass',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.bedroom_bass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Bedroom Bass',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.bedroom_bass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.bedroom_treble-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.bedroom_treble',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Treble',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'treble',
'unique_id': '00:11:22:33:44:55-C[1].Z[3]-treble',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.bedroom_treble-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Bedroom Treble',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.bedroom_treble',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.bedroom_turn_on_volume-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 2,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.bedroom_turn_on_volume',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Turn-on volume',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'turn_on_volume',
'unique_id': '00:11:22:33:44:55-C[1].Z[3]-turn_on_volume',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.bedroom_turn_on_volume-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Bedroom Turn-on volume',
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 2,
}),
'context': <ANY>,
'entity_id': 'number.bedroom_turn_on_volume',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '40.0',
})
# ---
# name: test_all_entities[number.kitchen_balance-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.kitchen_balance',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Balance',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'balance',
'unique_id': '00:11:22:33:44:55-C[1].Z[2]-balance',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.kitchen_balance-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Balance',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.kitchen_balance',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.kitchen_bass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.kitchen_bass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bass',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bass',
'unique_id': '00:11:22:33:44:55-C[1].Z[2]-bass',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.kitchen_bass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Bass',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.kitchen_bass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.kitchen_treble-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.kitchen_treble',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Treble',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'treble',
'unique_id': '00:11:22:33:44:55-C[1].Z[2]-treble',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.kitchen_treble-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Treble',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.kitchen_treble',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.kitchen_turn_on_volume-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 2,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.kitchen_turn_on_volume',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Turn-on volume',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'turn_on_volume',
'unique_id': '00:11:22:33:44:55-C[1].Z[2]-turn_on_volume',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.kitchen_turn_on_volume-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Kitchen Turn-on volume',
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 2,
}),
'context': <ANY>,
'entity_id': 'number.kitchen_turn_on_volume',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '40.0',
})
# ---
# name: test_all_entities[number.living_room_balance-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.living_room_balance',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Balance',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'balance',
'unique_id': '00:11:22:33:44:55-C[2].Z[9]-balance',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.living_room_balance-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Living Room Balance',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.living_room_balance',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.living_room_bass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.living_room_bass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bass',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bass',
'unique_id': '00:11:22:33:44:55-C[2].Z[9]-bass',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.living_room_bass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Living Room Bass',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.living_room_bass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.living_room_treble-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.living_room_treble',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Treble',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'treble',
'unique_id': '00:11:22:33:44:55-C[2].Z[9]-treble',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.living_room_treble-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Living Room Treble',
'max': 10,
'min': -10,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 1,
}),
'context': <ANY>,
'entity_id': 'number.living_room_treble',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0',
})
# ---
# name: test_all_entities[number.living_room_turn_on_volume-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 2,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'number',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'number.living_room_turn_on_volume',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Turn-on volume',
'platform': 'russound_rio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'turn_on_volume',
'unique_id': '00:11:22:33:44:55-C[2].Z[9]-turn_on_volume',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[number.living_room_turn_on_volume-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Living Room Turn-on volume',
'max': 100,
'min': 0,
'mode': <NumberMode.AUTO: 'auto'>,
'step': 2,
}),
'context': <ANY>,
'entity_id': 'number.living_room_turn_on_volume',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '40.0',
})
# ---

View File

@ -0,0 +1,70 @@
"""Tests for the Russound RIO number platform."""
from unittest.mock import AsyncMock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.number import (
ATTR_VALUE,
DOMAIN as NUMBER_DOMAIN,
SERVICE_SET_VALUE,
)
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from .const import NAME_ZONE_1
from tests.common import MockConfigEntry, snapshot_platform
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_all_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_russound_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test all entities."""
with patch("homeassistant.components.russound_rio.PLATFORMS", [Platform.NUMBER]):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
("entity_suffix", "value", "expected_method", "expected_arg"),
[
("bass", -5, "set_bass", -5),
("balance", 3, "set_balance", 3),
("treble", 7, "set_treble", 7),
("turn_on_volume", 60, "set_turn_on_volume", 30),
],
)
async def test_setting_number_value(
hass: HomeAssistant,
mock_russound_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_suffix: str,
value: int,
expected_method: str,
expected_arg: int,
) -> None:
"""Test setting value on Russound number entity."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: f"{NUMBER_DOMAIN}.{NAME_ZONE_1}_{entity_suffix}",
ATTR_VALUE: value,
},
blocking=True,
)
zone = mock_russound_client.controllers[1].zones[1]
getattr(zone, expected_method).assert_called_once_with(expected_arg)