Skip to content

Commit 7b5daf1

Browse files
Matter Switch move initialization to doConfigure
This change moves the initialization logic for buttons and switches to doConfigure. This keeps all of the profile selection logic all within doConfigure and allows the removal of logic gates from device_init that were there to ensure init code only ran one time. Also added is a new function that runs at init that can rename or delete persisted fields on the device.
1 parent 88e19e0 commit 7b5daf1

9 files changed

+189
-125
lines changed

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

+77-69
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,12 @@ local SWITCH_LEVEL_LIGHTING_MIN = 1
4444
local CURRENT_HUESAT_ATTR_MIN = 0
4545
local CURRENT_HUESAT_ATTR_MAX = 254
4646

47-
local SWITCH_INITIALIZED = "__switch_intialized"
48-
-- COMPONENT_TO_ENDPOINT_MAP is here only to preserve the endpoint mapping for
47+
-- COMPONENT_TO_ENDPOINT_MAP is here to preserve the endpoint mapping for
4948
-- devices that were joined to this driver as MCD devices before the transition
50-
-- to join all matter-switch devices as parent-child. This value will only exist
51-
-- in the device table for devices that joined prior to this transition, and it
52-
-- will not be set for new devices.
49+
-- to join switch devices as parent-child. This value will exist in the device
50+
-- table for devices that joined prior to this transition, and is also used for
51+
-- button devices that require component mapping.
5352
local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map"
54-
-- COMPONENT_TO_ENDPOINT_MAP_BUTTON is for devices with button endpoints, to
55-
-- preserve the MCD functionality for button devices from the matter-button
56-
-- driver after it was merged into the matter-switch driver. Note that devices
57-
-- containing both button endpoints and switch endpoints will use this field
58-
-- rather than COMPONENT_TO_ENDPOINT_MAP.
59-
local COMPONENT_TO_ENDPOINT_MAP_BUTTON = "__component_to_endpoint_map_button"
6053
local ENERGY_MANAGEMENT_ENDPOINT = "__energy_management_endpoint"
6154
local IS_PARENT_CHILD_DEVICE = "__is_parent_child_device"
6255
local COLOR_TEMP_BOUND_RECEIVED_KELVIN = "__colorTemp_bound_received_kelvin"
@@ -67,6 +60,12 @@ local LEVEL_BOUND_RECEIVED = "__level_bound_received"
6760
local LEVEL_MIN = "__level_min"
6861
local LEVEL_MAX = "__level_max"
6962
local COLOR_MODE = "__color_mode"
63+
64+
local updated_fields = {
65+
{ field_name = "__component_to_endpoint_map_button", updated_field_name = COMPONENT_TO_ENDPOINT_MAP },
66+
{ field_name = "__switch_intialized", updated_field_name = nil }
67+
}
68+
7069
local HUE_SAT_COLOR_MODE = clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION
7170
local X_Y_COLOR_MODE = clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY
7271

@@ -292,8 +291,6 @@ local HELD_THRESHOLD = 1
292291
-- this is the number of buttons for which we have a static profile already made
293292
local STATIC_BUTTON_PROFILE_SUPPORTED = {1, 2, 3, 4, 5, 6, 7, 8}
294293

295-
local BUTTON_DEVICE_PROFILED = "__button_device_profiled"
296-
297294
-- Some switches will send a MultiPressComplete event as part of a long press sequence. Normally the driver will create a
298295
-- button capability event on receipt of MultiPressComplete, but in this case that would result in an extra event because
299296
-- the "held" capability event is generated when the LongPress event is received. The IGNORE_NEXT_MPC flag is used
@@ -406,6 +403,7 @@ local function device_type_supports_button_switch_combination(device, endpoint_i
406403
end
407404

408405
local function get_first_non_zero_endpoint(endpoints)
406+
table.sort(endpoints)
409407
for _,ep in ipairs(endpoints) do
410408
if ep ~= 0 then -- 0 is the matter RootNode endpoint
411409
return ep
@@ -425,8 +423,6 @@ local function find_default_endpoint(device)
425423

426424
local switch_eps = device:get_endpoints(clusters.OnOff.ID)
427425
local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})
428-
table.sort(switch_eps)
429-
table.sort(button_eps)
430426

431427
-- Return the first switch endpoint as the default endpoint if no button endpoints are present
432428
if #button_eps == 0 and #switch_eps > 0 then
@@ -456,15 +452,15 @@ local function find_default_endpoint(device)
456452
end
457453

458454
local function component_to_endpoint(device, component)
459-
local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP_BUTTON) or device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {}
455+
local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {}
460456
if map[component] then
461457
return map[component]
462458
end
463459
return find_default_endpoint(device)
464460
end
465461

466462
local function endpoint_to_component(device, ep)
467-
local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP_BUTTON) or device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {}
463+
local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {}
468464
for component, endpoint in pairs(map) do
469465
if endpoint == ep then
470466
return component
@@ -473,6 +469,17 @@ local function endpoint_to_component(device, ep)
473469
return "main"
474470
end
475471

472+
local function check_field_name_updates(device)
473+
for _, field in ipairs(updated_fields) do
474+
if device:get_field(field.field_name) then
475+
if field.updated_field_name ~= nil then
476+
device:set_field(field.updated_field_name, device:get_field(field.field_name), {persist = true})
477+
end
478+
device:set_field(field.field_name, nil)
479+
end
480+
end
481+
end
482+
476483
local function assign_child_profile(device, child_ep)
477484
local profile
478485

@@ -512,41 +519,6 @@ local function assign_child_profile(device, child_ep)
512519
return profile or "switch-binary"
513520
end
514521

515-
local function do_configure(driver, device)
516-
if device:get_field(BUTTON_DEVICE_PROFILED) then
517-
return
518-
end
519-
local fan_eps = device:get_endpoints(clusters.FanControl.ID)
520-
local level_eps = device:get_endpoints(clusters.LevelControl.ID)
521-
local energy_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID)
522-
local power_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalPowerMeasurement.ID)
523-
local valve_eps = embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID)
524-
local profile_name = nil
525-
local level_support = ""
526-
if #level_eps > 0 then
527-
level_support = "-level"
528-
end
529-
if #energy_eps > 0 and #power_eps > 0 then
530-
profile_name = "plug" .. level_support .. "-power-energy-powerConsumption"
531-
elseif #energy_eps > 0 then
532-
profile_name = "plug" .. level_support .. "-energy-powerConsumption"
533-
elseif #power_eps > 0 then
534-
profile_name = "plug" .. level_support .. "-power"
535-
elseif #valve_eps > 0 then
536-
profile_name = "water-valve"
537-
if #embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID,
538-
{feature_bitmap = clusters.ValveConfigurationAndControl.types.Feature.LEVEL}) > 0 then
539-
profile_name = profile_name .. "-level"
540-
end
541-
elseif #fan_eps > 0 then
542-
profile_name = "light-color-level-fan"
543-
end
544-
545-
if profile_name then
546-
device:try_update_metadata({ profile = profile_name })
547-
end
548-
end
549-
550522
local function configure_buttons(device)
551523
if device.network_type == device_lib.NETWORK_TYPE_CHILD then
552524
return
@@ -603,7 +575,7 @@ local function build_button_component_map(device, main_endpoint, button_eps)
603575
component_map[button_component] = ep
604576
end
605577
end
606-
device:set_field(COMPONENT_TO_ENDPOINT_MAP_BUTTON, component_map, {persist = true})
578+
device:set_field(COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true})
607579
end
608580

609581
local function build_button_profile(device, main_endpoint, num_button_eps)
@@ -613,13 +585,10 @@ local function build_button_profile(device, main_endpoint, num_button_eps)
613585
end
614586
local battery_supported = #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) > 0
615587
if battery_supported then -- battery profiles are configured later, in power_source_attribute_list_handler
616-
local attribute_list_read = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {})
617-
attribute_list_read:merge(clusters.PowerSource.attributes.AttributeList:read())
618-
device:send(attribute_list_read)
588+
device:send(clusters.PowerSource.attributes.AttributeList:read(device))
619589
else
620590
device:try_update_metadata({profile = profile_name})
621591
end
622-
device:set_field(BUTTON_DEVICE_PROFILED, true)
623592
end
624593

625594
local function build_child_switch_profiles(driver, device, main_endpoint)
@@ -683,13 +652,15 @@ local function handle_light_switch_with_onOff_server_clusters(device, main_endpo
683652
end
684653

685654
local function initialize_buttons_and_switches(driver, device, main_endpoint)
655+
local profile_found = false
686656
local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})
687657
if tbl_contains(STATIC_BUTTON_PROFILE_SUPPORTED, #button_eps) then
688658
build_button_profile(device, main_endpoint, #button_eps)
689659
-- All button endpoints found will be added as additional components in the profile containing the main_endpoint.
690-
-- The resulting endpoint to component map is saved in the COMPONENT_TO_ENDPOINT_MAP_BUTTON field
660+
-- The resulting endpoint to component map is saved in the COMPONENT_TO_ENDPOINT_MAP field
691661
build_button_component_map(device, main_endpoint, button_eps)
692662
configure_buttons(device)
663+
profile_found = true
693664
end
694665

695666
-- Without support for bindings, only clusters that are implemented as server are counted. This count is handled
@@ -701,9 +672,9 @@ local function initialize_buttons_and_switches(driver, device, main_endpoint)
701672
-- Note: since their device type isn't supported, these devices join as a matter-thing.
702673
if num_switch_server_eps > 0 and detect_matter_thing(device) then
703674
handle_light_switch_with_onOff_server_clusters(device, main_endpoint)
675+
profile_found = true
704676
end
705-
706-
device:set_field(SWITCH_INITIALIZED, true, {persist = true})
677+
return profile_found
707678
end
708679

709680
local function detect_bridge(device)
@@ -721,20 +692,13 @@ local function device_init(driver, device)
721692
if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then
722693
return
723694
end
724-
695+
check_field_name_updates(device)
725696
device:set_component_to_endpoint_fn(component_to_endpoint)
726697
device:set_endpoint_to_component_fn(endpoint_to_component)
727-
728-
local main_endpoint = find_default_endpoint(device)
729-
if not device:get_field(COMPONENT_TO_ENDPOINT_MAP) and -- this field is only set for old MCD devices. See comments in the field def.
730-
not device:get_field(SWITCH_INITIALIZED) and
731-
not detect_bridge(device) then
732-
-- initialize the main device card with buttons if applicable, and create child devices as needed for multi-switch devices.
733-
initialize_buttons_and_switches(driver, device, main_endpoint)
734-
end
735698
if device:get_field(IS_PARENT_CHILD_DEVICE) then
736699
device:set_find_child(find_child)
737700
end
701+
local main_endpoint = find_default_endpoint(device)
738702
-- ensure subscription to all endpoint attributes- including those mapped to child devices
739703
for _, ep in ipairs(device.endpoints) do
740704
if ep.endpoint_id ~= main_endpoint then
@@ -756,6 +720,50 @@ local function device_init(driver, device)
756720
device:subscribe()
757721
end
758722

723+
local function do_configure(driver, device)
724+
if detect_bridge(device) then
725+
return
726+
end
727+
local main_endpoint = find_default_endpoint(device)
728+
-- initialize the main device card with buttons if applicable, and create child devices as needed for multi-switch devices.
729+
local profile_found = initialize_buttons_and_switches(driver, device, main_endpoint)
730+
if device:get_field(IS_PARENT_CHILD_DEVICE) then
731+
device:set_find_child(find_child)
732+
end
733+
if profile_found then
734+
return
735+
end
736+
737+
local fan_eps = device:get_endpoints(clusters.FanControl.ID)
738+
local level_eps = device:get_endpoints(clusters.LevelControl.ID)
739+
local energy_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID)
740+
local power_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalPowerMeasurement.ID)
741+
local valve_eps = embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID)
742+
local profile_name = nil
743+
local level_support = ""
744+
if #level_eps > 0 then
745+
level_support = "-level"
746+
end
747+
if #energy_eps > 0 and #power_eps > 0 then
748+
profile_name = "plug" .. level_support .. "-power-energy-powerConsumption"
749+
elseif #energy_eps > 0 then
750+
profile_name = "plug" .. level_support .. "-energy-powerConsumption"
751+
elseif #power_eps > 0 then
752+
profile_name = "plug" .. level_support .. "-power"
753+
elseif #valve_eps > 0 then
754+
profile_name = "water-valve"
755+
if #embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID,
756+
{feature_bitmap = clusters.ValveConfigurationAndControl.types.Feature.LEVEL}) > 0 then
757+
profile_name = profile_name .. "-level"
758+
end
759+
elseif #fan_eps > 0 then
760+
profile_name = "light-color-level-fan"
761+
end
762+
if profile_name then
763+
device:try_update_metadata({ profile = profile_name })
764+
end
765+
end
766+
759767
local function device_removed(driver, device)
760768
log.info("device removed")
761769
delete_import_poll_schedule(device)

0 commit comments

Comments
 (0)