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.
- pyvlx/__init__.py +21 -0
- pyvlx/api/__init__.py +23 -0
- pyvlx/api/activate_scene.py +63 -0
- pyvlx/api/api_event.py +73 -0
- pyvlx/api/command_send.py +85 -0
- pyvlx/api/factory_default.py +34 -0
- pyvlx/api/frame_creation.py +202 -0
- pyvlx/api/frames/__init__.py +76 -0
- pyvlx/api/frames/alias_array.py +45 -0
- pyvlx/api/frames/frame.py +56 -0
- pyvlx/api/frames/frame_activate_scene.py +92 -0
- pyvlx/api/frames/frame_activation_log_updated.py +14 -0
- pyvlx/api/frames/frame_command_send.py +280 -0
- pyvlx/api/frames/frame_discover_nodes.py +64 -0
- pyvlx/api/frames/frame_error_notification.py +42 -0
- pyvlx/api/frames/frame_facory_default.py +32 -0
- pyvlx/api/frames/frame_get_all_nodes_information.py +218 -0
- pyvlx/api/frames/frame_get_limitation.py +127 -0
- pyvlx/api/frames/frame_get_local_time.py +38 -0
- pyvlx/api/frames/frame_get_network_setup.py +64 -0
- pyvlx/api/frames/frame_get_node_information.py +223 -0
- pyvlx/api/frames/frame_get_protocol_version.py +53 -0
- pyvlx/api/frames/frame_get_scene_list.py +82 -0
- pyvlx/api/frames/frame_get_state.py +47 -0
- pyvlx/api/frames/frame_get_version.py +72 -0
- pyvlx/api/frames/frame_helper.py +40 -0
- pyvlx/api/frames/frame_house_status_monitor_disable_cfm.py +14 -0
- pyvlx/api/frames/frame_house_status_monitor_disable_req.py +14 -0
- pyvlx/api/frames/frame_house_status_monitor_enable_cfm.py +14 -0
- pyvlx/api/frames/frame_house_status_monitor_enable_req.py +14 -0
- pyvlx/api/frames/frame_leave_learn_state.py +41 -0
- pyvlx/api/frames/frame_node_information_changed.py +57 -0
- pyvlx/api/frames/frame_node_state_position_changed_notification.py +84 -0
- pyvlx/api/frames/frame_password_change.py +114 -0
- pyvlx/api/frames/frame_password_enter.py +70 -0
- pyvlx/api/frames/frame_reboot.py +32 -0
- pyvlx/api/frames/frame_set_node_name.py +73 -0
- pyvlx/api/frames/frame_set_utc.py +45 -0
- pyvlx/api/frames/frame_status_request.py +212 -0
- pyvlx/api/get_all_nodes_information.py +46 -0
- pyvlx/api/get_limitation.py +64 -0
- pyvlx/api/get_local_time.py +34 -0
- pyvlx/api/get_network_setup.py +34 -0
- pyvlx/api/get_node_information.py +42 -0
- pyvlx/api/get_protocol_version.py +40 -0
- pyvlx/api/get_scene_list.py +49 -0
- pyvlx/api/get_state.py +43 -0
- pyvlx/api/get_version.py +34 -0
- pyvlx/api/house_status_monitor.py +52 -0
- pyvlx/api/leave_learn_state.py +33 -0
- pyvlx/api/password_enter.py +39 -0
- pyvlx/api/reboot.py +33 -0
- pyvlx/api/session_id.py +20 -0
- pyvlx/api/set_node_name.py +32 -0
- pyvlx/api/set_utc.py +31 -0
- pyvlx/api/status_request.py +48 -0
- pyvlx/config.py +54 -0
- pyvlx/connection.py +182 -0
- pyvlx/const.py +685 -0
- pyvlx/dataobjects.py +161 -0
- pyvlx/discovery.py +100 -0
- pyvlx/exception.py +26 -0
- pyvlx/heartbeat.py +79 -0
- pyvlx/klf200gateway.py +167 -0
- pyvlx/lightening_device.py +102 -0
- pyvlx/log.py +4 -0
- pyvlx/node.py +74 -0
- pyvlx/node_helper.py +165 -0
- pyvlx/node_updater.py +162 -0
- pyvlx/nodes.py +99 -0
- pyvlx/on_off_switch.py +44 -0
- pyvlx/opening_device.py +644 -0
- pyvlx/parameter.py +357 -0
- pyvlx/py.typed +0 -0
- pyvlx/pyvlx.py +124 -0
- pyvlx/scene.py +53 -0
- pyvlx/scenes.py +60 -0
- pyvlx/slip.py +48 -0
- pyvlx/string_helper.py +20 -0
- pyvlx-0.2.27.dist-info/METADATA +122 -0
- pyvlx-0.2.27.dist-info/RECORD +84 -0
- pyvlx-0.2.27.dist-info/WHEEL +5 -0
- pyvlx-0.2.27.dist-info/licenses/LICENSE +165 -0
- 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()
|