Skip to content

Commit 8c62fe3

Browse files
committed
allow child devices to be re-profiled, clean up device profile names
1 parent c6e5c66 commit 8c62fe3

File tree

4 files changed

+72
-85
lines changed

4 files changed

+72
-85
lines changed

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -460,16 +460,16 @@ test.register_coroutine_test(
460460
test.register_coroutine_test(
461461
"Test driver switched event",
462462
function()
463+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" })
464+
local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device)
465+
for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do
466+
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end
467+
end
468+
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
463469
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "driverSwitched" })
470+
mock_child:expect_metadata_update({ profile = "light-color-level" })
464471
mock_device:expect_metadata_update({ profile = "light-level-3-button" })
465472
expect_configure_buttons()
466-
mock_device:expect_device_create({
467-
type = "EDGE_CHILD",
468-
label = "Matter Switch 2",
469-
profile = "light-color-level",
470-
parent_device_id = mock_device.id,
471-
parent_assigned_child_key = string.format("%d", mock_device_ep5)
472-
})
473473
end
474474
)
475475

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

Lines changed: 33 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,23 @@ function SwitchDeviceConfiguration.assign_child_profile(device, child_ep)
7070
return profile or "switch-binary"
7171
end
7272

73-
function SwitchDeviceConfiguration.create_child_switch_devices(driver, device, main_endpoint)
74-
local num_switch_server_eps = 0
73+
function SwitchDeviceConfiguration.create_child_switch_devices(driver, device, switch_eps, main_endpoint)
74+
local switch_server_ep = 0
7575
local parent_child_device = false
76-
local switch_eps = device:get_endpoints(clusters.OnOff.ID)
76+
7777
table.sort(switch_eps)
7878
for idx, ep in ipairs(switch_eps) do
79-
if device:supports_server_cluster(clusters.OnOff.ID, ep) then
80-
num_switch_server_eps = num_switch_server_eps + 1
81-
if ep ~= main_endpoint then -- don't create a child device that maps to the main endpoint
82-
local name = string.format("%s %d", device.label, num_switch_server_eps)
83-
local child_profile = SwitchDeviceConfiguration.assign_child_profile(device, ep)
79+
switch_server_ep = switch_server_ep + 1
80+
if ep ~= main_endpoint then -- don't create a child device that maps to the main endpoint
81+
local name = string.format("%s %d", device.label, switch_server_ep)
82+
local child_profile = SwitchDeviceConfiguration.assign_child_profile(device, ep)
83+
local child_device = nil
84+
if device:get_field(fields.IS_PARENT_CHILD_DEVICE) then
85+
child_device = switch_utils.find_child(device, ep)
86+
end
87+
if child_device then
88+
child_device:try_update_metadata({profile = child_profile })
89+
else
8490
driver:try_create_device(
8591
{
8692
type = "EDGE_CHILD",
@@ -91,11 +97,11 @@ function SwitchDeviceConfiguration.create_child_switch_devices(driver, device, m
9197
vendor_provided_label = name
9298
}
9399
)
94-
parent_child_device = true
95-
if idx == 1 and string.find(child_profile, "energy") then
96-
-- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it.
97-
device:set_field(fields.ENERGY_MANAGEMENT_ENDPOINT, ep, {persist = true})
98-
end
100+
end
101+
parent_child_device = true
102+
if idx == 1 and string.find(child_profile, "energy") then
103+
-- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it.
104+
device:set_field(fields.ENERGY_MANAGEMENT_ENDPOINT, ep, {persist = true})
99105
end
100106
end
101107
end
@@ -105,29 +111,22 @@ function SwitchDeviceConfiguration.create_child_switch_devices(driver, device, m
105111
device:set_field(fields.IS_PARENT_CHILD_DEVICE, true, {persist = true})
106112
device:set_find_child(switch_utils.find_child)
107113
end
108-
109-
-- this is needed in initialize_buttons_and_switches
110-
return num_switch_server_eps
111114
end
112115

113-
function SwitchDeviceConfiguration.update_devices_with_onOff_server_clusters(device, main_endpoint)
116+
function SwitchDeviceConfiguration.match_light_switch_device_profile(device, main_endpoint)
114117
local cluster_id = 0
115118
for _, ep in ipairs(device.endpoints) do
116119
-- main_endpoint only supports server cluster by definition of get_endpoints()
117120
if main_endpoint == ep.endpoint_id then
118121
for _, dt in ipairs(ep.device_types) do
119122
-- no device type that is not in the switch subset should be considered.
120-
if (fields.ON_OFF_SWITCH_ID <= dt.device_type_id and dt.device_type_id <= fields.ON_OFF_COLOR_DIMMER_SWITCH_ID) then
123+
if (fields.ON_OFF_LIGHT_SWITCH_ID <= dt.device_type_id and dt.device_type_id <= fields.COLOR_DIMMER_SWITCH_ID) then
121124
cluster_id = math.max(cluster_id, dt.device_type_id)
122125
end
123126
end
124-
break
127+
return fields.device_type_profile_map[cluster_id]
125128
end
126129
end
127-
128-
if fields.device_type_profile_map[cluster_id] then
129-
device:try_update_metadata({profile = fields.device_type_profile_map[cluster_id]})
130-
end
131130
end
132131

133132
function ButtonDeviceConfiguration.update_button_profile(device, main_endpoint, num_button_eps)
@@ -201,32 +200,25 @@ end
201200

202201
function DeviceConfiguration.match_profile(driver, device)
203202
local main_endpoint = switch_utils.find_default_endpoint(device)
203+
local profile_name = nil
204+
205+
local server_onoff_eps = device:get_endpoints(clusters.OnOff.ID, { cluster_type = "SERVER" })
206+
if #server_onoff_eps > 0 then
207+
SwitchDeviceConfiguration.create_child_switch_devices(driver, device, server_onoff_eps, main_endpoint)
208+
-- workaround: finds a profile for devices of the Light Switch device type set that break spec and implement OnOff as 'server' instead of 'client'.
209+
-- note: since the Light Switch device set isn't supported, these devices join as a matter-thing.
210+
if switch_utils.detect_matter_thing(device) then
211+
profile_name = SwitchDeviceConfiguration.match_light_switch_device_profile(device, main_endpoint)
212+
end
213+
end
204214

205215
-- initialize the main device card with buttons if applicable
206-
local profile_found = false
207216
local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})
208217
if switch_utils.tbl_contains(fields.STATIC_BUTTON_PROFILE_SUPPORTED, #button_eps) then
209218
ButtonDeviceConfiguration.update_button_profile(device, main_endpoint, #button_eps)
210219
-- All button endpoints found will be added as additional components in the profile containing the main_endpoint.
211-
-- The resulting endpoint to component map is saved in the COMPONENT_TO_ENDPOINT_MAP field
212220
ButtonDeviceConfiguration.update_button_component_map(device, main_endpoint, button_eps)
213221
ButtonDeviceConfiguration.configure_buttons(device)
214-
profile_found = true
215-
end
216-
217-
-- Without support for bindings, only clusters that are implemented as server are counted. This count is handled
218-
-- while building switch child profiles
219-
local num_switch_server_eps = SwitchDeviceConfiguration.create_child_switch_devices(driver, device, main_endpoint)
220-
221-
-- We do not support the Light Switch device types because they require OnOff to be implemented as 'client', which requires us to support bindings.
222-
-- However, this workaround profiles devices that claim to be Light Switches, but that break spec and implement OnOff as 'server'.
223-
-- Note: since their device type isn't supported, these devices join as a matter-thing.
224-
if num_switch_server_eps > 0 and switch_utils.detect_matter_thing(device) then
225-
SwitchDeviceConfiguration.update_devices_with_onOff_server_clusters(device, main_endpoint)
226-
profile_found = true
227-
end
228-
229-
if profile_found then
230222
return
231223
end
232224

@@ -235,7 +227,6 @@ function DeviceConfiguration.match_profile(driver, device)
235227
local energy_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID)
236228
local power_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalPowerMeasurement.ID)
237229
local valve_eps = embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID)
238-
local profile_name = nil
239230
local level_support = ""
240231
if #level_eps > 0 then
241232
level_support = "-level"

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

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -47,31 +47,31 @@ SwitchFields.CURRENT_HUESAT_ATTR_MAX = 254
4747

4848

4949
-- DEVICE TYPES
50-
SwitchFields.AGGREGATOR_DEVICE_TYPE_ID = 0x000E
51-
SwitchFields.ON_OFF_LIGHT_DEVICE_TYPE_ID = 0x0100
52-
SwitchFields.DIMMABLE_LIGHT_DEVICE_TYPE_ID = 0x0101
53-
SwitchFields.COLOR_TEMP_LIGHT_DEVICE_TYPE_ID = 0x010C
54-
SwitchFields.EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID = 0x010D
55-
SwitchFields.ON_OFF_PLUG_DEVICE_TYPE_ID = 0x010A
56-
SwitchFields.DIMMABLE_PLUG_DEVICE_TYPE_ID = 0x010B
57-
SwitchFields.ON_OFF_SWITCH_ID = 0x0103
58-
SwitchFields.ON_OFF_DIMMER_SWITCH_ID = 0x0104
59-
SwitchFields.ON_OFF_COLOR_DIMMER_SWITCH_ID = 0x0105
50+
SwitchFields.AGGREGATOR_ID = 0x000E
51+
SwitchFields.ON_OFF_LIGHT_ID = 0x0100
52+
SwitchFields.DIMMABLE_LIGHT_ID = 0x0101
53+
SwitchFields.COLOR_TEMPERATURE_LIGHT_ID = 0x010C
54+
SwitchFields.EXTENDED_COLOR_LIGHT_ID = 0x010D
55+
SwitchFields.ON_OFF_PLUG_IN_UNIT_ID = 0x010A
56+
SwitchFields.DIMMABLE_PLUG_IN_UNIT_ID = 0x010B
6057
SwitchFields.MOUNTED_ON_OFF_CONTROL_ID = 0x010F
6158
SwitchFields.MOUNTED_DIMMABLE_LOAD_CONTROL_ID = 0x0110
59+
SwitchFields.ON_OFF_LIGHT_SWITCH_ID = 0x0103
60+
SwitchFields.DIMMER_SWITCH_ID = 0x0104
61+
SwitchFields.COLOR_DIMMER_SWITCH_ID = 0x0105
6262
SwitchFields.GENERIC_SWITCH_ID = 0x000F
6363
SwitchFields.ELECTRICAL_SENSOR_ID = 0x0510
6464

6565
SwitchFields.device_type_profile_map = {
66-
[SwitchFields.ON_OFF_LIGHT_DEVICE_TYPE_ID] = "light-binary",
67-
[SwitchFields.DIMMABLE_LIGHT_DEVICE_TYPE_ID] = "light-level",
68-
[SwitchFields.COLOR_TEMP_LIGHT_DEVICE_TYPE_ID] = "light-level-colorTemperature",
69-
[SwitchFields.EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID] = "light-color-level",
70-
[SwitchFields.ON_OFF_PLUG_DEVICE_TYPE_ID] = "plug-binary",
71-
[SwitchFields.DIMMABLE_PLUG_DEVICE_TYPE_ID] = "plug-level",
72-
[SwitchFields.ON_OFF_SWITCH_ID] = "switch-binary",
73-
[SwitchFields.ON_OFF_DIMMER_SWITCH_ID] = "switch-level",
74-
[SwitchFields.ON_OFF_COLOR_DIMMER_SWITCH_ID] = "switch-color-level",
66+
[SwitchFields.ON_OFF_LIGHT_ID] = "light-binary",
67+
[SwitchFields.DIMMABLE_LIGHT_ID] = "light-level",
68+
[SwitchFields.COLOR_TEMPERATURE_LIGHT_ID] = "light-level-colorTemperature",
69+
[SwitchFields.EXTENDED_COLOR_LIGHT_ID] = "light-color-level",
70+
[SwitchFields.ON_OFF_PLUG_IN_UNIT_ID] = "plug-binary",
71+
[SwitchFields.DIMMABLE_PLUG_IN_UNIT_ID] = "plug-level",
72+
[SwitchFields.ON_OFF_LIGHT_SWITCH_ID] = "switch-binary",
73+
[SwitchFields.DIMMER_SWITCH_ID] = "switch-level",
74+
[SwitchFields.COLOR_DIMMER_SWITCH_ID] = "switch-color-level",
7575
[SwitchFields.MOUNTED_ON_OFF_CONTROL_ID] = "switch-binary",
7676
[SwitchFields.MOUNTED_DIMMABLE_LOAD_CONTROL_ID] = "switch-level",
7777
}
@@ -182,16 +182,16 @@ SwitchFields.supported_capabilities = {
182182
}
183183

184184
SwitchFields.device_type_attribute_map = {
185-
[SwitchFields.ON_OFF_LIGHT_DEVICE_TYPE_ID] = {
185+
[SwitchFields.ON_OFF_LIGHT_ID] = {
186186
clusters.OnOff.attributes.OnOff
187187
},
188-
[SwitchFields.DIMMABLE_LIGHT_DEVICE_TYPE_ID] = {
188+
[SwitchFields.DIMMABLE_LIGHT_ID] = {
189189
clusters.OnOff.attributes.OnOff,
190190
clusters.LevelControl.attributes.CurrentLevel,
191191
clusters.LevelControl.attributes.MaxLevel,
192192
clusters.LevelControl.attributes.MinLevel
193193
},
194-
[SwitchFields.COLOR_TEMP_LIGHT_DEVICE_TYPE_ID] = {
194+
[SwitchFields.COLOR_TEMPERATURE_LIGHT_ID] = {
195195
clusters.OnOff.attributes.OnOff,
196196
clusters.LevelControl.attributes.CurrentLevel,
197197
clusters.LevelControl.attributes.MaxLevel,
@@ -200,7 +200,7 @@ SwitchFields.device_type_attribute_map = {
200200
clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds,
201201
clusters.ColorControl.attributes.ColorTempPhysicalMinMireds
202202
},
203-
[SwitchFields.EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID] = {
203+
[SwitchFields.EXTENDED_COLOR_LIGHT_ID] = {
204204
clusters.OnOff.attributes.OnOff,
205205
clusters.LevelControl.attributes.CurrentLevel,
206206
clusters.LevelControl.attributes.MaxLevel,
@@ -213,25 +213,25 @@ SwitchFields.device_type_attribute_map = {
213213
clusters.ColorControl.attributes.CurrentX,
214214
clusters.ColorControl.attributes.CurrentY
215215
},
216-
[SwitchFields.ON_OFF_PLUG_DEVICE_TYPE_ID] = {
216+
[SwitchFields.ON_OFF_PLUG_IN_UNIT_ID] = {
217217
clusters.OnOff.attributes.OnOff
218218
},
219-
[SwitchFields.DIMMABLE_PLUG_DEVICE_TYPE_ID] = {
219+
[SwitchFields.DIMMABLE_PLUG_IN_UNIT_ID] = {
220220
clusters.OnOff.attributes.OnOff,
221221
clusters.LevelControl.attributes.CurrentLevel,
222222
clusters.LevelControl.attributes.MaxLevel,
223223
clusters.LevelControl.attributes.MinLevel
224224
},
225-
[SwitchFields.ON_OFF_SWITCH_ID] = {
225+
[SwitchFields.ON_OFF_LIGHT_SWITCH_ID] = {
226226
clusters.OnOff.attributes.OnOff
227227
},
228-
[SwitchFields.ON_OFF_DIMMER_SWITCH_ID] = {
228+
[SwitchFields.DIMMER_SWITCH_ID] = {
229229
clusters.OnOff.attributes.OnOff,
230230
clusters.LevelControl.attributes.CurrentLevel,
231231
clusters.LevelControl.attributes.MaxLevel,
232232
clusters.LevelControl.attributes.MinLevel
233233
},
234-
[SwitchFields.ON_OFF_COLOR_DIMMER_SWITCH_ID] = {
234+
[SwitchFields.COLOR_DIMMER_SWITCH_ID] = {
235235
clusters.OnOff.attributes.OnOff,
236236
clusters.LevelControl.attributes.CurrentLevel,
237237
clusters.LevelControl.attributes.MaxLevel,

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

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ function utils.device_type_supports_button_switch_combination(device, endpoint_i
8181
for _, ep in ipairs(device.endpoints) do
8282
if ep.endpoint_id == endpoint_id then
8383
for _, dt in ipairs(ep.device_types) do
84-
if dt.device_type_id == fields.DIMMABLE_LIGHT_DEVICE_TYPE_ID then
84+
if dt.device_type_id == fields.DIMMABLE_LIGHT_ID then
8585
for _, fingerprint in ipairs(fields.child_device_profile_overrides_per_vendor_id[0x115F]) do
8686
if device.manufacturer_info.product_id == fingerprint.product_id then
8787
return false -- For Aqara Dimmer Switch with Button.
@@ -185,7 +185,7 @@ end
185185
function utils.detect_bridge(device)
186186
for _, ep in ipairs(device.endpoints) do
187187
for _, dt in ipairs(ep.device_types) do
188-
if dt.device_type_id == fields.AGGREGATOR_DEVICE_TYPE_ID then
188+
if dt.device_type_id == fields.AGGREGATOR_ID then
189189
return true
190190
end
191191
end
@@ -194,12 +194,8 @@ function utils.detect_bridge(device)
194194
end
195195

196196
function utils.detect_matter_thing(device)
197-
for _, capability in ipairs(fields.supported_capabilities) do
198-
if device:supports_capability(capability) then
199-
return false
200-
end
201-
end
202-
return device:supports_capability(capabilities.refresh)
197+
-- every profile except for matter-thing supports at least 2 capabilities (refresh, firmwareUpdate)
198+
return #device.profile.components.main.capabilities == 1
203199
end
204200

205201
function utils.report_power_consumption_to_st_energy(device, latest_total_imported_energy_wh)

0 commit comments

Comments
 (0)