Skip to content

Commit 2b519fa

Browse files
redo logic for main, current, and target state ClosureControl attributes
1 parent b0c7eb5 commit 2b519fa

5 files changed

Lines changed: 103 additions & 115 deletions

File tree

drivers/SmartThings/matter-switch/src/sub_drivers/closures/closure_handlers/attribute_handlers.lua

Lines changed: 61 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,67 @@ clusters.ClosureControl = require "embedded_clusters.ClosureControl"
99

1010
local ClosureAttributeHandlers = {}
1111

12+
local function set_closure_control_state(device, endpoint_id, field)
13+
local cache = device:get_field(closure_fields.CLOSURE_CONTROL_STATE_CACHE) or {}
14+
if not cache[endpoint_id] then cache[endpoint_id] = {} end
15+
for k, v in pairs(field) do
16+
cache[endpoint_id][k] = v
17+
end
18+
device:set_field(closure_fields.CLOSURE_CONTROL_STATE_CACHE, cache)
19+
end
20+
21+
local function emit_closure_control_capability(device, endpoint_id)
22+
local closure_control_state = device:get_field(closure_fields.CLOSURE_CONTROL_STATE_CACHE)[endpoint_id] or {}
23+
24+
local main = closure_control_state.main
25+
local current = closure_control_state.current
26+
local target = closure_control_state.target
27+
28+
local closure_capability = capabilities.windowShade.windowShade
29+
if device:supports_capability_by_id(capabilities.doorControl.ID) then
30+
closure_capability = capabilities.doorControl.door
31+
end
32+
33+
if main == clusters.ClosureControl.types.MainStateEnum.MOVING then
34+
if target == clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED then
35+
device:emit_event_for_endpoint(endpoint_id, closure_capability.closing())
36+
elseif target == clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN then
37+
device:emit_event_for_endpoint(endpoint_id, closure_capability.opening())
38+
end
39+
elseif main == clusters.ClosureControl.types.MainStateEnum.STOPPED or main == nil then
40+
if current == nil then return end
41+
if current == clusters.ClosureControl.types.CurrentPositionEnum.FULLY_CLOSED then
42+
device:emit_event_for_endpoint(endpoint_id, closure_capability.closed())
43+
elseif current == clusters.ClosureControl.types.CurrentPositionEnum.FULLY_OPENED or
44+
device:supports_capability_by_id(capabilities.doorControl.ID) then
45+
-- doorControl does not support partially open; treat any not- fully closed as open
46+
device:emit_event_for_endpoint(endpoint_id, closure_capability.open())
47+
else
48+
device:emit_event_for_endpoint(endpoint_id, closure_capability.partially_open())
49+
end
50+
end
51+
end
52+
53+
function ClosureAttributeHandlers.main_state_attr_handler(driver, device, ib, response)
54+
if ib.data.value == nil then return end
55+
set_closure_control_state(device, ib.endpoint_id, { main = ib.data.value })
56+
emit_closure_control_capability(device, ib.endpoint_id)
57+
end
58+
59+
function ClosureAttributeHandlers.overall_current_state_attr_handler(driver, device, ib, response)
60+
if ib.data.elements == nil or ib.data.elements.position == nil or ib.data.elements.position.value == nil then return end
61+
local current = ib.data.elements.position.value
62+
set_closure_control_state(device, ib.endpoint_id, { current = current })
63+
emit_closure_control_capability(device, ib.endpoint_id)
64+
end
65+
66+
function ClosureAttributeHandlers.overall_target_state_attr_handler(driver, device, ib, response)
67+
if ib.data.elements == nil or ib.data.elements.position == nil or ib.data.elements.position.value == nil then return end
68+
local target = ib.data.elements.position.value
69+
set_closure_control_state(device, ib.endpoint_id, { target = target })
70+
emit_closure_control_capability(device, ib.endpoint_id)
71+
end
72+
1273
ClosureAttributeHandlers.current_pos_handler = function(attribute)
1374
return function(driver, device, ib, response)
1475
if ib.data.value == nil then return end
@@ -67,76 +128,4 @@ function ClosureAttributeHandlers.level_attr_handler(driver, device, ib, respons
67128
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windowShadeLevel.shadeLevel(level))
68129
end
69130

70-
function ClosureAttributeHandlers.main_state_attr_handler(driver, device, ib, response)
71-
if ib.data.value == nil then return end
72-
local windowShade = capabilities.windowShade.windowShade
73-
local current_state = device:get_field(closure_fields.CURRENT_STATE) or clusters.ClosureControl.types.CurrentPositionEnum.FULLY_CLOSED
74-
local target_state = device:get_field(closure_fields.TARGET_STATE) or clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED
75-
76-
if device:supports_capability_by_id(capabilities.windowShade.ID) then
77-
if ib.data.value == clusters.ClosureControl.types.MainStateEnum.MOVING then
78-
if target_state == clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED then
79-
device:emit_event_for_endpoint(ib.endpoint_id, windowShade.closing())
80-
elseif target_state == clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN then
81-
device:emit_event_for_endpoint(ib.endpoint_id, windowShade.opening())
82-
end
83-
elseif ib.data.value == clusters.ClosureControl.types.MainStateEnum.STOPPED then
84-
if current_state == clusters.ClosureControl.types.CurrentPositionEnum.FULLY_CLOSED then
85-
device:emit_event_for_endpoint(ib.endpoint_id, windowShade.closed())
86-
elseif current_state == clusters.ClosureControl.types.CurrentPositionEnum.FULLY_OPENED then
87-
device:emit_event_for_endpoint(ib.endpoint_id, windowShade.open())
88-
else -- PARTIALLY_OPENED, OPENED_FOR_PEDESTRIAN, OPENED_FOR_VENTILATION, or OPENED_AT_SIGNATURE
89-
device:emit_event_for_endpoint(ib.endpoint_id, windowShade.partially_open())
90-
end
91-
end
92-
else -- device:supports_capability_by_id(capabilities.doorControl.ID)
93-
if ib.data.value == clusters.ClosureControl.types.MainStateEnum.MOVING then
94-
if target_state == clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED then
95-
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.doorControl.door.closing())
96-
elseif target_state == clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN then
97-
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.doorControl.door.opening())
98-
end
99-
elseif ib.data.value == clusters.ClosureControl.types.MainStateEnum.STOPPED then
100-
if current_state == clusters.ClosureControl.types.CurrentPositionEnum.FULLY_CLOSED then
101-
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.doorControl.door.closed())
102-
else
103-
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.doorControl.doorControl.open())
104-
end
105-
end
106-
end
107-
device:set_field(closure_fields.MAIN_STATE, ib.data.value)
108-
end
109-
110-
function ClosureAttributeHandlers.overall_current_state_attr_handler(driver, device, ib, response)
111-
if ib.data.elements == nil or ib.data.elements.position.value == nil then return end
112-
local windowShade = capabilities.windowShade.windowShade
113-
local main_state = device:get_field(closure_fields.MAIN_STATE) or clusters.ClosureControl.types.MainStateEnum.STOPPED
114-
115-
if device:supports_capability_by_id(capabilities.windowShade.ID) then
116-
if main_state == clusters.ClosureControl.types.MainStateEnum.STOPPED then
117-
if ib.data.elements.position.value == clusters.ClosureControl.types.CurrentPositionEnum.FULLY_CLOSED then
118-
device:emit_event_for_endpoint(ib.endpoint_id, windowShade.closed())
119-
elseif ib.data.elements.position.value == clusters.ClosureControl.types.CurrentPositionEnum.FULLY_OPENED then
120-
device:emit_event_for_endpoint(ib.endpoint_id, windowShade.open())
121-
else -- PARTIALLY_OPENED, OPENED_FOR_PEDESTRIAN, OPENED_FOR_VENTILATION, or OPENED_AT_SIGNATURE
122-
device:emit_event_for_endpoint(ib.endpoint_id, windowShade.partially_open())
123-
end
124-
end
125-
else -- device:supports_capability_by_id(capabilities.doorControl.ID)
126-
if main_state == clusters.ClosureControl.types.MainStateEnum.STOPPED then
127-
if ib.data.elements.position.value == clusters.ClosureControl.types.CurrentPositionEnum.FULLY_CLOSED then
128-
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.doorControl.door.closed())
129-
else
130-
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.doorControl.doorControl.open())
131-
end
132-
end
133-
end
134-
device:set_field(closure_fields.CURRENT_STATE, ib.data.elements.position.value)
135-
end
136-
137-
function ClosureAttributeHandlers.overall_target_state_attr_handler(driver, device, ib, response)
138-
if ib.data.elements == nil or ib.data.elements.position.value == nil then return end
139-
device:set_field(closure_fields.TARGET_STATE, ib.data.elements.position.value)
140-
end
141-
142131
return ClosureAttributeHandlers

drivers/SmartThings/matter-switch/src/sub_drivers/closures/closure_handlers/capability_handlers.lua

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@ function ClosureCapabilityHandlers.handle_close(driver, device, cmd)
3535
req = clusters.WindowCovering.server.commands.UpOrOpen(device, endpoint_id)
3636
end
3737
else -- ClosureControl cluster
38-
req = clusters.ClosureControl.server.commands.MoveTo(device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED)
38+
req = clusters.ClosureControl.server.commands.MoveTo(
39+
device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED
40+
)
3941
if device:get_field(closure_fields.REVERSE_POLARITY) then
40-
req = clusters.ClosureControl.server.commands.MoveTo(device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN)
42+
req = clusters.ClosureControl.server.commands.MoveTo(
43+
device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN
44+
)
4145
end
4246
end
4347
device:send(req)
@@ -52,9 +56,13 @@ function ClosureCapabilityHandlers.handle_open(driver, device, cmd)
5256
req = clusters.WindowCovering.server.commands.DownOrClose(device, endpoint_id)
5357
end
5458
else -- ClosureControl cluster
55-
req = clusters.ClosureControl.server.commands.MoveTo(device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN)
59+
req = clusters.ClosureControl.server.commands.MoveTo(
60+
device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_OPEN
61+
)
5662
if device:get_field(closure_fields.REVERSE_POLARITY) then
57-
req = clusters.ClosureControl.server.commands.MoveTo(device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED)
63+
req = clusters.ClosureControl.server.commands.MoveTo(
64+
device, endpoint_id, clusters.ClosureControl.types.TargetPositionEnum.MOVE_TO_FULLY_CLOSED
65+
)
5866
end
5967
end
6068
device:send(req)

drivers/SmartThings/matter-switch/src/sub_drivers/closures/closure_utils/fields.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ ClosureFields.CURRENT_TILT = "__current_tilt"
88
ClosureFields.DEFAULT_PRESET_LEVEL = 50
99
ClosureFields.PRESET_LEVEL_KEY = "__preset_level_key"
1010
ClosureFields.REVERSE_POLARITY = "__reverse_polarity"
11-
ClosureFields.TARGET_STATE = "__target_state"
11+
-- Endpoint-scoped ClosureControl state cache key. A table is stored for each endpoint:
12+
-- { main = <MainStateEnum>, current = <CurrentPositionEnum>, target = <TargetPositionEnum> }
13+
ClosureFields.CLOSURE_CONTROL_STATE_CACHE = "__closure_control_state_cache"
1214

1315
return ClosureFields

drivers/SmartThings/matter-switch/src/sub_drivers/closures/closure_utils/utils.lua

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
-- Copyright © 2026 SmartThings, Inc.
22
-- Licensed under the Apache License, Version 2.0
33

4+
local capabilities = require "st.capabilities"
5+
local clusters = require "st.matter.clusters"
6+
local fields = require "switch_utils.fields"
7+
local im = require "st.matter.interaction_model"
8+
local switch_utils = require "switch_utils.utils"
49

510
local ClosureUtils = {}
611

712
function ClosureUtils.subscribe(device)
8-
local capabilities = require "st.capabilities"
9-
local clusters = require "st.matter.clusters"
10-
local fields = require "switch_utils.fields"
11-
local im = require "st.matter.interaction_model"
12-
local switch_utils = require "switch_utils.utils"
13-
1413
local closure_subscribed_attributes = {
1514
[capabilities.battery.ID] = {
1615
clusters.PowerSource.attributes.BatPercentRemaining,

drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -220,40 +220,29 @@ function WindowCoveringDeviceConfiguration.assign_profile_for_window_covering_ep
220220
return "window-covering-modular", optional_supported_component_capabilities
221221
end
222222

223-
function ClosureDeviceConfiguration.assign_profile_for_covering_ep(device, server_covering_ep_id)
224-
local ep_info = switch_utils.get_endpoint_info(device, server_covering_ep_id)
223+
local function get_optional_capabilities_for_closure_ep(device, server_ep_id)
224+
local ep_info = switch_utils.get_endpoint_info(device, server_ep_id)
225225
local optional_supported_component_capabilities = {}
226226
local main_component_capabilities = {}
227227
populate_battery_capability_if_supported(device, ep_info, main_component_capabilities)
228228
table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities})
229-
return "covering", optional_supported_component_capabilities
229+
return optional_supported_component_capabilities
230+
end
231+
232+
function ClosureDeviceConfiguration.assign_profile_for_covering_ep(device, server_covering_ep_id)
233+
return "covering", get_optional_capabilities_for_closure_ep(device, server_covering_ep_id)
230234
end
231235

232236
function ClosureDeviceConfiguration.assign_profile_for_gate_ep(device, server_gate_ep_id)
233-
local ep_info = switch_utils.get_endpoint_info(device, server_gate_ep_id)
234-
local optional_supported_component_capabilities = {}
235-
local main_component_capabilities = {}
236-
populate_battery_capability_if_supported(device, ep_info, main_component_capabilities)
237-
table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities})
238-
return "gate", optional_supported_component_capabilities
237+
return "gate", get_optional_capabilities_for_closure_ep(device, server_gate_ep_id)
239238
end
240239

241240
function ClosureDeviceConfiguration.assign_profile_for_door_ep(device, server_door_ep_id)
242-
local ep_info = switch_utils.get_endpoint_info(device, server_door_ep_id)
243-
local optional_supported_component_capabilities = {}
244-
local main_component_capabilities = {}
245-
populate_battery_capability_if_supported(device, ep_info, main_component_capabilities)
246-
table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities})
247-
return "door", optional_supported_component_capabilities
241+
return "door", get_optional_capabilities_for_closure_ep(device, server_door_ep_id)
248242
end
249243

250244
function ClosureDeviceConfiguration.assign_profile_for_garage_door_ep(device, server_garage_door_ep_id)
251-
local ep_info = switch_utils.get_endpoint_info(device, server_garage_door_ep_id)
252-
local optional_supported_component_capabilities = {}
253-
local main_component_capabilities = {}
254-
populate_battery_capability_if_supported(device, ep_info, main_component_capabilities)
255-
table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities})
256-
return "garage-door", optional_supported_component_capabilities
245+
return "garage-door", get_optional_capabilities_for_closure_ep(device, server_garage_door_ep_id)
257246
end
258247

259248

@@ -330,18 +319,19 @@ function DeviceConfiguration.match_profile(driver, device)
330319
-- initialize the main device card with closure if applicable
331320
local closure_ep_ids = switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.CLOSURE)
332321
if #closure_ep_ids > 0 then
333-
ChildConfiguration.create_or_update_child_devices(driver, device, closure_ep_ids, default_endpoint_id, ClosureDeviceConfiguration.assign_profile_for_closure_ep)
334-
end
335-
if switch_utils.tbl_contains(closure_ep_ids, default_endpoint_id) then
322+
local assign_closure_profile_fn_map = {
323+
[fields.closure_tag.COVERING] = ClosureDeviceConfiguration.assign_profile_for_covering_ep,
324+
[fields.closure_tag.WINDOW] = ClosureDeviceConfiguration.assign_profile_for_covering_ep,
325+
[fields.closure_tag.BARRIER] = ClosureDeviceConfiguration.assign_profile_for_covering_ep,
326+
[fields.closure_tag.CABINET] = ClosureDeviceConfiguration.assign_profile_for_covering_ep,
327+
[fields.closure_tag.GATE] = ClosureDeviceConfiguration.assign_profile_for_gate_ep,
328+
[fields.closure_tag.GARAGE_DOOR] = ClosureDeviceConfiguration.assign_profile_for_garage_door_ep,
329+
[fields.closure_tag.DOOR] = ClosureDeviceConfiguration.assign_profile_for_door_ep
330+
}
336331
local closure_tag = device:get_field(fields.profiling_data.CLOSURE_TAG)
337-
if closure_tag == fields.closure_tag.GATE then
338-
updated_profile, optional_component_capabilities = ClosureDeviceConfiguration.assign_profile_for_gate_ep(device, default_endpoint_id)
339-
elseif closure_tag == fields.closure_tag.DOOR then
340-
updated_profile, optional_component_capabilities = ClosureDeviceConfiguration.assign_profile_for_door_ep(device, default_endpoint_id)
341-
elseif closure_tag == fields.closure_tag.GARAGE_DOOR then
342-
updated_profile, optional_component_capabilities = ClosureDeviceConfiguration.assign_profile_for_garage_door_ep(device, default_endpoint_id)
343-
else -- COVERING, WINDOW, BARRIER, CABINET - use generic Covering
344-
updated_profile, optional_component_capabilities = ClosureDeviceConfiguration.assign_profile_for_covering_ep(device, default_endpoint_id)
332+
ChildConfiguration.create_or_update_child_devices(driver, device, closure_ep_ids, default_endpoint_id, assign_closure_profile_fn_map[closure_tag])
333+
if switch_utils.tbl_contains(closure_ep_ids, default_endpoint_id) then
334+
updated_profile, optional_component_capabilities = assign_closure_profile_fn_map[closure_tag](device, default_endpoint_id)
345335
end
346336
end
347337

0 commit comments

Comments
 (0)