Skip to content

Commit a4e2a02

Browse files
authored
Matter Switch: Write EXECUTE_IF_OFF bit to Control Cluster Options attribute (#2535)
1 parent eb90b37 commit a4e2a02

File tree

9 files changed

+119
-20
lines changed

9 files changed

+119
-20
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ end
4040

4141
function SwitchLifecycleHandlers.do_configure(driver, device)
4242
if device.network_type == device_lib.NETWORK_TYPE_MATTER and not switch_utils.detect_bridge(device) then
43+
switch_cfg.set_device_control_options(device)
4344
device_cfg.match_profile(driver, device)
4445
end
4546
end

drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,21 @@ function SwitchDeviceConfiguration.create_child_devices(driver, device, server_o
7272
device:set_find_child(switch_utils.find_child)
7373
end
7474

75+
-- Per the spec, these attributes are "meant to be changed only during commissioning."
76+
function SwitchDeviceConfiguration.set_device_control_options(device)
77+
for _, ep_info in ipairs(device.endpoints) do
78+
-- before the Matter 1.3 lua libs update (HUB FW 54), OptionsBitmap was defined as LevelControlOptions
79+
if switch_utils.ep_supports_cluster(ep_info, clusters.LevelControl.ID) then
80+
device:send(clusters.LevelControl.attributes.Options:write(device, ep_info.endpoint_id, clusters.LevelControl.types.LevelControlOptions.EXECUTE_IF_OFF))
81+
end
82+
-- before the Matter 1.4 lua libs update (HUB FW 56), there was no OptionsBitmap type defined
83+
if switch_utils.ep_supports_cluster(ep_info, clusters.ColorControl.ID) then
84+
local excute_if_off_bit = clusters.ColorControl.types.OptionsBitmap and clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF or 0x0001
85+
device:send(clusters.ColorControl.attributes.Options:write(device, ep_info.endpoint_id, excute_if_off_bit))
86+
end
87+
end
88+
end
89+
7590
function ButtonDeviceConfiguration.update_button_profile(device, default_endpoint_id, num_button_eps)
7691
local profile_name = string.gsub(num_button_eps .. "-button", "1%-", "") -- remove the "1-" in a device with 1 button ep
7792
if switch_utils.device_type_supports_button_switch_combination(device, default_endpoint_id) then

drivers/SmartThings/matter-switch/src/switch_utils/utils.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,22 @@ function utils.get_endpoint_info(device, endpoint_id)
247247
return {}
248248
end
249249

250+
function utils.ep_supports_cluster(ep_info, cluster_id, opts)
251+
opts = opts or {}
252+
local clus_has_features = function(cluster, checked_feature)
253+
return (cluster.feature_map & checked_feature) == checked_feature
254+
end
255+
for _, cluster in ipairs(ep_info.clusters) do
256+
if ((cluster.cluster_id == cluster_id)
257+
and (opts.feature_bitmap == nil or clus_has_features(cluster, opts.feature_bitmap))
258+
and ((opts.cluster_type == nil and cluster.cluster_type == "SERVER" or cluster.cluster_type == "BOTH")
259+
or (opts.cluster_type == cluster.cluster_type))
260+
or (cluster_id == nil)) then
261+
return true
262+
end
263+
end
264+
end
265+
250266
-- Fallback handler for responses that dont have their own handler
251267
function utils.matter_handler(driver, device, response_block)
252268
device.log.info(string.format("Fallback handler for %s", response_block))

drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ test.register_coroutine_test(
408408
"Test profile change on init for Electrical Sensor device type",
409409
function()
410410
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
411+
test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 2, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
411412
mock_device:expect_metadata_update({ profile = "plug-level-power-energy-powerConsumption" })
412413
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
413414
end,

drivers/SmartThings/matter-switch/src/test/test_matter_light_fan.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ local function test_init()
8787
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
8888

8989
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
90+
test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, mock_device_ep1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
91+
test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, mock_device_ep1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
9092
mock_device:expect_metadata_update({ profile = "light-color-level-fan" })
9193
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
9294
end

drivers/SmartThings/matter-switch/src/test/test_matter_multi_button_switch_mcd.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,9 @@ local function test_init()
226226
parent_assigned_child_key = string.format("%d", mock_device_ep5)
227227
})
228228
expect_configure_buttons()
229+
test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, mock_device_ep1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
230+
test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, mock_device_ep5, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
231+
test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, mock_device_ep5, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
229232
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
230233

231234
-- simulate the profile change update taking affect and the device info changing

drivers/SmartThings/matter-switch/src/test/test_matter_switch.lua

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,15 +200,24 @@ local function test_init_color_temp()
200200
subscribe_request:merge(cluster:subscribe(mock_device_color_temp))
201201
end
202202
end
203-
test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request})
203+
204204
test.socket.device_lifecycle:__queue_receive({ mock_device_color_temp.id, "added" })
205205
test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request})
206206

207207
test.socket.device_lifecycle:__queue_receive({ mock_device_color_temp.id, "init" })
208208
test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request})
209209

210210
test.socket.device_lifecycle:__queue_receive({ mock_device_color_temp.id, "doConfigure" })
211+
test.socket.matter:__expect_send({
212+
mock_device_color_temp.id,
213+
clusters.LevelControl.attributes.Options:write(mock_device_color_temp, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)
214+
})
215+
test.socket.matter:__expect_send({
216+
mock_device_color_temp.id,
217+
clusters.ColorControl.attributes.Options:write(mock_device_color_temp, 1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)
218+
})
211219
mock_device_color_temp:expect_metadata_update({ provisioning_state = "PROVISIONED" })
220+
test.socket.matter:__expect_send({mock_device_color_temp.id, subscribe_request})
212221
end
213222

214223
local function test_init_extended_color()
@@ -221,13 +230,21 @@ local function test_init_extended_color()
221230
end
222231
test.socket.matter:__expect_send({mock_device_extended_color.id, subscribe_request})
223232
test.socket.device_lifecycle:__queue_receive({ mock_device_extended_color.id, "added" })
224-
test.socket.matter:__expect_send({mock_device_extended_color.id, subscribe_request})
225233

226234
test.socket.device_lifecycle:__queue_receive({ mock_device_extended_color.id, "init" })
227235
test.socket.matter:__expect_send({mock_device_extended_color.id, subscribe_request})
228236

229237
test.socket.device_lifecycle:__queue_receive({ mock_device_extended_color.id, "doConfigure" })
238+
test.socket.matter:__expect_send({
239+
mock_device_extended_color.id,
240+
clusters.LevelControl.attributes.Options:write(mock_device_extended_color, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)
241+
})
242+
test.socket.matter:__expect_send({
243+
mock_device_extended_color.id,
244+
clusters.ColorControl.attributes.Options:write(mock_device_extended_color, 1, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)
245+
})
230246
mock_device_extended_color:expect_metadata_update({ provisioning_state = "PROVISIONED" })
247+
test.socket.matter:__expect_send({mock_device_extended_color.id, subscribe_request})
231248
end
232249

233250
test.register_message_test(

drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ local mock_device_mounted_on_off_control = test.mock_device.build_test_matter_de
143143
endpoint_id = 7,
144144
clusters = {
145145
{cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0},
146-
{cluster_id = clusters.LevelControl.ID, cluster_type = "CLIENT", feature_map = 2},
146+
{cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2},
147147

148148
},
149149
device_types = {
@@ -173,7 +173,7 @@ local mock_device_mounted_dimmable_load_control = test.mock_device.build_test_ma
173173
endpoint_id = 7,
174174
clusters = {
175175
{cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0},
176-
{cluster_id = clusters.LevelControl.ID, cluster_type = "CLIENT", feature_map = 2},
176+
{cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2},
177177

178178
},
179179
device_types = {
@@ -420,6 +420,10 @@ local function test_init_parent_child_switch_types()
420420
test.socket.matter:__expect_send({mock_device_parent_child_switch_types.id, subscribe_request})
421421

422422
test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_switch_types.id, "doConfigure" })
423+
test.socket.matter:__expect_send({
424+
mock_device_parent_child_switch_types.id,
425+
clusters.LevelControl.attributes.Options:write(mock_device_parent_child_switch_types, 7, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)
426+
})
423427
mock_device_parent_child_switch_types:expect_metadata_update({ profile = "switch-level" })
424428
mock_device_parent_child_switch_types:expect_metadata_update({ provisioning_state = "PROVISIONED" })
425429

@@ -463,6 +467,10 @@ end
463467
local function test_init_dimmer()
464468
test.mock_device.add_test_device(mock_device_dimmer)
465469
test.socket.device_lifecycle:__queue_receive({ mock_device_dimmer.id, "doConfigure" })
470+
test.socket.matter:__expect_send({
471+
mock_device_dimmer.id,
472+
clusters.LevelControl.attributes.Options:write(mock_device_dimmer, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)
473+
})
466474
mock_device_dimmer:expect_metadata_update({ profile = "switch-level" })
467475
mock_device_dimmer:expect_metadata_update({ provisioning_state = "PROVISIONED" })
468476
end
@@ -494,6 +502,10 @@ local function test_init_mounted_on_off_control()
494502
test.socket.matter:__expect_send({mock_device_mounted_on_off_control.id, subscribe_request})
495503

496504
test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_on_off_control.id, "doConfigure" })
505+
test.socket.matter:__expect_send({
506+
mock_device_mounted_on_off_control.id,
507+
clusters.LevelControl.attributes.Options:write(mock_device_mounted_on_off_control, 7, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)
508+
})
497509
mock_device_mounted_on_off_control:expect_metadata_update({ profile = "switch-binary" })
498510
mock_device_mounted_on_off_control:expect_metadata_update({ provisioning_state = "PROVISIONED" })
499511
end
@@ -502,6 +514,9 @@ local function test_init_mounted_dimmable_load_control()
502514
test.mock_device.add_test_device(mock_device_mounted_dimmable_load_control)
503515
local cluster_subscribe_list = {
504516
clusters.OnOff.attributes.OnOff,
517+
clusters.LevelControl.attributes.CurrentLevel,
518+
clusters.LevelControl.attributes.MinLevel,
519+
clusters.LevelControl.attributes.MaxLevel,
505520
}
506521
local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_mounted_dimmable_load_control)
507522
for i, cluster in ipairs(cluster_subscribe_list) do
@@ -516,6 +531,10 @@ local function test_init_mounted_dimmable_load_control()
516531
test.socket.matter:__expect_send({mock_device_mounted_dimmable_load_control.id, subscribe_request})
517532

518533
test.socket.device_lifecycle:__queue_receive({ mock_device_mounted_dimmable_load_control.id, "doConfigure" })
534+
test.socket.matter:__expect_send({
535+
mock_device_mounted_dimmable_load_control.id,
536+
clusters.LevelControl.attributes.Options:write(mock_device_mounted_dimmable_load_control, 7, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)
537+
})
519538
mock_device_mounted_dimmable_load_control:expect_metadata_update({ profile = "switch-level" })
520539
mock_device_mounted_dimmable_load_control:expect_metadata_update({ provisioning_state = "PROVISIONED" })
521540
end
@@ -557,6 +576,14 @@ local function test_init_parent_child_different_types()
557576
test.socket.matter:__expect_send({mock_device_parent_child_different_types.id, subscribe_request})
558577

559578
test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_different_types.id, "doConfigure" })
579+
test.socket.matter:__expect_send({
580+
mock_device_parent_child_different_types.id,
581+
clusters.LevelControl.attributes.Options:write(mock_device_parent_child_different_types, 10, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)
582+
})
583+
test.socket.matter:__expect_send({
584+
mock_device_parent_child_different_types.id,
585+
clusters.ColorControl.attributes.Options:write(mock_device_parent_child_different_types, 10, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)
586+
})
560587
mock_device_parent_child_different_types:expect_metadata_update({ profile = "switch-binary" })
561588
mock_device_parent_child_different_types:expect_metadata_update({ provisioning_state = "PROVISIONED" })
562589

@@ -575,6 +602,10 @@ local function test_init_parent_child_unsupported_device_type()
575602
test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_unsupported_device_type.id, "init" })
576603
test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_unsupported_device_type.id, "doConfigure" })
577604
mock_device_parent_child_unsupported_device_type:expect_metadata_update({ profile = "switch-binary" })
605+
test.socket.matter:__expect_send({
606+
mock_device_parent_child_unsupported_device_type.id,
607+
clusters.LevelControl.attributes.Options:write(mock_device_parent_child_unsupported_device_type, 10, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)
608+
})
578609
mock_device_parent_child_unsupported_device_type:expect_metadata_update({ provisioning_state = "PROVISIONED" })
579610

580611
mock_device_parent_child_unsupported_device_type:expect_device_create({
@@ -609,6 +640,10 @@ local function test_init_light_level_motion()
609640
test.socket.matter:__expect_send({mock_device_light_level_motion.id, subscribe_request})
610641

611642
test.socket.device_lifecycle:__queue_receive({ mock_device_light_level_motion.id, "doConfigure" })
643+
test.socket.matter:__expect_send({
644+
mock_device_light_level_motion.id,
645+
clusters.LevelControl.attributes.Options:write(mock_device_light_level_motion, 1, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)
646+
})
612647
mock_device_light_level_motion:expect_metadata_update({ profile = "light-level-motion" })
613648
mock_device_light_level_motion:expect_metadata_update({ provisioning_state = "PROVISIONED" })
614649
end

drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ local function test_init()
178178
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
179179

180180
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
181+
test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, child1_ep, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
182+
test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, child2_ep, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
183+
test.socket.matter:__expect_send({mock_device.id, clusters.ColorControl.attributes.Options:write(mock_device, child2_ep, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
181184
mock_device:expect_metadata_update({ profile = "light-binary" })
182185
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
183186

@@ -222,7 +225,9 @@ for i, endpoint in ipairs(mock_device_parent_child_endpoints_non_sequential.endp
222225
end
223226

224227
local function test_init_parent_child_endpoints_non_sequential()
225-
test.mock_device.add_test_device(mock_device_parent_child_endpoints_non_sequential)
228+
local unsup_mock_device = mock_device_parent_child_endpoints_non_sequential
229+
230+
test.mock_device.add_test_device(unsup_mock_device)
226231
local cluster_subscribe_list = {
227232
clusters.OnOff.attributes.OnOff,
228233
clusters.LevelControl.attributes.CurrentLevel,
@@ -236,49 +241,53 @@ local function test_init_parent_child_endpoints_non_sequential()
236241
clusters.ColorControl.attributes.CurrentX,
237242
clusters.ColorControl.attributes.CurrentY
238243
}
239-
local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_parent_child_endpoints_non_sequential)
244+
local subscribe_request = cluster_subscribe_list[1]:subscribe(unsup_mock_device)
240245
for i, cluster in ipairs(cluster_subscribe_list) do
241246
if i > 1 then
242-
subscribe_request:merge(cluster:subscribe(mock_device_parent_child_endpoints_non_sequential))
247+
subscribe_request:merge(cluster:subscribe(unsup_mock_device))
243248
end
244249
end
245250

246-
test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "added" })
247-
test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, subscribe_request})
251+
test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "added" })
252+
test.socket.matter:__expect_send({unsup_mock_device.id, subscribe_request})
253+
254+
test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "init" })
255+
test.socket.matter:__expect_send({unsup_mock_device.id, subscribe_request})
248256

249-
test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "init" })
250-
test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, subscribe_request})
257+
test.socket.device_lifecycle:__queue_receive({ unsup_mock_device.id, "doConfigure" })
258+
test.socket.matter:__expect_send({unsup_mock_device.id, clusters.LevelControl.attributes.Options:write(unsup_mock_device, child1_ep_non_sequential, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
259+
test.socket.matter:__expect_send({unsup_mock_device.id, clusters.LevelControl.attributes.Options:write(unsup_mock_device, child2_ep_non_sequential, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
260+
test.socket.matter:__expect_send({unsup_mock_device.id, clusters.ColorControl.attributes.Options:write(unsup_mock_device, child2_ep_non_sequential, clusters.ColorControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
251261

252-
test.socket.device_lifecycle:__queue_receive({ mock_device_parent_child_endpoints_non_sequential.id, "doConfigure" })
253-
mock_device_parent_child_endpoints_non_sequential:expect_metadata_update({ profile = "light-binary" })
254-
mock_device_parent_child_endpoints_non_sequential:expect_metadata_update({ provisioning_state = "PROVISIONED" })
262+
unsup_mock_device:expect_metadata_update({ profile = "light-binary" })
263+
unsup_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
255264

256265
for _, child in pairs(mock_children_non_sequential) do
257266
test.mock_device.add_test_device(child)
258267
end
259268

260-
mock_device_parent_child_endpoints_non_sequential:expect_device_create({
269+
unsup_mock_device:expect_device_create({
261270
type = "EDGE_CHILD",
262271
label = "Matter Switch 2",
263272
profile = "light-color-level",
264-
parent_device_id = mock_device_parent_child_endpoints_non_sequential.id,
273+
parent_device_id = unsup_mock_device.id,
265274
parent_assigned_child_key = string.format("%d", child2_ep_non_sequential)
266275
})
267276

268277
-- switch-binary will be selected as an overridden child device profile
269-
mock_device_parent_child_endpoints_non_sequential:expect_device_create({
278+
unsup_mock_device:expect_device_create({
270279
type = "EDGE_CHILD",
271280
label = "Matter Switch 3",
272281
profile = "switch-binary",
273-
parent_device_id = mock_device_parent_child_endpoints_non_sequential.id,
282+
parent_device_id = unsup_mock_device.id,
274283
parent_assigned_child_key = string.format("%d", child3_ep_non_sequential)
275284
})
276285

277-
mock_device_parent_child_endpoints_non_sequential:expect_device_create({
286+
unsup_mock_device:expect_device_create({
278287
type = "EDGE_CHILD",
279288
label = "Matter Switch 4",
280289
profile = "light-level",
281-
parent_device_id = mock_device_parent_child_endpoints_non_sequential.id,
290+
parent_device_id = unsup_mock_device.id,
282291
parent_assigned_child_key = string.format("%d", child1_ep_non_sequential)
283292
})
284293
end

0 commit comments

Comments
 (0)