diff --git a/drivers/SmartThings/zigbee-button/fingerprints.yml b/drivers/SmartThings/zigbee-button/fingerprints.yml index 48c16ce41e..eca4a37cde 100644 --- a/drivers/SmartThings/zigbee-button/fingerprints.yml +++ b/drivers/SmartThings/zigbee-button/fingerprints.yml @@ -14,6 +14,16 @@ zigbeeManufacturer: manufacturer: LUMI model: lumi.remote.acn003 deviceProfileName: one-button-battery + - id: "LUMI/lumi.remote.b18ac1" + deviceLabel: Aqara Wireless Remote Switch H1 (Single Rocker) + manufacturer: LUMI + model: lumi.remote.b18ac1 + deviceProfileName: one-button-battery + - id: "LUMI/lumi.remote.b28ac1" + deviceLabel: Aqara Wireless Remote Switch H1 (Double Rocker) + manufacturer: LUMI + model: lumi.remote.b28ac1 + deviceProfileName: two-buttons-battery - id: "HEIMAN/SOS-EM" deviceLabel: HEIMAN Button manufacturer: HEIMAN diff --git a/drivers/SmartThings/zigbee-button/src/aqara/init.lua b/drivers/SmartThings/zigbee-button/src/aqara/init.lua index 5fd1076b4a..2402fb38e1 100644 --- a/drivers/SmartThings/zigbee-button/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-button/src/aqara/init.lua @@ -1,4 +1,4 @@ --- Copyright 2024 SmartThings +-- Copyright 2025 SmartThings -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. @@ -17,20 +17,22 @@ local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" local capabilities = require "st.capabilities" - +local supported_values = require "zigbee-multi-button.supported_values" local PowerConfiguration = clusters.PowerConfiguration -local PRIVATE_CLUSTER_ID = 0xFCC0 -local PRIVATE_ATTRIBUTE_ID_T1 = 0x0009 -local PRIVATE_ATTRIBUTE_ID_E1 = 0x0125 -local MFG_CODE = 0x115F +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MULTISTATE_INPUT_ATTRIBUTE_ID = 0x0125 local MULTISTATE_INPUT_CLUSTER_ID = 0x0012 local PRESENT_ATTRIBUTE_ID = 0x0055 +local MFG_CODE = 0x115F local FINGERPRINTS = { { mfr = "LUMI", model = "lumi.remote.b1acn02" }, - { mfr = "LUMI", model = "lumi.remote.acn003" } + { mfr = "LUMI", model = "lumi.remote.acn003" }, + { mfr = "LUMI", model = "lumi.remote.b18ac1" }, + { mfr = "LUMI", model = "lumi.remote.b28ac1" } } local configuration = { @@ -53,72 +55,80 @@ local configuration = { } local function present_value_attr_handler(driver, device, value, zb_rx) - if value.value == 1 then - device:emit_event(capabilities.button.button.pushed({state_change = true})) - elseif value.value == 2 then - device:emit_event(capabilities.button.button.double({state_change = true})) - elseif value.value == 0 then - device:emit_event(capabilities.button.button.held({state_change = true})) - end + local src_endpoint = zb_rx.address_header.src_endpoint.value + local event_map = { [1] = "pushed", [2] = "double", [0] = "held" } + local event_value = event_map[value.value] + if not event_value then return end + device:emit_component_event(device.profile.components.main, capabilities.button.button[event_value]({ state_change = true })) + if device:get_model() == "lumi.remote.b28ac1" and src_endpoint == 1 then + device:emit_component_event(device.profile.components.button1, capabilities.button.button[event_value]({ state_change = true })) + elseif src_endpoint == 2 then + device:emit_component_event(device.profile.components.button2, capabilities.button.button[event_value]({ state_change = true })) + end end local is_aqara_products = function(opts, driver, device) - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - return true - end + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true end - return false + end + return false end local function device_init(driver, device) - battery_defaults.build_linear_voltage_init(2.6, 3.0)(driver, device) - if configuration ~= nil then - for _, attribute in ipairs(configuration) do - device:add_configured_attribute(attribute) - device:add_monitored_attribute(attribute) - end + battery_defaults.build_linear_voltage_init(2.6, 3.0)(driver, device) + if configuration ~= nil then + for _, attribute in ipairs(configuration) do + device:add_configured_attribute(attribute) + device:add_monitored_attribute(attribute) end + end end local function added_handler(self, device) - device:emit_event(capabilities.button.supportedButtonValues({"pushed","held","double"}, {visibility = { displayed = false }})) - device:emit_event(capabilities.button.numberOfButtons({value = 1})) - device:emit_event(capabilities.button.button.pushed({state_change = false})) - device:emit_event(capabilities.battery.battery(100)) + local config = supported_values.get_device_parameters(device) + for _, component in pairs(device.profile.components) do + local number_of_buttons = component.id == "main" and config.NUMBER_OF_BUTTONS or 1 + if config ~= nil then + device:emit_component_event(component, capabilities.button.supportedButtonValues(config.SUPPORTED_BUTTON_VALUES), {visibility = { displayed = false }}) + else + device:emit_component_event(component, capabilities.button.supportedButtonValues({"pushed", "held", "double"}, {visibility = { displayed = false }})) + end + device:emit_component_event(component, capabilities.button.numberOfButtons({value = number_of_buttons})) + end + device:emit_event(capabilities.button.button.pushed({state_change = false})) + device:emit_event(capabilities.battery.battery(100)) end local function do_configure(driver, device) device:configure() - if device:get_model() == "lumi.remote.b1acn02" then + if device:get_model() == "lumi.remote.acn003" then + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, MULTISTATE_INPUT_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 2)) + else device:send(cluster_base.write_manufacturer_specific_attribute(device, - PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_T1, MFG_CODE, data_types.Uint8, 1)) - elseif device:get_model() == "lumi.remote.acn003" then + PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1)) device:send(cluster_base.write_manufacturer_specific_attribute(device, - PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_E1, MFG_CODE, data_types.Uint8, 2)) + PRIVATE_CLUSTER_ID, MULTISTATE_INPUT_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 2)) end - -- when the wireless switch T1 accesses the network, the gateway sends - -- private attribute 0009 to make the device no longer distinguish - -- between the standard gateway and the aqara gateway. - -- When wireless switch E1 is connected to the network, the gateway sends - -- private attribute 0125 to enable the device to send double-click and long-press packets. end local aqara_wireless_switch_handler = { - NAME = "Aqara Wireless Switch Handler", - lifecycle_handlers = { - init = device_init, - added = added_handler, - doConfigure = do_configure - }, - zigbee_handlers = { - attr = { - [MULTISTATE_INPUT_CLUSTER_ID] = { - [PRESENT_ATTRIBUTE_ID] = present_value_attr_handler - } + NAME = "Aqara Wireless Switch Handler", + lifecycle_handlers = { + init = device_init, + added = added_handler, + doConfigure = do_configure + }, + zigbee_handlers = { + attr = { + [MULTISTATE_INPUT_CLUSTER_ID] = { + [PRESENT_ATTRIBUTE_ID] = present_value_attr_handler } - }, - can_handle = is_aqara_products + } + }, + can_handle = is_aqara_products } return aqara_wireless_switch_handler diff --git a/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua index 88e803fd40..750a3e2060 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua @@ -1,4 +1,4 @@ --- Copyright 2024 SmartThings +-- Copyright 2025 SmartThings -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. @@ -19,15 +19,14 @@ local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" - -local MULTISTATE_INPUT_CLUSTER_ID = 0x0012 -local PRESENT_ATTRIBUTE_ID = 0x0055 local PowerConfiguration = clusters.PowerConfiguration local MFG_CODE = 0x115F local PRIVATE_CLUSTER_ID = 0xFCC0 -local PRIVATE_ATTRIBUTE_ID_T1 = 0x0009 -local PRIVATE_ATTRIBUTE_ID_E1 = 0x0125 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MULTISTATE_INPUT_ATTRIBUTE_ID = 0x0125 +local MULTISTATE_INPUT_CLUSTER_ID = 0x0012 +local PRESENT_ATTRIBUTE_ID = 0x55 local mock_device_e1 = test.mock_device.build_test_zigbee_device( { @@ -57,20 +56,39 @@ local mock_device_t1 = test.mock_device.build_test_zigbee_device( } ) +local mock_device_h1 = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("two-buttons-battery.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.remote.b28ac1", + server_clusters = { 0x0001, 0x0012 } + }, + [2] = { + id = 2, + server_clusters = { 0x0001, 0x0012 } + } + } + } +) + zigbee_test_utils.prepare_zigbee_env_info() local function test_init() test.mock_device.add_test_device(mock_device_e1) test.mock_device.add_test_device(mock_device_t1) + test.mock_device.add_test_device(mock_device_h1) zigbee_test_utils.init_noop_health_check_timer() end test.set_test_init_function(test_init) test.register_coroutine_test( - "Handle added lifecycle -- e1", + "Handle added lifecycle -- Single Rocker Switch (E1,T1,H1)", function() test.socket.device_lifecycle:__queue_receive({ mock_device_e1.id, "added" }) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed","held","double"}, {visibility = { displayed = false }}))) + test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed","held","double"}))) test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.button.numberOfButtons({value = 1}))) test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.button.button.pushed({state_change = false}))) test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", capabilities.battery.battery(100))) @@ -78,13 +96,27 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "Handle added lifecycle -- t1", + "Handle added lifecycle -- Double Rocker Switch (H1)", function() - test.socket.device_lifecycle:__queue_receive({ mock_device_t1.id, "added" }) - test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed","held","double"}, {visibility = { displayed = false }}))) - test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main", capabilities.button.numberOfButtons({value = 1}))) - test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main", capabilities.button.button.pushed({state_change = false}))) - test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main", capabilities.battery.battery(100))) + test.socket.device_lifecycle:__queue_receive({ mock_device_h1.id, "added" }) + + for button_name, component in pairs(mock_device_h1.profile.components) do + local number_of_buttons = component.id == "main" and 2 or 1 + test.socket.capability:__expect_send( + mock_device_h1:generate_test_message( + button_name, + capabilities.button.supportedButtonValues({ "pushed", "held", "double" }) + ) + ) + test.socket.capability:__expect_send( + mock_device_h1:generate_test_message( + button_name, + capabilities.button.numberOfButtons({ value = number_of_buttons }) + ) + ) + end + test.socket.capability:__expect_send(mock_device_h1:generate_test_message("main", capabilities.button.button.pushed({state_change = false}))) + test.socket.capability:__expect_send(mock_device_h1:generate_test_message("main", capabilities.battery.battery(100))) end ) @@ -109,15 +141,14 @@ test.register_coroutine_test( zigbee_test_utils.build_attr_config(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, PRESENT_ATTRIBUTE_ID, 0x0003, 0x1C20, data_types.Uint16, 0x0001) }) test.socket.zigbee:__expect_send({ mock_device_e1.id, - cluster_base.write_manufacturer_specific_attribute(mock_device_e1, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_E1, MFG_CODE, + cluster_base.write_manufacturer_specific_attribute(mock_device_e1, PRIVATE_CLUSTER_ID, MULTISTATE_INPUT_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 2) }) mock_device_e1:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end ) - test.register_coroutine_test( - "Handle doConfigure lifecycle -- t1", + "Handle doConfigure lifecycle -- t1 h1", function() test.socket.device_lifecycle:__queue_receive({ mock_device_t1.id, "doConfigure" }) test.socket.zigbee:__expect_send({ @@ -137,54 +168,79 @@ test.register_coroutine_test( zigbee_test_utils.build_attr_config(mock_device_t1, MULTISTATE_INPUT_CLUSTER_ID, PRESENT_ATTRIBUTE_ID, 0x0003, 0x1C20, data_types.Uint16, 0x0001) }) test.socket.zigbee:__expect_send({ mock_device_t1.id, - cluster_base.write_manufacturer_specific_attribute(mock_device_t1, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_T1, MFG_CODE, + cluster_base.write_manufacturer_specific_attribute(mock_device_t1, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) + test.socket.zigbee:__expect_send({ mock_device_t1.id, + cluster_base.write_manufacturer_specific_attribute(mock_device_t1, PRIVATE_CLUSTER_ID, MULTISTATE_INPUT_ATTRIBUTE_ID, MFG_CODE, + data_types.Uint8, 2) }) mock_device_t1:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end ) test.register_coroutine_test( - "Reported button should be handled: pushed true", + "Single Rocker Switch (E1,T1,H1) Reported button should be handled: (pushed double held) true", function() - local attr_report_data = { + local attr_report_data_pushed = { { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0001 } } test.socket.zigbee:__queue_receive({ mock_device_e1.id, - zigbee_test_utils.build_attribute_report(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data, MFG_CODE) + zigbee_test_utils.build_attribute_report(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data_pushed, MFG_CODE) }) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", - capabilities.button.button.pushed({state_change = true}))) - end -) - -test.register_coroutine_test( - "Reported button should be handled: double true", - function() - local attr_report_data = { + test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main",capabilities.button.button.pushed({state_change = true}))) + test.wait_for_events() + local attr_report_data_double = { { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0002 } } test.socket.zigbee:__queue_receive({ mock_device_e1.id, - zigbee_test_utils.build_attribute_report(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data, MFG_CODE) + zigbee_test_utils.build_attribute_report(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data_double, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main",capabilities.button.button.double({state_change = true}))) + test.wait_for_events() + local attr_report_data_held = { + { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0000 } + } + test.socket.zigbee:__queue_receive({ + mock_device_e1.id, + zigbee_test_utils.build_attribute_report(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data_held, MFG_CODE) }) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", - capabilities.button.button.double({state_change = true}))) + test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main",capabilities.button.button.held({state_change = true}))) end ) test.register_coroutine_test( - "Reported button should be handled: held true", + "Double Rocker Switch (H1) Reported button should be handled: (pushed double held) true", function() - local attr_report_data = { + local attr_report_data_pushed = { + { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0001 } + } + test.socket.zigbee:__queue_receive({ + mock_device_h1.id, + zigbee_test_utils.build_attribute_report(mock_device_h1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data_pushed, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device_h1:generate_test_message("main",capabilities.button.button.pushed({state_change = true}))) + test.socket.capability:__expect_send(mock_device_h1:generate_test_message("button1", capabilities.button.button.pushed({state_change = true}))) + test.wait_for_events() + local attr_report_data_double = { + { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0002 } + } + test.socket.zigbee:__queue_receive({ + mock_device_h1.id, + zigbee_test_utils.build_attribute_report(mock_device_h1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data_double, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device_h1:generate_test_message("main",capabilities.button.button.double({state_change = true}))) + test.socket.capability:__expect_send(mock_device_h1:generate_test_message("button1", capabilities.button.button.double({state_change = true}))) + test.wait_for_events() + local attr_report_data_held = { { PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0000 } } test.socket.zigbee:__queue_receive({ - mock_device_e1.id, - zigbee_test_utils.build_attribute_report(mock_device_e1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data, MFG_CODE) + mock_device_h1.id, + zigbee_test_utils.build_attribute_report(mock_device_h1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data_held, MFG_CODE) }) - test.socket.capability:__expect_send(mock_device_e1:generate_test_message("main", - capabilities.button.button.held({state_change = true}))) + test.socket.capability:__expect_send(mock_device_h1:generate_test_message("main",capabilities.button.button.held({state_change = true}))) + test.socket.capability:__expect_send(mock_device_h1:generate_test_message("button1", capabilities.button.button.held({state_change = true}))) end ) diff --git a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.lua b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.lua index 9b7ffc3f91..d6b1fb9691 100644 --- a/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.lua +++ b/drivers/SmartThings/zigbee-button/src/zigbee-multi-button/supported_values.lua @@ -90,14 +90,18 @@ local devices = { BUTTON_PUSH_HELD_DOUBLE_1 = { MATCHING_MATRIX = { { mfr = "ShinaSystem", model = "BSM-300Z" }, - { mfr = "ShinaSystem", model = "SBM300ZB1" } + { mfr = "ShinaSystem", model = "SBM300ZB1" }, + { mfr = "LUMI", model = "lumi.remote.b1acn02" }, + { mfr = "LUMI", model = "lumi.remote.acn003" }, + { mfr = "LUMI", model = "lumi.remote.b18ac1" } }, SUPPORTED_BUTTON_VALUES = { "pushed", "held", "double" }, NUMBER_OF_BUTTONS = 1 }, BUTTON_PUSH_HELD_DOUBLE_2 = { MATCHING_MATRIX = { - { mfr = "ShinaSystem", model = "SBM300ZB2" } + { mfr = "ShinaSystem", model = "SBM300ZB2" }, + { mfr = "LUMI", model = "lumi.remote.b28ac1" } }, SUPPORTED_BUTTON_VALUES = { "pushed", "held", "double" }, NUMBER_OF_BUTTONS = 2