Skip to content

Commit ee09b1d

Browse files
Refactor multicomponent configuration
This commit simplifies and generalizes the configuration for fan and button multi-component devices
1 parent 2363ec5 commit ee09b1d

File tree

1 file changed

+72
-32
lines changed
  • drivers/SmartThings/matter-switch/src

1 file changed

+72
-32
lines changed

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

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ local MOUNTED_ON_OFF_CONTROL_ID = 0x010F
8888
local MOUNTED_DIMMABLE_LOAD_CONTROL_ID = 0x0110
8989
local GENERIC_SWITCH_ID = 0x000F
9090
local ELECTRICAL_SENSOR_ID = 0x0510
91+
local FAN_DEVICE_TYPE_ID = 0x002B
9192
local device_type_profile_map = {
9293
[ON_OFF_LIGHT_DEVICE_TYPE_ID] = "light-binary",
9394
[DIMMABLE_LIGHT_DEVICE_TYPE_ID] = "light-level",
@@ -406,25 +407,64 @@ local function ignore_initial_color_read(device, attr_bit)
406407
return false
407408
end
408409

410+
local supported_mcd_configs_per_main_endpoint_device_type = {
411+
[DIMMABLE_LIGHT_DEVICE_TYPE_ID] = {
412+
[GENERIC_SWITCH_ID] = { 1, 2, 3, 4, 5, 6, 7, 8 }
413+
},
414+
[FAN_DEVICE_TYPE_ID] = {
415+
[EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID] = { 1 }
416+
}
417+
}
418+
409419
--- device_type_supports_multicomponent_configuration helper function used to check
410-
--- whether the device type for an endpoint is currently supported by a profile for
411-
--- multicomponent devices.
412-
local function device_type_supports_multicomponent_configuration(device, endpoint_id, device_type_id)
420+
--- whether the device is currently supported by a profile for a multicomponent configuration.
421+
local function device_type_supports_multicomponent_configuration(device, main_endpoint, secondary_endpoints)
422+
local main_device_type = nil
423+
local secondary_device_types_count = {}
424+
413425
for _, ep in ipairs(device.endpoints) do
414-
if ep.endpoint_id == endpoint_id then
426+
if ep.endpoint_id == main_endpoint then
415427
for _, dt in ipairs(ep.device_types) do
416428
for _, fingerprint in ipairs(child_device_profile_overrides_per_vendor_id[0x115F]) do
417429
if device.manufacturer_info.product_id == fingerprint.product_id then
418430
return false -- For Aqara Dimmer Switch with Button.
419431
end
420432
end
421-
if dt.device_type_id == device_type_id then
422-
return true
433+
if supported_mcd_configs_per_main_endpoint_device_type[dt.device_type_id] then
434+
main_device_type = dt.device_type_id
435+
break
423436
end
424437
end
425438
end
426439
end
427-
return false
440+
441+
if not main_device_type then
442+
return false
443+
end
444+
445+
for _, ep in ipairs(device.endpoints) do
446+
if secondary_endpoints[ep.endpoint_id] and ep.endpoint_id ~= main_endpoint then
447+
for _, dt in ipairs(ep.device_types) do
448+
if supported_mcd_configs_per_main_endpoint_device_type[main_device_type][dt.device_type_id] then
449+
secondary_device_types_count[dt.device_type_id] = (secondary_device_types_count[dt.device_type_id] or 0) + 1
450+
else
451+
return false
452+
end
453+
end
454+
end
455+
end
456+
457+
for secondary_device_type, count in pairs(secondary_device_types_count) do
458+
local allowed_counts = supported_mcd_configs_per_main_endpoint_device_type[main_device_type][secondary_device_type]
459+
460+
for _, allowed_count in ipairs(allowed_counts) do
461+
if count == allowed_count then
462+
return true
463+
end
464+
end
465+
466+
return false
467+
end
428468
end
429469

430470
local function get_first_non_zero_endpoint(endpoints)
@@ -472,7 +512,7 @@ local function find_default_endpoint(device)
472512
-- default endpoint.
473513
if #switch_eps > 0 and #button_eps > 0 then
474514
local main_endpoint = get_first_non_zero_endpoint(switch_eps)
475-
if device_type_supports_multicomponent_configuration(device, main_endpoint, DIMMABLE_LIGHT_DEVICE_TYPE_ID) then
515+
if device_type_supports_multicomponent_configuration(device, main_endpoint, button_eps) then
476516
return main_endpoint
477517
else
478518
device.log.warn("The main switch endpoint does not contain a supported device type for a component configuration with buttons")
@@ -592,25 +632,25 @@ local function find_child(parent, ep_id)
592632
return parent:get_child_by_parent_assigned_key(string.format("%d", ep_id))
593633
end
594634

595-
local function build_component_map(device, main_endpoint, button_eps, light_eps)
596-
local eps, component_name, component_field
597-
if button_eps ~= nil and STATIC_BUTTON_PROFILE_SUPPORTED[#button_eps] then
598-
eps = button_eps
599-
component_name = "button"
635+
local function build_component_map(device, main_endpoint, secondary_endpoints, secondary_device_type)
636+
local secondary_component_name, component_field
637+
if secondary_device_type == GENERIC_SWITCH_ID then
638+
secondary_component_name = "button"
600639
component_field = COMPONENT_TO_ENDPOINT_MAP_BUTTON
601-
elseif light_eps ~= nil then
602-
eps = light_eps
603-
component_name = "light"
640+
elseif secondary_device_type == EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID then
641+
secondary_component_name = "light"
604642
component_field = COMPONENT_TO_ENDPOINT_MAP_FAN
643+
else
644+
device.log.warn_with({hub_logs = true}, "Device uses unsupported configuration for a multicomponent profile")
645+
return
605646
end
606-
if eps == nil then return end
607647

608648
local component_map = {}
609649
component_map["main"] = main_endpoint
610-
for component_num, ep in ipairs(eps) do
650+
for component_num, ep in ipairs(secondary_endpoints) do
611651
if ep ~= main_endpoint then
612-
local component = component_name
613-
if #eps > 1 then
652+
local component = secondary_component_name
653+
if #secondary_endpoints > 1 then
614654
component = component .. component_num
615655
end
616656
component_map[component] = ep
@@ -619,13 +659,13 @@ local function build_component_map(device, main_endpoint, button_eps, light_eps)
619659
device:set_field(component_field, component_map, {persist = true})
620660
end
621661

622-
local function build_button_profile(device, endpoint, num_button_eps)
662+
local function build_button_profile(device, main_endpoint, secondary_endpoints)
623663
local profile_name
624664
local battery_supported
625-
if device_type_supports_multicomponent_configuration(device, endpoint, DIMMABLE_LIGHT_DEVICE_TYPE_ID) then
626-
profile_name = "light-level-" .. num_button_eps .. "-button"
665+
if device_type_supports_multicomponent_configuration(device, main_endpoint, secondary_endpoints) then
666+
profile_name = "light-level-" .. #secondary_endpoints .. "-button"
627667
else
628-
profile_name = num_button_eps .. "-button"
668+
profile_name = #secondary_endpoints .. "-button"
629669
battery_supported = #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) > 0
630670
if device.manufacturer_info.vendor_id == HUE_MANUFACTURER_ID then battery_supported = false end -- no battery support in Hue case
631671
if battery_supported then
@@ -643,8 +683,8 @@ local function build_button_profile(device, endpoint, num_button_eps)
643683
device:set_field(BUTTON_DEVICE_PROFILED, true)
644684
end
645685

646-
local function build_fan_profile(device, endpoint, num_fan_eps)
647-
if device_type_supports_multicomponent_configuration(device, endpoint, EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID) and num_fan_eps == 1 then
686+
local function build_fan_profile(device, main_endpoint, secondary_endpoints)
687+
if device_type_supports_multicomponent_configuration(device, main_endpoint, secondary_endpoints) then
648688
device:try_update_metadata({profile = "fan-light-color-level"})
649689
device:set_field(FAN_DEVICE_PROFILED, true)
650690
else
@@ -719,15 +759,15 @@ local function initialize_buttons_and_switches(driver, device, main_endpoint)
719759
table.sort(fan_eps)
720760

721761
-- Any button endpoints found will be added as additional components in the profile containing the
722-
-- main endpoint. A fan endpoint will be considered the main endpoint and other additional switch
723-
-- endpoints will be added as additional components. The resulting endpoint to component map is
762+
-- main endpoint. A fan endpoint will be considered the main endpoint and the additional switch
763+
-- endpoint will be added as an additional component. The resulting endpoint to component map is
724764
-- saved in the corresponding component to endpoint map field.
725765
if #button_eps > 0 then
726-
build_component_map(device, main_endpoint, button_eps, nil)
727-
build_button_profile(device, main_endpoint, #button_eps)
766+
build_component_map(device, main_endpoint, button_eps, GENERIC_SWITCH_ID)
767+
build_button_profile(device, main_endpoint, button_eps)
728768
elseif #fan_eps > 0 then
729-
build_component_map(device, main_endpoint, nil, switch_eps)
730-
build_fan_profile(device, switch_eps[1], #fan_eps)
769+
build_component_map(device, main_endpoint, switch_eps, EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID)
770+
build_fan_profile(device, main_endpoint, switch_eps)
731771
device:set_field(SWITCH_INITIALIZED, true, {persist = true})
732772
return
733773
end

0 commit comments

Comments
 (0)