core/homeassistant/components/nuki/lock.py

180 lines
5.4 KiB
Python

"""Nuki.io lock platform."""
from __future__ import annotations
from abc import abstractmethod
from typing import Any
from pynuki import NukiLock, NukiOpener
from pynuki.constants import MODE_OPENER_CONTINUOUS
from pynuki.device import NukiDevice
from requests.exceptions import RequestException
import voluptuous as vol
from homeassistant.components.lock import LockEntity, LockEntityFeature
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import NukiEntryData
from .const import ATTR_ENABLE, ATTR_UNLATCH, DOMAIN as NUKI_DOMAIN, ERROR_STATES
from .entity import NukiEntity
from .helpers import CannotConnect
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Nuki lock platform."""
entry_data: NukiEntryData = hass.data[NUKI_DOMAIN][entry.entry_id]
coordinator = entry_data.coordinator
entities: list[NukiDeviceEntity] = [
NukiLockEntity(coordinator, lock) for lock in entry_data.locks
]
entities.extend(
[NukiOpenerEntity(coordinator, opener) for opener in entry_data.openers]
)
async_add_entities(entities)
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
"lock_n_go",
{
vol.Optional(ATTR_UNLATCH, default=False): cv.boolean,
},
"lock_n_go",
)
platform.async_register_entity_service(
"set_continuous_mode",
{
vol.Required(ATTR_ENABLE): cv.boolean,
},
"set_continuous_mode",
)
class NukiDeviceEntity[_NukiDeviceT: NukiDevice](NukiEntity[_NukiDeviceT], LockEntity):
"""Representation of a Nuki device."""
_attr_has_entity_name = True
_attr_supported_features = LockEntityFeature.OPEN
_attr_translation_key = "nuki_lock"
_attr_name = None
@property
def unique_id(self) -> str | None:
"""Return a unique ID."""
return self._nuki_device.nuki_id
@property
def available(self) -> bool:
"""Return True if entity is available."""
return super().available and self._nuki_device.state not in ERROR_STATES
@abstractmethod
def lock(self, **kwargs: Any) -> None:
"""Lock the device."""
@abstractmethod
def unlock(self, **kwargs: Any) -> None:
"""Unlock the device."""
@abstractmethod
def open(self, **kwargs: Any) -> None:
"""Open the door latch."""
class NukiLockEntity(NukiDeviceEntity[NukiLock]):
"""Representation of a Nuki lock."""
@property
def is_locked(self) -> bool:
"""Return true if lock is locked."""
return self._nuki_device.is_locked
def lock(self, **kwargs: Any) -> None:
"""Lock the device."""
try:
self._nuki_device.lock()
except RequestException as err:
raise CannotConnect from err
def unlock(self, **kwargs: Any) -> None:
"""Unlock the device."""
try:
self._nuki_device.unlock()
except RequestException as err:
raise CannotConnect from err
def open(self, **kwargs: Any) -> None:
"""Open the door latch."""
try:
self._nuki_device.unlatch()
except RequestException as err:
raise CannotConnect from err
def lock_n_go(self, unlatch: bool) -> None:
"""Lock and go.
This will first unlock the door, then wait for 20 seconds (or another
amount of time depending on the lock settings) and relock.
"""
try:
self._nuki_device.lock_n_go(unlatch)
except RequestException as err:
raise CannotConnect from err
class NukiOpenerEntity(NukiDeviceEntity[NukiOpener]):
"""Representation of a Nuki opener."""
@property
def is_locked(self) -> bool:
"""Return true if either ring-to-open or continuous mode is enabled."""
return not (
self._nuki_device.is_rto_activated
or self._nuki_device.mode == MODE_OPENER_CONTINUOUS
)
def lock(self, **kwargs: Any) -> None:
"""Disable ring-to-open."""
try:
self._nuki_device.deactivate_rto()
except RequestException as err:
raise CannotConnect from err
def unlock(self, **kwargs: Any) -> None:
"""Enable ring-to-open."""
try:
self._nuki_device.activate_rto()
except RequestException as err:
raise CannotConnect from err
def open(self, **kwargs: Any) -> None:
"""Buzz open the door."""
try:
self._nuki_device.electric_strike_actuation()
except RequestException as err:
raise CannotConnect from err
def lock_n_go(self, unlatch: bool) -> None:
"""Stub service."""
def set_continuous_mode(self, enable: bool) -> None:
"""Continuous Mode.
This feature will cause the door to automatically open when anyone
rings the bell. This is similar to ring-to-open, except that it does
not automatically deactivate
"""
try:
if enable:
self._nuki_device.activate_continuous_mode()
else:
self._nuki_device.deactivate_continuous_mode()
except RequestException as err:
raise CannotConnect from err