Andrew Hayworth 5ae65142b8 Allow verisure locks to be configured with a default code (#18873)
* Allow verisure locks to be configured with a default code

* linting fix

* PR feedback

* PR feedback - try harder to prevent future typos

A python mock is a magical thing, and will respond to basicaly
any method you call on it. It's somewhat better to assert against
an explicit variable named 'mock', rather than to assert on the
method name you wanted to mock... could prevent a typo from messing up
tests.

* PR feedback: convert tests to integration-style tests

Set up a fake verisure hub, stub out a _lot_ of calls, then test
after platform discovery and service calls.

It should be noted that we're overriding the `update()` calls in
these tests. This was done to prevent even further mocking of
the verisure hub's responses.

Hopefully, this'll be a foundation for people to write more tests.

* more pr feedback
2018-12-03 07:25:54 +01:00

137 lines
4.3 KiB
Python

"""
Interfaces with Verisure locks.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/verisure/
"""
import logging
from time import sleep
from time import time
from homeassistant.components.verisure import HUB as hub
from homeassistant.components.verisure import (
CONF_LOCKS, CONF_DEFAULT_LOCK_CODE, CONF_CODE_DIGITS)
from homeassistant.components.lock import LockDevice
from homeassistant.const import (
ATTR_CODE, STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Verisure platform."""
locks = []
if int(hub.config.get(CONF_LOCKS, 1)):
hub.update_overview()
locks.extend([
VerisureDoorlock(device_label)
for device_label in hub.get(
"$.doorLockStatusList[*].deviceLabel")])
add_entities(locks)
class VerisureDoorlock(LockDevice):
"""Representation of a Verisure doorlock."""
def __init__(self, device_label):
"""Initialize the Verisure lock."""
self._device_label = device_label
self._state = STATE_UNKNOWN
self._digits = hub.config.get(CONF_CODE_DIGITS)
self._changed_by = None
self._change_timestamp = 0
self._default_lock_code = hub.config.get(CONF_DEFAULT_LOCK_CODE)
@property
def name(self):
"""Return the name of the lock."""
return hub.get_first(
"$.doorLockStatusList[?(@.deviceLabel=='%s')].area",
self._device_label)
@property
def state(self):
"""Return the state of the lock."""
return self._state
@property
def available(self):
"""Return True if entity is available."""
return hub.get_first(
"$.doorLockStatusList[?(@.deviceLabel=='%s')]",
self._device_label) is not None
@property
def changed_by(self):
"""Last change triggered by."""
return self._changed_by
@property
def code_format(self):
"""Return the required six digit code."""
return '^\\d{%s}$' % self._digits
def update(self):
"""Update lock status."""
if time() - self._change_timestamp < 10:
return
hub.update_overview()
status = hub.get_first(
"$.doorLockStatusList[?(@.deviceLabel=='%s')].lockedState",
self._device_label)
if status == 'UNLOCKED':
self._state = STATE_UNLOCKED
elif status == 'LOCKED':
self._state = STATE_LOCKED
elif status != 'PENDING':
_LOGGER.error('Unknown lock state %s', status)
self._changed_by = hub.get_first(
"$.doorLockStatusList[?(@.deviceLabel=='%s')].userString",
self._device_label)
@property
def is_locked(self):
"""Return true if lock is locked."""
return self._state == STATE_LOCKED
def unlock(self, **kwargs):
"""Send unlock command."""
if self._state == STATE_UNLOCKED:
return
code = kwargs.get(ATTR_CODE, self._default_lock_code)
if code is None:
_LOGGER.error("Code required but none provided")
return
self.set_lock_state(code, STATE_UNLOCKED)
def lock(self, **kwargs):
"""Send lock command."""
if self._state == STATE_LOCKED:
return
code = kwargs.get(ATTR_CODE, self._default_lock_code)
if code is None:
_LOGGER.error("Code required but none provided")
return
self.set_lock_state(code, STATE_LOCKED)
def set_lock_state(self, code, state):
"""Send set lock state command."""
lock_state = 'lock' if state == STATE_LOCKED else 'unlock'
transaction_id = hub.session.set_lock_state(
code,
self._device_label,
lock_state)['doorLockStateChangeTransactionId']
_LOGGER.debug("Verisure doorlock %s", state)
transaction = {}
while 'result' not in transaction:
sleep(0.5)
transaction = hub.session.get_lock_state_transaction(
transaction_id)
if transaction['result'] == 'OK':
self._state = state
self._change_timestamp = time()