From b249b8c3873156200aabe367ca00dc55a17d9644 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Fri, 3 Oct 2025 10:34:10 -0500 Subject: [PATCH 1/4] add modular lock subscription updates --- .../matter-lock/src/new-matter-lock/init.lua | 24 +++- .../src/test/test_matter_lock_modular.lua | 126 +++++++++++++++++- 2 files changed, 147 insertions(+), 3 deletions(-) diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua index 7ae8a26e6f..880f33b4ee 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -285,9 +285,29 @@ local function match_profile_switch(driver, device) device:try_update_metadata({profile = profile_name}) end +local function compare_components(synced_components, prev_components) + if #synced_components ~= #prev_components then + return false + end + for _, component in pairs(synced_components) do + if (prev_components[component.id] == nil) or + (#component.capabilities ~= #prev_components[component.id].capabilities) then + return false + end + for _, capability in pairs(component.capabilities) do + if prev_components[component.id][capability.id] == nil then + return false + end + end + end + return true +end + local function info_changed(driver, device, event, args) - if device.profile.id == args.old_st_store.profile.id then - return + if device.profile.id == args.old_st_store.profile.id and + version.api >= 15 and version.rpc >= 9 and -- ignore component check for FW<58 + compare_components(device.profile.components, args.old_st_store.profile.components) then + return end for cap_id, attributes in pairs(subscribed_attributes) do if device:supports_capability_by_id(cap_id) then diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index 5dd117a70e..ca903b808b 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -173,6 +173,45 @@ local mock_device_user_pin_schedule_unlatch = test.mock_device.build_test_matter } }) +local mock_device_modular = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("lock-modular-embedded-unlatch.yml"), + manufacturer_info = { + vendor_id = 0x147F, + product_id = 0x0001, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0016, device_type_revision = 1 } -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = DoorLock.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0x1591, -- PIN & USR & COTA & WDSCH & YDSCH & UNLATCH + }, + { + cluster_id = clusters.PowerSource.ID, + cluster_type = "SERVER", + feature_map = 10 + }, + }, + device_types = { + { device_type_id = 0x000A, device_type_revision = 1 } -- Door Lock + } + } + } +}) + + local function test_init() test.disable_startup_messages() -- subscribe request @@ -271,6 +310,27 @@ local function test_init_user_pin_schedule_unlatch() mock_device_user_pin_schedule_unlatch:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end +local function test_init_modular() + test.disable_startup_messages() + -- subscribe request + local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_modular) + subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_modular)) + -- add test device + test.mock_device.add_test_device(mock_device_modular) + -- actual onboarding flow + test.socket.device_lifecycle:__queue_receive({ mock_device_modular.id, "added" }) + test.socket.capability:__expect_send( + mock_device_modular:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + test.socket.matter:__expect_send({mock_device_modular.id, clusters.PowerSource.attributes.AttributeList:read()}) + test.socket.device_lifecycle:__queue_receive({ mock_device_modular.id, "init" }) + test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_modular.id, "doConfigure" }) + mock_device_modular:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + test.set_test_init_function(test_init) test.register_coroutine_test( @@ -517,7 +577,7 @@ test.register_coroutine_test( }) } ) - test.socket.capability:__expect_send( + test.socket.capability:__expect_send( mock_device_user_pin_schedule_unlatch:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "unlatched", "not fully locked"}, {visibility = {displayed = false}})) ) test.socket.capability:__expect_send( @@ -529,4 +589,68 @@ test.register_coroutine_test( { test_init = test_init_user_pin_schedule_unlatch } ) +test.register_coroutine_test( + "Test modular lock profile update (modular to modular) with user, pin. schedule, and unlatch supported. Ensure infoChanged updates subscription", + function() + test.socket.matter:__queue_receive( + { + mock_device_modular.id, + clusters.PowerSource.attributes.AttributeList:build_test_report_data(mock_device_modular, 1, + { + uint32(0), + uint32(1), + uint32(2), + uint32(12), -- BatPercentRemaining + uint32(14), -- BatChargeLevel + uint32(31), + uint32(65528), + uint32(65529), + uint32(65531), + uint32(65532), + uint32(65533), + }) + } + ) + test.socket.capability:__expect_send( + mock_device_modular:generate_test_message("main", capabilities.lock.supportedLockValues({"locked", "unlocked", "unlatched", "not fully locked"}, {visibility = {displayed = false}})) + ) + test.socket.capability:__expect_send( + mock_device_modular:generate_test_message("main", capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}})) + ) + mock_device_modular:expect_metadata_update({ profile = "lock-modular-embedded-unlatch", optional_component_capabilities = {{"main", {"lockUsers", "lockCredentials", "lockSchedules", "battery"}}} }) + + local updated_device_profile = t_utils.get_profile_definition("lock-modular-embedded-unlatch.yml", + {enabled_optional_capabilities = {{ "main", {"lockUsers", "lockCredentials", "lockSchedules", "battery"}}, + },} + ) + updated_device_profile.id = "00000000-1111-2222-3333-000000000010" + + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive(mock_device_modular:generate_info_changed({ profile = updated_device_profile })) + + local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device_modular) + subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.attributes.NumberOfPINUsersSupported:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.attributes.MaxPINCodeLength:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.attributes.MinPINCodeLength:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.attributes.RequirePINforRemoteOperation:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.events.LockOperation:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.events.DoorLockAlarm:subscribe(mock_device_modular)) + subscribe_request:merge(DoorLock.events.LockUserChange:subscribe(mock_device_modular)) + subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device_modular)) + test.socket.matter:__expect_send({mock_device_modular.id, subscribe_request}) + test.socket.capability:__expect_send( + mock_device_modular:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) + ) + -- test.socket.capability:__expect_send( + -- mock_device_modular:generate_test_message("main", capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) + -- ) + + end, + { test_init = test_init_modular } +) + test.run_registered_tests() From 706b5c07ec2bd8cabe4991cbc7051d9d1ce06340 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 8 Oct 2025 13:25:22 -0500 Subject: [PATCH 2/4] update test file --- .../matter-lock/src/test/test_matter_lock_modular.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua index ca903b808b..d6382faa06 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_modular.lua @@ -14,7 +14,6 @@ local test = require "integration_test" local capabilities = require "st.capabilities" -test.add_package_capability("lockAlarm.yml") local clusters = require "st.matter.clusters" local t_utils = require "integration_test.utils" local uint32 = require "st.matter.data_types.Uint32" @@ -645,10 +644,9 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device_modular:generate_test_message("main", capabilities.lockAlarm.alarm.clear({state_change = true})) ) - -- test.socket.capability:__expect_send( - -- mock_device_modular:generate_test_message("main", capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) - -- ) - + test.socket.capability:__expect_send( + mock_device_modular:generate_test_message("main", capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) + ) end, { test_init = test_init_modular } ) From 77808c368dc6799b524b53944ad1460117c4e7cb Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Sat, 11 Oct 2025 13:03:10 -0500 Subject: [PATCH 3/4] remove embedded lockAlarm custom capability def --- .../matter-lock/capabilities/lockAlarm.yml | 27 ------------------- .../src/test/test_aqara_matter_lock.lua | 1 - .../src/test/test_bridged_matter_lock.lua | 1 - .../matter-lock/src/test/test_matter_lock.lua | 1 - .../src/test/test_matter_lock_battery.lua | 1 - .../test/test_matter_lock_batteryLevel.lua | 1 - .../src/test/test_matter_lock_codes.lua | 1 - .../src/test/test_matter_lock_cota.lua | 1 - .../src/test/test_matter_lock_unlatch.lua | 1 - .../src/test/test_new_matter_lock.lua | 1 - .../src/test/test_new_matter_lock_battery.lua | 1 - 11 files changed, 37 deletions(-) delete mode 100644 drivers/SmartThings/matter-lock/capabilities/lockAlarm.yml diff --git a/drivers/SmartThings/matter-lock/capabilities/lockAlarm.yml b/drivers/SmartThings/matter-lock/capabilities/lockAlarm.yml deleted file mode 100644 index c10f387dbf..0000000000 --- a/drivers/SmartThings/matter-lock/capabilities/lockAlarm.yml +++ /dev/null @@ -1,27 +0,0 @@ -id: lockAlarm -version: 1 -status: proposed -name: Lock Alarm -ephemeral: false -attributes: - alarm: - schema: - type: object - properties: - value: - type: string - enum: - - clear - - lockFactoryReset - - damaged - - forcedOpeningAttempt - - unableToLockTheDoor - - notClosedForALongTime - - highTemperature - - attemptsExceeded - - physicalImpact - additionalProperties: false - required: - - value - enumCommands: [] -commands: {} \ No newline at end of file diff --git a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua index 41ab9840fb..5c4add0ff5 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_aqara_matter_lock.lua @@ -15,7 +15,6 @@ local test = require "integration_test" test.set_rpc_version(0) local capabilities = require "st.capabilities" -test.add_package_capability("lockAlarm.yml") local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" diff --git a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua index 41ecbd612e..0d17cafda6 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_bridged_matter_lock.lua @@ -13,7 +13,6 @@ -- limitations under the License. local test = require "integration_test" -test.add_package_capability("lockAlarm.yml") local t_utils = require "integration_test.utils" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua index 0123d43239..0db34172d5 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua @@ -14,7 +14,6 @@ local test = require "integration_test" local capabilities = require "st.capabilities" -test.add_package_capability("lockAlarm.yml") local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua index 92af7fcabc..8e0be6359d 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_battery.lua @@ -13,7 +13,6 @@ -- limitations under the License. local test = require "integration_test" -test.add_package_capability("lockAlarm.yml") local t_utils = require "integration_test.utils" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua index d76556a42d..c53547025c 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_batteryLevel.lua @@ -14,7 +14,6 @@ local test = require "integration_test" local capabilities = require "st.capabilities" -test.add_package_capability("lockAlarm.yml") local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua index ad68ffbe3b..2960d905c2 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_codes.lua @@ -14,7 +14,6 @@ local test = require "integration_test" local capabilities = require "st.capabilities" -test.add_package_capability("lockAlarm.yml") local t_utils = require "integration_test.utils" local json = require "st.json" local clusters = require "st.matter.clusters" diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua index f25fe2a19c..57468ef2fc 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_cota.lua @@ -14,7 +14,6 @@ local test = require "integration_test" local capabilities = require "st.capabilities" -test.add_package_capability("lockAlarm.yml") local t_utils = require "integration_test.utils" local json = require "st.json" local clusters = require "st.matter.clusters" diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua index 421134ec2a..38154d7b04 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock_unlatch.lua @@ -15,7 +15,6 @@ local test = require "integration_test" test.set_rpc_version(0) local capabilities = require "st.capabilities" -test.add_package_capability("lockAlarm.yml") local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" local DoorLock = clusters.DoorLock diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index ab9ac68c88..7d5a59ff32 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -15,7 +15,6 @@ local test = require "integration_test" test.set_rpc_version(0) local capabilities = require "st.capabilities" -test.add_package_capability("lockAlarm.yml") local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" local DoorLock = clusters.DoorLock diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua index d58bfb1bdd..6926f1d62d 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock_battery.lua @@ -15,7 +15,6 @@ local test = require "integration_test" test.set_rpc_version(0) local capabilities = require "st.capabilities" -test.add_package_capability("lockAlarm.yml") local clusters = require "st.matter.clusters" local t_utils = require "integration_test.utils" local uint32 = require "st.matter.data_types.Uint32" From d891f62d585318392455dd2fa659902e06050904 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Tue, 14 Oct 2025 18:24:23 -0500 Subject: [PATCH 4/4] simplify logic --- .../matter-lock/src/new-matter-lock/init.lua | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua index 880f33b4ee..cbab5a4db0 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -33,6 +33,8 @@ local MIN_EPOCH_S = 0 local MAX_EPOCH_S = 0xffffffff local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00 +local MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED" + local RESPONSE_STATUS_MAP = { [DoorLock.types.DlStatus.SUCCESS] = "success", [DoorLock.types.DlStatus.FAILURE] = "failure", @@ -248,6 +250,7 @@ local function match_profile_modular(driver, device) table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities}) device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs}) + device:set_field(MODULAR_PROFILE_UPDATED, true) end local function match_profile_switch(driver, device) @@ -285,30 +288,11 @@ local function match_profile_switch(driver, device) device:try_update_metadata({profile = profile_name}) end -local function compare_components(synced_components, prev_components) - if #synced_components ~= #prev_components then - return false - end - for _, component in pairs(synced_components) do - if (prev_components[component.id] == nil) or - (#component.capabilities ~= #prev_components[component.id].capabilities) then - return false - end - for _, capability in pairs(component.capabilities) do - if prev_components[component.id][capability.id] == nil then - return false - end - end - end - return true -end - local function info_changed(driver, device, event, args) - if device.profile.id == args.old_st_store.profile.id and - version.api >= 15 and version.rpc >= 9 and -- ignore component check for FW<58 - compare_components(device.profile.components, args.old_st_store.profile.components) then - return + if device.profile.id == args.old_st_store.profile.id and not device:get_field(MODULAR_PROFILE_UPDATED) then + return end + device:set_field(MODULAR_PROFILE_UPDATED, nil) for cap_id, attributes in pairs(subscribed_attributes) do if device:supports_capability_by_id(cap_id) then for _, attr in ipairs(attributes) do