conson-xp 1.1.0__py3-none-any.whl → 1.3.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.1.0.dist-info → conson_xp-1.3.0.dist-info}/METADATA +1 -5
- conson_xp-1.3.0.dist-info/RECORD +164 -0
- xp/__init__.py +4 -3
- xp/cli/__init__.py +1 -1
- xp/cli/commands/__init__.py +1 -2
- xp/cli/commands/conbus/conbus.py +9 -37
- xp/cli/commands/conbus/conbus_actiontable_commands.py +26 -4
- xp/cli/commands/conbus/conbus_autoreport_commands.py +58 -30
- xp/cli/commands/conbus/conbus_blink_commands.py +61 -29
- xp/cli/commands/conbus/conbus_config_commands.py +10 -5
- xp/cli/commands/conbus/conbus_custom_commands.py +16 -5
- xp/cli/commands/conbus/conbus_datapoint_commands.py +32 -10
- xp/cli/commands/conbus/conbus_discover_commands.py +20 -7
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +114 -39
- xp/cli/commands/conbus/conbus_linknumber_commands.py +50 -25
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +36 -5
- xp/cli/commands/conbus/conbus_output_commands.py +52 -14
- xp/cli/commands/conbus/conbus_raw_commands.py +17 -6
- xp/cli/commands/conbus/conbus_receive_commands.py +20 -10
- xp/cli/commands/conbus/conbus_scan_commands.py +17 -4
- xp/cli/commands/file_commands.py +35 -18
- xp/cli/commands/homekit/homekit.py +14 -8
- xp/cli/commands/homekit/homekit_start_commands.py +8 -6
- xp/cli/commands/module_commands.py +38 -23
- xp/cli/commands/reverse_proxy_commands.py +27 -19
- xp/cli/commands/server/server_commands.py +18 -18
- xp/cli/commands/telegram/telegram.py +4 -12
- xp/cli/commands/telegram/telegram_blink_commands.py +10 -8
- xp/cli/commands/telegram/telegram_checksum_commands.py +19 -8
- xp/cli/commands/telegram/telegram_discover_commands.py +2 -4
- xp/cli/commands/telegram/telegram_linknumber_commands.py +11 -8
- xp/cli/commands/telegram/telegram_parse_commands.py +10 -9
- xp/cli/commands/telegram/telegram_version_commands.py +8 -4
- xp/cli/main.py +5 -3
- xp/cli/utils/click_tree.py +23 -3
- xp/cli/utils/datapoint_type_choice.py +20 -0
- xp/cli/utils/decorators.py +165 -14
- xp/cli/utils/error_handlers.py +49 -18
- xp/cli/utils/formatters.py +95 -10
- xp/cli/utils/serial_number_type.py +18 -0
- xp/cli/utils/system_function_choice.py +20 -0
- xp/cli/utils/xp_module_type.py +20 -0
- xp/connection/__init__.py +1 -1
- xp/connection/exceptions.py +5 -5
- xp/models/__init__.py +1 -1
- xp/models/actiontable/__init__.py +1 -0
- xp/models/actiontable/actiontable.py +17 -1
- xp/models/actiontable/msactiontable_xp20.py +10 -0
- xp/models/actiontable/msactiontable_xp24.py +20 -3
- xp/models/actiontable/msactiontable_xp33.py +27 -4
- xp/models/conbus/__init__.py +1 -0
- xp/models/conbus/conbus.py +34 -4
- xp/models/conbus/conbus_autoreport.py +20 -2
- xp/models/conbus/conbus_blink.py +22 -2
- xp/models/conbus/conbus_client_config.py +22 -1
- xp/models/conbus/conbus_connection_status.py +16 -2
- xp/models/conbus/conbus_custom.py +21 -2
- xp/models/conbus/conbus_datapoint.py +25 -2
- xp/models/conbus/conbus_discover.py +18 -2
- xp/models/conbus/conbus_lightlevel.py +20 -2
- xp/models/conbus/conbus_linknumber.py +20 -2
- xp/models/conbus/conbus_output.py +22 -2
- xp/models/conbus/conbus_raw.py +17 -2
- xp/models/conbus/conbus_receive.py +16 -2
- xp/models/conbus/conbus_writeconfig.py +60 -0
- xp/models/homekit/__init__.py +1 -0
- xp/models/homekit/homekit_accessory.py +15 -1
- xp/models/homekit/homekit_config.py +52 -0
- xp/models/homekit/homekit_conson_config.py +32 -0
- xp/models/log_entry.py +49 -9
- xp/models/protocol/__init__.py +1 -0
- xp/models/protocol/conbus_protocol.py +130 -21
- xp/models/telegram/__init__.py +1 -0
- xp/models/telegram/action_type.py +16 -2
- xp/models/telegram/datapoint_type.py +36 -2
- xp/models/telegram/event_telegram.py +46 -10
- xp/models/telegram/event_type.py +8 -1
- xp/models/telegram/input_action_type.py +34 -2
- xp/models/telegram/input_type.py +9 -1
- xp/models/telegram/module_type.py +69 -19
- xp/models/telegram/module_type_code.py +43 -1
- xp/models/telegram/output_telegram.py +30 -6
- xp/models/telegram/reply_telegram.py +56 -11
- xp/models/telegram/system_function.py +35 -3
- xp/models/telegram/system_telegram.py +18 -4
- xp/models/telegram/telegram.py +12 -3
- xp/models/telegram/telegram_type.py +8 -1
- xp/models/telegram/timeparam_type.py +27 -0
- xp/models/write_config_type.py +17 -2
- xp/services/__init__.py +1 -1
- xp/services/conbus/__init__.py +1 -0
- xp/services/conbus/actiontable/__init__.py +1 -0
- xp/services/conbus/actiontable/actiontable_service.py +33 -2
- xp/services/conbus/actiontable/msactiontable_service.py +40 -3
- xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +36 -4
- xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +45 -5
- xp/services/conbus/conbus_blink_all_service.py +40 -21
- xp/services/conbus/conbus_blink_service.py +37 -13
- xp/services/conbus/conbus_custom_service.py +29 -13
- xp/services/conbus/conbus_datapoint_queryall_service.py +40 -16
- xp/services/conbus/conbus_datapoint_service.py +42 -18
- xp/services/conbus/conbus_discover_service.py +43 -7
- xp/services/conbus/conbus_output_service.py +33 -13
- xp/services/conbus/conbus_raw_service.py +36 -16
- xp/services/conbus/conbus_receive_service.py +38 -6
- xp/services/conbus/conbus_scan_service.py +44 -18
- xp/services/conbus/write_config_service.py +193 -0
- xp/services/homekit/__init__.py +1 -0
- xp/services/homekit/homekit_cache_service.py +31 -6
- xp/services/homekit/homekit_conbus_service.py +33 -2
- xp/services/homekit/homekit_config_validator.py +97 -15
- xp/services/homekit/homekit_conson_validator.py +51 -7
- xp/services/homekit/homekit_dimminglight.py +47 -1
- xp/services/homekit/homekit_dimminglight_service.py +35 -1
- xp/services/homekit/homekit_hap_service.py +71 -18
- xp/services/homekit/homekit_lightbulb.py +35 -1
- xp/services/homekit/homekit_lightbulb_service.py +30 -2
- xp/services/homekit/homekit_module_service.py +23 -1
- xp/services/homekit/homekit_outlet.py +47 -1
- xp/services/homekit/homekit_outlet_service.py +44 -2
- xp/services/homekit/homekit_service.py +113 -19
- xp/services/log_file_service.py +37 -41
- xp/services/module_type_service.py +26 -5
- xp/services/protocol/__init__.py +1 -1
- xp/services/protocol/conbus_protocol.py +110 -16
- xp/services/protocol/protocol_factory.py +40 -0
- xp/services/protocol/telegram_protocol.py +38 -7
- xp/services/reverse_proxy_service.py +79 -14
- xp/services/server/__init__.py +1 -0
- xp/services/server/base_server_service.py +102 -14
- xp/services/server/cp20_server_service.py +12 -4
- xp/services/server/server_service.py +26 -11
- xp/services/server/xp130_server_service.py +11 -3
- xp/services/server/xp20_server_service.py +11 -3
- xp/services/server/xp230_server_service.py +11 -3
- xp/services/server/xp24_server_service.py +33 -6
- xp/services/server/xp33_server_service.py +41 -8
- xp/services/telegram/__init__.py +1 -0
- xp/services/telegram/telegram_blink_service.py +19 -31
- xp/services/telegram/telegram_checksum_service.py +10 -10
- xp/services/telegram/telegram_datapoint_service.py +70 -0
- xp/services/telegram/telegram_discover_service.py +58 -29
- xp/services/telegram/telegram_link_number_service.py +27 -40
- xp/services/telegram/telegram_output_service.py +46 -49
- xp/services/telegram/telegram_service.py +41 -41
- xp/services/telegram/telegram_version_service.py +4 -2
- xp/utils/__init__.py +1 -1
- xp/utils/dependencies.py +4 -47
- xp/utils/serialization.py +6 -0
- xp/utils/time_utils.py +6 -11
- conson_xp-1.1.0.dist-info/RECORD +0 -181
- xp/api/__init__.py +0 -1
- xp/api/main.py +0 -110
- xp/api/models/__init__.py +0 -1
- xp/api/models/api.py +0 -20
- xp/api/models/discover.py +0 -21
- xp/api/routers/__init__.py +0 -17
- xp/api/routers/conbus.py +0 -5
- xp/api/routers/conbus_blink.py +0 -105
- xp/api/routers/conbus_custom.py +0 -63
- xp/api/routers/conbus_datapoint.py +0 -67
- xp/api/routers/conbus_output.py +0 -147
- xp/api/routers/errors.py +0 -37
- xp/cli/commands/api.py +0 -16
- xp/cli/commands/api_start_commands.py +0 -126
- xp/services/conbus/conbus_autoreport_get_service.py +0 -85
- xp/services/conbus/conbus_autoreport_set_service.py +0 -128
- xp/services/conbus/conbus_lightlevel_get_service.py +0 -101
- xp/services/conbus/conbus_lightlevel_set_service.py +0 -205
- xp/services/conbus/conbus_linknumber_get_service.py +0 -86
- xp/services/conbus/conbus_linknumber_set_service.py +0 -155
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Module type models for the XP system."""
|
|
2
|
+
|
|
1
3
|
from dataclasses import dataclass
|
|
2
4
|
from typing import Dict, List, Optional
|
|
3
5
|
|
|
@@ -6,25 +8,35 @@ from xp.models.telegram.module_type_code import MODULE_TYPE_REGISTRY
|
|
|
6
8
|
|
|
7
9
|
@dataclass
|
|
8
10
|
class ModuleType:
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
+
"""Represents a module type in the XP system.
|
|
12
|
+
|
|
11
13
|
Contains the module code, name, and description.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
code: Numeric module type code.
|
|
17
|
+
name: Module name.
|
|
18
|
+
description: Module description.
|
|
19
|
+
module_category: Module category.
|
|
20
|
+
is_reserved: True if module type is reserved.
|
|
21
|
+
is_push_button_panel: True if module is a push button panel.
|
|
22
|
+
is_ir_capable: True if module has IR capabilities.
|
|
23
|
+
category: Module category based on its type.
|
|
12
24
|
"""
|
|
13
25
|
|
|
14
26
|
code: int
|
|
15
27
|
name: str
|
|
16
28
|
description: str
|
|
29
|
+
module_category: str = ""
|
|
17
30
|
|
|
18
31
|
@classmethod
|
|
19
32
|
def from_code(cls, code: int) -> Optional["ModuleType"]:
|
|
20
|
-
"""
|
|
21
|
-
Create ModuleType from a numeric code.
|
|
33
|
+
"""Create ModuleType from a numeric code.
|
|
22
34
|
|
|
23
35
|
Args:
|
|
24
|
-
code: The numeric module type code
|
|
36
|
+
code: The numeric module type code.
|
|
25
37
|
|
|
26
38
|
Returns:
|
|
27
|
-
ModuleType instance or None if code is invalid
|
|
39
|
+
ModuleType instance or None if code is invalid.
|
|
28
40
|
"""
|
|
29
41
|
module_info = MODULE_TYPE_REGISTRY.get(code)
|
|
30
42
|
if module_info:
|
|
@@ -33,14 +45,13 @@ class ModuleType:
|
|
|
33
45
|
|
|
34
46
|
@classmethod
|
|
35
47
|
def from_name(cls, name: str) -> Optional["ModuleType"]:
|
|
36
|
-
"""
|
|
37
|
-
Create ModuleType from a module name.
|
|
48
|
+
"""Create ModuleType from a module name.
|
|
38
49
|
|
|
39
50
|
Args:
|
|
40
|
-
name: The module name (case-insensitive)
|
|
51
|
+
name: The module name (case-insensitive).
|
|
41
52
|
|
|
42
53
|
Returns:
|
|
43
|
-
ModuleType instance or None if name is invalid
|
|
54
|
+
ModuleType instance or None if name is invalid.
|
|
44
55
|
"""
|
|
45
56
|
name_upper = name.upper()
|
|
46
57
|
for code, info in MODULE_TYPE_REGISTRY.items():
|
|
@@ -50,12 +61,20 @@ class ModuleType:
|
|
|
50
61
|
|
|
51
62
|
@property
|
|
52
63
|
def is_reserved(self) -> bool:
|
|
53
|
-
"""Check if this module type is reserved
|
|
64
|
+
"""Check if this module type is reserved.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
True if module type is reserved, False otherwise.
|
|
68
|
+
"""
|
|
54
69
|
return self.name in ("XP26X1", "XP26X2")
|
|
55
70
|
|
|
56
71
|
@property
|
|
57
72
|
def is_push_button_panel(self) -> bool:
|
|
58
|
-
"""Check if this module type is a push button panel
|
|
73
|
+
"""Check if this module type is a push button panel.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
True if module is a push button panel, False otherwise.
|
|
77
|
+
"""
|
|
59
78
|
return self.name in (
|
|
60
79
|
"XP2606",
|
|
61
80
|
"XP2606A",
|
|
@@ -68,7 +87,11 @@ class ModuleType:
|
|
|
68
87
|
|
|
69
88
|
@property
|
|
70
89
|
def is_ir_capable(self) -> bool:
|
|
71
|
-
"""Check if this module type has IR capabilities
|
|
90
|
+
"""Check if this module type has IR capabilities.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
True if module has IR capabilities, False otherwise.
|
|
94
|
+
"""
|
|
72
95
|
return any(ir_type in self.name for ir_type in ("38kHz", "B&O")) or any(
|
|
73
96
|
ir_code in self.name
|
|
74
97
|
for ir_code in (
|
|
@@ -83,7 +106,11 @@ class ModuleType:
|
|
|
83
106
|
|
|
84
107
|
@property
|
|
85
108
|
def category(self) -> str:
|
|
86
|
-
"""Get the module category based on its type
|
|
109
|
+
"""Get the module category based on its type.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Module category string.
|
|
113
|
+
"""
|
|
87
114
|
if self.code <= 1:
|
|
88
115
|
return "System"
|
|
89
116
|
elif 2 <= self.code <= 6:
|
|
@@ -95,7 +122,11 @@ class ModuleType:
|
|
|
95
122
|
return "Unknown"
|
|
96
123
|
|
|
97
124
|
def to_dict(self) -> Dict:
|
|
98
|
-
"""Convert to dictionary for JSON serialization
|
|
125
|
+
"""Convert to dictionary for JSON serialization.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Dictionary representation of the module type.
|
|
129
|
+
"""
|
|
99
130
|
return {
|
|
100
131
|
"code": self.code,
|
|
101
132
|
"name": self.name,
|
|
@@ -107,12 +138,20 @@ class ModuleType:
|
|
|
107
138
|
}
|
|
108
139
|
|
|
109
140
|
def __str__(self) -> str:
|
|
110
|
-
"""
|
|
141
|
+
"""Return human-readable string representation.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Formatted string representation.
|
|
145
|
+
"""
|
|
111
146
|
return f"{self.name} (Code {self.code}): {self.description}"
|
|
112
147
|
|
|
113
148
|
|
|
114
149
|
def get_all_module_types() -> List[ModuleType]:
|
|
115
|
-
"""Get all available module types
|
|
150
|
+
"""Get all available module types.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
List of all ModuleType instances.
|
|
154
|
+
"""
|
|
116
155
|
return [
|
|
117
156
|
module_type
|
|
118
157
|
for module_type in [
|
|
@@ -123,7 +162,11 @@ def get_all_module_types() -> List[ModuleType]:
|
|
|
123
162
|
|
|
124
163
|
|
|
125
164
|
def get_module_types_by_category() -> Dict[str, List[ModuleType]]:
|
|
126
|
-
"""Get module types grouped by category
|
|
165
|
+
"""Get module types grouped by category.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Dictionary mapping category names to lists of ModuleType instances.
|
|
169
|
+
"""
|
|
127
170
|
categories: Dict[str, List[ModuleType]] = {}
|
|
128
171
|
for module_type in get_all_module_types():
|
|
129
172
|
category = module_type.category
|
|
@@ -134,5 +177,12 @@ def get_module_types_by_category() -> Dict[str, List[ModuleType]]:
|
|
|
134
177
|
|
|
135
178
|
|
|
136
179
|
def is_valid_module_code(code: int) -> bool:
|
|
137
|
-
"""Check if a module code is valid
|
|
180
|
+
"""Check if a module code is valid.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
code: Module code to validate.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
True if code is valid, False otherwise.
|
|
187
|
+
"""
|
|
138
188
|
return code in MODULE_TYPE_REGISTRY
|
|
@@ -1,9 +1,51 @@
|
|
|
1
|
+
"""Module type code enumeration for XP system modules."""
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
4
|
from typing import Dict
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
class ModuleTypeCode(Enum):
|
|
6
|
-
"""Enum representing all XP system module type codes
|
|
8
|
+
"""Enum representing all XP system module type codes.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
NOMOD: No module (code 0).
|
|
12
|
+
ALLMOD: Matches every module type (code 1).
|
|
13
|
+
CP20: CP switch link module (code 2).
|
|
14
|
+
CP70A: CP 38kHz IR link module (code 3).
|
|
15
|
+
CP70B: CP B&O IR link module (code 4).
|
|
16
|
+
CP70C: CP UHF link module (code 5).
|
|
17
|
+
CP70D: CP timer link module (code 6).
|
|
18
|
+
XP24: XP relay module (code 7).
|
|
19
|
+
XP31UNI: XP universal load light dimmer (code 8).
|
|
20
|
+
XP31BCU: XP ballast controller 0-10V (code 9).
|
|
21
|
+
XP31DD: XP ballast controller DSI/DALI (code 10).
|
|
22
|
+
XP33: XP 3-channel light dimmer (code 11).
|
|
23
|
+
CP485: CP RS485 interface module (code 12).
|
|
24
|
+
XP130: Ethernet/TCP/IP interface module (code 13).
|
|
25
|
+
XP2606: 5-way push button panel with sesam (code 14).
|
|
26
|
+
XP2606A: 5-way push button with IR receiver (code 15).
|
|
27
|
+
XP2606B: 5-way push button with B&O IR (code 16).
|
|
28
|
+
XP26X1: Reserved (code 17).
|
|
29
|
+
XP26X2: Reserved (code 18).
|
|
30
|
+
XP2506: 5-way push button panel with sesam Conson design (code 19).
|
|
31
|
+
XP2506A: 5-way push button panel with sesam and 38kHz IR Conson design (code 20).
|
|
32
|
+
XP2506B: 5-way push button panel with sesam and B&O IR Conson design (code 21).
|
|
33
|
+
XPX1_8: 8-way push button panel interface (code 22).
|
|
34
|
+
XP134: Junctionbox interlink (code 23).
|
|
35
|
+
XP24P: XP24P module (code 24).
|
|
36
|
+
XP28A: XP28A module (code 25).
|
|
37
|
+
XP28B: XP28B module (code 26).
|
|
38
|
+
CONTOOL: CONTOOL module (code 27).
|
|
39
|
+
XP28: XP28 module (code 28).
|
|
40
|
+
XP31LR: XP 1-channel lightdimmer (code 29).
|
|
41
|
+
XP33LR: XP 33 3-channel lightdimmer (code 30).
|
|
42
|
+
XP31CR: XP 31 1-channel dimmer (code 31).
|
|
43
|
+
XP31BC: XP 31 1-channel dimmer (code 32).
|
|
44
|
+
XP20: XP switch link module (code 33).
|
|
45
|
+
XP230: Ethernet/TCPIP interface module (code 34).
|
|
46
|
+
XP33LED: XP 3-channel LED dimmer (code 35).
|
|
47
|
+
XP31LED: XP 1-channel LED dimmer (code 36).
|
|
48
|
+
"""
|
|
7
49
|
|
|
8
50
|
NOMOD = 0 # No module
|
|
9
51
|
ALLMOD = 1 # Code matching every moduletype
|
|
@@ -17,11 +17,18 @@ from xp.models.telegram.telegram import Telegram
|
|
|
17
17
|
|
|
18
18
|
@dataclass
|
|
19
19
|
class OutputTelegram(Telegram):
|
|
20
|
-
"""
|
|
21
|
-
Represents a parsed XP output telegram from the console bus.
|
|
20
|
+
"""Represent a parsed XP output telegram from the console bus.
|
|
22
21
|
|
|
23
22
|
Format: <S{serial_number}F27D{input:02d}{action}{checksum}>
|
|
24
23
|
Examples: <S0012345008F27D00AAFN>
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
serial_number: Serial number of the device.
|
|
27
|
+
output_number: Output number (0-3 for XP24, 0-2 for XP33, 0 for XP31).
|
|
28
|
+
action_type: Type of action to perform.
|
|
29
|
+
system_function: System function code.
|
|
30
|
+
action_description: Human-readable action description.
|
|
31
|
+
input_description: Human-readable input description.
|
|
25
32
|
"""
|
|
26
33
|
|
|
27
34
|
serial_number: str = ""
|
|
@@ -32,12 +39,17 @@ class OutputTelegram(Telegram):
|
|
|
32
39
|
system_function: SystemFunction = SystemFunction.ACTION
|
|
33
40
|
|
|
34
41
|
def __post_init__(self) -> None:
|
|
42
|
+
"""Initialize timestamp if not provided."""
|
|
35
43
|
if self.timestamp is None:
|
|
36
44
|
self.timestamp = datetime.now()
|
|
37
45
|
|
|
38
46
|
@property
|
|
39
47
|
def action_description(self) -> str:
|
|
40
|
-
"""Get human-readable action description
|
|
48
|
+
"""Get human-readable action description.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Human-readable description of the action.
|
|
52
|
+
"""
|
|
41
53
|
descriptions = {
|
|
42
54
|
ActionType.OFF_PRESS: "Press (Make)",
|
|
43
55
|
ActionType.ON_RELEASE: "Release (Break)",
|
|
@@ -50,11 +62,19 @@ class OutputTelegram(Telegram):
|
|
|
50
62
|
|
|
51
63
|
@property
|
|
52
64
|
def input_description(self) -> str:
|
|
53
|
-
"""Get human-readable input description
|
|
65
|
+
"""Get human-readable input description.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Description of the input/output number.
|
|
69
|
+
"""
|
|
54
70
|
return f"Input {self.output_number}"
|
|
55
71
|
|
|
56
72
|
def to_dict(self) -> dict:
|
|
57
|
-
"""Convert to dictionary for JSON serialization
|
|
73
|
+
"""Convert to dictionary for JSON serialization.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Dictionary representation of the output telegram.
|
|
77
|
+
"""
|
|
58
78
|
return {
|
|
59
79
|
"serial_number": self.serial_number,
|
|
60
80
|
"system_function": self.system_function,
|
|
@@ -71,7 +91,11 @@ class OutputTelegram(Telegram):
|
|
|
71
91
|
}
|
|
72
92
|
|
|
73
93
|
def __str__(self) -> str:
|
|
74
|
-
"""
|
|
94
|
+
"""Return human-readable string representation.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Formatted string representation.
|
|
98
|
+
"""
|
|
75
99
|
return (
|
|
76
100
|
f"XP Output: {self.action_description} "
|
|
77
101
|
f"on {self.input_description} "
|
|
@@ -16,11 +16,11 @@ from xp.models.telegram.telegram_type import TelegramType
|
|
|
16
16
|
|
|
17
17
|
@dataclass
|
|
18
18
|
class ReplyTelegram(Telegram):
|
|
19
|
-
"""
|
|
20
|
-
Represents a parsed reply telegram from the console bus.
|
|
19
|
+
"""Represents a parsed reply telegram from the console bus.
|
|
21
20
|
|
|
22
21
|
Format: <R{serial_number}F{function_code}D{data}{checksum}>
|
|
23
22
|
Format: <R{serial_number}F{function_code}D{datapoint_type}{data_value}{checksum}>
|
|
23
|
+
|
|
24
24
|
Examples:
|
|
25
25
|
- raw_telegram: <R0020012521F02D18+26,0§CIL>
|
|
26
26
|
- telegram_type : ReplyTelegram (R)
|
|
@@ -30,6 +30,14 @@ class ReplyTelegram(Telegram):
|
|
|
30
30
|
- datapoint_type: 18
|
|
31
31
|
- data_value: +26,0§C
|
|
32
32
|
- checksum: IL
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
serial_number: Serial number of the device.
|
|
36
|
+
system_function: System function code.
|
|
37
|
+
data: Raw data payload.
|
|
38
|
+
datapoint_type: Type of datapoint.
|
|
39
|
+
data_value: Parsed data value.
|
|
40
|
+
parse_datapoint_value: Parsed value based on datapoint type.
|
|
33
41
|
"""
|
|
34
42
|
|
|
35
43
|
serial_number: str = ""
|
|
@@ -39,13 +47,18 @@ class ReplyTelegram(Telegram):
|
|
|
39
47
|
data_value: str = ""
|
|
40
48
|
|
|
41
49
|
def __post_init__(self) -> None:
|
|
50
|
+
"""Initialize timestamp and telegram type."""
|
|
42
51
|
if self.timestamp is None:
|
|
43
52
|
self.timestamp = datetime.now()
|
|
44
53
|
self.telegram_type = TelegramType.REPLY
|
|
45
54
|
|
|
46
55
|
@property
|
|
47
56
|
def parse_datapoint_value(self) -> dict[str, Any]:
|
|
48
|
-
"""Parse the data value based on data point type
|
|
57
|
+
"""Parse the data value based on data point type.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dictionary containing parsed value and metadata.
|
|
61
|
+
"""
|
|
49
62
|
if self.datapoint_type == DataPointType.TEMPERATURE:
|
|
50
63
|
return self._parse_temperature_value()
|
|
51
64
|
elif self.datapoint_type == DataPointType.SW_TOP_VERSION:
|
|
@@ -61,7 +74,11 @@ class ReplyTelegram(Telegram):
|
|
|
61
74
|
return {"raw_value": self.data_value, "parsed": False}
|
|
62
75
|
|
|
63
76
|
def _parse_temperature_value(self) -> dict:
|
|
64
|
-
"""Parse temperature value like '+26,0§C'
|
|
77
|
+
"""Parse temperature value like '+26,0§C'.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Dictionary containing parsed temperature value and metadata.
|
|
81
|
+
"""
|
|
65
82
|
try:
|
|
66
83
|
# Remove unit indicator (§C)
|
|
67
84
|
value_part = self.data_value.replace("§C", "")
|
|
@@ -84,7 +101,11 @@ class ReplyTelegram(Telegram):
|
|
|
84
101
|
}
|
|
85
102
|
|
|
86
103
|
def _parse_humidity_value(self) -> dict:
|
|
87
|
-
"""Parse humidity value like '+65,5§H'
|
|
104
|
+
"""Parse humidity value like '+65,5§H'.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Dictionary containing parsed humidity value and metadata.
|
|
108
|
+
"""
|
|
88
109
|
try:
|
|
89
110
|
# Remove unit indicator (§H)
|
|
90
111
|
value_part = self.data_value.replace("§RH", "")
|
|
@@ -107,7 +128,11 @@ class ReplyTelegram(Telegram):
|
|
|
107
128
|
}
|
|
108
129
|
|
|
109
130
|
def _parse_voltage_value(self) -> dict:
|
|
110
|
-
"""Parse voltage value like '+12,5§V'
|
|
131
|
+
"""Parse voltage value like '+12,5§V'.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Dictionary containing parsed voltage value and metadata.
|
|
135
|
+
"""
|
|
111
136
|
try:
|
|
112
137
|
# Remove unit indicator (§V)
|
|
113
138
|
value_part = self.data_value.replace("§V", "")
|
|
@@ -130,7 +155,11 @@ class ReplyTelegram(Telegram):
|
|
|
130
155
|
}
|
|
131
156
|
|
|
132
157
|
def _parse_current_value(self) -> dict:
|
|
133
|
-
"""Parse current value like '+0,25§A'
|
|
158
|
+
"""Parse current value like '+0,25§A'.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Dictionary containing parsed current value and metadata.
|
|
162
|
+
"""
|
|
134
163
|
try:
|
|
135
164
|
# Remove unit indicator (§A)
|
|
136
165
|
value_part = self.data_value.replace("§A", "")
|
|
@@ -153,7 +182,11 @@ class ReplyTelegram(Telegram):
|
|
|
153
182
|
}
|
|
154
183
|
|
|
155
184
|
def _parse_module_type_value(self) -> dict:
|
|
156
|
-
"""Parse status value
|
|
185
|
+
"""Parse status value.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Dictionary containing parsed module type value.
|
|
189
|
+
"""
|
|
157
190
|
# Status values are typically alphanumeric codes
|
|
158
191
|
return {
|
|
159
192
|
"module_type": self.data_value,
|
|
@@ -162,7 +195,11 @@ class ReplyTelegram(Telegram):
|
|
|
162
195
|
}
|
|
163
196
|
|
|
164
197
|
def _parse_sw_version_value(self) -> dict:
|
|
165
|
-
"""Parse version value like 'XP230_V1.00.04'
|
|
198
|
+
"""Parse version value like 'XP230_V1.00.04'.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Dictionary containing parsed version information.
|
|
202
|
+
"""
|
|
166
203
|
try:
|
|
167
204
|
# Version format: {PRODUCT}_{VERSION}
|
|
168
205
|
# Examples: XP230_V1.00.04, XP20_V0.01.05, XP33LR_V0.04.02, XP24_V0.34.03
|
|
@@ -198,7 +235,11 @@ class ReplyTelegram(Telegram):
|
|
|
198
235
|
}
|
|
199
236
|
|
|
200
237
|
def to_dict(self) -> dict[str, Any]:
|
|
201
|
-
"""Convert to dictionary for JSON serialization
|
|
238
|
+
"""Convert to dictionary for JSON serialization.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Dictionary representation of the reply telegram.
|
|
242
|
+
"""
|
|
202
243
|
parsed_data = self.parse_datapoint_value
|
|
203
244
|
|
|
204
245
|
return {
|
|
@@ -234,7 +275,11 @@ class ReplyTelegram(Telegram):
|
|
|
234
275
|
}
|
|
235
276
|
|
|
236
277
|
def __str__(self) -> str:
|
|
237
|
-
"""Human-readable string representation
|
|
278
|
+
"""Human-readable string representation.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Formatted string representation.
|
|
282
|
+
"""
|
|
238
283
|
parsed = self.parse_datapoint_value
|
|
239
284
|
if parsed.get("parsed", False) and "formatted" in parsed:
|
|
240
285
|
value_display = parsed["formatted"]
|
|
@@ -1,9 +1,30 @@
|
|
|
1
|
+
"""System function enumeration for system telegrams."""
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
4
|
from typing import Optional
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
class SystemFunction(str, Enum):
|
|
6
|
-
"""System function codes for system telegrams
|
|
8
|
+
"""System function codes for system telegrams.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
NONE: Undefined function.
|
|
12
|
+
DISCOVERY: Discover function.
|
|
13
|
+
READ_DATAPOINT: Read datapoint.
|
|
14
|
+
READ_CONFIG: Read configuration.
|
|
15
|
+
WRITE_CONFIG: Write configuration.
|
|
16
|
+
BLINK: Blink LED function.
|
|
17
|
+
UNBLINK: Unblink LED function.
|
|
18
|
+
DOWNLOAD_MSACTIONTABLE: Download module specific action table.
|
|
19
|
+
DOWNLOAD_ACTIONTABLE: Download ActionTable.
|
|
20
|
+
EOF: End of msactiontable response.
|
|
21
|
+
MSACTIONTABLE: Module specific action table response.
|
|
22
|
+
ACTIONTABLE: Module specific action table response.
|
|
23
|
+
ACK: Acknowledge response.
|
|
24
|
+
NAK: Not acknowledge response.
|
|
25
|
+
UNKNOWN_26: Used after discover, unknown purpose.
|
|
26
|
+
ACTION: Action function.
|
|
27
|
+
"""
|
|
7
28
|
|
|
8
29
|
NONE = "00" # Undefined
|
|
9
30
|
DISCOVERY = "01" # Discover function
|
|
@@ -25,7 +46,11 @@ class SystemFunction(str, Enum):
|
|
|
25
46
|
ACTION = "27" # Action function
|
|
26
47
|
|
|
27
48
|
def get_description(self) -> str:
|
|
28
|
-
"""Get the description of the SystemFunction
|
|
49
|
+
"""Get the description of the SystemFunction.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Human-readable description of the function.
|
|
53
|
+
"""
|
|
29
54
|
return (
|
|
30
55
|
{
|
|
31
56
|
self.DISCOVERY: "Discover function",
|
|
@@ -47,7 +72,14 @@ class SystemFunction(str, Enum):
|
|
|
47
72
|
|
|
48
73
|
@classmethod
|
|
49
74
|
def from_code(cls, code: str) -> Optional["SystemFunction"]:
|
|
50
|
-
"""Get SystemFunction from code string
|
|
75
|
+
"""Get SystemFunction from code string.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
code: Function code string.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
SystemFunction instance if found, None otherwise.
|
|
82
|
+
"""
|
|
51
83
|
for func in cls:
|
|
52
84
|
if func.value.lower() == code.lower():
|
|
53
85
|
return func
|
|
@@ -16,11 +16,16 @@ from xp.models.telegram.telegram_type import TelegramType
|
|
|
16
16
|
|
|
17
17
|
@dataclass
|
|
18
18
|
class SystemTelegram(Telegram):
|
|
19
|
-
"""
|
|
20
|
-
Represents a parsed system telegram from the console bus.
|
|
19
|
+
"""Represents a parsed system telegram from the console bus.
|
|
21
20
|
|
|
22
21
|
Format: <S{serial_number}F{function_code}D{datapoint_type}{checksum}>
|
|
23
22
|
Examples: <S0020012521F02D18FN>
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
serial_number: Serial number of the device.
|
|
26
|
+
system_function: System function code.
|
|
27
|
+
data: Data payload.
|
|
28
|
+
datapoint_type: Type of datapoint.
|
|
24
29
|
"""
|
|
25
30
|
|
|
26
31
|
serial_number: str = ""
|
|
@@ -29,12 +34,17 @@ class SystemTelegram(Telegram):
|
|
|
29
34
|
datapoint_type: Optional[DataPointType] = None
|
|
30
35
|
|
|
31
36
|
def __post_init__(self) -> None:
|
|
37
|
+
"""Initialize timestamp and telegram type."""
|
|
32
38
|
if self.timestamp is None:
|
|
33
39
|
self.timestamp = datetime.now()
|
|
34
40
|
self.telegram_type = TelegramType.SYSTEM
|
|
35
41
|
|
|
36
42
|
def to_dict(self) -> dict[str, Any]:
|
|
37
|
-
"""Convert to dictionary for JSON serialization
|
|
43
|
+
"""Convert to dictionary for JSON serialization.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Dictionary representation of the system telegram.
|
|
47
|
+
"""
|
|
38
48
|
return {
|
|
39
49
|
"serial_number": self.serial_number,
|
|
40
50
|
"system_function": (
|
|
@@ -67,7 +77,11 @@ class SystemTelegram(Telegram):
|
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
def __str__(self) -> str:
|
|
70
|
-
"""Human-readable string representation
|
|
80
|
+
"""Human-readable string representation.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Formatted string representation.
|
|
84
|
+
"""
|
|
71
85
|
system_func_name = (
|
|
72
86
|
self.system_function.name if self.system_function else "Unknown"
|
|
73
87
|
)
|
xp/models/telegram/telegram.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Base telegram model for console bus communication."""
|
|
2
|
+
|
|
1
3
|
from dataclasses import dataclass
|
|
2
4
|
from datetime import datetime
|
|
3
5
|
from typing import Optional
|
|
@@ -7,9 +9,16 @@ from xp.models.telegram.telegram_type import TelegramType
|
|
|
7
9
|
|
|
8
10
|
@dataclass
|
|
9
11
|
class Telegram:
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
Can be an EventTelegram, SystemTelegram or ReplyTelegram
|
|
12
|
+
"""Represents an abstract telegram from the console bus.
|
|
13
|
+
|
|
14
|
+
Can be an EventTelegram, SystemTelegram or ReplyTelegram.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
checksum: Telegram checksum value.
|
|
18
|
+
raw_telegram: Raw telegram string.
|
|
19
|
+
checksum_validated: Whether checksum validation passed.
|
|
20
|
+
timestamp: Timestamp when telegram was received.
|
|
21
|
+
telegram_type: Type of telegram (EVENT, SYSTEM, or REPLY).
|
|
13
22
|
"""
|
|
14
23
|
|
|
15
24
|
checksum: str
|
|
@@ -4,7 +4,14 @@ from enum import Enum
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class TelegramType(str, Enum):
|
|
7
|
-
"""Enumeration of telegram types in the console bus system.
|
|
7
|
+
"""Enumeration of telegram types in the console bus system.
|
|
8
|
+
|
|
9
|
+
Attributes:
|
|
10
|
+
EVENT: Event telegram (E).
|
|
11
|
+
REPLY: Reply telegram (R).
|
|
12
|
+
SYSTEM: System telegram (S).
|
|
13
|
+
CPEVENT: CP event telegram (O).
|
|
14
|
+
"""
|
|
8
15
|
|
|
9
16
|
EVENT = "E"
|
|
10
17
|
REPLY = "R"
|
|
@@ -1,7 +1,34 @@
|
|
|
1
|
+
"""Time parameter enumeration for telegram actions."""
|
|
2
|
+
|
|
1
3
|
from enum import IntEnum
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class TimeParam(IntEnum):
|
|
7
|
+
"""Time parameter values for action timing.
|
|
8
|
+
|
|
9
|
+
Attributes:
|
|
10
|
+
NONE: No time parameter.
|
|
11
|
+
T05SEC: 0.5 second delay.
|
|
12
|
+
T1SEC: 1 second delay.
|
|
13
|
+
T2SEC: 2 second delay.
|
|
14
|
+
T5SEC: 5 second delay.
|
|
15
|
+
T10SEC: 10 second delay.
|
|
16
|
+
T15SEC: 15 second delay.
|
|
17
|
+
T20SEC: 20 second delay.
|
|
18
|
+
T30SEC: 30 second delay.
|
|
19
|
+
T45SEC: 45 second delay.
|
|
20
|
+
T1MIN: 1 minute delay.
|
|
21
|
+
T2MIN: 2 minute delay.
|
|
22
|
+
T5MIN: 5 minute delay.
|
|
23
|
+
T10MIN: 10 minute delay.
|
|
24
|
+
T15MIN: 15 minute delay.
|
|
25
|
+
T20MIN: 20 minute delay.
|
|
26
|
+
T30MIN: 30 minute delay.
|
|
27
|
+
T45MIN: 45 minute delay.
|
|
28
|
+
T60MIN: 60 minute delay.
|
|
29
|
+
T120MIN: 120 minute delay.
|
|
30
|
+
"""
|
|
31
|
+
|
|
5
32
|
NONE = 0
|
|
6
33
|
T05SEC = 1
|
|
7
34
|
T1SEC = 2
|
xp/models/write_config_type.py
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
"""Write config type enumeration."""
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
4
|
from typing import Optional
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
class WriteConfigType(str, Enum):
|
|
6
|
-
"""Write Config types for system telegrams
|
|
8
|
+
"""Write Config types for system telegrams.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
LINK_NUMBER: Link number configuration (code 04).
|
|
12
|
+
MODULE_NUMBER: Module number configuration (code 05).
|
|
13
|
+
SYSTEM_TYPE: System type configuration (code 06).
|
|
14
|
+
"""
|
|
7
15
|
|
|
8
16
|
LINK_NUMBER = "04"
|
|
9
17
|
MODULE_NUMBER = "05"
|
|
@@ -11,7 +19,14 @@ class WriteConfigType(str, Enum):
|
|
|
11
19
|
|
|
12
20
|
@classmethod
|
|
13
21
|
def from_code(cls, code: str) -> Optional["WriteConfigType"]:
|
|
14
|
-
"""Get
|
|
22
|
+
"""Get WriteConfigType from code string.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
code: Configuration type code string.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
WriteConfigType instance if found, None otherwise.
|
|
29
|
+
"""
|
|
15
30
|
for dp_type in cls:
|
|
16
31
|
if dp_type.value == code:
|
|
17
32
|
return dp_type
|
xp/services/__init__.py
CHANGED
xp/services/conbus/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Conbus service layer."""
|