diff --git a/drivers/SmartThings/matter-switch/profiles/3-button-battery-temperature-humidity.yml b/drivers/SmartThings/matter-switch/profiles/3-button-battery-temperature-humidity.yml new file mode 100644 index 0000000000..2fa77164ef --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/3-button-battery-temperature-humidity.yml @@ -0,0 +1,99 @@ +name: 3-button-battery-temperature-humidity +components: +- id: main + capabilities: + - id: temperatureMeasurement + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: TempHumiditySensor +- id: button1 + 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 +preferences: + - preferenceId: tempOffset + explicit: true + - preferenceId: humidityOffset + explicit: true +deviceConfig: + dashboard: + states: + - component: main + capability: temperatureMeasurement + version: 1 + group: main + composite: true + - component: main + capability: relativeHumidityMeasurement + version: 1 + group: main + values: + - label: " {{humidity.value}} {{humidity.unit}}" + composite: true + actions: [] + basicPlus: [] + detailView: + - component: main + capability: temperatureMeasurement + version: 1 + - component: main + capability: relativeHumidityMeasurement + version: 1 + - component: main + capability: battery + version: 1 + - component: main + capability: refresh + version: 1 + - component: button1 + capability: button + version: 1 + - component: button2 + capability: button + version: 1 + - component: button3 + capability: button + version: 1 + automation: + conditions: + - component: main + capability: temperatureMeasurement + version: 1 + - component: main + capability: relativeHumidityMeasurement + version: 1 + - component: main + capability: battery + version: 1 + - component: button1 + capability: button + version: 1 + - component: button2 + capability: button + version: 1 + - component: button3 + capability: button + version: 1 + actions: [] diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 2ce8ab2bbe..1d4b05fe3c 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -280,6 +280,10 @@ local EMULATE_HELD = "__emulate_held" -- for non-MSR (MomentarySwitchRelease) de local SUPPORTS_MULTI_PRESS = "__multi_button" -- for MSM devices (MomentarySwitchMultiPress), create an event on receipt of MultiPressComplete local INITIAL_PRESS_ONLY = "__initial_press_only" -- for devices that support MS (MomentarySwitch), but not MSR (MomentarySwitchRelease) +local TEMP_BOUND_RECEIVED = "__temp_bound_received" +local TEMP_MIN = "__temp_min" +local TEMP_MAX = "__temp_max" + local HUE_MANUFACTURER_ID = 0x100B local AQARA_MANUFACTURER_ID = 0x115F @@ -528,7 +532,7 @@ local function initialize_switch(driver, device) table.sort(switch_eps) table.sort(button_eps) - local profile_name = nil + local profile_name = "" local component_map = {} local component_map_used = false @@ -539,7 +543,15 @@ local function initialize_switch(driver, device) -- that have been implemented as server. This can be removed when we have -- support for bindings. local num_switch_server_eps = 0 - local main_endpoint = find_default_endpoint(device) + local main_endpoint + local temperature_eps = device:get_endpoints(clusters.TemperatureMeasurement.ID) + local humidity_eps = device:get_endpoints(clusters.RelativeHumidityMeasurement.ID) + if #temperature_eps > 0 and #humidity_eps > 0 then + -- In case of Aqara Climate Sensor W100, in order to sequentially set the button name to button 1, 2, 3 + main_endpoint = device.MATTER_DEFAULT_ENDPOINT + else + main_endpoint = find_default_endpoint(device) + end -- If a switch endpoint is present, it will be the main endpoint and therefore the -- main component. If button endpoints are present, they will be added as @@ -613,17 +625,21 @@ local function initialize_switch(driver, device) battery_support = true end if #button_eps > 1 and tbl_contains(STATIC_BUTTON_PROFILE_SUPPORTED, #button_eps) then + if #temperature_eps > 0 and #humidity_eps > 0 then + device.log.debug("So far, it means Aqara Climate Sensor W100.") + profile_name = "-temperature-humidity" + end if battery_support then - profile_name = string.format("%d-button-battery", #button_eps) + profile_name = string.format("%d-button-battery", #button_eps) .. profile_name else - profile_name = string.format("%d-button", #button_eps) + profile_name = string.format("%d-button", #button_eps) .. profile_name end elseif not battery_support then -- a battery-less button/remote profile_name = "button" end - if profile_name then + if profile_name ~= "" then device:try_update_metadata({profile = profile_name}) device:set_field(DEFERRED_CONFIGURE, true) device:set_field(BUTTON_DEVICE_PROFILED, true) @@ -1199,6 +1215,49 @@ local function device_added(driver, device) device_init(driver, device) end +local function temperature_attr_handler(driver, device, ib, response) + local measured_value = ib.data.value + if measured_value ~= nil then + local temp = measured_value / 100.0 + local unit = "C" + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureMeasurement.temperature({value = temp, unit = unit})) + end +end + +local temp_attr_handler_factory = function(minOrMax) + return function(driver, device, ib, response) + if ib.data.value == nil then + return + end + local temp = ib.data.value / 100.0 + local unit = "C" + set_field_for_endpoint(device, TEMP_BOUND_RECEIVED..minOrMax, ib.endpoint_id, temp) + local min = get_field_for_endpoint(device, TEMP_BOUND_RECEIVED..TEMP_MIN, ib.endpoint_id) + local max = get_field_for_endpoint(device, TEMP_BOUND_RECEIVED..TEMP_MAX, ib.endpoint_id) + if min ~= nil and max ~= nil then + if min < max then + -- Only emit the capability for RPC version >= 5 (unit conversion for + -- temperature range capability is only supported for RPC >= 5) + if version.rpc >= 5 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = min, maximum = max }, unit = unit })) + end + set_field_for_endpoint(device, TEMP_BOUND_RECEIVED..TEMP_MIN, ib.endpoint_id, nil) + set_field_for_endpoint(device, TEMP_BOUND_RECEIVED..TEMP_MAX, ib.endpoint_id, nil) + else + device.log.warn_with({hub_logs = true}, string.format("Device reported a min temperature %d that is not lower than the reported max temperature %d", min, max)) + end + end + end +end + +local function humidity_attr_handler(driver, device, ib, response) + local measured_value = ib.data.value + if measured_value ~= nil then + local humidity = utils.round(measured_value / 100.0) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.relativeHumidityMeasurement.humidity(humidity)) + end +end + local matter_driver_template = { lifecycle_handlers = { init = device_init, @@ -1249,6 +1308,14 @@ local matter_driver_template = { }, [clusters.Switch.ID] = { [clusters.Switch.attributes.MultiPressMax.ID] = max_press_handler + }, + [clusters.RelativeHumidityMeasurement.ID] = { + [clusters.RelativeHumidityMeasurement.attributes.MeasuredValue.ID] = humidity_attr_handler + }, + [clusters.TemperatureMeasurement.ID] = { + [clusters.TemperatureMeasurement.attributes.MeasuredValue.ID] = temperature_attr_handler, + [clusters.TemperatureMeasurement.attributes.MinMeasuredValue.ID] = temp_attr_handler_factory(TEMP_MIN), + [clusters.TemperatureMeasurement.attributes.MaxMeasuredValue.ID] = temp_attr_handler_factory(TEMP_MAX), } }, event = { @@ -1302,6 +1369,14 @@ local matter_driver_template = { }, [capabilities.powerMeter.ID] = { clusters.ElectricalPowerMeasurement.attributes.ActivePower + }, + [capabilities.relativeHumidityMeasurement.ID] = { + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue + }, + [capabilities.temperatureMeasurement.ID] = { + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue } }, subscribed_events = { @@ -1352,7 +1427,9 @@ local matter_driver_template = { capabilities.powerConsumptionReport, capabilities.valve, capabilities.button, - capabilities.battery + capabilities.battery, + capabilities.temperatureMeasurement, + capabilities.relativeHumidityMeasurement }, sub_drivers = { require("eve-energy"), diff --git a/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua b/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua new file mode 100644 index 0000000000..9729c2f529 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua @@ -0,0 +1,472 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local utils = require "st.utils" +local dkjson = require "dkjson" + +local clusters = require "st.matter.generated.zap_clusters" +local button_attr = capabilities.button.button + +local DEFERRED_CONFIGURE = "__DEFERRED_CONFIGURE" + +-- Mock a 3-button device with temperature and humidity sensor +local aqara_mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("3-button-battery-temperature-humidity.yml"), + manufacturer_info = {vendor_id = 0x115F, product_id = 0x2004, product_name = "Aqara Climate Sensor W100"}, + label = "Climate Sensor W100", + device_id = "00000000-1111-2222-3333-000000000001", + 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 = 1, + clusters = { + {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0302, device_type_revision = 1}, + } + }, + { + endpoint_id = 2, + clusters = { + {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "BOTH"}, + }, + device_types = { + {device_type_id = 0x0307, device_type_revision = 1}, + } + }, + { + endpoint_id = 3, + clusters = { + {cluster_id = clusters.Switch.ID, cluster_type = "SERVER", cluster_revision = 1, + feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH | + clusters.Switch.types.Feature.MOMENTARY_SWITCH_MULTI_PRESS | + clusters.Switch.types.Feature.MOMENTARY_SWITCH_LONG_PRESS, + } + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 4, + clusters = { + {cluster_id = clusters.Switch.ID, cluster_type = "SERVER", cluster_revision = 1, + feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH | + clusters.Switch.types.Feature.MOMENTARY_SWITCH_MULTI_PRESS | + clusters.Switch.types.Feature.MOMENTARY_SWITCH_LONG_PRESS, + } + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 5, + clusters = { + {cluster_id = clusters.Switch.ID, cluster_type = "SERVER", cluster_revision = 1, + feature_map = clusters.Switch.types.Feature.MOMENTARY_SWITCH | + clusters.Switch.types.Feature.MOMENTARY_SWITCH_MULTI_PRESS | + clusters.Switch.types.Feature.MOMENTARY_SWITCH_LONG_PRESS, + } + }, + device_types = { + {device_type_id = 0x000F, device_type_revision = 1} -- Generic Switch + } + }, + { + endpoint_id = 6, + clusters = { + {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER", + feature_map = clusters.PowerSource.types.PowerSourceFeature.BATTERY + } + }, + device_types = { + {device_type_id = 0x0011, device_type_revision = 1} + } + } + } +}) + +local function test_init() + local opts = { persist = true } + local cluster_subscribe_list = { + clusters.PowerSource.server.attributes.BatPercentRemaining, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue, + clusters.RelativeHumidityMeasurement.attributes.MeasuredValue, + clusters.Switch.server.events.InitialPress, + clusters.Switch.server.events.LongPress, + clusters.Switch.server.events.ShortRelease, + clusters.Switch.server.events.MultiPressComplete, + } + + local cluster_read_list = { + clusters.Switch.attributes.MultiPressMax + } + local read_request + + local subscribe_request = cluster_subscribe_list[1]:subscribe(aqara_mock_device) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(aqara_mock_device)) + end + end + + test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) + test.mock_device.add_test_device(aqara_mock_device) + test.set_rpc_version(5) + + test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "added" }) + test.mock_devices_api._expected_device_updates[aqara_mock_device.device_id] = "00000000-1111-2222-3333-000000000001" + test.mock_devices_api._expected_device_updates[1] = {device_id = "00000000-1111-2222-3333-000000000001"} + test.mock_devices_api._expected_device_updates[1].metadata = {deviceId="00000000-1111-2222-3333-000000000001", profileReference="3-button-battery-temperature-humidity"} + + aqara_mock_device:set_field(DEFERRED_CONFIGURE, true, opts) + local device_info_copy = utils.deep_copy(aqara_mock_device.raw_st_data) + device_info_copy.profile.id = "3-button-battery-temperature-humidity" + local device_info_json = dkjson.encode(device_info_copy) + test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "infoChanged", device_info_json }) + test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request}) + + read_request = cluster_read_list[1]:read(aqara_mock_device, 3) + read_request:merge(cluster_read_list[1]:subscribe(aqara_mock_device)) + test.socket.matter:__expect_send({aqara_mock_device.id, read_request}) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button1", button_attr.pushed({state_change = false}))) + + read_request = cluster_read_list[1]:read(aqara_mock_device, 4) + read_request:merge(cluster_read_list[1]:subscribe(aqara_mock_device)) + test.socket.matter:__expect_send({aqara_mock_device.id, read_request}) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", button_attr.pushed({state_change = false}))) + + read_request = cluster_read_list[1]:read(aqara_mock_device, 5) + read_request:merge(cluster_read_list[1]:subscribe(aqara_mock_device)) + test.socket.matter:__expect_send({aqara_mock_device.id, read_request}) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button3", button_attr.pushed({state_change = false}))) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Temperature reports should generate correct messages", + function () + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.TemperatureMeasurement.server.attributes.MeasuredValue:build_test_report_data(aqara_mock_device, 1, 40*100) + } + ) + test.socket.capability:__expect_send( + aqara_mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) + ) + end +) + +test.register_coroutine_test( + "Min and max temperature attributes set capability constraint", + function () + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.TemperatureMeasurement.attributes.MinMeasuredValue:build_test_report_data(aqara_mock_device, 1, 500) + } + ) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.TemperatureMeasurement.attributes.MaxMeasuredValue:build_test_report_data(aqara_mock_device, 1, 4000) + } + ) + test.socket.capability:__expect_send( + aqara_mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 5.00, maximum = 40.00 }, unit = "C" })) + ) + end +) + +test.register_coroutine_test( + "Relative humidity reports should generate correct messages", + function () + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.RelativeHumidityMeasurement.server.attributes.MeasuredValue:build_test_report_data(aqara_mock_device, 2, 4049) + } + ) + test.socket.capability:__expect_send( + aqara_mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 40 })) + ) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.RelativeHumidityMeasurement.server.attributes.MeasuredValue:build_test_report_data(aqara_mock_device, 2, 4050) + } + ) + test.socket.capability:__expect_send( + aqara_mock_device:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 41 })) + ) + end +) + +test.register_coroutine_test( + "Battery percent reports should generate correct messages", + function () + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data(aqara_mock_device, 1, 150) + } + ) + test.socket.capability:__expect_send( + aqara_mock_device:generate_test_message("main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5))) + ) + 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( + { + aqara_mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report(aqara_mock_device, 3, {new_position = 1}) + } + ) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report(aqara_mock_device, 3, {previous_position = 0}) + } + ) + 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( + { + aqara_mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report(aqara_mock_device, 3, {new_position = 1}) + } + ) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report(aqara_mock_device, 3, {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( + { + aqara_mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report(aqara_mock_device, 3, {new_position = 1}) + } + ) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report(aqara_mock_device, 3, {previous_position = 0}) + } + ) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report(aqara_mock_device, 4, {new_position = 1}) + } + ) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report(aqara_mock_device, 4, {new_position = 1, current_number_of_presses_counted = 2}) + } + ) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report(aqara_mock_device, 4, {new_position = 0, total_number_of_presses_counted = 2, previous_position = 1}) + } + ) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button2", 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( + { + aqara_mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report(aqara_mock_device, 3, {new_position = 1}) + } + ) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report(aqara_mock_device, 3, {new_position = 1}) + } + ) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button1", button_attr.held({state_change = true}))) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.LongRelease:build_test_event_report(aqara_mock_device, 3, {previous_position = 0}) + } + ) + end +) + +test.register_coroutine_test( + "Handle long press sequence for a long hold on multi button", + function () + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report(aqara_mock_device, 5, {new_position = 1}) + } + ) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.LongPress:build_test_event_report(aqara_mock_device, 5, {new_position = 1}) + } + ) + test.socket.capability:__expect_send(aqara_mock_device:generate_test_message("button3", button_attr.held({state_change = true}))) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.LongRelease:build_test_event_report(aqara_mock_device, 5, {previous_position = 0}) + } + ) + end +) + +test.register_coroutine_test( + "Handle double press", + function() + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report(aqara_mock_device, 3, {new_position = 1, total_number_of_presses_counted = 2, previous_position = 0}) + } + ) + test.socket.capability:__expect_send( + aqara_mock_device:generate_test_message("button1", button_attr.double({state_change = true})) + ) + end +) + +test.register_coroutine_test( + "Receiving a max press attribute of 2 should emit correct event", + function() + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data(aqara_mock_device, 3, 2) + } + ) + test.socket.capability:__expect_send( + aqara_mock_device:generate_test_message("button1", capabilities.button.supportedButtonValues({"pushed", "double", "held"}, {visibility = {displayed = false}})) + ) + end +) + +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( + { + aqara_mock_device.id, + clusters.Switch.events.InitialPress:build_test_event_report(aqara_mock_device, 4, {new_position = 1}) + } + ) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.matter:__queue_receive( + { + aqara_mock_device.id, + clusters.Switch.events.ShortRelease:build_test_event_report(aqara_mock_device, 4, {previous_position = 0}) + } + ) + end +) + +test.register_message_test( + "Receiving a max press attribute of 3 should emit correct event", { + { + channel = "matter", + direction = "receive", + message = { + aqara_mock_device.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + aqara_mock_device, 5, 3 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = aqara_mock_device:generate_test_message("button3", + 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 = { + aqara_mock_device.id, + clusters.Switch.attributes.MultiPressMax:build_test_report_data( + aqara_mock_device, 3, 7 + ) + }, + }, + { + channel = "capability", + direction = "send", + message = aqara_mock_device:generate_test_message("button1", + capabilities.button.supportedButtonValues({"pushed", "double", "held", "pushed_3x", "pushed_4x", "pushed_5x", "pushed_6x"}, {visibility = {displayed = false}})) + }, + } +) + +test.run_registered_tests() +