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
6 changes: 6 additions & 0 deletions drivers/SmartThings/zigbee-switch/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2446,6 +2446,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-light-restore-wireless
zigbeeGeneric:
- id: "genericSwitch"
deviceLabel: Zigbee Switch
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: switch-button-wireless
components:
- id: main
capabilities:
- id: switch
Comment thread
thinkaName marked this conversation as resolved.
version: 1
- id: button
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: RemoteController
preferences:
- preferenceId: stse.changeToWirelessSwitch
explicit: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: switch-light-restore-wireless
components:
- id: main
capabilities:
- id: switch
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: Switch
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
15 changes: 15 additions & 0 deletions drivers/SmartThings/zigbee-switch/profiles/switch-wireless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: switch-wireless
components:
- id: main
capabilities:
- id: switch
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: Switch
preferences:
- preferenceId: stse.changeToWirelessSwitch
explicit: true
12 changes: 12 additions & 0 deletions drivers/SmartThings/zigbee-switch/src/firstled-io/can_handle.lua
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Copyright 2026 SmartThings, Inc.
-- Licensed under the Apache License, Version 2.0

--The number of children determines the number of sub-devices to be created. Each sub-device has the capability to switch between a switch and a button.
--The number of buttons determines how many buttons devices will be created.
--The driver supports a series of device combinations, such as 4+4, 3+3, 2+2, 4+0, etc., of switch and button type products.
return {
{ mfr = "FIRSTLED", model = "M4S4BAC", children = 4, buttons = 4, child_profile = "switch-wireless" }
}
202 changes: 202 additions & 0 deletions drivers/SmartThings/zigbee-switch/src/firstled-io/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
-- 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")

--stse.changeToWirelessSwitch
--switch mode:The local switch and the app can control the relay.The button capability is not working.
--wirelessSwitch mode:The local switch does not control the relay. Once triggered, it will report to the system as "RecallScene",emit_event button.pushed. The relay can be controlled via the app.
local preference_map = {
Comment thread
thinkaName marked this conversation as resolved.
["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 is_parent_device(device)
local parent = device:get_parent_device()
return parent == nil
end

--If it is in the switch mode, only the switch will be displayed. If it is in the wirelessSwitch mode, both the switch and the button will be displayed.
local function toggle_button_visibility(device, show)
local is_parent = is_parent_device(device)
if is_parent then
if show then
device:try_update_metadata({profile = "switch-button-light-restore-wireless"})
--It will take some time for the profile to be updated. Otherwise, the button operation below will fail.
device.thread:call_with_delay(5, function()
device:emit_event(capabilities.button.numberOfButtons({value = 1}, {visibility = {displayed = false}}))
device:emit_event(capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))
end)
device:emit_event(capabilities.button.numberOfButtons({ value = 1 },
{ visibility = { displayed = false } }))
device:emit_event(capabilities.button.supportedButtonValues({ "pushed" },
{ visibility = { displayed = false } }))
else
device:try_update_metadata({profile = "switch-light-restore-wireless"})
end
else
if show then
device:try_update_metadata({profile = "switch-button-wireless"})
device.thread:call_with_delay(5, function()
device:emit_event(capabilities.button.numberOfButtons({value = 1}, {visibility = {displayed = false}}))
device:emit_event(capabilities.button.supportedButtonValues({"pushed"}, {visibility = {displayed = false}}))
end)
else
device:try_update_metadata({profile = "switch-wireless"})
end
end
end

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,
Comment thread
thinkaName marked this conversation as resolved.
attr.mfg_code, attr.data_type, value))
--Switch to the corresponding profile based on stse.changeToWirelessSwitch
if id == "stse.changeToWirelessSwitch" then
toggle_button_visibility(device, value)
end
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

--Create composite switches such as 4+4, 1+1, 4+0, 3+0 children+button
local function device_added(driver, device)
Comment thread
cjswedes marked this conversation as resolved.
-- Create the corresponding number of child devices based on the value of fingerprint.children
if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then
local children_amount = get_children_amount(device)
if children_amount >= 2 then
Comment thread
cjswedes marked this conversation as resolved.
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 the corresponding number of button devices based on the value of fingerprint.buttons
local button_amount = get_button_amount(device)
if button_amount >= 1 then
Comment thread
cjswedes marked this conversation as resolved.
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)
Comment thread
thinkaName marked this conversation as resolved.
end
end
end
elseif device.network_type == "DEVICE_EDGE_CHILD" then
device:emit_event(capabilities.button.numberOfButtons({ value = 1 },
{ visibility = { displayed = false } }))
device:emit_event(capabilities.button.supportedButtonValues({ "pushed" },
{ visibility = { displayed = false } }))
end
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
3 changes: 2 additions & 1 deletion drivers/SmartThings/zigbee-switch/src/sub_drivers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Loading
Loading