pyvlx 0.2.27__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. pyvlx/__init__.py +21 -0
  2. pyvlx/api/__init__.py +23 -0
  3. pyvlx/api/activate_scene.py +63 -0
  4. pyvlx/api/api_event.py +73 -0
  5. pyvlx/api/command_send.py +85 -0
  6. pyvlx/api/factory_default.py +34 -0
  7. pyvlx/api/frame_creation.py +202 -0
  8. pyvlx/api/frames/__init__.py +76 -0
  9. pyvlx/api/frames/alias_array.py +45 -0
  10. pyvlx/api/frames/frame.py +56 -0
  11. pyvlx/api/frames/frame_activate_scene.py +92 -0
  12. pyvlx/api/frames/frame_activation_log_updated.py +14 -0
  13. pyvlx/api/frames/frame_command_send.py +280 -0
  14. pyvlx/api/frames/frame_discover_nodes.py +64 -0
  15. pyvlx/api/frames/frame_error_notification.py +42 -0
  16. pyvlx/api/frames/frame_facory_default.py +32 -0
  17. pyvlx/api/frames/frame_get_all_nodes_information.py +218 -0
  18. pyvlx/api/frames/frame_get_limitation.py +127 -0
  19. pyvlx/api/frames/frame_get_local_time.py +38 -0
  20. pyvlx/api/frames/frame_get_network_setup.py +64 -0
  21. pyvlx/api/frames/frame_get_node_information.py +223 -0
  22. pyvlx/api/frames/frame_get_protocol_version.py +53 -0
  23. pyvlx/api/frames/frame_get_scene_list.py +82 -0
  24. pyvlx/api/frames/frame_get_state.py +47 -0
  25. pyvlx/api/frames/frame_get_version.py +72 -0
  26. pyvlx/api/frames/frame_helper.py +40 -0
  27. pyvlx/api/frames/frame_house_status_monitor_disable_cfm.py +14 -0
  28. pyvlx/api/frames/frame_house_status_monitor_disable_req.py +14 -0
  29. pyvlx/api/frames/frame_house_status_monitor_enable_cfm.py +14 -0
  30. pyvlx/api/frames/frame_house_status_monitor_enable_req.py +14 -0
  31. pyvlx/api/frames/frame_leave_learn_state.py +41 -0
  32. pyvlx/api/frames/frame_node_information_changed.py +57 -0
  33. pyvlx/api/frames/frame_node_state_position_changed_notification.py +84 -0
  34. pyvlx/api/frames/frame_password_change.py +114 -0
  35. pyvlx/api/frames/frame_password_enter.py +70 -0
  36. pyvlx/api/frames/frame_reboot.py +32 -0
  37. pyvlx/api/frames/frame_set_node_name.py +73 -0
  38. pyvlx/api/frames/frame_set_utc.py +45 -0
  39. pyvlx/api/frames/frame_status_request.py +212 -0
  40. pyvlx/api/get_all_nodes_information.py +46 -0
  41. pyvlx/api/get_limitation.py +64 -0
  42. pyvlx/api/get_local_time.py +34 -0
  43. pyvlx/api/get_network_setup.py +34 -0
  44. pyvlx/api/get_node_information.py +42 -0
  45. pyvlx/api/get_protocol_version.py +40 -0
  46. pyvlx/api/get_scene_list.py +49 -0
  47. pyvlx/api/get_state.py +43 -0
  48. pyvlx/api/get_version.py +34 -0
  49. pyvlx/api/house_status_monitor.py +52 -0
  50. pyvlx/api/leave_learn_state.py +33 -0
  51. pyvlx/api/password_enter.py +39 -0
  52. pyvlx/api/reboot.py +33 -0
  53. pyvlx/api/session_id.py +20 -0
  54. pyvlx/api/set_node_name.py +32 -0
  55. pyvlx/api/set_utc.py +31 -0
  56. pyvlx/api/status_request.py +48 -0
  57. pyvlx/config.py +54 -0
  58. pyvlx/connection.py +182 -0
  59. pyvlx/const.py +685 -0
  60. pyvlx/dataobjects.py +161 -0
  61. pyvlx/discovery.py +100 -0
  62. pyvlx/exception.py +26 -0
  63. pyvlx/heartbeat.py +79 -0
  64. pyvlx/klf200gateway.py +167 -0
  65. pyvlx/lightening_device.py +102 -0
  66. pyvlx/log.py +4 -0
  67. pyvlx/node.py +74 -0
  68. pyvlx/node_helper.py +165 -0
  69. pyvlx/node_updater.py +162 -0
  70. pyvlx/nodes.py +99 -0
  71. pyvlx/on_off_switch.py +44 -0
  72. pyvlx/opening_device.py +644 -0
  73. pyvlx/parameter.py +357 -0
  74. pyvlx/py.typed +0 -0
  75. pyvlx/pyvlx.py +124 -0
  76. pyvlx/scene.py +53 -0
  77. pyvlx/scenes.py +60 -0
  78. pyvlx/slip.py +48 -0
  79. pyvlx/string_helper.py +20 -0
  80. pyvlx-0.2.27.dist-info/METADATA +122 -0
  81. pyvlx-0.2.27.dist-info/RECORD +84 -0
  82. pyvlx-0.2.27.dist-info/WHEEL +5 -0
  83. pyvlx-0.2.27.dist-info/licenses/LICENSE +165 -0
  84. pyvlx-0.2.27.dist-info/top_level.txt +1 -0
@@ -0,0 +1,644 @@
1
+ """Module for Opening devices."""
2
+ import asyncio
3
+ import datetime
4
+ from asyncio import Task
5
+ from typing import TYPE_CHECKING, Any, Optional
6
+
7
+ from .api.command_send import CommandSend
8
+ from .api.get_limitation import GetLimitation
9
+ from .const import Velocity
10
+ from .exception import PyVLXException
11
+ from .node import Node
12
+ from .parameter import (
13
+ CurrentPosition, DualRollerShutterPosition, IgnorePosition, Parameter,
14
+ Position, TargetPosition)
15
+
16
+ if TYPE_CHECKING:
17
+ from pyvlx import PyVLX
18
+
19
+
20
+ class OpeningDevice(Node):
21
+ """Meta class for opening device with one main parameter for position."""
22
+
23
+ def __init__(
24
+ self,
25
+ pyvlx: "PyVLX",
26
+ node_id: int,
27
+ name: str,
28
+ serial_number: Optional[str] = None,
29
+ position_parameter: Parameter = Parameter(),
30
+ ):
31
+ """Initialize opening device.
32
+
33
+ Parameters:
34
+ * pyvlx: PyVLX object
35
+ * node_id: internal id for addressing nodes.
36
+ Provided by KLF 200 device
37
+ * name: node name
38
+ * serial_number: serial number of the node.
39
+ * position_parameter: initial position of the opening device.
40
+
41
+ """
42
+ super().__init__(
43
+ pyvlx=pyvlx, node_id=node_id, name=name, serial_number=serial_number
44
+ )
45
+ self.position: Position = Position(parameter=position_parameter)
46
+ self.target: Position = Position(parameter=position_parameter)
47
+ self.is_opening: bool = False
48
+ self.is_closing: bool = False
49
+ self.state_received_at: Optional[datetime.datetime] = None
50
+ self.estimated_completion: Optional[datetime.datetime] = None
51
+ self.use_default_velocity: bool = False
52
+ self.default_velocity: Velocity = Velocity.DEFAULT
53
+ self.open_position_target: int = 0
54
+ self.close_position_target: int = 100
55
+ self._update_task: Task | None = None
56
+
57
+ async def _update_calls(self) -> None:
58
+ """While cover are moving, perform periodically update calls."""
59
+ while self.is_moving():
60
+ await asyncio.sleep(1)
61
+ await self.after_update()
62
+ if self._update_task:
63
+ self._update_task.cancel()
64
+ self._update_task = None
65
+
66
+ async def set_position(
67
+ self,
68
+ position: Position,
69
+ velocity: Velocity | int | None = Velocity.DEFAULT,
70
+ wait_for_completion: bool = True,
71
+ ) -> None:
72
+ """Set opening device to desired position.
73
+
74
+ Parameters:
75
+ * position: Position object containing the target position.
76
+ * velocity: Velocity to be used during transition.
77
+ * wait_for_completion: If set, function will return
78
+ after device has reached target position.
79
+
80
+ """
81
+ kwargs: Any = {}
82
+
83
+ if (
84
+ velocity is None or velocity is Velocity.DEFAULT
85
+ ) and self.use_default_velocity:
86
+ velocity = self.default_velocity
87
+
88
+ if isinstance(velocity, Velocity):
89
+ if velocity is not Velocity.DEFAULT:
90
+ if velocity is Velocity.SILENT:
91
+ kwargs["fp1"] = Parameter(raw=b"\x00\x00")
92
+ else:
93
+ kwargs["fp1"] = Parameter(raw=b"\xC8\x00")
94
+ elif isinstance(velocity, int):
95
+ kwargs["fp1"] = Position.from_percent(velocity)
96
+
97
+ command = CommandSend(
98
+ pyvlx=self.pyvlx,
99
+ wait_for_completion=wait_for_completion,
100
+ node_id=self.node_id,
101
+ parameter=position,
102
+ functional_parameter=kwargs,
103
+ )
104
+ await command.send()
105
+ await self.after_update()
106
+
107
+ async def open(
108
+ self,
109
+ velocity: Velocity | int | None = Velocity.DEFAULT,
110
+ wait_for_completion: bool = True,
111
+ ) -> None:
112
+ """Open opening device.
113
+
114
+ Parameters:
115
+ * velocity: Velocity to be used during transition.
116
+ * wait_for_completion: If set, function will return
117
+ after device has reached target position.
118
+
119
+ """
120
+ await self.set_position(
121
+ position=Position(position_percent=self.open_position_target),
122
+ velocity=velocity,
123
+ wait_for_completion=wait_for_completion,
124
+ )
125
+
126
+ async def close(
127
+ self,
128
+ velocity: Velocity | int | None = Velocity.DEFAULT,
129
+ wait_for_completion: bool = True,
130
+ ) -> None:
131
+ """Close opening device.
132
+
133
+ Parameters:
134
+ * velocity: Velocity to be used during transition.
135
+ * wait_for_completion: If set, function will return
136
+ after device has reached target position.
137
+
138
+ """
139
+ await self.set_position(
140
+ position=Position(position_percent=self.close_position_target),
141
+ velocity=velocity,
142
+ wait_for_completion=wait_for_completion,
143
+ )
144
+
145
+ async def stop(self, wait_for_completion: bool = True) -> None:
146
+ """Stop opening device.
147
+
148
+ Parameters:
149
+ * wait_for_completion: If set, function will return
150
+ after device has reached target position.
151
+
152
+ """
153
+ await self.set_position(
154
+ position=CurrentPosition(), wait_for_completion=wait_for_completion
155
+ )
156
+
157
+ def is_moving(self) -> bool:
158
+ """Return moving state of the cover."""
159
+ return self.is_opening or self.is_closing
160
+
161
+ def movement_percent(self) -> int:
162
+ """Return movement percentage of the cover."""
163
+ if (
164
+ self.estimated_completion is None
165
+ or self.state_received_at is None
166
+ or self.estimated_completion < datetime.datetime.now()
167
+ ):
168
+ return 100
169
+
170
+ movement_duration_s: float = (
171
+ self.estimated_completion - self.state_received_at
172
+ ).total_seconds()
173
+ time_passed_s: float = (
174
+ datetime.datetime.now() - self.state_received_at
175
+ ).total_seconds()
176
+
177
+ percent: int = int(time_passed_s / movement_duration_s * 100)
178
+ percent = max(percent, 0)
179
+ percent = min(percent, 100)
180
+ return percent
181
+
182
+ def get_position(self) -> Position:
183
+ """Return position of the cover."""
184
+ if self.is_moving():
185
+ percent = self.movement_percent()
186
+ movement_origin = self.position.position_percent
187
+ movement_target = self.target.position_percent
188
+ current_position = (
189
+ movement_origin + (movement_target - movement_origin) / 100 * percent
190
+ )
191
+ if not self._update_task:
192
+ self._update_task = self.pyvlx.loop.create_task(self._update_calls())
193
+ return Position(position_percent=int(current_position))
194
+ return self.position
195
+
196
+ def __str__(self) -> str:
197
+ """Return object as readable string."""
198
+ return '<{} name="{}" node_id="{}" serial_number="{}" position="{}"/>'.format(
199
+ type(self).__name__,
200
+ self.name,
201
+ self.node_id,
202
+ self.serial_number,
203
+ self.position,
204
+ )
205
+
206
+
207
+ class Window(OpeningDevice):
208
+ """Window object."""
209
+
210
+ def __init__(
211
+ self,
212
+ pyvlx: "PyVLX",
213
+ node_id: int,
214
+ name: str,
215
+ serial_number: Optional[str],
216
+ position_parameter: Parameter = Parameter(),
217
+ rain_sensor: bool = False,
218
+ ):
219
+ """Initialize Window class.
220
+
221
+ Parameters:
222
+ * pyvlx: PyVLX object
223
+ * node_id: internal id for addressing nodes.
224
+ Provided by KLF 200 device
225
+ * name: node name
226
+ * serial_number: serial number of the node.
227
+ * position_parameter: initial position of the opening device.
228
+ * rain_sensor: set if device is equipped with a
229
+ rain sensor.
230
+
231
+ """
232
+ super().__init__(
233
+ pyvlx=pyvlx,
234
+ node_id=node_id,
235
+ name=name,
236
+ serial_number=serial_number,
237
+ position_parameter=position_parameter,
238
+ )
239
+ self.rain_sensor = rain_sensor
240
+
241
+ def __str__(self) -> str:
242
+ """Return object as readable string."""
243
+ return '<{} name="{}" node_id="{}" rain_sensor={} serial_number="{}" position="{}"/>'.format(
244
+ type(self).__name__,
245
+ self.name,
246
+ self.node_id,
247
+ self.rain_sensor,
248
+ self.serial_number,
249
+ self.position,
250
+ )
251
+
252
+ async def get_limitation(self) -> GetLimitation:
253
+ """Return limitation."""
254
+ get_limitation = GetLimitation(pyvlx=self.pyvlx, node_id=self.node_id)
255
+ await get_limitation.do_api_call()
256
+ if not get_limitation.success:
257
+ raise PyVLXException("Unable to send command")
258
+ return get_limitation
259
+
260
+
261
+ class Blind(OpeningDevice):
262
+ """Blind objects."""
263
+
264
+ def __init__(
265
+ self,
266
+ pyvlx: "PyVLX",
267
+ node_id: int,
268
+ name: str,
269
+ serial_number: Optional[str],
270
+ position_parameter: Parameter = Parameter(),
271
+ ):
272
+ """Initialize Blind class.
273
+
274
+ Parameters:
275
+ * pyvlx: PyVLX object
276
+ * node_id: internal id for addressing nodes.
277
+ Provided by KLF 200 device
278
+ * name: node name
279
+
280
+ """
281
+ super().__init__(
282
+ pyvlx=pyvlx,
283
+ node_id=node_id,
284
+ name=name,
285
+ serial_number=serial_number,
286
+ position_parameter=position_parameter,
287
+ )
288
+ self.orientation: Position = Position(position_percent=0)
289
+ self.target_orientation: Position = TargetPosition()
290
+ self.target_position: Position = TargetPosition()
291
+ self.open_orientation_target: int = 50
292
+ self.close_orientation_target: int = 100
293
+
294
+ async def set_position_and_orientation(
295
+ self,
296
+ position: Position,
297
+ wait_for_completion: bool = True,
298
+ velocity: Velocity | int | None = None,
299
+ orientation: Optional[Position] = None,
300
+ ) -> None:
301
+ """Set window to desired position.
302
+
303
+ Parameters:
304
+ * position: Position object containing the current position.
305
+ * velocity: Velocity to be used during transition.
306
+ * target_position: Position object holding the target position
307
+ which allows to adjust the position while the blind is in movement
308
+ without stopping the blind (if orientation position has been changed.)
309
+ * wait_for_completion: If set, function will return
310
+ after device has reached target position.
311
+ * orientation: If set, the orientation of the device will be set in the same request.
312
+ Note, that, if the position is set to 0, the orientation will be set to 0 too.
313
+
314
+ """
315
+ self.target_position = position
316
+ kwargs: Any = {}
317
+
318
+ if orientation is not None:
319
+ kwargs["fp3"] = orientation
320
+ elif self.target_position == Position(position_percent=0):
321
+ kwargs["fp3"] = Position(position_percent=0)
322
+ else:
323
+ kwargs["fp3"] = IgnorePosition()
324
+
325
+ if (
326
+ velocity is None or velocity is Velocity.DEFAULT
327
+ ) and self.use_default_velocity:
328
+ velocity = self.default_velocity
329
+
330
+ if isinstance(velocity, Velocity):
331
+ if velocity is not Velocity.DEFAULT:
332
+ if velocity is Velocity.SILENT:
333
+ # The above code is declaring a variable called `kwargs`.
334
+ kwargs["fp1"] = Parameter(raw=b"\x00\x00")
335
+ else:
336
+ kwargs["fp1"] = Parameter(raw=b"\xC8\x00")
337
+ elif isinstance(velocity, int):
338
+ kwargs["fp1"] = Position.from_percent(velocity)
339
+
340
+ command = CommandSend(
341
+ pyvlx=self.pyvlx,
342
+ node_id=self.node_id,
343
+ parameter=position,
344
+ wait_for_completion=wait_for_completion,
345
+ **kwargs
346
+ )
347
+ await command.send()
348
+ await self.after_update()
349
+
350
+ async def set_position(
351
+ self,
352
+ position: Position,
353
+ velocity: Velocity | int | None = Velocity.DEFAULT,
354
+ wait_for_completion: bool = True,
355
+ ) -> None:
356
+ """Set window to desired position.
357
+
358
+ Parameters:
359
+ * position: Position object containing the current position.
360
+ * velocity: Velocity to be used during transition.
361
+ * target_position: Position object holding the target position
362
+ which allows to adjust the position while the blind is in movement
363
+ without stopping the blind (if orientation position has been changed.)
364
+ * wait_for_completion: If set, function will return
365
+ after device has reached target position.
366
+ """
367
+ await self.set_position_and_orientation(
368
+ position=position,
369
+ wait_for_completion=wait_for_completion,
370
+ velocity=velocity,
371
+ )
372
+
373
+ async def open(
374
+ self,
375
+ velocity: Velocity | int | None = Velocity.DEFAULT,
376
+ wait_for_completion: bool = True,
377
+ ) -> None:
378
+ """Open window.
379
+
380
+ Parameters:
381
+ * velocity: Velocity to be used during transition.
382
+ * wait_for_completion: If set, function will return
383
+ after device has reached target position.
384
+ """
385
+ await self.set_position(
386
+ position=Position(position_percent=self.open_position_target),
387
+ velocity=velocity,
388
+ wait_for_completion=wait_for_completion,
389
+ )
390
+
391
+ async def close(
392
+ self,
393
+ velocity: Velocity | int | None = Velocity.DEFAULT,
394
+ wait_for_completion: bool = True,
395
+ ) -> None:
396
+ """Close window.
397
+
398
+ Parameters:
399
+ * velocity: Velocity to be used during transition.
400
+ * wait_for_completion: If set, function will return
401
+ after device has reached target position.
402
+ """
403
+ await self.set_position(
404
+ position=Position(position_percent=self.close_position_target),
405
+ velocity=velocity,
406
+ wait_for_completion=wait_for_completion,
407
+ )
408
+
409
+ async def stop(self, wait_for_completion: bool = True) -> None:
410
+ """Stop Blind position."""
411
+ await self.set_position_and_orientation(
412
+ position=CurrentPosition(),
413
+ wait_for_completion=wait_for_completion,
414
+ orientation=self.target_orientation,
415
+ )
416
+
417
+ async def set_orientation(
418
+ self, orientation: Position, wait_for_completion: bool = True
419
+ ) -> None:
420
+ """Set Blind shades to desired orientation.
421
+
422
+ Parameters:
423
+ * orientation: Position object containing the target orientation.
424
+ + target_orientation: Position object holding the target orientation
425
+ which allows to adjust the orientation while the blind is in movement
426
+ without stopping the blind (if the position has been changed.)
427
+ * wait_for_completion: If set, function will return
428
+ after device has reached target position.
429
+
430
+ """
431
+ self.target_orientation = orientation
432
+ self.orientation = orientation
433
+
434
+ fp3 = (
435
+ Position(position_percent=0)
436
+ if self.target_position == Position(position_percent=0)
437
+ else self.target_orientation
438
+ )
439
+
440
+ print("Orientation in device: %s " % (orientation))
441
+ command = CommandSend(
442
+ pyvlx=self.pyvlx,
443
+ wait_for_completion=wait_for_completion,
444
+ node_id=self.node_id,
445
+ parameter=self.target_position,
446
+ fp3=fp3,
447
+ )
448
+ await command.send()
449
+ await self.after_update()
450
+ # KLF200 always send UNKNOWN position for functional parameter,
451
+ # so orientation is set directly and not via GW_NODE_STATE_POSITION_CHANGED_NTF
452
+
453
+ async def open_orientation(self, wait_for_completion: bool = True) -> None:
454
+ """Open Blind slats orientation.
455
+
456
+ Blind slats with ±90° orientation are open at 50%
457
+ """
458
+ await self.set_orientation(
459
+ orientation=Position(position_percent=self.open_orientation_target),
460
+ wait_for_completion=wait_for_completion,
461
+ )
462
+
463
+ async def close_orientation(self, wait_for_completion: bool = True) -> None:
464
+ """Close Blind slats."""
465
+ await self.set_orientation(
466
+ orientation=Position(position_percent=self.close_orientation_target),
467
+ wait_for_completion=wait_for_completion,
468
+ )
469
+
470
+ async def stop_orientation(self, wait_for_completion: bool = True) -> None:
471
+ """Stop Blind slats."""
472
+ await self.set_orientation(
473
+ orientation=CurrentPosition(), wait_for_completion=wait_for_completion
474
+ )
475
+
476
+
477
+ class Awning(OpeningDevice):
478
+ """Awning objects."""
479
+
480
+
481
+ class DualRollerShutter(OpeningDevice):
482
+ """DualRollerShutter object."""
483
+
484
+ def __init__(
485
+ self,
486
+ pyvlx: "PyVLX",
487
+ node_id: int,
488
+ name: str,
489
+ serial_number: Optional[str],
490
+ position_parameter: Parameter = Parameter(),
491
+ ):
492
+ """Initialize Blind class.
493
+
494
+ Parameters:
495
+ * pyvlx: PyVLX object
496
+ * node_id: internal id for addressing nodes.
497
+ Provided by KLF 200 device
498
+ * name: node name
499
+
500
+ """
501
+ super().__init__(
502
+ pyvlx=pyvlx,
503
+ node_id=node_id,
504
+ name=name,
505
+ serial_number=serial_number,
506
+ position_parameter=position_parameter,
507
+ )
508
+ self.position_upper_curtain: Position = Position(position_percent=0)
509
+ self.position_lower_curtain: Position = Position(position_percent=0)
510
+ self.target_position: Any = Position()
511
+ self.active_parameter: int = 0
512
+
513
+ async def set_position(
514
+ self,
515
+ position: Position,
516
+ velocity: Velocity | int | None = Velocity.DEFAULT,
517
+ wait_for_completion: bool = True,
518
+ curtain: str = "dual",
519
+ ) -> None:
520
+ """Set DualRollerShutter to desired position.
521
+
522
+ Parameters:
523
+ * position: Position object containing the current position.
524
+ * target_position: Position object holding the target position
525
+ which allows to adjust the position while the blind is in movement
526
+ * wait_for_completion: If set, function will return
527
+ after device has reached target position.
528
+ """
529
+ kwargs: Any = {}
530
+
531
+ if curtain == "upper":
532
+ self.target_position = DualRollerShutterPosition()
533
+ self.active_parameter = 1
534
+ kwargs["fp1"] = position
535
+ kwargs["fp2"] = TargetPosition()
536
+ elif curtain == "lower":
537
+ self.target_position = DualRollerShutterPosition()
538
+ self.active_parameter = 2
539
+ kwargs["fp1"] = TargetPosition()
540
+ kwargs["fp2"] = position
541
+ else:
542
+ self.target_position = position
543
+ self.active_parameter = 0
544
+
545
+ if (
546
+ velocity is None or velocity is Velocity.DEFAULT
547
+ ) and self.use_default_velocity:
548
+ velocity = self.default_velocity
549
+
550
+ if isinstance(velocity, Velocity):
551
+ if velocity is not Velocity.DEFAULT:
552
+ if velocity is Velocity.SILENT:
553
+ kwargs["fp3"] = Parameter(raw=b"\x00\x00")
554
+ else:
555
+ kwargs["fp3"] = Parameter(raw=b"\xC8\x00")
556
+ elif isinstance(velocity, int):
557
+ kwargs["fp3"] = Position.from_percent(velocity)
558
+
559
+ command = CommandSend(
560
+ pyvlx=self.pyvlx,
561
+ wait_for_completion=wait_for_completion,
562
+ node_id=self.node_id,
563
+ parameter=self.target_position,
564
+ active_parameter=self.active_parameter,
565
+ **kwargs
566
+ )
567
+ await command.send()
568
+ if position.position <= Position.MAX:
569
+ if curtain == "upper":
570
+ self.position_upper_curtain = position
571
+ elif curtain == "lower":
572
+ self.position_lower_curtain = position
573
+ else:
574
+ self.position = position
575
+ await self.after_update()
576
+
577
+ async def open(
578
+ self,
579
+ velocity: Velocity | int | None = Velocity.DEFAULT,
580
+ wait_for_completion: bool = True,
581
+ curtain: str = "dual",
582
+ ) -> None:
583
+ """Open DualRollerShutter.
584
+
585
+ Parameters:
586
+ * wait_for_completion: If set, function will return
587
+ after device has reached target position.
588
+
589
+ """
590
+ await self.set_position(
591
+ position=Position(position_percent=self.open_position_target),
592
+ velocity=velocity,
593
+ wait_for_completion=wait_for_completion,
594
+ curtain=curtain,
595
+ )
596
+
597
+ async def close(
598
+ self,
599
+ velocity: Velocity | int | None = Velocity.DEFAULT,
600
+ wait_for_completion: bool = True,
601
+ curtain: str = "dual",
602
+ ) -> None:
603
+ """Close DualRollerShutter.
604
+
605
+ Parameters:
606
+ * wait_for_completion: If set, function will return
607
+ after device has reached target position.
608
+ """
609
+ await self.set_position(
610
+ position=Position(position_percent=self.close_position_target),
611
+ velocity=velocity,
612
+ wait_for_completion=wait_for_completion,
613
+ curtain=curtain,
614
+ )
615
+
616
+ async def stop(
617
+ self,
618
+ wait_for_completion: bool = True,
619
+ velocity: Velocity | int | None = Velocity.DEFAULT,
620
+ curtain: str = "dual",
621
+ ) -> None:
622
+ """Stop Blind position."""
623
+ await self.set_position(
624
+ position=CurrentPosition(),
625
+ velocity=velocity,
626
+ wait_for_completion=wait_for_completion,
627
+ curtain=curtain,
628
+ )
629
+
630
+
631
+ class RollerShutter(OpeningDevice):
632
+ """RollerShutter object."""
633
+
634
+
635
+ class GarageDoor(OpeningDevice):
636
+ """GarageDoor object."""
637
+
638
+
639
+ class Gate(OpeningDevice):
640
+ """Gate object."""
641
+
642
+
643
+ class Blade(OpeningDevice):
644
+ """Blade object."""