simplipy/device/lock.py
"""Define a SimpliSafe lock."""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from enum import Enum
from typing import TYPE_CHECKING, Any, cast
from simplipy.const import LOGGER
from simplipy.device import DeviceTypes, DeviceV3
if TYPE_CHECKING:
from simplipy.system import System
class LockStates(Enum):
"""States that a lock can be in."""
UNLOCKED = 0
LOCKED = 1
JAMMED = 2
UNKNOWN = 99
class Lock(DeviceV3):
"""A lock that works with V3 systems.
Note that this class shouldn't be instantiated directly; it will be
instantiated as appropriate via :meth:`simplipy.API.async_get_systems`.
Args:
request: The request method from the :meth:`simplipy.API` object.
system: A :meth:`simplipy.system.System` object (or one of its subclasses).
device_type: The type of device represented.
serial: The serial number of the device.
"""
class _InternalStates(Enum):
"""Define an enum to map internal lock states to values we understand."""
LOCKED = 1
UNLOCKED = 2
def __init__(
self,
request: Callable[..., Awaitable[dict[str, Any]]],
system: System,
device_type: DeviceTypes,
serial: str,
) -> None:
"""Initialize.
Args:
request: The request method from the :meth:`simplipy.API` object.
system: A :meth:`simplipy.system.System` object (or one of its subclasses).
device_type: The type of device represented.
serial: The serial number of the device.
"""
super().__init__(system, device_type, serial)
self._request = request
@property
def disabled(self) -> bool:
"""Return whether the lock is disabled.
Returns:
The lock's disable status.
"""
return cast(
bool, self._system.sensor_data[self._serial]["status"]["lockDisabled"]
)
@property
def lock_low_battery(self) -> bool:
"""Return whether the lock's battery is low.
Returns:
The lock's low battery status.
"""
return cast(
bool, self._system.sensor_data[self._serial]["status"]["lockLowBattery"]
)
@property
def pin_pad_low_battery(self) -> bool:
"""Return whether the pin pad's battery is low.
Returns:
The pinpad's low battery status.
"""
return cast(
bool, self._system.sensor_data[self._serial]["status"]["pinPadLowBattery"]
)
@property
def pin_pad_offline(self) -> bool:
"""Return whether the pin pad is offline.
Returns:
The pinpad's offline status.
"""
return cast(
bool, self._system.sensor_data[self._serial]["status"]["pinPadOffline"]
)
@property
def state(self) -> LockStates:
"""Return the current state of the lock.
Returns:
The lock's state.
"""
if bool(self._system.sensor_data[self._serial]["status"]["lockJamState"]):
return LockStates.JAMMED
raw_state = self._system.sensor_data[self._serial]["status"]["lockState"]
try:
internal_state = self._InternalStates(raw_state)
except ValueError:
LOGGER.error("Unknown raw lock state: %s", raw_state)
return LockStates.UNKNOWN
if internal_state == self._InternalStates.LOCKED:
return LockStates.LOCKED
return LockStates.UNLOCKED
def as_dict(self) -> dict[str, Any]:
"""Return dictionary version of this device.
Returns:
A dict representation of this device.
"""
return {
**super().as_dict(),
"disabled": self.disabled,
"lock_low_battery": self.lock_low_battery,
"pin_pad_low_battery": self.pin_pad_low_battery,
"pin_pad_offline": self.pin_pad_offline,
"state": self.state.value,
}
async def async_lock(self) -> None:
"""Lock the lock."""
await self._request(
"post",
f"doorlock/{self._system.system_id}/{self.serial}/state",
json={"state": "lock"},
)
# Update the internal state representation:
self._system.sensor_data[self._serial]["status"]["lockState"] = (
self._InternalStates.LOCKED.value
)
async def async_unlock(self) -> None:
"""Unlock the lock."""
await self._request(
"post",
f"doorlock/{self._system.system_id}/{self.serial}/state",
json={"state": "unlock"},
)
# Update the internal state representation:
self._system.sensor_data[self._serial]["status"]["lockState"] = (
self._InternalStates.UNLOCKED.value
)