Add switch entity to Cambridge Audio (#128530)

This commit is contained in:
Noah Husby 2024-10-25 05:22:50 -04:00 committed by GitHub
parent 7f9e5e29a8
commit c9d0bfce54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 263 additions and 2 deletions

View file

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

View file

@ -9,6 +9,17 @@
"off": "mdi:brightness-3"
}
}
},
"switch": {
"pre_amp": {
"default": "mdi:volume-high",
"state": {
"off": "mdi:volume-low"
}
},
"early_update": {
"default": "mdi:update"
}
}
}
}

View file

@ -30,9 +30,17 @@
"state": {
"bright": "Bright",
"dim": "Dim",
"off": "Off"
"off": "[%key:common::state::off%]"
}
}
},
"switch": {
"pre_amp": {
"name": "Pre-Amp"
},
"early_update": {
"name": "Early update"
}
}
},
"exceptions": {

View file

@ -0,0 +1,82 @@
"""Support for Cambridge Audio switch entities."""
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any
from aiostreammagic import StreamMagicClient
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
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 CambridgeAudioSwitchEntityDescription(SwitchEntityDescription):
"""Describes Cambridge Audio switch entity."""
value_fn: Callable[[StreamMagicClient], bool]
set_value_fn: Callable[[StreamMagicClient, bool], Awaitable[None]]
CONTROL_ENTITIES: tuple[CambridgeAudioSwitchEntityDescription, ...] = (
CambridgeAudioSwitchEntityDescription(
key="pre_amp",
translation_key="pre_amp",
entity_category=EntityCategory.CONFIG,
value_fn=lambda client: client.state.pre_amp_mode,
set_value_fn=lambda client, value: client.set_pre_amp_mode(value),
),
CambridgeAudioSwitchEntityDescription(
key="early_update",
translation_key="early_update",
entity_category=EntityCategory.CONFIG,
value_fn=lambda client: client.update.early_update,
set_value_fn=lambda client, value: client.set_early_update(value),
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Cambridge Audio switch entities based on a config entry."""
async_add_entities(
CambridgeAudioSwitch(entry.runtime_data, description)
for description in CONTROL_ENTITIES
)
class CambridgeAudioSwitch(CambridgeAudioEntity, SwitchEntity):
"""Defines a Cambridge Audio switch entity."""
entity_description: CambridgeAudioSwitchEntityDescription
def __init__(
self,
client: StreamMagicClient,
description: CambridgeAudioSwitchEntityDescription,
) -> None:
"""Initialize Cambridge Audio switch."""
super().__init__(client)
self.entity_description = description
self._attr_unique_id = f"{client.info.unit_id}-{description.key}"
@property
def is_on(self) -> bool:
"""Return the state of the switch."""
return self.entity_description.value_fn(self.client)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self.entity_description.set_value_fn(self.client, True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self.entity_description.set_value_fn(self.client, False)

View file

@ -11,6 +11,7 @@ from aiostreammagic.models import (
PresetList,
Source,
State,
Update,
)
import pytest
@ -59,6 +60,7 @@ def mock_stream_magic_client() -> Generator[AsyncMock]:
load_fixture("get_now_playing.json", DOMAIN)
)
client.display = Display.from_json(load_fixture("get_display.json", DOMAIN))
client.update = Update.from_json(load_fixture("get_update.json", DOMAIN))
client.preset_list = PresetList.from_json(
load_fixture("get_presets_list.json", DOMAIN)
)

View file

@ -0,0 +1,5 @@
{
"early_update": false,
"update_available": false,
"updating": false
}

View file

@ -0,0 +1,93 @@
# serializer version: 1
# name: test_all_entities[switch.cambridge_audio_cxnv2_early_update-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.cambridge_audio_cxnv2_early_update',
'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': 'Early update',
'platform': 'cambridge_audio',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'early_update',
'unique_id': '0020c2d8-early_update',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[switch.cambridge_audio_cxnv2_early_update-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Cambridge Audio CXNv2 Early update',
}),
'context': <ANY>,
'entity_id': 'switch.cambridge_audio_cxnv2_early_update',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_entities[switch.cambridge_audio_cxnv2_pre_amp-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.cambridge_audio_cxnv2_pre_amp',
'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': 'Pre-Amp',
'platform': 'cambridge_audio',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'pre_amp',
'unique_id': '0020c2d8-pre_amp',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[switch.cambridge_audio_cxnv2_pre_amp-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Cambridge Audio CXNv2 Pre-Amp',
}),
'context': <ANY>,
'entity_id': 'switch.cambridge_audio_cxnv2_pre_amp',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View file

@ -0,0 +1,60 @@
"""Tests for the Cambridge Audio switch platform."""
from unittest.mock import AsyncMock, patch
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SERVICE_TURN_ON
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, 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.SWITCH]):
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(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: "switch.cambridge_audio_cxnv2_early_update",
},
blocking=True,
)
mock_stream_magic_client.set_early_update.assert_called_once_with(True)
mock_stream_magic_client.set_early_update.reset_mock()
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{
ATTR_ENTITY_ID: "switch.cambridge_audio_cxnv2_early_update",
},
blocking=True,
)
mock_stream_magic_client.set_early_update.assert_called_once_with(False)