diff --git a/drivers/SmartThings/matter-switch/profiles/3-button-motion.yml b/drivers/SmartThings/matter-switch/profiles/3-button-motion.yml new file mode 100644 index 0000000000..e02e66d9aa --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/3-button-motion.yml @@ -0,0 +1,30 @@ +name: 3-button-motion +components: + - id: main + capabilities: + - id: button + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + 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: motion + capabilities: + - id: motionSensor + version: 1 + categories: + - name: MotionSensor \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/profiles/6-button-motion.yml b/drivers/SmartThings/matter-switch/profiles/6-button-motion.yml new file mode 100644 index 0000000000..e77e40ff57 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/6-button-motion.yml @@ -0,0 +1,48 @@ +name: 6-button-motion +components: + - id: main + capabilities: + - id: button + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + 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: motion + capabilities: + - id: motionSensor + version: 1 + categories: + - name: MotionSensor \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index a4bf6e64f9..e9cd5106fb 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -558,8 +558,8 @@ local function find_child(parent, ep_id) return parent:get_child_by_parent_assigned_key(string.format("%d", ep_id)) end -local function build_button_component_map(device, main_endpoint, button_eps) - -- create component mapping on the main profile button endpoints +local function build_button_component_map(device, main_endpoint, button_eps, motion_eps) + -- create component mapping on the main profile button endpoints and motion endpoint table.sort(button_eps) local component_map = {} component_map["main"] = main_endpoint @@ -572,14 +572,20 @@ local function build_button_component_map(device, main_endpoint, button_eps) component_map[button_component] = ep end end + if #motion_eps > 0 then + component_map["motion"] = motion_eps[1] + end device:set_field(COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true}) end -local function build_button_profile(device, main_endpoint, num_button_eps) +local function build_button_profile(device, main_endpoint, num_button_eps, num_motion_eps) local profile_name = string.gsub(num_button_eps .. "-button", "1%-", "") -- remove the "1-" in a device with 1 button ep if device_type_supports_button_switch_combination(device, main_endpoint) then profile_name = "light-level-" .. profile_name end + if num_motion_eps > 0 then + profile_name = profile_name .. "-motion" + end local battery_supported = #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) > 0 if battery_supported then -- battery profiles are configured later, in power_source_attribute_list_handler device:send(clusters.PowerSource.attributes.AttributeList:read(device)) @@ -651,11 +657,12 @@ end local function initialize_buttons_and_switches(driver, device, main_endpoint) local profile_found = false local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}) + local motion_eps = device:get_endpoints(clusters.OccupancySensing.ID) if tbl_contains(STATIC_BUTTON_PROFILE_SUPPORTED, #button_eps) then - build_button_profile(device, main_endpoint, #button_eps) + build_button_profile(device, main_endpoint, #button_eps, #motion_eps) -- All button endpoints found will be added as additional components in the profile containing the main_endpoint. -- The resulting endpoint to component map is saved in the COMPONENT_TO_ENDPOINT_MAP field - build_button_component_map(device, main_endpoint, button_eps) + build_button_component_map(device, main_endpoint, button_eps, motion_eps) configure_buttons(device) profile_found = true end @@ -1129,7 +1136,7 @@ local function illuminance_attr_handler(driver, device, ib, response) end local function occupancy_attr_handler(driver, device, ib, response) - 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()) end local function cumul_energy_imported_handler(driver, device, ib, response) diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_motion.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_motion.lua new file mode 100644 index 0000000000..817aeb8530 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_motion.lua @@ -0,0 +1,765 @@ +local test = require "integration_test" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local utils = require "st.utils" +local dkjson = require "dkjson" + +local clusters = require "st.matter.generated.zap_clusters" +local button_attr = capabilities.button.button + +local mock_device = test.mock_device.build_test_matter_device( + { + profile = t_utils.get_profile_definition("6-button-motion.yml"), -- on a real device we would switch to this, rather than fingerprint to it + manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, + endpoints = { + { + endpoint_id = 0, + clusters = {}, + device_types = {} + }, + { + endpoint_id = 10, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 20, + 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 + } + }, + { + endpoint_id = 30, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 40, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 50, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 60, + clusters = { + { + cluster_id = clusters.Switch.ID, + feature_map = clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS | + clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS, + cluster_type = "SERVER" + }, + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 70, + clusters = { + {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- OccupancySensor + } + }, + }, +} +) + +-- add device for each mock device +local CLUSTER_SUBSCRIBE_LIST ={ + clusters.OccupancySensing.attributes.Occupancy, + clusters.Switch.server.events.InitialPress, + clusters.Switch.server.events.LongPress, + clusters.Switch.server.events.ShortRelease, + clusters.Switch.server.events.MultiPressComplete, +} + +local function expect_configure_buttons() + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", button_attr.pushed({state_change = false}))) + + test.socket.capability:__expect_send(mock_device:generate_test_message("button2", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.pushed({state_change = false}))) + + test.socket.capability:__expect_send(mock_device:generate_test_message("button3", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = false}))) + + test.socket.capability:__expect_send(mock_device:generate_test_message("button4", capabilities.button.supportedButtonValues({"pushed", "held"}, {visibility = {displayed = false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("button4", button_attr.pushed({state_change = false}))) + + test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, 50)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("button5", button_attr.pushed({state_change = false}))) + + test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, 60)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("button6", button_attr.pushed({state_change = false}))) +end + +-- All messages queued and expectations set are done before the driver is actually run +local function test_init() + -- we dont want the integration test framework to generate init/doConfigure, we are doing that here + -- so we can set the proper expectations on those events. + test.disable_startup_messages() + test.mock_device.add_test_device(mock_device) -- make sure the cache is populated + + -- added sets a bunch of fields on the device, and calls init + local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device) + for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do + if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end + end + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + -- init results in subscription interaction + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + + --doConfigure sets the provisioning state to provisioned + mock_device:expect_metadata_update({ profile = "6-button-motion" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + expect_configure_buttons() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + + -- simulate the profile change update taking affect and the device info changing + local device_info_copy = utils.deep_copy(mock_device.raw_st_data) + device_info_copy.profile.id = "6-buttons-motion" + local device_info_json = dkjson.encode(device_info_copy) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json }) + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + expect_configure_buttons() +end +test.set_test_init_function(test_init) + +test.register_message_test( + "Handle single press sequence, no hold", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} --move to position 1? + ), + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) --should send initial press + } +} +) + +test.register_message_test( + "Handle single press sequence for short release-supported button", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 20, {new_position = 1} --move to position 1? + ), + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 20, {previous_position = 0} --move to position 1? + ), + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button2", button_attr.pushed({state_change = true})) --should send initial press + } +} +) + +test.register_coroutine_test( + "Handle single press sequence for emulated hold on short-release-only button", + function () + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 20, {new_position = 1} + ) + }) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 20, {previous_position = 0} + ) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("button2", button_attr.held({state_change = true}))) + end +) + +test.register_coroutine_test( + "Handle single press sequence for a long hold on long-release-capable button", -- only a long press event should generate a held event + function () + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 30, {new_position = 1} + ) + }) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 30, {previous_position = 0} + ) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = true}))) + end +) + +test.register_coroutine_test( + "Handle single press sequence for a long hold on multi button", -- pushes should only be generated from multiPressComplete events + function () + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 50, {new_position = 1} + ) + }) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 50, {previous_position = 0} + ) + }) + end +) + +test.register_coroutine_test( + "Handle single press sequence for a multi press on multi button", + function () + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 60, {previous_position = 0} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_device, 60, {new_position = 1, current_number_of_presses_counted = 2} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_device, 60, {new_position = 0, total_number_of_presses_counted = 2, previous_position = 1} + ) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("button6", button_attr.double({state_change = true}))) + end +) + +test.register_coroutine_test( + "Handle long press sequence for a long hold on long-release-capable button", -- only a long press event should generate a held event + function () + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 40, {new_position = 1} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 40, {new_position = 1} + ) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("button4", button_attr.held({state_change = true}))) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.LongRelease:build_test_event_report( + mock_device, 40, {previous_position = 0} + ) + }) + end +) + +test.register_coroutine_test( + "Occupancy reports should generate correct messages", + function () + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 70, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("motion", capabilities.motionSensor.motion.active())) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 70, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("motion", capabilities.motionSensor.motion.inactive())) + end +) + +test.register_coroutine_test( + "Handle long press sequence for a long hold on multi button", + function () + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + }) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("button6", button_attr.held({state_change = true}))) + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.Switch.events.LongRelease:build_test_event_report( + mock_device, 60, {previous_position = 0} + ) + }) + end +) + +test.register_message_test( + "Handle single press sequence, with hold", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ), + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) --should send initial press + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ), + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.held({state_change = true})) + } +} +) + +test.register_message_test( + "Handle release after short press", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report( + mock_device, 10, {previous_position = 1} + ) + } + }, + { -- this is a double event because the test device in this test shouldn't support the above event + -- but we handle it anyway + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) + }, + } +) + +test.register_message_test( + "Handle release after long press", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ), + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.held({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.LongRelease:build_test_event_report( + mock_device, 10, {previous_position = 1} + ) + } + }, + } +) + +test.register_message_test( + "Receiving a max press attribute of 2 should emit correct event", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_device, 10, 2 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) + }, + } +) + +test.register_message_test( + "Receiving a max press attribute of 3 should emit correct event", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_device, 60, 3 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button6", + capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x"}, {visibility = {displayed = false}})) + }, + } +) + +test.register_message_test( + "Receiving a max press attribute of greater than 6 should only emit up to pushed_6x", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_device, 10, 7 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.button.supportedButtonValues({"pushed", "double", "pushed_3x", "pushed_4x", "pushed_5x", "pushed_6x"}, {visibility = {displayed = false}})) + }, + } +) + +test.register_message_test( + "Handle double press", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ) + } + }, + { -- again, on a device that reports that it supports double press, this event + -- will not be generated. See a multi-button test file for that case + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_device, 10, {new_position = 1, total_number_of_presses_counted = 2, previous_position = 0} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.double({state_change = true})) + }, + +} +) + +test.register_message_test( + "Handle multi press for 4 times", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 10, {new_position = 1} + ) + } + }, + { -- again, on a device that reports that it supports double press, this event + -- will not be generated. See a multi-button test file for that case + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_device, 10, {new_position = 1, total_number_of_presses_counted = 4, previous_position=0} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", button_attr.pushed_4x({state_change = true})) + }, + +} +) + +test.register_message_test( + "Receiving a max press attribute of 2 should emit correct event", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + mock_device, 50, 2 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button5", + capabilities.button.supportedButtonValues({"pushed", "double"}, {visibility = {displayed = false}})) + }, + } +) + +test.register_message_test( + "Handle a long press including MultiPressComplete", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button6", button_attr.held({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_device, 60, {new_position = 0, total_number_of_presses_counted = 1, previous_position=0} + ) + } + } + -- no double event +} +) + +test.register_message_test( + "Handle long press followed by single press", { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button6", button_attr.held({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_device, 60, {new_position = 1} + ) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_device, 60, {new_position = 0, total_number_of_presses_counted = 1, previous_position=0} + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("button6", button_attr.pushed({state_change = true})) + } + } +) +-- run the tests +test.run_registered_tests()