diff --git a/homeassistant/components/lock/nuki.py b/homeassistant/components/lock/nuki.py index 144bfc3ec45..b47305fa227 100644 --- a/homeassistant/components/lock/nuki.py +++ b/homeassistant/components/lock/nuki.py @@ -4,28 +4,47 @@ Nuki.io lock platform. For more details about this platform, please refer to the documentation https://home-assistant.io/components/lock.nuki/ """ +import asyncio from datetime import timedelta import logging +from os import path import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.lock import (LockDevice, PLATFORM_SCHEMA) -from homeassistant.const import (CONF_HOST, CONF_PORT, CONF_TOKEN) -from homeassistant.util import Throttle +from homeassistant.components.lock import (DOMAIN, LockDevice, PLATFORM_SCHEMA) +from homeassistant.config import load_yaml_config_file +from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_HOST, CONF_PORT, CONF_TOKEN) +from homeassistant.helpers.service import extract_entity_ids -REQUIREMENTS = ['pynuki==1.2.2'] +REQUIREMENTS = ['pynuki==1.3.1'] _LOGGER = logging.getLogger(__name__) DEFAULT_PORT = 8080 +ATTR_BATTERY_CRITICAL = 'battery_critical' +ATTR_NUKI_ID = 'nuki_id' +ATTR_UNLATCH = 'unlatch' +NUKI_DATA = 'nuki' +SERVICE_LOCK_N_GO = 'nuki_lock_n_go' +SERVICE_UNLATCH = 'nuki_unlatch' + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Required(CONF_TOKEN): cv.string }) +LOCK_N_GO_SERVICE_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_UNLATCH, default=False): cv.boolean +}) + +UNLATCH_SERVICE_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids +}) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=5) @@ -38,6 +57,34 @@ def setup_platform(hass, config, add_devices, discovery_info=None): bridge = NukiBridge(config.get(CONF_HOST), config.get(CONF_TOKEN)) add_devices([NukiLock(lock) for lock in bridge.locks]) + def service_handler(service): + """Service handler for nuki services.""" + entity_ids = extract_entity_ids(hass, service) + all_locks = hass.data[NUKI_DATA][DOMAIN] + target_locks = [] + if not entity_ids: + target_locks = all_locks + else: + for lock in all_locks: + if lock.entity_id in entity_ids: + target_locks.append(lock) + for lock in target_locks: + if service.service == SERVICE_LOCK_N_GO: + unlatch = service.data[ATTR_UNLATCH] + lock.lock_n_go(unlatch=unlatch) + elif service.service == SERVICE_UNLATCH: + lock.unlatch() + + descriptions = load_yaml_config_file( + path.join(path.dirname(__file__), 'services.yaml')) + + hass.services.register( + DOMAIN, SERVICE_LOCK_N_GO, service_handler, + descriptions.get(SERVICE_LOCK_N_GO), schema=LOCK_N_GO_SERVICE_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_UNLATCH, service_handler, + descriptions.get(SERVICE_UNLATCH), schema=UNLATCH_SERVICE_SCHEMA) + class NukiLock(LockDevice): """Representation of a Nuki lock.""" @@ -47,6 +94,16 @@ class NukiLock(LockDevice): self._nuki_lock = nuki_lock self._locked = nuki_lock.is_locked self._name = nuki_lock.name + self._battery_critical = nuki_lock.battery_critical + + @asyncio.coroutine + def async_added_to_hass(self): + """Callback when entity is added to hass.""" + if NUKI_DATA not in self.hass.data: + self.hass.data[NUKI_DATA] = {} + if DOMAIN not in self.hass.data[NUKI_DATA]: + self.hass.data[NUKI_DATA][DOMAIN] = [] + self.hass.data[NUKI_DATA][DOMAIN].append(self) @property def name(self): @@ -58,12 +115,20 @@ class NukiLock(LockDevice): """Return true if lock is locked.""" return self._locked - @Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) + @property + def device_state_attributes(self): + """Return the device specific state attributes.""" + data = { + ATTR_BATTERY_CRITICAL: self._battery_critical, + ATTR_NUKI_ID: self._nuki_lock.nuki_id} + return data + def update(self): """Update the nuki lock properties.""" self._nuki_lock.update(aggressive=False) self._name = self._nuki_lock.name self._locked = self._nuki_lock.is_locked + self._battery_critical = self._nuki_lock.battery_critical def lock(self, **kwargs): """Lock the device.""" @@ -72,3 +137,15 @@ class NukiLock(LockDevice): def unlock(self, **kwargs): """Unlock the device.""" self._nuki_lock.unlock() + + def lock_n_go(self, unlatch=False, **kwargs): + """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. + """ + self._nuki_lock.lock_n_go(unlatch, kwargs) + + def unlatch(self, **kwargs): + """Unlatch door.""" + self._nuki_lock.unlatch() diff --git a/homeassistant/components/lock/services.yaml b/homeassistant/components/lock/services.yaml index df370ca0168..04e9f458f9c 100644 --- a/homeassistant/components/lock/services.yaml +++ b/homeassistant/components/lock/services.yaml @@ -20,6 +20,25 @@ get_usercode: description: Code slot to retrive a code from example: 1 +nuki_lock_n_go: + description: "Lock 'n' Go" + + fields: + entity_id: + description: Entity id of the Nuki lock + example: 'lock.front_door' + unlatch: + description: Whether to unlatch the lock + example: false + +nuki_unlatch: + description: "Unlatch" + + fields: + entity_id: + description: Entity id of the Nuki lock + example: 'lock.front_door' + lock: description: Lock all or specified locks diff --git a/requirements_all.txt b/requirements_all.txt index b2a51a60338..1dc2c334676 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -658,7 +658,7 @@ pynetgear==0.3.3 pynetio==0.1.6 # homeassistant.components.lock.nuki -pynuki==1.2.2 +pynuki==1.3.1 # homeassistant.components.sensor.nut pynut2==2.1.2