From c237c6be65e470e2b87cabcc349c20ee2992f167 Mon Sep 17 00:00:00 2001 From: thinkaName <962679819@qq.com> Date: Wed, 1 Apr 2026 14:50:29 +0800 Subject: [PATCH] add MultiIR contact sensor MIR_MC100 --- .../zigbee-contact/fingerprints.yml | 5 + .../contact-battery-tamper-profile.yml | 16 ++ .../zigbee-contact/src/MultiIR/can_handle.lua | 13 ++ .../src/MultiIR/fingerprints.lua | 6 + .../zigbee-contact/src/MultiIR/init.lua | 71 ++++++++ .../zigbee-contact/src/sub_drivers.lua | 1 + .../src/test/test_multiir_contact_tamper.lua | 167 ++++++++++++++++++ tools/localizations/cn.csv | 1 + 8 files changed, 280 insertions(+) create mode 100755 drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-profile.yml create mode 100755 drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua create mode 100755 drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua create mode 100755 drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua create mode 100755 drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua diff --git a/drivers/SmartThings/zigbee-contact/fingerprints.yml b/drivers/SmartThings/zigbee-contact/fingerprints.yml index c30c3296a2..81ed824683 100644 --- a/drivers/SmartThings/zigbee-contact/fingerprints.yml +++ b/drivers/SmartThings/zigbee-contact/fingerprints.yml @@ -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" diff --git a/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-profile.yml b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-profile.yml new file mode 100755 index 0000000000..3b8c889fda --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper-profile.yml @@ -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 diff --git a/drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua b/drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua new file mode 100755 index 0000000000..d389619c78 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/MultiIR/can_handle.lua @@ -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 diff --git a/drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua b/drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua new file mode 100755 index 0000000000..4bda1794f0 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/MultiIR/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "MultIR", model = "MIR-MC100" } +} diff --git a/drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua b/drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua new file mode 100755 index 0000000000..8596c6a21f --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/MultiIR/init.lua @@ -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 diff --git a/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua b/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua index 394de5a72d..8a4977ca5e 100644 --- a/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-contact/src/sub_drivers.lua @@ -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 diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua b/drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua new file mode 100755 index 0000000000..dcef4ef2c8 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/test/test_multiir_contact_tamper.lua @@ -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() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index c03098c37f..6d24e38345 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -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