diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index db44b09caa..cd3fc2b7a9 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2441,6 +2441,12 @@ zigbeeManufacturer: manufacturer: JNL model: Y-K002-001 deviceProfileName: basic-switch + #FIRSTLED + - id: "FIRSTLED/M4S4BAC" + deviceLabel: Mirror Series 4x4 1 + manufacturer: FIRSTLED + model: M4S4BAC + deviceProfileName: switch-button-light-restore-wireless zigbeeGeneric: - id: "genericSwitch" deviceLabel: Zigbee Switch diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-button-light-restore-wireless.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-button-light-restore-wireless.yml new file mode 100644 index 0000000000..72ae4d1bb2 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-button-light-restore-wireless.yml @@ -0,0 +1,39 @@ +name: switch-button-light-restore-wireless +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: button + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController +preferences: + - title: "背光灯(backlight/백라이트)" + name: backlight + description: "背光灯(backlight/백라이트)" + required: false + preferenceType: enumeration + definition: + options: + 0: "关闭(close/닫다)" + 1: "打开(open/열다)" + 2: "人体移动检测(human detection/인체 움직임 검사)" + default: 2 + - title: "开关上电状态(relay powerOn state)" + name: powerOnStatus + description: "开关上电状态(relay powerOn state/릴레이 초기 동작 상태)" + required: false + preferenceType: enumeration + definition: + options: + 0: "关闭(close/닫다)" + 1: "打开(open/열다)" + 2: "恢复记忆状态(restore/복원)" + default: 2 + - preferenceId: stse.changeToWirelessSwitch + explicit: true diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-button-wireless.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-button-wireless.yml new file mode 100644 index 0000000000..c48290e1a8 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-button-wireless.yml @@ -0,0 +1,17 @@ +name: switch-button-wireless +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: button + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RemoteController +preferences: + - preferenceId: stse.changeToWirelessSwitch + explicit: true diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua new file mode 100644 index 0000000000..5ad2d32a4b --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua @@ -0,0 +1,12 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device) + local FINGERPRINTS = require("firstled-io.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true, require("firstled-io") + end + end + return false +end diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua new file mode 100644 index 0000000000..e323b13663 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "FIRSTLED", model = "M4S4BAC", buttons = 4 , children = 4, child_profile = "switch-button-wireless" } +} diff --git a/drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua b/drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua new file mode 100644 index 0000000000..f073b0d51c --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua @@ -0,0 +1,164 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local device_lib = require "st.device" +local capabilities = require "st.capabilities" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local zcl_clusters = require "st.zigbee.zcl.clusters" + +local Scenes = zcl_clusters.Scenes +local PRIVATE_CLUSTER_ID = 0xFCCA +local MFG_CODE = 0x1235 +local FINGERPRINTS = require("firstled-io.fingerprints") + +local preference_map = { + ["backlight"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0000, + mfg_code = MFG_CODE, + data_type = data_types.Uint8, + }, + ["powerOnStatus"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0001, + mfg_code = MFG_CODE, + data_type = data_types.Uint8, + }, + ["stse.changeToWirelessSwitch"] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = 0x0002, + mfg_code = MFG_CODE, + data_type = data_types.Boolean + } +} + +local function device_info_changed(driver, device, event, args) + local preferences = device.preferences + local old_preferences = args.old_st_store.preferences + if preferences ~= nil then + for id, attr in pairs(preference_map) do + local old_value = old_preferences[id] + local value = preferences[id] + if value ~= nil and value ~= old_value then + if attr.data_type == data_types.Uint8 then + value = tonumber(value) + end + device:send(cluster_base.write_manufacturer_specific_attribute(device, attr.cluster_id, attr.attribute_id, + attr.mfg_code, attr.data_type, value)) + end + end + end +end + +local function get_children_amount(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.children + end + end +end + +local function get_button_amount(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.buttons + end + end +end + +local function get_child_profile_name(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.child_profile + end + end +end + +local function find_child(parent, ep_id) + return parent:get_child_by_parent_assigned_key(string.format("%02X", ep_id)) +end + +local function device_added(driver, device) + -- Only create children for the actual Zigbee device and not the children + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + local children_amount = get_children_amount(device) + if children_amount >= 2 then + for i = 2, children_amount, 1 do + if find_child(device, i) == nil then + local name = string.format("%s%d", string.sub(device.label, 0, -2), i) + local child_profile = get_child_profile_name(device) + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = child_profile, + parent_device_id = device.id, + parent_assigned_child_key = string.format("%02X", i), + vendor_provided_label = name + } + driver:try_create_device(metadata) + end + end + end + + -- Create Button if necessary + local button_amount = get_button_amount(device) + if button_amount >= 1 then + for i = children_amount+1,children_amount + button_amount, 1 do + if find_child(device, i) == nil then + local name = string.format("%s%d", string.sub(device.label, 0, -2), i) + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = "button", + parent_device_id = device.id, + parent_assigned_child_key = string.format("%02X", i), + vendor_provided_label = name, + } + driver:try_create_device(metadata) + end + end + end + + -- for wireless button + device:emit_event(capabilities.button.numberOfButtons({ value = children_amount }, + { visibility = { displayed = false } })) + + elseif device.network_type == "DEVICE_EDGE_CHILD" then + device:emit_event(capabilities.button.numberOfButtons({ value = 1 }, + { visibility = { displayed = false } })) + end + device:emit_event(capabilities.button.supportedButtonValues({ "pushed" }, + { visibility = { displayed = false } })) +end + +local function scenes_cluster_handler(driver, device, zb_rx) + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, + capabilities.button.button.pushed({ state_change = true })) +end + +local function device_init(self, device) + -- for multiple switch + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + device:set_find_child(find_child) + end +end + +local firstled_switch_handler = { + NAME = "FIRSTLED Switch Handler", + lifecycle_handlers = { + init = device_init, + added = device_added, + infoChanged = device_info_changed + }, + zigbee_handlers = { + cluster = { + [Scenes.ID] = { + [Scenes.server.commands.RecallScene.ID] = scenes_cluster_handler, + } + } + }, + can_handle = require("firstled-io.can_handle"), +} + +return firstled_switch_handler diff --git a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua index 736c2a0464..ba8f097430 100644 --- a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua @@ -37,5 +37,6 @@ return { lazy_load_if_possible("frient"), lazy_load_if_possible("frient-IO"), lazy_load_if_possible("color_temp_range_handlers"), - lazy_load_if_possible("stateless_handlers") + lazy_load_if_possible("stateless_handlers"), + lazy_load_if_possible("firstled-io") } diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua new file mode 100644 index 0000000000..f50a8753dd --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_firstled_switch.lua @@ -0,0 +1,873 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local frameCtrl = require "st.zigbee.zcl.frame_ctrl" + +local OnOff = clusters.OnOff +local Scenes = clusters.Scenes +local PRIVATE_CLUSTER_ID = 0xFCCA +local MFG_CODE = 0x1235 + +local parent_switch_profile_def = t_utils.get_profile_definition("switch-button-light-restore-wireless.yml") +local child_switch_profile_def = t_utils.get_profile_definition("switch-button-wireless.yml") +local scene_switch_profile_def = t_utils.get_profile_definition("button.yml") + +local mock_base_device = test.mock_device.build_test_zigbee_device( + { + profile = parent_switch_profile_def, + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "FIRSTLED", + model = "M4S4BAC", + server_clusters = { 0x0004,0x0006 } + } + } + } +) + +local mock_parent_device = test.mock_device.build_test_zigbee_device( + { + profile = parent_switch_profile_def, + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "FIRSTLED", + model = "M4S4BAC", + server_clusters = { 0x0004,0x0006 } + } + } + } +) + +-- Switch 2 (Common Switch) +local mock_first_child = test.mock_device.build_test_child_device( + { + profile = child_switch_profile_def, + device_network_id = string.format("%04X:%02X", mock_parent_device:get_short_address(), 2), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = string.format("%02X", 2) + } +) + +-- Switch 3 (Common Switch) +local mock_second_child = test.mock_device.build_test_child_device( + { + profile = child_switch_profile_def, + device_network_id = string.format("%04X:%02X", mock_parent_device:get_short_address(), 3), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = string.format("%02X", 3) + } +) + +-- Switch 4 (Common Switch) +local mock_third_child = test.mock_device.build_test_child_device( + { + profile = child_switch_profile_def, + device_network_id = string.format("%04X:%02X", mock_parent_device:get_short_address(), 4), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = string.format("%02X", 4) + } +) + +-- Switch 5 (Scene Control Button) +local mock_fourth_child = test.mock_device.build_test_child_device( + { + profile = scene_switch_profile_def, + device_network_id = string.format("%04X:%02X", mock_parent_device:get_short_address(), 5), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = string.format("%02X", 5) + } +) + +-- Switch 6 (Scene Control Button) +local mock_fifth_child = test.mock_device.build_test_child_device( + { + profile = scene_switch_profile_def, + device_network_id = string.format("%04X:%02X", mock_parent_device:get_short_address(), 6), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = string.format("%02X", 6) + } +) + +-- Switch 7 (Scene Control Button) +local mock_sixth_child = test.mock_device.build_test_child_device( + { + profile = scene_switch_profile_def, + device_network_id = string.format("%04X:%02X", mock_parent_device:get_short_address(), 7), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = string.format("%02X", 7) + } +) + +-- Switch 8 (Scene Control Button) +local mock_seventh_child = test.mock_device.build_test_child_device( + { + profile = scene_switch_profile_def, + device_network_id = string.format("%04X:%02X", mock_parent_device:get_short_address(), 8), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = string.format("%02X", 8) + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_base_device) + test.mock_device.add_test_device(mock_parent_device) + test.mock_device.add_test_device(mock_first_child) + test.mock_device.add_test_device(mock_second_child) + test.mock_device.add_test_device(mock_third_child) + test.mock_device.add_test_device(mock_fourth_child) + test.mock_device.add_test_device(mock_fifth_child) + test.mock_device.add_test_device(mock_sixth_child) + test.mock_device.add_test_device(mock_seventh_child) +end + + +test.set_test_init_function(test_init) + +-- 4 Common Switch Tests +test.register_message_test( + "Reported on off status should be handled by parent device: on", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_parent_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + true) :from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_parent_device:generate_test_message("main", capabilities.switch.switch.on()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported on off status should be handled by first child device: on", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_first_child.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + true) :from_endpoint(0x02) } + }, + { + channel = "capability", + direction = "send", + message = mock_first_child:generate_test_message("main", capabilities.switch.switch.on()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported on off status should be handled by Second child device: on", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_second_child.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + true) :from_endpoint(0x03) } + }, + { + channel = "capability", + direction = "send", + message = mock_second_child:generate_test_message("main", capabilities.switch.switch.on()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported on off status should be handled by third child device: on", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_third_child.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + true) :from_endpoint(0x04) } + }, + { + channel = "capability", + direction = "send", + message = mock_third_child:generate_test_message("main", capabilities.switch.switch.on()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_third_child.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "reported on off status should be handled by parent device: off", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_parent_device.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + false) :from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_parent_device:generate_test_message("main", capabilities.switch.switch.off()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_parent_device.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported on off status should be handled by first child device: off", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_first_child.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + false) :from_endpoint(0x02) } + }, + { + channel = "capability", + direction = "send", + message = mock_first_child:generate_test_message("main", capabilities.switch.switch.off()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_first_child.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported on off status should be handled by Second child device: off", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_second_child.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + false) :from_endpoint(0x03) } + }, + { + channel = "capability", + direction = "send", + message = mock_second_child:generate_test_message("main", capabilities.switch.switch.off()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_second_child.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Reported on off status should be handled by third child device: off", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "zigbee", + direction = "receive", + message = { mock_third_child.id, OnOff.attributes.OnOff:build_test_attr_report(mock_parent_device, + false) :from_endpoint(0x04) } + }, + { + channel = "capability", + direction = "send", + message = mock_third_child:generate_test_message("main", capabilities.switch.switch.off()) + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_attr_handler", + { device_uuid = mock_third_child.id, capability_id = "switch", capability_attr_id = "switch" } + } + }, + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Capability on command switch on should be handled : parent device", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_parent_device.id, "init" } + }, + { + channel = "capability", + direction = "receive", + message = { mock_parent_device.id, { capability = "switch", component = "main", command = "on", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_parent_device.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x01) } + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Capability on command switch on should be handled : first child device", + { + { + channel = "capability", + direction = "receive", + message = { mock_first_child.id, { capability = "switch", component = "main", command = "on", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_first_child.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x02) } + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Capability on command switch on should be handled : second child device", + { + { + channel = "capability", + direction = "receive", + message = { mock_second_child.id, { capability = "switch", component = "main", command = "on", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_second_child.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x03) } + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Capability on command switch on should be handled : third child device", + { + { + channel = "capability", + direction = "receive", + message = { mock_third_child.id, { capability = "switch", component = "main", command = "on", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_third_child.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.On(mock_parent_device):to_endpoint(0x04) } + } + }, + { + min_api_version = 19 + } +) + + +test.register_message_test( + "Capability off command switch off should be handled : parent device", + { + { + channel = "capability", + direction = "receive", + message = { mock_parent_device.id, { capability = "switch", component = "main", command = "off", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_parent_device.id, capability_id = "switch", capability_cmd_id = "off" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x01) } + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Capability off command switch off should be handled : first child device", + { + { + channel = "capability", + direction = "receive", + message = { mock_first_child.id, { capability = "switch", component = "main", command = "off", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_first_child.id, capability_id = "switch", capability_cmd_id = "off" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x02) } + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Capability off command switch off should be handled : second child device", + { + { + channel = "capability", + direction = "receive", + message = { mock_second_child.id, { capability = "switch", component = "main", command = "off", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_second_child.id, capability_id = "switch", capability_cmd_id = "off" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x03) } + } + }, + { + min_api_version = 19 + } +) + +test.register_message_test( + "Capability off command switch off should be handled : third child device", + { + { + channel = "capability", + direction = "receive", + message = { mock_third_child.id, { capability = "switch", component = "main", command = "off", args = { } } } + }, + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_third_child.id, capability_id = "switch", capability_cmd_id = "off" } + } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_parent_device.id, OnOff.server.commands.Off(mock_parent_device):to_endpoint(0x04) } + } + }, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "RecallScene command should be handled on ep 1", + function() + local scenes_command = Scenes.server.commands.RecallScene.build_test_rx(mock_parent_device, 0xF0F0, 0x01) + local frm_ctrl = frameCtrl(0x11) + scenes_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_parent_device.id, scenes_command }) + test.socket.capability:__expect_send(mock_parent_device:generate_test_message("main", capabilities.button.button.pushed( + { state_change = true } + ))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "RecallScene command should be handled on ep 2", + function() + local scenes_command = Scenes.server.commands.RecallScene.build_test_rx(mock_first_child, 0xF0F0, 0x02) + local frm_ctrl = frameCtrl(0x11) + scenes_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_parent_device.id, scenes_command }) + test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.button.button.pushed( + { state_change = true } + ))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "RecallScene command should be handled on ep 3", + function() + local scenes_command = Scenes.server.commands.RecallScene.build_test_rx(mock_second_child, 0xF0F0, 0x03) + local frm_ctrl = frameCtrl(0x11) + scenes_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_parent_device.id, scenes_command }) + test.socket.capability:__expect_send(mock_second_child:generate_test_message("main", capabilities.button.button.pushed( + { state_change = true } + ))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "RecallScene command should be handled on ep 4", + function() + local scenes_command = Scenes.server.commands.RecallScene.build_test_rx(mock_third_child, 0xF0F0, 0x04) + local frm_ctrl = frameCtrl(0x11) + scenes_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_parent_device.id, scenes_command }) + test.socket.capability:__expect_send(mock_third_child:generate_test_message("main", capabilities.button.button.pushed( + { state_change = true } + ))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "RecallScene command should be handled on ep 5", + function() + local scenes_command = Scenes.server.commands.RecallScene.build_test_rx(mock_fourth_child, 0xF0F0, 0x05) + local frm_ctrl = frameCtrl(0x11) + scenes_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_parent_device.id, scenes_command }) + test.socket.capability:__expect_send(mock_fourth_child:generate_test_message("main", capabilities.button.button.pushed( + { state_change = true } + ))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "RecallScene command should be handled on ep 6", + function() + local scenes_command = Scenes.server.commands.RecallScene.build_test_rx(mock_fifth_child, 0xF0F0, 0x06) + local frm_ctrl = frameCtrl(0x11) + scenes_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_parent_device.id, scenes_command }) + test.socket.capability:__expect_send(mock_fifth_child:generate_test_message("main", capabilities.button.button.pushed( + { state_change = true } + ))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "RecallScene command should be handled on ep 7", + function() + local scenes_command = Scenes.server.commands.RecallScene.build_test_rx(mock_sixth_child, 0xF0F0, 0x07) + local frm_ctrl = frameCtrl(0x11) + scenes_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_parent_device.id, scenes_command }) + test.socket.capability:__expect_send(mock_sixth_child:generate_test_message("main", capabilities.button.button.pushed( + { state_change = true } + ))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "RecallScene command should be handled on ep 8", + function() + local scenes_command = Scenes.server.commands.RecallScene.build_test_rx(mock_seventh_child, 0xF0F0, 0x08) + local frm_ctrl = frameCtrl(0x11) + scenes_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_parent_device.id, scenes_command }) + test.socket.capability:__expect_send(mock_seventh_child:generate_test_message("main", capabilities.button.button.pushed( + { state_change = true } + ))) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Handle backlight 0 in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_parent_device:generate_info_changed({ + preferences = { backlight = "0" } + })) + test.socket.zigbee:__expect_send({ mock_parent_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent_device, PRIVATE_CLUSTER_ID, + 0x0000, MFG_CODE, data_types.Uint8, 0) }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Handle backlight 1 in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_parent_device:generate_info_changed({ + preferences = { backlight = "1" } + })) + test.socket.zigbee:__expect_send({ mock_parent_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent_device, PRIVATE_CLUSTER_ID, + 0x0000, MFG_CODE, data_types.Uint8, 1) }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Handle backlight 2 in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_parent_device:generate_info_changed({ + preferences = { backlight = "2" } + })) + test.socket.zigbee:__expect_send({ mock_parent_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent_device, PRIVATE_CLUSTER_ID, + 0x0000, MFG_CODE, data_types.Uint8, 2) }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Handle powerOnStatus 0 in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_parent_device:generate_info_changed({ + preferences = { powerOnStatus = "0" } + })) + test.socket.zigbee:__expect_send({ mock_parent_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent_device, PRIVATE_CLUSTER_ID, + 0x0001, MFG_CODE, data_types.Uint8, 0) }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Handle powerOnStatus 1 in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_parent_device:generate_info_changed({ + preferences = { powerOnStatus = "1" } + })) + test.socket.zigbee:__expect_send({ mock_parent_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent_device, PRIVATE_CLUSTER_ID, + 0x0001, MFG_CODE, data_types.Uint8, 1) }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Handle powerOnStatus 2 in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_parent_device:generate_info_changed({ + preferences = { powerOnStatus = "2" } + })) + test.socket.zigbee:__expect_send({ mock_parent_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent_device, PRIVATE_CLUSTER_ID, + 0x0001, MFG_CODE, data_types.Uint8, 2) }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Handle changeToWirelessSwitch true in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_parent_device:generate_info_changed({ + preferences = { ["stse.changeToWirelessSwitch"] = true } + })) + test.socket.zigbee:__expect_send({ mock_parent_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent_device, PRIVATE_CLUSTER_ID, + 0x0002, MFG_CODE, data_types.Boolean, true) }) + end, + { + min_api_version = 19 + } +) + +test.register_coroutine_test( + "Handle changeToWirelessSwitch false in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_parent_device:generate_info_changed({ + preferences = { ["stse.changeToWirelessSwitch"] = false } + })) + test.socket.zigbee:__expect_send({ mock_parent_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_parent_device, PRIVATE_CLUSTER_ID, + 0x0002, MFG_CODE, data_types.Boolean, false) }) + end, + { + min_api_version = 19 + } +) + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index a67fdfeddf..d1a3ed3369 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -140,3 +140,4 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "MultiIR Smart button MIR-SO100",麦乐克智能按钮MIR-SO100 "MultiIR Smoke Detector MIR-SM200",麦乐克烟雾报警器MIR-SM200 "MultiIR Siren MIR-SR100",麦乐克声光报警器MIR-SR100 +"Mirror Series 4x4 1",镜系列4x4 1