Skip to content
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-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-no-fw-update
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: smoke-battery-tamper-no-fw-update
components:
- id: main
capabilities:
- id: smokeDetector
version: 1
- id: tamperAlert
version: 1
- id: battery
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" }
}
53 changes: 53 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,53 @@
-- 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())
Comment on lines +10 to +13

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For ZoneStatus attribute bitmap, I see the following in the spec for bits that can be set:

Image

Is the alarm2 bit corresponding to the test functionality just the way the device works? Do we need to also be considering the test mode bit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is included in the capability definition of the smoke detector. It is also required in the Test Suite test items. If this is not added, the test will fail.

@cjswedes cjswedes Apr 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I understand that the "tested" variation is a part of the smokeDetector capability, I guess my question is if it is hooked up correctly, since the spec shows a different bit that could be used to indicate it is being tested. Did you validate the test functionality with a real device? Its fine if this is how it works, I just wanted to confirm, since its not how I would expect after reading the spec.

Edit: Sorry, I am a bit new to reviewing WWST PRs. I was able to find the test results you are referencing, and since it passes, LGTM.

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_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 MultiIR_smoke_detector_handler = {
NAME = "MultiIR Smoke Detector Handler",
lifecycle_handlers = {
added = added_handler
},
zigbee_handlers = {
cluster = {
[IASZone.ID] = {
[IASZone.client.commands.ZoneStatusChangeNotification.ID] = ias_zone_status_change_handler
}
},
attr = {
[IASZone.ID] = {
[IASZone.attributes.ZoneStatus.ID] = generate_event_from_zone_status
}
}
},
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,195 @@
-- 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 mock_device = test.mock_device.build_test_zigbee_device(
{ profile = t_utils.get_profile_definition("smoke-battery-tamper-no-fw-update.yml"),
zigbee_endpoints = {
[0x01] = {
id = 0x01,
manufacturer = "MultIR",
model = "MIR-SM200",
server_clusters = { 0x0001,0x0020, 0x0500, 0x0502 }
}
}
}
)

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