diff --git a/homeassistant/components/tessie/lock.py b/homeassistant/components/tessie/lock.py index 1a0d879cd79..9a27e95c73e 100644 --- a/homeassistant/components/tessie/lock.py +++ b/homeassistant/components/tessie/lock.py @@ -3,9 +3,15 @@ from __future__ import annotations from typing import Any -from tessie_api import lock, open_unlock_charge_port, unlock +from tessie_api import ( + disable_speed_limit, + enable_speed_limit, + lock, + open_unlock_charge_port, + unlock, +) -from homeassistant.components.lock import LockEntity +from homeassistant.components.lock import ATTR_CODE, LockEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError @@ -24,7 +30,7 @@ async def async_setup_entry( async_add_entities( klass(vehicle.state_coordinator) - for klass in (TessieLockEntity, TessieCableLockEntity) + for klass in (TessieLockEntity, TessieCableLockEntity, TessieSpeedLimitEntity) for vehicle in data ) @@ -55,6 +61,38 @@ class TessieLockEntity(TessieEntity, LockEntity): self.set((self.key, False)) +class TessieSpeedLimitEntity(TessieEntity, LockEntity): + """Speed Limit with PIN entity for Tessie.""" + + _attr_code_format = r"^\d\d\d\d$" + + def __init__( + self, + coordinator: TessieStateUpdateCoordinator, + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator, "vehicle_state_speed_limit_mode_active") + + @property + def is_locked(self) -> bool | None: + """Return the state of the Lock.""" + return self._value + + async def async_lock(self, **kwargs: Any) -> None: + """Enable speed limit with pin.""" + code: str | None = kwargs.get(ATTR_CODE) + if code: + await self.run(enable_speed_limit, pin=code) + self.set((self.key, True)) + + async def async_unlock(self, **kwargs: Any) -> None: + """Disable speed limit with pin.""" + code: str | None = kwargs.get(ATTR_CODE) + if code: + await self.run(disable_speed_limit, pin=code) + self.set((self.key, False)) + + class TessieCableLockEntity(TessieEntity, LockEntity): """Cable Lock entity for Tessie.""" diff --git a/homeassistant/components/tessie/strings.json b/homeassistant/components/tessie/strings.json index 01e6a654163..f5900095836 100644 --- a/homeassistant/components/tessie/strings.json +++ b/homeassistant/components/tessie/strings.json @@ -59,6 +59,9 @@ }, "charge_state_charge_port_latch": { "name": "Charge cable lock" + }, + "vehicle_state_speed_limit_mode_active": { + "name": "Speed limit" } }, "media_player": { diff --git a/tests/components/tessie/snapshots/test_lock.ambr b/tests/components/tessie/snapshots/test_lock.ambr index cef92a1226f..026e02603c9 100644 --- a/tests/components/tessie/snapshots/test_lock.ambr +++ b/tests/components/tessie/snapshots/test_lock.ambr @@ -87,3 +87,48 @@ 'state': 'locked', }) # --- +# name: test_locks[lock.test_speed_limit-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'lock', + 'entity_category': None, + 'entity_id': 'lock.test_speed_limit', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Speed limit', + 'platform': 'tessie', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'vehicle_state_speed_limit_mode_active', + 'unique_id': 'VINVINVIN-vehicle_state_speed_limit_mode_active', + 'unit_of_measurement': None, + }) +# --- +# name: test_locks[lock.test_speed_limit-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'code_format': '^\\d\\d\\d\\d$', + 'friendly_name': 'Test Speed limit', + 'supported_features': , + }), + 'context': , + 'entity_id': 'lock.test_speed_limit', + 'last_changed': , + 'last_updated': , + 'state': 'unlocked', + }) +# --- diff --git a/tests/components/tessie/test_lock.py b/tests/components/tessie/test_lock.py index b1e4f24ac59..ca921583d97 100644 --- a/tests/components/tessie/test_lock.py +++ b/tests/components/tessie/test_lock.py @@ -6,6 +6,7 @@ import pytest from syrupy import SnapshotAssertion from homeassistant.components.lock import ( + ATTR_CODE, DOMAIN as LOCK_DOMAIN, SERVICE_LOCK, SERVICE_UNLOCK, @@ -27,9 +28,8 @@ async def test_locks( assert_entities(hass, entry.entry_id, entity_registry, snapshot) - entity_id = "lock.test_lock" - # Test lock set value functions + entity_id = "lock.test_lock" with patch("homeassistant.components.tessie.lock.lock") as mock_run: await hass.services.async_call( LOCK_DOMAIN, @@ -47,8 +47,8 @@ async def test_locks( {ATTR_ENTITY_ID: [entity_id]}, blocking=True, ) - mock_run.assert_called_once() + assert hass.states.get(entity_id).state == STATE_UNLOCKED # Test charge cable lock set value functions entity_id = "lock.test_charge_cable_lock" @@ -71,3 +71,37 @@ async def test_locks( ) assert hass.states.get(entity_id).state == STATE_UNLOCKED mock_run.assert_called_once() + + # Test lock set value functions + entity_id = "lock.test_speed_limit" + with patch( + "homeassistant.components.tessie.lock.enable_speed_limit" + ) as mock_enable_speed_limit: + await hass.services.async_call( + LOCK_DOMAIN, + SERVICE_LOCK, + {ATTR_ENTITY_ID: [entity_id], ATTR_CODE: "1234"}, + blocking=True, + ) + assert hass.states.get(entity_id).state == STATE_LOCKED + mock_enable_speed_limit.assert_called_once() + + with patch( + "homeassistant.components.tessie.lock.disable_speed_limit" + ) as mock_disable_speed_limit: + await hass.services.async_call( + LOCK_DOMAIN, + SERVICE_UNLOCK, + {ATTR_ENTITY_ID: [entity_id], ATTR_CODE: "1234"}, + blocking=True, + ) + assert hass.states.get(entity_id).state == STATE_UNLOCKED + mock_disable_speed_limit.assert_called_once() + + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + LOCK_DOMAIN, + SERVICE_UNLOCK, + {ATTR_ENTITY_ID: [entity_id], ATTR_CODE: "abc"}, + blocking=True, + )