Skip to content

Commit 0c4f7e2

Browse files
committed
Use native & default handlers for ZLL devices
1 parent b1552f1 commit 0c4f7e2

File tree

8 files changed

+248
-16
lines changed

8 files changed

+248
-16
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
-- Copyright 2022 SmartThings
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
15+
local capabilities = require "st.capabilities"
16+
local clusters = require "st.zigbee.zcl.clusters"
17+
local switch_defaults = require "st.zigbee.defaults.switch_defaults"
18+
local configurationMap = require "configurations"
19+
local utils = require "st.utils"
20+
21+
local ColorControl = clusters.ColorControl
22+
23+
local CURRENT_X = "current_x_value" -- y value from xyY color space
24+
local CURRENT_Y = "current_y_value" -- x value from xyY color space
25+
local Y_TRISTIMULUS_VALUE = "y_tristimulus_value" -- Y tristimulus value which is used to convert color xyY -> RGB -> HSV
26+
local HUESAT_TIMER = "huesat_timer"
27+
local TARGET_HUE = "target_hue"
28+
local TARGET_SAT = "target_sat"
29+
30+
local IKEA_XY_COLOR_BULB_FINGERPRINTS = {
31+
["IKEA of Sweden"] = {
32+
["TRADFRI bulb E27 CWS opal 600lm"] = true,
33+
["TRADFRI bulb E26 CWS opal 600lm"] = true
34+
}
35+
}
36+
37+
local function can_handle_ikea_xy_color_bulb(opts, driver, device)
38+
local can_handle = (IKEA_XY_COLOR_BULB_FINGERPRINTS[device:get_manufacturer()] or {})[device:get_model()]
39+
if can_handle then
40+
local subdriver = require("ikea-xy-color-bulb")
41+
return true, subdriver
42+
else
43+
return false
44+
end
45+
end
46+
47+
local function do_configure(driver, device)
48+
device:configure()
49+
end
50+
51+
local function device_added(driver, device)
52+
device:refresh()
53+
end
54+
55+
local device_init = function(self, device)
56+
device:remove_configured_attribute(ColorControl.ID, ColorControl.attributes.CurrentHue.ID)
57+
device:remove_configured_attribute(ColorControl.ID, ColorControl.attributes.CurrentSaturation.ID)
58+
device:remove_monitored_attribute(ColorControl.ID, ColorControl.attributes.CurrentHue.ID)
59+
device:remove_monitored_attribute(ColorControl.ID, ColorControl.attributes.CurrentSaturation.ID)
60+
61+
local configuration = configurationMap.get_device_configuration(device)
62+
if configuration ~= nil then
63+
for _, attribute in ipairs(configuration) do
64+
device:add_configured_attribute(attribute)
65+
end
66+
end
67+
end
68+
69+
local function store_xyY_values(device, x, y, Y)
70+
device:set_field(Y_TRISTIMULUS_VALUE, Y)
71+
device:set_field(CURRENT_X, x)
72+
device:set_field(CURRENT_Y, y)
73+
end
74+
75+
local query_device = function(device)
76+
return function()
77+
device:send(ColorControl.attributes.CurrentX:read(device))
78+
device:send(ColorControl.attributes.CurrentY:read(device))
79+
end
80+
end
81+
82+
local function set_color_handler(driver, device, cmd)
83+
-- Cancel the hue/sat timer if it's running, since setColor includes both hue and saturation
84+
local huesat_timer = device:get_field(HUESAT_TIMER)
85+
if huesat_timer ~= nil then
86+
device.thread:cancel_timer(huesat_timer)
87+
device:set_field(HUESAT_TIMER, nil)
88+
end
89+
90+
local hue = (cmd.args.color.hue ~= nil and cmd.args.color.hue > 99) and 99 or cmd.args.color.hue
91+
local sat = cmd.args.color.saturation
92+
93+
local x, y, Y = utils.safe_hsv_to_xy(hue, sat)
94+
store_xyY_values(device, x, y, Y)
95+
switch_defaults.on(driver, device, cmd)
96+
97+
device:send(ColorControl.commands.MoveToColor(device, x, y, 0x0000))
98+
99+
device:set_field(TARGET_HUE, nil)
100+
device:set_field(TARGET_SAT, nil)
101+
device.thread:call_with_delay(2, query_device(device))
102+
end
103+
104+
local huesat_timer_callback = function(driver, device, cmd)
105+
return function()
106+
device:set_field(HUESAT_TIMER, nil)
107+
local hue = device:get_field(TARGET_HUE)
108+
local sat = device:get_field(TARGET_SAT)
109+
hue = hue ~= nil and hue or device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME)
110+
sat = sat ~= nil and sat or device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.saturation.NAME)
111+
cmd.args = {
112+
color = {
113+
hue = hue,
114+
saturation = sat
115+
}
116+
}
117+
set_color_handler(driver, device, cmd)
118+
end
119+
end
120+
121+
local function set_hue_sat_helper(driver, device, cmd, hue, sat)
122+
local huesat_timer = device:get_field(HUESAT_TIMER)
123+
if huesat_timer ~= nil then
124+
device.thread:cancel_timer(huesat_timer)
125+
device:set_field(HUESAT_TIMER, nil)
126+
end
127+
if hue ~= nil and sat ~= nil then
128+
cmd.args = {
129+
color = {
130+
hue = hue,
131+
saturation = sat
132+
}
133+
}
134+
set_color_handler(driver, device, cmd)
135+
else
136+
if hue ~= nil then
137+
device:set_field(TARGET_HUE, hue)
138+
elseif sat ~= nil then
139+
device:set_field(TARGET_SAT, sat)
140+
end
141+
device:set_field(HUESAT_TIMER, device.thread:call_with_delay(0.2, huesat_timer_callback(driver, device, cmd)))
142+
end
143+
end
144+
145+
local function set_hue_handler(driver, device, cmd)
146+
set_hue_sat_helper(driver, device, cmd, cmd.args.hue, device:get_field(TARGET_SAT))
147+
end
148+
149+
local function set_saturation_handler(driver, device, cmd)
150+
set_hue_sat_helper(driver, device, cmd, device:get_field(TARGET_HUE), cmd.args.saturation)
151+
end
152+
153+
local function current_x_attr_handler(driver, device, value, zb_rx)
154+
local Y_tristimulus = device:get_field(Y_TRISTIMULUS_VALUE)
155+
local y = device:get_field(CURRENT_Y)
156+
local x = value.value
157+
158+
if y then
159+
local hue, saturation = utils.safe_xy_to_hsv(x, y, Y_tristimulus)
160+
161+
device:emit_event(capabilities.colorControl.hue(hue))
162+
device:emit_event(capabilities.colorControl.saturation(saturation))
163+
end
164+
165+
device:set_field(CURRENT_X, x)
166+
end
167+
168+
local function current_y_attr_handler(driver, device, value, zb_rx)
169+
local Y_tristimulus = device:get_field(Y_TRISTIMULUS_VALUE)
170+
local x = device:get_field(CURRENT_X)
171+
local y = value.value
172+
173+
if x then
174+
local hue, saturation = utils.safe_xy_to_hsv(x, y, Y_tristimulus)
175+
176+
device:emit_event(capabilities.colorControl.hue(hue))
177+
device:emit_event(capabilities.colorControl.saturation(saturation))
178+
end
179+
180+
device:set_field(CURRENT_Y, y)
181+
end
182+
183+
local ikea_xy_color_bulb = {
184+
NAME = "IKEA XY Color Bulb",
185+
lifecycle_handlers = {
186+
doConfigure = do_configure,
187+
added = device_added,
188+
init = configurationMap.power_reconfig_wrapper(device_init)
189+
},
190+
capability_handlers = {
191+
[capabilities.colorControl.ID] = {
192+
[capabilities.colorControl.commands.setColor.NAME] = set_color_handler,
193+
[capabilities.colorControl.commands.setHue.NAME] = set_hue_handler,
194+
[capabilities.colorControl.commands.setSaturation.NAME] = set_saturation_handler
195+
}
196+
},
197+
zigbee_handlers = {
198+
attr = {
199+
[ColorControl.ID] = {
200+
[ColorControl.attributes.CurrentX.ID] = current_x_attr_handler,
201+
[ColorControl.attributes.CurrentY.ID] = current_y_attr_handler
202+
}
203+
}
204+
},
205+
can_handle = can_handle_ikea_xy_color_bulb
206+
}
207+
208+
return ikea_xy_color_bulb

drivers/SmartThings/zigbee-switch/src/init.lua

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@ local SimpleMetering = clusters.SimpleMetering
2222
local ElectricalMeasurement = clusters.ElectricalMeasurement
2323
local preferences = require "preferences"
2424
local device_lib = require "st.device"
25+
local version = require "version"
26+
local constants = require "st.zigbee.constants"
2527

2628
local function lazy_load_if_possible(sub_driver_name)
27-
-- gets the current lua libs api version
28-
local version = require "version"
29-
3029
-- version 9 will include the lazy loading functions
3130
if version.api >= 9 then
3231
return ZigbeeDriver.lazy_load_sub_driver(require(sub_driver_name))
@@ -41,7 +40,9 @@ local function info_changed(self, device, event, args)
4140
end
4241

4342
local do_configure = function(self, device)
44-
device:refresh()
43+
if version.api < 16 or (version.api > 15 and device:get_profile_id() ~= constants.ZLL_PROFILE_ID) then
44+
device:refresh()
45+
end
4546
device:configure()
4647

4748
-- Additional one time configuration
@@ -126,6 +127,9 @@ local function device_added(driver, device, event)
126127
end
127128
end
128129
end
130+
if version.api > 15 and device:get_profile_id() == constants.ZLL_PROFILE_ID then
131+
device:refresh()
132+
end
129133
end
130134

131135

@@ -157,7 +161,7 @@ local zigbee_switch_driver_template = {
157161
lazy_load_if_possible("zigbee-dimming-light"),
158162
lazy_load_if_possible("white-color-temp-bulb"),
159163
lazy_load_if_possible("rgbw-bulb"),
160-
lazy_load_if_possible("zll-dimmer-bulb"),
164+
(version.api < 16) and lazy_load_if_possible("zll-dimmer-bulb") or lazy_load_if_possible("ikea-xy-color-bulb"),
161165
lazy_load_if_possible("zll-polling"),
162166
lazy_load_if_possible("zigbee-switch-power"),
163167
lazy_load_if_possible("ge-link-bulb"),

drivers/SmartThings/zigbee-switch/src/test/test_sengled_dimmer_bulb_with_motion_sensor.lua

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ local test = require "integration_test"
1616
local capabilities = require "st.capabilities"
1717
local clusters = require "st.zigbee.zcl.clusters"
1818
local t_utils = require "integration_test.utils"
19+
local version = require "version"
1920
local zigbee_test_utils = require "integration_test.zigbee_test_utils"
2021

2122
local OnOff = clusters.OnOff
2223
local Level = clusters.Level
2324
local IASZone = clusters.IASZone
24-
local IasEnrollResponseCode = IASZone.types.EnrollResponseCode
2525

2626
local mock_device = test.mock_device.build_test_zigbee_device(
2727
{ profile = t_utils.get_profile_definition("on-off-level-motion-sensor.yml"),
@@ -31,7 +31,8 @@ local mock_device = test.mock_device.build_test_zigbee_device(
3131
id = 1,
3232
manufacturer = "sengled",
3333
model = "E13-N11",
34-
server_clusters = { 0x0006, 0x0008, 0x0500 }
34+
server_clusters = { 0x0006, 0x0008, 0x0500 },
35+
profile_id = 0xC05E
3536
}
3637
}
3738
}
@@ -76,14 +77,6 @@ test.register_coroutine_test(
7677
mock_device.id,
7778
IASZone.attributes.ZoneStatus:configure_reporting(mock_device, 30, 300, 1)
7879
})
79-
test.socket.zigbee:__expect_send({
80-
mock_device.id,
81-
IASZone.attributes.IASCIEAddress:write(mock_device, zigbee_test_utils.mock_hub_eui)
82-
})
83-
test.socket.zigbee:__expect_send({
84-
mock_device.id,
85-
IASZone.server.commands.ZoneEnrollResponse(mock_device, IasEnrollResponseCode.SUCCESS, 0x00)
86-
})
8780
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
8881
end
8982
)
@@ -116,6 +109,7 @@ test.register_coroutine_test(
116109
test.socket.zigbee:__set_channel_ordering("relaxed")
117110
test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot")
118111
test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "on", args = {} } })
112+
if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "on") end
119113

120114
test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device) })
121115

@@ -134,6 +128,7 @@ test.register_coroutine_test(
134128
test.socket.zigbee:__set_channel_ordering("relaxed")
135129
test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot")
136130
test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "off", args = {} } })
131+
if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "off") end
137132

138133
test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.Off(mock_device) })
139134

@@ -152,6 +147,7 @@ test.register_coroutine_test(
152147
test.socket.zigbee:__set_channel_ordering("relaxed")
153148
test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot")
154149
test.socket.capability:__queue_receive({ mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 57 } } })
150+
if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switchLevel", "setLevel") end
155151

156152
test.socket.zigbee:__expect_send({ mock_device.id, Level.server.commands.MoveToLevelWithOnOff(mock_device, 144, 0xFFFF) })
157153

drivers/SmartThings/zigbee-switch/src/test/test_zll_color_temp_bulb.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
local test = require "integration_test"
1616
local clusters = require "st.zigbee.zcl.clusters"
1717
local t_utils = require "integration_test.utils"
18+
local version = require "version"
1819
local zigbee_test_utils = require "integration_test.zigbee_test_utils"
1920

2021
local OnOff = clusters.OnOff
@@ -83,6 +84,7 @@ test.register_coroutine_test(
8384
test.socket.zigbee:__set_channel_ordering("relaxed")
8485
test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot")
8586
test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "on", args = {} } })
87+
if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "on") end
8688
test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device)})
8789
test.wait_for_events()
8890
test.mock_time.advance_time(2)
@@ -98,6 +100,7 @@ test.register_coroutine_test(
98100
test.socket.zigbee:__set_channel_ordering("relaxed")
99101
test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot")
100102
test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "off", args = {} } })
103+
if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "off") end
101104
test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.Off(mock_device)})
102105
test.wait_for_events()
103106
test.mock_time.advance_time(2)
@@ -113,6 +116,7 @@ test.register_coroutine_test(
113116
test.socket.zigbee:__set_channel_ordering("relaxed")
114117
test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot")
115118
test.socket.capability:__queue_receive({ mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = {50} } })
119+
if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switchLevel", "setLevel") end
116120
test.socket.zigbee:__expect_send({ mock_device.id, Level.commands.MoveToLevelWithOnOff(mock_device, math.floor(50 / 100.0 * 254), 0xFFFF)})
117121
test.wait_for_events()
118122
test.mock_time.advance_time(2)
@@ -128,6 +132,7 @@ test.register_coroutine_test(
128132
test.socket.zigbee:__set_channel_ordering("relaxed")
129133
test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot")
130134
test.socket.capability:__queue_receive({ mock_device.id, { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {200} } })
135+
if version.api > 15 then mock_device:expect_native_cmd_handler_registration("colorTemperature", "setColorTemperature") end
131136
test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device)})
132137
test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.commands.MoveToColorTemperature(mock_device, 5000, 0x0000)})
133138
test.wait_for_events()

drivers/SmartThings/zigbee-switch/src/test/test_zll_dimmer_bulb.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
local test = require "integration_test"
1616
local clusters = require "st.zigbee.zcl.clusters"
1717
local t_utils = require "integration_test.utils"
18+
local version = require "version"
1819
local zigbee_test_utils = require "integration_test.zigbee_test_utils"
1920

2021
local OnOff = clusters.OnOff
@@ -121,6 +122,7 @@ test.register_coroutine_test(
121122
test.socket.zigbee:__set_channel_ordering("relaxed")
122123
test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot")
123124
test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "on", args = {} } })
125+
if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "on") end
124126

125127
test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.On(mock_device) })
126128

@@ -138,6 +140,7 @@ test.register_coroutine_test(
138140
test.socket.zigbee:__set_channel_ordering("relaxed")
139141
test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot")
140142
test.socket.capability:__queue_receive({ mock_device.id, { capability = "switch", component = "main", command = "off", args = {} } })
143+
if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switch", "off") end
141144

142145
test.socket.zigbee:__expect_send({ mock_device.id, OnOff.commands.Off(mock_device) })
143146

@@ -155,6 +158,7 @@ test.register_coroutine_test(
155158
test.socket.zigbee:__set_channel_ordering("relaxed")
156159
test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot")
157160
test.socket.capability:__queue_receive({ mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 57 } } })
161+
if version.api > 15 then mock_device:expect_native_cmd_handler_registration("switchLevel", "setLevel") end
158162

159163
test.socket.zigbee:__expect_send({ mock_device.id, Level.server.commands.MoveToLevelWithOnOff(mock_device, 144, 0xFFFF) })
160164

0 commit comments

Comments
 (0)