Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 45 additions & 12 deletions homeassistant/components/lock/zwave.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
POLYCONTROL = 0x10E
DANALOCK_V2_BTZE = 0x2
POLYCONTROL_DANALOCK_V2_BTZE_LOCK = (POLYCONTROL, DANALOCK_V2_BTZE)
WORKAROUND_V2BTZE = 'v2btze'
WORKAROUND_DEVICE_STATE = 'state'
WORKAROUND_V2BTZE = 1
WORKAROUND_DEVICE_STATE = 2
WORKAROUND_CLEAR_ALARMS = 4

DEVICE_MAPPINGS = {
POLYCONTROL_DANALOCK_V2_BTZE_LOCK: WORKAROUND_V2BTZE,
Expand All @@ -43,21 +44,23 @@
# Yale YRD220 (as reported by adrum in PR #17386)
(0x0109, 0x0000): WORKAROUND_DEVICE_STATE,
# Schlage BE469
(0x003B, 0x5044): WORKAROUND_DEVICE_STATE,
(0x003B, 0x5044): WORKAROUND_DEVICE_STATE | WORKAROUND_CLEAR_ALARMS,
# Schlage FE599NX
(0x003B, 0x504C): WORKAROUND_DEVICE_STATE,
}

LOCK_NOTIFICATION = {
'1': 'Manual Lock',
'2': 'Manual Unlock',
'3': 'RF Lock',
'4': 'RF Unlock',
'5': 'Keypad Lock',
'6': 'Keypad Unlock',
'11': 'Lock Jammed',
'254': 'Unknown Event'
}
NOTIFICATION_RF_LOCK = '3'
NOTIFICATION_RF_UNLOCK = '4'
LOCK_NOTIFICATION[NOTIFICATION_RF_LOCK] = 'RF Lock'
LOCK_NOTIFICATION[NOTIFICATION_RF_UNLOCK] = 'RF Unlock'

LOCK_ALARM_TYPE = {
'9': 'Deadbolt Jammed',
Expand All @@ -66,8 +69,6 @@
'19': 'Unlocked with Keypad by user ',
'21': 'Manually Locked ',
'22': 'Manually Unlocked ',
'24': 'Locked by RF',
'25': 'Unlocked by RF',
'27': 'Auto re-lock',
'33': 'User deleted: ',
'112': 'Master code changed or User added: ',
Expand All @@ -79,6 +80,10 @@
'168': 'Critical Battery Level',
'169': 'Battery too low to operate'
}
ALARM_RF_LOCK = '24'
ALARM_RF_UNLOCK = '25'
LOCK_ALARM_TYPE[ALARM_RF_LOCK] = 'Locked by RF'
LOCK_ALARM_TYPE[ALARM_RF_UNLOCK] = 'Unlocked by RF'

MANUAL_LOCK_ALARM_LEVEL = {
'1': 'by Key Cylinder or Inside thumb turn',
Expand Down Expand Up @@ -229,6 +234,7 @@ def __init__(self, values):
self._lock_status = None
self._v2btze = None
self._state_workaround = False
self._clear_alarms_workaround = False

# Enable appropriate workaround flags for our device
# Make sure that we have values for the key before converting to int
Expand All @@ -237,15 +243,19 @@ def __init__(self, values):
specific_sensor_key = (int(self.node.manufacturer_id, 16),
int(self.node.product_id, 16))
if specific_sensor_key in DEVICE_MAPPINGS:
if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_V2BTZE:
workaround = DEVICE_MAPPINGS[specific_sensor_key]
if workaround & WORKAROUND_V2BTZE:
self._v2btze = 1
_LOGGER.debug("Polycontrol Danalock v2 BTZE "
"workaround enabled")
if DEVICE_MAPPINGS[specific_sensor_key] == \
WORKAROUND_DEVICE_STATE:
if workaround & WORKAROUND_DEVICE_STATE:
self._state_workaround = True
_LOGGER.debug(
"Notification device state workaround enabled")
if workaround & WORKAROUND_CLEAR_ALARMS:
self._clear_alarms_workaround = True
message = "Clear alarms device state workaround enabled"
_LOGGER.debug(message)
self.update_properties()

def update_properties(self):
Expand All @@ -265,16 +275,33 @@ def update_properties(self):
"Lock state set from Access Control value and is %s, "
"get=%s", str(notification_data), self.state)

if self._clear_alarms_workaround and \
(not self.values.alarm_type or
self.values.alarm_type.data is None):

self._state = self.values.primary.data
if self._state:
self._notification = \
LOCK_NOTIFICATION.get(NOTIFICATION_RF_LOCK)
self._lock_status = LOCK_ALARM_TYPE[ALARM_RF_LOCK]
else:
self._notification = LOCK_NOTIFICATION.get(
NOTIFICATION_RF_UNLOCK)
self._lock_status = LOCK_ALARM_TYPE[ALARM_RF_UNLOCK]

message = "Lock state set to %s, notification set to %s, " + \
"status set to %s based on cleared alarm workaround"
_LOGGER.debug(message, self._state, self._notification,
self._lock_status)

if not self.values.alarm_type:
return

alarm_type = self.values.alarm_type.data
_LOGGER.debug("Lock alarm_type is %s", str(alarm_type))
if self.values.alarm_level:
alarm_level = self.values.alarm_level.data
else:
alarm_level = None
_LOGGER.debug("Lock alarm_level is %s", str(alarm_level))

if not alarm_type:
return
Expand Down Expand Up @@ -303,10 +330,16 @@ def is_locked(self):

def lock(self, **kwargs):
"""Lock the device."""
if self._clear_alarms_workaround and self.values.alarm_type:
self.values.alarm_type = None

self.values.primary.data = True

def unlock(self, **kwargs):
"""Unlock the device."""
if self._clear_alarms_workaround and self.values.alarm_type:
self.values.alarm_type = None

self.values.primary.data = False

@property
Expand Down
32 changes: 31 additions & 1 deletion tests/components/lock/test_zwave.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_lock_value_changed(mock_openzwave):
assert device.is_locked


def test_lock_value_changed_workaround(mock_openzwave):
def test_lock_state_workaround(mock_openzwave):
"""Test value changed for Z-Wave lock using notification state."""
node = MockNode(manufacturer_id='0090', product_id='0440')
values = MockEntityValues(
Expand All @@ -78,6 +78,36 @@ def test_lock_value_changed_workaround(mock_openzwave):
assert not device.is_locked


def test_clear_alarms_workaround(mock_openzwave):
"""Test value changed for Z-Wave lock by alarm-clearing workaround."""
node = MockNode(manufacturer_id='003B', product_id='5044')
values = MockEntityValues(
primary=MockValue(data=True, node=node),
access_control=None,
alarm_type=None,
alarm_level=None,
)
device = zwave.get_device(node=node, values=values)
assert device.is_locked
assert device.device_state_attributes[zwave.ATTR_NOTIFICATION] == 'RF Lock'

values.access_control = MockValue(data=6, node=node)
values.alarm_type = MockValue(data=19, node=node)
values.alarm_level = MockValue(data=3, node=node)
value_changed(values.access_control)
value_changed(values.alarm_type)
value_changed(values.alarm_type)
assert not device.is_locked
assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \
'Unlocked with Keypad by user 3'

device.lock()
value_changed(values.primary)
assert device.is_locked
assert not values.alarm_type.data
assert device.device_state_attributes[zwave.ATTR_NOTIFICATION] == 'RF Lock'


def test_v2btze_value_changed(mock_openzwave):
"""Test value changed for v2btze Z-Wave lock."""
node = MockNode(manufacturer_id='010e', product_id='0002')
Expand Down