From 89520fea9bf38b3c1ed5c0af7f3779fb354b5f12 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 10 Oct 2024 00:30:28 -0400 Subject: [PATCH] Add lock util function to set multiple usercodes --- test/util/test_lock.py | 42 +++++++++++++++++++++ zwave_js_server/const/command_class/lock.py | 1 + zwave_js_server/util/lock.py | 34 +++++++++++++++-- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/test/util/test_lock.py b/test/util/test_lock.py index bda6bddf8..6d64d17bb 100644 --- a/test/util/test_lock.py +++ b/test/util/test_lock.py @@ -11,6 +11,10 @@ ATTR_IN_USE, ATTR_NAME, ATTR_USERCODE, + LOCK_USERCODE_ID_PROPERTY, + LOCK_USERCODE_PROPERTY, + LOCK_USERCODE_STATUS_PROPERTY, + CodeSlotStatus, DoorLockCCConfigurationSetOptions, OperationType, ) @@ -24,6 +28,7 @@ get_usercodes, set_configuration, set_usercode, + set_usercodes, ) from .const import CODE_SLOTS @@ -102,6 +107,43 @@ async def test_set_usercode(lock_schlage_be469, mock_command, uuid4): assert len(ack_commands) == 1 +async def test_set_usercodes(lock_schlage_be469, mock_command, uuid4): + """Test set_usercodes utility function.""" + node = lock_schlage_be469 + ack_commands = mock_command( + {"command": "endpoint.invoke_cc_api", "endpoint": 0, "nodeId": node.node_id}, + {"response": {"status": 255}}, + ) + + # Test wrong types to ensure values get converted + await set_usercodes(node, {"1": 1234}) + assert len(ack_commands) == 1 + assert ack_commands[0] == { + "command": "endpoint.invoke_cc_api", + "commandClass": 99, + "endpoint": 0, + "methodName": "setMany", + "nodeId": 20, + "messageId": uuid4, + "args": [ + [ + { + LOCK_USERCODE_STATUS_PROPERTY: CodeSlotStatus.ENABLED, + LOCK_USERCODE_ID_PROPERTY: 1, + LOCK_USERCODE_PROPERTY: "1234", + } + ] + ], + } + + # Test invalid code length + with pytest.raises(ValueError): + await set_usercodes(node, {1: "123"}) + + # assert no new command calls + assert len(ack_commands) == 1 + + async def test_clear_usercode(lock_schlage_be469, mock_command, uuid4): """Test clear_usercode utility function.""" node = lock_schlage_be469 diff --git a/zwave_js_server/const/command_class/lock.py b/zwave_js_server/const/command_class/lock.py index 3be9714af..6783b4bf0 100644 --- a/zwave_js_server/const/command_class/lock.py +++ b/zwave_js_server/const/command_class/lock.py @@ -118,6 +118,7 @@ class CodeSlotStatus(IntEnum): # User Code CC constants LOCK_USERCODE_PROPERTY = "userCode" +LOCK_USERCODE_ID_PROPERTY = "userId" LOCK_USERCODE_STATUS_PROPERTY = "userIdStatus" ATTR_CODE_SLOT = "code_slot" diff --git a/zwave_js_server/util/lock.py b/zwave_js_server/util/lock.py index bb33de719..843a23b4a 100644 --- a/zwave_js_server/util/lock.py +++ b/zwave_js_server/util/lock.py @@ -14,6 +14,7 @@ CURRENT_BLOCK_TO_BLOCK_PROPERTY, CURRENT_HOLD_AND_RELEASE_TIME_PROPERTY, CURRENT_TWIST_ASSIST_PROPERTY, + LOCK_USERCODE_ID_PROPERTY, LOCK_USERCODE_PROPERTY, LOCK_USERCODE_STATUS_PROPERTY, CodeSlotStatus, @@ -68,7 +69,7 @@ def _get_code_slots(node: Node, include_usercode: bool = False) -> list[CodeSlot except NotFoundError: return slots - code_slot = int(value.property_key) # type: ignore[arg-type] + code_slot = int(value.property_key) in_use = ( None if status_value.value is None @@ -104,7 +105,7 @@ def get_usercode(node: Node, code_slot: int) -> CodeSlot: value = get_code_slot_value(node, code_slot, LOCK_USERCODE_PROPERTY) status_value = get_code_slot_value(node, code_slot, LOCK_USERCODE_STATUS_PROPERTY) - code_slot = int(value.property_key) # type: ignore[arg-type] + code_slot = int(value.property_key) in_use = ( None if status_value.value is None @@ -130,6 +131,7 @@ async def get_usercode_from_node(node: Node, code_slot: int) -> CodeSlot: This call will populate the ValueDB and trigger value update events from the driver. """ + # https://zwave-js.github.io/node-zwave-js/#/api/CCs/UserCode?id=get await node.async_invoke_cc_api( CommandClass.USER_CODE, "get", code_slot, wait_for_result=True ) @@ -141,13 +143,38 @@ async def set_usercode( ) -> SetValueResult | None: """Set the usercode to index X on the lock.""" value = get_code_slot_value(node, code_slot, LOCK_USERCODE_PROPERTY) + usercode = str(usercode) - if len(str(usercode)) < 4: + if len(usercode) < 4: raise ValueError("User code must be at least 4 digits") return await node.async_set_value(value, usercode) +async def set_usercodes(node: Node, codes: dict[int, str]) -> SetValueResult | None: + """Set the usercode to index X on the lock.""" + if any(len(str(usercode)) < 4 for usercode in codes.values()): + raise ValueError("User codes must be at least 4 digits") + + codes_api = [ + { + LOCK_USERCODE_ID_PROPERTY: int(code_slot), + LOCK_USERCODE_STATUS_PROPERTY: CodeSlotStatus.ENABLED, + LOCK_USERCODE_PROPERTY: str(usercode), + } + for code_slot, usercode in codes.items() + ] + + # https://zwave-js.github.io/node-zwave-js/#/api/CCs/UserCode?id=setmany + data = await node.async_invoke_cc_api( + CommandClass.USER_CODE, "setMany", codes_api, wait_for_result=True + ) + + if not data: + return None + return SupervisionResult(data) + + async def clear_usercode(node: Node, code_slot: int) -> SetValueResult | None: """Clear a code slot on the lock.""" value = get_code_slot_value(node, code_slot, LOCK_USERCODE_STATUS_PROPERTY) @@ -197,6 +224,7 @@ async def set_configuration( if errors: raise ValueError("\n".join(errors)) + # https://zwave-js.github.io/node-zwave-js/#/api/CCs/UserCode?id=setconfiguration data = await endpoint.async_invoke_cc_api( CommandClass.DOOR_LOCK, "setConfiguration", configuration.to_dict() )