Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Requset to add a fingerprints for Kichler Zigbee fan light #1278

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions drivers/SmartThings/zigbee-fan/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ zigbeeManufacturer:
manufacturer: Samsung Electronics
model: SAMSUNG-ITM-Z-003
deviceProfileName: fan-light
# KICHLER FAN LIGHT
- id: "Kichler/KICHLER-FANLIGHT-Z-301"
deviceLabel: Fan light
manufacturer: KICHLER
model: KICHLER-FANLIGHT-Z-301
deviceProfileName: kichler-fan-light
67 changes: 67 additions & 0 deletions drivers/SmartThings/zigbee-fan/profiles/kichler-fan-light.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: kichler-fan-light
components:
- id: main
label: Fan
capabilities:
- id: switch
version: 1
- id: fanSpeed
version: 1
config:
values:
- key: "fanSpeed.value"
range: [0, 3]
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: Fan
- id: light
label: Light
capabilities:
- id: switch
version: 1
- id: switchLevel
version: 1
config:
values:
- key: "level.value"
range: [0, 100]
- id: refresh
version: 1
categories:
- name: Light
preferences:
- name: "trim"
title: "Minimum Dimming Level"
description: "Set the lowest dimming level"
required: false
preferenceType: enumeration
definition:
options:
10 : "10%"
15 : "15%"
20 : "20%"
25 : "25%"
default: 10
Comment on lines +36 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In relation to this preference, I'm happy to direct you to this new feature we added, which is the ability to set min/max values for the switch level attribute. You may find that using this is simpler than having to do the trimming yourself.

Here's an example of how to emit this event:

https://github.com/SmartThingsCommunity/SmartThingsEdgeDrivers/pull/1220/files#diff-7ab55ddbc6a9760ea5b6f928abee244c3142c74389743ce05b895b938ad81f10R372

The min/max should be displayed and carried across automations as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is expected that there will be models derived from this model. We will try to use the code you suggested at that time. Thank you.

- name: "breezemode"
title: "Breeze Mode"
description: "Set the lowest fan speed to breeze mode"
required: false
preferenceType: enumeration
definition:
options:
0 : "Disable"
1 : "Enable"
default: 0
- name: "fandirection"
title: "Fan Direction"
description: "Set Fan Direction : Cannot change it again for 10 seconds after the change. Please set it again after 10 seconds."
required: false
preferenceType: enumeration
definition:
options:
0 : "Reverse"
1 : "Forward"
default: 1
1 change: 1 addition & 0 deletions drivers/SmartThings/zigbee-fan/src/configurations.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ local devices = {
ITM_FAN_LIGHT = {
FINGERPRINTS = {
{ mfr = "Samsung Electronics", model = "SAMSUNG-ITM-Z-003" },
{ mfr = "KICHLER", model = "KICHLER-FANLIGHT-Z-301" },
},
CONFIGURATION = {
{
Expand Down
3 changes: 2 additions & 1 deletion drivers/SmartThings/zigbee-fan/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ local zigbee_fan_driver = {
capabilities.fanspeed
},
sub_drivers = {
require("fan-light")
require("fan-light"),
require("kichler-fan-light")
},
lifecycle_handlers = {
init = device_init
Expand Down
176 changes: 176 additions & 0 deletions drivers/SmartThings/zigbee-fan/src/kichler-fan-light/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
-- Copyright 2024 SmartThings
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
local clusters = require "st.zigbee.zcl.clusters"
local capabilities = require "st.capabilities"
local FanControl = clusters.FanControl
local Level = clusters.Level
local OnOff = clusters.OnOff

local FINGERPRINTS = {
{ mfr = "KICHLER", model = "KICHLER-FANLIGHT-Z-301" },
}

local function can_handle_itm_fanlight(opts, driver, device)
for _, fingerprint in ipairs(FINGERPRINTS) do
if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then
return true
end
end
return false
end

-- CAPABILITY HANDLERS
local function on_handler(driver, device, command)
if command.component == 'light' then
local last_level = device:get_field('LAST_DIM_LEVEL') or 100
local level = math.floor((last_level/100.0) * 254 )
device:send_to_component('light', Level.server.commands.MoveToLevelWithOnOff(device, level, command.args.rate or 0xFFFF))
device:set_field('LAST_DIM_LEVEL', last_level, {persist = true})
else
local speed = device:get_field('LAST_FAN_SPD') or 1
device:send(FanControl.attributes.FanMode:write(device, speed))
end
device:send(FanControl.attributes.FanMode:read(device))
end

local function off_handler(driver, device, command)
if command.component == 'light' then
local last_level = device:get_field('LAST_DIM_LEVEL') or 100
device:send_to_component('light', Level.server.commands.MoveToLevelWithOnOff(device, 0, command.args.rate or 0xFFFF))
device:set_field('LAST_DIM_LEVEL', last_level, {persist = true})
else
device:send(FanControl.attributes.FanMode:write(device, FanControl.attributes.FanMode.OFF))
end
device:send(FanControl.attributes.FanMode:read(device))
end

local function switch_level_handler(driver, device, command)
local trim_level = tonumber(device.preferences.trim) or 10
if command.args.level <= trim_level and command.args.level >= 1 then
local level = math.floor((trim_level/100.0) * 254 )
device:emit_component_event(device.profile.components.light, capabilities.switchLevel.level(command.args.level))
device:send_to_component('light', Level.server.commands.MoveToLevelWithOnOff(device, level, command.args.rate or 0xFFFF))
device:emit_component_event(device.profile.components.light, capabilities.switchLevel.level(trim_level))
Comment on lines +62 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the intent behind these two level events from the same component here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to use the Trim function. This trim is used to limit the minimum value of dimming.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the point of the trim function, but it seems like you'd want to only emit the trimmed value, not both the untrimmed and trimmed value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to refresh the value below the trim setting value so that it is not reflected in the UI. If you set it to trim 25% and control it to 10%, it will be 25%.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So why send both the 10% event and the 25% event? Wouldn't you want to just send the 25% event?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

24캡처

Set Trim to 25%.
If the user has changed to 10% in the UI, the intention is to express it as 25%.
However, if you adjust 25% and then adjust it to less than that (e.g., 20%), it is to inform you of the event that it has been changed to go back to 25%

device:set_field('LAST_DIM_LEVEL', trim_level, {persist = true})
elseif command.args.level < 1 then
local level = 0
device:send_to_component('light', Level.server.commands.MoveToLevelWithOnOff(device, level, command.args.rate or 0xFFFF))
else
local level = math.floor((command.args.level/100.0) * 254 )
device:send_to_component('light', Level.server.commands.MoveToLevelWithOnOff(device, level, command.args.rate or 0xFFFF))
device:set_field('LAST_DIM_LEVEL', command.args.level, {persist = true})
end
end

local function fan_speed_handler(driver, device, command)
if command.args.speed < 4 then
device:send(FanControl.attributes.FanMode:write(device, command.args.speed))
device:send(FanControl.attributes.FanMode:read(device))
end
end

-- ZIGBEE HANDLERS
local function zb_fan_control_handler(driver, device, value, zb_rx)
if value.value < 4 then
device:emit_event(capabilities.fanSpeed.fanSpeed(value.value))
local evt = capabilities.switch.switch(value.value > 0 and 'on' or 'off', { visibility = { displayed = true } })
device:emit_component_event(device.profile.components.main, evt)
device:emit_component_event(device.profile.components.main, capabilities.fanSpeed.fanSpeed(value.value))
if value.value > 0 then
device:set_field('LAST_FAN_SPD', value.value, {persist = true})
end
end
end

local function zb_level_handler(driver, device, value, zb_rx)
local evt = capabilities.switchLevel.level(math.floor(0.5 + (value.value / 254.0) * 100))
device:emit_component_event(device.profile.components.light, evt)
end

local function zb_onoff_handler(driver, device, value, zb_rx)
local attr = capabilities.switch.switch
local evt = value.value and attr.on() or attr.off()
device:emit_component_event(device.profile.components.light, evt)
end

local function info_changed(driver, device, event, args)
if device.preferences ~= nil then
local current_level = device:get_latest_state('light', capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) or 100
local new_trim = tonumber(device.preferences.trim)
local old_trim = tonumber(args.old_st_store.preferences.trim)
if args.old_st_store.preferences.trim ~= device.preferences.trim then
local newlevel = math.floor((current_level/100.0) * 254 )
if old_trim < new_trim then
if new_trim >= current_level then
newlevel = math.floor((new_trim/100.0) * 254 )
end
end
device:send_to_component('light', Level.server.commands.MoveToLevelWithOnOff(device, newlevel, 0))
end
if device.preferences.breezemode ~= args.old_st_store.preferences.breezemode then
local speed = device:get_field('LAST_FAN_SPD') or 1
local breeze_flag = tonumber(device.preferences.breezemode)
if breeze_flag == 0 then
device:send(FanControl.attributes.FanMode:write(device, 5))
device:send(FanControl.attributes.FanMode:write(device, speed))
elseif breeze_flag == 1 then
device:send(FanControl.attributes.FanMode:write(device, 4))
end
end
if device.preferences.fandirection ~= args.old_st_store.preferences.fandirection then
local send_fandirection_time = device:get_field('FANDIRECTION_SENDTIME') or 0
local current_fandirection_time = os.time()
local time_difference = os.difftime(current_fandirection_time, send_fandirection_time) or 0
if time_difference >= 10 or send_fandirection_time == 0 then
device:send(FanControl.attributes.FanMode:write(device, 6))
device:set_field('FANDIRECTION_SENDTIME', current_fandirection_time, {persist = false})
end
end
end
end

local kichler_fan_light = {
NAME = "KICHLER Fan Light",
zigbee_handlers = {
attr = {
[FanControl.ID] = {
[FanControl.attributes.FanMode.ID] = zb_fan_control_handler
},
[Level.ID] = {
[Level.attributes.CurrentLevel.ID] = zb_level_handler
},
[OnOff.ID] = {
[OnOff.attributes.OnOff.ID] = zb_onoff_handler
}
}
},
capability_handlers = {
[capabilities.switch.ID] = {
[capabilities.switch.commands.on.NAME] = on_handler,
[capabilities.switch.commands.off.NAME] = off_handler,
},
[capabilities.switchLevel.ID] = {
[capabilities.switchLevel.commands.setLevel.NAME] = switch_level_handler
},
[capabilities.fanSpeed.ID] = {
[capabilities.fanSpeed.commands.setFanSpeed.NAME] = fan_speed_handler
}
},
lifecycle_handlers = {
infoChanged = info_changed
},
can_handle = can_handle_itm_fanlight
}

return kichler_fan_light
Loading
Loading