From e711e3f18f760d5aabe761c686e9038f0fa34ae5 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Tue, 14 Jul 2026 00:59:09 -0500 Subject: [PATCH 1/4] Matter Sensor: Support MCD for FP400 and reprofiling --- .../matter-sensor/profiles/aqara-fp400.yml | 51 ++++++ .../sensor_handlers/attribute_handlers.lua | 4 +- .../matter-sensor/src/sensor_utils/utils.lua | 44 +++++ .../air_quality_sensor_utils/utils.lua | 44 ----- .../sub_drivers/air_quality_sensor/init.lua | 3 +- .../src/sub_drivers/aqara_fp400/init.lua | 49 +++++- .../src/test/test_matter_aqara_fp400.lua | 154 +++++++++++++++++- 7 files changed, 291 insertions(+), 58 deletions(-) diff --git a/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml b/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml index c2d5b7b037..3305e7457f 100644 --- a/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml +++ b/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml @@ -12,3 +12,54 @@ components: version: 1 categories: - name: PresenceSensor +- id: sensor1 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor2 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor3 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor4 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor5 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor +- id: sensor6 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor + categories: + - name: PresenceSensor +- id: sensor8 + capabilities: + - id: presenceSensor + version: 1 + optional: true + categories: + - name: PresenceSensor diff --git a/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua index eaa8730fc4..267f5b14a4 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_handlers/attribute_handlers.lua @@ -177,9 +177,9 @@ end function AttributeHandlers.occupancy_measured_value_handler(driver, device, ib, response) if device:supports_capability(capabilities.motionSensor) then - device:emit_event(ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) + device:emit_event_for_endpoint(ib.endpoint_id, ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) else - device:emit_event(ib.data.value == 0x01 and capabilities.presenceSensor.presence("present") or capabilities.presenceSensor.presence("not present")) + device:emit_event_for_endpoint(ib.endpoint_id, ib.data.value == 0x01 and capabilities.presenceSensor.presence("present") or capabilities.presenceSensor.presence("not present")) end end diff --git a/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua index 513c9f4602..75aa284aab 100644 --- a/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua +++ b/drivers/SmartThings/matter-sensor/src/sensor_utils/utils.lua @@ -36,4 +36,48 @@ function utils.tbl_contains(array, value) return false end +--- Deeply compare two values. +--- Handles metatables. Can optionally ignore cycle checking and/or function differences. +--- +--- @param a any +--- @param b any +--- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } +--- @param seen table|nil +--- @return boolean +function utils.deep_equals(a, b, opts, seen) + if a == b then return true end -- same object + if type(a) ~= type(b) then return false end -- different type + if type(a) == "function" and opts and opts.ignore_functions then return true end + if type(a) ~= "table" then return false end -- same type but not table, thus was already compared + + -- check for cycles in table references and preserve reference topology. + if not (opts and opts.ignore_cycles) then + seen = seen or {} + seen[a] = seen[a] or {} + if seen[a][b] then + return seen[a][b] + end + seen[a][b] = true + end + + -- Compare keys/values from a + for k, v in pairs(a) do + if not utils.deep_equals(v, b[k], opts, seen) then + return false + end + end + + -- Ensure b doesn't have extra keys + for k in pairs(b) do + if a[k] == nil then + return false + end + end + + -- Compare metatables + local mt_a = getmetatable(a) + local mt_b = getmetatable(b) + return utils.deep_equals(mt_a, mt_b, opts, seen) +end + return utils diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua index c11ac46605..1a36f5f920 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua @@ -77,48 +77,4 @@ function AirQualitySensorUtils.set_supported_health_concern_values(device) end end ---- Deeply compare two values. ---- Handles metatables. Can optionally ignore cycle checking and/or function differences. ---- ---- @param a any ---- @param b any ---- @param opts table|nil { ignore_functions = boolean, ignore_cycles = boolean } ---- @param seen table|nil ---- @return boolean -function AirQualitySensorUtils.deep_equals(a, b, opts, seen) - if a == b then return true end -- same object - if type(a) ~= type(b) then return false end -- different type - if type(a) == "function" and opts and opts.ignore_functions then return true end - if type(a) ~= "table" then return false end -- same type but not table, thus was already compared - - -- check for cycles in table references and preserve reference topology. - if not (opts and opts.ignore_cycles) then - seen = seen or {} - seen[a] = seen[a] or {} - if seen[a][b] then - return seen[a][b] - end - seen[a][b] = true - end - - -- Compare keys/values from a - for k, v in pairs(a) do - if not AirQualitySensorUtils.deep_equals(v, b[k], opts, seen) then - return false - end - end - - -- Ensure b doesn't have extra keys - for k in pairs(b) do - if a[k] == nil then - return false - end - end - - -- Compare metatables - local mt_a = getmetatable(a) - local mt_b = getmetatable(b) - return AirQualitySensorUtils.deep_equals(mt_a, mt_b, opts, seen) -end - return AirQualitySensorUtils diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua index 383af643a6..1ce1cc2176 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua @@ -4,6 +4,7 @@ local version = require "version" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" +local sensor_utils = require "sensor_utils.utils" local aqs_utils = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.utils" local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" local attribute_handlers = require "sub_drivers.air_quality_sensor.air_quality_sensor_handlers.attribute_handlers" @@ -67,7 +68,7 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device) end function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, args) - if not aqs_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then + if not sensor_utils.deep_equals(device.profile, args.old_st_store.profile, { ignore_functions = true }) then if device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) then --re-up subscription with new capabilities using the modular supports_capability override device:extend_device("supports_capability_by_id", aqs_utils.supports_capability_by_id_modular) diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua index 5f47c86047..e74272e67b 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua @@ -1,21 +1,64 @@ -- Copyright 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local capabilities = require "st.capabilities" +local sensor_utils = require "sensor_utils.utils" + +local function endpoint_to_component(device, endpoint_id) + local ENDPOINT_TO_COMPONENT_MAP = { + [3] = "sensor1", + [4] = "sensor2", + [5] = "sensor3", + [6] = "sensor4", + [7] = "sensor5", + [8] = "sensor6", + [9] = "sensor7", + } + return ENDPOINT_TO_COMPONENT_MAP[endpoint_id] or "main" +end + +local function match_profile(device) + local enabled_optional_component_capabilities = {} + for _, ep in ipairs(device.endpoints) do + -- since EP0 is the root node, EP1 is a permanent Presence Sensor, and EP2 is a permanent Light Sensor + if ep.endpoint_id > 2 then + -- ex. sensor1, sensor2, etc. for EP3, EP4, etc. + table.insert(enabled_optional_component_capabilities,{ string.format("sensor%d", ep.endpoint_id - 2), { capabilities.presenceSensor.ID } }) + end + end + device:try_update_metadata({profile = "aqara-fp400", optional_component_capabilities = enabled_optional_component_capabilities }) +end + + local Fp400LifecycleHandlers = {} --- overwrite to avoid unnecessary metadata update calls -function Fp400LifecycleHandlers.do_configure() end +function Fp400LifecycleHandlers.do_configure(driver, device) + match_profile(device) +end --- overwrite to avoid unnecessary metadata update calls function Fp400LifecycleHandlers.driver_switched(driver, device) + match_profile(device) device:try_update_metadata({provisioning_state = "PROVISIONED"}) end +function Fp400LifecycleHandlers.info_changed(driver, device, event, args) + if not sensor_utils.deep_equals(args.old_st_store.endpoints, device.endpoints) then + match_profile(device) + end +end + +function Fp400LifecycleHandlers.device_init(driver, device) + device:set_endpoint_to_component_fn(endpoint_to_component) + device:subscribe() +end + local aqara_fp400_handler = { NAME = "aqara-fp400", lifecycle_handlers = { doConfigure = Fp400LifecycleHandlers.do_configure, driverSwitched = Fp400LifecycleHandlers.driver_switched, + infoChanged = Fp400LifecycleHandlers.info_changed, + init = Fp400LifecycleHandlers.device_init, }, can_handle = require("sub_drivers.aqara_fp400.can_handle"), } diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua index 7f384c744e..f322bef38c 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua @@ -20,16 +20,49 @@ local matter_endpoints = { endpoint_id = 1, clusters = { {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor + } + }, + { + endpoint_id = 2, + clusters = { {cluster_id = clusters.IlluminanceMeasurement.ID, cluster_type = "SERVER"}, }, + device_types = { + {device_type_id = 0x0106, device_type_revision = 1} -- Light Sensor + } + }, + { + endpoint_id = 3, + clusters = { + {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, device_types = { {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor } - } + }, + { + endpoint_id = 5, + clusters = { + {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor + } + }, +} + +local enabled_optional_component_capability_pairs = { + { "sensor1", { capabilities.presenceSensor.ID } }, + { "sensor3", { capabilities.presenceSensor.ID } } } local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("aqara-fp400.yml"), + profile = t_utils.get_profile_definition("aqara-fp400.yml", + { enabled_optional_capabilities = enabled_optional_component_capability_pairs } + ), manufacturer_info = { vendor_id = 0x115F, product_id = 0x2009, @@ -44,7 +77,6 @@ local function subscribe_on_init(dev) end local function test_init() - test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = subscribe_on_init(mock_device) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) @@ -52,11 +84,11 @@ end test.set_test_init_function(test_init) test.register_coroutine_test( - "Test no profile change on doConfigure for FP400", + "Test profile change on doConfigure for FP400", function() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) - -- The FP400 sub-driver overrides doConfigure to be a no-op -- When doConfigure completes successfully, the framework automatically provisions the device + mock_device:expect_metadata_update({ profile = "aqara-fp400", optional_component_capabilities = enabled_optional_component_capability_pairs }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) test.wait_for_events() end, @@ -70,7 +102,7 @@ test.register_coroutine_test( function() local current_profile = mock_device.profile.id test.socket.device_lifecycle:__queue_receive({ mock_device.id, "driverSwitched" }) - -- The FP400 sub-driver overrides driverSwitched to only update provisioning state + mock_device:expect_metadata_update({ profile = "aqara-fp400", optional_component_capabilities = enabled_optional_component_capability_pairs }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -- Ensure profile has not changed test.wait_for_events() @@ -84,6 +116,7 @@ test.register_coroutine_test( test.register_message_test( "Occupancy reports should generate correct presence messages", { + -- from EP1 { channel = "matter", direction = "receive", @@ -109,7 +142,61 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_device:generate_test_message("main", capabilities.presenceSensor.presence("not present")) - } + }, + -- from EP3 + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 3, 1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("sensor1", capabilities.presenceSensor.presence("present")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 3, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("sensor1", capabilities.presenceSensor.presence("not present")) + }, + -- from EP5 + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 5, 1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("sensor3", capabilities.presenceSensor.presence("present")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 5, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("sensor3", capabilities.presenceSensor.presence("not present")) + }, }, { min_api_version = 17 @@ -124,7 +211,7 @@ test.register_message_test( direction = "receive", message = { mock_device.id, - clusters.IlluminanceMeasurement.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 21370) + clusters.IlluminanceMeasurement.attributes.MeasuredValue:build_test_report_data(mock_device, 2, 21370) } }, { @@ -138,4 +225,55 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Test profile change on endpoints increment in infoChanged for FP400", + function() + local current_profile_id = mock_device.profile.id + local incremented_matter_endpoints = matter_endpoints + table.insert(incremented_matter_endpoints, { + endpoint_id = 4, + clusters = { + {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- Occupancy Sensor + } + }) + local incremented_enabled_optional_component_capability_pairs = { + { "sensor1", { capabilities.presenceSensor.ID } }, + { "sensor3", { capabilities.presenceSensor.ID } }, + { "sensor2", { capabilities.presenceSensor.ID } }, + } + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ endpoints = incremented_matter_endpoints })) + mock_device:expect_metadata_update({ profile = "aqara-fp400", optional_component_capabilities = incremented_enabled_optional_component_capability_pairs }) + -- Ensure profile has not changed + test.wait_for_events() + assert(mock_device.profile.id == current_profile_id, "Profile should not change on infoChanged") + end, + { + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Test profile change on endpoints decrement in infoChanged for FP400", + function() + local current_profile_id = mock_device.profile.id + local decremented_matter_endpoints = matter_endpoints + table.remove(decremented_matter_endpoints, 4) -- remove EP3 + local decremented_enabled_optional_component_capability_pairs = { + { "sensor3", { capabilities.presenceSensor.ID } }, + { "sensor2", { capabilities.presenceSensor.ID } }, + } + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ endpoints = decremented_matter_endpoints })) + mock_device:expect_metadata_update({ profile = "aqara-fp400", optional_component_capabilities = decremented_enabled_optional_component_capability_pairs }) + -- Ensure profile has not changed + test.wait_for_events() + assert(mock_device.profile.id == current_profile_id, "Profile should not change on infoChanged") + end, + { + min_api_version = 17 + } +) + test.run_registered_tests() From 5f83682f608b165a3c0be638572f39b8afb828a0 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Tue, 14 Jul 2026 11:11:41 -0500 Subject: [PATCH 2/4] add missing component, update comp2ep mapping --- .../matter-sensor/profiles/aqara-fp400.yml | 5 +++++ .../src/sub_drivers/aqara_fp400/init.lua | 21 ++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml b/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml index 3305e7457f..d4c9fde930 100644 --- a/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml +++ b/drivers/SmartThings/matter-sensor/profiles/aqara-fp400.yml @@ -54,6 +54,11 @@ components: optional: true categories: - name: PresenceSensor +- id: sensor7 + capabilities: + - id: presenceSensor + version: 1 + optional: true categories: - name: PresenceSensor - id: sensor8 diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua index e74272e67b..07e026fe9d 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/aqara_fp400/init.lua @@ -4,28 +4,28 @@ local capabilities = require "st.capabilities" local sensor_utils = require "sensor_utils.utils" +local COMPONENT_TO_ENDPOINT_MAP = "__COMPONENT_TO_ENDPOINT_MAP" + local function endpoint_to_component(device, endpoint_id) - local ENDPOINT_TO_COMPONENT_MAP = { - [3] = "sensor1", - [4] = "sensor2", - [5] = "sensor3", - [6] = "sensor4", - [7] = "sensor5", - [8] = "sensor6", - [9] = "sensor7", - } - return ENDPOINT_TO_COMPONENT_MAP[endpoint_id] or "main" + local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {} + for component_id, ep_id in pairs(map) do + if ep_id == endpoint_id then return component_id end + end + return "main" end local function match_profile(device) local enabled_optional_component_capabilities = {} + local component_to_endpoint_map = {} for _, ep in ipairs(device.endpoints) do -- since EP0 is the root node, EP1 is a permanent Presence Sensor, and EP2 is a permanent Light Sensor if ep.endpoint_id > 2 then + component_to_endpoint_map[string.format("sensor%d", ep.endpoint_id - 2)] = ep.endpoint_id -- ex. sensor1, sensor2, etc. for EP3, EP4, etc. table.insert(enabled_optional_component_capabilities,{ string.format("sensor%d", ep.endpoint_id - 2), { capabilities.presenceSensor.ID } }) end end + device:set_field(COMPONENT_TO_ENDPOINT_MAP, component_to_endpoint_map, { persist = true }) device:try_update_metadata({profile = "aqara-fp400", optional_component_capabilities = enabled_optional_component_capabilities }) end @@ -44,6 +44,7 @@ end function Fp400LifecycleHandlers.info_changed(driver, device, event, args) if not sensor_utils.deep_equals(args.old_st_store.endpoints, device.endpoints) then match_profile(device) + device:subscribe() end end From ea21532f3a82715a98c56b79487cdc3c1005cfc4 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Tue, 14 Jul 2026 11:34:03 -0500 Subject: [PATCH 3/4] fix tests considering new subscriptions and e2c mapping --- .../src/test/test_matter_aqara_fp400.lua | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua index f322bef38c..e6259275db 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua @@ -2,9 +2,11 @@ -- Licensed under the Apache License, Version 2.0 local test = require "integration_test" +local st_utils = require "st.utils" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local t_utils = require "integration_test.utils" +local sensor_utils = require "sensor_utils.utils" local matter_endpoints = { { @@ -70,14 +72,14 @@ local mock_device = test.mock_device.build_test_matter_device({ endpoints = matter_endpoints }) -local function subscribe_on_init(dev) +local function do_subscribe(dev) local subscribe_request = clusters.OccupancySensing.attributes.Occupancy:subscribe(dev) subscribe_request:merge(clusters.IlluminanceMeasurement.attributes.MeasuredValue:subscribe(dev)) return subscribe_request end local function test_init() - local subscribe_request = subscribe_on_init(mock_device) + local subscribe_request = do_subscribe(mock_device) test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) end @@ -91,6 +93,10 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ profile = "aqara-fp400", optional_component_capabilities = enabled_optional_component_capability_pairs }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) test.wait_for_events() + assert(sensor_utils.deep_equals(st_utils.deep_copy(mock_device:get_field("__COMPONENT_TO_ENDPOINT_MAP")), { + sensor1 = 3, + sensor3 = 5 + }), "Component to endpoint map should be set correctly on doConfigure") end, { min_api_version = 17 @@ -199,7 +205,15 @@ test.register_message_test( }, }, { - min_api_version = 17 + test_init = function () + local subscribe_request = do_subscribe(mock_device) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.mock_device.add_test_device(mock_device) + -- we can assume this will be set elsewhere-- see other tests. + mock_device:set_field("__COMPONENT_TO_ENDPOINT_MAP", + { sensor1 = 3, sensor3 = 5 }, { persist = true } + ) + end } ) @@ -246,9 +260,16 @@ test.register_coroutine_test( } test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ endpoints = incremented_matter_endpoints })) mock_device:expect_metadata_update({ profile = "aqara-fp400", optional_component_capabilities = incremented_enabled_optional_component_capability_pairs }) + local subscribe_request = do_subscribe(mock_device) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) -- Ensure profile has not changed test.wait_for_events() assert(mock_device.profile.id == current_profile_id, "Profile should not change on infoChanged") + assert(sensor_utils.deep_equals(st_utils.deep_copy(mock_device:get_field("__COMPONENT_TO_ENDPOINT_MAP")), { + sensor1 = 3, + sensor3 = 5, + sensor2 = 4 + }), "Component to endpoint map should be set correctly on doConfigure") end, { min_api_version = 17 @@ -267,9 +288,15 @@ test.register_coroutine_test( } test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ endpoints = decremented_matter_endpoints })) mock_device:expect_metadata_update({ profile = "aqara-fp400", optional_component_capabilities = decremented_enabled_optional_component_capability_pairs }) + local subscribe_request = do_subscribe(mock_device) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) -- Ensure profile has not changed test.wait_for_events() assert(mock_device.profile.id == current_profile_id, "Profile should not change on infoChanged") + assert(sensor_utils.deep_equals(st_utils.deep_copy(mock_device:get_field("__COMPONENT_TO_ENDPOINT_MAP")), { + sensor3 = 5, + sensor2 = 4 + }), "Component to endpoint map should be set correctly on doConfigure") end, { min_api_version = 17 From 30a04ffea65847fdc288122802d128e923660833 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Tue, 14 Jul 2026 11:34:18 -0500 Subject: [PATCH 4/4] fixup! whitespace --- .../src/test/test_matter_aqara_fp400.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua index e6259275db..81688e4479 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_aqara_fp400.lua @@ -206,13 +206,13 @@ test.register_message_test( }, { test_init = function () - local subscribe_request = do_subscribe(mock_device) - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) - test.mock_device.add_test_device(mock_device) - -- we can assume this will be set elsewhere-- see other tests. - mock_device:set_field("__COMPONENT_TO_ENDPOINT_MAP", - { sensor1 = 3, sensor3 = 5 }, { persist = true } - ) + local subscribe_request = do_subscribe(mock_device) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.mock_device.add_test_device(mock_device) + -- we can assume this will be set elsewhere-- see other tests. + mock_device:set_field("__COMPONENT_TO_ENDPOINT_MAP", + { sensor1 = 3, sensor3 = 5 }, { persist = true } + ) end } )