@@ -31,92 +31,57 @@ local DeviceConfiguration = {}
3131local SwitchDeviceConfiguration = {}
3232local ButtonDeviceConfiguration = {}
3333
34- function SwitchDeviceConfiguration .assign_child_profile (device , child_ep )
35- local profile
34+ function SwitchDeviceConfiguration .assign_profile_for_onoff_ep (device , server_onoff_ep_id , is_child_device )
35+ local ep_info = switch_utils . get_endpoint_info ( device , server_onoff_ep_id )
3636
37- for _ , ep in ipairs (device .endpoints ) do
38- if ep .endpoint_id == child_ep then
39- -- Some devices report multiple device types which are a subset of
40- -- a superset device type (For example, Dimmable Light is a superset of
41- -- On/Off light). This mostly applies to the four light types, so we will want
42- -- to match the profile for the superset device type. This can be done by
43- -- matching to the device type with the highest ID
44- local id = 0
45- for _ , dt in ipairs (ep .device_types ) do
46- id = math.max (id , dt .device_type_id )
47- end
48- profile = fields .device_type_profile_map [id ]
49- break
50- end
51- end
52-
53- -- vendor override checks
54- if child_ep == switch_utils .get_product_override_field (device , " ep_id" ) or profile == switch_utils .get_product_override_field (device , " initial_profile" ) then
55- profile = switch_utils .get_product_override_field (device , " target_profile" ) or profile
56- end
37+ -- per spec, the Switch device types support OnOff as CLIENT, though some vendors break spec and support it as SERVER.
38+ local primary_dt_id = switch_utils .find_max_subset_device_type (ep_info , fields .DEVICE_TYPE_ID .LIGHT )
39+ or switch_utils .find_max_subset_device_type (ep_info , fields .DEVICE_TYPE_ID .SWITCH )
40+ or ep_info .device_types [1 ] and ep_info .device_types [1 ].device_type_id
5741
58- -- default to "switch-binary" if no profile is found
59- return profile or " switch-binary"
60- end
61-
62- function SwitchDeviceConfiguration .create_child_switch_devices (driver , device , main_endpoint )
63- local num_switch_server_eps = 0
64- local parent_child_device = false
65- local switch_eps = device :get_endpoints (clusters .OnOff .ID )
66- table.sort (switch_eps )
67- for idx , ep in ipairs (switch_eps ) do
68- if device :supports_server_cluster (clusters .OnOff .ID , ep ) then
69- num_switch_server_eps = num_switch_server_eps + 1
70- if ep ~= main_endpoint then -- don't create a child device that maps to the main endpoint
71- local name = string.format (" %s %d" , device .label , num_switch_server_eps )
72- local child_profile = SwitchDeviceConfiguration .assign_child_profile (device , ep )
73- driver :try_create_device (
74- {
75- type = " EDGE_CHILD" ,
76- label = name ,
77- profile = child_profile ,
78- parent_device_id = device .id ,
79- parent_assigned_child_key = string.format (" %d" , ep ),
80- vendor_provided_label = name
81- }
82- )
83- parent_child_device = true
84- if idx == 1 and string.find (child_profile , " energy" ) then
85- -- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it.
86- device :set_field (fields .ENERGY_MANAGEMENT_ENDPOINT , ep , {persist = true })
87- end
88- end
89- end
90- end
42+ local generic_profile = fields .device_type_profile_map [primary_dt_id ]
9143
92- -- If the device is a parent child device, set the find_child function on init. This is persisted because initialize_buttons_and_switches
93- -- is only run once, but find_child function should be set on each driver init.
94- if parent_child_device then
95- device :set_field (fields .IS_PARENT_CHILD_DEVICE , true , {persist = true })
44+ if is_child_device and (
45+ server_onoff_ep_id == switch_utils .get_product_override_field (device , " ep_id" ) or
46+ generic_profile == switch_utils .get_product_override_field (device , " initial_profile" )
47+ ) then
48+ generic_profile = switch_utils .get_product_override_field (device , " target_profile" ) or generic_profile
9649 end
9750
98- -- this is needed in initialize_buttons_and_switches
99- return num_switch_server_eps
51+ -- if no supported device type is found, return switch-binary as a generic "OnOff EP" profile
52+ return generic_profile or " switch-binary "
10053end
10154
102- function SwitchDeviceConfiguration .update_devices_with_onOff_server_clusters (device , main_endpoint )
103- local cluster_id = 0
104- for _ , ep in ipairs (device .endpoints ) do
105- -- main_endpoint only supports server cluster by definition of get_endpoints()
106- if main_endpoint == ep .endpoint_id then
107- for _ , dt in ipairs (ep .device_types ) do
108- -- no device type that is not in the switch subset should be considered.
109- if (fields .ON_OFF_SWITCH_ID <= dt .device_type_id and dt .device_type_id <= fields .ON_OFF_COLOR_DIMMER_SWITCH_ID ) then
110- cluster_id = math.max (cluster_id , dt .device_type_id )
111- end
55+ function SwitchDeviceConfiguration .create_child_devices (driver , device , onoff_ep_ids , main_endpoint_id )
56+ if # onoff_ep_ids == 1 and onoff_ep_ids [1 ] == main_endpoint_id then -- no children will be created
57+ return
58+ end
59+
60+ local device_num = 0
61+ table.sort (onoff_ep_ids )
62+ for idx , ep_id in ipairs (onoff_ep_ids ) do
63+ device_num = device_num + 1
64+ if ep_id ~= main_endpoint_id then -- don't create a child device that maps to the main endpoint
65+ local name = string.format (" %s %d" , device .label , device_num )
66+ local child_profile = SwitchDeviceConfiguration .assign_profile_for_onoff_ep (device , ep_id , true )
67+ driver :try_create_device ({
68+ type = " EDGE_CHILD" ,
69+ label = name ,
70+ profile = child_profile ,
71+ parent_device_id = device .id ,
72+ parent_assigned_child_key = string.format (" %d" , ep_id ),
73+ vendor_provided_label = name
74+ })
75+ if idx == 1 and string.find (child_profile , " energy" ) then
76+ -- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it.
77+ device :set_field (fields .ENERGY_MANAGEMENT_ENDPOINT , ep_id , {persist = true })
11278 end
113- break
11479 end
11580 end
11681
117- if fields . device_type_profile_map [ cluster_id ] then
118- device :try_update_metadata ({ profile = fields . device_type_profile_map [ cluster_id ] })
119- end
82+ -- Persist so that the find_child function is always set on each driver init.
83+ device :set_field ( fields . IS_PARENT_CHILD_DEVICE , true , { persist = true })
84+ device : set_find_child ( switch_utils . find_child )
12085end
12186
12287function ButtonDeviceConfiguration .update_button_profile (device , main_endpoint , num_button_eps )
@@ -192,75 +157,61 @@ end
192157
193158-- [[ PROFILE MATCHING AND CONFIGURATIONS ]] --
194159
195- function DeviceConfiguration .initialize_buttons_and_switches (driver , device , main_endpoint )
196- local profile_found = false
197- local button_eps = device :get_endpoints (clusters .Switch .ID , {feature_bitmap = clusters .Switch .types .SwitchFeature .MOMENTARY_SWITCH })
198- if switch_utils .tbl_contains (fields .STATIC_BUTTON_PROFILE_SUPPORTED , # button_eps ) then
199- ButtonDeviceConfiguration .update_button_profile (device , main_endpoint , # button_eps )
200- -- All button endpoints found will be added as additional components in the profile containing the main_endpoint.
201- -- The resulting endpoint to component map is saved in the COMPONENT_TO_ENDPOINT_MAP field
202- ButtonDeviceConfiguration .update_button_component_map (device , main_endpoint , button_eps )
203- ButtonDeviceConfiguration .configure_buttons (device )
204- profile_found = true
205- end
206-
207- -- Without support for bindings, only clusters that are implemented as server are counted. This count is handled
208- -- while building switch child profiles
209- local num_switch_server_eps = SwitchDeviceConfiguration .create_child_switch_devices (driver , device , main_endpoint )
160+ function DeviceConfiguration .match_profile (driver , device )
161+ local main_endpoint_id = switch_utils .find_default_endpoint (device )
162+ local updated_profile = nil
210163
211- -- We do not support the Light Switch device types because they require OnOff to be implemented as 'client', which requires us to support bindings.
212- -- However, this workaround profiles devices that claim to be Light Switches, but that break spec and implement OnOff as 'server'.
213- -- Note: since their device type isn't supported, these devices join as a matter-thing.
214- if num_switch_server_eps > 0 and switch_utils . detect_matter_thing ( device ) then
215- SwitchDeviceConfiguration . update_devices_with_onOff_server_clusters ( device , main_endpoint )
216- profile_found = true
164+ if # embedded_cluster_utils . get_endpoints ( device , clusters . ValveConfigurationAndControl . ID ) > 0 then
165+ updated_profile = " water-valve "
166+ if # embedded_cluster_utils . get_endpoints ( device , clusters . ValveConfigurationAndControl . ID ,
167+ { feature_bitmap = clusters . ValveConfigurationAndControl . types . Feature . LEVEL }) > 0 then
168+ updated_profile = updated_profile .. " -level "
169+ end
217170 end
218- return profile_found
219- end
220171
221- function DeviceConfiguration .match_profile (driver , device )
222- local main_endpoint = switch_utils .find_default_endpoint (device )
223- -- initialize the main device card with buttons if applicable, and create child devices as needed for multi-switch devices.
224- local profile_found = DeviceConfiguration .initialize_buttons_and_switches (driver , device , main_endpoint )
225- if device :get_field (fields .IS_PARENT_CHILD_DEVICE ) then
226- device :set_find_child (switch_utils .find_child )
227- end
228- if profile_found then
229- return
172+ local server_onoff_ep_ids = device :get_endpoints (clusters .OnOff .ID ) -- get_endpoints defaults to return EPs supporting SERVER or BOTH
173+ if # server_onoff_ep_ids > 0 then
174+ SwitchDeviceConfiguration .create_child_devices (driver , device , server_onoff_ep_ids , main_endpoint_id )
230175 end
231176
232- local fan_eps = device :get_endpoints (clusters .FanControl .ID )
233- local level_eps = device :get_endpoints (clusters .LevelControl .ID )
234- local energy_eps = embedded_cluster_utils .get_endpoints (device , clusters .ElectricalEnergyMeasurement .ID )
235- local power_eps = embedded_cluster_utils .get_endpoints (device , clusters .ElectricalPowerMeasurement .ID )
236- local valve_eps = embedded_cluster_utils .get_endpoints (device , clusters .ValveConfigurationAndControl .ID )
237- local profile_name = nil
238- local level_support = " "
239- if # level_eps > 0 then
240- level_support = " -level"
241- end
242- if # energy_eps > 0 and # power_eps > 0 then
243- profile_name = " plug" .. level_support .. " -power-energy-powerConsumption"
244- elseif # energy_eps > 0 then
245- profile_name = " plug" .. level_support .. " -energy-powerConsumption"
246- elseif # power_eps > 0 then
247- profile_name = " plug" .. level_support .. " -power"
248- elseif # valve_eps > 0 then
249- profile_name = " water-valve"
250- if # embedded_cluster_utils .get_endpoints (device , clusters .ValveConfigurationAndControl .ID ,
251- {feature_bitmap = clusters .ValveConfigurationAndControl .types .Feature .LEVEL }) > 0 then
252- profile_name = profile_name .. " -level"
177+ if switch_utils .tbl_contains (server_onoff_ep_ids , main_endpoint_id ) then
178+ updated_profile = SwitchDeviceConfiguration .assign_profile_for_onoff_ep (device , main_endpoint_id )
179+ local generic_profile = function (s ) return string.find (updated_profile or " " , s , 1 , true ) end
180+ if generic_profile (" plug-binary" ) or generic_profile (" plug-level" ) then
181+ if switch_utils .check_switch_category_vendor_overrides (device ) then
182+ updated_profile = string.gsub (updated_profile , " plug" , " switch" )
183+ else
184+ local electrical_tags = " "
185+ if # embedded_cluster_utils .get_endpoints (device , clusters .ElectricalPowerMeasurement .ID ) > 0 then electrical_tags = electrical_tags .. " -power" end
186+ if # embedded_cluster_utils .get_endpoints (device , clusters .ElectricalEnergyMeasurement .ID ) > 0 then electrical_tags = electrical_tags .. " -energy-powerConsumption" end
187+ if electrical_tags ~= " " then updated_profile = string.gsub (updated_profile , " -binary" , " " ) .. electrical_tags end
188+ end
189+ elseif generic_profile (" light-color-level" ) and # device :get_endpoints (clusters .FanControl .ID ) > 0 then
190+ updated_profile = " light-color-level-fan"
191+ elseif generic_profile (" light-level" ) and # device :get_endpoints (clusters .OccupancySensing .ID ) > 0 then
192+ updated_profile = " light-level-motion"
193+ elseif generic_profile (" light-level-colorTemperature" ) or generic_profile (" light-color-level" ) then
194+ -- ignore attempts to dynamically profile light-level-colorTemperature and light-color-level devices for now, since
195+ -- these may lose fingerprinted Kelvin ranges when dynamically profiled.
196+ return
253197 end
254- elseif # fan_eps > 0 then
255- profile_name = " light-color-level-fan"
256198 end
257- if profile_name then
258- device :try_update_metadata ({ profile = profile_name })
199+
200+ -- initialize the main device card with buttons if applicable
201+ local button_eps = device :get_endpoints (clusters .Switch .ID , {feature_bitmap = clusters .Switch .types .SwitchFeature .MOMENTARY_SWITCH })
202+ if switch_utils .tbl_contains (fields .STATIC_BUTTON_PROFILE_SUPPORTED , # button_eps ) then
203+ ButtonDeviceConfiguration .update_button_profile (device , main_endpoint_id , # button_eps )
204+ -- All button endpoints found will be added as additional components in the profile containing the main_endpoint.
205+ ButtonDeviceConfiguration .update_button_component_map (device , main_endpoint_id , button_eps )
206+ ButtonDeviceConfiguration .configure_buttons (device )
207+ return
259208 end
209+
210+ device :try_update_metadata ({ profile = updated_profile })
260211end
261212
262213return {
263214 DeviceCfg = DeviceConfiguration ,
264215 SwitchCfg = SwitchDeviceConfiguration ,
265216 ButtonCfg = ButtonDeviceConfiguration
266- }
217+ }
0 commit comments