conson-xp 1.18.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.
Files changed (176) hide show
  1. conson_xp-1.18.0.dist-info/METADATA +412 -0
  2. conson_xp-1.18.0.dist-info/RECORD +176 -0
  3. conson_xp-1.18.0.dist-info/WHEEL +4 -0
  4. conson_xp-1.18.0.dist-info/entry_points.txt +5 -0
  5. conson_xp-1.18.0.dist-info/licenses/LICENSE +29 -0
  6. xp/__init__.py +9 -0
  7. xp/cli/__init__.py +5 -0
  8. xp/cli/__main__.py +6 -0
  9. xp/cli/commands/__init__.py +153 -0
  10. xp/cli/commands/conbus/__init__.py +25 -0
  11. xp/cli/commands/conbus/conbus.py +128 -0
  12. xp/cli/commands/conbus/conbus_actiontable_commands.py +233 -0
  13. xp/cli/commands/conbus/conbus_autoreport_commands.py +108 -0
  14. xp/cli/commands/conbus/conbus_blink_commands.py +163 -0
  15. xp/cli/commands/conbus/conbus_config_commands.py +29 -0
  16. xp/cli/commands/conbus/conbus_custom_commands.py +57 -0
  17. xp/cli/commands/conbus/conbus_datapoint_commands.py +113 -0
  18. xp/cli/commands/conbus/conbus_discover_commands.py +61 -0
  19. xp/cli/commands/conbus/conbus_event_commands.py +81 -0
  20. xp/cli/commands/conbus/conbus_lightlevel_commands.py +207 -0
  21. xp/cli/commands/conbus/conbus_linknumber_commands.py +102 -0
  22. xp/cli/commands/conbus/conbus_modulenumber_commands.py +104 -0
  23. xp/cli/commands/conbus/conbus_msactiontable_commands.py +94 -0
  24. xp/cli/commands/conbus/conbus_output_commands.py +163 -0
  25. xp/cli/commands/conbus/conbus_raw_commands.py +62 -0
  26. xp/cli/commands/conbus/conbus_receive_commands.py +59 -0
  27. xp/cli/commands/conbus/conbus_scan_commands.py +58 -0
  28. xp/cli/commands/file_commands.py +186 -0
  29. xp/cli/commands/homekit/__init__.py +3 -0
  30. xp/cli/commands/homekit/homekit.py +118 -0
  31. xp/cli/commands/homekit/homekit_start_commands.py +43 -0
  32. xp/cli/commands/module_commands.py +187 -0
  33. xp/cli/commands/reverse_proxy_commands.py +178 -0
  34. xp/cli/commands/server/__init__.py +3 -0
  35. xp/cli/commands/server/server_commands.py +135 -0
  36. xp/cli/commands/telegram/__init__.py +5 -0
  37. xp/cli/commands/telegram/telegram.py +41 -0
  38. xp/cli/commands/telegram/telegram_blink_commands.py +79 -0
  39. xp/cli/commands/telegram/telegram_checksum_commands.py +112 -0
  40. xp/cli/commands/telegram/telegram_discover_commands.py +41 -0
  41. xp/cli/commands/telegram/telegram_linknumber_commands.py +86 -0
  42. xp/cli/commands/telegram/telegram_parse_commands.py +75 -0
  43. xp/cli/commands/telegram/telegram_version_commands.py +52 -0
  44. xp/cli/main.py +87 -0
  45. xp/cli/utils/__init__.py +1 -0
  46. xp/cli/utils/click_tree.py +57 -0
  47. xp/cli/utils/datapoint_type_choice.py +57 -0
  48. xp/cli/utils/decorators.py +351 -0
  49. xp/cli/utils/error_handlers.py +201 -0
  50. xp/cli/utils/formatters.py +312 -0
  51. xp/cli/utils/module_type_choice.py +56 -0
  52. xp/cli/utils/serial_number_type.py +52 -0
  53. xp/cli/utils/system_function_choice.py +57 -0
  54. xp/cli/utils/xp_module_type.py +53 -0
  55. xp/connection/__init__.py +13 -0
  56. xp/connection/exceptions.py +22 -0
  57. xp/models/__init__.py +36 -0
  58. xp/models/actiontable/__init__.py +1 -0
  59. xp/models/actiontable/actiontable.py +43 -0
  60. xp/models/actiontable/msactiontable_xp20.py +53 -0
  61. xp/models/actiontable/msactiontable_xp24.py +58 -0
  62. xp/models/actiontable/msactiontable_xp33.py +65 -0
  63. xp/models/conbus/__init__.py +1 -0
  64. xp/models/conbus/conbus.py +87 -0
  65. xp/models/conbus/conbus_autoreport.py +67 -0
  66. xp/models/conbus/conbus_blink.py +80 -0
  67. xp/models/conbus/conbus_client_config.py +55 -0
  68. xp/models/conbus/conbus_connection_status.py +40 -0
  69. xp/models/conbus/conbus_custom.py +58 -0
  70. xp/models/conbus/conbus_datapoint.py +89 -0
  71. xp/models/conbus/conbus_discover.py +64 -0
  72. xp/models/conbus/conbus_event_raw.py +47 -0
  73. xp/models/conbus/conbus_lightlevel.py +52 -0
  74. xp/models/conbus/conbus_linknumber.py +54 -0
  75. xp/models/conbus/conbus_output.py +57 -0
  76. xp/models/conbus/conbus_raw.py +45 -0
  77. xp/models/conbus/conbus_receive.py +42 -0
  78. xp/models/conbus/conbus_writeconfig.py +60 -0
  79. xp/models/homekit/__init__.py +1 -0
  80. xp/models/homekit/homekit_accessory.py +35 -0
  81. xp/models/homekit/homekit_config.py +106 -0
  82. xp/models/homekit/homekit_conson_config.py +86 -0
  83. xp/models/log_entry.py +130 -0
  84. xp/models/protocol/__init__.py +1 -0
  85. xp/models/protocol/conbus_protocol.py +312 -0
  86. xp/models/response.py +42 -0
  87. xp/models/telegram/__init__.py +1 -0
  88. xp/models/telegram/action_type.py +31 -0
  89. xp/models/telegram/datapoint_type.py +82 -0
  90. xp/models/telegram/event_telegram.py +140 -0
  91. xp/models/telegram/event_type.py +15 -0
  92. xp/models/telegram/input_action_type.py +69 -0
  93. xp/models/telegram/input_type.py +17 -0
  94. xp/models/telegram/module_type.py +188 -0
  95. xp/models/telegram/module_type_code.py +205 -0
  96. xp/models/telegram/output_telegram.py +103 -0
  97. xp/models/telegram/reply_telegram.py +297 -0
  98. xp/models/telegram/system_function.py +116 -0
  99. xp/models/telegram/system_telegram.py +94 -0
  100. xp/models/telegram/telegram.py +28 -0
  101. xp/models/telegram/telegram_type.py +19 -0
  102. xp/models/telegram/timeparam_type.py +51 -0
  103. xp/models/write_config_type.py +33 -0
  104. xp/services/__init__.py +26 -0
  105. xp/services/actiontable/__init__.py +1 -0
  106. xp/services/actiontable/actiontable_serializer.py +273 -0
  107. xp/services/actiontable/msactiontable_serializer.py +7 -0
  108. xp/services/actiontable/msactiontable_xp20_serializer.py +169 -0
  109. xp/services/actiontable/msactiontable_xp24_serializer.py +120 -0
  110. xp/services/actiontable/msactiontable_xp33_serializer.py +239 -0
  111. xp/services/conbus/__init__.py +1 -0
  112. xp/services/conbus/actiontable/__init__.py +1 -0
  113. xp/services/conbus/actiontable/actiontable_download_service.py +158 -0
  114. xp/services/conbus/actiontable/actiontable_list_service.py +91 -0
  115. xp/services/conbus/actiontable/actiontable_show_service.py +89 -0
  116. xp/services/conbus/actiontable/actiontable_upload_service.py +211 -0
  117. xp/services/conbus/actiontable/msactiontable_service.py +232 -0
  118. xp/services/conbus/conbus_blink_all_service.py +181 -0
  119. xp/services/conbus/conbus_blink_service.py +158 -0
  120. xp/services/conbus/conbus_custom_service.py +156 -0
  121. xp/services/conbus/conbus_datapoint_queryall_service.py +182 -0
  122. xp/services/conbus/conbus_datapoint_service.py +170 -0
  123. xp/services/conbus/conbus_discover_service.py +312 -0
  124. xp/services/conbus/conbus_event_raw_service.py +181 -0
  125. xp/services/conbus/conbus_output_service.py +194 -0
  126. xp/services/conbus/conbus_raw_service.py +122 -0
  127. xp/services/conbus/conbus_receive_service.py +115 -0
  128. xp/services/conbus/conbus_scan_service.py +150 -0
  129. xp/services/conbus/write_config_service.py +194 -0
  130. xp/services/homekit/__init__.py +1 -0
  131. xp/services/homekit/homekit_cache_service.py +307 -0
  132. xp/services/homekit/homekit_conbus_service.py +93 -0
  133. xp/services/homekit/homekit_config_validator.py +310 -0
  134. xp/services/homekit/homekit_conson_validator.py +121 -0
  135. xp/services/homekit/homekit_dimminglight.py +182 -0
  136. xp/services/homekit/homekit_dimminglight_service.py +148 -0
  137. xp/services/homekit/homekit_hap_service.py +342 -0
  138. xp/services/homekit/homekit_lightbulb.py +120 -0
  139. xp/services/homekit/homekit_lightbulb_service.py +86 -0
  140. xp/services/homekit/homekit_module_service.py +56 -0
  141. xp/services/homekit/homekit_outlet.py +168 -0
  142. xp/services/homekit/homekit_outlet_service.py +121 -0
  143. xp/services/homekit/homekit_service.py +359 -0
  144. xp/services/log_file_service.py +309 -0
  145. xp/services/module_type_service.py +257 -0
  146. xp/services/protocol/__init__.py +21 -0
  147. xp/services/protocol/conbus_event_protocol.py +360 -0
  148. xp/services/protocol/conbus_protocol.py +318 -0
  149. xp/services/protocol/protocol_factory.py +78 -0
  150. xp/services/protocol/telegram_protocol.py +264 -0
  151. xp/services/reverse_proxy_service.py +435 -0
  152. xp/services/server/__init__.py +1 -0
  153. xp/services/server/base_server_service.py +366 -0
  154. xp/services/server/cp20_server_service.py +65 -0
  155. xp/services/server/device_service_factory.py +94 -0
  156. xp/services/server/server_service.py +428 -0
  157. xp/services/server/xp130_server_service.py +67 -0
  158. xp/services/server/xp20_server_service.py +92 -0
  159. xp/services/server/xp230_server_service.py +58 -0
  160. xp/services/server/xp24_server_service.py +245 -0
  161. xp/services/server/xp33_server_service.py +535 -0
  162. xp/services/telegram/__init__.py +1 -0
  163. xp/services/telegram/telegram_blink_service.py +138 -0
  164. xp/services/telegram/telegram_checksum_service.py +149 -0
  165. xp/services/telegram/telegram_datapoint_service.py +82 -0
  166. xp/services/telegram/telegram_discover_service.py +277 -0
  167. xp/services/telegram/telegram_link_number_service.py +216 -0
  168. xp/services/telegram/telegram_output_service.py +322 -0
  169. xp/services/telegram/telegram_service.py +380 -0
  170. xp/services/telegram/telegram_version_service.py +288 -0
  171. xp/utils/__init__.py +12 -0
  172. xp/utils/checksum.py +61 -0
  173. xp/utils/dependencies.py +531 -0
  174. xp/utils/event_helper.py +31 -0
  175. xp/utils/serialization.py +205 -0
  176. xp/utils/time_utils.py +134 -0
@@ -0,0 +1,103 @@
1
+ """XP output telegram model for console bus communication.
2
+
3
+ XP output telegrams are used for controlling relay inputs on XP modules.
4
+ Each XP24 module has 4 inputs (0-3) that can be pressed or released.
5
+ Each XP33 module has 3 inputs (0-2) that can be pressed or released.
6
+ Each XP31 module has 1 inputs (0-0) that can be pressed or released.
7
+ """
8
+
9
+ from dataclasses import dataclass
10
+ from datetime import datetime
11
+ from typing import Optional
12
+
13
+ from xp.models.telegram.action_type import ActionType
14
+ from xp.models.telegram.system_function import SystemFunction
15
+ from xp.models.telegram.telegram import Telegram
16
+
17
+
18
+ @dataclass
19
+ class OutputTelegram(Telegram):
20
+ """Represent a parsed XP output telegram from the console bus.
21
+
22
+ Format: <S{serial_number}F27D{input:02d}{action}{checksum}>
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.
32
+ """
33
+
34
+ serial_number: str = ""
35
+ output_number: Optional[int] = (
36
+ None # 0-3 for XP24 modules, 0-2 for XP33, 0 for XP31
37
+ )
38
+ action_type: Optional[ActionType] = None
39
+ system_function: SystemFunction = SystemFunction.ACTION
40
+
41
+ def __post_init__(self) -> None:
42
+ """Initialize timestamp if not provided."""
43
+ if self.timestamp is None:
44
+ self.timestamp = datetime.now()
45
+
46
+ @property
47
+ def action_description(self) -> str:
48
+ """Get human-readable action description.
49
+
50
+ Returns:
51
+ Human-readable description of the action.
52
+ """
53
+ descriptions = {
54
+ ActionType.OFF_PRESS: "Press (Make)",
55
+ ActionType.ON_RELEASE: "Release (Break)",
56
+ }
57
+ return (
58
+ descriptions.get(self.action_type, "Unknown Action")
59
+ if self.action_type
60
+ else "Unknown Action"
61
+ )
62
+
63
+ @property
64
+ def input_description(self) -> str:
65
+ """Get human-readable input description.
66
+
67
+ Returns:
68
+ Description of the input/output number.
69
+ """
70
+ return f"Input {self.output_number}"
71
+
72
+ def to_dict(self) -> dict:
73
+ """Convert to dictionary for JSON serialization.
74
+
75
+ Returns:
76
+ Dictionary representation of the output telegram.
77
+ """
78
+ return {
79
+ "serial_number": self.serial_number,
80
+ "system_function": self.system_function,
81
+ "output_number": self.output_number,
82
+ "input_description": self.input_description,
83
+ "action_type": {
84
+ "code": self.action_type.value if self.action_type else None,
85
+ "description": self.action_description,
86
+ },
87
+ "checksum": self.checksum,
88
+ "checksum_validated": self.checksum_validated,
89
+ "raw_telegram": self.raw_telegram,
90
+ "timestamp": self.timestamp.isoformat() if self.timestamp else None,
91
+ }
92
+
93
+ def __str__(self) -> str:
94
+ """Return human-readable string representation.
95
+
96
+ Returns:
97
+ Formatted string representation.
98
+ """
99
+ return (
100
+ f"XP Output: {self.action_description} "
101
+ f"on {self.input_description} "
102
+ f"for device {self.serial_number}"
103
+ )
@@ -0,0 +1,297 @@
1
+ """Reply telegram model for console bus communication.
2
+
3
+ Reply telegrams are responses to system telegrams, containing the requested data
4
+ like temperature readings, status information, etc.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from datetime import datetime
9
+ from typing import Any, Optional
10
+
11
+ from xp.models.telegram.datapoint_type import DataPointType
12
+ from xp.models.telegram.system_function import SystemFunction
13
+ from xp.models.telegram.telegram import Telegram
14
+ from xp.models.telegram.telegram_type import TelegramType
15
+
16
+
17
+ @dataclass
18
+ class ReplyTelegram(Telegram):
19
+ """Represents a parsed reply telegram from the console bus.
20
+
21
+ Format: <R{serial_number}F{function_code}D{data}{checksum}>
22
+ Format: <R{serial_number}F{function_code}D{datapoint_type}{data_value}{checksum}>
23
+
24
+ Examples:
25
+ - raw_telegram: <R0020012521F02D18+26,0§CIL>
26
+ - telegram_type : ReplyTelegram (R)
27
+ - serial_number: 0020012521
28
+ - function_code: 02
29
+ - data: 18+26,0§C
30
+ - datapoint_type: 18
31
+ - data_value: +26,0§C
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.
41
+ """
42
+
43
+ serial_number: str = ""
44
+ system_function: SystemFunction = SystemFunction.NONE
45
+ data: str = ""
46
+ datapoint_type: Optional[DataPointType] = None
47
+ data_value: str = ""
48
+
49
+ def __post_init__(self) -> None:
50
+ """Initialize timestamp and telegram type."""
51
+ if self.timestamp is None:
52
+ self.timestamp = datetime.now()
53
+ self.telegram_type = TelegramType.REPLY
54
+
55
+ @property
56
+ def parse_datapoint_value(self) -> dict[str, Any]:
57
+ """Parse the data value based on data point type.
58
+
59
+ Returns:
60
+ Dictionary containing parsed value and metadata.
61
+ """
62
+ if self.datapoint_type == DataPointType.TEMPERATURE:
63
+ return self._parse_temperature_value()
64
+ elif self.datapoint_type == DataPointType.SW_TOP_VERSION:
65
+ return self._parse_humidity_value()
66
+ elif self.datapoint_type == DataPointType.VOLTAGE:
67
+ return self._parse_voltage_value()
68
+ elif self.datapoint_type == DataPointType.MODULE_ENERGY_LEVEL:
69
+ return self._parse_current_value()
70
+ elif self.datapoint_type == DataPointType.MODULE_TYPE:
71
+ return self._parse_module_type_value()
72
+ elif self.datapoint_type == DataPointType.SW_VERSION:
73
+ return self._parse_sw_version_value()
74
+ return {"raw_value": self.data_value, "parsed": False}
75
+
76
+ def _parse_temperature_value(self) -> dict:
77
+ """Parse temperature value like '+26,0§C'.
78
+
79
+ Returns:
80
+ Dictionary containing parsed temperature value and metadata.
81
+ """
82
+ try:
83
+ # Remove unit indicator (§C)
84
+ value_part = self.data_value.replace("§C", "")
85
+ # Replace comma with dot for decimal
86
+ value_str = value_part.replace(",", ".")
87
+ temperature = float(value_str)
88
+
89
+ return {
90
+ "value": temperature,
91
+ "unit": "°C",
92
+ "formatted": f"{temperature:.1f}°C",
93
+ "raw_value": self.data_value,
94
+ "parsed": True,
95
+ }
96
+ except (ValueError, AttributeError):
97
+ return {
98
+ "raw_value": self.data_value,
99
+ "parsed": False,
100
+ "error": "Failed to parse temperature",
101
+ }
102
+
103
+ def _parse_humidity_value(self) -> dict:
104
+ """Parse humidity value like '+65,5§H'.
105
+
106
+ Returns:
107
+ Dictionary containing parsed humidity value and metadata.
108
+ """
109
+ try:
110
+ # Remove unit indicator (§H)
111
+ value_part = self.data_value.replace("§RH", "")
112
+ # Replace comma with dot for decimal
113
+ value_str = value_part.replace(",", ".")
114
+ humidity = float(value_str)
115
+
116
+ return {
117
+ "value": humidity,
118
+ "unit": "%RH",
119
+ "formatted": f"{humidity:.1f}%RH",
120
+ "raw_value": self.data_value,
121
+ "parsed": True,
122
+ }
123
+ except (ValueError, AttributeError):
124
+ return {
125
+ "raw_value": self.data_value,
126
+ "parsed": False,
127
+ "error": "Failed to parse humidity",
128
+ }
129
+
130
+ def _parse_voltage_value(self) -> dict:
131
+ """Parse voltage value like '+12,5§V'.
132
+
133
+ Returns:
134
+ Dictionary containing parsed voltage value and metadata.
135
+ """
136
+ try:
137
+ # Remove unit indicator (§V)
138
+ value_part = self.data_value.replace("§V", "")
139
+ # Replace comma with dot for decimal
140
+ value_str = value_part.replace(",", ".")
141
+ voltage = float(value_str)
142
+
143
+ return {
144
+ "value": voltage,
145
+ "unit": "V",
146
+ "formatted": f"{voltage:.1f}V",
147
+ "raw_value": self.data_value,
148
+ "parsed": True,
149
+ }
150
+ except (ValueError, AttributeError):
151
+ return {
152
+ "raw_value": self.data_value,
153
+ "parsed": False,
154
+ "error": "Failed to parse voltage",
155
+ }
156
+
157
+ def _parse_current_value(self) -> dict:
158
+ """Parse current value like '+0,25§A'.
159
+
160
+ Returns:
161
+ Dictionary containing parsed current value and metadata.
162
+ """
163
+ try:
164
+ # Remove unit indicator (§A)
165
+ value_part = self.data_value.replace("§A", "")
166
+ # Replace comma with dot for decimal
167
+ value_str = value_part.replace(",", ".")
168
+ current = float(value_str)
169
+
170
+ return {
171
+ "value": current,
172
+ "unit": "A",
173
+ "formatted": f"{current:.2f}A",
174
+ "raw_value": self.data_value,
175
+ "parsed": True,
176
+ }
177
+ except (ValueError, AttributeError):
178
+ return {
179
+ "raw_value": self.data_value,
180
+ "parsed": False,
181
+ "error": "Failed to parse current",
182
+ }
183
+
184
+ def _parse_module_type_value(self) -> dict:
185
+ """Parse status value.
186
+
187
+ Returns:
188
+ Dictionary containing parsed module type value.
189
+ """
190
+ # Status values are typically alphanumeric codes
191
+ return {
192
+ "module_type": self.data_value,
193
+ "raw_value": self.data_value,
194
+ "parsed": True,
195
+ }
196
+
197
+ def _parse_sw_version_value(self) -> dict:
198
+ """Parse version value like 'XP230_V1.00.04'.
199
+
200
+ Returns:
201
+ Dictionary containing parsed version information.
202
+ """
203
+ try:
204
+ # Version format: {PRODUCT}_{VERSION}
205
+ # Examples: XP230_V1.00.04, XP20_V0.01.05, XP33LR_V0.04.02, XP24_V0.34.03
206
+ if "_V" in self.data_value:
207
+ parts = self.data_value.split("_V", 1)
208
+ if len(parts) == 2:
209
+ product = parts[0]
210
+ version = parts[1]
211
+
212
+ return {
213
+ "product": product,
214
+ "version": version,
215
+ "full_version": self.data_value,
216
+ "formatted": f"{product} v{version}",
217
+ "raw_value": self.data_value,
218
+ "parsed": True,
219
+ }
220
+
221
+ # If format doesn't match expected pattern, treat as raw
222
+ return {
223
+ "full_version": self.data_value,
224
+ "formatted": self.data_value,
225
+ "raw_value": self.data_value,
226
+ "parsed": False,
227
+ "error": "Version format not recognized",
228
+ }
229
+
230
+ except (ValueError, AttributeError):
231
+ return {
232
+ "raw_value": self.data_value,
233
+ "parsed": False,
234
+ "error": "Failed to parse version",
235
+ }
236
+
237
+ def to_dict(self) -> dict[str, Any]:
238
+ """Convert to dictionary for JSON serialization.
239
+
240
+ Returns:
241
+ Dictionary representation of the reply telegram.
242
+ """
243
+ parsed_data = self.parse_datapoint_value
244
+
245
+ return {
246
+ "serial_number": self.serial_number,
247
+ "system_function": (
248
+ {
249
+ "code": (
250
+ self.system_function.value if self.system_function else None
251
+ ),
252
+ "description": (
253
+ self.system_function.name if self.system_function else None
254
+ ),
255
+ }
256
+ if self.system_function
257
+ else None
258
+ ),
259
+ "datapoint_type": (
260
+ {
261
+ "code": self.datapoint_type.value if self.datapoint_type else None,
262
+ "description": (
263
+ self.datapoint_type.name if self.datapoint_type else None
264
+ ),
265
+ }
266
+ if self.datapoint_type
267
+ else None
268
+ ),
269
+ "data_value": {"raw": self.data_value, "parsed": parsed_data},
270
+ "checksum": self.checksum,
271
+ "checksum_validated": self.checksum_validated,
272
+ "raw_telegram": self.raw_telegram,
273
+ "timestamp": self.timestamp.isoformat() if self.timestamp else None,
274
+ "telegram_type": self.telegram_type.value,
275
+ }
276
+
277
+ def __str__(self) -> str:
278
+ """Human-readable string representation.
279
+
280
+ Returns:
281
+ Formatted string representation.
282
+ """
283
+ parsed = self.parse_datapoint_value
284
+ if parsed.get("parsed", False) and "formatted" in parsed:
285
+ value_display = parsed["formatted"]
286
+ else:
287
+ value_display = self.data_value
288
+
289
+ system_func_name = (
290
+ self.system_function.name if self.system_function else "Unknown"
291
+ )
292
+ datapoint_name = self.datapoint_type.name if self.datapoint_type else "Unknown"
293
+ return (
294
+ f"Reply Telegram: {system_func_name}\n "
295
+ f"for {datapoint_name} = {value_display} "
296
+ f"from device {self.serial_number}"
297
+ )
@@ -0,0 +1,116 @@
1
+ """System function enumeration for system telegrams."""
2
+
3
+ from enum import Enum
4
+ from typing import Optional
5
+
6
+
7
+ class SystemFunction(str, Enum):
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
+ UPLOAD_FIRMWARE_START: Start upload firmware.
19
+ UPLOAD_FIRMWARE_STOP: Stop upload firmware.
20
+ UPLOAD_FIRMWARE: Upload firmware.
21
+ UPLOAD_ACTIONTABLE: Upload ActionTable to module.
22
+ DOWNLOAD_ACTIONTABLE: Download ActionTable.
23
+ UPLOAD_MSACTIONTABLE: Upload module specific action table to module.
24
+ DOWNLOAD_MSACTIONTABLE: Download module specific action table.
25
+ TELEGRAM_WRITE_START: Start writing telegram.
26
+ TELEGRAM_READ_START: Start reading telegram.
27
+ EOF: End of msactiontable response.
28
+ TELEGRAM: Module specific telegram response.
29
+ MSACTIONTABLE: Module specific action table response.
30
+ ACTIONTABLE: Module specific action table response.
31
+ ACK: Acknowledge response.
32
+ NAK: Not acknowledge response.
33
+ UPLOAD_TOP_FIRMWARE_START: Start upload firmware (TOP).
34
+ UPLOAD_TOP_FIRMWARE_STOP: Stop upload firmware (TOP).
35
+ UPLOAD_TOP_FIRMWARE: Upload firmware (TOP).
36
+ ROTATE_ENABLE: Enable rotate.
37
+ ROTATE_DISABLE: Disable rotate.
38
+ UNKNOWN_26: Used after discover, unknown purpose.
39
+ ACTION: Action function.
40
+ """
41
+
42
+ NONE = "00" # F00D Undefined
43
+ DISCOVERY = "01" # F01D Discover function
44
+ READ_DATAPOINT = "02" # F02D Read datapoint
45
+ READ_CONFIG = "03" # F03D Read configuration
46
+ WRITE_CONFIG = "04" # F04D Write configuration
47
+ BLINK = "05" # F05D Blink LED function
48
+ UNBLINK = "06" # F06D Unblink LED function
49
+
50
+ UPLOAD_FIRMWARE_START = "07" # F07D Start Upload firmware
51
+ UPLOAD_FIRMWARE_STOP = "08" # F08D Stop Upload firmware
52
+ UPLOAD_FIRMWARE = "09" # F09D Upload firmware
53
+
54
+ UPLOAD_ACTIONTABLE = "10" # F10D Upload ActionTable
55
+ DOWNLOAD_ACTIONTABLE = "11" # F11D Download ActionTable
56
+ UPLOAD_MSACTIONTABLE = "12" # F12D Upload MsActionTable to module
57
+ DOWNLOAD_MSACTIONTABLE = "13" # F13D Download MsActionTable
58
+
59
+ TELEGRAM_WRITE_START = "14" # F14D Start writing telegram
60
+ TELEGRAM_READ_START = "15" # F15D Start reading telegram
61
+ EOF = "16" # F16D End of msactiontable response
62
+ TELEGRAM = "17" # F17D module specific Telegram response
63
+ MSACTIONTABLE = "17" # F17D module specific ms action table (Telegram) response
64
+ ACTIONTABLE = "17" # F17D module specific action table (Telegram) response
65
+ ACK = "18" # F18D Acknowledge / continue response
66
+ NAK = "19" # F19D Not acknowledge response
67
+
68
+ UPLOAD_TOP_FIRMWARE_START = "20" # F20D Start Upload firmware (TOP)
69
+ UPLOAD_TOP_FIRMWARE_STOP = "21" # F21D Stop Upload firmware (TOP)
70
+ UPLOAD_TOP_FIRMWARE = "22" # F22D Upload firmware (TOP)
71
+
72
+ ROTATE_ENABLE = "23" # F23D Enable rotate
73
+ ROTATE_DISABLE = "24" # F24D Disable rotate
74
+
75
+ UNKNOWN_26 = "26" # F26D Used after discover, but don't know what it is
76
+ ACTION = "27" # F27D Action function
77
+
78
+ def get_description(self) -> str:
79
+ """Get the description of the SystemFunction.
80
+
81
+ Returns:
82
+ Human-readable description of the function.
83
+ """
84
+ return (
85
+ {
86
+ self.DISCOVERY: "Discover function",
87
+ self.READ_DATAPOINT: "Read datapoint",
88
+ self.READ_CONFIG: "Read configuration",
89
+ self.WRITE_CONFIG: "Write configuration",
90
+ self.BLINK: "Blink LED function",
91
+ self.DOWNLOAD_MSACTIONTABLE: "Download the msactiontable",
92
+ self.DOWNLOAD_ACTIONTABLE: "Download ActionTable",
93
+ self.EOF: "End of msactiontable response",
94
+ self.ACTIONTABLE: "Actiontable response",
95
+ self.MSACTIONTABLE: "Msactiontable response",
96
+ self.UNBLINK: "Unblink LED function",
97
+ self.ACK: "Acknowledge response",
98
+ self.NAK: "Not acknowledge response",
99
+ self.ACTION: "Action function",
100
+ }
101
+ ).get(self, "Unknown function")
102
+
103
+ @classmethod
104
+ def from_code(cls, code: str) -> Optional["SystemFunction"]:
105
+ """Get SystemFunction from code string.
106
+
107
+ Args:
108
+ code: Function code string.
109
+
110
+ Returns:
111
+ SystemFunction instance if found, None otherwise.
112
+ """
113
+ for func in cls:
114
+ if func.value.lower() == code.lower():
115
+ return func
116
+ return None
@@ -0,0 +1,94 @@
1
+ """System telegram model for console bus communication.
2
+
3
+ System telegrams are used for system-related information like updating firmware
4
+ and reading temperature from modules.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from datetime import datetime
9
+ from typing import Any, Optional
10
+
11
+ from xp.models.telegram.datapoint_type import DataPointType
12
+ from xp.models.telegram.system_function import SystemFunction
13
+ from xp.models.telegram.telegram import Telegram
14
+ from xp.models.telegram.telegram_type import TelegramType
15
+
16
+
17
+ @dataclass
18
+ class SystemTelegram(Telegram):
19
+ """Represents a parsed system telegram from the console bus.
20
+
21
+ Format: <S{serial_number}F{function_code}D{datapoint_type}{checksum}>
22
+ Examples: <S0020012521F02D18FN>
23
+
24
+ Attributes:
25
+ serial_number: Serial number of the device (0020012521)
26
+ system_function: System function code (02).
27
+ data: Data payload (18)
28
+ datapoint_type: Type of datapoint (18).
29
+ """
30
+
31
+ serial_number: str = ""
32
+ system_function: Optional[SystemFunction] = None
33
+ data: str = ""
34
+ datapoint_type: Optional[DataPointType] = None
35
+
36
+ def __post_init__(self) -> None:
37
+ """Initialize timestamp and telegram type."""
38
+ if self.timestamp is None:
39
+ self.timestamp = datetime.now()
40
+ self.telegram_type = TelegramType.SYSTEM
41
+
42
+ def to_dict(self) -> dict[str, Any]:
43
+ """Convert to dictionary for JSON serialization.
44
+
45
+ Returns:
46
+ Dictionary representation of the system telegram.
47
+ """
48
+ return {
49
+ "serial_number": self.serial_number,
50
+ "system_function": (
51
+ {
52
+ "code": (
53
+ self.system_function.value if self.system_function else None
54
+ ),
55
+ "description": (
56
+ self.system_function.name if self.system_function else None
57
+ ),
58
+ }
59
+ if self.system_function
60
+ else None
61
+ ),
62
+ "datapoint_type": (
63
+ {
64
+ "code": self.datapoint_type.value if self.datapoint_type else None,
65
+ "description": (
66
+ self.datapoint_type.name if self.datapoint_type else None
67
+ ),
68
+ }
69
+ if self.datapoint_type
70
+ else None
71
+ ),
72
+ "checksum": self.checksum,
73
+ "checksum_validated": self.checksum_validated,
74
+ "raw_telegram": self.raw_telegram,
75
+ "timestamp": self.timestamp.isoformat() if self.timestamp else None,
76
+ "telegram_type": self.telegram_type.value,
77
+ }
78
+
79
+ def __str__(self) -> str:
80
+ """Human-readable string representation.
81
+
82
+ Returns:
83
+ Formatted string representation.
84
+ """
85
+ system_func_name = (
86
+ self.system_function.name if self.system_function else "Unknown"
87
+ )
88
+ data = self.data or "None"
89
+ data = self.datapoint_type.name if self.datapoint_type else data
90
+ return (
91
+ f"System Telegram: {system_func_name} "
92
+ f"with data {data} "
93
+ f"from device {self.serial_number}"
94
+ )
@@ -0,0 +1,28 @@
1
+ """Base telegram model for console bus communication."""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from typing import Optional
6
+
7
+ from xp.models.telegram.telegram_type import TelegramType
8
+
9
+
10
+ @dataclass
11
+ class Telegram:
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).
22
+ """
23
+
24
+ checksum: str
25
+ raw_telegram: str
26
+ checksum_validated: Optional[bool] = None
27
+ timestamp: Optional[datetime] = None
28
+ telegram_type: TelegramType = TelegramType.EVENT
@@ -0,0 +1,19 @@
1
+ """Telegram type enumeration for console bus communication."""
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class TelegramType(str, Enum):
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
+ """
15
+
16
+ EVENT = "E"
17
+ REPLY = "R"
18
+ SYSTEM = "S"
19
+ CPEVENT = "O"