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 StateMachineEnum = {
29
+ STATE_IDLE = 0x00 ,
30
+ STATE_MOVING = 0x01 ,
31
+ STATE_OPERATIONAL_STATE_FIRED = 0x02 ,
32
+ STATE_CURRENT_POSITION_FIRED = 0x03
33
+ }
34
+
25
35
local battery_support = {
26
36
NO_BATTERY = " NO_BATTERY" ,
27
37
BATTERY_LEVEL = " BATTERY_LEVEL" ,
28
38
BATTERY_PERCENTAGE = " BATTERY_PERCENTAGE"
29
39
}
30
- local REVERSE_POLARITY = " __reverse_polarity"
31
40
32
41
local function find_default_endpoint (device , cluster )
33
42
local res = device .MATTER_DEFAULT_ENDPOINT
@@ -188,59 +197,63 @@ local function handle_shade_tilt_level(driver, device, cmd)
188
197
device :send (req )
189
198
end
190
199
200
+ --- Update the window shade status according to the lift and tilt positions.
201
+ --- LIFT TILT Window Shade
202
+ --- 100 any Open
203
+ --- 1-99 any Partially Open
204
+ --- 0 1-100 Partially Open
205
+ --- 0 0 Closed
206
+ --- 0 nil Closed
207
+ --- nil 100 Open
208
+ --- nil 1-99 Partially Open
209
+ --- nil 0 Closed
210
+ --- Note that lift or tilt may be nil if either the window shade does not
211
+ --- support them or if they haven't been received from a device report yet.
212
+ local function update_shade_status (device , endpoint_id , lift_position , tilt_position )
213
+ local windowShade = capabilities .windowShade .windowShade
214
+ if lift_position == nil then
215
+ if tilt_position == 0 then
216
+ device :emit_event_for_endpoint (endpoint_id , windowShade .closed ())
217
+ elseif tilt_position == 100 then
218
+ device :emit_event_for_endpoint (endpoint_id , windowShade .open ())
219
+ else
220
+ device :emit_event_for_endpoint (endpoint_id , windowShade .partially_open ())
221
+ end
222
+ elseif lift_position == 100 then
223
+ device :emit_event_for_endpoint (endpoint_id , windowShade .open ())
224
+ elseif lift_position > 0 then
225
+ device :emit_event_for_endpoint (endpoint_id , windowShade .partially_open ())
226
+ elseif lift_position == 0 then
227
+ if tilt_position == nil or tilt_position == 0 then
228
+ device :emit_event_for_endpoint (endpoint_id , windowShade .closed ())
229
+ elseif tilt_position > 0 then
230
+ device :emit_event_for_endpoint (endpoint_id , windowShade .partially_open ())
231
+ end
232
+ else
233
+ device :emit_event_for_endpoint (endpoint_id , windowShade .unknown ())
234
+ end
235
+ end
236
+
191
237
-- current lift/tilt percentage, changed to 100ths percent
192
238
local current_pos_handler = function (attribute )
193
239
return function (driver , device , ib , response )
194
- if ib .data .value == nil then
195
- return
196
- end
197
- local windowShade = capabilities .windowShade .windowShade
240
+ if ib .data .value == nil then return end
198
241
local position = reverse_polarity_if_needed (device , math.floor ((ib .data .value / 100 )))
199
242
device :emit_event_for_endpoint (ib .endpoint_id , attribute (position ))
200
243
201
244
if attribute == capabilities .windowShadeLevel .shadeLevel then
202
245
device :set_field (CURRENT_LIFT , position )
203
- else
246
+ else -- attribute = capabilities.windowShadeTiltLevel.shadeTiltLevel
204
247
device :set_field (CURRENT_TILT , position )
205
248
end
206
249
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
250
+ local state_machine = device :get_field (STATE_MACHINE )
251
+ -- When state_machine is STATE_IDLE or STATE_CURRENT_POSITION_FIRED, nothing to do
252
+ if state_machine == StateMachineEnum .STATE_MOVING then
253
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_CURRENT_POSITION_FIRED )
254
+ elseif state_machine == StateMachineEnum .STATE_OPERATIONAL_STATE_FIRED or state_machine == nil then
255
+ update_shade_status (device , ib .endpoint_id , device :get_field (CURRENT_LIFT ), device :get_field (CURRENT_TILT ))
256
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_IDLE )
244
257
end
245
258
end
246
259
end
@@ -249,12 +262,30 @@ end
249
262
local function current_status_handler (driver , device , ib , response )
250
263
local windowShade = capabilities .windowShade .windowShade
251
264
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 ())
265
+ local state_machine = device :get_field (STATE_MACHINE )
266
+ -- When state_machine is STATE_OPERATIONAL_STATE_FIRED, nothing to do
267
+ if state_machine == StateMachineEnum .STATE_IDLE then
268
+ if state == 1 then -- opening
269
+ device :emit_event_for_endpoint (ib .endpoint_id , windowShade .opening ())
270
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_MOVING )
271
+ elseif state == 2 then -- closing
272
+ device :emit_event_for_endpoint (ib .endpoint_id , windowShade .closing ())
273
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_MOVING )
274
+ end
275
+ elseif state_machine == StateMachineEnum .STATE_MOVING then
276
+ if state == 0 then
277
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_OPERATIONAL_STATE_FIRED )
278
+ elseif state == 1 then -- opening
279
+ device :emit_event_for_endpoint (ib .endpoint_id , windowShade .opening ())
280
+ elseif state == 2 then -- closing
281
+ device :emit_event_for_endpoint (ib .endpoint_id , windowShade .closing ())
282
+ else
283
+ device :emit_event_for_endpoint (ib .endpoint_id , windowShade .unknown ())
284
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_IDLE )
285
+ end
286
+ elseif state_machine == StateMachineEnum .STATE_CURRENT_POSITION_FIRED then
287
+ update_shade_status (device , ib .endpoint_id , device :get_field (CURRENT_LIFT ), device :get_field (CURRENT_TILT ))
288
+ device :set_field (STATE_MACHINE , StateMachineEnum .STATE_IDLE )
258
289
end
259
290
end
260
291
@@ -367,10 +398,6 @@ local matter_driver_template = {
367
398
capabilities .windowShadePreset ,
368
399
capabilities .battery ,
369
400
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
401
}
375
402
}
376
403
0 commit comments