Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aqara Wireless Mini Switch T1 #1145

Merged
merged 4 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions drivers/SmartThings/zigbee-button/fingerprints.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
zigbeeManufacturer:
- id: "LUMI/lumi.remote.b1acn02"
deviceLabel: Aqara Wireless Mini Switch T1
manufacturer: LUMI
model: lumi.remote.b1acn02
deviceProfileName: one-button-battery
- id: "HEIMAN/SOS-EM"
deviceLabel: HEIMAN Button
manufacturer: HEIMAN
Expand Down
108 changes: 108 additions & 0 deletions drivers/SmartThings/zigbee-button/src/aqara/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
-- 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 battery_defaults = require "st.zigbee.defaults.battery_defaults"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing CopyRight Profile.

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 PowerConfiguration = clusters.PowerConfiguration
local PRIVATE_CLUSTER_ID = 0xFCC0
local PRIVATE_ATTRIBUTE_ID = 0x0009
local MFG_CODE = 0x115F

local MULTISTATE_INPUT_CLUSTER_ID = 0x0012
local PRESENT_ATTRIBUTE_ID = 0x0055

local FINGERPRINTS = {
{ mfr = "LUMI", model = "lumi.remote.b1acn02" }
}

local configuration = {
{
cluster = MULTISTATE_INPUT_CLUSTER_ID,
attribute = PRESENT_ATTRIBUTE_ID,
minimum_interval = 3,
maximum_interval = 7200,
data_type = data_types.Uint16,
reportable_change = 1
},
{
cluster = PowerConfiguration.ID,
attribute = PowerConfiguration.attributes.BatteryVoltage.ID,
minimum_interval = 30,
maximum_interval = 3600,
data_type = PowerConfiguration.attributes.BatteryVoltage.base_type,
reportable_change = 1
}
}

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
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
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
end
end

local function added_handler(self, device)
device:send(cluster_base.write_manufacturer_specific_attribute(device,
PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1))
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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be missing something, but is the battery value held at 100%? Is there a specific function that is updating the value? Or is it an automatic reportable change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the "added" lifecycle event handler that fires when the device is added for the first time. In general we do this so that the value is populated on add so there are no UI hiccups.

After the fact, the value will be read using the standard zigbee attributes and handled by the defaults.

end

local aqara_wireless_switch_handler = {
NAME = "Aqara Wireless Switch Handler",
lifecycle_handlers = {
init = device_init,
added = added_handler
},
zigbee_handlers = {
attr = {
[MULTISTATE_INPUT_CLUSTER_ID] = {
[PRESENT_ATTRIBUTE_ID] = present_value_attr_handler
}
}
},
can_handle = is_aqara_products
}

return aqara_wireless_switch_handler

1 change: 1 addition & 0 deletions drivers/SmartThings/zigbee-button/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ local zigbee_button_driver_template = {
}
},
sub_drivers = {
require("aqara"),
require("pushButton"),
require("frient"),
require("zigbee-multi-button"),
Expand Down
134 changes: 134 additions & 0 deletions drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
-- 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 zigbee_test_utils = require "integration_test.zigbee_test_utils"
local capabilities = require "st.capabilities"
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 = 0x0009


local mock_device_t1 = test.mock_device.build_test_zigbee_device(
{
profile = t_utils.get_profile_definition("one-button-battery.yml"),
zigbee_endpoints = {
[1] = {
id = 1,
manufacturer = "LUMI",
model = "lumi.remote.b1acn02",
server_clusters = { 0x0001, 0x0012 }
}
}
}
)

zigbee_test_utils.prepare_zigbee_env_info()
local function test_init()
test.mock_device.add_test_device(mock_device_t1)
end

test.set_test_init_function(test_init)

test.register_coroutine_test(
"Handle added lifecycle",
function()
test.socket.device_lifecycle:__queue_receive({ mock_device_t1.id, "added" })

test.socket.zigbee:__expect_send({ mock_device_t1.id,
cluster_base.write_manufacturer_specific_attribute(mock_device_t1, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE,
data_types.Uint8, 1) })


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)))
end
)

test.register_coroutine_test(
"Reported button should be handled: pushed true",
function()
local attr_report_data = {
{ PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0001 }
}
test.socket.zigbee:__queue_receive({
mock_device_t1.id,
zigbee_test_utils.build_attribute_report(mock_device_t1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data, MFG_CODE)
})
test.socket.capability:__expect_send(mock_device_t1: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 = {
{ PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0002 }
}
test.socket.zigbee:__queue_receive({
mock_device_t1.id,
zigbee_test_utils.build_attribute_report(mock_device_t1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data, MFG_CODE)
})
test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main",
capabilities.button.button.double({state_change = true})))
end
)

test.register_coroutine_test(
"Reported button should be handled: held true",
function()
local attr_report_data = {
{ PRESENT_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0000 }
}
test.socket.zigbee:__queue_receive({
mock_device_t1.id,
zigbee_test_utils.build_attribute_report(mock_device_t1, MULTISTATE_INPUT_CLUSTER_ID, attr_report_data, MFG_CODE)
})
test.socket.capability:__expect_send(mock_device_t1:generate_test_message("main",
capabilities.button.button.held({state_change = true})))
end
)

test.register_message_test(
"Battery voltage report should be handled",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device_t1.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device_t1, 30) }
},
{
channel = "capability",
direction = "send",
message = mock_device_t1:generate_test_message("main", capabilities.battery.battery(100))
}
}
)



test.run_registered_tests()
Loading