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-contact/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ zigbeeManufacturer:
manufacturer: Aug. Winkhaus SE
model: FM.V.ZB
deviceProfileName: contact-battery-profile
- id: "MultIR/MIR-MC100"
deviceLabel: MultiIR Contact Sensor MIR-MC100
manufacturer: MultIR
model: MIR-MC100
deviceProfileName: contact-battery-tamper-profile
zigbeeGeneric:
- id: "contact-generic"
deviceLabel: "Zigbee Contact Sensor"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: contact-battery-tamper-profile
components:
- id: main
capabilities:
- id: contactSensor
version: 1
- id: battery
version: 1
- id: tamperAlert
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: ContactSensor
13 changes: 13 additions & 0 deletions drivers/SmartThings/zigbee-contact/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-MC100" }
}
71 changes: 71 additions & 0 deletions drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
-- Copyright 2026 SmartThings, Inc.
-- Licensed under the Apache License, Version 2.0


local clusters = require "st.zigbee.zcl.clusters"
local configurationMap = require "configurations"
local capabilities = require "st.capabilities"

local IASZone = clusters.IASZone

local function generate_event_from_zone_status(driver, device, zone_status, zb_rx)
device:emit_event(zone_status:is_alarm1_set() and capabilities.contactSensor.contact.open() or capabilities.contactSensor.contact.closed())
if device:supports_capability_by_id(capabilities.tamperAlert.ID) 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, attr_val, zb_rx)
generate_event_from_zone_status(driver, device, attr_val, zb_rx)
end

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

local function device_init(driver, device)
device:remove_configured_attribute(IASZone.ID, IASZone.attributes.ZoneStatus.ID)
device:remove_monitored_attribute(IASZone.ID, IASZone.attributes.ZoneStatus.ID)
local configuration = configurationMap.get_device_configuration(device)
if configuration ~= nil then
for _, attribute in ipairs(configuration) do
device:add_configured_attribute(attribute)
end
end
end

local function added_handler(driver, device)
device:emit_event(capabilities.battery.battery(100))
device:emit_event(capabilities.contactSensor.contact.closed())
if device:supports_capability_by_id(capabilities.tamperAlert.ID) then
device:emit_event(capabilities.tamperAlert.tamper.clear())
end
end

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

local MultiIR_sensor = {
NAME = "MultiIR Contact Tamper",
lifecycle_handlers = {
init = device_init,
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_sensor
1 change: 1 addition & 0 deletions drivers/SmartThings/zigbee-contact/src/sub_drivers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ local sub_drivers = {
lazy_load_if_possible("smartsense-multi"),
lazy_load_if_possible("sengled"),
lazy_load_if_possible("frient"),
lazy_load_if_possible("MultiIR"),
}
return sub_drivers
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
-- 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("contact-battery-tamper-profile.yml"),
zigbee_endpoints = {
[0x01] = {
id = 0x01,
manufacturer = "MultIR",
model = "MIR-MC100",
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.contactSensor.contact.closed()))
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: contact/closed 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.contactSensor.contact.closed())
},
{
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: contact/open 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.contactSensor.contact.open())
},
{
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: contact/open 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.contactSensor.contact.open())
},
{
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: contact/closed 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.contactSensor.contact.closed())
},
{
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 Contact Sensor MIR-MC100",麦乐克门窗开关传感器MIR-MC100
Loading