Skip to content

Commit 36fa094

Browse files
CHAD-16365: SLGA: Add implementation for Zigbee lockCodes:migrate command
1 parent 4d01c76 commit 36fa094

File tree

2 files changed

+131
-0
lines changed

2 files changed

+131
-0
lines changed

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

Lines changed: 39 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,42 @@ 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 = 1, #ordered_codes do
308+
local code_slot, code_name = ordered_codes[index], lock_codes[ ordered_codes[index] ]
309+
table.insert(lock_users, {userIndex = index, userType = "guest", userName = code_name})
310+
table.insert(lock_credentials, {userIndex = index, credentialIndex = tonumber(code_slot), credentialType = "pin"})
311+
end
312+
313+
local code_length = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.codeLength.NAME)
314+
local max_code_len = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodeLength.NAME)
315+
local min_code_len = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.minCodeLength.NAME)
316+
local max_codes = device:get_latest_state("main", capabilities.lockCodes.ID, capabilities.lockCodes.maxCodes.NAME)
317+
318+
if (code_length ~= nil) then
319+
max_code_len = code_length
320+
min_code_len = code_length
321+
end
322+
323+
device:emit_event(LockCredentials.minPinCodeLen(min_code_len, { visibility = { displayed = false } }))
324+
device:emit_event(LockCredentials.maxPinCodeLen(max_code_len, { visibility = { displayed = false } }))
325+
device:emit_event(LockCredentials.pinUsersSupported(max_codes, { visibility = { displayed = false } }))
326+
device:emit_event(LockCredentials.credentials(lock_credentials, { visibility = { displayed = false } }))
327+
device:emit_event(LockCredentials.supportedCredentials({"pin"}, { visibility = { displayed = false } }))
328+
device:emit_event(LockUsers.users(lock_users, { visibility = { displayed = false } }))
329+
device:emit_event(LockCodes.migrated(true, { visibility = { displayed = false } }))
330+
end
331+
294332
local function device_added(driver, device)
295333
lock_utils.populate_state_from_data(device)
296334

@@ -436,6 +474,7 @@ local zigbee_lock_driver = {
436474
[LockCodes.commands.requestCode.NAME] = request_code,
437475
[LockCodes.commands.setCode.NAME] = set_code,
438476
[LockCodes.commands.nameSlot.NAME] = name_slot,
477+
[LockCodes.commands.migrate.NAME] = migrate,
439478
},
440479
[Lock.ID] = {
441480
[Lock.commands.lock.NAME] = lock,
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
["2"] = "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", ["2"] = "Steven" })
58+
-- Validate state cache
59+
mock_datastore.__assert_device_store_contains(mock_device.id, "__state_cache",
60+
{
61+
main = {
62+
lockCodes = {
63+
lockCodes = {value = json.encode({ ["1"] = "Zach", ["2"] = "Steven" }) }
64+
}
65+
}
66+
}
67+
)
68+
-- Validate migration complete flag
69+
mock_datastore.__assert_device_store_contains(mock_device.id, "migrationComplete", true)
70+
71+
-- Set min/max code length attributes
72+
test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.attributes.MinPINCodeLength:build_test_attr_report(mock_device, 5) })
73+
test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.attributes.MaxPINCodeLength:build_test_attr_report(mock_device, 10) })
74+
test.socket.zigbee:__queue_receive({ mock_device.id, DoorLock.attributes.NumberOfPINUsersSupported:build_test_attr_report(mock_device, 4) })
75+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.minCodeLength(5, { visibility = { displayed = false } })))
76+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.maxCodeLength(10, { visibility = { displayed = false } })))
77+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.maxCodes(4, { visibility = { displayed = false } })))
78+
79+
-- Validate `migrate` command functionality.
80+
test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "migrate", args = {} } })
81+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.minPinCodeLen(5, { visibility = { displayed = false } })))
82+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.maxPinCodeLen(10, { visibility = { displayed = false } })))
83+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.pinUsersSupported(4, { visibility = { displayed = false } })))
84+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.credentials({{credentialIndex=1, credentialType="pin", userIndex=1}, {credentialIndex=2, credentialType="pin", userIndex=2}}, { visibility = { displayed = false } })))
85+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCredentials.supportedCredentials({"pin"}, { visibility = { displayed = false } })))
86+
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 } })))
87+
test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.lockCodes.migrated(true, { visibility = { displayed = false } })))
88+
test.wait_for_events()
89+
end
90+
)
91+
92+
test.run_registered_tests()

0 commit comments

Comments
 (0)