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
5 changes: 5 additions & 0 deletions drivers/SmartThings/zigbee-button/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ zigbeeManufacturer:
manufacturer: WALL HERO
model: ACL-401SCA4
deviceProfileName: thirty-buttons
- id: "MultIR/MIR-SO100"
deviceLabel: MultiIR Smart button MIR-SO100
manufacturer: MultIR
model: MIR-SO100
deviceProfileName: one-button-battery
zigbeeGeneric:
- id: "generic-button-sensor"
deviceLabel: "Zigbee Generic Button"
Expand Down
13 changes: 13 additions & 0 deletions drivers/SmartThings/zigbee-button/src/MultiIR/can_handle.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- Copyright 2026 SmartThings, Inc.
-- Licensed under the Apache License, Version 2.0

return function(opts, driver, device, ...)
local FINGERPRINTS = require "MultiIR.fingerprints"
for _, fingerprint in ipairs(FINGERPRINTS) do
if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then
local subdriver = require("MultiIR")
return true, subdriver
end
end
return false
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Copyright 2026 SmartThings, Inc.
-- Licensed under the Apache License, Version 2.0

return {
{ mfr = "MultIR", model = "MIR-SO100" }
}
48 changes: 48 additions & 0 deletions drivers/SmartThings/zigbee-button/src/MultiIR/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
-- Copyright 2026 SmartThings, Inc.
-- Licensed under the Apache License, Version 2.0


local zcl_clusters = require "st.zigbee.zcl.clusters"
local capabilities = require "st.capabilities"
local button_utils = require "button_utils"
local log = require "log"

local IASZone = zcl_clusters.IASZone
local PRIVATE_CMD_ID = 0xF1

local function ias_zone_private_cmd_handler(self, device, zb_rx)
local cmd_data = zb_rx.body.zcl_body.body_bytes:byte(1)
if cmd_data == 0 then
device:emit_event(capabilities.button.button.pushed({state_change = true}))
elseif cmd_data == 1 then
device:emit_event(capabilities.button.button.double({state_change = true}))
elseif cmd_data == 0x80 then
device:emit_event(capabilities.button.button.held({state_change = true}))
else
log.info("ias_zone_private_cmd Unknown value",zb_rx.body.zcl_body.body_bytes:byte(1))
end
end

local function added_handler(self, device)
device:emit_event(capabilities.button.supportedButtonValues({"pushed","double","held"}, {visibility = { displayed = false }}))
device:emit_event(capabilities.button.numberOfButtons({value = 1}, {visibility = { displayed = false }}))
button_utils.emit_event_if_latest_state_missing(device, "main", capabilities.button, capabilities.button.button.NAME, capabilities.button.button.pushed({state_change = false}))
end

local MultiIR_Emergency_Button = {
NAME = "MultiIR Emergency Button",
lifecycle_handlers = {
added = added_handler,
},
zigbee_handlers = {
cluster = {
[IASZone.ID] = {
[PRIVATE_CMD_ID] = ias_zone_private_cmd_handler
}
}
},
sub_drivers = {},
can_handle = require("MultiIR.can_handle"),
}

return MultiIR_Emergency_Button
1 change: 1 addition & 0 deletions drivers/SmartThings/zigbee-button/src/sub_drivers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ local sub_drivers = {
lazy_load_if_possible("ewelink"),
lazy_load_if_possible("thirdreality"),
lazy_load_if_possible("ezviz"),
lazy_load_if_possible("MultiIR"),
}
return sub_drivers
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
-- Copyright 2026 SmartThings, Inc.
-- Licensed under the Apache License, Version 2.0

-- Mock out globals
local capabilities = require "st.capabilities"
local clusters = require "st.zigbee.zcl.clusters"
local t_utils = require "integration_test.utils"
local test = require "integration_test"
local zigbee_test_utils = require "integration_test.zigbee_test_utils"

local IASZone = clusters.IASZone

local button_attr = capabilities.button.button
local PRIVATE_CMD_ID = 0xF1

local mock_device = test.mock_device.build_test_zigbee_device(
{
profile = t_utils.get_profile_definition("one-button-battery.yml"),
zigbee_endpoints = {
[1] = {
id = 1,
manufacturer = "MultIR",
model = "MIR-SO100",
server_clusters = {0x0000, 0x0001, 0x0003, 0x0020, 0x0500, 0x0B05}
}
}
}
)

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

test.set_test_init_function(test_init)



test.register_coroutine_test(
"added lifecycle event",
function()
-- The initial button pushed event should be send during the device's first time onboarding
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
test.socket.capability:__set_channel_ordering("relaxed")
test.socket.capability:__expect_send(
mock_device:generate_test_message(
"main",
capabilities.button.supportedButtonValues({ "pushed","double","held" }, { visibility = { displayed = false } })
)
)
test.socket.capability:__expect_send(
mock_device:generate_test_message(
"main",
capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } })
)
)
test.socket.capability:__expect_send({
mock_device.id,
{
capability_id = "button", component_id = "main",
attribute_id = "button", state = { value = "pushed" }
}
})
-- Avoid sending the initial button pushed event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle.
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
test.socket.capability:__set_channel_ordering("relaxed")
test.socket.capability:__expect_send(
mock_device:generate_test_message(
"main",
capabilities.button.supportedButtonValues({ "pushed","double","held" }, { visibility = { displayed = false } })
)
)
test.socket.capability:__expect_send(
mock_device:generate_test_message(
"main",
capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } })
)
)
end,
{
min_api_version = 19
}
)

test.register_message_test(
"IASZone cmd 0xF1 0x00 are handled",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, IASZone.ID, PRIVATE_CMD_ID, 0x0000, "\x00", 0x01) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.button.button.pushed({state_change = true}))
}
}
)

test.register_message_test(
"IASZone cmd 0xF1 0x01 are handled",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, IASZone.ID, PRIVATE_CMD_ID, 0x0000, "\x01", 0x01) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.button.button.double({state_change = true}))
}
}
)

test.register_message_test(
"IASZone cmd 0xF1 0x01 are handled",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, zigbee_test_utils.build_custom_command_id(mock_device, IASZone.ID, PRIVATE_CMD_ID, 0x0000, "\x80", 0x01) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.button.button.held({state_change = true}))
}
}
)

test.run_registered_tests()
1 change: 1 addition & 0 deletions tools/localizations/cn.csv
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,4 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1
"WISTAR WSCMXJ Smart Curtain Motor",威仕达智能开合帘电机 WSCMXJ
"HAOJAI Smart Switch 3-key",好家智能三键开关
"HAOJAI Smart Switch 6-key",好家智能六键开关
"MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100
Loading