Skip to content

Commit e7b42a3

Browse files
committed
Support multi-endpoint window covering
Signed-off-by: Hunsup Jung <hunsup.jung@samsung.com>
1 parent 53c74eb commit e7b42a3

1 file changed

Lines changed: 101 additions & 7 deletions

File tree

  • drivers/SmartThings/matter-window-covering/src

‎drivers/SmartThings/matter-window-covering/src/init.lua‎

Lines changed: 101 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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
76
local capabilities = require "st.capabilities"
@@ -10,6 +9,12 @@ local clusters = require "st.matter.clusters"
109
local MatterDriver = require "st.matter.driver"
1110
local 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+
1318
local 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)
155160
end
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+
157246
local 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)
191280
end
192281

193282
local 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
202296
end
203297

0 commit comments

Comments
 (0)