-
Notifications
You must be signed in to change notification settings - Fork 529
add MultiIR Smoke Detector MIR-SM200 #2874
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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" } | ||
| } |
| 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 | ||||||||
|
Comment on lines
+22
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This function is unnecessary. Use |
||||||||
|
|
||||||||
| 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 | ||||||||
|
Comment on lines
+37
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The defaults cover this and inject a refresh command. You should remove it and use the default. |
||||||||
|
|
||||||||
| 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 | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,196 @@ | ||||||
| -- 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-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) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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() | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: add newline to EOF |
||||||
There was a problem hiding this comment.
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:
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?