zencontrol-python 0.1.0__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.
@@ -0,0 +1,203 @@
1
+ """
2
+ API-level type definitions.
3
+
4
+ This module contains types and enums that belong to the API layer:
5
+ - DALI address types, instance types, color types
6
+ - Event masks and modes used by the TPI protocol
7
+ - Constants used by the API layer
8
+ """
9
+
10
+ from enum import Enum
11
+ from typing import Optional, Self
12
+ from dataclasses import dataclass
13
+
14
+
15
+ class ZenAddressType(Enum):
16
+ BROADCAST = 0
17
+ ECG = 1 # Control Gear
18
+ ECD = 2 # Control Device
19
+ GROUP = 3
20
+
21
+
22
+ class ZenInstanceType(Enum):
23
+ PUSH_BUTTON = 0x01
24
+ ABSOLUTE_INPUT = 0x02
25
+ OCCUPANCY_SENSOR = 0x03
26
+ LIGHT_SENSOR = 0x04
27
+ GENERAL_SENSOR = 0x06
28
+
29
+
30
+ class ZenColourType(Enum):
31
+ XY = 0x10
32
+ TC = 0x20 # Tunable White
33
+ RGBWAF = 0x80
34
+
35
+
36
+ class ZenErrorCode(Enum):
37
+ CHECKSUM = 0x01
38
+ SHORT_CIRCUIT = 0x02
39
+ RECEIVE_ERROR = 0x03
40
+ UNKNOWN_CMD = 0x04
41
+ PAID_FEATURE = 0xB0
42
+ INVALID_ARGS = 0xB1
43
+ CMD_REFUSED = 0xB2
44
+ QUEUE_FAILURE = 0xB3
45
+ RESPONSE_UNAVAIL = 0xB4
46
+ OTHER_DALI_ERROR = 0xB5
47
+ MAX_LIMIT = 0xB6
48
+ UNEXPECTED_RESULT = 0xB7
49
+ UNKNOWN_TARGET = 0xB8
50
+
51
+
52
+ @dataclass
53
+ class ZenEventMode:
54
+ enabled: bool = False
55
+ filtering: bool = False
56
+ unicast: bool = False
57
+ multicast: bool = False
58
+
59
+ def bitmask(self) -> int:
60
+ mode_flag = 0x00
61
+ if self.enabled: mode_flag |= 0x01
62
+ if self.filtering: mode_flag |= 0x02
63
+ if self.unicast: mode_flag |= 0x40
64
+ if not self.multicast: mode_flag |= 0x80
65
+ return mode_flag
66
+
67
+ @classmethod
68
+ def from_byte(cls, mode_flag: int) -> Self:
69
+ return cls(
70
+ enabled = (mode_flag & 0x01) != 0,
71
+ filtering = (mode_flag & 0x02) != 0,
72
+ unicast = (mode_flag & 0x40) != 0,
73
+ multicast = (mode_flag & 0x80) == 0
74
+ )
75
+
76
+
77
+ class ZenEventCode(Enum):
78
+ BUTTON_PRESS = 0x00
79
+ BUTTON_HOLD = 0x01
80
+ ABSOLUTE_INPUT = 0x02
81
+ LEVEL_CHANGE = 0x03
82
+ GROUP_LEVEL_CHANGE = 0x04
83
+ SCENE_CHANGE = 0x05
84
+ IS_OCCUPIED = 0x06
85
+ SYSTEM_VARIABLE_CHANGE = 0x07
86
+ COLOUR_CHANGE = 0x08
87
+ PROFILE_CHANGE = 0x09
88
+ GROUP_OCCUPIED = 0x0A
89
+ LEVEL_CHANGE_V2 = 0x0B
90
+
91
+
92
+ @dataclass
93
+ class ZenEventMask:
94
+ button_press: bool = False
95
+ button_hold: bool = False
96
+ absolute_input: bool = False
97
+ level_change: bool = False
98
+ group_level_change: bool = False
99
+ scene_change: bool = False
100
+ is_occupied: bool = False
101
+ system_variable_change: bool = False
102
+ colour_change: bool = False
103
+ profile_change: bool = False
104
+ group_occupied: bool = False
105
+ level_change_v2: bool = False
106
+
107
+ @classmethod
108
+ def all_events(cls):
109
+ # Exclude deprecated level_change / group_level_change — use level_change_v2 (spec V2.001.121+)
110
+ return cls(
111
+ button_press = True,
112
+ button_hold = True,
113
+ absolute_input = True,
114
+ scene_change = True,
115
+ is_occupied = True,
116
+ system_variable_change = True,
117
+ colour_change = True,
118
+ profile_change = True,
119
+ group_occupied = True,
120
+ level_change_v2 = True
121
+ )
122
+
123
+ @classmethod
124
+ def from_upper_lower(cls, upper: int, lower: int) -> Self:
125
+ return cls.from_double_byte((upper << 8) | lower)
126
+
127
+ @classmethod
128
+ def from_double_byte(cls, event_mask: int) -> Self:
129
+ return cls(
130
+ button_press = (event_mask & (1 << ZenEventCode.BUTTON_PRESS.value)) != 0,
131
+ button_hold = (event_mask & (1 << ZenEventCode.BUTTON_HOLD.value)) != 0,
132
+ absolute_input = (event_mask & (1 << ZenEventCode.ABSOLUTE_INPUT.value)) != 0,
133
+ level_change = (event_mask & (1 << ZenEventCode.LEVEL_CHANGE.value)) != 0,
134
+ group_level_change = (event_mask & (1 << ZenEventCode.GROUP_LEVEL_CHANGE.value)) != 0,
135
+ scene_change = (event_mask & (1 << ZenEventCode.SCENE_CHANGE.value)) != 0,
136
+ is_occupied = (event_mask & (1 << ZenEventCode.IS_OCCUPIED.value)) != 0,
137
+ system_variable_change = (event_mask & (1 << ZenEventCode.SYSTEM_VARIABLE_CHANGE.value)) != 0,
138
+ colour_change = (event_mask & (1 << ZenEventCode.COLOUR_CHANGE.value)) != 0,
139
+ profile_change = (event_mask & (1 << ZenEventCode.PROFILE_CHANGE.value)) != 0,
140
+ group_occupied = (event_mask & (1 << ZenEventCode.GROUP_OCCUPIED.value)) != 0,
141
+ level_change_v2 = (event_mask & (1 << ZenEventCode.LEVEL_CHANGE_V2.value)) != 0
142
+ )
143
+
144
+ def bitmask(self) -> int:
145
+ event_mask = 0x00
146
+ if self.button_press: event_mask |= (1 << ZenEventCode.BUTTON_PRESS.value)
147
+ if self.button_hold: event_mask |= (1 << ZenEventCode.BUTTON_HOLD.value)
148
+ if self.absolute_input: event_mask |= (1 << ZenEventCode.ABSOLUTE_INPUT.value)
149
+ if self.level_change: event_mask |= (1 << ZenEventCode.LEVEL_CHANGE.value)
150
+ if self.group_level_change: event_mask |= (1 << ZenEventCode.GROUP_LEVEL_CHANGE.value)
151
+ if self.scene_change: event_mask |= (1 << ZenEventCode.SCENE_CHANGE.value)
152
+ if self.is_occupied: event_mask |= (1 << ZenEventCode.IS_OCCUPIED.value)
153
+ if self.system_variable_change: event_mask |= (1 << ZenEventCode.SYSTEM_VARIABLE_CHANGE.value)
154
+ if self.colour_change: event_mask |= (1 << ZenEventCode.COLOUR_CHANGE.value)
155
+ if self.profile_change: event_mask |= (1 << ZenEventCode.PROFILE_CHANGE.value)
156
+ if self.group_occupied: event_mask |= (1 << ZenEventCode.GROUP_OCCUPIED.value)
157
+ if self.level_change_v2: event_mask |= (1 << ZenEventCode.LEVEL_CHANGE_V2.value)
158
+ return event_mask
159
+
160
+ def upper(self) -> int:
161
+ return (self.bitmask() >> 8) & 0xFF
162
+
163
+ def lower(self) -> int:
164
+ return self.bitmask() & 0xFF
165
+
166
+
167
+ # API-level constants
168
+ class Const:
169
+ """API-level constants"""
170
+ # UDP protocol - use zen_protocol constants
171
+ RESPONSE_TIMEOUT = 3.0 # Default timeout from ClientConst
172
+
173
+ # DALI limits
174
+ MAX_ECG = 64 # 0-63
175
+ MAX_ECD = 64 # 0-63
176
+ MAX_INSTANCE = 32 # 0-31
177
+ MAX_GROUP = 16 # 0-15
178
+ MAX_SCENE = 12 # 0-11
179
+ MAX_SYSVAR = 148 # 0-147
180
+ MAX_LEVEL = 254 # 255 is mask value (i.e. no change)
181
+ MIN_KELVIN = 1000
182
+ MAX_KELVIN = 20000
183
+
184
+ # Color temperature defaults (only used if query_dali_colour_temp_limits fails)
185
+ DEFAULT_WARMEST_TEMP = 2700
186
+ DEFAULT_COOLEST_TEMP = 6500
187
+
188
+ # RGB channel counts
189
+ RGB_CHANNELS = 3
190
+ RGBW_CHANNELS = 4
191
+ RGBWW_CHANNELS = 5
192
+
193
+ # Button press constants
194
+ LONG_PRESS_COUNT = 2
195
+ DEFAULT_HOLD_TIME = 60
196
+
197
+ # Cache
198
+ CACHE_TIMEOUT = 1*60*60 # 1 hour
199
+
200
+ # Event-listener reconnect (ZenControl supervisor)
201
+ RECONNECT_MIN_DELAY = 1.0
202
+ RECONNECT_MAX_DELAY = 30.0
203
+ RECONNECT_HEALTHY_SECONDS = 60.0
@@ -0,0 +1,30 @@
1
+ """
2
+ ZenControl library exceptions.
3
+
4
+ This module defines all custom exceptions used throughout the library.
5
+ """
6
+
7
+
8
+ class ZenError(Exception):
9
+ """Base exception for Zen protocol errors"""
10
+ pass
11
+
12
+
13
+ class ZenTimeoutError(ZenError):
14
+ """Raised when a command times out"""
15
+ pass
16
+
17
+
18
+ class ZenResponseError(ZenError):
19
+ """Raised when receiving an invalid response"""
20
+ pass
21
+
22
+
23
+ class ZenConnectionError(ZenError):
24
+ """Raised when connection to controller fails"""
25
+ pass
26
+
27
+
28
+ class ZenConfigurationError(ZenError):
29
+ """Raised when configuration is invalid"""
30
+ pass
@@ -0,0 +1,33 @@
1
+ """
2
+ High-level interface models and client.
3
+
4
+ This module contains models that belong to the zen_interface layer:
5
+ - ZenControl (main client for high-level usage)
6
+ - ZenLight, ZenGroup, ZenButton, etc. (high-level Pythonic objects)
7
+ - Business logic and convenience methods
8
+ """
9
+
10
+ from .interface import (
11
+ ZenControl,
12
+ ZenController,
13
+ ZenProfile,
14
+ ZenLight,
15
+ ZenGroup,
16
+ ZenButton,
17
+ ZenMotionSensor,
18
+ ZenSystemVariable,
19
+ )
20
+
21
+ __all__ = [
22
+ # High-level client
23
+ "ZenControl",
24
+
25
+ # High-level models
26
+ "ZenController",
27
+ "ZenProfile",
28
+ "ZenLight",
29
+ "ZenGroup",
30
+ "ZenButton",
31
+ "ZenMotionSensor",
32
+ "ZenSystemVariable",
33
+ ]