From cf4f01fbbc1b68977557dfb433a3a3a6519bef4e Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Thu, 5 Dec 2024 12:19:54 -0600 Subject: [PATCH 1/4] Add profile for NEO SmartPlug/Button device --- .../matter-switch/profiles/plug-button.yml | 18 +++++++++++++++ .../SmartThings/matter-switch/src/init.lua | 23 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 drivers/SmartThings/matter-switch/profiles/plug-button.yml diff --git a/drivers/SmartThings/matter-switch/profiles/plug-button.yml b/drivers/SmartThings/matter-switch/profiles/plug-button.yml new file mode 100644 index 0000000000..0f9e8b1b56 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/plug-button.yml @@ -0,0 +1,18 @@ +name: plug-button +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug + - id: button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 3fab77fa3a..4dff2fe9e1 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -155,6 +155,10 @@ local child_device_profile_overrides = { { vendor_id = 0x1321, product_id = 0x000D, child_profile = "switch-binary" }, } +local fingerprint_profile_overrides = { + { vendor_id = 0x137F, product_id = 0x027B }, -- NEO Power Plug +} + local detect_matter_thing local CUMULATIVE_REPORTS_NOT_SUPPORTED = "__cumulative_reports_not_supported" @@ -500,6 +504,16 @@ local function find_child(parent, ep_id) return parent:get_child_by_parent_assigned_key(string.format("%d", ep_id)) end +local function check_fingerprint_profile_overrides(device) + for _, fingerprint in ipairs(fingerprint_profile_overrides) do + if device.manufacturer_info.vendor_id == fingerprint.vendor_id and + device.manufacturer_info.product_id == fingerprint.product_id then + return true + end + end + return false +end + local function initialize_switch(driver, device) local switch_eps = device:get_endpoints(clusters.OnOff.ID) local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) @@ -571,6 +585,15 @@ local function initialize_switch(driver, device) device:set_field(COMPONENT_TO_ENDPOINT_MAP_BUTTON, component_map, {persist = true}) end + -- If there is a custom static profile for the device, configure buttons if needed and + -- then return to prevent profile from being updated. + if check_fingerprint_profile_overrides(device) then + if #button_eps > 0 then + configure_buttons(device) + end + return + end + if #button_eps > 0 and is_supported_combination_button_switch_device_type(device, main_endpoint) then if #button_eps == 1 then profile_name = "light-level-button" From 6a16137acc4401e2a08b7cc8431d84db7ceebfe9 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Mon, 9 Dec 2024 10:35:38 -0600 Subject: [PATCH 2/4] Refactor device_init handling for button/switch initialization --- .../SmartThings/matter-switch/src/init.lua | 52 +++++-------- .../test_matter_multi_button_switch_mcd.lua | 77 +++++++++++++++++++ 2 files changed, 97 insertions(+), 32 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 4dff2fe9e1..268650d54f 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -155,10 +155,6 @@ local child_device_profile_overrides = { { vendor_id = 0x1321, product_id = 0x000D, child_profile = "switch-binary" }, } -local fingerprint_profile_overrides = { - { vendor_id = 0x137F, product_id = 0x027B }, -- NEO Power Plug -} - local detect_matter_thing local CUMULATIVE_REPORTS_NOT_SUPPORTED = "__cumulative_reports_not_supported" @@ -350,16 +346,20 @@ end --- whether the device type for an endpoint is currently supported by a profile for --- combination button/switch devices. local function is_supported_combination_button_switch_device_type(device, endpoint_id) - for _, ep in ipairs(device.endpoints) do - if ep.endpoint_id == endpoint_id then - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == DIMMABLE_LIGHT_DEVICE_TYPE_ID then - return true + local switch_eps = device:get_endpoints(clusters.OnOff.ID) + local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) + if #switch_eps > 0 and #button_eps > 0 then + for _, ep in ipairs(device.endpoints) do + if ep.endpoint_id == endpoint_id then + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == DIMMABLE_LIGHT_DEVICE_TYPE_ID or dt.device_type_id == ON_OFF_PLUG_DEVICE_TYPE_ID then + return true, dt.device_type_id + end end end end end - return false + return false, nil end local function get_first_non_zero_endpoint(endpoints) @@ -397,7 +397,8 @@ local function find_default_endpoint(device) -- default endpoint. if #switch_eps > 0 and #button_eps > 0 then local main_endpoint = get_first_non_zero_endpoint(switch_eps) - if is_supported_combination_button_switch_device_type(device, main_endpoint) then + local is_supported_device_type, _ = is_supported_combination_button_switch_device_type(device, main_endpoint) + if is_supported_device_type then return main_endpoint else device.log.warn("The main switch endpoint does not contain a supported device type for a component configuration with buttons") @@ -504,16 +505,6 @@ local function find_child(parent, ep_id) return parent:get_child_by_parent_assigned_key(string.format("%d", ep_id)) end -local function check_fingerprint_profile_overrides(device) - for _, fingerprint in ipairs(fingerprint_profile_overrides) do - if device.manufacturer_info.vendor_id == fingerprint.vendor_id and - device.manufacturer_info.product_id == fingerprint.product_id then - return true - end - end - return false -end - local function initialize_switch(driver, device) local switch_eps = device:get_endpoints(clusters.OnOff.ID) local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) @@ -585,20 +576,17 @@ local function initialize_switch(driver, device) device:set_field(COMPONENT_TO_ENDPOINT_MAP_BUTTON, component_map, {persist = true}) end - -- If there is a custom static profile for the device, configure buttons if needed and - -- then return to prevent profile from being updated. - if check_fingerprint_profile_overrides(device) then - if #button_eps > 0 then - configure_buttons(device) + local is_supported_button_switch_device, device_type_id = is_supported_combination_button_switch_device_type(device, main_endpoint) + if is_supported_button_switch_device then + if device_type_id == DIMMABLE_LIGHT_DEVICE_TYPE_ID then + profile_name = "light-level" + else + profile_name = "plug" end - return - end - - if #button_eps > 0 and is_supported_combination_button_switch_device_type(device, main_endpoint) then if #button_eps == 1 then - profile_name = "light-level-button" + profile_name = profile_name .. "-button" else - profile_name = "light-level" .. string.format("-%d-button", #button_eps) + profile_name = profile_name .. string.format("-%d-button", #button_eps) end device:try_update_metadata({profile = profile_name}) device:set_field(DEFERRED_CONFIGURE, true) 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 a5f2420557..70de16a682 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 @@ -153,6 +153,48 @@ local mock_device_mcd_unsupported_switch_device_type = test.mock_device.build_te } }) +local mock_device_plug_button = test.mock_device.build_test_matter_device({ + label = "Matter Switch", + profile = t_utils.get_profile_definition("plug-button.yml"), + manufacturer_info = { + vendor_id = 0x137F, + product_id = 0x027B, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, + }, + device_types = { + {device_type_id = 0x0016, device_type_revision = 1} -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0}, + }, + device_types = { + {device_type_id = 0x010A, device_type_revision = 1} -- OnOff Plug + } + }, + { + endpoint_id = 2, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_RELEASE, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + } + } +}) + local child_profile = t_utils.get_profile_definition("light-color-level.yml") local child_data = { profile = child_profile, @@ -243,6 +285,34 @@ local function test_init_mcd_unsupported_switch_device_type() }) end +local function test_init_plug_button() + test.socket.matter:__set_channel_ordering("relaxed") + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.Switch.server.events.InitialPress, + clusters.Switch.server.events.LongPress, + clusters.Switch.server.events.ShortRelease, + clusters.Switch.server.events.MultiPressComplete, + } + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_plug_button) + for i, clus in ipairs(cluster_subscribe_list) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_plug_button)) end + end + test.socket.matter:__expect_send({mock_device_plug_button.id, subscribe_request}) + test.mock_device.add_test_device(mock_device_plug_button) + test.socket.device_lifecycle:__queue_receive({ mock_device_plug_button.id, "added" }) + mock_device_plug_button:expect_metadata_update({ profile = "plug-button" }) + local device_info_copy = utils.deep_copy(mock_device_plug_button.raw_st_data) + device_info_copy.profile.id = "button" + local device_info_json = dkjson.encode(device_info_copy) + test.socket.device_lifecycle:__queue_receive({ mock_device_plug_button.id, "infoChanged", device_info_json }) + test.socket.matter:__expect_send({mock_device_plug_button.id, subscribe_request}) + test.socket.matter:__expect_send({mock_device_plug_button.id, subscribe_request}) + + test.socket.capability:__expect_send(mock_device_plug_button:generate_test_message("button", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(mock_device_plug_button:generate_test_message("button", button_attr.pushed({state_change = false}))) +end + test.set_test_init_function(test_init) test.register_message_test( @@ -410,5 +480,12 @@ test.register_coroutine_test( { test_init = test_init_mcd_unsupported_switch_device_type } ) +test.register_coroutine_test( + "Test initialization for plug/button device", + function() + end, + { test_init = test_init_plug_button } +) + -- run the tests test.run_registered_tests() From 30c225582c91bddb4125f86eea46734c9fd51d96 Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Mon, 9 Dec 2024 10:41:18 -0600 Subject: [PATCH 3/4] Adding additional profiles --- .../matter-switch/profiles/plug-2-button.yml | 24 ++++++++ .../matter-switch/profiles/plug-3-button.yml | 30 ++++++++++ .../matter-switch/profiles/plug-4-button.yml | 36 +++++++++++ .../matter-switch/profiles/plug-5-button.yml | 42 +++++++++++++ .../matter-switch/profiles/plug-6-button.yml | 48 +++++++++++++++ .../matter-switch/profiles/plug-7-button.yml | 54 +++++++++++++++++ .../matter-switch/profiles/plug-8-button.yml | 60 +++++++++++++++++++ 7 files changed, 294 insertions(+) create mode 100644 drivers/SmartThings/matter-switch/profiles/plug-2-button.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/plug-3-button.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/plug-4-button.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/plug-5-button.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/plug-6-button.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/plug-7-button.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/plug-8-button.yml diff --git a/drivers/SmartThings/matter-switch/profiles/plug-2-button.yml b/drivers/SmartThings/matter-switch/profiles/plug-2-button.yml new file mode 100644 index 0000000000..c9a514a5cc --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/plug-2-button.yml @@ -0,0 +1,24 @@ +name: plug-2-button +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug + - id: button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController diff --git a/drivers/SmartThings/matter-switch/profiles/plug-3-button.yml b/drivers/SmartThings/matter-switch/profiles/plug-3-button.yml new file mode 100644 index 0000000000..952f6d06d3 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/plug-3-button.yml @@ -0,0 +1,30 @@ +name: plug-3-button +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug + - id: button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController diff --git a/drivers/SmartThings/matter-switch/profiles/plug-4-button.yml b/drivers/SmartThings/matter-switch/profiles/plug-4-button.yml new file mode 100644 index 0000000000..afac64c9b4 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/plug-4-button.yml @@ -0,0 +1,36 @@ +name: plug-4-button +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug + - id: button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button4 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController diff --git a/drivers/SmartThings/matter-switch/profiles/plug-5-button.yml b/drivers/SmartThings/matter-switch/profiles/plug-5-button.yml new file mode 100644 index 0000000000..dfe0d4cf05 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/plug-5-button.yml @@ -0,0 +1,42 @@ +name: plug-5-button +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug + - id: button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button4 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button5 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController diff --git a/drivers/SmartThings/matter-switch/profiles/plug-6-button.yml b/drivers/SmartThings/matter-switch/profiles/plug-6-button.yml new file mode 100644 index 0000000000..ef8fe07d96 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/plug-6-button.yml @@ -0,0 +1,48 @@ +name: plug-6-button +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug + - id: button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button4 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button5 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button6 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController diff --git a/drivers/SmartThings/matter-switch/profiles/plug-7-button.yml b/drivers/SmartThings/matter-switch/profiles/plug-7-button.yml new file mode 100644 index 0000000000..957fcb1aab --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/plug-7-button.yml @@ -0,0 +1,54 @@ +name: plug-7-button +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug + - id: button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button4 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button5 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button6 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button7 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController diff --git a/drivers/SmartThings/matter-switch/profiles/plug-8-button.yml b/drivers/SmartThings/matter-switch/profiles/plug-8-button.yml new file mode 100644 index 0000000000..1ae3e541b8 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/plug-8-button.yml @@ -0,0 +1,60 @@ +name: plug-8-button +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug + - id: button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button4 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button5 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button6 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button7 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button8 + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController From 894ca3badf755c64e5cf7b4a6a8b84a4c227988f Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Mon, 9 Dec 2024 12:41:25 -0600 Subject: [PATCH 4/4] Remove plug-button profile, which is now included in a different PR --- .../matter-switch/profiles/plug-button.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 drivers/SmartThings/matter-switch/profiles/plug-button.yml diff --git a/drivers/SmartThings/matter-switch/profiles/plug-button.yml b/drivers/SmartThings/matter-switch/profiles/plug-button.yml deleted file mode 100644 index 0f9e8b1b56..0000000000 --- a/drivers/SmartThings/matter-switch/profiles/plug-button.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: plug-button -components: - - id: main - capabilities: - - id: switch - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: SmartPlug - - id: button - capabilities: - - id: button - version: 1 - categories: - - name: RemoteController