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
This commit is contained in:
thomkaufmann 2020-05-20 14:44:57 +02:00 committed by GitHub
parent 2b3cf97979
commit 4f317353e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 67 additions and 21 deletions

View File

@ -1,4 +1,5 @@
"""Nuki.io lock platform.""" """Nuki.io lock platform."""
from abc import ABC, abstractmethod
from datetime import timedelta from datetime import timedelta
import logging import logging
@ -50,9 +51,10 @@ LOCK_N_GO_SERVICE_SCHEMA = vol.Schema(
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Nuki lock platform.""" """Set up the Nuki lock platform."""
bridge = NukiBridge( 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): def service_handler(service):
"""Service handler for nuki services.""" """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) lock.lock_n_go(unlatch=unlatch)
hass.services.register( 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) add_entities(devices)
class NukiLock(LockEntity): class NukiDeviceEntity(LockEntity, ABC):
"""Representation of a Nuki lock.""" """Representation of a Nuki device."""
def __init__(self, nuki_lock): def __init__(self, nuki_device):
"""Initialize the lock.""" """Initialize the lock."""
self._nuki_lock = nuki_lock self._nuki_device = nuki_device
self._available = nuki_lock.state not in ERROR_STATES self._available = nuki_device.state not in ERROR_STATES
@property @property
def name(self): def name(self):
"""Return the name of the lock.""" """Return the name of the lock."""
return self._nuki_lock.name return self._nuki_device.name
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
"""Return a unique ID.""" """Return a unique ID."""
return self._nuki_lock.nuki_id return self._nuki_device.nuki_id
@property @property
@abstractmethod
def is_locked(self): def is_locked(self):
"""Return true if lock is locked.""" """Return true if lock is locked."""
return self._nuki_lock.is_locked
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the device specific state attributes.""" """Return the device specific state attributes."""
data = { data = {
ATTR_BATTERY_CRITICAL: self._nuki_lock.battery_critical, ATTR_BATTERY_CRITICAL: self._nuki_device.battery_critical,
ATTR_NUKI_ID: self._nuki_lock.nuki_id, ATTR_NUKI_ID: self._nuki_device.nuki_id,
} }
return data return data
@ -117,28 +121,49 @@ class NukiLock(LockEntity):
"""Update the nuki lock properties.""" """Update the nuki lock properties."""
for level in (False, True): for level in (False, True):
try: try:
self._nuki_lock.update(aggressive=level) self._nuki_device.update(aggressive=level)
except RequestException: except RequestException:
_LOGGER.warning("Network issues detect with %s", self.name) _LOGGER.warning("Network issues detect with %s", self.name)
self._available = False self._available = False
continue continue
# If in error state, we force an update and repoll data # 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: if self._available:
break break
@abstractmethod
def lock(self, **kwargs): def lock(self, **kwargs):
"""Lock the device.""" """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): def unlock(self, **kwargs):
"""Unlock the device.""" """Unlock the device."""
self._nuki_lock.unlock() self._nuki_device.unlock()
def open(self, **kwargs): def open(self, **kwargs):
"""Open the door latch.""" """Open the door latch."""
self._nuki_lock.unlatch() self._nuki_device.unlatch()
def lock_n_go(self, unlatch=False, **kwargs): def lock_n_go(self, unlatch=False, **kwargs):
"""Lock and go. """Lock and go.
@ -146,4 +171,25 @@ class NukiLock(LockEntity):
This will first unlock the door, then wait for 20 seconds (or another This will first unlock the door, then wait for 20 seconds (or another
amount of time depending on the lock settings) and relock. 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()

View File

@ -2,6 +2,6 @@
"domain": "nuki", "domain": "nuki",
"name": "Nuki", "name": "Nuki",
"documentation": "https://www.home-assistant.io/integrations/nuki", "documentation": "https://www.home-assistant.io/integrations/nuki",
"requirements": ["pynuki==1.3.3"], "requirements": ["pynuki==1.3.7"],
"codeowners": ["@pvizeli"] "codeowners": ["@pvizeli"]
} }

View File

@ -1486,7 +1486,7 @@ pynetgear==0.6.1
pynetio==0.1.9.1 pynetio==0.1.9.1
# homeassistant.components.nuki # homeassistant.components.nuki
pynuki==1.3.3 pynuki==1.3.7
# homeassistant.components.nut # homeassistant.components.nut
pynut2==2.1.2 pynut2==2.1.2