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
pyvlx/node.py ADDED
@@ -0,0 +1,74 @@
1
+ """
2
+ Module for basic object for nodes.
3
+
4
+ Node object is an interface class and should
5
+ be derived by other objects like window openers
6
+ and roller shutters.
7
+ """
8
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, Optional
9
+
10
+ from .api import SetNodeName
11
+ from .exception import PyVLXException
12
+
13
+ if TYPE_CHECKING:
14
+ from pyvlx import PyVLX
15
+
16
+ CallbackType = Callable[["Node"], Awaitable[None]]
17
+
18
+
19
+ class Node:
20
+ """Class for node abstraction."""
21
+
22
+ def __init__(self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: Optional[str]):
23
+ """Initialize Node object."""
24
+ self.pyvlx = pyvlx
25
+ self.node_id = node_id
26
+ self.name = name
27
+ self.serial_number = serial_number
28
+ self.device_updated_cbs: List[CallbackType] = []
29
+ self.pyvlx.connection.register_connection_opened_cb(self.after_update)
30
+ self.pyvlx.connection.register_connection_closed_cb(self.after_update)
31
+
32
+ def register_device_updated_cb(self, device_updated_cb: CallbackType) -> None:
33
+ """Register device updated callback."""
34
+ self.device_updated_cbs.append(device_updated_cb)
35
+
36
+ def unregister_device_updated_cb(self, device_updated_cb: CallbackType) -> None:
37
+ """Unregister device updated callback."""
38
+ self.device_updated_cbs.remove(device_updated_cb)
39
+
40
+ async def after_update(self) -> None:
41
+ """Execute callbacks after internal state has been changed."""
42
+ for device_updated_cb in self.device_updated_cbs:
43
+ # pylint: disable=not-callable
44
+ await self.pyvlx.loop.create_task(device_updated_cb(self)) # type: ignore
45
+
46
+ async def rename(self, name: str) -> None:
47
+ """Change name of node."""
48
+ set_node_name = SetNodeName(pyvlx=self.pyvlx, node_id=self.node_id, name=name)
49
+ await set_node_name.do_api_call()
50
+ if not set_node_name.success:
51
+ raise PyVLXException("Unable to rename node")
52
+ self.name = name
53
+
54
+ @property
55
+ def is_available(self) -> bool:
56
+ """Return True if node is available."""
57
+ return self.pyvlx.get_connected()
58
+
59
+ def __str__(self) -> str:
60
+ """Return object as readable string."""
61
+ return (
62
+ '<{} name="{}" '
63
+ 'node_id="{}" '
64
+ 'serial_number="{}"/>'.format(
65
+ type(self).__name__, self.name, self.node_id, self.serial_number
66
+ )
67
+ )
68
+
69
+ def __eq__(self, other: Any) -> bool:
70
+ """Equal operator."""
71
+ return (
72
+ type(self).__name__ == type(other).__name__
73
+ and self.__dict__ == other.__dict__
74
+ )
pyvlx/node_helper.py ADDED
@@ -0,0 +1,165 @@
1
+ """Helper module for Node objects."""
2
+ from typing import TYPE_CHECKING, Optional, Union
3
+
4
+ from .api.frames import (
5
+ FrameGetAllNodesInformationNotification,
6
+ FrameGetNodeInformationNotification)
7
+ from .const import NodeTypeWithSubtype
8
+ from .lightening_device import Light
9
+ from .log import PYVLXLOG
10
+ from .node import Node
11
+ from .on_off_switch import OnOffSwitch
12
+ from .opening_device import (
13
+ Awning, Blade, Blind, DualRollerShutter, GarageDoor, Gate, RollerShutter,
14
+ Window)
15
+
16
+ if TYPE_CHECKING:
17
+ from pyvlx import PyVLX
18
+
19
+
20
+ def convert_frame_to_node(
21
+ pyvlx: "PyVLX",
22
+ frame: Union[
23
+ FrameGetNodeInformationNotification, FrameGetAllNodesInformationNotification
24
+ ],
25
+ ) -> Optional[Node]:
26
+ """Convert FrameGet[All]Node[s]InformationNotification into Node object."""
27
+ # pylint: disable=too-many-return-statements
28
+
29
+ if frame.node_type == NodeTypeWithSubtype.WINDOW_OPENER:
30
+ return Window(
31
+ pyvlx=pyvlx,
32
+ node_id=frame.node_id,
33
+ name=frame.name,
34
+ serial_number=frame.serial_number,
35
+ position_parameter=frame.current_position,
36
+ rain_sensor=False,
37
+ )
38
+
39
+ if frame.node_type == NodeTypeWithSubtype.WINDOW_OPENER_WITH_RAIN_SENSOR:
40
+ return Window(
41
+ pyvlx=pyvlx,
42
+ node_id=frame.node_id,
43
+ name=frame.name,
44
+ serial_number=frame.serial_number,
45
+ position_parameter=frame.current_position,
46
+ rain_sensor=True,
47
+ )
48
+
49
+ if frame.node_type == NodeTypeWithSubtype.DUAL_ROLLER_SHUTTER:
50
+ return DualRollerShutter(
51
+ pyvlx=pyvlx,
52
+ node_id=frame.node_id,
53
+ name=frame.name,
54
+ serial_number=frame.serial_number,
55
+ position_parameter=frame.current_position,
56
+ )
57
+
58
+ if frame.node_type in [
59
+ NodeTypeWithSubtype.ROLLER_SHUTTER,
60
+ NodeTypeWithSubtype.SWINGING_SHUTTERS,
61
+ ]:
62
+ return RollerShutter(
63
+ pyvlx=pyvlx,
64
+ node_id=frame.node_id,
65
+ name=frame.name,
66
+ serial_number=frame.serial_number,
67
+ position_parameter=frame.current_position,
68
+ )
69
+
70
+ if frame.node_type in [
71
+ NodeTypeWithSubtype.INTERIOR_VENETIAN_BLIND,
72
+ NodeTypeWithSubtype.VERTICAL_INTERIOR_BLINDS,
73
+ NodeTypeWithSubtype.INTERIOR_VENETIAN_BLIND,
74
+ ]:
75
+ return RollerShutter(
76
+ pyvlx=pyvlx,
77
+ node_id=frame.node_id,
78
+ name=frame.name,
79
+ serial_number=frame.serial_number,
80
+ )
81
+
82
+ # Blinds have position and orientation (inherit frame.current_position_fp3) attribute
83
+ if frame.node_type in [
84
+ NodeTypeWithSubtype.EXTERIOR_VENETIAN_BLIND,
85
+ NodeTypeWithSubtype.ADJUSTABLE_SLUTS_ROLLING_SHUTTER,
86
+ NodeTypeWithSubtype.LOUVER_BLIND,
87
+ ]:
88
+ return Blind(
89
+ pyvlx=pyvlx,
90
+ node_id=frame.node_id,
91
+ name=frame.name,
92
+ serial_number=frame.serial_number,
93
+ position_parameter=frame.current_position,
94
+ )
95
+
96
+ if frame.node_type in [
97
+ NodeTypeWithSubtype.VERTICAL_EXTERIOR_AWNING,
98
+ NodeTypeWithSubtype.HORIZONTAL_AWNING,
99
+ NodeTypeWithSubtype.HORIZONTAL_AWNING_ALT,
100
+ ]:
101
+ return Awning(
102
+ pyvlx=pyvlx,
103
+ node_id=frame.node_id,
104
+ name=frame.name,
105
+ serial_number=frame.serial_number,
106
+ position_parameter=frame.current_position,
107
+ )
108
+
109
+ if frame.node_type == NodeTypeWithSubtype.ON_OFF_SWITCH:
110
+ return OnOffSwitch(
111
+ pyvlx=pyvlx,
112
+ node_id=frame.node_id,
113
+ name=frame.name,
114
+ serial_number=frame.serial_number,
115
+ )
116
+
117
+ if frame.node_type in [
118
+ NodeTypeWithSubtype.GARAGE_DOOR_OPENER,
119
+ NodeTypeWithSubtype.LINAR_ANGULAR_POSITION_OF_GARAGE_DOOR,
120
+ ]:
121
+ return GarageDoor(
122
+ pyvlx=pyvlx,
123
+ node_id=frame.node_id,
124
+ name=frame.name,
125
+ serial_number=frame.serial_number,
126
+ position_parameter=frame.current_position,
127
+ )
128
+
129
+ if frame.node_type == NodeTypeWithSubtype.GATE_OPENER:
130
+ return Gate(
131
+ pyvlx=pyvlx,
132
+ node_id=frame.node_id,
133
+ name=frame.name,
134
+ serial_number=frame.serial_number,
135
+ position_parameter=frame.current_position,
136
+ )
137
+
138
+ if frame.node_type == NodeTypeWithSubtype.GATE_OPENER_ANGULAR_POSITION:
139
+ return Gate(
140
+ pyvlx=pyvlx,
141
+ node_id=frame.node_id,
142
+ name=frame.name,
143
+ serial_number=frame.serial_number,
144
+ position_parameter=frame.current_position,
145
+ )
146
+
147
+ if frame.node_type == NodeTypeWithSubtype.BLADE_OPENER:
148
+ return Blade(
149
+ pyvlx=pyvlx,
150
+ node_id=frame.node_id,
151
+ name=frame.name,
152
+ serial_number=frame.serial_number,
153
+ position_parameter=frame.current_position,
154
+ )
155
+
156
+ if frame.node_type in [NodeTypeWithSubtype.LIGHT, NodeTypeWithSubtype.LIGHT_ON_OFF]:
157
+ return Light(
158
+ pyvlx=pyvlx,
159
+ node_id=frame.node_id,
160
+ name=frame.name,
161
+ serial_number=frame.serial_number,
162
+ )
163
+
164
+ PYVLXLOG.warning("%s not implemented", frame.node_type)
165
+ return None
pyvlx/node_updater.py ADDED
@@ -0,0 +1,162 @@
1
+ """Module for updating nodes via frames."""
2
+ import datetime
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from .api.frames import (
6
+ FrameBase, FrameGetAllNodesInformationNotification,
7
+ FrameNodeStatePositionChangedNotification, FrameStatusRequestNotification)
8
+ from .const import NodeParameter, OperatingState
9
+ from .lightening_device import LighteningDevice
10
+ from .log import PYVLXLOG
11
+ from .on_off_switch import OnOffSwitch
12
+ from .opening_device import Blind, DualRollerShutter, OpeningDevice
13
+ from .parameter import Intensity, Parameter, Position, SwitchParameter
14
+
15
+ if TYPE_CHECKING:
16
+ from pyvlx import PyVLX
17
+
18
+
19
+ class NodeUpdater:
20
+ """Class for updating nodes via incoming frames, usually received by house monitor."""
21
+
22
+ def __init__(self, pyvlx: "PyVLX"):
23
+ """Initialize NodeUpdater object."""
24
+ self.pyvlx = pyvlx
25
+
26
+ async def process_frame_status_request_notification(
27
+ self, frame: FrameStatusRequestNotification
28
+ ) -> None:
29
+ """Process FrameStatusRequestNotification."""
30
+ PYVLXLOG.debug("NodeUpdater process frame: %s", frame)
31
+ if frame.node_id not in self.pyvlx.nodes:
32
+ return
33
+ node = self.pyvlx.nodes[frame.node_id]
34
+ if isinstance(node, Blind):
35
+ if NodeParameter(0) not in frame.parameter_data: # MP missing in frame
36
+ return
37
+ if NodeParameter(3) not in frame.parameter_data: # FP3 missing in frame
38
+ return
39
+ position = Position(frame.parameter_data[NodeParameter(0)])
40
+ orientation = Position(frame.parameter_data[NodeParameter(3)])
41
+ if position.position <= Parameter.MAX:
42
+ node.position = position
43
+ PYVLXLOG.debug("%s position changed to: %s", node.name, position)
44
+ if orientation.position <= Parameter.MAX:
45
+ node.orientation = orientation
46
+ PYVLXLOG.debug("%s orientation changed to: %s", node.name, orientation)
47
+ await node.after_update()
48
+
49
+ if isinstance(node, DualRollerShutter):
50
+ if NodeParameter(0) not in frame.parameter_data: # MP missing in frame
51
+ return
52
+ if NodeParameter(1) not in frame.parameter_data: # FP1 missing in frame
53
+ return
54
+ if NodeParameter(2) not in frame.parameter_data: # FP2 missing in frame
55
+ return
56
+ position = Position(frame.parameter_data[NodeParameter(0)])
57
+ position_upper_curtain = Position(frame.parameter_data[NodeParameter(1)])
58
+ position_lower_curtain = Position(frame.parameter_data[NodeParameter(2)])
59
+ if position.position <= Parameter.MAX:
60
+ node.position = position
61
+ PYVLXLOG.debug("%s position changed to: %s", node.name, position)
62
+ if position_upper_curtain.position <= Parameter.MAX:
63
+ node.position_upper_curtain = position_upper_curtain
64
+ PYVLXLOG.debug(
65
+ "%s position upper curtain changed to: %s",
66
+ node.name,
67
+ position_upper_curtain,
68
+ )
69
+ if position_lower_curtain.position <= Parameter.MAX:
70
+ node.position_lower_curtain = position_lower_curtain
71
+ PYVLXLOG.debug(
72
+ "%s position lower curtain changed to: %s",
73
+ node.name,
74
+ position_lower_curtain,
75
+ )
76
+ await node.after_update()
77
+
78
+ async def process_frame(self, frame: FrameBase) -> None:
79
+ """Update nodes via frame, usually received by house monitor."""
80
+ if isinstance(
81
+ frame,
82
+ (
83
+ FrameGetAllNodesInformationNotification,
84
+ FrameNodeStatePositionChangedNotification,
85
+ ),
86
+ ):
87
+ PYVLXLOG.debug("NodeUpdater process frame: %s", frame)
88
+ if frame.node_id not in self.pyvlx.nodes:
89
+ return
90
+ node = self.pyvlx.nodes[frame.node_id]
91
+ position = Position(frame.current_position)
92
+ target: Any = Position(frame.target)
93
+ # KLF transmits for functional parameters basically always 'No feed-back value known’ (0xF7FF).
94
+ # In home assistant this cause unreasonable values like -23%. Therefore a check is implemented
95
+ # whether the frame parameter is inside the maximum range.
96
+
97
+ # Set opening device status
98
+ if isinstance(node, OpeningDevice):
99
+ if (position.position > target.position <= Parameter.MAX) and (
100
+ (frame.state == OperatingState.EXECUTING)
101
+ or frame.remaining_time > 0
102
+ ):
103
+ node.is_opening = True
104
+ PYVLXLOG.debug("%s is opening", node.name)
105
+ node.state_received_at = datetime.datetime.now()
106
+ node.estimated_completion = (
107
+ node.state_received_at
108
+ + datetime.timedelta(0, frame.remaining_time)
109
+ )
110
+ PYVLXLOG.debug(
111
+ "%s will be opening until", node.estimated_completion
112
+ )
113
+ elif (position.position < target.position <= Parameter.MAX) and (
114
+ (frame.state == OperatingState.EXECUTING)
115
+ or frame.remaining_time > 0
116
+ ):
117
+ node.is_closing = True
118
+ PYVLXLOG.debug("%s is closing", node.name)
119
+ node.state_received_at = datetime.datetime.now()
120
+ node.estimated_completion = (
121
+ node.state_received_at
122
+ + datetime.timedelta(0, frame.remaining_time)
123
+ )
124
+ PYVLXLOG.debug(
125
+ "%s will be closing until", node.estimated_completion
126
+ )
127
+ else:
128
+ if node.is_opening:
129
+ node.is_opening = False
130
+ node.state_received_at = None
131
+ node.estimated_completion = None
132
+ PYVLXLOG.debug("%s stops opening", node.name)
133
+ if node.is_closing:
134
+ node.is_closing = False
135
+ PYVLXLOG.debug("%s stops closing", node.name)
136
+
137
+ # Set main parameter
138
+ if isinstance(node, OpeningDevice):
139
+ if position.position <= Parameter.MAX:
140
+ node.position = position
141
+ node.target = target
142
+ PYVLXLOG.debug("%s position changed to: %s", node.name, position)
143
+ await node.after_update()
144
+ elif isinstance(node, LighteningDevice):
145
+ intensity = Intensity(frame.current_position)
146
+ if intensity.intensity <= Parameter.MAX:
147
+ node.intensity = intensity
148
+ PYVLXLOG.debug("%s intensity changed to: %s", node.name, intensity)
149
+ await node.after_update()
150
+ elif isinstance(node, OnOffSwitch):
151
+ state = SwitchParameter(frame.current_position)
152
+ target = SwitchParameter(frame.target)
153
+ if state.state == target.state:
154
+ if state.state == Parameter.ON:
155
+ node.parameter = state
156
+ PYVLXLOG.debug("%s state changed to: %s", node.name, state)
157
+ elif state.state == Parameter.OFF:
158
+ node.parameter = state
159
+ PYVLXLOG.debug("%s state changed to: %s", node.name, state)
160
+ await node.after_update()
161
+ elif isinstance(frame, FrameStatusRequestNotification):
162
+ await self.process_frame_status_request_notification(frame)
pyvlx/nodes.py ADDED
@@ -0,0 +1,99 @@
1
+ """Module for storing nodes."""
2
+ from typing import TYPE_CHECKING, Iterator, List, Optional, Union
3
+
4
+ from .api import GetAllNodesInformation, GetNodeInformation
5
+ from .exception import PyVLXException
6
+ from .node import Node
7
+ from .node_helper import convert_frame_to_node
8
+
9
+ if TYPE_CHECKING:
10
+ from pyvlx import PyVLX
11
+
12
+
13
+ class Nodes:
14
+ """Object for storing node objects."""
15
+
16
+ def __init__(self, pyvlx: "PyVLX"):
17
+ """Initialize Nodes object."""
18
+ self.pyvlx = pyvlx
19
+ self.__nodes: List[Node] = []
20
+
21
+ def __iter__(self) -> Iterator[Node]:
22
+ """Iterate."""
23
+ yield from self.__nodes
24
+
25
+ def __getitem__(self, key: Union[str, int]) -> Node:
26
+ """Return node by name or by index."""
27
+ if isinstance(key, int):
28
+ for node in self.__nodes:
29
+ if node.node_id == key:
30
+ return node
31
+ for node in self.__nodes:
32
+ if node.name == key:
33
+ return node
34
+ raise KeyError
35
+
36
+ def __contains__(self, key: Union[str, int, Node]) -> bool:
37
+ """Check if key is in index."""
38
+ if isinstance(key, int):
39
+ for node in self.__nodes:
40
+ if node.node_id == key:
41
+ return True
42
+ if isinstance(key, Node):
43
+ for node in self.__nodes:
44
+ if node == key:
45
+ return True
46
+ for node in self.__nodes:
47
+ if node.name == key:
48
+ return True
49
+ return False
50
+
51
+ def __len__(self) -> int:
52
+ """Return number of nodes."""
53
+ return len(self.__nodes)
54
+
55
+ def add(self, node: Node) -> None:
56
+ """Add Node, replace existing node if node with node_id is present."""
57
+ if not isinstance(node, Node):
58
+ raise TypeError()
59
+ for i, j in enumerate(self.__nodes):
60
+ if j.node_id == node.node_id:
61
+ self.__nodes[i] = node
62
+ return
63
+ self.__nodes.append(node)
64
+
65
+ def clear(self) -> None:
66
+ """Clear internal node array."""
67
+ self.__nodes = []
68
+
69
+ async def load(self, node_id: Optional[int] = None) -> None:
70
+ """Load nodes from KLF 200, if no node_id is specified all nodes are loaded."""
71
+ if node_id is not None:
72
+ await self._load_node(node_id=node_id)
73
+ else:
74
+ await self._load_all_nodes()
75
+
76
+ async def _load_node(self, node_id: int) -> None:
77
+ """Load single node via API."""
78
+ get_node_information = GetNodeInformation(pyvlx=self.pyvlx, node_id=node_id)
79
+ await get_node_information.do_api_call()
80
+ if not get_node_information.success:
81
+ raise PyVLXException("Unable to retrieve node information")
82
+ notification_frame = get_node_information.notification_frame
83
+ if notification_frame is None:
84
+ return
85
+ node = convert_frame_to_node(self.pyvlx, notification_frame)
86
+ if node is not None:
87
+ self.add(node)
88
+
89
+ async def _load_all_nodes(self) -> None:
90
+ """Load all nodes via API."""
91
+ get_all_nodes_information = GetAllNodesInformation(pyvlx=self.pyvlx)
92
+ await get_all_nodes_information.do_api_call()
93
+ if not get_all_nodes_information.success:
94
+ raise PyVLXException("Unable to retrieve node information")
95
+ self.clear()
96
+ for notification_frame in get_all_nodes_information.notification_frames:
97
+ node = convert_frame_to_node(self.pyvlx, notification_frame)
98
+ if node is not None:
99
+ self.add(node)
pyvlx/on_off_switch.py ADDED
@@ -0,0 +1,44 @@
1
+ """Module for on/off switches."""
2
+ from typing import TYPE_CHECKING, Optional
3
+
4
+ from .api.command_send import CommandSend
5
+ from .node import Node
6
+ from .parameter import SwitchParameter, SwitchParameterOff, SwitchParameterOn
7
+
8
+ if TYPE_CHECKING:
9
+ from pyvlx import PyVLX
10
+
11
+
12
+ class OnOffSwitch(Node):
13
+ """Class for controlling on-off switches."""
14
+
15
+ def __init__(self, pyvlx: "PyVLX", node_id: int, name: str, serial_number: Optional[str]):
16
+ """Initialize opening device."""
17
+ super().__init__(
18
+ pyvlx=pyvlx, node_id=node_id, name=name, serial_number=serial_number
19
+ )
20
+ self.parameter = SwitchParameter()
21
+
22
+ async def set_state(self, parameter: SwitchParameter) -> None:
23
+ """Set switch to desired state."""
24
+ command = CommandSend(
25
+ pyvlx=self.pyvlx, node_id=self.node_id, parameter=parameter
26
+ )
27
+ await command.send()
28
+ await self.after_update()
29
+
30
+ async def set_on(self) -> None:
31
+ """Set switch on."""
32
+ await self.set_state(SwitchParameterOn())
33
+
34
+ async def set_off(self) -> None:
35
+ """Set switch off."""
36
+ await self.set_state(SwitchParameterOff())
37
+
38
+ def is_on(self) -> bool:
39
+ """Return if switch is set to on."""
40
+ return self.parameter.is_on()
41
+
42
+ def is_off(self) -> bool:
43
+ """Return if switch is set to off."""
44
+ return self.parameter.is_off()