From a8e635c8c5b3c4d2c3efb405ab9cb8b8bf987c30 Mon Sep 17 00:00:00 2001 From: seojune Date: Fri, 16 Jan 2026 10:17:27 +0900 Subject: [PATCH 1/8] Update the system to manage credentialInfoTable within the persist area --- drivers/Aqara/aqara-lock/config.yml | 2 +- .../Aqara/aqara-lock/src/credential_utils.lua | 30 ++++++++++++++++++- drivers/Aqara/aqara-lock/src/init.lua | 7 ++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/drivers/Aqara/aqara-lock/config.yml b/drivers/Aqara/aqara-lock/config.yml index 31c0c0b6a8..3303d2d8af 100644 --- a/drivers/Aqara/aqara-lock/config.yml +++ b/drivers/Aqara/aqara-lock/config.yml @@ -1,7 +1,7 @@ name: 'Aqara Lock' packageKey: 'aqara-lock' permissions: - aqara_provisioning: {} + # aqara_provisioning: {} zigbee: {} description: "SmartThings driver for Aqara Doorlock" vendorSupportInformation: "https://www.aqara.com/en/support/" diff --git a/drivers/Aqara/aqara-lock/src/credential_utils.lua b/drivers/Aqara/aqara-lock/src/credential_utils.lua index 5725d5f55a..82eb32af57 100644 --- a/drivers/Aqara/aqara-lock/src/credential_utils.lua +++ b/drivers/Aqara/aqara-lock/src/credential_utils.lua @@ -1,3 +1,4 @@ +local log = require "log" local capabilities = require "st.capabilities" local utils = require "st.utils" @@ -6,6 +7,28 @@ local lockCredentialInfo = capabilities["stse.lockCredentialInfo"] local credential_utils = {} local HOST_COUNT = "__host_count" +local PERSIST_DATA = "__persist_area" + +credential_utils.attrCopy = function(table) + local copy = {} + for key, value in pairs(table) do + copy[key] = utils.deep_copy(value) + end + return copy +end + +credential_utils.backup_data = function(device)-- Back up data the persistent + local credentialInfoTable = utils.deep_copy(device:get_latest_state("main", lockCredentialInfo.ID, + lockCredentialInfo.credentialInfo.NAME, {})) + local backupData = credential_utils.attrCopy(credentialInfoTable) + device:set_field(PERSIST_DATA, backupData, { persist = true }) +end + +credential_utils.sync = function(driver, device) + log.warn("credential_utils.sync entry") + local credentialInfoTable = device:get_field(PERSIST_DATA) + device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) +end credential_utils.save_data = function(driver) driver.datastore:save() @@ -29,6 +52,7 @@ credential_utils.update_remote_control_status = function(driver, device, added) device:set_field(HOST_COUNT, host_cnt, { persist = true }) credential_utils.save_data(driver) + credential_utils.backup_data(device) end credential_utils.sync_all_credential_info = function(driver, device, command) @@ -39,6 +63,7 @@ credential_utils.sync_all_credential_info = function(driver, device, command) end device:emit_event(lockCredentialInfo.credentialInfo(command.args.credentialInfo, { visibility = { displayed = false } })) credential_utils.save_data(driver) + credential_utils.backup_data(device) end credential_utils.upsert_credential_info = function(driver, device, command) @@ -71,9 +96,10 @@ credential_utils.upsert_credential_info = function(driver, device, command) if credential_utils.is_exist_host(device) == false then credential_utils.update_remote_control_status(driver, device, true) end - + device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) credential_utils.save_data(driver) + credential_utils.backup_data(device) end credential_utils.delete_user = function(driver, device, command) @@ -96,6 +122,7 @@ credential_utils.delete_user = function(driver, device, command) device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) credential_utils.save_data(driver) + credential_utils.backup_data(device) end credential_utils.delete_credential = function(driver, device, command) @@ -117,6 +144,7 @@ credential_utils.delete_credential = function(driver, device, command) device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) credential_utils.save_data(driver) + credential_utils.backup_data(device) end credential_utils.find_userLabel = function(driver, device, value) diff --git a/drivers/Aqara/aqara-lock/src/init.lua b/drivers/Aqara/aqara-lock/src/init.lua index e9eb32c9b4..5500e7a44c 100644 --- a/drivers/Aqara/aqara-lock/src/init.lua +++ b/drivers/Aqara/aqara-lock/src/init.lua @@ -98,6 +98,10 @@ local function device_added(self, device) credential_utils.save_data(self) end +local function device_driverSwitched(self, device) + credential_utils.sync(self, device) +end + local function toValue(payload, start, length) return utils.deserialize_int(string.sub(payload, start, start + length - 1), length, false, false) end @@ -338,7 +342,8 @@ local aqara_locks_handler = { }, lifecycle_handlers = { init = device_init, - added = device_added + added = device_added, + driverSwitched = device_driverSwitched }, secret_data_handlers = { [security.SECRET_KIND_AQARA] = my_secret_data_handler From d7d7b7182c2e798f5ca562b9614ed88feff62433 Mon Sep 17 00:00:00 2001 From: seojune Date: Fri, 16 Jan 2026 13:30:11 +0900 Subject: [PATCH 2/8] Improvement of credential info management during Hub switch-over or replacement. --- drivers/Aqara/aqara-lock/config.yml | 2 +- drivers/Aqara/aqara-lock/src/credential_utils.lua | 6 +++--- drivers/Aqara/aqara-lock/src/init.lua | 8 ++------ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/Aqara/aqara-lock/config.yml b/drivers/Aqara/aqara-lock/config.yml index 3303d2d8af..31c0c0b6a8 100644 --- a/drivers/Aqara/aqara-lock/config.yml +++ b/drivers/Aqara/aqara-lock/config.yml @@ -1,7 +1,7 @@ name: 'Aqara Lock' packageKey: 'aqara-lock' permissions: - # aqara_provisioning: {} + aqara_provisioning: {} zigbee: {} description: "SmartThings driver for Aqara Doorlock" vendorSupportInformation: "https://www.aqara.com/en/support/" diff --git a/drivers/Aqara/aqara-lock/src/credential_utils.lua b/drivers/Aqara/aqara-lock/src/credential_utils.lua index 82eb32af57..7f3e07df18 100644 --- a/drivers/Aqara/aqara-lock/src/credential_utils.lua +++ b/drivers/Aqara/aqara-lock/src/credential_utils.lua @@ -1,4 +1,3 @@ -local log = require "log" local capabilities = require "st.capabilities" local utils = require "st.utils" @@ -25,9 +24,10 @@ credential_utils.backup_data = function(device)-- Back up data the persistent end credential_utils.sync = function(driver, device) - log.warn("credential_utils.sync entry") local credentialInfoTable = device:get_field(PERSIST_DATA) - device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + if credentialInfoTable ~= nil then + device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + end end credential_utils.save_data = function(driver) diff --git a/drivers/Aqara/aqara-lock/src/init.lua b/drivers/Aqara/aqara-lock/src/init.lua index 5500e7a44c..9afe6bb77e 100644 --- a/drivers/Aqara/aqara-lock/src/init.lua +++ b/drivers/Aqara/aqara-lock/src/init.lua @@ -86,6 +86,7 @@ local function device_init(self, device) end device:emit_event(capabilities.battery.quantity(battery_quantity)) device:emit_event(capabilities.batteryLevel.quantity(battery_quantity)) + credential_utils.sync(self, device) end local function device_added(self, device) @@ -98,10 +99,6 @@ local function device_added(self, device) credential_utils.save_data(self) end -local function device_driverSwitched(self, device) - credential_utils.sync(self, device) -end - local function toValue(payload, start, length) return utils.deserialize_int(string.sub(payload, start, start + length - 1), length, false, false) end @@ -342,8 +339,7 @@ local aqara_locks_handler = { }, lifecycle_handlers = { init = device_init, - added = device_added, - driverSwitched = device_driverSwitched + added = device_added }, secret_data_handlers = { [security.SECRET_KIND_AQARA] = my_secret_data_handler From 478df5e4d7bf65f6355f0036a269ababc78ed21d Mon Sep 17 00:00:00 2001 From: seojune Date: Mon, 19 Jan 2026 12:31:58 +0900 Subject: [PATCH 3/8] Remove whitespace --- drivers/Aqara/aqara-lock/src/credential_utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/Aqara/aqara-lock/src/credential_utils.lua b/drivers/Aqara/aqara-lock/src/credential_utils.lua index 7f3e07df18..e74944a650 100644 --- a/drivers/Aqara/aqara-lock/src/credential_utils.lua +++ b/drivers/Aqara/aqara-lock/src/credential_utils.lua @@ -96,7 +96,7 @@ credential_utils.upsert_credential_info = function(driver, device, command) if credential_utils.is_exist_host(device) == false then credential_utils.update_remote_control_status(driver, device, true) end - + device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) credential_utils.save_data(driver) credential_utils.backup_data(device) From eaad1cd31100c782dc7aa3a7adbaa390b5287f9b Mon Sep 17 00:00:00 2001 From: seojune Date: Mon, 26 Jan 2026 10:44:24 +0900 Subject: [PATCH 4/8] Revision of credential data management --- .../Aqara/aqara-lock/src/credential_utils.lua | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/drivers/Aqara/aqara-lock/src/credential_utils.lua b/drivers/Aqara/aqara-lock/src/credential_utils.lua index e74944a650..014cba0ba2 100644 --- a/drivers/Aqara/aqara-lock/src/credential_utils.lua +++ b/drivers/Aqara/aqara-lock/src/credential_utils.lua @@ -8,25 +8,24 @@ local credential_utils = {} local HOST_COUNT = "__host_count" local PERSIST_DATA = "__persist_area" -credential_utils.attrCopy = function(table) - local copy = {} +credential_utils.eventResource = function(table) + local credentialResource = {} for key, value in pairs(table) do - copy[key] = utils.deep_copy(value) + credentialResource[key] = value end - return copy + return credentialResource end credential_utils.backup_data = function(device)-- Back up data the persistent local credentialInfoTable = utils.deep_copy(device:get_latest_state("main", lockCredentialInfo.ID, lockCredentialInfo.credentialInfo.NAME, {})) - local backupData = credential_utils.attrCopy(credentialInfoTable) - device:set_field(PERSIST_DATA, backupData, { persist = true }) + device:set_field(PERSIST_DATA, credentialInfoTable, { persist = true }) end credential_utils.sync = function(driver, device) - local credentialInfoTable = device:get_field(PERSIST_DATA) - if credentialInfoTable ~= nil then - device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) + local table = device:get_field(PERSIST_DATA) or nil + if table ~= nil then + device:emit_event(lockCredentialInfo.credentialInfo(credential_utils.eventResource(table), { visibility = { displayed = false } })) end end @@ -51,8 +50,8 @@ credential_utils.update_remote_control_status = function(driver, device, added) end device:set_field(HOST_COUNT, host_cnt, { persist = true }) - credential_utils.save_data(driver) credential_utils.backup_data(device) + credential_utils.save_data(driver) end credential_utils.sync_all_credential_info = function(driver, device, command) @@ -62,8 +61,8 @@ credential_utils.sync_all_credential_info = function(driver, device, command) end end device:emit_event(lockCredentialInfo.credentialInfo(command.args.credentialInfo, { visibility = { displayed = false } })) - credential_utils.save_data(driver) credential_utils.backup_data(device) + credential_utils.save_data(driver) end credential_utils.upsert_credential_info = function(driver, device, command) @@ -98,8 +97,8 @@ credential_utils.upsert_credential_info = function(driver, device, command) end device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) - credential_utils.save_data(driver) credential_utils.backup_data(device) + credential_utils.save_data(driver) end credential_utils.delete_user = function(driver, device, command) @@ -121,8 +120,8 @@ credential_utils.delete_user = function(driver, device, command) end device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) - credential_utils.save_data(driver) credential_utils.backup_data(device) + credential_utils.save_data(driver) end credential_utils.delete_credential = function(driver, device, command) @@ -143,8 +142,8 @@ credential_utils.delete_credential = function(driver, device, command) end device:emit_event(lockCredentialInfo.credentialInfo(credentialInfoTable, { visibility = { displayed = false } })) - credential_utils.save_data(driver) credential_utils.backup_data(device) + credential_utils.save_data(driver) end credential_utils.find_userLabel = function(driver, device, value) From 4cc53eaa1772e176faf9a47e272d7221054d0802 Mon Sep 17 00:00:00 2001 From: seojune Date: Tue, 27 Jan 2026 14:05:36 +0900 Subject: [PATCH 5/8] Modify to save the current data if PERSIST_DATA is nil. --- drivers/Aqara/aqara-lock/src/credential_utils.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/Aqara/aqara-lock/src/credential_utils.lua b/drivers/Aqara/aqara-lock/src/credential_utils.lua index 014cba0ba2..163011f42c 100644 --- a/drivers/Aqara/aqara-lock/src/credential_utils.lua +++ b/drivers/Aqara/aqara-lock/src/credential_utils.lua @@ -26,6 +26,8 @@ credential_utils.sync = function(driver, device) local table = device:get_field(PERSIST_DATA) or nil if table ~= nil then device:emit_event(lockCredentialInfo.credentialInfo(credential_utils.eventResource(table), { visibility = { displayed = false } })) + else + credential_utils.backup_data(device) end end From a983b4aa4f77926e538fd632af0954929d310847 Mon Sep 17 00:00:00 2001 From: seojune Date: Thu, 29 Jan 2026 10:47:06 +0900 Subject: [PATCH 6/8] Fix code indentation --- drivers/Aqara/aqara-lock/src/credential_utils.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/Aqara/aqara-lock/src/credential_utils.lua b/drivers/Aqara/aqara-lock/src/credential_utils.lua index 163011f42c..df0e904886 100644 --- a/drivers/Aqara/aqara-lock/src/credential_utils.lua +++ b/drivers/Aqara/aqara-lock/src/credential_utils.lua @@ -11,12 +11,12 @@ local PERSIST_DATA = "__persist_area" credential_utils.eventResource = function(table) local credentialResource = {} for key, value in pairs(table) do - credentialResource[key] = value - end - return credentialResource + credentialResource[key] = value + end + return credentialResource end -credential_utils.backup_data = function(device)-- Back up data the persistent +credential_utils.backup_data = function(device) -- Back up data the persistent local credentialInfoTable = utils.deep_copy(device:get_latest_state("main", lockCredentialInfo.ID, lockCredentialInfo.credentialInfo.NAME, {})) device:set_field(PERSIST_DATA, credentialInfoTable, { persist = true }) @@ -25,7 +25,8 @@ end credential_utils.sync = function(driver, device) local table = device:get_field(PERSIST_DATA) or nil if table ~= nil then - device:emit_event(lockCredentialInfo.credentialInfo(credential_utils.eventResource(table), { visibility = { displayed = false } })) + device:emit_event(lockCredentialInfo.credentialInfo(credential_utils.eventResource(table), + { visibility = { displayed = false } })) else credential_utils.backup_data(device) end From ea494de85b40f06e563e99a578289af2576c216c Mon Sep 17 00:00:00 2001 From: seojune Date: Fri, 30 Jan 2026 10:35:13 +0900 Subject: [PATCH 7/8] Adding test cases --- .../src/test/test_aqara_lock_L100.lua | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua diff --git a/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua b/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua new file mode 100644 index 0000000000..6bf3737fdf --- /dev/null +++ b/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua @@ -0,0 +1,81 @@ +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local remoteControlStatus = capabilities.remoteControlStatus +local antiLockStatus = capabilities["stse.antiLockStatus"] +test.add_package_capability("antiLockStatus.yaml") +local lockCredentialInfo = capabilities["stse.lockCredentialInfo"] +test.add_package_capability("lockCredentialInfo.yaml") +local lockAlarm = capabilities["lockAlarm"] +test.add_package_capability("lockAlarm.yaml") +local Battery = capabilities.battery +local BatteryLevel = capabilities.batteryLevel +local Lock = capabilities.lock + +local PRI_CLU = 0xFCC0 +local PRI_ATTR = 0xFFF3 +local MFG_CODE = 0x115F + +local HOST_COUNT = "__host_count" +local PERSIST_DATA = "__persist_area" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-lock-battery.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Lumi", + model = "aqara.lock.akr001", + server_clusters = { PRI_CLU } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + local SUPPORTED_ALARM_VALUES = { "damaged", "forcedOpeningAttempt", "unableToLockTheDoor", "notClosedForALongTime", + "highTemperature", "attemptsExceeded" } + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + lockAlarm.supportedAlarmValues(SUPPORTED_ALARM_VALUES, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + Lock.supportedUnlockDirections({"fromInside", "fromOutside"}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.type("AA"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.type("AA"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.quantity(6))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.quantity(6))) + local credentialInfoData = { + { credentialId = 1, credentialType = "keypad", userId = "1", userLabel = "june", userType = "host" } + } + mock_device:set_field(PERSIST_DATA, credentialInfoData, { persist = true }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + lockCredentialInfo.credentialInfo(credentialInfoData, { visibility = { displayed = false } }))) + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle added lifecycle - only regular user", + function() + mock_device:set_field(HOST_COUNT, 1, { persist = true }) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + remoteControlStatus.remoteControlEnabled('true', { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Battery.battery(100))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", BatteryLevel.battery("normal"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + lockAlarm.alarm.clear({ visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + antiLockStatus.antiLockStatus('unknown', { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", Lock.lock("locked"))) + end +) + +test.run_registered_tests() From c7a5f130eb5067276e2037f770111236e77fb79d Mon Sep 17 00:00:00 2001 From: seojune Date: Fri, 30 Jan 2026 13:23:18 +0900 Subject: [PATCH 8/8] Remove the unused variables --- drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua b/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua index 6bf3737fdf..3801183490 100644 --- a/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua +++ b/drivers/Aqara/aqara-lock/src/test/test_aqara_lock_L100.lua @@ -1,8 +1,6 @@ local test = require "integration_test" local t_utils = require "integration_test.utils" local capabilities = require "st.capabilities" -local cluster_base = require "st.zigbee.cluster_base" -local data_types = require "st.zigbee.data_types" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local remoteControlStatus = capabilities.remoteControlStatus @@ -17,8 +15,6 @@ local BatteryLevel = capabilities.batteryLevel local Lock = capabilities.lock local PRI_CLU = 0xFCC0 -local PRI_ATTR = 0xFFF3 -local MFG_CODE = 0x115F local HOST_COUNT = "__host_count" local PERSIST_DATA = "__persist_area"