From 3137f75221e4d0452ae6ddbd83c60eb78b4d8301 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 22 Sep 2024 16:15:24 +0200 Subject: [PATCH] Add switch to Yale Smart Living (#126366) --- .../components/yale_smart_alarm/const.py | 1 + .../components/yale_smart_alarm/entity.py | 1 + .../components/yale_smart_alarm/lock.py | 1 - .../components/yale_smart_alarm/manifest.json | 2 +- .../components/yale_smart_alarm/strings.json | 5 + .../components/yale_smart_alarm/switch.py | 59 ++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/yale_smart_alarm/conftest.py | 1 + .../snapshots/test_switch.ambr | 277 ++++++++++++++++++ .../yale_smart_alarm/test_switch.py | 46 +++ 11 files changed, 393 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/yale_smart_alarm/switch.py create mode 100644 tests/components/yale_smart_alarm/snapshots/test_switch.ambr create mode 100644 tests/components/yale_smart_alarm/test_switch.py diff --git a/homeassistant/components/yale_smart_alarm/const.py b/homeassistant/components/yale_smart_alarm/const.py index e7b732c6cf9..4166d0085d5 100644 --- a/homeassistant/components/yale_smart_alarm/const.py +++ b/homeassistant/components/yale_smart_alarm/const.py @@ -40,6 +40,7 @@ PLATFORMS = [ Platform.BUTTON, Platform.LOCK, Platform.SENSOR, + Platform.SWITCH, ] STATE_MAP = { diff --git a/homeassistant/components/yale_smart_alarm/entity.py b/homeassistant/components/yale_smart_alarm/entity.py index a0d08d19ba5..e37dc3562f5 100644 --- a/homeassistant/components/yale_smart_alarm/entity.py +++ b/homeassistant/components/yale_smart_alarm/entity.py @@ -45,6 +45,7 @@ class YaleLockEntity(CoordinatorEntity[YaleDataUpdateCoordinator]): identifiers={(DOMAIN, lock.sid())}, via_device=(DOMAIN, coordinator.entry.data[CONF_USERNAME]), ) + self.lock_data = lock class YaleAlarmEntity(CoordinatorEntity[YaleDataUpdateCoordinator], Entity): diff --git a/homeassistant/components/yale_smart_alarm/lock.py b/homeassistant/components/yale_smart_alarm/lock.py index 7374a7c06de..65913dbb3bd 100644 --- a/homeassistant/components/yale_smart_alarm/lock.py +++ b/homeassistant/components/yale_smart_alarm/lock.py @@ -58,7 +58,6 @@ class YaleDoorlock(YaleLockEntity, LockEntity): """Initialize the Yale Lock Device.""" super().__init__(coordinator, lock) self._attr_code_format = rf"^\d{{{code_format}}}$" - self.lock_data = lock async def async_unlock(self, **kwargs: Any) -> None: """Send unlock command.""" diff --git a/homeassistant/components/yale_smart_alarm/manifest.json b/homeassistant/components/yale_smart_alarm/manifest.json index d9e75195db2..9a13cf72db9 100644 --- a/homeassistant/components/yale_smart_alarm/manifest.json +++ b/homeassistant/components/yale_smart_alarm/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/yale_smart_alarm", "iot_class": "cloud_polling", "loggers": ["yalesmartalarmclient"], - "requirements": ["yalesmartalarmclient==0.4.2"] + "requirements": ["yalesmartalarmclient==0.4.3"] } diff --git a/homeassistant/components/yale_smart_alarm/strings.json b/homeassistant/components/yale_smart_alarm/strings.json index 63260c03e7f..abaa6996bbe 100644 --- a/homeassistant/components/yale_smart_alarm/strings.json +++ b/homeassistant/components/yale_smart_alarm/strings.json @@ -55,6 +55,11 @@ "panic": { "name": "Panic button" } + }, + "switch": { + "autolock": { + "name": "Autolock" + } } }, "exceptions": { diff --git a/homeassistant/components/yale_smart_alarm/switch.py b/homeassistant/components/yale_smart_alarm/switch.py new file mode 100644 index 00000000000..e8c0817c2de --- /dev/null +++ b/homeassistant/components/yale_smart_alarm/switch.py @@ -0,0 +1,59 @@ +"""Switches for Yale Alarm.""" + +from __future__ import annotations + +from typing import Any + +from yalesmartalarmclient import YaleLock + +from homeassistant.components.switch import SwitchEntity +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import YaleConfigEntry +from .coordinator import YaleDataUpdateCoordinator +from .entity import YaleLockEntity + + +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, SwitchEntity): + """Representation of a Yale autolock switch.""" + + _attr_translation_key = "autolock" + + def __init__(self, coordinator: YaleDataUpdateCoordinator, lock: YaleLock) -> None: + """Initialize the Yale Autolock Switch.""" + super().__init__(coordinator, lock) + self._attr_unique_id = f"{lock.sid()}-autolock" + self._attr_is_on = self.lock_data.autolock() + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the entity on.""" + if await self.hass.async_add_executor_job(self.lock_data.set_autolock, True): + self._attr_is_on = True + self.async_write_ha_state() + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the entity off.""" + if await self.hass.async_add_executor_job(self.lock_data.set_autolock, False): + self._attr_is_on = False + self.async_write_ha_state() + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._attr_is_on = self.lock_data.autolock() + super()._handle_coordinator_update() diff --git a/requirements_all.txt b/requirements_all.txt index 8ccd9ab9238..844be54fa0f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3002,7 +3002,7 @@ xmltodict==0.13.0 xs1-api-client==3.0.0 # homeassistant.components.yale_smart_alarm -yalesmartalarmclient==0.4.2 +yalesmartalarmclient==0.4.3 # homeassistant.components.august # homeassistant.components.yale diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 62149115e81..44c463bc6c5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2388,7 +2388,7 @@ xknxproject==3.7.1 xmltodict==0.13.0 # homeassistant.components.yale_smart_alarm -yalesmartalarmclient==0.4.2 +yalesmartalarmclient==0.4.3 # homeassistant.components.august # homeassistant.components.yale diff --git a/tests/components/yale_smart_alarm/conftest.py b/tests/components/yale_smart_alarm/conftest.py index 2a43eb8c6e7..7a7abcac67c 100644 --- a/tests/components/yale_smart_alarm/conftest.py +++ b/tests/components/yale_smart_alarm/conftest.py @@ -64,6 +64,7 @@ async def load_config_entry( client.auth = Mock() client.auth.get_authenticated = Mock(return_value=data) client.auth.post_authenticated = Mock(return_value={"code": "000"}) + client.auth.put_authenticated = Mock(return_value={"code": "000"}) client.lock_api = YaleDoorManAPI(client.auth) locks = [ YaleLock(device, lock_api=client.lock_api) diff --git a/tests/components/yale_smart_alarm/snapshots/test_switch.ambr b/tests/components/yale_smart_alarm/snapshots/test_switch.ambr new file mode 100644 index 00000000000..f631a6fcbfe --- /dev/null +++ b/tests/components/yale_smart_alarm/snapshots/test_switch.ambr @@ -0,0 +1,277 @@ +# serializer version: 1 +# name: test_switch[load_platforms0][switch.device1_autolock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.device1_autolock', + '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': 'Autolock', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'autolock', + 'unique_id': '1111-autolock', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][switch.device1_autolock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device1 Autolock', + }), + 'context': , + 'entity_id': 'switch.device1_autolock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[load_platforms0][switch.device2_autolock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.device2_autolock', + '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': 'Autolock', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'autolock', + 'unique_id': '2222-autolock', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][switch.device2_autolock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device2 Autolock', + }), + 'context': , + 'entity_id': 'switch.device2_autolock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[load_platforms0][switch.device3_autolock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.device3_autolock', + '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': 'Autolock', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'autolock', + 'unique_id': '3333-autolock', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][switch.device3_autolock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device3 Autolock', + }), + 'context': , + 'entity_id': 'switch.device3_autolock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[load_platforms0][switch.device7_autolock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.device7_autolock', + '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': 'Autolock', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'autolock', + 'unique_id': '7777-autolock', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][switch.device7_autolock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device7 Autolock', + }), + 'context': , + 'entity_id': 'switch.device7_autolock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[load_platforms0][switch.device8_autolock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.device8_autolock', + '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': 'Autolock', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'autolock', + 'unique_id': '8888-autolock', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][switch.device8_autolock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device8 Autolock', + }), + 'context': , + 'entity_id': 'switch.device8_autolock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[load_platforms0][switch.device9_autolock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.device9_autolock', + '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': 'Autolock', + 'platform': 'yale_smart_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'autolock', + 'unique_id': '9999-autolock', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[load_platforms0][switch.device9_autolock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device9 Autolock', + }), + 'context': , + 'entity_id': 'switch.device9_autolock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/yale_smart_alarm/test_switch.py b/tests/components/yale_smart_alarm/test_switch.py new file mode 100644 index 00000000000..b189a3fd003 --- /dev/null +++ b/tests/components/yale_smart_alarm/test_switch.py @@ -0,0 +1,46 @@ +"""The test for the Yale smart living switch.""" + +from __future__ import annotations + +from unittest.mock import Mock + +import pytest +from syrupy.assertion import SnapshotAssertion +from yalesmartalarmclient import YaleSmartAlarmData + +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SERVICE_TURN_OFF +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.mark.parametrize( + "load_platforms", + [[Platform.SWITCH]], +) +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 autolock switch.""" + + await snapshot_platform( + hass, entity_registry, snapshot, load_config_entry[0].entry_id + ) + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + { + ATTR_ENTITY_ID: "switch.device1_autolock", + }, + blocking=True, + ) + + state = hass.states.get("switch.device1_autolock") + assert state.state == STATE_OFF