From a44bf164e5a3d43094a32b9ab37862755c6057d3 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 30 Sep 2024 12:55:09 +0200 Subject: [PATCH] Add select volume to yale_smart_alarm (#127005) --- .../components/yale_smart_alarm/const.py | 1 + .../components/yale_smart_alarm/icons.json | 10 + .../components/yale_smart_alarm/select.py | 58 +++ .../components/yale_smart_alarm/strings.json | 10 + .../snapshots/test_select.ambr | 343 ++++++++++++++++++ .../yale_smart_alarm/test_select.py | 66 ++++ 6 files changed, 488 insertions(+) create mode 100644 homeassistant/components/yale_smart_alarm/select.py create mode 100644 tests/components/yale_smart_alarm/snapshots/test_select.ambr create mode 100644 tests/components/yale_smart_alarm/test_select.py diff --git a/homeassistant/components/yale_smart_alarm/const.py b/homeassistant/components/yale_smart_alarm/const.py index 4166d0085d5..41a754e4ce7 100644 --- a/homeassistant/components/yale_smart_alarm/const.py +++ b/homeassistant/components/yale_smart_alarm/const.py @@ -39,6 +39,7 @@ PLATFORMS = [ Platform.BINARY_SENSOR, Platform.BUTTON, Platform.LOCK, + Platform.SELECT, Platform.SENSOR, Platform.SWITCH, ] diff --git a/homeassistant/components/yale_smart_alarm/icons.json b/homeassistant/components/yale_smart_alarm/icons.json index 4cb5888a406..fb83ea88f97 100644 --- a/homeassistant/components/yale_smart_alarm/icons.json +++ b/homeassistant/components/yale_smart_alarm/icons.json @@ -4,6 +4,16 @@ "panic": { "default": "mdi:alarm-light" } + }, + "select": { + "volume": { + "default": "mdi:volume-high", + "state": { + "high": "mdi:volume-high", + "low": "mdi:volume-low", + "off": "mdi:volume-off" + } + } } } } diff --git a/homeassistant/components/yale_smart_alarm/select.py b/homeassistant/components/yale_smart_alarm/select.py new file mode 100644 index 00000000000..11b5a47eb89 --- /dev/null +++ b/homeassistant/components/yale_smart_alarm/select.py @@ -0,0 +1,58 @@ +"""Select for Yale Alarm.""" + +from __future__ import annotations + +from yalesmartalarmclient import YaleLock, YaleLockVolume + +from homeassistant.components.select import SelectEntity +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import YaleConfigEntry +from .coordinator import YaleDataUpdateCoordinator +from .entity import YaleLockEntity + +VOLUME_OPTIONS = {value.name.lower(): str(value.value) for value in YaleLockVolume} + + +async def async_setup_entry( + hass: HomeAssistant, entry: YaleConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the Yale switch entry.""" + + coordinator = entry.runtime_data + + async_add_entities( + YaleAutolockSwitch(coordinator, lock) + for lock in coordinator.locks + if lock.supports_lock_config() + ) + + +class YaleAutolockSwitch(YaleLockEntity, SelectEntity): + """Representation of a Yale autolock switch.""" + + _attr_translation_key = "volume" + + def __init__(self, coordinator: YaleDataUpdateCoordinator, lock: YaleLock) -> None: + """Initialize the Yale Autolock Switch.""" + super().__init__(coordinator, lock) + self._attr_unique_id = f"{lock.sid()}-volume" + self._attr_current_option = self.lock_data.volume().name.lower() + self._attr_options = [volume.name.lower() for volume in YaleLockVolume] + + async def async_select_option(self, option: str) -> None: + """Change the selected option.""" + convert_to_value = VOLUME_OPTIONS[option] + option_enum = YaleLockVolume(convert_to_value) + if await self.hass.async_add_executor_job( + self.lock_data.set_volume, option_enum + ): + self._attr_current_option = self.lock_data.volume().name.lower() + self.async_write_ha_state() + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._attr_current_option = self.lock_data.volume().name.lower() + super()._handle_coordinator_update() diff --git a/homeassistant/components/yale_smart_alarm/strings.json b/homeassistant/components/yale_smart_alarm/strings.json index abaa6996bbe..8bade77f5f6 100644 --- a/homeassistant/components/yale_smart_alarm/strings.json +++ b/homeassistant/components/yale_smart_alarm/strings.json @@ -60,6 +60,16 @@ "autolock": { "name": "Autolock" } + }, + "select": { + "volume": { + "name": "Volume", + "state": { + "high": "High", + "low": "Low", + "off": "[%key:common::state::off%]" + } + } } }, "exceptions": { diff --git a/tests/components/yale_smart_alarm/snapshots/test_select.ambr b/tests/components/yale_smart_alarm/snapshots/test_select.ambr new file mode 100644 index 00000000000..52ec7a99c2c --- /dev/null +++ b/tests/components/yale_smart_alarm/snapshots/test_select.ambr @@ -0,0 +1,343 @@ +# serializer version: 1 +# name: test_switch[load_platforms0][select.device1_volume-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': None, + 'entity_id': 'select.device1_volume', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Volume', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'volume', + 'unique_id': '1111-volume', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][select.device1_volume-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device1 Volume', + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'context': , + 'entity_id': 'select.device1_volume', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'low', + }) +# --- +# name: test_switch[load_platforms0][select.device2_volume-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': None, + 'entity_id': 'select.device2_volume', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Volume', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'volume', + 'unique_id': '2222-volume', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][select.device2_volume-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device2 Volume', + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'context': , + 'entity_id': 'select.device2_volume', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'low', + }) +# --- +# name: test_switch[load_platforms0][select.device3_volume-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': None, + 'entity_id': 'select.device3_volume', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Volume', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'volume', + 'unique_id': '3333-volume', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][select.device3_volume-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device3 Volume', + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'context': , + 'entity_id': 'select.device3_volume', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'low', + }) +# --- +# name: test_switch[load_platforms0][select.device7_volume-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': None, + 'entity_id': 'select.device7_volume', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Volume', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'volume', + 'unique_id': '7777-volume', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][select.device7_volume-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device7 Volume', + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'context': , + 'entity_id': 'select.device7_volume', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'low', + }) +# --- +# name: test_switch[load_platforms0][select.device8_volume-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': None, + 'entity_id': 'select.device8_volume', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Volume', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'volume', + 'unique_id': '8888-volume', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][select.device8_volume-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device8 Volume', + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'context': , + 'entity_id': 'select.device8_volume', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'low', + }) +# --- +# name: test_switch[load_platforms0][select.device9_volume-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': None, + 'entity_id': 'select.device9_volume', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Volume', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'volume', + 'unique_id': '9999-volume', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][select.device9_volume-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device9 Volume', + 'options': list([ + 'high', + 'low', + 'off', + ]), + }), + 'context': , + 'entity_id': 'select.device9_volume', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'low', + }) +# --- diff --git a/tests/components/yale_smart_alarm/test_select.py b/tests/components/yale_smart_alarm/test_select.py new file mode 100644 index 00000000000..c874f83aed7 --- /dev/null +++ b/tests/components/yale_smart_alarm/test_select.py @@ -0,0 +1,66 @@ +"""The test for the Yale smart living select.""" + +from __future__ import annotations + +from unittest.mock import Mock + +import pytest +from syrupy.assertion import SnapshotAssertion +from yalesmartalarmclient import YaleSmartAlarmData + +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.exceptions import ServiceValidationError +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.mark.parametrize( + "load_platforms", + [[Platform.SELECT]], +) +async def test_switch( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + load_config_entry: tuple[MockConfigEntry, Mock], + get_data: YaleSmartAlarmData, + snapshot: SnapshotAssertion, +) -> None: + """Test the Yale Smart Living volume select.""" + client = load_config_entry[1] + + await snapshot_platform( + hass, entity_registry, snapshot, load_config_entry[0].entry_id + ) + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.device1_volume", + ATTR_OPTION: "high", + }, + blocking=True, + ) + + client.auth.post_authenticated.assert_called_once() + client.auth.put_authenticated.assert_called_once() + + state = hass.states.get("select.device1_volume") + assert state.state == "high" + + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.device1_volume", + ATTR_OPTION: "not_exist", + }, + blocking=True, + )