From 4f317353e08d28661d67682e5f6437802ae8b5dc Mon Sep 17 00:00:00 2001 From: thomkaufmann Date: Wed, 20 May 2020 14:44:57 +0200 Subject: [PATCH] Add Nuki Opener integration (#35702) * Add Nuki Opener integration * Update pynuki version requirement; fix typo * Update requirements_all.txt * Create base class of shared lock and opener code * Clean up code formatting * Update requirements_all; Run isort * Remove unnecessary pass statements --- homeassistant/components/nuki/lock.py | 84 ++++++++++++++++----- homeassistant/components/nuki/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index 3d382496b28..13825cede94 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -1,4 +1,5 @@ """Nuki.io lock platform.""" +from abc import ABC, abstractmethod from datetime import timedelta import logging @@ -50,9 +51,10 @@ LOCK_N_GO_SERVICE_SCHEMA = vol.Schema( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Nuki lock platform.""" bridge = NukiBridge( - config[CONF_HOST], config[CONF_TOKEN], config[CONF_PORT], DEFAULT_TIMEOUT + config[CONF_HOST], config[CONF_TOKEN], config[CONF_PORT], DEFAULT_TIMEOUT, ) - devices = [NukiLock(lock) for lock in bridge.locks] + + devices = [NukiLockEntity(lock) for lock in bridge.locks] def service_handler(service): """Service handler for nuki services.""" @@ -65,41 +67,43 @@ def setup_platform(hass, config, add_entities, discovery_info=None): lock.lock_n_go(unlatch=unlatch) hass.services.register( - DOMAIN, SERVICE_LOCK_N_GO, service_handler, schema=LOCK_N_GO_SERVICE_SCHEMA + DOMAIN, SERVICE_LOCK_N_GO, service_handler, schema=LOCK_N_GO_SERVICE_SCHEMA, ) + devices.extend([NukiOpenerEntity(opener) for opener in bridge.openers]) + add_entities(devices) -class NukiLock(LockEntity): - """Representation of a Nuki lock.""" +class NukiDeviceEntity(LockEntity, ABC): + """Representation of a Nuki device.""" - def __init__(self, nuki_lock): + def __init__(self, nuki_device): """Initialize the lock.""" - self._nuki_lock = nuki_lock - self._available = nuki_lock.state not in ERROR_STATES + self._nuki_device = nuki_device + self._available = nuki_device.state not in ERROR_STATES @property def name(self): """Return the name of the lock.""" - return self._nuki_lock.name + return self._nuki_device.name @property def unique_id(self) -> str: """Return a unique ID.""" - return self._nuki_lock.nuki_id + return self._nuki_device.nuki_id @property + @abstractmethod def is_locked(self): """Return true if lock is locked.""" - return self._nuki_lock.is_locked @property def device_state_attributes(self): """Return the device specific state attributes.""" data = { - ATTR_BATTERY_CRITICAL: self._nuki_lock.battery_critical, - ATTR_NUKI_ID: self._nuki_lock.nuki_id, + ATTR_BATTERY_CRITICAL: self._nuki_device.battery_critical, + ATTR_NUKI_ID: self._nuki_device.nuki_id, } return data @@ -117,28 +121,49 @@ class NukiLock(LockEntity): """Update the nuki lock properties.""" for level in (False, True): try: - self._nuki_lock.update(aggressive=level) + self._nuki_device.update(aggressive=level) except RequestException: _LOGGER.warning("Network issues detect with %s", self.name) self._available = False continue # If in error state, we force an update and repoll data - self._available = self._nuki_lock.state not in ERROR_STATES + self._available = self._nuki_device.state not in ERROR_STATES if self._available: break + @abstractmethod def lock(self, **kwargs): """Lock the device.""" - self._nuki_lock.lock() + + @abstractmethod + def unlock(self, **kwargs): + """Unlock the device.""" + + @abstractmethod + def open(self, **kwargs): + """Open the door latch.""" + + +class NukiLockEntity(NukiDeviceEntity): + """Representation of a Nuki lock.""" + + @property + def is_locked(self): + """Return true if lock is locked.""" + return self._nuki_device.is_locked + + def lock(self, **kwargs): + """Lock the device.""" + self._nuki_device.lock() def unlock(self, **kwargs): """Unlock the device.""" - self._nuki_lock.unlock() + self._nuki_device.unlock() def open(self, **kwargs): """Open the door latch.""" - self._nuki_lock.unlatch() + self._nuki_device.unlatch() def lock_n_go(self, unlatch=False, **kwargs): """Lock and go. @@ -146,4 +171,25 @@ class NukiLock(LockEntity): This will first unlock the door, then wait for 20 seconds (or another amount of time depending on the lock settings) and relock. """ - self._nuki_lock.lock_n_go(unlatch, kwargs) + self._nuki_device.lock_n_go(unlatch, kwargs) + + +class NukiOpenerEntity(NukiDeviceEntity): + """Representation of a Nuki opener.""" + + @property + def is_locked(self): + """Return true if ring-to-open is enabled.""" + return not self._nuki_device.is_rto_activated + + def lock(self, **kwargs): + """Disable ring-to-open.""" + self._nuki_device.deactivate_rto() + + def unlock(self, **kwargs): + """Enable ring-to-open.""" + self._nuki_device.activate_rto() + + def open(self, **kwargs): + """Buzz open the door.""" + self._nuki_device.electric_strike_actuation() diff --git a/homeassistant/components/nuki/manifest.json b/homeassistant/components/nuki/manifest.json index a51ff3752a5..386b36a3ca9 100644 --- a/homeassistant/components/nuki/manifest.json +++ b/homeassistant/components/nuki/manifest.json @@ -2,6 +2,6 @@ "domain": "nuki", "name": "Nuki", "documentation": "https://www.home-assistant.io/integrations/nuki", - "requirements": ["pynuki==1.3.3"], + "requirements": ["pynuki==1.3.7"], "codeowners": ["@pvizeli"] } diff --git a/requirements_all.txt b/requirements_all.txt index e5364311843..ebdad2e0803 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1486,7 +1486,7 @@ pynetgear==0.6.1 pynetio==0.1.9.1 # homeassistant.components.nuki -pynuki==1.3.3 +pynuki==1.3.7 # homeassistant.components.nut pynut2==2.1.2