Skip to content

Commit 39cfed8

Browse files
CHAD-16365: SLGA: Add implementation for Zigbee lockCodes:migrate command
1 parent 6118e50 commit 39cfed8

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

drivers/SmartThings/zigbee-lock/src/init.lua

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ local capabilities = require "st.capabilities"
2828
local Battery = capabilities.battery
2929
local Lock = capabilities.lock
3030
local LockCodes = capabilities.lockCodes
31+
local LockCredentials = capabilities.lockCredentials
32+
local LockUsers = capabilities.lockUsers
3133

3234
-- Enums
3335
local UserStatusEnum = LockCluster.types.DrlkUserStatus
@@ -291,6 +293,41 @@ local name_slot = function(driver, device, command)
291293
end
292294
end
293295

296+
local migrate = function(driver, device, command)
297+
local lock_users = {}
298+
local lock_credentials = {}
299+
local lock_codes = lock_utils.get_lock_codes(device)
300+
local ordered_codes = {}
301+
302+
for code in pairs(lock_codes) do
303+
table.insert(ordered_codes, code)
304+
end
305+
306+
table.sort(ordered_codes)
307+
for index, code_slot in ipairs(ordered_codes) do
308+
table.insert(lock_users, {userIndex = index, userType = "guest", userName = lock_codes[code_slot]})
309+
table.insert(lock_credentials, {userIndex = index, credentialIndex = tonumber(code_slot), credentialType = "pin"})
310+
end
311+
312+
local code_length = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.codeLength.NAME)
313+
local max_code_len = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodeLength.NAME)
314+
local min_code_len = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.minCodeLength.NAME)
315+
local max_codes = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodes.NAME)
316+
317+
if (code_length ~= nil) then
318+
max_code_len = code_length
319+
min_code_len = code_length
320+
end
321+
322+
device:emit_event(LockCredentials.minPinCodeLen(min_code_len, { visibility = { displayed = false } }))
323+
device:emit_event(LockCredentials.maxPinCodeLen(max_code_len, { visibility = { displayed = false } }))
324+
device:emit_event(LockCredentials.pinUsersSupported(max_codes, { visibility = { displayed = false } }))
325+
device:emit_event(LockCredentials.credentials(lock_credentials, { visibility = { displayed = false } }))
326+
device:emit_event(LockCredentials.supportedCredentials({"pin"}, { visibility = { displayed = false } }))
327+
device:emit_event(LockUsers.users(lock_users, { visibility = { displayed = false } }))
328+
device:emit_event(LockCodes.migrated(true, { visibility = { displayed = false } }))
329+
end
330+
294331
local function device_added(driver, device)
295332
lock_utils.populate_state_from_data(device)
296333

@@ -436,6 +473,7 @@ local zigbee_lock_driver = {
436473
[LockCodes.commands.requestCode.NAME] = request_code,
437474
[LockCodes.commands.setCode.NAME] = set_code,
438475
[LockCodes.commands.nameSlot.NAME] = name_slot,
476+
[LockCodes.commands.migrate.NAME] = migrate,
439477
},
440478
[Lock.ID] = {
441479
[Lock.commands.lock.NAME] = lock,
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
-- Copyright 2025 SmartThings
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
15+
-- Mock out globals
16+
local test = require "integration_test"
17+
local zigbee_test_utils = require "integration_test.zigbee_test_utils"
18+
local t_utils = require "integration_test.utils"
19+
20+
local clusters = require "st.zigbee.zcl.clusters"
21+
local PowerConfiguration = clusters.PowerConfiguration
22+
local DoorLock = clusters.DoorLock
23+
local Alarm = clusters.Alarms
24+
local capabilities = require "st.capabilities"
25+
26+
local json = require "st.json"
27+
28+
local mock_datastore = require "integration_test.mock_env_datastore"
29+
30+
local mock_device = test.mock_device.build_test_zigbee_device(
31+
{
32+
profile = t_utils.get_profile_definition("base-lock.yml"),
33+
data = {
34+
lockCodes = json.encode({
35+
["1"] = "Zach",
36+
["5"] = "Steven"
37+
}),
38+
}
39+
}
40+
)
41+
42+
zigbee_test_utils.prepare_zigbee_env_info()
43+
local function test_init()end
44+
45+
test.set_test_init_function(test_init)
46+
47+
test.register_coroutine_test(
48+
"Device called 'migrate' command",
49+
function()
50+
test.mock_device.add_test_device(mock_device)
51+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
52+
test.socket.zigbee:__expect_send({ mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) })
53+
test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.attributes.LockState:read(mock_device) })
54+
test.socket.zigbee:__expect_send({ mock_device.id, Alarm.attributes.AlarmCount:read(mock_device) })
55+
test.wait_for_events()
56+
-- Validate lockCodes field
57+
mock_datastore.__assert_device_store_contains(mock_device.id, "lockCodes", { ["1"] = "Zach", ["5"] = "Steven" })
58+
-- Validate migration complete flag
59+
mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true)
60+
61+
-- Set min/max code length attributes
62+
test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.attributes.MinPINCodeLength:build_test_attr_report(mock_device, 5) })
63+
test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.attributes.MaxPINCodeLength:build_test_attr_report(mock_device, 10) })
64+
test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, 4) })
65+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.minCodeLength(5, { visibility = { displayed = false } })))
66+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.maxCodeLength(10, { visibility = { displayed = false } })))
67+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(4, { visibility = { displayed = false } })))
68+
test.wait_for_events()
69+
-- Validate `migrate` command functionality.
70+
test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "migrate", args = {} } })
71+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.minPinCodeLen(5, { visibility = { displayed = false } })))
72+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.maxPinCodeLen(10, { visibility = { displayed = false } })))
73+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.pinUsersSupported(4, { visibility = { displayed = false } })))
74+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.credentials({{credentialIndex=1, credentialType="pin", userIndex=1}, {credentialIndex=5, credentialType="pin", userIndex=2}}, { visibility = { displayed = false } })))
75+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.supportedCredentials({"pin"}, { visibility = { displayed = false } })))
76+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockUsers.users({{userIndex=1, userName="Zach", userType="guest"}, {userIndex=2, userName="Steven", userType="guest"}}, { visibility = { displayed = false } })))
77+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })))
78+
test.wait_for_events()
79+
end
80+
)
81+
82+
test.run_registered_tests()

0 commit comments

Comments
 (0)