Skip to content

Commit b45d598

Browse files
authored
Add clear_usercode utility function (#55)
1 parent 062f819 commit b45d598

File tree

3 files changed

+76
-8
lines changed

3 files changed

+76
-8
lines changed

test/util/test_lock.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from zwave_js_server.const import ATTR_USERCODE
55
from zwave_js_server.exceptions import NotFoundError
66
from zwave_js_server.util.lock import (
7+
clear_usercode,
78
get_code_slots,
89
get_usercode,
910
get_usercodes,
@@ -93,3 +94,50 @@ async def test_set_usercode(lock_schlage_be469, mock_command, uuid4):
9394

9495
# assert no new command calls
9596
assert len(ack_commands) == 1
97+
98+
99+
async def test_clear_usercode(lock_schlage_be469, mock_command, uuid4):
100+
"""Test clear_usercode utility function."""
101+
node = lock_schlage_be469
102+
ack_commands = mock_command(
103+
{"command": "node.set_value", "nodeId": node.node_id},
104+
{"success": True},
105+
)
106+
107+
# Test valid code
108+
await clear_usercode(node, 1)
109+
assert len(ack_commands) == 1
110+
assert ack_commands[0] == {
111+
"command": "node.set_value",
112+
"nodeId": 20,
113+
"messageId": uuid4,
114+
"valueId": {
115+
"commandClassName": "User Code",
116+
"commandClass": 99,
117+
"endpoint": 0,
118+
"property": "userIdStatus",
119+
"propertyName": "userIdStatus",
120+
"propertyKey": 1,
121+
"propertyKeyName": "1",
122+
"metadata": {
123+
"type": "number",
124+
"readable": True,
125+
"writeable": True,
126+
"label": "User ID status (1)",
127+
"states": {
128+
"0": "Available",
129+
"1": "Enabled",
130+
"2": "Disabled",
131+
},
132+
},
133+
"value": 1,
134+
},
135+
"value": 0,
136+
}
137+
138+
# Test invalid code slot
139+
with pytest.raises(NotFoundError):
140+
await clear_usercode(node, 100)
141+
142+
# assert no new command calls
143+
assert len(ack_commands) == 1

zwave_js_server/const.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ class DoorLockMode(IntEnum):
146146
SECURED = 255
147147

148148

149+
class CodeSlotStatus(IntEnum):
150+
"""Enum with all (known/used) Z-Wave code slot statuses."""
151+
152+
AVAILABLE = 0
153+
ENABLED = 1
154+
DISABLED = 2
155+
156+
149157
# Depending on the Commmand Class being used by the lock, the lock state is
150158
# different so we need a map to track it
151159
LOCK_CMD_CLASS_TO_LOCKED_STATE_MAP = {
@@ -161,6 +169,7 @@ class DoorLockMode(IntEnum):
161169
}
162170

163171
LOCK_USERCODE_PROPERTY = "userCode"
172+
LOCK_USERCODE_STATUS_PROPERTY = "userIdStatus"
164173
ATTR_CODE_SLOT = "code_slot"
165174
ATTR_IN_USE = "in_use"
166175
ATTR_NAME = "name"

zwave_js_server/util/lock.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,30 @@
77
ATTR_NAME,
88
ATTR_USERCODE,
99
LOCK_USERCODE_PROPERTY,
10+
LOCK_USERCODE_STATUS_PROPERTY,
11+
CodeSlotStatus,
1012
CommandClass,
1113
)
1214
from ..exceptions import NotFoundError
1315
from ..model.node import Node
1416
from ..model.value import get_value_id, Value
1517

1618

17-
def get_code_slot_value(node: Node, code_slot: int) -> Value:
18-
"""Get a value."""
19+
def get_code_slot_value(node: Node, code_slot: int, property_name: str) -> Value:
20+
"""Get a code slot value."""
1921
value = node.values.get(
2022
get_value_id(
2123
node,
2224
{
2325
"commandClass": CommandClass.USER_CODE,
24-
"property": LOCK_USERCODE_PROPERTY,
26+
"property": property_name,
2527
"propertyKeyName": str(code_slot),
2628
},
2729
)
2830
)
2931

3032
if not value:
31-
raise NotFoundError(f"Code slot {code_slot} not found")
33+
raise NotFoundError(f"{property_name} for code slot {code_slot} not found")
3234

3335
return value
3436

@@ -43,7 +45,10 @@ def _get_code_slots(
4345
# Loop until we can't find a code slot
4446
while True:
4547
try:
46-
value = get_code_slot_value(node, code_slot)
48+
value = get_code_slot_value(node, code_slot, LOCK_USERCODE_PROPERTY)
49+
status_value = get_code_slot_value(
50+
node, code_slot, LOCK_USERCODE_STATUS_PROPERTY
51+
)
4752
except NotFoundError:
4853
return slots
4954

@@ -52,7 +57,7 @@ def _get_code_slots(
5257
slot = {
5358
ATTR_CODE_SLOT: int(value.property_key), # type: ignore
5459
ATTR_NAME: value.metadata.label,
55-
ATTR_IN_USE: bool(value.value),
60+
ATTR_IN_USE: status_value.value == CodeSlotStatus.ENABLED,
5661
}
5762
if include_usercode:
5863
slot[ATTR_USERCODE] = value.value if value.value else None
@@ -73,16 +78,22 @@ def get_usercodes(node: Node) -> List[Dict[str, Optional[Union[int, bool, str]]]
7378

7479
def get_usercode(node: Node, code_slot: int) -> Optional[str]:
7580
"""Get usercode from slot X on the lock."""
76-
value = get_code_slot_value(node, code_slot)
81+
value = get_code_slot_value(node, code_slot, LOCK_USERCODE_PROPERTY)
7782

7883
return str(value.value) if value.value else None
7984

8085

8186
async def set_usercode(node: Node, code_slot: int, usercode: str) -> None:
8287
"""Set the usercode to index X on the lock."""
83-
value = get_code_slot_value(node, code_slot)
88+
value = get_code_slot_value(node, code_slot, LOCK_USERCODE_PROPERTY)
8489

8590
if len(str(usercode)) < 4:
8691
raise ValueError("User code must be at least 4 digits")
8792

8893
await node.async_set_value(value, usercode)
94+
95+
96+
async def clear_usercode(node: Node, code_slot: int) -> None:
97+
"""Clear a code slot on the lock."""
98+
value = get_code_slot_value(node, code_slot, LOCK_USERCODE_STATUS_PROPERTY)
99+
await node.async_set_value(value, CodeSlotStatus.AVAILABLE.value)

0 commit comments

Comments
 (0)