"""Matter lock.""" from __future__ import annotations from enum import IntFlag from typing import Any from chip.clusters import Objects as clusters from homeassistant.components.lock import LockEntity, LockEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_CODE, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import LOGGER from .entity import MatterEntity from .helpers import get_matter from .models import MatterDiscoverySchema async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Matter lock from Config Entry.""" matter = get_matter(hass) matter.register_platform_handler(Platform.LOCK, async_add_entities) class MatterLock(MatterEntity, LockEntity): """Representation of a Matter lock.""" features: int | None = None @property def code_format(self) -> str | None: """Regex for code format or None if no code is required.""" if self.get_matter_attribute_value( clusters.DoorLock.Attributes.RequirePINforRemoteOperation ): min_pincode_length = int( self.get_matter_attribute_value( clusters.DoorLock.Attributes.MinPINCodeLength ) ) max_pincode_length = int( self.get_matter_attribute_value( clusters.DoorLock.Attributes.MaxPINCodeLength ) ) return f"^\\d{{{min_pincode_length},{max_pincode_length}}}$" return None @property def supports_door_position_sensor(self) -> bool: """Return True if the lock supports door position sensor.""" if self.features is None: return False return bool(self.features & DoorLockFeature.kDoorPositionSensor) async def send_device_command( self, command: clusters.ClusterCommand, timed_request_timeout_ms: int = 1000, ) -> None: """Send a command to the device.""" await self.matter_client.send_device_command( node_id=self._endpoint.node.node_id, endpoint_id=self._endpoint.endpoint_id, command=command, timed_request_timeout_ms=timed_request_timeout_ms, ) async def async_lock(self, **kwargs: Any) -> None: """Lock the lock with pin if needed.""" code: str = kwargs.get( ATTR_CODE, self._lock_option_default_code, ) code_bytes = code.encode() if code else None await self.send_device_command( command=clusters.DoorLock.Commands.LockDoor(code_bytes) ) async def async_unlock(self, **kwargs: Any) -> None: """Unlock the lock with pin if needed.""" code: str = kwargs.get( ATTR_CODE, self._lock_option_default_code, ) code_bytes = code.encode() if code else None await self.send_device_command( command=clusters.DoorLock.Commands.UnlockDoor(code_bytes) ) @callback def _update_from_device(self) -> None: """Update the entity from the device.""" if self.features is None: self.features = int( self.get_matter_attribute_value(clusters.DoorLock.Attributes.FeatureMap) ) lock_state = self.get_matter_attribute_value( clusters.DoorLock.Attributes.LockState ) LOGGER.debug("Lock state: %s for %s", lock_state, self.entity_id) if lock_state is clusters.DoorLock.Enums.DlLockState.kLocked: self._attr_is_locked = True self._attr_is_locking = False self._attr_is_unlocking = False elif lock_state is clusters.DoorLock.Enums.DlLockState.kUnlocked: self._attr_is_locked = False self._attr_is_locking = False self._attr_is_unlocking = False elif lock_state is clusters.DoorLock.Enums.DlLockState.kNotFullyLocked: if self.is_locked is True: self._attr_is_unlocking = True elif self.is_locked is False: self._attr_is_locking = True else: # According to the matter docs a null state can happen during device startup. self._attr_is_locked = None self._attr_is_locking = None self._attr_is_unlocking = None if self.supports_door_position_sensor: door_state = self.get_matter_attribute_value( clusters.DoorLock.Attributes.DoorState ) assert door_state is not None LOGGER.debug("Door state: %s for %s", door_state, self.entity_id) self._attr_is_jammed = ( door_state is clusters.DoorLock.Enums.DoorStateEnum.kDoorJammed ) class DoorLockFeature(IntFlag): """Temp enum that represents the features of a door lock. Should be replaced by the library provided one once that is released. """ kPinCredential = 0x1 # noqa: N815 kRfidCredential = 0x2 # noqa: N815 kFingerCredentials = 0x4 # noqa: N815 kLogging = 0x8 # noqa: N815 kWeekDayAccessSchedules = 0x10 # noqa: N815 kDoorPositionSensor = 0x20 # noqa: N815 kFaceCredentials = 0x40 # noqa: N815 kCredentialsOverTheAirAccess = 0x80 # noqa: N815 kUser = 0x100 # noqa: N815 kNotification = 0x200 # noqa: N815 kYearDayAccessSchedules = 0x400 # noqa: N815 kHolidaySchedules = 0x800 # noqa: N815 DISCOVERY_SCHEMAS = [ MatterDiscoverySchema( platform=Platform.LOCK, entity_description=LockEntityDescription(key="MatterLock", name=None), entity_class=MatterLock, required_attributes=(clusters.DoorLock.Attributes.LockState,), optional_attributes=(clusters.DoorLock.Attributes.DoorState,), ), ]