conson-xp 1.36.0__py3-none-any.whl → 1.38.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.
- {conson_xp-1.36.0.dist-info → conson_xp-1.38.0.dist-info}/METADATA +6 -1
- {conson_xp-1.36.0.dist-info → conson_xp-1.38.0.dist-info}/RECORD +51 -46
- xp/__init__.py +1 -1
- xp/cli/commands/__init__.py +2 -0
- xp/cli/commands/conbus/__init__.py +2 -0
- xp/cli/commands/conbus/conbus.py +26 -0
- xp/cli/commands/conbus/conbus_actiontable_commands.py +18 -9
- xp/cli/commands/conbus/conbus_autoreport_commands.py +5 -2
- xp/cli/commands/conbus/conbus_event_commands.py +1 -7
- xp/cli/commands/conbus/conbus_export_commands.py +7 -6
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +2 -0
- xp/cli/commands/conbus/conbus_linknumber_commands.py +2 -0
- xp/cli/commands/conbus/conbus_modulenumber_commands.py +2 -0
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +92 -9
- xp/cli/commands/conbus/conbus_output_commands.py +4 -0
- xp/models/actiontable/msactiontable.py +9 -0
- xp/models/actiontable/msactiontable_xp20.py +14 -14
- xp/models/actiontable/msactiontable_xp24.py +228 -11
- xp/models/actiontable/msactiontable_xp33.py +42 -14
- xp/models/conbus/conbus_export.py +6 -2
- xp/models/config/__init__.py +1 -0
- xp/models/{homekit/homekit_conson_config.py → config/conson_module_config.py} +3 -1
- xp/models/protocol/conbus_protocol.py +1 -1
- xp/models/telegram/datapoint_type.py +30 -0
- xp/services/actiontable/msactiontable_xp20_serializer.py +12 -0
- xp/services/actiontable/msactiontable_xp24_serializer.py +13 -1
- xp/services/actiontable/msactiontable_xp33_serializer.py +12 -0
- xp/services/conbus/actiontable/actiontable_download_service.py +8 -5
- xp/services/conbus/actiontable/actiontable_list_service.py +2 -1
- xp/services/conbus/actiontable/actiontable_show_service.py +2 -2
- xp/services/conbus/actiontable/actiontable_upload_service.py +1 -1
- xp/services/conbus/conbus_datapoint_service.py +1 -1
- xp/services/conbus/conbus_event_list_service.py +1 -1
- xp/services/conbus/conbus_export_service.py +1 -1
- xp/services/conbus/msactiontable/__init__.py +1 -0
- xp/services/conbus/{actiontable/msactiontable_service.py → msactiontable/msactiontable_download_service.py} +9 -6
- xp/services/conbus/msactiontable/msactiontable_list_service.py +92 -0
- xp/services/conbus/msactiontable/msactiontable_show_service.py +89 -0
- xp/services/homekit/homekit_config_validator.py +1 -1
- xp/services/homekit/homekit_conson_validator.py +1 -1
- xp/services/homekit/homekit_dimminglight.py +1 -1
- xp/services/homekit/homekit_lightbulb.py +1 -1
- xp/services/homekit/homekit_module_service.py +1 -1
- xp/services/homekit/homekit_outlet.py +1 -1
- xp/services/protocol/conbus_event_protocol.py +7 -3
- xp/services/server/server_service.py +1 -1
- xp/services/term/state_monitor_service.py +1 -1
- xp/utils/dependencies.py +36 -14
- {conson_xp-1.36.0.dist-info → conson_xp-1.38.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.36.0.dist-info → conson_xp-1.38.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.36.0.dist-info → conson_xp-1.38.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -121,6 +121,7 @@ def xp_output_status(ctx: click.Context, serial_number: str) -> None:
|
|
|
121
121
|
response: Datapoint response object.
|
|
122
122
|
"""
|
|
123
123
|
click.echo(json.dumps(response.to_dict(), indent=2))
|
|
124
|
+
service.stop_reactor()
|
|
124
125
|
|
|
125
126
|
with service:
|
|
126
127
|
service.on_finish.connect(on_finish)
|
|
@@ -128,6 +129,7 @@ def xp_output_status(ctx: click.Context, serial_number: str) -> None:
|
|
|
128
129
|
serial_number=serial_number,
|
|
129
130
|
datapoint_type=DataPointType.MODULE_OUTPUT_STATE,
|
|
130
131
|
)
|
|
132
|
+
service.start_reactor()
|
|
131
133
|
|
|
132
134
|
|
|
133
135
|
@conbus_output.command("state")
|
|
@@ -156,6 +158,7 @@ def xp_module_state(ctx: click.Context, serial_number: str) -> None:
|
|
|
156
158
|
response: Datapoint response object.
|
|
157
159
|
"""
|
|
158
160
|
click.echo(json.dumps(response.to_dict(), indent=2))
|
|
161
|
+
service.stop_reactor()
|
|
159
162
|
|
|
160
163
|
with service:
|
|
161
164
|
service.on_finish.connect(on_finish)
|
|
@@ -163,3 +166,4 @@ def xp_module_state(ctx: click.Context, serial_number: str) -> None:
|
|
|
163
166
|
serial_number=serial_number,
|
|
164
167
|
datapoint_type=DataPointType.MODULE_STATE,
|
|
165
168
|
)
|
|
169
|
+
service.start_reactor()
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""XP20 Action Table models for input actions and settings."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
|
+
from xp.models.actiontable.msactiontable import MsActionTable
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
class InputChannel:
|
|
7
|
+
|
|
8
|
+
class InputChannel(BaseModel):
|
|
8
9
|
"""Configuration for a single input channel in XP20 action table.
|
|
9
10
|
|
|
10
11
|
Attributes:
|
|
@@ -19,13 +20,12 @@ class InputChannel:
|
|
|
19
20
|
invert: bool = False
|
|
20
21
|
short_long: bool = False
|
|
21
22
|
group_on_off: bool = False
|
|
22
|
-
and_functions: list[bool] =
|
|
23
|
+
and_functions: list[bool] = Field(default_factory=lambda: [False] * 8)
|
|
23
24
|
sa_function: bool = False
|
|
24
25
|
ta_function: bool = False
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
class Xp20MsActionTable:
|
|
28
|
+
class Xp20MsActionTable(MsActionTable):
|
|
29
29
|
"""XP20 Action Table for managing 8 input channels.
|
|
30
30
|
|
|
31
31
|
Contains configuration for 8 input channels (input1 through input8),
|
|
@@ -43,11 +43,11 @@ class Xp20MsActionTable:
|
|
|
43
43
|
input8: Configuration for input channel 8.
|
|
44
44
|
"""
|
|
45
45
|
|
|
46
|
-
input1: InputChannel =
|
|
47
|
-
input2: InputChannel =
|
|
48
|
-
input3: InputChannel =
|
|
49
|
-
input4: InputChannel =
|
|
50
|
-
input5: InputChannel =
|
|
51
|
-
input6: InputChannel =
|
|
52
|
-
input7: InputChannel =
|
|
53
|
-
input8: InputChannel =
|
|
46
|
+
input1: InputChannel = Field(default_factory=InputChannel)
|
|
47
|
+
input2: InputChannel = Field(default_factory=InputChannel)
|
|
48
|
+
input3: InputChannel = Field(default_factory=InputChannel)
|
|
49
|
+
input4: InputChannel = Field(default_factory=InputChannel)
|
|
50
|
+
input5: InputChannel = Field(default_factory=InputChannel)
|
|
51
|
+
input6: InputChannel = Field(default_factory=InputChannel)
|
|
52
|
+
input7: InputChannel = Field(default_factory=InputChannel)
|
|
53
|
+
input8: InputChannel = Field(default_factory=InputChannel)
|
|
@@ -1,26 +1,88 @@
|
|
|
1
1
|
"""XP24 Action Table models for input actions and settings."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import Any, ClassVar, Union
|
|
4
4
|
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
6
|
+
|
|
7
|
+
from xp.models.actiontable.msactiontable import MsActionTable
|
|
5
8
|
from xp.models.telegram.input_action_type import InputActionType
|
|
6
9
|
from xp.models.telegram.timeparam_type import TimeParam
|
|
7
10
|
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
class InputAction:
|
|
12
|
+
class InputAction(BaseModel):
|
|
11
13
|
"""Represents an input action with type and parameter.
|
|
12
14
|
|
|
13
15
|
Attributes:
|
|
16
|
+
model_config: Pydantic configuration to preserve enum objects.
|
|
14
17
|
type: The input action type.
|
|
15
18
|
param: Time parameter for the action.
|
|
16
19
|
"""
|
|
17
20
|
|
|
21
|
+
model_config = ConfigDict(use_enum_values=False)
|
|
22
|
+
|
|
18
23
|
type: InputActionType = InputActionType.TOGGLE
|
|
19
24
|
param: TimeParam = TimeParam.NONE
|
|
20
25
|
|
|
26
|
+
@field_validator("type", mode="before")
|
|
27
|
+
@classmethod
|
|
28
|
+
def validate_action_type(
|
|
29
|
+
cls, v: Union[str, int, InputActionType]
|
|
30
|
+
) -> InputActionType:
|
|
31
|
+
"""Convert string or int to InputActionType enum.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
v: Input value (can be string name, int value, or enum).
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
InputActionType enum value.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If the value cannot be converted to InputActionType.
|
|
41
|
+
"""
|
|
42
|
+
if isinstance(v, InputActionType):
|
|
43
|
+
return v
|
|
44
|
+
if isinstance(v, str):
|
|
45
|
+
try:
|
|
46
|
+
return InputActionType[v]
|
|
47
|
+
except KeyError:
|
|
48
|
+
raise ValueError(f"Invalid InputActionType: {v}")
|
|
49
|
+
if isinstance(v, int):
|
|
50
|
+
try:
|
|
51
|
+
return InputActionType(v)
|
|
52
|
+
except ValueError:
|
|
53
|
+
raise ValueError(f"Invalid InputActionType value: {v}")
|
|
54
|
+
raise ValueError(f"Invalid type for InputActionType: {type(v)}")
|
|
55
|
+
|
|
56
|
+
@field_validator("param", mode="before")
|
|
57
|
+
@classmethod
|
|
58
|
+
def validate_time_param(cls, v: Union[str, int, TimeParam]) -> TimeParam:
|
|
59
|
+
"""Convert string or int to TimeParam enum.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
v: Input value (can be string name, int value, or enum).
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
TimeParam enum value.
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
ValueError: If the value cannot be converted to TimeParam.
|
|
69
|
+
"""
|
|
70
|
+
if isinstance(v, TimeParam):
|
|
71
|
+
return v
|
|
72
|
+
if isinstance(v, str):
|
|
73
|
+
try:
|
|
74
|
+
return TimeParam[v]
|
|
75
|
+
except KeyError:
|
|
76
|
+
raise ValueError(f"Invalid TimeParam: {v}")
|
|
77
|
+
if isinstance(v, int):
|
|
78
|
+
try:
|
|
79
|
+
return TimeParam(v)
|
|
80
|
+
except ValueError:
|
|
81
|
+
raise ValueError(f"Invalid TimeParam value: {v}")
|
|
82
|
+
raise ValueError(f"Invalid type for TimeParam: {type(v)}")
|
|
21
83
|
|
|
22
|
-
|
|
23
|
-
class Xp24MsActionTable:
|
|
84
|
+
|
|
85
|
+
class Xp24MsActionTable(MsActionTable):
|
|
24
86
|
"""XP24 Action Table for managing input actions and settings.
|
|
25
87
|
|
|
26
88
|
Each input has an action type (TOGGLE, ON, LEVELSET, etc.)
|
|
@@ -29,6 +91,8 @@ class Xp24MsActionTable:
|
|
|
29
91
|
Attributes:
|
|
30
92
|
MS300: Timing constant for 300ms.
|
|
31
93
|
MS500: Timing constant for 500ms.
|
|
94
|
+
ACTION_SHORT_CODES: Mapping from InputActionType to short code strings.
|
|
95
|
+
SHORT_CODE_TO_ACTION: Reverse mapping from short codes to InputActionType.
|
|
32
96
|
input1_action: Action configuration for input 1.
|
|
33
97
|
input2_action: Action configuration for input 2.
|
|
34
98
|
input3_action: Action configuration for input 3.
|
|
@@ -41,14 +105,52 @@ class Xp24MsActionTable:
|
|
|
41
105
|
"""
|
|
42
106
|
|
|
43
107
|
# MS timing constants
|
|
44
|
-
MS300 = 12
|
|
45
|
-
MS500 = 20
|
|
108
|
+
MS300: ClassVar[int] = 12
|
|
109
|
+
MS500: ClassVar[int] = 20
|
|
110
|
+
|
|
111
|
+
# Short format mapping for InputActionType
|
|
112
|
+
ACTION_SHORT_CODES: ClassVar[dict[InputActionType, str]] = {
|
|
113
|
+
InputActionType.VOID: "V",
|
|
114
|
+
InputActionType.ON: "ON",
|
|
115
|
+
InputActionType.OFF: "OF",
|
|
116
|
+
InputActionType.TOGGLE: "T",
|
|
117
|
+
InputActionType.BLOCK: "BL",
|
|
118
|
+
InputActionType.AUXRELAY: "AX",
|
|
119
|
+
InputActionType.MUTUALEX: "MX",
|
|
120
|
+
InputActionType.LEVELUP: "LU",
|
|
121
|
+
InputActionType.LEVELDOWN: "LD",
|
|
122
|
+
InputActionType.LEVELINC: "LI",
|
|
123
|
+
InputActionType.LEVELDEC: "LC",
|
|
124
|
+
InputActionType.LEVELSET: "LS",
|
|
125
|
+
InputActionType.FADETIME: "FT",
|
|
126
|
+
InputActionType.SCENESET: "SS",
|
|
127
|
+
InputActionType.SCENENEXT: "SN",
|
|
128
|
+
InputActionType.SCENEPREV: "SP",
|
|
129
|
+
InputActionType.CTRLMETHOD: "CM",
|
|
130
|
+
InputActionType.RETURNDATA: "RD",
|
|
131
|
+
InputActionType.DELAYEDON: "DO",
|
|
132
|
+
InputActionType.EVENTTIMER1: "E1",
|
|
133
|
+
InputActionType.EVENTTIMER2: "E2",
|
|
134
|
+
InputActionType.EVENTTIMER3: "E3",
|
|
135
|
+
InputActionType.EVENTTIMER4: "E4",
|
|
136
|
+
InputActionType.STEPCTRL: "SC",
|
|
137
|
+
InputActionType.STEPCTRLUP: "SU",
|
|
138
|
+
InputActionType.STEPCTRLDOWN: "SD",
|
|
139
|
+
InputActionType.LEVELSETINTERN: "LN",
|
|
140
|
+
InputActionType.FADE: "FD",
|
|
141
|
+
InputActionType.LEARN: "LR",
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# Reverse mapping for parsing
|
|
145
|
+
SHORT_CODE_TO_ACTION: ClassVar[dict[str, InputActionType]] = {
|
|
146
|
+
v: k for k, v in ACTION_SHORT_CODES.items()
|
|
147
|
+
}
|
|
46
148
|
|
|
47
149
|
# Input actions for each input (default to TOGGLE with None parameter)
|
|
48
|
-
input1_action: InputAction =
|
|
49
|
-
input2_action: InputAction =
|
|
50
|
-
input3_action: InputAction =
|
|
51
|
-
input4_action: InputAction =
|
|
150
|
+
input1_action: InputAction = Field(default_factory=InputAction)
|
|
151
|
+
input2_action: InputAction = Field(default_factory=InputAction)
|
|
152
|
+
input3_action: InputAction = Field(default_factory=InputAction)
|
|
153
|
+
input4_action: InputAction = Field(default_factory=InputAction)
|
|
52
154
|
|
|
53
155
|
# Boolean settings
|
|
54
156
|
mutex12: bool = False # Mutual exclusion between inputs 1-2
|
|
@@ -56,3 +158,118 @@ class Xp24MsActionTable:
|
|
|
56
158
|
curtain12: bool = False # Curtain setting for inputs 1-2
|
|
57
159
|
curtain34: bool = False # Curtain setting for inputs 3-4
|
|
58
160
|
mutual_deadtime: int = MS300 # Master timing (MS300=12 or MS500=20)
|
|
161
|
+
|
|
162
|
+
def to_short_format(self, include_settings: bool = False) -> str:
|
|
163
|
+
"""Convert action table to short format string.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
include_settings: Include settings after pipe separator.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Short format string (e.g., "XP24 T:1 T:2 T:0 T:0").
|
|
170
|
+
"""
|
|
171
|
+
# Format input actions
|
|
172
|
+
actions = [
|
|
173
|
+
self.input1_action,
|
|
174
|
+
self.input2_action,
|
|
175
|
+
self.input3_action,
|
|
176
|
+
self.input4_action,
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
action_parts = []
|
|
180
|
+
for action in actions:
|
|
181
|
+
short_code = self.ACTION_SHORT_CODES.get(action.type, "??")
|
|
182
|
+
param_value = action.param.value
|
|
183
|
+
action_parts.append(f"{short_code}:{param_value}")
|
|
184
|
+
|
|
185
|
+
result = f"XP24 {' '.join(action_parts)}"
|
|
186
|
+
|
|
187
|
+
# Add settings if requested
|
|
188
|
+
if include_settings:
|
|
189
|
+
settings = (
|
|
190
|
+
f"M12:{1 if self.mutex12 else 0} "
|
|
191
|
+
f"M34:{1 if self.mutex34 else 0} "
|
|
192
|
+
f"C12:{1 if self.curtain12 else 0} "
|
|
193
|
+
f"C34:{1 if self.curtain34 else 0} "
|
|
194
|
+
f"DT:{self.mutual_deadtime}"
|
|
195
|
+
)
|
|
196
|
+
result = f"{result} | {settings}"
|
|
197
|
+
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def from_short_format(cls, short_str: str) -> "Xp24MsActionTable":
|
|
202
|
+
"""Parse short format string into action table.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
short_str: Short format string.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Xp24MsActionTable instance.
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
ValueError: If format is invalid.
|
|
212
|
+
"""
|
|
213
|
+
# Split by pipe to separate actions from settings
|
|
214
|
+
parts = short_str.split("|")
|
|
215
|
+
action_part = parts[0].strip()
|
|
216
|
+
settings_part = parts[1].strip() if len(parts) > 1 else None
|
|
217
|
+
|
|
218
|
+
# Parse action part
|
|
219
|
+
tokens = action_part.split()
|
|
220
|
+
if len(tokens) != 5 or tokens[0] != "XP24":
|
|
221
|
+
raise ValueError(
|
|
222
|
+
f"Invalid short format: expected 'XP24 <a1> <a2> <a3> <a4>', got '{action_part}'"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Parse input actions
|
|
226
|
+
input_actions = []
|
|
227
|
+
for i, token in enumerate(tokens[1:5], 1):
|
|
228
|
+
if ":" not in token:
|
|
229
|
+
raise ValueError(f"Invalid action format at position {i}: '{token}'")
|
|
230
|
+
|
|
231
|
+
code, param_str = token.split(":", 1)
|
|
232
|
+
|
|
233
|
+
# Look up action type
|
|
234
|
+
if code not in cls.SHORT_CODE_TO_ACTION:
|
|
235
|
+
raise ValueError(f"Unknown action code: '{code}'")
|
|
236
|
+
|
|
237
|
+
action_type = cls.SHORT_CODE_TO_ACTION[code]
|
|
238
|
+
|
|
239
|
+
# Parse param
|
|
240
|
+
try:
|
|
241
|
+
param_value = int(param_str)
|
|
242
|
+
param_type = TimeParam(param_value)
|
|
243
|
+
except (ValueError, KeyError):
|
|
244
|
+
raise ValueError(f"Invalid time param: '{param_str}'")
|
|
245
|
+
|
|
246
|
+
input_actions.append(InputAction(type=action_type, param=param_type))
|
|
247
|
+
|
|
248
|
+
# Parse settings if present
|
|
249
|
+
kwargs: dict[str, Any] = {
|
|
250
|
+
"input1_action": input_actions[0],
|
|
251
|
+
"input2_action": input_actions[1],
|
|
252
|
+
"input3_action": input_actions[2],
|
|
253
|
+
"input4_action": input_actions[3],
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if settings_part:
|
|
257
|
+
# Parse settings: M12:0 M34:1 C12:0 C34:1 DT:12
|
|
258
|
+
for setting in settings_part.split():
|
|
259
|
+
if ":" not in setting:
|
|
260
|
+
continue
|
|
261
|
+
|
|
262
|
+
key, value = setting.split(":", 1)
|
|
263
|
+
|
|
264
|
+
if key == "M12":
|
|
265
|
+
kwargs["mutex12"] = value == "1"
|
|
266
|
+
elif key == "M34":
|
|
267
|
+
kwargs["mutex34"] = value == "1"
|
|
268
|
+
elif key == "C12":
|
|
269
|
+
kwargs["curtain12"] = value == "1"
|
|
270
|
+
elif key == "C34":
|
|
271
|
+
kwargs["curtain34"] = value == "1"
|
|
272
|
+
elif key == "DT":
|
|
273
|
+
kwargs["mutual_deadtime"] = int(value)
|
|
274
|
+
|
|
275
|
+
return cls(**kwargs)
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""XP33 Action Table models for output and scene configuration."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import Union
|
|
4
4
|
|
|
5
|
+
from pydantic import BaseModel, Field, field_validator
|
|
6
|
+
|
|
7
|
+
from xp.models.actiontable.msactiontable import MsActionTable
|
|
5
8
|
from xp.models.telegram.timeparam_type import TimeParam
|
|
6
9
|
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
class Xp33Output:
|
|
11
|
+
class Xp33Output(BaseModel):
|
|
10
12
|
"""Represents an XP33 output configuration.
|
|
11
13
|
|
|
12
14
|
Attributes:
|
|
@@ -24,8 +26,7 @@ class Xp33Output:
|
|
|
24
26
|
leading_edge: bool = False
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
class Xp33Scene:
|
|
29
|
+
class Xp33Scene(BaseModel):
|
|
29
30
|
"""Represents a scene configuration.
|
|
30
31
|
|
|
31
32
|
Attributes:
|
|
@@ -40,9 +41,36 @@ class Xp33Scene:
|
|
|
40
41
|
output3_level: int = 0
|
|
41
42
|
time: TimeParam = TimeParam.NONE
|
|
42
43
|
|
|
44
|
+
@field_validator("time", mode="before")
|
|
45
|
+
@classmethod
|
|
46
|
+
def validate_time_param(cls, v: Union[str, int, TimeParam]) -> TimeParam:
|
|
47
|
+
"""Convert string or int to TimeParam enum.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
v: Input value (can be string name, int value, or enum).
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
TimeParam enum value.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: If the value cannot be converted to TimeParam.
|
|
57
|
+
"""
|
|
58
|
+
if isinstance(v, TimeParam):
|
|
59
|
+
return v
|
|
60
|
+
if isinstance(v, str):
|
|
61
|
+
try:
|
|
62
|
+
return TimeParam[v]
|
|
63
|
+
except KeyError:
|
|
64
|
+
raise ValueError(f"Invalid TimeParam: {v}")
|
|
65
|
+
if isinstance(v, int):
|
|
66
|
+
try:
|
|
67
|
+
return TimeParam(v)
|
|
68
|
+
except ValueError:
|
|
69
|
+
raise ValueError(f"Invalid TimeParam value: {v}")
|
|
70
|
+
raise ValueError(f"Invalid type for TimeParam: {type(v)}")
|
|
71
|
+
|
|
43
72
|
|
|
44
|
-
|
|
45
|
-
class Xp33MsActionTable:
|
|
73
|
+
class Xp33MsActionTable(MsActionTable):
|
|
46
74
|
"""XP33 Action Table for managing outputs and scenes.
|
|
47
75
|
|
|
48
76
|
Attributes:
|
|
@@ -55,11 +83,11 @@ class Xp33MsActionTable:
|
|
|
55
83
|
scene4: Configuration for scene 4.
|
|
56
84
|
"""
|
|
57
85
|
|
|
58
|
-
output1: Xp33Output =
|
|
59
|
-
output2: Xp33Output =
|
|
60
|
-
output3: Xp33Output =
|
|
86
|
+
output1: Xp33Output = Field(default_factory=Xp33Output)
|
|
87
|
+
output2: Xp33Output = Field(default_factory=Xp33Output)
|
|
88
|
+
output3: Xp33Output = Field(default_factory=Xp33Output)
|
|
61
89
|
|
|
62
|
-
scene1: Xp33Scene =
|
|
63
|
-
scene2: Xp33Scene =
|
|
64
|
-
scene3: Xp33Scene =
|
|
65
|
-
scene4: Xp33Scene =
|
|
90
|
+
scene1: Xp33Scene = Field(default_factory=Xp33Scene)
|
|
91
|
+
scene2: Xp33Scene = Field(default_factory=Xp33Scene)
|
|
92
|
+
scene3: Xp33Scene = Field(default_factory=Xp33Scene)
|
|
93
|
+
scene4: Xp33Scene = Field(default_factory=Xp33Scene)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
-
from xp.models.
|
|
6
|
+
from xp.models.config.conson_module_config import ConsonModuleListConfig
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@dataclass
|
|
@@ -14,8 +14,10 @@ class ConbusExportResponse:
|
|
|
14
14
|
success: Whether the operation was successful.
|
|
15
15
|
config: Exported module configuration list.
|
|
16
16
|
device_count: Number of devices exported.
|
|
17
|
+
actiontable_count: Number of action tables downloaded.
|
|
18
|
+
actiontable_failed: Number of action table downloads that failed.
|
|
17
19
|
output_file: Path to output file.
|
|
18
|
-
export_status: Export status (OK,
|
|
20
|
+
export_status: Export status (OK, PARTIAL_ACTIONTABLE, FAILED_TIMEOUT, etc.).
|
|
19
21
|
error: Error message if operation failed.
|
|
20
22
|
sent_telegrams: List of telegrams sent during export.
|
|
21
23
|
received_telegrams: List of telegrams received during export.
|
|
@@ -24,6 +26,8 @@ class ConbusExportResponse:
|
|
|
24
26
|
success: bool
|
|
25
27
|
config: Optional[ConsonModuleListConfig] = None
|
|
26
28
|
device_count: int = 0
|
|
29
|
+
actiontable_count: int = 0
|
|
30
|
+
actiontable_failed: int = 0
|
|
27
31
|
output_file: str = "export.yml"
|
|
28
32
|
export_status: str = "OK"
|
|
29
33
|
error: Optional[str] = None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Configuration models package."""
|
|
@@ -23,6 +23,7 @@ class ConsonModuleConfig(BaseModel):
|
|
|
23
23
|
sw_version: Optional software version.
|
|
24
24
|
hw_version: Optional hardware version.
|
|
25
25
|
action_table: Optional action table configuration.
|
|
26
|
+
msaction_table: Optional ms action table configuration.
|
|
26
27
|
auto_report_status: Optional auto report status.
|
|
27
28
|
"""
|
|
28
29
|
|
|
@@ -37,8 +38,9 @@ class ConsonModuleConfig(BaseModel):
|
|
|
37
38
|
conbus_port: Optional[int] = None
|
|
38
39
|
sw_version: Optional[str] = None
|
|
39
40
|
hw_version: Optional[str] = None
|
|
40
|
-
action_table: Optional[List[str]] = None
|
|
41
41
|
auto_report_status: Optional[str] = None
|
|
42
|
+
action_table: Optional[List[str]] = None
|
|
43
|
+
msaction_table: Optional[str] = None
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
class ConsonModuleListConfig(BaseModel):
|
|
@@ -7,8 +7,8 @@ from typing import TYPE_CHECKING, Union
|
|
|
7
7
|
from bubus import BaseEvent
|
|
8
8
|
from pydantic import Field
|
|
9
9
|
|
|
10
|
+
from xp.models.config.conson_module_config import ConsonModuleConfig
|
|
10
11
|
from xp.models.homekit.homekit_config import HomekitAccessoryConfig
|
|
11
|
-
from xp.models.homekit.homekit_conson_config import ConsonModuleConfig
|
|
12
12
|
from xp.models.telegram.datapoint_type import DataPointType
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
@@ -30,6 +30,36 @@ class DataPointType(str, Enum):
|
|
|
30
30
|
SW_TOP_VERSION: Software top version.
|
|
31
31
|
VOLTAGE: Voltage data point.
|
|
32
32
|
AUTO_REPORT_STATUS: Auto report status.
|
|
33
|
+
|
|
34
|
+
Sample telegram datapoint request:
|
|
35
|
+
<S0020044966F02D30FL>
|
|
36
|
+
|
|
37
|
+
Sample telegram datapoint reply:
|
|
38
|
+
<R0020044966F02D00XP24FH>
|
|
39
|
+
<R0020044966F02D01XP24FG>
|
|
40
|
+
<R0020044966F02D02XP24_V0.34.03GI>
|
|
41
|
+
<R0020044966F02D030020044966FB>
|
|
42
|
+
<R0020044966F02D0406FL>
|
|
43
|
+
<R0020044966F02D050007FL>
|
|
44
|
+
<R0020044966F02D0601FO>
|
|
45
|
+
<R0020044966F02D0707FJ>
|
|
46
|
+
<R0020044966F02D08MIFF>
|
|
47
|
+
<R0020044966F02D09OFFBP>
|
|
48
|
+
<R0020044966F02D1000FI>
|
|
49
|
+
<R0020044966F02D11xxxx0000FJ>
|
|
50
|
+
<R0020044966F02D12xxxx0000FK>
|
|
51
|
+
<R0020044966F02D1500:000[%],01:000[%],02:000[%],03:000[%]HB>
|
|
52
|
+
<R0020044966F02D1600:000[H],01:000[H],02:000[H],03:000[H]HC>
|
|
53
|
+
<R0020044966F02D1700:00000[NA],01:00000[NA],02:00000[NA],03:00000[NA]HD>
|
|
54
|
+
<R0020044966F02D18+28,5§CIM>
|
|
55
|
+
<R0020044966F02D19??FB>
|
|
56
|
+
<R0020044966F02D20??FL>
|
|
57
|
+
<R0020044966F02D21PPFK>
|
|
58
|
+
<R0020044966F02D22PLLKMDINEM>
|
|
59
|
+
<R0020044966F02D23AAFI>
|
|
60
|
+
<R0020044966F02D24AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFP>
|
|
61
|
+
<R0020044966F02D25AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFO>
|
|
62
|
+
<R0020044966F02D26??FN>
|
|
33
63
|
"""
|
|
34
64
|
|
|
35
65
|
MODULE_TYPE = "00" # Module type (XP24, XP33, ..)
|
|
@@ -15,6 +15,18 @@ TA_FUNCTION_INDEX: int = 12
|
|
|
15
15
|
class Xp20MsActionTableSerializer:
|
|
16
16
|
"""Handles serialization/deserialization of XP20 action tables to/from telegrams."""
|
|
17
17
|
|
|
18
|
+
@staticmethod
|
|
19
|
+
def format_decoded_output(action_table: Xp20MsActionTable) -> str:
|
|
20
|
+
"""Serialize XP20 action table to humane compact readable format.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
action_table: XP20 action table to serialize
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Human-readable string describing XP20 action table
|
|
27
|
+
"""
|
|
28
|
+
return ""
|
|
29
|
+
|
|
18
30
|
@staticmethod
|
|
19
31
|
def to_data(action_table: Xp20MsActionTable) -> str:
|
|
20
32
|
"""Serialize XP20 action table to telegram hex string format.
|
|
@@ -9,6 +9,18 @@ from xp.utils.serialization import de_nibbles, nibbles
|
|
|
9
9
|
class Xp24MsActionTableSerializer:
|
|
10
10
|
"""Handles serialization/deserialization of XP24 action tables to/from telegrams."""
|
|
11
11
|
|
|
12
|
+
@staticmethod
|
|
13
|
+
def format_decoded_output(action_table: Xp24MsActionTable) -> str:
|
|
14
|
+
"""Serialize XP24 action table to humane compact readable format.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
action_table: XP24 action table to serialize
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Human-readable string describing XP24 action table
|
|
21
|
+
"""
|
|
22
|
+
return action_table.to_short_format()
|
|
23
|
+
|
|
12
24
|
@staticmethod
|
|
13
25
|
def to_data(action_table: Xp24MsActionTable) -> str:
|
|
14
26
|
"""Serialize action table to telegram format.
|
|
@@ -117,4 +129,4 @@ class Xp24MsActionTableSerializer:
|
|
|
117
129
|
action_type = InputActionType(function_id)
|
|
118
130
|
param_type = TimeParam(param_id)
|
|
119
131
|
|
|
120
|
-
return InputAction(action_type, param_type)
|
|
132
|
+
return InputAction(type=action_type, param=param_type)
|
|
@@ -12,6 +12,18 @@ from xp.utils.serialization import bits_to_byte, byte_to_bits, de_nibbles, nibbl
|
|
|
12
12
|
class Xp33MsActionTableSerializer:
|
|
13
13
|
"""Handles serialization/deserialization of XP33 action tables to/from telegrams."""
|
|
14
14
|
|
|
15
|
+
@staticmethod
|
|
16
|
+
def format_decoded_output(action_table: Xp33MsActionTable) -> str:
|
|
17
|
+
"""Serialize XP33 action table to humane compact readable format.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
action_table: XP33 action table to serialize
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Human-readable string describing XP33 action table
|
|
24
|
+
"""
|
|
25
|
+
return ""
|
|
26
|
+
|
|
15
27
|
@staticmethod
|
|
16
28
|
def _percentage_to_byte(percentage: int) -> int:
|
|
17
29
|
"""Convert percentage (0-100) to byte value for telegram encoding."""
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from dataclasses import asdict
|
|
5
|
-
from typing import Any, Optional
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
6
|
|
|
7
7
|
from psygnal import Signal
|
|
8
8
|
|
|
9
|
+
from xp.models.actiontable.actiontable import ActionTable
|
|
9
10
|
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
10
11
|
from xp.models.telegram.system_function import SystemFunction
|
|
11
12
|
from xp.models.telegram.telegram_type import TelegramType
|
|
@@ -14,7 +15,7 @@ from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
|
14
15
|
from xp.services.telegram.telegram_service import TelegramService
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
class
|
|
18
|
+
class ActionTableDownloadService:
|
|
18
19
|
"""TCP client service for downloading action tables from Conbus modules.
|
|
19
20
|
|
|
20
21
|
Manages TCP socket connections, handles telegram generation and transmission,
|
|
@@ -28,7 +29,9 @@ class ActionTableService:
|
|
|
28
29
|
|
|
29
30
|
on_progress: Signal = Signal(str)
|
|
30
31
|
on_error: Signal = Signal(str)
|
|
31
|
-
on_finish: Signal = Signal(
|
|
32
|
+
on_finish: Signal = Signal(
|
|
33
|
+
ActionTable, Dict[str, Any], list[str]
|
|
34
|
+
) # (ActionTable, Dict[str, Any], list[str])
|
|
32
35
|
|
|
33
36
|
def __init__(
|
|
34
37
|
self,
|
|
@@ -123,7 +126,7 @@ class ActionTableService:
|
|
|
123
126
|
actiontable = self.serializer.from_encoded_string(all_data)
|
|
124
127
|
actiontable_dict = asdict(actiontable)
|
|
125
128
|
actiontable_short = self.serializer.format_decoded_output(actiontable)
|
|
126
|
-
self.on_finish.emit(
|
|
129
|
+
self.on_finish.emit(actiontable, actiontable_dict, actiontable_short)
|
|
127
130
|
|
|
128
131
|
def timeout(self) -> None:
|
|
129
132
|
"""Handle timeout event."""
|
|
@@ -172,7 +175,7 @@ class ActionTableService:
|
|
|
172
175
|
"""Stop the reactor."""
|
|
173
176
|
self.conbus_protocol.stop_reactor()
|
|
174
177
|
|
|
175
|
-
def __enter__(self) -> "
|
|
178
|
+
def __enter__(self) -> "ActionTableDownloadService":
|
|
176
179
|
"""Enter context manager - reset state for singleton reuse.
|
|
177
180
|
|
|
178
181
|
Returns:
|