1
- -- Copyright 2022 SmartThings
1
+ -- Copyright 2025 SmartThings
2
2
--
3
3
-- Licensed under the Apache License, Version 2.0 (the "License");
4
4
-- you may not use this file except in compliance with the License.
15
15
-- Note: Currently only support for window shades with the PositionallyAware Feature
16
16
-- Note: No support for setting device into calibration mode, it must be done manually
17
17
local capabilities = require " st.capabilities"
18
+ local clusters = require " st.matter.clusters"
18
19
local im = require " st.matter.interaction_model"
19
20
local log = require " log"
20
- local clusters = require " st.matter.clusters"
21
21
local MatterDriver = require " st.matter.driver"
22
22
23
23
local CURRENT_LIFT = " __current_lift"
24
24
local CURRENT_TILT = " __current_tilt"
25
+ local REVERSE_POLARITY = " __reverse_polarity"
26
+ local STATE_MACHINE = " __state_machine"
27
+
28
+ local DEFAULT_LEVEL = 0
29
+
30
+ local StateMachineEnum = {
31
+ STATE_IDLE = 0x00 ,
32
+ STATE_MOVING = 0x01 ,
33
+ STATE_OPERATIONAL_STATE_FIRED = 0x02 ,
34
+ STATE_CURRENT_POSITION_FIRED = 0x03
35
+ }
36
+
25
37
local battery_support = {
26
38
NO_BATTERY = " NO_BATTERY" ,
27
39
BATTERY_LEVEL = " BATTERY_LEVEL" ,
28
40
BATTERY_PERCENTAGE = " BATTERY_PERCENTAGE"
29
41
}
30
- local REVERSE_POLARITY = " __reverse_polarity"
31
42
32
43
local function find_default_endpoint (device , cluster )
33
44
local res = device .MATTER_DEFAULT_ENDPOINT
@@ -188,59 +199,63 @@ local function handle_shade_tilt_level(driver, device, cmd)
188
199
device :send (req )
189
200
end
190
201
202
+ --- Update the window shade status according to the lift and tilt positions.
203
+ --- LIFT TILT Window Shade
204
+ --- 100 any Open
205
+ --- 1-99 any Partially Open
206
+ --- 0 1-100 Partially Open
207
+ --- 0 0 Closed
208
+ --- 0 nil Closed
209
+ --- nil 100 Open
210
+ --- nil 1-99 Partially Open
211
+ --- nil 0 Closed
212
+ --- Note that lift or tilt may be nil if either the window shade does not
213
+ --- support them or if they haven't been received from a device report yet.
214
+ local function update_shade_status (device , endpoint_id , lift_position , tilt_position )
215
+ local windowShade = capabilities .windowShade .windowShade
216
+ if lift_position == nil then
217
+ if tilt_position == 0 then
218
+ device :emit_event_for_endpoint (endpoint_id , windowShade .closed ())
219
+ elseif tilt_position == 100 then
220
+ device :emit_event_for_endpoint (endpoint_id , windowShade .open ())
221
+ else
222
+ device :emit_event_for_endpoint (endpoint_id , windowShade .partially_open ())
223
+ end
224
+ elseif lift_position == 100 then
225
+ device :emit_event_for_endpoint (endpoint_id , windowShade .open ())
226
+ elseif lift_position > 0 then
227
+ device :emit_event_for_endpoint (endpoint_id , windowShade .partially_open ())
228
+ elseif lift_position == 0 then
229
+ if tilt_position == nil or tilt_position == 0 then
230
+ device :emit_event_for_endpoint (endpoint_id , windowShade .closed ())
231
+ elseif tilt_position > 0 then
232
+ device :emit_event_for_endpoint (endpoint_id , windowShade .partially_open ())
233
+ end
234
+ else
235
+ device :emit_event_for_endpoint (endpoint_id , windowShade .unknown ())
236
+ end
237
+ end
238
+
191
239
-- current lift/tilt percentage, changed to 100ths percent
192
240
local current_pos_handler = function (attribute )
193
241
return function (driver , device , ib , response )
194
- if ib .data .value == nil then
195
- return
196
- end
197
- local windowShade = capabilities .windowShade .windowShade
242
+ if ib .data .value == nil then return end
198
243
local position = reverse_polarity_if_needed (device , math.floor ((ib .data .value / 100 )))
199
244
device :emit_event_for_endpoint (ib .endpoint_id , attribute (position ))
200
245
201
246
if attribute == capabilities .windowShadeLevel .shadeLevel then
202
247
device :set_field (CURRENT_LIFT , position )
203
- else
248
+ else -- attribute = capabilities.windowShadeTiltLevel.shadeTiltLevel
204
249
device :set_field (CURRENT_TILT , position )
205
250
end
206
251
207
- local lift_position = device :get_field (CURRENT_LIFT )
208
- local tilt_position = device :get_field (CURRENT_TILT )
209
-
210
- -- Update the window shade status according to the lift and tilt positions.
211
- -- LIFT TILT Window Shade
212
- -- 100 any Open
213
- -- 1-99 any Partially Open
214
- -- 0 1-100 Partially Open
215
- -- 0 0 Closed
216
- -- 0 nil Closed
217
- -- nil 100 Open
218
- -- nil 1-99 Partially Open
219
- -- nil 0 Closed
220
- -- Note that lift or tilt may be nil if either the window shade does not
221
- -- support them or if they haven't been received from a device report yet.
222
-
223
- if lift_position == nil then
224
- if tilt_position == 0 then
225
- device :emit_event_for_endpoint (ib .endpoint_id , windowShade .closed ())
226
- elseif tilt_position == 100 then
227
- device :emit_event_for_endpoint (ib .endpoint_id , windowShade .open ())
228
- else
229
- device :emit_event_for_endpoint (ib .endpoint_id , windowShade .partially_open ())
230
- end
231
-
232
- elseif lift_position == 100 then
233
- device :emit_event_for_endpoint (ib .endpoint_id , windowShade .open ())
234
-
235
- elseif lift_position > 0 then
236
- device :emit_event_for_endpoint (ib .endpoint_id , windowShade .partially_open ())
237
-
238
- elseif lift_position == 0 then
239
- if tilt_position == nil or tilt_position == 0 then
240
- device :emit_event_for_endpoint (ib .endpoint_id , windowShade .closed ())
241
- elseif tilt_position > 0 then
242
- device :emit_event_for_endpoint (ib .endpoint_id , windowShade .partially_open ())
243
- end
252
+ local state_machine = device :get_field (STATE_MACHINE )
253
+ -- When state_machine is STATE_IDLE or STATE_CURRENT_POSITION_FIRED, nothing to do
254
+ if state_machine == StateMachineEnum .STATE_MOVING then
255
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_CURRENT_POSITION_FIRED )
256
+ elseif state_machine == StateMachineEnum .STATE_OPERATIONAL_STATE_FIRED or state_machine == nil then
257
+ update_shade_status (device , ib .endpoint_id , device :get_field (CURRENT_LIFT ), device :get_field (CURRENT_TILT ))
258
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_IDLE )
244
259
end
245
260
end
246
261
end
@@ -249,12 +264,30 @@ end
249
264
local function current_status_handler (driver , device , ib , response )
250
265
local windowShade = capabilities .windowShade .windowShade
251
266
local state = ib .data .value & clusters .WindowCovering .types .OperationalStatus .GLOBAL
252
- if state == 1 then -- opening
253
- device :emit_event_for_endpoint (ib .endpoint_id , windowShade .opening ())
254
- elseif state == 2 then -- closing
255
- device :emit_event_for_endpoint (ib .endpoint_id , windowShade .closing ())
256
- elseif state ~= 0 then -- unknown
257
- device :emit_event_for_endpoint (ib .endpoint_id , windowShade .unknown ())
267
+ local state_machine = device :get_field (STATE_MACHINE )
268
+ -- When state_machine is STATE_OPERATIONAL_STATE_FIRED, nothing to do
269
+ if state_machine == StateMachineEnum .STATE_IDLE then
270
+ if state == 1 then -- opening
271
+ device :emit_event_for_endpoint (ib .endpoint_id , windowShade .opening ())
272
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_MOVING )
273
+ elseif state == 2 then -- closing
274
+ device :emit_event_for_endpoint (ib .endpoint_id , windowShade .closing ())
275
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_MOVING )
276
+ end
277
+ elseif state_machine == StateMachineEnum .STATE_MOVING then
278
+ if state == 0 then
279
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_OPERATIONAL_STATE_FIRED )
280
+ elseif state == 1 then -- opening
281
+ device :emit_event_for_endpoint (ib .endpoint_id , windowShade .opening ())
282
+ elseif state == 2 then -- closing
283
+ device :emit_event_for_endpoint (ib .endpoint_id , windowShade .closing ())
284
+ else
285
+ device :emit_event_for_endpoint (ib .endpoint_id , windowShade .unknown ())
286
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_IDLE )
287
+ end
288
+ elseif state_machine == StateMachineEnum .STATE_CURRENT_POSITION_FIRED then
289
+ update_shade_status (device , ib .endpoint_id , device :get_field (CURRENT_LIFT ), device :get_field (CURRENT_TILT ))
290
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_IDLE )
258
291
end
259
292
end
260
293
@@ -367,10 +400,6 @@ local matter_driver_template = {
367
400
capabilities .windowShadePreset ,
368
401
capabilities .battery ,
369
402
capabilities .batteryLevel ,
370
- },
371
- sub_drivers = {
372
- -- for devices sending a position update while device is in motion
373
- require (" matter-window-covering-position-updates-while-moving" )
374
403
}
375
404
}
376
405
0 commit comments