From b637d9cf5833c1f12a4419c53ecce4eb8def3344 Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Mon, 25 Sep 2023 12:40:50 -0500 Subject: [PATCH 1/6] Matter Switch: fix endpoint to component mapping The matter switch driver was initially written with the assumption that for multi-endpoint devices the endpoints would be sequentially numbered starting from 1. However, this is not a requirement in the matter spec, and devices like composed bridged devices often do not follow this assumption. To address this, endpoints are now queried on device add and then mapped to the corresponding component. --- .../SmartThings/matter-switch/src/init.lua | 73 ++++++++--------- .../src/test/test_matter_switch.lua | 3 +- .../src/test/test_multi_switch.lua | 81 +++++++++++++++++++ 3 files changed, 120 insertions(+), 37 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 81ac2420e3..b1b24eb2dc 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -29,39 +29,25 @@ local COLOR_TEMPERATURE_KELVIN_MIN = 1 local COLOR_TEMPERATURE_MIRED_MAX = CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MIN local COLOR_TEMPERATURE_MIRED_MIN = CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MAX +local ENDPOINT_TO_COMPONENT_MAP = "__endpoint_to_component_map" + local function convert_huesat_st_to_matter(val) return math.floor((val * 0xFE) / 100.0 + 0.5) end ---- component_to_endpoint helper function to handle situations where ---- device does not have endpoint ids in sequential order from 1 ---- In this case the function returns the lowest endpoint value that isn't 0 -local function find_default_endpoint(device, component) - local res = device.MATTER_DEFAULT_ENDPOINT - local eps = device:get_endpoints(nil) - table.sort(eps) - for _, v in ipairs(eps) do - if v ~= 0 then --0 is the matter RootNode endpoint - res = v - break - end +local function component_to_endpoint(device, component_name) + local map = device:get_field(ENDPOINT_TO_COMPONENT_MAP) or {} + for ep, component in pairs(map) do + if component == component_name then return ep end end - return res -end - -local function component_to_endpoint(device, component_id) - -- Assumes matter endpoint layout is sequentional starting at 1. - local ep_num = component_id:match("switch(%d)") - return ep_num and tonumber(ep_num) or find_default_endpoint(device, component_id) end local function endpoint_to_component(device, ep) - local switch_comp = string.format("switch%d", ep) - if device.profile.components[switch_comp] ~= nil then - return switch_comp - else - return "main" + local map = device:get_field(ENDPOINT_TO_COMPONENT_MAP) or {} + if map[ep] and device.profile.components[map[ep]] then + return map[ep] end + return "main" end local function device_init(driver, device) @@ -71,6 +57,24 @@ local function device_init(driver, device) device:subscribe() end +local function device_added(driver, device) + local switch_eps = device:get_endpoints(clusters.OnOff.ID) + table.sort(switch_eps) + + local endpoint_map = {} + local current_component_number = 1 + for _, ep in ipairs(switch_eps) do + if current_component_number == 1 then + endpoint_map[ep] = "main" + else + endpoint_map[ep] = string.format("switch%d", current_component_number) + end + current_component_number = current_component_number + 1 + end + + device:set_field(ENDPOINT_TO_COMPONENT_MAP, endpoint_map, {persist = true}) +end + local function device_removed(driver, device) log.info("device removed") end @@ -82,18 +86,14 @@ local function do_configure(driver, device) -- where devices with multiple endpoints with the same device type cannot be detected local switch_eps = device:get_endpoints(clusters.OnOff.ID) local num_switch_eps = #switch_eps - table.sort(switch_eps) - --Default MCD switch handling depends on consecutive endpoint numbering - if num_switch_eps == switch_eps[num_switch_eps] then - if num_switch_eps > 1 then - device:try_update_metadata({profile = string.format("switch-%d", math.min(num_switch_eps, MAX_MULTI_SWITCH_EPS))}) - end - if num_switch_eps > MAX_MULTI_SWITCH_EPS then - error(string.format( - "Matter multi switch device will not function. Profile doesn't exist with %d components", - num_switch_eps - )) - end + if num_switch_eps > 1 then + device:try_update_metadata({profile = string.format("switch-%d", math.min(num_switch_eps, MAX_MULTI_SWITCH_EPS))}) + end + if num_switch_eps > MAX_MULTI_SWITCH_EPS then + error(string.format( + "Matter multi switch device will have limited function. Profile doesn't exist with %d components", + num_switch_eps + )) end end @@ -280,6 +280,7 @@ end local matter_driver_template = { lifecycle_handlers = { init = device_init, + added = device_added, removed = device_removed, doConfigure = do_configure, }, 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 4063e8e74b..f6354e7552 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -91,6 +91,7 @@ local function test_init() end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_no_hue_sat) for i, cluster in ipairs(cluster_subscribe_list) do if i > 1 then @@ -99,7 +100,7 @@ local function test_init() end test.socket.matter:__expect_send({mock_device_no_hue_sat.id, subscribe_request}) test.mock_device.add_test_device(mock_device_no_hue_sat) - + test.socket.device_lifecycle:__queue_receive({ mock_device_no_hue_sat.id, "added" }) end test.set_test_init_function(test_init) diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua index 3b2bbefcfc..a74c6ca332 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua @@ -100,6 +100,52 @@ local mock_2switch = test.mock_device.build_test_matter_device({ } }) +local mock_3switch_non_sequential = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("light-binary.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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 = 10, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + }, + device_types = { + device_type_id = 0x0100, device_type_revision = 2, -- On/Off Light + } + }, + { + endpoint_id = 14, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + }, + device_types = { + device_type_id = 0x0100, device_type_revision = 2, -- On/Off Light + } + }, + { + endpoint_id = 15, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + }, + device_types = { + device_type_id = 0x0100, device_type_revision = 2, -- On/Off Light + } + }, + } +}) + local function test_init() local cluster_subscribe_list = { @@ -109,9 +155,15 @@ local function test_init() local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch) test.socket.matter:__expect_send({mock_3switch.id, subscribe_request}) test.mock_device.add_test_device(mock_3switch) + test.socket.device_lifecycle:__queue_receive({ mock_3switch.id, "added" }) local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_2switch) test.socket.matter:__expect_send({mock_2switch.id, subscribe_request}) test.mock_device.add_test_device(mock_2switch) + test.socket.device_lifecycle:__queue_receive({ mock_2switch.id, "added" }) + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch_non_sequential) + test.socket.matter:__expect_send({mock_3switch_non_sequential.id, subscribe_request}) + test.mock_device.add_test_device(mock_3switch_non_sequential) + test.socket.device_lifecycle:__queue_receive({ mock_3switch_non_sequential.id, "added" }) end test.set_test_init_function(test_init) @@ -130,6 +182,13 @@ test.register_coroutine_test( mock_2switch:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end) +test.register_coroutine_test( + "Profile change for 3 switch device with non sequential endpoints", function() + test.socket.device_lifecycle:__queue_receive({ mock_3switch_non_sequential.id, "doConfigure" }) + mock_3switch_non_sequential:expect_metadata_update({ profile = "switch-3" }) + mock_3switch_non_sequential:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end) + test.register_message_test( "On command to component switch should send the appropriate commands", { @@ -152,4 +211,26 @@ test.register_message_test( } ) +test.register_message_test( + "On command to component switch should send the appropriate commands for devices with non-sequential endpoints", + { + { + channel = "capability", + direction = "receive", + message = { + mock_3switch_non_sequential.id, + { capability = "switch", component = "switch3", command = "on", args = { } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_3switch_non_sequential.id, + clusters.OnOff.server.commands.On(mock_3switch_non_sequential, 15) -- switch 3 is on endpoint 15 + } + } + } +) + test.run_registered_tests() From a9e0c3ac0e37798c0fd3f4927ce6b4ad80d88fc0 Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Mon, 25 Sep 2023 14:12:16 -0500 Subject: [PATCH 2/6] fixup! move mapping to 'init', remove added --- .../SmartThings/matter-switch/src/init.lua | 59 +++++++++++++------ .../src/test/test_matter_switch.lua | 2 - .../src/test/test_multi_switch.lua | 3 - 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index b1b24eb2dc..0535690a44 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -35,11 +35,50 @@ local function convert_huesat_st_to_matter(val) return math.floor((val * 0xFE) / 100.0 + 0.5) end +--- component_to_endpoint helper function to handle situations where +--- device does not have endpoint ids in sequential order from 1 +--- In this case the function returns the lowest endpoint value that isn't 0 +local function find_default_endpoint(device, component) + local res = device.MATTER_DEFAULT_ENDPOINT + local eps = device:get_endpoints(nil) + table.sort(eps) + for _, v in ipairs(eps) do + if v ~= 0 then --0 is the matter RootNode endpoint + res = v + break + end + end + return res +end + +local function create_endpoint_to_component_map(device) + local switch_eps = device:get_endpoints(clusters.OnOff.ID) + table.sort(switch_eps) + + local endpoint_map = {} + local current_component_number = 1 + + -- For switch devices, the profile components follow the naming convention "switch%d", + -- with the exception of "main" being the first component. Each component will then map + -- to the next lowest endpoint that hasn't been mapped yet. + for _, ep in ipairs(switch_eps) do + if current_component_number == 1 then + endpoint_map[ep] = "main" + else + endpoint_map[ep] = string.format("switch%d", current_component_number) + end + current_component_number = current_component_number + 1 + end + + device:set_field(ENDPOINT_TO_COMPONENT_MAP, endpoint_map, {persist = true}) +end + local function component_to_endpoint(device, component_name) local map = device:get_field(ENDPOINT_TO_COMPONENT_MAP) or {} for ep, component in pairs(map) do if component == component_name then return ep end end + return find_default_endpoint(device, component_name) end local function endpoint_to_component(device, ep) @@ -52,29 +91,12 @@ end local function device_init(driver, device) log.info_with({hub_logs=true}, "device init") + create_endpoint_to_component_map(device) device:set_component_to_endpoint_fn(component_to_endpoint) device:set_endpoint_to_component_fn(endpoint_to_component) device:subscribe() end -local function device_added(driver, device) - local switch_eps = device:get_endpoints(clusters.OnOff.ID) - table.sort(switch_eps) - - local endpoint_map = {} - local current_component_number = 1 - for _, ep in ipairs(switch_eps) do - if current_component_number == 1 then - endpoint_map[ep] = "main" - else - endpoint_map[ep] = string.format("switch%d", current_component_number) - end - current_component_number = current_component_number + 1 - end - - device:set_field(ENDPOINT_TO_COMPONENT_MAP, endpoint_map, {persist = true}) -end - local function device_removed(driver, device) log.info("device removed") end @@ -280,7 +302,6 @@ end local matter_driver_template = { lifecycle_handlers = { init = device_init, - added = device_added, removed = device_removed, doConfigure = do_configure, }, 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 f6354e7552..bedc8d77ee 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua @@ -91,7 +91,6 @@ local function test_init() end test.socket.matter:__expect_send({mock_device.id, subscribe_request}) test.mock_device.add_test_device(mock_device) - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_no_hue_sat) for i, cluster in ipairs(cluster_subscribe_list) do if i > 1 then @@ -100,7 +99,6 @@ local function test_init() end test.socket.matter:__expect_send({mock_device_no_hue_sat.id, subscribe_request}) test.mock_device.add_test_device(mock_device_no_hue_sat) - test.socket.device_lifecycle:__queue_receive({ mock_device_no_hue_sat.id, "added" }) end test.set_test_init_function(test_init) diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua index a74c6ca332..876079ed3f 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua @@ -155,15 +155,12 @@ local function test_init() local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch) test.socket.matter:__expect_send({mock_3switch.id, subscribe_request}) test.mock_device.add_test_device(mock_3switch) - test.socket.device_lifecycle:__queue_receive({ mock_3switch.id, "added" }) local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_2switch) test.socket.matter:__expect_send({mock_2switch.id, subscribe_request}) test.mock_device.add_test_device(mock_2switch) - test.socket.device_lifecycle:__queue_receive({ mock_2switch.id, "added" }) local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch_non_sequential) test.socket.matter:__expect_send({mock_3switch_non_sequential.id, subscribe_request}) test.mock_device.add_test_device(mock_3switch_non_sequential) - test.socket.device_lifecycle:__queue_receive({ mock_3switch_non_sequential.id, "added" }) end test.set_test_init_function(test_init) From d70a3099c1abcd571de9b96b22ad399797ffd37a Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Mon, 25 Sep 2023 16:06:04 -0500 Subject: [PATCH 3/6] fixup! move mapping and profile switch to init, fix unit tests --- .../SmartThings/matter-switch/src/init.lua | 43 ++++++----- .../src/test/test_multi_switch.lua | 75 ++++++++++++------- 2 files changed, 69 insertions(+), 49 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 0535690a44..76b38ab3ee 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -51,7 +51,7 @@ local function find_default_endpoint(device, component) return res end -local function create_endpoint_to_component_map(device) +local function initialize_switch(device) local switch_eps = device:get_endpoints(clusters.OnOff.ID) table.sort(switch_eps) @@ -71,6 +71,21 @@ local function create_endpoint_to_component_map(device) end device:set_field(ENDPOINT_TO_COMPONENT_MAP, endpoint_map, {persist = true}) + + -- New profiles need to be added for devices that have more switch endpoints + local MAX_MULTI_SWITCH_EPS = 7 + -- Note: This profile switching is needed because of shortcoming in the generic fingerprints + -- where devices with multiple endpoints with the same device type cannot be detected + local num_switch_eps = #switch_eps + if num_switch_eps > 1 then + device:try_update_metadata({profile = string.format("switch-%d", math.min(num_switch_eps, MAX_MULTI_SWITCH_EPS))}) + end + if num_switch_eps > MAX_MULTI_SWITCH_EPS then + error(string.format( + "Matter multi switch device will have limited function. Profile doesn't exist with %d components", + num_switch_eps + )) + end end local function component_to_endpoint(device, component_name) @@ -91,7 +106,10 @@ end local function device_init(driver, device) log.info_with({hub_logs=true}, "device init") - create_endpoint_to_component_map(device) + if not device:get_field(ENDPOINT_TO_COMPONENT_MAP) then + -- create endpoint to component map and switch profile as needed + initialize_switch(device) + end device:set_component_to_endpoint_fn(component_to_endpoint) device:set_endpoint_to_component_fn(endpoint_to_component) device:subscribe() @@ -101,24 +119,6 @@ local function device_removed(driver, device) log.info("device removed") end -local function do_configure(driver, device) - -- New profiles need to be added for devices that have more switch endpoints - local MAX_MULTI_SWITCH_EPS = 7 - -- Note: This profile switching is needed because of shortcoming in the generic fingerprints - -- where devices with multiple endpoints with the same device type cannot be detected - local switch_eps = device:get_endpoints(clusters.OnOff.ID) - local num_switch_eps = #switch_eps - if num_switch_eps > 1 then - device:try_update_metadata({profile = string.format("switch-%d", math.min(num_switch_eps, MAX_MULTI_SWITCH_EPS))}) - end - if num_switch_eps > MAX_MULTI_SWITCH_EPS then - error(string.format( - "Matter multi switch device will have limited function. Profile doesn't exist with %d components", - num_switch_eps - )) - end -end - local function handle_switch_on(driver, device, cmd) local endpoint_id = device:component_to_endpoint(cmd.component) --TODO use OnWithRecallGlobalScene for devices with the LT feature @@ -302,8 +302,7 @@ end local matter_driver_template = { lifecycle_handlers = { init = device_init, - removed = device_removed, - doConfigure = do_configure, + removed = device_removed }, matter_handlers = { attr = { diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua index 876079ed3f..92c4f843d8 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua @@ -146,8 +146,7 @@ local mock_3switch_non_sequential = test.mock_device.build_test_matter_device({ } }) - -local function test_init() +local function test_init_mock_3switch() local cluster_subscribe_list = { clusters.OnOff.attributes.OnOff, } @@ -155,37 +154,32 @@ local function test_init() local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch) test.socket.matter:__expect_send({mock_3switch.id, subscribe_request}) test.mock_device.add_test_device(mock_3switch) + mock_3switch:expect_metadata_update({ profile = "switch-3" }) +end + +local function test_init_mock_2switch() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + } + test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_2switch) test.socket.matter:__expect_send({mock_2switch.id, subscribe_request}) test.mock_device.add_test_device(mock_2switch) + mock_2switch:expect_metadata_update({ profile = "switch-2" }) +end + +local function test_init_mock_3switch_non_sequential() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + } + test.socket.matter:__set_channel_ordering("relaxed") local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_3switch_non_sequential) test.socket.matter:__expect_send({mock_3switch_non_sequential.id, subscribe_request}) test.mock_device.add_test_device(mock_3switch_non_sequential) + mock_3switch_non_sequential:expect_metadata_update({ profile = "switch-3" }) end -test.set_test_init_function(test_init) - - -test.register_coroutine_test( - "Profile change for 3 switch device", function() - test.socket.device_lifecycle:__queue_receive({ mock_3switch.id, "doConfigure" }) - mock_3switch:expect_metadata_update({ profile = "switch-3" }) - mock_3switch:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -end) - -test.register_coroutine_test( - "Profile change for 2 switch device", function() - test.socket.device_lifecycle:__queue_receive({ mock_2switch.id, "doConfigure" }) - mock_2switch:expect_metadata_update({ profile = "switch-2" }) - mock_2switch:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -end) - -test.register_coroutine_test( - "Profile change for 3 switch device with non sequential endpoints", function() - test.socket.device_lifecycle:__queue_receive({ mock_3switch_non_sequential.id, "doConfigure" }) - mock_3switch_non_sequential:expect_metadata_update({ profile = "switch-3" }) - mock_3switch_non_sequential:expect_metadata_update({ provisioning_state = "PROVISIONED" }) -end) +-- The custom "test_init" function also checks that the appropriate profile is switched on init test.register_message_test( "On command to component switch should send the appropriate commands", { @@ -205,9 +199,35 @@ test.register_message_test( clusters.OnOff.server.commands.On(mock_3switch, 2) } } - } + }, + { test_init = test_init_mock_3switch } ) +-- The custom "test_init" function also checks that the appropriate profile is switched on init +test.register_message_test( + "On command to component switch should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_2switch.id, + { capability = "switch", component = "main", command = "on", args = { } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_2switch.id, + clusters.OnOff.server.commands.On(mock_2switch, 1) + } + } + }, + { test_init = test_init_mock_2switch } +) + +-- The custom "test_init" function also checks that the appropriate profile is switched on init test.register_message_test( "On command to component switch should send the appropriate commands for devices with non-sequential endpoints", { @@ -227,7 +247,8 @@ test.register_message_test( clusters.OnOff.server.commands.On(mock_3switch_non_sequential, 15) -- switch 3 is on endpoint 15 } } - } + }, + { test_init = test_init_mock_3switch_non_sequential } ) test.run_registered_tests() From 5ae1591ed3d6c93b1c2a2d3599eb1b2e5ad3ab16 Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Mon, 25 Sep 2023 17:44:29 -0500 Subject: [PATCH 4/6] fixup! fix unit test name --- .../SmartThings/matter-switch/src/test/test_multi_switch.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua index 92c4f843d8..b9e3dc4e70 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch.lua @@ -205,7 +205,7 @@ test.register_message_test( -- The custom "test_init" function also checks that the appropriate profile is switched on init test.register_message_test( - "On command to component switch should send the appropriate commands", + "On command to main component should send the appropriate commands", { { channel = "capability", From 2ec17138768b2e55bf7fa10f7979eae59ed034db Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Tue, 26 Sep 2023 10:10:57 -0500 Subject: [PATCH 5/6] fixup! add more error logs, simplify loop --- .../SmartThings/matter-switch/src/init.lua | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 76b38ab3ee..166671e7a8 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -30,6 +30,8 @@ local COLOR_TEMPERATURE_MIRED_MAX = CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN local COLOR_TEMPERATURE_MIRED_MIN = CONVERSION_CONSTANT/COLOR_TEMPERATURE_KELVIN_MAX local ENDPOINT_TO_COMPONENT_MAP = "__endpoint_to_component_map" +-- New profiles need to be added for devices that have more switch endpoints +local MAX_MULTI_SWITCH_EPS = 7 local function convert_huesat_st_to_matter(val) return math.floor((val * 0xFE) / 100.0 + 0.5) @@ -54,26 +56,20 @@ end local function initialize_switch(device) local switch_eps = device:get_endpoints(clusters.OnOff.ID) table.sort(switch_eps) - local endpoint_map = {} - local current_component_number = 1 -- For switch devices, the profile components follow the naming convention "switch%d", -- with the exception of "main" being the first component. Each component will then map -- to the next lowest endpoint that hasn't been mapped yet. - for _, ep in ipairs(switch_eps) do - if current_component_number == 1 then + for i, ep in ipairs(switch_eps) do + if i == 1 then endpoint_map[ep] = "main" else - endpoint_map[ep] = string.format("switch%d", current_component_number) + endpoint_map[ep] = string.format("switch%d", i) end - current_component_number = current_component_number + 1 end device:set_field(ENDPOINT_TO_COMPONENT_MAP, endpoint_map, {persist = true}) - - -- New profiles need to be added for devices that have more switch endpoints - local MAX_MULTI_SWITCH_EPS = 7 -- Note: This profile switching is needed because of shortcoming in the generic fingerprints -- where devices with multiple endpoints with the same device type cannot be detected local num_switch_eps = #switch_eps @@ -82,8 +78,9 @@ local function initialize_switch(device) end if num_switch_eps > MAX_MULTI_SWITCH_EPS then error(string.format( - "Matter multi switch device will have limited function. Profile doesn't exist with %d components", - num_switch_eps + "Matter multi switch device will have limited function. Profile doesn't exist with %d components, max is %d", + num_switch_eps, + MAX_MULTI_SWITCH_EPS )) end end @@ -101,6 +98,7 @@ local function endpoint_to_component(device, ep) if map[ep] and device.profile.components[map[ep]] then return map[ep] end + log.warn_with({hub_logs = true}, string.format("Device has more than supported number of switches (max %d), mapping excess endpoint to main component", MAX_MULTI_SWITCH_EPS)) return "main" end From f8aa183ff321e025c6509ceb632d978083c66fbb Mon Sep 17 00:00:00 2001 From: Cooper Towns Date: Tue, 26 Sep 2023 11:37:40 -0500 Subject: [PATCH 6/6] fixup! add cluster to find_default_endpoint --- drivers/SmartThings/matter-switch/src/init.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 166671e7a8..ceacd916f9 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -40,9 +40,11 @@ end --- component_to_endpoint helper function to handle situations where --- device does not have endpoint ids in sequential order from 1 --- In this case the function returns the lowest endpoint value that isn't 0 +--- and supports the OnOff cluster. This is done to bypass the +--- BRIDGED_NODE_DEVICE_TYPE on bridged devices local function find_default_endpoint(device, component) local res = device.MATTER_DEFAULT_ENDPOINT - local eps = device:get_endpoints(nil) + local eps = device:get_endpoints(clusters.OnOff.ID) table.sort(eps) for _, v in ipairs(eps) do if v ~= 0 then --0 is the matter RootNode endpoint