Add select entity to Cambridge Audio (#128527)

* Add select entity to Cambridge Audio

* Add select entity to Cambridge Audio

* Update test name
This commit is contained in:
Noah Husby 2024-10-16 13:57:10 -04:00 committed by GitHub
parent 59e5eb9a1c
commit af41a41046
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 219 additions and 2 deletions

View file

@ -15,7 +15,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from .const import CONNECT_TIMEOUT, STREAM_MAGIC_EXCEPTIONS from .const import CONNECT_TIMEOUT, STREAM_MAGIC_EXCEPTIONS
PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER] PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER, Platform.SELECT]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View file

@ -0,0 +1,14 @@
{
"entity": {
"select": {
"display_brightness": {
"default": "mdi:brightness-7",
"state": {
"bright": "mdi:brightness-7",
"dim": "mdi:brightness-6",
"off": "mdi:brightness-3"
}
}
}
}
}

View file

@ -0,0 +1,76 @@
"""Support for Cambridge Audio select entities."""
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from aiostreammagic import StreamMagicClient
from aiostreammagic.models import DisplayBrightness
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .entity import CambridgeAudioEntity
@dataclass(frozen=True, kw_only=True)
class CambridgeAudioSelectEntityDescription(SelectEntityDescription):
"""Describes Cambridge Audio select entity."""
value_fn: Callable[[StreamMagicClient], str | None]
set_value_fn: Callable[[StreamMagicClient, str], Awaitable[None]]
CONTROL_ENTITIES: tuple[CambridgeAudioSelectEntityDescription, ...] = (
CambridgeAudioSelectEntityDescription(
key="display_brightness",
translation_key="display_brightness",
options=[x.value for x in DisplayBrightness],
entity_category=EntityCategory.CONFIG,
value_fn=lambda client: client.display.brightness,
set_value_fn=lambda client, value: client.set_display_brightness(
DisplayBrightness(value)
),
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Cambridge Audio select entities based on a config entry."""
client: StreamMagicClient = entry.runtime_data
entities: list[CambridgeAudioSelect] = [
CambridgeAudioSelect(client, description) for description in CONTROL_ENTITIES
]
async_add_entities(entities)
class CambridgeAudioSelect(CambridgeAudioEntity, SelectEntity):
"""Defines a Cambridge Audio select entity."""
entity_description: CambridgeAudioSelectEntityDescription
def __init__(
self,
client: StreamMagicClient,
description: CambridgeAudioSelectEntityDescription,
) -> None:
"""Initialize Cambridge Audio select."""
super().__init__(client)
self.entity_description = description
self._attr_unique_id = f"{client.info.unit_id}-{description.key}"
@property
def current_option(self) -> str | None:
"""Return the state of the select."""
return self.entity_description.value_fn(self.client)
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
await self.entity_description.set_value_fn(self.client, option)

View file

@ -22,5 +22,17 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
} }
},
"entity": {
"select": {
"display_brightness": {
"name": "Display brightness",
"state": {
"bright": "Bright",
"dim": "Dim",
"off": "Off"
}
}
}
} }
} }

View file

@ -3,7 +3,7 @@
from collections.abc import Generator from collections.abc import Generator
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from aiostreammagic.models import Info, NowPlaying, PlayState, Source, State from aiostreammagic.models import Display, Info, NowPlaying, PlayState, Source, State
import pytest import pytest
from homeassistant.components.cambridge_audio.const import DOMAIN from homeassistant.components.cambridge_audio.const import DOMAIN
@ -50,6 +50,7 @@ def mock_stream_magic_client() -> Generator[AsyncMock]:
client.now_playing = NowPlaying.from_json( client.now_playing = NowPlaying.from_json(
load_fixture("get_now_playing.json", DOMAIN) load_fixture("get_now_playing.json", DOMAIN)
) )
client.display = Display.from_json(load_fixture("get_display.json", DOMAIN))
client.is_connected = Mock(return_value=True) client.is_connected = Mock(return_value=True)
client.position_last_updated = client.play_state.position client.position_last_updated = client.play_state.position
client.unregister_state_update_callbacks = AsyncMock(return_value=True) client.unregister_state_update_callbacks = AsyncMock(return_value=True)

View file

@ -0,0 +1,3 @@
{
"brightness": "bright"
}

View file

@ -0,0 +1,58 @@
# serializer version: 1
# name: test_all_entities[select.cambridge_audio_cxnv2_display_brightness-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'bright',
'dim',
'off',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.cambridge_audio_cxnv2_display_brightness',
'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': 'Display brightness',
'platform': 'cambridge_audio',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'display_brightness',
'unique_id': '0020c2d8-display_brightness',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[select.cambridge_audio_cxnv2_display_brightness-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Cambridge Audio CXNv2 Display brightness',
'options': list([
'bright',
'dim',
'off',
]),
}),
'context': <ANY>,
'entity_id': 'select.cambridge_audio_cxnv2_display_brightness',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'bright',
})
# ---

View file

@ -0,0 +1,53 @@
"""Tests for the Cambridge Audio select platform."""
from unittest.mock import AsyncMock, patch
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.select import (
DOMAIN as SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
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_stream_magic_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test all entities."""
with patch("homeassistant.components.cambridge_audio.PLATFORMS", [Platform.SELECT]):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_setting_value(
hass: HomeAssistant,
mock_stream_magic_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test setting value."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.cambridge_audio_cxnv2_display_brightness",
ATTR_OPTION: "dim",
},
blocking=True,
)
mock_stream_magic_client.set_display_brightness.assert_called_once_with("dim")