Skip to content
Open
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
10 changes: 10 additions & 0 deletions drivers/SmartThings/zigbee-button/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ zigbeeManufacturer:
manufacturer: LUMI
model: lumi.remote.acn003
deviceProfileName: one-button-battery
- id: "LUMI/lumi.remote.b186acn03"
deviceLabel: Aqara Wireless Remote Switch T1 (Single Rocker)
manufacturer: LUMI
model: lumi.remote.b186acn03
deviceProfileName: one-button-batteryLevel
- id: "LUMI/lumi.remote.b286acn03"
deviceLabel: Aqara Wireless Remote Switch T1 (Double Rocker)
manufacturer: LUMI
model: lumi.remote.b286acn03
deviceProfileName: aqara-double-buttons
- id: "HEIMAN/SOS-EM"
deviceLabel: HEIMAN Button
manufacturer: HEIMAN
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: aqara-double-buttons
components:
- id: main
capabilities:
- id: button
version: 1
- id: batteryLevel
version: 1
- id: refresh
version: 1
categories:
- name: RemoteController
- id: button1
capabilities:
- id: button
version: 1
categories:
- name: RemoteController
- id: button2
capabilities:
- id: button
version: 1
categories:
- name: RemoteController
- id: all
capabilities:
- id: button
version: 1
categories:
- name: RemoteController
Copy link
Contributor

Choose a reason for hiding this comment

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

rename to one-button-batteryLevel and include the firmwareUpdate capability.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have updated.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: one-button-batteryLevel
components:
- id: main
capabilities:
- id: button
version: 1
- id: batteryLevel
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: Button
160 changes: 101 additions & 59 deletions drivers/SmartThings/zigbee-button/src/aqara/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,74 +29,113 @@ local MFG_CODE = 0x115F
local MULTISTATE_INPUT_CLUSTER_ID = 0x0012
local PRESENT_ATTRIBUTE_ID = 0x0055

local COMP_LIST = { "button1", "button2", "all" }
local FINGERPRINTS = {
{ mfr = "LUMI", model = "lumi.remote.b1acn02" },
{ mfr = "LUMI", model = "lumi.remote.acn003" }
["lumi.remote.b1acn02"] = { mfr = "LUMI", btn_cnt = 1 },
["lumi.remote.acn003"] = { mfr = "LUMI", btn_cnt = 1 },
["lumi.remote.b186acn03"] = { mfr = "LUMI", btn_cnt = 1 },
["lumi.remote.b286acn03"] = { mfr = "LUMI", btn_cnt = 3 }
}

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
}
{
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
local end_point = zb_rx.address_header.src_endpoint.value
local btn_evt_cnt = FINGERPRINTS[device:get_model()].btn_cnt or 1
local evt = capabilities.button.button.held({ state_change = true })
Copy link
Contributor

Choose a reason for hiding this comment

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

should we be explicit about this only being the case when the value is 0, just in case a future device uses values > 2?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't anticipate any issues, as it should work the same way with values of 2 or higher

if value.value == 1 then
evt = capabilities.button.button.pushed({ state_change = true })
elseif value.value == 2 then
evt = capabilities.button.button.double({ state_change = true })
end
device:emit_event(evt)
if btn_evt_cnt > 1 then
device:emit_component_event(device.profile.components[COMP_LIST[end_point]], evt)
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like button events will only be emitted from the all component when they come from endpoint 3, but they will always be emitted from the main endpoint. That's a bit confusing.

In the past we've used the main component as an any/all component for button presses. It's a pattern with some precedent. Did you avoid that with your profiles because the name is not descriptive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To monitor all button events on the card, they are set to be emitted from the main component. Endpoint 3 triggers an event when both button 1 and button 2 are pressed simultaneously, so the all component is used to indicate that both buttons must be pressed together.

Copy link
Contributor

Choose a reason for hiding this comment

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

@greens This device appears to have two physical buttons, but it actually functions like a 3-button device. When both buttons are pressed simultaneously, it triggers the button event for the all component, but not for button1 or button2. However, when naming it button3, it would be difficult for users to associate it with pressing simultaneously, so it was suggested to create it as an all component.
The function of the main component is intended to update the main device simultaneously whenever an event occurs at any endpoint, just like other multi-button devices.

If you have better idea about all component naming, please let me know

end
end
local function battery_level_handler(driver, device, value, zb_rx)
local voltage = value.value
local batteryLevel = "normal"
if voltage <= 25 then
batteryLevel = "critical"
elseif voltage < 28 then
batteryLevel = "warning"
end
device:emit_event(capabilities.batteryLevel.battery(batteryLevel))
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
local isAqaraProducts = false
if FINGERPRINTS[device:get_model()] and FINGERPRINTS[device:get_model()].mfr == device:get_manufacturer() then
isAqaraProducts = true
end
return isAqaraProducts
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)
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)
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}))
button_utils.emit_event_if_latest_state_missing(device, "main", capabilities.button, capabilities.button.button.NAME, capabilities.button.button.pushed({state_change = false}))
device:emit_event(capabilities.battery.battery(100))
local btn_evt_cnt = FINGERPRINTS[device:get_model()].btn_cnt or 1

device:emit_event(capabilities.button.supportedButtonValues({ "pushed", "held", "double" },
{ visibility = { displayed = false } }))
device:emit_event(capabilities.button.numberOfButtons({ value = 1 }))
button_utils.emit_event_if_latest_state_missing(device, "main", capabilities.button, capabilities.button.button.NAME,
capabilities.button.button.pushed({ state_change = false }))
device:emit_event(capabilities.batteryLevel.battery.normal())
device:emit_event(capabilities.batteryLevel.type("CR2032"))
device:emit_event(capabilities.batteryLevel.quantity(1))

if btn_evt_cnt > 1 then
for i = 1, btn_evt_cnt do
device:emit_component_event(device.profile.components[COMP_LIST[i]],
capabilities.button.supportedButtonValues({ "pushed", "held", "double" },
{ visibility = { displayed = false } }))
device:emit_component_event(device.profile.components[COMP_LIST[i]],
capabilities.button.numberOfButtons({ value = 1 }))
device:emit_component_event(device.profile.components[COMP_LIST[i]],
capabilities.button.button.pushed({ state_change = false }))
button_utils.emit_event_if_latest_state_missing(device, COMP_LIST[i], capabilities.button,
capabilities.button.button.NAME, capabilities.button.button.pushed({ state_change = false }))
end
end
end

local function do_configure(driver, device)
local ATTR_ID = PRIVATE_ATTRIBUTE_ID_T1
local cmd_value = 1

device:configure()
if device:get_model() == "lumi.remote.b1acn02" then
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
device:send(cluster_base.write_manufacturer_specific_attribute(device,
PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID_E1, MFG_CODE, data_types.Uint8, 2))
if device:get_model() == "lumi.remote.acn003" then
ATTR_ID = PRIVATE_ATTRIBUTE_ID_E1
cmd_value = 2
end
device:send(cluster_base.write_manufacturer_specific_attribute(device,
PRIVATE_CLUSTER_ID, ATTR_ID, MFG_CODE, data_types.Uint8, cmd_value))
-- 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.
Expand All @@ -105,20 +144,23 @@ local function do_configure(driver, device)
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
},
[PowerConfiguration.ID] = {
[PowerConfiguration.attributes.BatteryVoltage.ID] = battery_level_handler
}
},
can_handle = is_aqara_products
}
},
can_handle = is_aqara_products
}

return aqara_wireless_switch_handler
Loading
Loading