Skip to content

Commit 89520fe

Browse files
committed
Add lock util function to set multiple usercodes
1 parent 8ef40fa commit 89520fe

File tree

3 files changed

+74
-3
lines changed

3 files changed

+74
-3
lines changed

test/util/test_lock.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
ATTR_IN_USE,
1212
ATTR_NAME,
1313
ATTR_USERCODE,
14+
LOCK_USERCODE_ID_PROPERTY,
15+
LOCK_USERCODE_PROPERTY,
16+
LOCK_USERCODE_STATUS_PROPERTY,
17+
CodeSlotStatus,
1418
DoorLockCCConfigurationSetOptions,
1519
OperationType,
1620
)
@@ -24,6 +28,7 @@
2428
get_usercodes,
2529
set_configuration,
2630
set_usercode,
31+
set_usercodes,
2732
)
2833

2934
from .const import CODE_SLOTS
@@ -102,6 +107,43 @@ async def test_set_usercode(lock_schlage_be469, mock_command, uuid4):
102107
assert len(ack_commands) == 1
103108

104109

110+
async def test_set_usercodes(lock_schlage_be469, mock_command, uuid4):
111+
"""Test set_usercodes utility function."""
112+
node = lock_schlage_be469
113+
ack_commands = mock_command(
114+
{"command": "endpoint.invoke_cc_api", "endpoint": 0, "nodeId": node.node_id},
115+
{"response": {"status": 255}},
116+
)
117+
118+
# Test wrong types to ensure values get converted
119+
await set_usercodes(node, {"1": 1234})
120+
assert len(ack_commands) == 1
121+
assert ack_commands[0] == {
122+
"command": "endpoint.invoke_cc_api",
123+
"commandClass": 99,
124+
"endpoint": 0,
125+
"methodName": "setMany",
126+
"nodeId": 20,
127+
"messageId": uuid4,
128+
"args": [
129+
[
130+
{
131+
LOCK_USERCODE_STATUS_PROPERTY: CodeSlotStatus.ENABLED,
132+
LOCK_USERCODE_ID_PROPERTY: 1,
133+
LOCK_USERCODE_PROPERTY: "1234",
134+
}
135+
]
136+
],
137+
}
138+
139+
# Test invalid code length
140+
with pytest.raises(ValueError):
141+
await set_usercodes(node, {1: "123"})
142+
143+
# assert no new command calls
144+
assert len(ack_commands) == 1
145+
146+
105147
async def test_clear_usercode(lock_schlage_be469, mock_command, uuid4):
106148
"""Test clear_usercode utility function."""
107149
node = lock_schlage_be469

zwave_js_server/const/command_class/lock.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class CodeSlotStatus(IntEnum):
118118

119119
# User Code CC constants
120120
LOCK_USERCODE_PROPERTY = "userCode"
121+
LOCK_USERCODE_ID_PROPERTY = "userId"
121122
LOCK_USERCODE_STATUS_PROPERTY = "userIdStatus"
122123

123124
ATTR_CODE_SLOT = "code_slot"

zwave_js_server/util/lock.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
CURRENT_BLOCK_TO_BLOCK_PROPERTY,
1515
CURRENT_HOLD_AND_RELEASE_TIME_PROPERTY,
1616
CURRENT_TWIST_ASSIST_PROPERTY,
17+
LOCK_USERCODE_ID_PROPERTY,
1718
LOCK_USERCODE_PROPERTY,
1819
LOCK_USERCODE_STATUS_PROPERTY,
1920
CodeSlotStatus,
@@ -68,7 +69,7 @@ def _get_code_slots(node: Node, include_usercode: bool = False) -> list[CodeSlot
6869
except NotFoundError:
6970
return slots
7071

71-
code_slot = int(value.property_key) # type: ignore[arg-type]
72+
code_slot = int(value.property_key)
7273
in_use = (
7374
None
7475
if status_value.value is None
@@ -104,7 +105,7 @@ def get_usercode(node: Node, code_slot: int) -> CodeSlot:
104105
value = get_code_slot_value(node, code_slot, LOCK_USERCODE_PROPERTY)
105106
status_value = get_code_slot_value(node, code_slot, LOCK_USERCODE_STATUS_PROPERTY)
106107

107-
code_slot = int(value.property_key) # type: ignore[arg-type]
108+
code_slot = int(value.property_key)
108109
in_use = (
109110
None
110111
if status_value.value is None
@@ -130,6 +131,7 @@ async def get_usercode_from_node(node: Node, code_slot: int) -> CodeSlot:
130131
This call will populate the ValueDB and trigger value update events from the
131132
driver.
132133
"""
134+
# https://zwave-js.github.io/node-zwave-js/#/api/CCs/UserCode?id=get
133135
await node.async_invoke_cc_api(
134136
CommandClass.USER_CODE, "get", code_slot, wait_for_result=True
135137
)
@@ -141,13 +143,38 @@ async def set_usercode(
141143
) -> SetValueResult | None:
142144
"""Set the usercode to index X on the lock."""
143145
value = get_code_slot_value(node, code_slot, LOCK_USERCODE_PROPERTY)
146+
usercode = str(usercode)
144147

145-
if len(str(usercode)) < 4:
148+
if len(usercode) < 4:
146149
raise ValueError("User code must be at least 4 digits")
147150

148151
return await node.async_set_value(value, usercode)
149152

150153

154+
async def set_usercodes(node: Node, codes: dict[int, str]) -> SetValueResult | None:
155+
"""Set the usercode to index X on the lock."""
156+
if any(len(str(usercode)) < 4 for usercode in codes.values()):
157+
raise ValueError("User codes must be at least 4 digits")
158+
159+
codes_api = [
160+
{
161+
LOCK_USERCODE_ID_PROPERTY: int(code_slot),
162+
LOCK_USERCODE_STATUS_PROPERTY: CodeSlotStatus.ENABLED,
163+
LOCK_USERCODE_PROPERTY: str(usercode),
164+
}
165+
for code_slot, usercode in codes.items()
166+
]
167+
168+
# https://zwave-js.github.io/node-zwave-js/#/api/CCs/UserCode?id=setmany
169+
data = await node.async_invoke_cc_api(
170+
CommandClass.USER_CODE, "setMany", codes_api, wait_for_result=True
171+
)
172+
173+
if not data:
174+
return None
175+
return SupervisionResult(data)
176+
177+
151178
async def clear_usercode(node: Node, code_slot: int) -> SetValueResult | None:
152179
"""Clear a code slot on the lock."""
153180
value = get_code_slot_value(node, code_slot, LOCK_USERCODE_STATUS_PROPERTY)
@@ -197,6 +224,7 @@ async def set_configuration(
197224
if errors:
198225
raise ValueError("\n".join(errors))
199226

227+
# https://zwave-js.github.io/node-zwave-js/#/api/CCs/UserCode?id=setconfiguration
200228
data = await endpoint.async_invoke_cc_api(
201229
CommandClass.DOOR_LOCK, "setConfiguration", configuration.to_dict()
202230
)

0 commit comments

Comments
 (0)