11-- Copyright 2022 SmartThings, Inc.
22-- Licensed under the Apache License, Version 2.0
33
4-
54-- Note: Currently only support for window shades with the PositionallyAware Feature
65-- Note: No support for setting device into calibration mode, it must be done manually
76local capabilities = require " st.capabilities"
@@ -10,6 +9,12 @@ local clusters = require "st.matter.clusters"
109local MatterDriver = require " st.matter.driver"
1110local utils = require " st.utils"
1211
12+ local IS_PARENT_CHILD_DEVICE = " __is_parent_child_device"
13+ --- If the ASSIGNED_CHILD_KEY field is populated for an endpoint, it should be
14+ --- used as the key in the get_child_by_parent_assigned_key() function. This allows
15+ --- multiple endpoints to associate with the same child device, though right now child
16+ local ASSIGNED_CHILD_KEY = " __assigned_child_key"
17+
1318local battery_support = {
1419 NO_BATTERY = " NO_BATTERY" ,
1520 BATTERY_LEVEL = " BATTERY_LEVEL" ,
@@ -154,6 +159,90 @@ local function component_to_endpoint(device, component_name)
154159 return find_default_endpoint (device , clusters .WindowCovering .ID )
155160end
156161
162+ local function create_feature_table (lift_eps , tilt_eps )
163+ local result = {}
164+ local i , j = 1 , 1
165+ while i <= # lift_eps or j <= # tilt_eps do
166+ local lift_ep , tilt_ep = lift_eps [i ], tilt_eps [j ]
167+ if tilt_ep == nil or (lift_ep ~= nil and lift_ep < tilt_ep ) then
168+ result [# result + 1 ] = { endpoint = lift_ep , has_lift = true , has_tilt = false }
169+ i = i + 1
170+ elseif lift_ep == nil or tilt_ep < lift_ep then
171+ result [# result + 1 ] = { endpoint = tilt_ep , has_lift = false , has_tilt = true }
172+ j = j + 1
173+ else -- lift_ep == tilt_ep
174+ result [# result + 1 ] = { endpoint = lift_ep , has_lift = true , has_tilt = true }
175+ i , j = i + 1 , j + 1
176+ end
177+ end
178+ return result
179+ end
180+
181+ local function update_profile_table (device )
182+ device .log .info_with ({hub_logs = true }, string.format (" !!! update_profile_table !!!" ))
183+ local lift_eps = device :get_endpoints (clusters .WindowCovering .ID , {feature_bitmap = clusters .WindowCovering .types .Feature .LIFT })
184+ local tilt_eps = device :get_endpoints (clusters .WindowCovering .ID , {feature_bitmap = clusters .WindowCovering .types .Feature .TILT })
185+ table.sort (lift_eps )
186+ table.sort (tilt_eps )
187+ local feature_table = create_feature_table (lift_eps , tilt_eps )
188+ local profile_table = {}
189+
190+ for _ , entry in pairs (feature_table ) do
191+ device .log .info_with ({hub_logs = true }, string.format (" !!! endpoint: %s, has_lift: %s, has_tilt !!!" , entry .endpoint , entry .has_lift , entry .has_tilt ))
192+ profile_table [entry .endpoint ] = " window-covering"
193+ if entry .has_tile then
194+ profile_table [entry .endpoint ] = profile_table [entry .endpoint ] .. " -tilt"
195+ if entry .has_lift then
196+ profile_table [entry .endpoint ] = profile_table [entry .endpoint ] .. " -only"
197+ end
198+ end
199+ end
200+ return profile_table
201+ end
202+
203+ local function find_child (parent_device , ep_id )
204+ parent_device .log .info_with ({hub_logs = true }, string.format (" !!! find_child !!!" ))
205+ local assigned_key = parent_device :get_field (string.format (" %s_%d" , ASSIGNED_CHILD_KEY , ep_id )) or ep_id
206+ return parent_device :get_child_by_parent_assigned_key (string.format (" %d" , assigned_key ))
207+ end
208+
209+ local function create_or_update_multi_devices (driver , device , wc_cluster_eps )
210+ device .log .info_with ({hub_logs = true }, string.format (" !!! create_or_update_child_devices !!!" ))
211+ local profile_table = update_profile_table (device )
212+ local default_endpoint_id = find_default_endpoint (device )
213+ table.sort (wc_cluster_eps )
214+ for device_num , ep_id in ipairs (wc_cluster_eps ) do
215+ if ep_id ~= default_endpoint_id then -- don't create a child device that maps to the main endpoint
216+ local label_and_name = string.format (" %s %d" , device .label , device_num )
217+ local child_profile = profile_table [ep_id ] or " window-covering"
218+ local existing_child_device = device :get_field (IS_PARENT_CHILD_DEVICE ) and find_child (device , ep_id )
219+
220+ device .log .info_with ({hub_logs = true }, string.format (" !!! do_create_or_update: %s !!!" , child_profile ))
221+
222+ if not existing_child_device then
223+ driver :try_create_device ({
224+ type = " EDGE_CHILD" ,
225+ label = label_and_name ,
226+ profile = child_profile ,
227+ parent_device_id = device .id ,
228+ parent_assigned_child_key = string.format (" %d" , ep_id ),
229+ vendor_provided_label = label_and_name
230+ })
231+ else
232+ existing_child_device :try_update_metadata ({
233+ profile = child_profile ,
234+ optional_component_capabilities = optional_component_capabilities
235+ })
236+ end
237+ end
238+ end
239+ -- Persist so that the find_child function is always set on each driver init.
240+ device :set_field (IS_PARENT_CHILD_DEVICE , true , {persist = true })
241+ device :set_find_child (find_child )
242+ -- Update parent device's profile
243+ device :try_update_metadata ({profile = profile_table [default_endpoint_id ] or " window-covering" })
244+ end
245+
157246local function match_profile (device , battery_supported )
158247 local lift_eps = device :get_endpoints (clusters .WindowCovering .ID , {feature_bitmap = clusters .WindowCovering .types .Feature .LIFT })
159248 local tilt_eps = device :get_endpoints (clusters .WindowCovering .ID , {feature_bitmap = clusters .WindowCovering .types .Feature .TILT })
@@ -191,13 +280,18 @@ local function device_init(driver, device)
191280end
192281
193282local function do_configure (driver , device )
194- local battery_feature_eps = device :get_endpoints (clusters .PowerSource .ID , {feature_bitmap = clusters .PowerSource .types .PowerSourceFeature .BATTERY })
195- if # battery_feature_eps > 0 then
196- local attribute_list_read = im .InteractionRequest (im .InteractionRequest .RequestType .READ , {})
197- attribute_list_read :merge (clusters .PowerSource .attributes .AttributeList :read ())
198- device :send (attribute_list_read )
283+ local wc_cluster_eps = device :get_endpoints (clusters .WindowCovering .ID )
284+ if # wc_cluster_eps > 1 then
285+ create_or_update_multi_devices (driver , device , wc_cluster_eps )
199286 else
200- match_profile (device , battery_support .NO_BATTERY )
287+ local battery_feature_eps = device :get_endpoints (clusters .PowerSource .ID , {feature_bitmap = clusters .PowerSource .types .PowerSourceFeature .BATTERY })
288+ if # battery_feature_eps > 0 then
289+ local attribute_list_read = im .InteractionRequest (im .InteractionRequest .RequestType .READ , {})
290+ attribute_list_read :merge (clusters .PowerSource .attributes .AttributeList :read ())
291+ device :send (attribute_list_read )
292+ else
293+ match_profile (device , battery_support .NO_BATTERY )
294+ end
201295 end
202296end
203297
0 commit comments