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 @@ -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
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
version: 1
- id: button
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: RemoteController
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,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" }
}
164 changes: 164 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,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 PRIVATE_ATTRIBUTE_ID = 0x0000
local MFG_CODE = 0x1235
local FINGERPRINTS = require("firstled-io.fingerprints")

local preference_map = {
["stse.turnOffIndicatorLight"] = {
cluster_id = PRIVATE_CLUSTER_ID,
attribute_id = 0x0000,
mfg_code = MFG_CODE,
data_type = data_types.Boolean,
},
["stse.restorePowerState"] = {
cluster_id = PRIVATE_CLUSTER_ID,
attribute_id = 0x0001,
mfg_code = MFG_CODE,
data_type = data_types.Boolean,
},
["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
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 .. i,
}
driver:try_create_device(metadata)
end
end
end

-- for wireless button
device:emit_event(capabilities.button.numberOfButtons({ value = children_amount },
{ visibility = { displayed = false } }))

device:send(cluster_base.write_manufacturer_specific_attribute(device,
PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) -- private
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
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