diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 2302ffc4dc..3be68c255b 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -40,6 +40,7 @@ end function SwitchLifecycleHandlers.do_configure(driver, device) if device.network_type == device_lib.NETWORK_TYPE_MATTER and not switch_utils.detect_bridge(device) then + switch_cfg.set_device_control_options(device) device_cfg.match_profile(driver, device) end end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua index 626080b147..adc0da500e 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua @@ -72,6 +72,21 @@ function SwitchDeviceConfiguration.create_child_devices(driver, device, server_o device:set_find_child(switch_utils.find_child) end +-- Per the spec, these attributes are "meant to be changed only during commissioning." +function SwitchDeviceConfiguration.set_device_control_options(device) + for _, ep_info in ipairs(device.endpoints) do + -- before the Matter 1.3 lua libs update (HUB FW 54), OptionsBitmap was defined as LevelControlOptions + if switch_utils.ep_supports_cluster(ep_info, clusters.LevelControl.ID) then + device:send(clusters.LevelControl.attributes.Options:write(device, ep_info.endpoint_id, clusters.LevelControl.types.LevelControlOptions.EXECUTE_IF_OFF)) + end + -- before the Matter 1.4 lua libs update (HUB FW 56), there was no OptionsBitmap type defined + if switch_utils.ep_supports_cluster(ep_info, clusters.ColorControl.ID) then + local excute_if_off_bit = clusters.ColorControl.types.OptionsBitmap and clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF or 0x0001 + device:send(clusters.ColorControl.attributes.Options:write(device, ep_info.endpoint_id, excute_if_off_bit)) + end + end +end + function ButtonDeviceConfiguration.update_button_profile(device, default_endpoint_id, num_button_eps) local profile_name = string.gsub(num_button_eps .. "-button", "1%-", "") -- remove the "1-" in a device with 1 button ep if switch_utils.device_type_supports_button_switch_combination(device, default_endpoint_id) then diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index 0f7ac435cb..7611d403de 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -187,6 +187,22 @@ function utils.get_endpoint_info(device, endpoint_id) return {} end +function utils.ep_supports_cluster(ep_info, cluster_id, opts) + opts = opts or {} + local clus_has_features = function(cluster, checked_feature) + return (cluster.feature_map & checked_feature) == checked_feature + end + for _, cluster in ipairs(ep_info.clusters) do + if ((cluster.cluster_id == cluster_id) + and (opts.feature_bitmap == nil or clus_has_features(cluster, opts.feature_bitmap)) + and ((opts.cluster_type == nil and cluster.cluster_type == "SERVER" or cluster.cluster_type == "BOTH") + or (opts.cluster_type == cluster.cluster_type)) + or (cluster_id == nil)) then + return true + end + end +end + -- Fallback handler for responses that dont have their own handler function utils.matter_handler(driver, device, response_block) device.log.info(string.format("Fallback handler for %s", response_block)) diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua index 920e24c6aa..7d2f962f49 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua @@ -400,6 +400,7 @@ test.register_coroutine_test( "Test profile change on init for Electrical Sensor device type", function() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 2, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) mock_device:expect_metadata_update({ profile = "plug-level-power-energy-powerConsumption" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end, diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_light_fan.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_light_fan.lua index d50d16b8fe..a35f5bda50 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_light_fan.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_light_fan.lua @@ -87,6 +87,8 @@ local function test_init() test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, mock_device_ep1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, mock_device_ep1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) mock_device:expect_metadata_update({ profile = "light-color-level-fan" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua index ab35eff35c..0eb10ba6f7 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua @@ -226,6 +226,9 @@ local function test_init() parent_assigned_child_key = string.format("%d", mock_device_ep5) }) expect_configure_buttons() + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, mock_device_ep1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, mock_device_ep5, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, mock_device_ep5, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) -- simulate the profile change update taking affect and the device info changing diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua index ba9ae3c921..e38003ce5a 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -200,7 +200,7 @@ local function test_init_color_temp() subscribe_request:merge(cluster:subscribe(mock_device_color_temp)) end end - test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device_color_temp.id, "added" }) test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request}) @@ -208,7 +208,16 @@ local function test_init_color_temp() test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_color_temp.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_color_temp.id, + clusters.LevelControl.attributes.Options:write(mock_device_color_temp, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + test.socket.matter:__expect_send({ + mock_device_color_temp.id, + clusters.ColorControl.attributes.Options:write(mock_device_color_temp, 1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_color_temp:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request}) end local function test_init_extended_color() @@ -221,13 +230,21 @@ local function test_init_extended_color() end test.socket.matter:__expect_send({mock_device_extended_color.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_extended_color.id, "added" }) - test.socket.matter:__expect_send({mock_device_extended_color.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_extended_color.id, "init" }) test.socket.matter:__expect_send({mock_device_extended_color.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_extended_color.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_extended_color.id, + clusters.LevelControl.attributes.Options:write(mock_device_extended_color, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + test.socket.matter:__expect_send({ + mock_device_extended_color.id, + clusters.ColorControl.attributes.Options:write(mock_device_extended_color, 1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_extended_color:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.socket.matter:__expect_send({mock_device_extended_color.id, subscribe_request}) end test.register_message_test( diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua index 560230cdc7..920b9a4b5f 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua @@ -143,7 +143,7 @@ local mock_device_mounted_on_off_control = test.mock_device.build_test_matter_de endpoint_id = 7, clusters = { {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - {cluster_id = clusters.LevelControl.ID, cluster_type = "CLIENT", feature_map = 2}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, }, device_types = { @@ -173,7 +173,7 @@ local mock_device_mounted_dimmable_load_control = test.mock_device.build_test_ma endpoint_id = 7, clusters = { {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, - {cluster_id = clusters.LevelControl.ID, cluster_type = "CLIENT", feature_map = 2}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, }, device_types = { @@ -420,6 +420,10 @@ local function test_init_parent_child_switch_types() test.socket.matter:__expect_send({mock_device_parent_child_switch_types.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_switch_types.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_parent_child_switch_types.id, + clusters.LevelControl.attributes.Options:write(mock_device_parent_child_switch_types, 7, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_parent_child_switch_types:expect_metadata_update({ profile = "switch-level" }) mock_device_parent_child_switch_types:expect_metadata_update({ provisioning_state = "PROVISIONED" }) @@ -463,6 +467,10 @@ end local function test_init_dimmer() test.mock_device.add_test_device(mock_device_dimmer) test.socket.device_lifecycle:__queue_receive({ mock_device_dimmer.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_dimmer.id, + clusters.LevelControl.attributes.Options:write(mock_device_dimmer, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_dimmer:expect_metadata_update({ profile = "switch-level" }) mock_device_dimmer:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -494,6 +502,10 @@ local function test_init_mounted_on_off_control() test.socket.matter:__expect_send({mock_device_mounted_on_off_control.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_on_off_control.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_mounted_on_off_control.id, + clusters.LevelControl.attributes.Options:write(mock_device_mounted_on_off_control, 7, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_mounted_on_off_control:expect_metadata_update({ profile = "switch-binary" }) mock_device_mounted_on_off_control:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -502,6 +514,9 @@ local function test_init_mounted_dimmable_load_control() test.mock_device.add_test_device(mock_device_mounted_dimmable_load_control) local cluster_subscribe_list = { clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.LevelControl.attributes.MaxLevel, } local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_mounted_dimmable_load_control) for i, cluster in ipairs(cluster_subscribe_list) do @@ -516,6 +531,10 @@ local function test_init_mounted_dimmable_load_control() test.socket.matter:__expect_send({mock_device_mounted_dimmable_load_control.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_dimmable_load_control.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_mounted_dimmable_load_control.id, + clusters.LevelControl.attributes.Options:write(mock_device_mounted_dimmable_load_control, 7, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_mounted_dimmable_load_control:expect_metadata_update({ profile = "switch-level" }) mock_device_mounted_dimmable_load_control:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -557,6 +576,14 @@ local function test_init_parent_child_different_types() test.socket.matter:__expect_send({mock_device_parent_child_different_types.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_different_types.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_parent_child_different_types.id, + clusters.LevelControl.attributes.Options:write(mock_device_parent_child_different_types, 10, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) + test.socket.matter:__expect_send({ + mock_device_parent_child_different_types.id, + clusters.ColorControl.attributes.Options:write(mock_device_parent_child_different_types, 10, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_parent_child_different_types:expect_metadata_update({ profile = "switch-binary" }) mock_device_parent_child_different_types:expect_metadata_update({ provisioning_state = "PROVISIONED" }) @@ -575,6 +602,10 @@ local function test_init_parent_child_unsupported_device_type() test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_unsupported_device_type.id, "init" }) test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_unsupported_device_type.id, "doConfigure" }) mock_device_parent_child_unsupported_device_type:expect_metadata_update({ profile = "switch-binary" }) + test.socket.matter:__expect_send({ + mock_device_parent_child_unsupported_device_type.id, + clusters.LevelControl.attributes.Options:write(mock_device_parent_child_unsupported_device_type, 10, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_parent_child_unsupported_device_type:expect_metadata_update({ provisioning_state = "PROVISIONED" }) mock_device_parent_child_unsupported_device_type:expect_device_create({ @@ -609,6 +640,10 @@ local function test_init_light_level_motion() test.socket.matter:__expect_send({mock_device_light_level_motion.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device_light_level_motion.id, "doConfigure" }) + test.socket.matter:__expect_send({ + mock_device_light_level_motion.id, + clusters.LevelControl.attributes.Options:write(mock_device_light_level_motion, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF) + }) mock_device_light_level_motion:expect_metadata_update({ profile = "light-level-motion" }) mock_device_light_level_motion:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua index 0d3cd90854..178a5bf37d 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua @@ -178,6 +178,9 @@ local function test_init() test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, child1_ep, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, child2_ep, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, child2_ep, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) mock_device:expect_metadata_update({ profile = "light-binary" }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) @@ -222,7 +225,9 @@ for i, endpoint in ipairs(mock_device_parent_child_endpoints_non_sequential.endp end local function test_init_parent_child_endpoints_non_sequential() - test.mock_device.add_test_device(mock_device_parent_child_endpoints_non_sequential) + local unsup_mock_device = mock_device_parent_child_endpoints_non_sequential + + test.mock_device.add_test_device(unsup_mock_device) local cluster_subscribe_list = { clusters.OnOff.attributes.OnOff, clusters.LevelControl.attributes.CurrentLevel, @@ -236,49 +241,53 @@ local function test_init_parent_child_endpoints_non_sequential() clusters.ColorControl.attributes.CurrentX, clusters.ColorControl.attributes.CurrentY } - local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_parent_child_endpoints_non_sequential) + local subscribe_request = cluster_subscribe_list[1]:subscribe(unsup_mock_device) for i, cluster in ipairs(cluster_subscribe_list) do if i > 1 then - subscribe_request:merge(cluster:subscribe(mock_device_parent_child_endpoints_non_sequential)) + subscribe_request:merge(cluster:subscribe(unsup_mock_device)) end end - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "added" }) - test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "added" }) + test.socket.matter:__expect_send({unsup_mock_device.id, subscribe_request}) + + test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "init" }) + test.socket.matter:__expect_send({unsup_mock_device.id, subscribe_request}) - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "init" }) - test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "doConfigure" }) + test.socket.matter:__expect_send({unsup_mock_device.id, clusters.LevelControl.attributes.Options:write(unsup_mock_device, child1_ep_non_sequential, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({unsup_mock_device.id, clusters.LevelControl.attributes.Options:write(unsup_mock_device, child2_ep_non_sequential, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) + test.socket.matter:__expect_send({unsup_mock_device.id, clusters.ColorControl.attributes.Options:write(unsup_mock_device, child2_ep_non_sequential, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)}) - test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "doConfigure" }) - mock_device_parent_child_endpoints_non_sequential:expect_metadata_update({ profile = "light-binary" }) - mock_device_parent_child_endpoints_non_sequential:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + unsup_mock_device:expect_metadata_update({ profile = "light-binary" }) + unsup_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) for _, child in pairs(mock_children_non_sequential) do test.mock_device.add_test_device(child) end - mock_device_parent_child_endpoints_non_sequential:expect_device_create({ + unsup_mock_device:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 2", profile = "light-color-level", - parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_device_id = unsup_mock_device.id, parent_assigned_child_key = string.format("%d", child2_ep_non_sequential) }) -- switch-binary will be selected as an overridden child device profile - mock_device_parent_child_endpoints_non_sequential:expect_device_create({ + unsup_mock_device:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 3", profile = "switch-binary", - parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_device_id = unsup_mock_device.id, parent_assigned_child_key = string.format("%d", child3_ep_non_sequential) }) - mock_device_parent_child_endpoints_non_sequential:expect_device_create({ + unsup_mock_device:expect_device_create({ type = "EDGE_CHILD", label = "Matter Switch 4", profile = "light-level", - parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_device_id = unsup_mock_device.id, parent_assigned_child_key = string.format("%d", child1_ep_non_sequential) }) end