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-smoke-detector/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,8 @@ zigbeeManufacturer:
manufacturer: HEIMAN
model: GASSensor-N
deviceProfileName: smoke-detector
- id: "MultIR/MIR-SM200"
deviceLabel: MultiIR Smoke Detector MIR-SM200
manufacturer: MultIR
model: MIR-SM200
deviceProfileName: smoke-battery-tamper
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: smoke-battery-tamper
components:
- id: main
capabilities:
- id: smokeDetector
version: 1
- id: tamperAlert
version: 1
- id: battery
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: SmokeDetector
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-SM200" }
}
62 changes: 62 additions & 0 deletions drivers/SmartThings/zigbee-smoke-detector/src/MultiIR/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
-- 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 IASZone = zcl_clusters.IASZone

local function generate_event_from_zone_status(driver, device, zone_status, zb_rx)
if zone_status:is_alarm1_set() then
device:emit_event(capabilities.smokeDetector.smoke.detected())
elseif zone_status:is_alarm2_set() then
device:emit_event(capabilities.smokeDetector.smoke.tested())
else
device:emit_event(capabilities.smokeDetector.smoke.clear())
end
if device:supports_capability(capabilities.tamperAlert) then
device:emit_event(zone_status:is_tamper_set() and capabilities.tamperAlert.tamper.detected() or capabilities.tamperAlert.tamper.clear())
end
end

local function ias_zone_status_attr_handler(driver, device, zone_status, zb_rx)
generate_event_from_zone_status(driver, device, zone_status, zb_rx)
end

local function ias_zone_status_change_handler(driver, device, zb_rx)
local zone_status = zb_rx.body.zcl_body.zone_status
generate_event_from_zone_status(driver, device, zone_status, zb_rx)
end

local function added_handler(self, device)
device:emit_event(capabilities.battery.battery(100))
device:emit_event(capabilities.smokeDetector.smoke.clear())
device:emit_event(capabilities.tamperAlert.tamper.clear())
end

local function do_configure(self, device)
device:configure()
end

local MultiIR_smoke_detector_handler = {
NAME = "MultiIR Smoke Detector Handler",
lifecycle_handlers = {
added = added_handler,
doConfigure = do_configure
},
zigbee_handlers = {
cluster = {
[IASZone.ID] = {
[IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler
}
},
attr = {
[IASZone.ID] = {
[IASZone.attributes.ZoneStatus.ID] = ias_zone_status_attr_handler
}
}
},
can_handle = require("MultiIR.can_handle")
}

return MultiIR_smoke_detector_handler
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ local sub_drivers = {
lazy_load_if_possible("frient"),
lazy_load_if_possible("aqara-gas"),
lazy_load_if_possible("aqara"),
lazy_load_if_possible("MultiIR"),
}
return sub_drivers
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
-- Copyright 2026 SmartThings, Inc.
-- Licensed under the Apache License, Version 2.0

local test = require "integration_test"
local clusters = require "st.zigbee.zcl.clusters"
local capabilities = require "st.capabilities"
local t_utils = require "integration_test.utils"
local zigbee_test_utils = require "integration_test.zigbee_test_utils"

local IASZone = clusters.IASZone
local PowerConfiguration = clusters.PowerConfiguration

local mock_device = test.mock_device.build_test_zigbee_device(
{ profile = t_utils.get_profile_definition("smoke-battery-tamper.yml"),
zigbee_endpoints = {
[0x01] = {
id = 0x01,
manufacturer = "MultIR",
model = "MIR-SM200",
server_clusters = { 0x0001,0x0003, 0x0005, 0x0006 }
}
}
}
)

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(
"Handle added lifecycle",
function()
test.socket.zigbee:__set_channel_ordering("relaxed")
test.socket.capability:__set_channel_ordering("relaxed")
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
capabilities.battery.battery(100)))
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
capabilities.smokeDetector.smoke.clear()))
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
capabilities.tamperAlert.tamper.clear()))
end,
{
min_api_version = 19
}
)

test.register_coroutine_test(
"Handle doConfigure lifecycle",
function()
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
test.socket.zigbee:__expect_send({
mock_device.id,
zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, PowerConfiguration.ID)
})
test.socket.zigbee:__expect_send({
mock_device.id,
PowerConfiguration.attributes.BatteryPercentageRemaining:configure_reporting(mock_device, 0x001e, 0x5460, 1)
})
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
end,
{
min_api_version = 19
}
)

test.register_message_test(
"Reported ZoneStatus should be handled: smoke/clear tamper/clear",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear())
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())
}
},
{
min_api_version = 19
}
)

test.register_message_test(
"Reported ZoneStatus should be handled: smoke/detected tamper/detected",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0005) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected())
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected())
}
},
{
min_api_version = 19
}
)

test.register_message_test(
"Reported ZoneStatus should be handled: smoke/tested tamper/detected",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0006) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.tested())
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected())
}
},
{
min_api_version = 19
}
)

test.register_message_test(
"ZoneStatusChangeNotification should be handled: smoke/detected tamper/detected",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0005, 0x00) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected())
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected())
}
},
{
min_api_version = 19
}
)

test.register_message_test(
"ZoneStatusChangeNotification should be handled: smoke/tested tamper/detected",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0006, 0x00) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.tested())
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected())
}
},
{
min_api_version = 19
}
)

test.register_message_test(
"ZoneStatusChangeNotification should be handled: smoke/clear tamper/clear",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear())
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())
}
},
{
min_api_version = 19
}
)

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 Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200
Loading