Skip to content

Commit eda1721

Browse files
committed
re-profile device if a matter_version update occurs
1 parent da4a05f commit eda1721

10 files changed

+97
-50
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,16 @@ end
6666
function SwitchLifecycleHandlers.info_changed(driver, device, event, args)
6767
if device.profile.id ~= args.old_st_store.profile.id then
6868
device:subscribe()
69-
local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})
70-
if #button_eps > 0 and device.network_type == device_lib.NETWORK_TYPE_MATTER then
69+
if device.network_type == device_lib.NETWORK_TYPE_MATTER then
7170
button_cfg.configure_buttons(device)
7271
end
7372
end
73+
74+
if device.matter_version.software ~= args.old_st_store.matter_version.software then
75+
if device.network_type == device_lib.NETWORK_TYPE_MATTER and not switch_utils.detect_bridge(device) then
76+
device_cfg.match_profile(driver, device)
77+
end
78+
end
7479
end
7580

7681
function SwitchLifecycleHandlers.device_removed(driver, device)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ local button_attr = capabilities.button.button
2525
local aqara_mock_device = test.mock_device.build_test_matter_device({
2626
profile = t_utils.get_profile_definition("3-button-battery-temperature-humidity.yml"),
2727
manufacturer_info = {vendor_id = 0x115F, product_id = 0x2004, product_name = "Aqara Climate Sensor W100"},
28+
matter_version = {hardware = 1, software = 1},
2829
label = "Climate Sensor W100",
2930
device_id = "00000000-1111-2222-3333-000000000001",
3031
endpoints = {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ local aqara_child2_ep = 2
2727
local aqara_mock_device = test.mock_device.build_test_matter_device({
2828
profile = t_utils.get_profile_definition("4-button.yml"),
2929
manufacturer_info = {vendor_id = 0x115F, product_id = 0x1009, product_name = "Aqara Light Switch H2"},
30+
matter_version = {hardware = 1, software = 1},
3031
label = "Aqara Light Switch",
3132
device_id = "00000000-1111-2222-3333-000000000001",
3233
endpoints = {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ local uint32 = require "st.matter.data_types.Uint32"
1212
local mock_device = test.mock_device.build_test_matter_device({
1313
profile = t_utils.get_profile_definition("button-battery.yml"),
1414
manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000},
15+
matter_version = {hardware = 1, software = 1},
1516
endpoints = {
1617
{
1718
endpoint_id = 0,

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ local clusters = require "st.matter.generated.zap_clusters"
88
local button_attr = capabilities.button.button
99

1010
-- Mock a 5-button device using endpoints non-consecutive endpoints
11-
local mock_device = test.mock_device.build_test_matter_device(
12-
{
13-
profile = t_utils.get_profile_definition("5-button-battery.yml"), -- on a real device we would switch to this, rather than fingerprint to it
14-
manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000},
15-
endpoints = {
11+
local mock_device = test.mock_device.build_test_matter_device({
12+
profile = t_utils.get_profile_definition("5-button-battery.yml"), -- on a real device we would switch to this, rather than fingerprint to it
13+
manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000},
14+
matter_version = {hardware = 1, software = 1},
15+
endpoints = {
1616
{
1717
endpoint_id = 0,
1818
clusters = {},
@@ -87,8 +87,7 @@ local mock_device = test.mock_device.build_test_matter_device(
8787
}
8888
},
8989
},
90-
}
91-
)
90+
})
9291

9392
-- add device for each mock device
9493
local CLUSTER_SUBSCRIBE_LIST ={

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

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ local mock_device = test.mock_device.build_test_matter_device({
2525
vendor_id = 0x0000,
2626
product_id = 0x0000,
2727
},
28+
matter_version = {hardware = 1, software = 1},
2829
endpoints = {
2930
{
3031
endpoint_id = 0,
@@ -105,6 +106,7 @@ local mock_device_mcd_unsupported_switch_device_type = test.mock_device.build_te
105106
vendor_id = 0x0000,
106107
product_id = 0x0000,
107108
},
109+
matter_version = {hardware = 1, software = 1},
108110
endpoints = {
109111
{
110112
endpoint_id = 0,
@@ -192,49 +194,35 @@ local function expect_configure_buttons()
192194
test.socket.capability:__expect_send(mock_device:generate_test_message("button3", button_attr.pushed({state_change = false})))
193195
end
194196

195-
-- All messages queued and expectations set are done before the driver is actually run
196197
local function test_init()
197-
-- we dont want the integration test framework to generate init/doConfigure, we are doing that here
198-
-- so we can set the proper expectations on those events.
199198
test.disable_startup_messages()
200199
test.mock_device.add_test_device(mock_device) -- make sure the cache is populated
201200
test.mock_device.add_test_device(mock_child)
202201

203-
-- added sets a bunch of fields on the device, and calls init
204202
local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device)
205203
for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do
206204
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end
207205
end
208206
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
209207
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
210208

211-
-- init results in subscription interaction
212209
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
213210
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" })
214211

215-
--doConfigure sets the provisioning state to provisioned
216-
mock_device:expect_metadata_update({ profile = "light-level-3-button" })
217-
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
212+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
218213
mock_device:expect_device_create({
219214
type = "EDGE_CHILD",
220215
label = "Matter Switch 2",
221216
profile = "light-color-level",
222217
parent_device_id = mock_device.id,
223218
parent_assigned_child_key = string.format("%d", mock_device_ep5)
224219
})
220+
mock_device:expect_metadata_update({ profile = "light-level-3-button" })
225221
expect_configure_buttons()
226-
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
227-
228-
-- simulate the profile change update taking affect and the device info changing
229-
local device_info_copy = utils.deep_copy(mock_device.raw_st_data)
230-
device_info_copy.profile.id = "5-buttons-battery"
231-
local device_info_json = dkjson.encode(device_info_copy)
232-
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json })
233-
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
234-
expect_configure_buttons()
222+
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
235223

236-
test.socket.matter:__expect_send({mock_device.id, clusters.OnOff.attributes.OnOff:read(mock_device)})
237224
test.socket.device_lifecycle:__queue_receive({ mock_child.id, "added" })
225+
test.socket.matter:__expect_send({mock_device.id, clusters.OnOff.attributes.OnOff:read(mock_device)})
238226
test.socket.device_lifecycle:__queue_receive({ mock_child.id, "init" })
239227
mock_child:expect_metadata_update({ provisioning_state = "PROVISIONED" })
240228
test.socket.device_lifecycle:__queue_receive({ mock_child.id, "doConfigure" })
@@ -452,18 +440,48 @@ test.register_coroutine_test(
452440
test.register_coroutine_test(
453441
"Test driver switched event",
454442
function()
443+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" })
444+
local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device)
445+
for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do
446+
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end
447+
end
448+
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
455449
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "driverSwitched" })
450+
mock_child:expect_metadata_update({ profile = "light-color-level" })
456451
mock_device:expect_metadata_update({ profile = "light-level-3-button" })
457452
expect_configure_buttons()
458-
mock_device:expect_device_create({
459-
type = "EDGE_CHILD",
460-
label = "Matter Switch 2",
461-
profile = "light-color-level",
462-
parent_device_id = mock_device.id,
463-
parent_assigned_child_key = string.format("%d", mock_device_ep5)
464-
})
465453
end
466454
)
467455

456+
test.register_coroutine_test(
457+
"Test info changed event with parent device profile update",
458+
function()
459+
local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device)
460+
for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do
461+
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end
462+
end
463+
local updated_device_profile = t_utils.get_profile_definition("light-level-3-button.yml")
464+
updated_device_profile.id = "updated device profile id"
465+
test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ profile = updated_device_profile }))
466+
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
467+
expect_configure_buttons()
468+
end
469+
)
470+
471+
test.register_coroutine_test(
472+
"Test info changed event with matter_version update",
473+
function()
474+
local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device)
475+
for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do
476+
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end
477+
end
478+
test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ matter_version = { hardware = 1, software = 2 } })) -- bump to 2
479+
mock_child:expect_metadata_update({ profile = "light-color-level" })
480+
mock_device:expect_metadata_update({ profile = "light-level-3-button" })
481+
expect_configure_buttons()
482+
end
483+
)
484+
485+
468486
-- run the tests
469487
test.run_registered_tests()

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ local mock_device_onoff = test.mock_device.build_test_matter_device({
2424
vendor_id = 0x0000,
2525
product_id = 0x0000,
2626
},
27+
matter_version = {
28+
hardware = 1,
29+
software = 1,
30+
},
2731
endpoints = {
2832
{
2933
endpoint_id = 0,

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ local mock_device = test.mock_device.build_test_matter_device({
3434
vendor_id = 0x0000,
3535
product_id = 0x0000,
3636
},
37+
matter_version = {
38+
hardware = 1,
39+
software = 1,
40+
},
3741
endpoints = {
3842
{
3943
endpoint_id = 0,
@@ -689,4 +693,14 @@ test.register_coroutine_test(
689693
{ test_init = test_init_parent_child_endpoints_non_sequential }
690694
)
691695

696+
test.register_coroutine_test(
697+
"Test info changed event with matter_version update",
698+
function()
699+
test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ matter_version = { hardware = 1, software = 2 } })) -- bump to 2
700+
mock_children[child1_ep]:expect_metadata_update({ profile = "light-level" })
701+
mock_children[child2_ep]:expect_metadata_update({ profile = "light-color-level" })
702+
mock_device:expect_metadata_update({ profile = "light-binary" })
703+
end
704+
)
705+
692706
test.run_registered_tests()

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

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ function SwitchDeviceConfiguration.assign_profile_for_onoff_ep(device, onoff_ep_
6262
return profile
6363
end
6464

65-
function SwitchDeviceConfiguration.create_child_devices(driver, device, server_onoff_ep_ids, main_endpoint_id)
66-
if #server_onoff_ep_ids == 1 and server_onoff_ep_ids[1] == main_endpoint_id then -- no children will be created
65+
function SwitchDeviceConfiguration.create_or_update_child_devices(driver, device, server_onoff_ep_ids, main_endpoint_id)
66+
if #server_onoff_ep_ids == 1 and server_onoff_ep_ids[1] == main_endpoint_id then -- no children will exist
6767
return
6868
end
6969

@@ -72,18 +72,23 @@ function SwitchDeviceConfiguration.create_child_devices(driver, device, server_o
7272
for idx, ep_id in ipairs(server_onoff_ep_ids) do
7373
device_num = device_num + 1
7474
if ep_id ~= main_endpoint_id then -- don't create a child device that maps to the main endpoint
75-
local name = string.format("%s %d", device.label, device_num)
75+
local child_device_name = string.format("%s %d", device.label, device_num)
7676
local child_profile = SwitchDeviceConfiguration.assign_profile_for_onoff_ep(device, ep_id, true)
77-
driver:try_create_device(
78-
{
77+
local existing_child_device = device:get_field(fields.IS_PARENT_CHILD_DEVICE) and switch_utils.find_child(device, ep_id)
78+
if not existing_child_device then
79+
driver:try_create_device({
7980
type = "EDGE_CHILD",
80-
label = name,
81+
label = child_device_name,
8182
profile = child_profile,
8283
parent_device_id = device.id,
8384
parent_assigned_child_key = string.format("%d", ep_id),
84-
vendor_provided_label = name
85-
}
86-
)
85+
vendor_provided_label = child_device_name
86+
})
87+
else
88+
existing_child_device:try_update_metadata({
89+
profile = child_profile
90+
})
91+
end
8792
if idx == 1 and string.find(child_profile, "energy") then
8893
-- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it.
8994
device:set_field(fields.ENERGY_MANAGEMENT_ENDPOINT, ep_id, {persist = true})
@@ -133,7 +138,7 @@ function ButtonDeviceConfiguration.configure_buttons(device)
133138
local msl_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_LONG_PRESS})
134139
local msm_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH_MULTI_PRESS})
135140

136-
for _, ep in ipairs(ms_eps) do
141+
for _, ep in ipairs(ms_eps or {}) do
137142
if device.profile.components[switch_utils.endpoint_to_component(device, ep)] then
138143
device.log.info_with({hub_logs=true}, string.format("Configuring Supported Values for generic switch endpoint %d", ep))
139144
local supportedButtonValues_event
@@ -180,7 +185,7 @@ function DeviceConfiguration.match_profile(driver, device)
180185

181186
local server_onoff_ep_ids = device:get_endpoints(clusters.OnOff.ID, { cluster_type = "SERVER" })
182187
if #server_onoff_ep_ids > 0 then
183-
SwitchDeviceConfiguration.create_child_devices(driver, device, server_onoff_ep_ids, main_endpoint_id)
188+
SwitchDeviceConfiguration.create_or_update_child_devices(driver, device, server_onoff_ep_ids, main_endpoint_id)
184189
updated_profile = SwitchDeviceConfiguration.assign_profile_for_onoff_ep(device, main_endpoint_id)
185190
local find_substr = function(s, p) return string.find(s or "", p, 1, true) end
186191

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,11 @@ function utils.detect_bridge(device)
221221
end
222222

223223
function utils.detect_matter_thing(device)
224-
for _, capability in ipairs(fields.supported_capabilities) do
225-
if device:supports_capability(capability) then
226-
return false
227-
end
224+
-- every profile except for matter-thing supports at least 2 capabilities (refresh, firmwareUpdate)
225+
for i, _ in pairs(device.profile.components.main.capabilities) do
226+
if i > 1 then return false end
228227
end
229-
return device:supports_capability(capabilities.refresh)
228+
return true
230229
end
231230

232231
function utils.report_power_consumption_to_st_energy(device, latest_total_imported_energy_wh)

0 commit comments

Comments
 (0)