conson-xp 1.46.0__py3-none-any.whl → 1.48.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 (179) hide show
  1. {conson_xp-1.46.0.dist-info → conson_xp-1.48.0.dist-info}/METADATA +1 -1
  2. conson_xp-1.48.0.dist-info/RECORD +210 -0
  3. xp/__init__.py +3 -2
  4. xp/cli/commands/conbus/conbus.py +1 -1
  5. xp/cli/commands/conbus/conbus_actiontable_commands.py +33 -19
  6. xp/cli/commands/conbus/conbus_autoreport_commands.py +8 -4
  7. xp/cli/commands/conbus/conbus_blink_commands.py +20 -10
  8. xp/cli/commands/conbus/conbus_config_commands.py +2 -1
  9. xp/cli/commands/conbus/conbus_custom_commands.py +4 -2
  10. xp/cli/commands/conbus/conbus_datapoint_commands.py +10 -5
  11. xp/cli/commands/conbus/conbus_discover_commands.py +8 -4
  12. xp/cli/commands/conbus/conbus_event_commands.py +8 -4
  13. xp/cli/commands/conbus/conbus_export_commands.py +8 -4
  14. xp/cli/commands/conbus/conbus_lightlevel_commands.py +16 -8
  15. xp/cli/commands/conbus/conbus_linknumber_commands.py +8 -4
  16. xp/cli/commands/conbus/conbus_modulenumber_commands.py +8 -4
  17. xp/cli/commands/conbus/conbus_msactiontable_commands.py +78 -40
  18. xp/cli/commands/conbus/conbus_output_commands.py +16 -8
  19. xp/cli/commands/conbus/conbus_raw_commands.py +6 -3
  20. xp/cli/commands/conbus/conbus_receive_commands.py +6 -3
  21. xp/cli/commands/conbus/conbus_scan_commands.py +6 -3
  22. xp/cli/commands/file_commands.py +6 -3
  23. xp/cli/commands/homekit/homekit.py +4 -2
  24. xp/cli/commands/homekit/homekit_start_commands.py +2 -1
  25. xp/cli/commands/module_commands.py +8 -4
  26. xp/cli/commands/reverse_proxy_commands.py +8 -4
  27. xp/cli/commands/server/server_commands.py +6 -3
  28. xp/cli/commands/telegram/telegram_blink_commands.py +4 -2
  29. xp/cli/commands/telegram/telegram_checksum_commands.py +4 -2
  30. xp/cli/commands/telegram/telegram_discover_commands.py +2 -1
  31. xp/cli/commands/telegram/telegram_linknumber_commands.py +4 -2
  32. xp/cli/commands/telegram/telegram_parse_commands.py +4 -2
  33. xp/cli/commands/telegram/telegram_version_commands.py +2 -1
  34. xp/cli/commands/term/term_commands.py +4 -2
  35. xp/cli/main.py +2 -1
  36. xp/cli/utils/click_tree.py +6 -3
  37. xp/cli/utils/datapoint_type_choice.py +4 -2
  38. xp/cli/utils/decorators.py +42 -21
  39. xp/cli/utils/error_handlers.py +16 -8
  40. xp/cli/utils/formatters.py +22 -11
  41. xp/cli/utils/module_type_choice.py +4 -2
  42. xp/cli/utils/serial_number_type.py +4 -2
  43. xp/cli/utils/system_function_choice.py +4 -2
  44. xp/cli/utils/xp_module_type.py +4 -2
  45. xp/models/actiontable/actiontable.py +8 -8
  46. xp/models/actiontable/actiontable_type.py +20 -0
  47. xp/models/actiontable/msactiontable_xp20.py +8 -4
  48. xp/models/actiontable/msactiontable_xp24.py +12 -6
  49. xp/models/actiontable/msactiontable_xp33.py +20 -10
  50. xp/models/conbus/conbus.py +8 -4
  51. xp/models/conbus/conbus_autoreport.py +4 -2
  52. xp/models/conbus/conbus_blink.py +4 -2
  53. xp/models/conbus/conbus_client_config.py +6 -3
  54. xp/models/conbus/conbus_connection_status.py +4 -2
  55. xp/models/conbus/conbus_custom.py +4 -2
  56. xp/models/conbus/conbus_datapoint.py +4 -2
  57. xp/models/conbus/conbus_discover.py +6 -3
  58. xp/models/conbus/conbus_event_list.py +4 -2
  59. xp/models/conbus/conbus_event_raw.py +4 -2
  60. xp/models/conbus/conbus_export.py +2 -1
  61. xp/models/conbus/conbus_lightlevel.py +4 -2
  62. xp/models/conbus/conbus_linknumber.py +4 -2
  63. xp/models/conbus/conbus_logger_config.py +8 -4
  64. xp/models/conbus/conbus_output.py +4 -2
  65. xp/models/conbus/conbus_raw.py +4 -2
  66. xp/models/conbus/conbus_receive.py +4 -2
  67. xp/models/conbus/conbus_writeconfig.py +4 -2
  68. xp/models/config/conson_module_config.py +8 -4
  69. xp/models/homekit/homekit_accessory.py +4 -2
  70. xp/models/homekit/homekit_config.py +12 -6
  71. xp/models/log_entry.py +16 -8
  72. xp/models/protocol/conbus_protocol.py +36 -18
  73. xp/models/response.py +12 -8
  74. xp/models/telegram/action_type.py +4 -2
  75. xp/models/telegram/datapoint_type.py +4 -2
  76. xp/models/telegram/event_telegram.py +14 -7
  77. xp/models/telegram/event_type.py +2 -1
  78. xp/models/telegram/input_action_type.py +2 -1
  79. xp/models/telegram/input_type.py +2 -1
  80. xp/models/telegram/module_type.py +24 -12
  81. xp/models/telegram/module_type_code.py +2 -1
  82. xp/models/telegram/output_telegram.py +16 -10
  83. xp/models/telegram/reply_telegram.py +24 -13
  84. xp/models/telegram/system_function.py +6 -3
  85. xp/models/telegram/system_telegram.py +10 -6
  86. xp/models/telegram/telegram.py +2 -1
  87. xp/models/telegram/telegram_type.py +2 -1
  88. xp/models/telegram/timeparam_type.py +2 -1
  89. xp/models/term/connection_state.py +4 -2
  90. xp/models/term/module_state.py +2 -1
  91. xp/models/term/protocol_keys_config.py +6 -3
  92. xp/models/term/status_message.py +2 -1
  93. xp/models/term/telegram_display.py +2 -1
  94. xp/models/write_config_type.py +4 -2
  95. xp/services/actiontable/actiontable_serializer.py +34 -41
  96. xp/services/{conbus/actiontable/actiontable_download_state_machine.py → actiontable/download_state_machine.py} +13 -8
  97. xp/services/actiontable/msactiontable_xp20_serializer.py +73 -50
  98. xp/services/actiontable/msactiontable_xp24_serializer.py +73 -54
  99. xp/services/actiontable/msactiontable_xp33_serializer.py +44 -20
  100. xp/services/actiontable/serializer_protocol.py +76 -0
  101. xp/services/conbus/actiontable/actiontable_download_service.py +68 -31
  102. xp/services/conbus/actiontable/actiontable_list_service.py +17 -4
  103. xp/services/conbus/actiontable/actiontable_show_service.py +10 -6
  104. xp/services/conbus/actiontable/actiontable_upload_service.py +17 -9
  105. xp/services/conbus/conbus_blink_all_service.py +16 -8
  106. xp/services/conbus/conbus_blink_service.py +14 -7
  107. xp/services/conbus/conbus_custom_service.py +16 -8
  108. xp/services/conbus/conbus_datapoint_queryall_service.py +18 -9
  109. xp/services/conbus/conbus_datapoint_service.py +18 -9
  110. xp/services/conbus/conbus_discover_service.py +24 -13
  111. xp/services/conbus/conbus_event_list_service.py +11 -7
  112. xp/services/conbus/conbus_event_raw_service.py +18 -10
  113. xp/services/conbus/conbus_export_service.py +28 -14
  114. xp/services/conbus/conbus_output_service.py +18 -10
  115. xp/services/conbus/conbus_raw_service.py +16 -8
  116. xp/services/conbus/conbus_receive_service.py +18 -10
  117. xp/services/conbus/conbus_scan_service.py +18 -10
  118. xp/services/conbus/msactiontable/msactiontable_upload_service.py +18 -10
  119. xp/services/conbus/write_config_service.py +18 -9
  120. xp/services/homekit/homekit_cache_service.py +12 -6
  121. xp/services/homekit/homekit_conbus_service.py +12 -6
  122. xp/services/homekit/homekit_config_validator.py +34 -17
  123. xp/services/homekit/homekit_conson_validator.py +18 -9
  124. xp/services/homekit/homekit_dimminglight.py +14 -7
  125. xp/services/homekit/homekit_dimminglight_service.py +14 -7
  126. xp/services/homekit/homekit_hap_service.py +18 -9
  127. xp/services/homekit/homekit_lightbulb.py +10 -5
  128. xp/services/homekit/homekit_lightbulb_service.py +10 -5
  129. xp/services/homekit/homekit_module_service.py +8 -4
  130. xp/services/homekit/homekit_outlet.py +14 -7
  131. xp/services/homekit/homekit_outlet_service.py +12 -6
  132. xp/services/homekit/homekit_service.py +24 -12
  133. xp/services/log_file_service.py +16 -8
  134. xp/services/module_type_service.py +10 -5
  135. xp/services/protocol/conbus_event_protocol.py +51 -26
  136. xp/services/protocol/conbus_protocol.py +36 -19
  137. xp/services/protocol/protocol_factory.py +12 -6
  138. xp/services/protocol/telegram_protocol.py +12 -6
  139. xp/services/reverse_proxy_service.py +26 -14
  140. xp/services/server/base_server_service.py +42 -23
  141. xp/services/server/client_buffer_manager.py +12 -7
  142. xp/services/server/cp20_server_service.py +10 -7
  143. xp/services/server/device_service_factory.py +12 -8
  144. xp/services/server/server_service.py +18 -11
  145. xp/services/server/xp130_server_service.py +11 -8
  146. xp/services/server/xp20_server_service.py +16 -10
  147. xp/services/server/xp230_server_service.py +10 -7
  148. xp/services/server/xp24_server_service.py +22 -13
  149. xp/services/server/xp33_server_service.py +44 -25
  150. xp/services/telegram/telegram_blink_service.py +14 -8
  151. xp/services/telegram/telegram_checksum_service.py +12 -7
  152. xp/services/telegram/telegram_datapoint_service.py +14 -9
  153. xp/services/telegram/telegram_discover_service.py +28 -15
  154. xp/services/telegram/telegram_link_number_service.py +18 -10
  155. xp/services/telegram/telegram_output_service.py +24 -12
  156. xp/services/telegram/telegram_service.py +22 -11
  157. xp/services/telegram/telegram_version_service.py +14 -8
  158. xp/services/term/protocol_monitor_service.py +30 -16
  159. xp/services/term/state_monitor_service.py +39 -21
  160. xp/term/protocol.py +12 -6
  161. xp/term/state.py +12 -7
  162. xp/term/widgets/help_menu.py +6 -3
  163. xp/term/widgets/modules_list.py +20 -10
  164. xp/term/widgets/protocol_log.py +12 -6
  165. xp/term/widgets/status_footer.py +10 -5
  166. xp/utils/checksum.py +6 -3
  167. xp/utils/dependencies.py +25 -30
  168. xp/utils/event_helper.py +6 -4
  169. xp/utils/logging.py +6 -3
  170. xp/utils/serialization.py +30 -16
  171. xp/utils/state_machine.py +16 -9
  172. xp/utils/time_utils.py +6 -3
  173. conson_xp-1.46.0.dist-info/RECORD +0 -211
  174. xp/services/conbus/msactiontable/msactiontable_download_service.py +0 -275
  175. xp/services/conbus/msactiontable/msactiontable_list_service.py +0 -100
  176. xp/services/conbus/msactiontable/msactiontable_show_service.py +0 -89
  177. {conson_xp-1.46.0.dist-info → conson_xp-1.48.0.dist-info}/WHEEL +0 -0
  178. {conson_xp-1.46.0.dist-info → conson_xp-1.48.0.dist-info}/entry_points.txt +0 -0
  179. {conson_xp-1.46.0.dist-info → conson_xp-1.48.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,9 @@ import re
5
5
  from xp.models import ModuleTypeCode
6
6
  from xp.models.actiontable.actiontable import ActionTable, ActionTableEntry
7
7
  from xp.models.telegram.input_action_type import InputActionType
8
+ from xp.models.telegram.system_function import SystemFunction
8
9
  from xp.models.telegram.timeparam_type import TimeParam
10
+ from xp.services.actiontable.serializer_protocol import ActionTableSerializerProtocol
9
11
  from xp.utils.serialization import (
10
12
  byte_to_unsigned,
11
13
  de_bcd,
@@ -19,8 +21,9 @@ from xp.utils.serialization import (
19
21
  )
20
22
 
21
23
 
22
- class ActionTableSerializer:
23
- """Handles serialization/deserialization of ActionTable to/from telegrams.
24
+ class ActionTableSerializer(ActionTableSerializerProtocol):
25
+ """
26
+ Handles serialization/deserialization of ActionTable to/from telegrams.
24
27
 
25
28
  Attributes:
26
29
  MAX_ENTRIES: Maximum number of entries in an ActionTable (96).
@@ -29,15 +32,27 @@ class ActionTableSerializer:
29
32
  MAX_ENTRIES = 96 # ActionTable must always contain exactly 96 entries
30
33
 
31
34
  @staticmethod
32
- def from_data(data: bytes) -> ActionTable:
33
- """Deserialize telegram data to ActionTable.
35
+ def download_type() -> SystemFunction:
36
+ """
37
+ Get the download system function type.
38
+
39
+ Returns:
40
+ The download system function: DOWNLOAD_ACTIONTABLE
41
+ """
42
+ return SystemFunction.DOWNLOAD_ACTIONTABLE
43
+
44
+ @staticmethod
45
+ def from_encoded_string(encoded_data: str) -> ActionTable:
46
+ """
47
+ Deserialize telegram data to ActionTable.
34
48
 
35
49
  Args:
36
- data: Raw byte data from telegram
50
+ encoded_data: Raw byte data from telegram
37
51
 
38
52
  Returns:
39
53
  Decoded ActionTable
40
54
  """
55
+ data = de_nibbles(encoded_data)
41
56
  entries = []
42
57
 
43
58
  # Process data in 5-byte chunks
@@ -92,14 +107,15 @@ class ActionTableSerializer:
92
107
  return ActionTable(entries=entries)
93
108
 
94
109
  @staticmethod
95
- def to_data(action_table: ActionTable) -> bytes:
96
- """Serialize ActionTable to telegram byte data.
110
+ def to_encoded_string(action_table: ActionTable) -> str:
111
+ """
112
+ Convert ActionTable to base64-encoded string format.
97
113
 
98
114
  Args:
99
- action_table: ActionTable to serialize
115
+ action_table: ActionTable to encode
100
116
 
101
117
  Returns:
102
- Raw byte data for telegram (always 480 bytes for 96 entries)
118
+ Base64-encoded string representation
103
119
  """
104
120
  data = bytearray()
105
121
 
@@ -128,37 +144,12 @@ class ActionTableSerializer:
128
144
  for _ in range(ActionTableSerializer.MAX_ENTRIES - current_entries):
129
145
  data.extend(padding_bytes)
130
146
 
131
- return bytes(data)
132
-
133
- @staticmethod
134
- def to_encoded_string(action_table: ActionTable) -> str:
135
- """Convert ActionTable to base64-encoded string format.
136
-
137
- Args:
138
- action_table: ActionTable to encode
139
-
140
- Returns:
141
- Base64-encoded string representation
142
- """
143
- data = ActionTableSerializer.to_data(action_table)
144
147
  return nibbles(data)
145
148
 
146
149
  @staticmethod
147
- def from_encoded_string(encoded_data: str) -> ActionTable:
148
- """Convert base64-encoded string to ActionTable.
149
-
150
- Args:
151
- encoded_data: Base64-encoded string
152
-
153
- Returns:
154
- Decoded ActionTable
150
+ def to_short_string(action_table: ActionTable) -> list[str]:
155
151
  """
156
- data = de_nibbles(encoded_data)
157
- return ActionTableSerializer.from_data(data)
158
-
159
- @staticmethod
160
- def format_decoded_output(action_table: ActionTable) -> list[str]:
161
- """Format ActionTable as human-readable decoded output.
152
+ Format ActionTable as human-readable decoded output.
162
153
 
163
154
  Args:
164
155
  action_table: ActionTable to format
@@ -194,8 +185,9 @@ class ActionTableSerializer:
194
185
  return lines
195
186
 
196
187
  @staticmethod
197
- def parse_action_string(action_str: str) -> ActionTableEntry:
198
- """Parse action table entry from string format.
188
+ def _parse_action_string(action_str: str) -> ActionTableEntry:
189
+ """
190
+ Parse action table entry from string format.
199
191
 
200
192
  Args:
201
193
  action_str: String in format "CP20 0 0 > 1 OFF" or "CP20 0 1 > 1 ~ON"
@@ -257,8 +249,9 @@ class ActionTableSerializer:
257
249
  )
258
250
 
259
251
  @staticmethod
260
- def parse_action_table(action_strings: list[str]) -> ActionTable:
261
- """Parse action table from list of string entries.
252
+ def from_short_string(action_strings: list[str]) -> ActionTable:
253
+ """
254
+ Parse action table from short string representation.
262
255
 
263
256
  Args:
264
257
  action_strings: List of action strings from conson.yml
@@ -267,7 +260,7 @@ class ActionTableSerializer:
267
260
  Parsed ActionTable
268
261
  """
269
262
  entries = [
270
- ActionTableSerializer.parse_action_string(action_str)
263
+ ActionTableSerializer._parse_action_string(action_str)
271
264
  for action_str in action_strings
272
265
  ]
273
266
  return ActionTable(entries=entries)
@@ -9,10 +9,11 @@ from statemachine.factory import StateMachineMetaclass
9
9
 
10
10
 
11
11
  class AbstractStateMachineMeta(StateMachineMetaclass, ABCMeta):
12
- """Combined metaclass for abstract state machines.
12
+ """
13
+ Combined metaclass for abstract state machines.
13
14
 
14
- Combines StateMachineMetaclass (for state machine introspection) with
15
- ABCMeta (for abstract method enforcement).
15
+ Combines StateMachineMetaclass (for state machine introspection) with ABCMeta (for
16
+ abstract method enforcement).
16
17
  """
17
18
 
18
19
  pass
@@ -23,7 +24,8 @@ MAX_ERROR_RETRIES = 3 # Max retries for error_status_received before giving up
23
24
 
24
25
 
25
26
  class Phase(Enum):
26
- """Download workflow phases.
27
+ """
28
+ Download workflow phases.
27
29
 
28
30
  The download workflow consists of three sequential phases:
29
31
  - INIT: Drain pending telegrams, query error status → proceed to DOWNLOAD
@@ -41,8 +43,9 @@ class Phase(Enum):
41
43
  CLEANUP = "cleanup"
42
44
 
43
45
 
44
- class ActionTableDownloadStateMachine(StateMachine, metaclass=AbstractStateMachineMeta):
45
- """State machine for ActionTable download workflow.
46
+ class DownloadStateMachine(StateMachine, metaclass=AbstractStateMachineMeta):
47
+ """
48
+ State machine for ActionTable download workflow.
46
49
 
47
50
  Pure state machine with states, transitions, and guards. Subclasses can
48
51
  override on_enter_* methods to add protocol-specific behavior.
@@ -146,7 +149,8 @@ class ActionTableDownloadStateMachine(StateMachine, metaclass=AbstractStateMachi
146
149
 
147
150
  @phase.setter
148
151
  def phase(self, value: Phase) -> None:
149
- """Set current phase.
152
+ """
153
+ Set current phase.
150
154
 
151
155
  Args:
152
156
  value: The phase value to set.
@@ -160,7 +164,8 @@ class ActionTableDownloadStateMachine(StateMachine, metaclass=AbstractStateMachi
160
164
 
161
165
  @error_retry_count.setter
162
166
  def error_retry_count(self, value: int) -> None:
163
- """Set error retry count.
167
+ """
168
+ Set error retry count.
164
169
 
165
170
  Args:
166
171
  value: The error retry count value to set.
@@ -1,6 +1,8 @@
1
1
  """Serializer for XP20 Action Table telegram encoding/decoding."""
2
2
 
3
3
  from xp.models.actiontable.msactiontable_xp20 import InputChannel, Xp20MsActionTable
4
+ from xp.models.telegram.system_function import SystemFunction
5
+ from xp.services.actiontable.serializer_protocol import ActionTableSerializerProtocol
4
6
  from xp.utils.serialization import byte_to_bits, de_nibbles, nibbles
5
7
 
6
8
  # Index constants for clarity in implementation
@@ -12,24 +14,65 @@ SA_FUNCTION_INDEX: int = 11
12
14
  TA_FUNCTION_INDEX: int = 12
13
15
 
14
16
 
15
- class Xp20MsActionTableSerializer:
17
+ class Xp20MsActionTableSerializer(ActionTableSerializerProtocol):
16
18
  """Handles serialization/deserialization of XP20 action tables to/from telegrams."""
17
19
 
18
20
  @staticmethod
19
- def format_decoded_output(action_table: Xp20MsActionTable) -> list[str]:
20
- """Serialize XP20 action table to humane compact readable format.
21
+ def download_type() -> SystemFunction:
22
+ """
23
+ Get the download system function type.
24
+
25
+ Returns:
26
+ The download system function: DOWNLOAD_MSACTIONTABLE
27
+ """
28
+ return SystemFunction.DOWNLOAD_MSACTIONTABLE
29
+
30
+ @staticmethod
31
+ def from_encoded_string(encoded_data: str) -> Xp20MsActionTable:
32
+ """
33
+ Deserialize telegram data to XP20 action table.
21
34
 
22
35
  Args:
23
- action_table: XP20 action table to serialize
36
+ encoded_data: 64-character hex string with A-P encoding
24
37
 
25
38
  Returns:
26
- Human-readable string describing XP20 action table
39
+ Decoded XP20 action table
40
+
41
+ Raises:
42
+ ValueError: If input length is not 64 characters
27
43
  """
28
- return action_table.to_short_format()
44
+ raw_length = len(encoded_data)
45
+ if raw_length < 64: # Minimum: 4 char prefix + 64 chars data
46
+ raise ValueError(
47
+ f"XP20 action table data must be 64 characters long, got {len(encoded_data)}"
48
+ )
49
+
50
+ raw_bytes = de_nibbles(encoded_data)
51
+
52
+ # Decode input channels
53
+ input_channels = []
54
+ for input_index in range(8):
55
+ input_channel = Xp20MsActionTableSerializer._decode_input_channel(
56
+ raw_bytes, input_index
57
+ )
58
+ input_channels.append(input_channel)
59
+
60
+ # Create and return XP20 action table
61
+ return Xp20MsActionTable(
62
+ input1=input_channels[0],
63
+ input2=input_channels[1],
64
+ input3=input_channels[2],
65
+ input4=input_channels[3],
66
+ input5=input_channels[4],
67
+ input6=input_channels[5],
68
+ input7=input_channels[6],
69
+ input8=input_channels[7],
70
+ )
29
71
 
30
72
  @staticmethod
31
- def to_data(action_table: Xp20MsActionTable) -> str:
32
- """Serialize XP20 action table to telegram hex string format.
73
+ def to_encoded_string(action_table: Xp20MsActionTable) -> str:
74
+ """
75
+ Serialize XP20 action table to telegram hex string format.
33
76
 
34
77
  Args:
35
78
  action_table: XP20 action table to serialize
@@ -60,59 +103,38 @@ class Xp20MsActionTableSerializer:
60
103
 
61
104
  encoded_data = nibbles(raw_bytes)
62
105
  # Convert raw bytes to hex string with A-P encoding
63
- return "AAAA" + encoded_data
106
+ return encoded_data
64
107
 
65
108
  @staticmethod
66
- def from_data(msactiontable_rawdata: str) -> Xp20MsActionTable:
67
- """Deserialize telegram data to XP20 action table.
109
+ def to_short_string(action_table: Xp20MsActionTable) -> list[str]:
110
+ """
111
+ Serialize XP20 action table to humane compact readable format.
68
112
 
69
113
  Args:
70
- msactiontable_rawdata: 64-character hex string with A-P encoding
114
+ action_table: XP20 action table to serialize
71
115
 
72
116
  Returns:
73
- Decoded XP20 action table
74
-
75
- Raises:
76
- ValueError: If input length is not 64 characters
117
+ Human-readable string describing XP20 action table
77
118
  """
78
- raw_length = len(msactiontable_rawdata)
79
- if raw_length < 68: # Minimum: 4 char prefix + 64 chars data
80
- raise ValueError(
81
- f"XP20 action table data must be 68 characters long, got {len(msactiontable_rawdata)}"
82
- )
83
-
84
- # Remove action table count prefix (first 4 characters: AAAA, AAAB, etc.)
85
- data = msactiontable_rawdata[4:]
86
-
87
- # Take first 64 chars (32 bytes) as per pseudocode
88
- hex_data = data[:64]
119
+ return action_table.to_short_format()
89
120
 
90
- # Convert hex string to bytes using deNibble (A-P encoding)
91
- raw_bytes = de_nibbles(hex_data)
121
+ @staticmethod
122
+ def from_short_string(action_strings: list[str]) -> Xp20MsActionTable:
123
+ """
124
+ Parse XP20 action table from short string format.
92
125
 
93
- # Decode input channels
94
- input_channels = []
95
- for input_index in range(8):
96
- input_channel = Xp20MsActionTableSerializer._decode_input_channel(
97
- raw_bytes, input_index
98
- )
99
- input_channels.append(input_channel)
126
+ Args:
127
+ action_strings: List of short format strings to parse
100
128
 
101
- # Create and return XP20 action table
102
- return Xp20MsActionTable(
103
- input1=input_channels[0],
104
- input2=input_channels[1],
105
- input3=input_channels[2],
106
- input4=input_channels[3],
107
- input5=input_channels[4],
108
- input6=input_channels[5],
109
- input7=input_channels[6],
110
- input8=input_channels[7],
111
- )
129
+ Returns:
130
+ Parsed XP20 action table
131
+ """
132
+ return Xp20MsActionTable.from_short_format(action_strings)
112
133
 
113
134
  @staticmethod
114
- def _decode_input_channel(raw_bytes: bytearray, input_index: int) -> InputChannel:
115
- """Extract input channel configuration from raw bytes.
135
+ def _decode_input_channel(raw_bytes: bytes, input_index: int) -> InputChannel:
136
+ """
137
+ Extract input channel configuration from raw bytes.
116
138
 
117
139
  Args:
118
140
  raw_bytes: Raw byte array from telegram
@@ -146,7 +168,8 @@ class Xp20MsActionTableSerializer:
146
168
  def _encode_input_channel(
147
169
  input_channel: InputChannel, input_index: int, raw_bytes: bytearray
148
170
  ) -> None:
149
- """Encode input channel configuration into raw bytes.
171
+ """
172
+ Encode input channel configuration into raw bytes.
150
173
 
151
174
  Args:
152
175
  input_channel: Input channel configuration to encode
@@ -2,34 +2,77 @@
2
2
 
3
3
  from xp.models.actiontable.msactiontable_xp24 import InputAction, Xp24MsActionTable
4
4
  from xp.models.telegram.input_action_type import InputActionType
5
+ from xp.models.telegram.system_function import SystemFunction
5
6
  from xp.models.telegram.timeparam_type import TimeParam
7
+ from xp.services.actiontable.serializer_protocol import ActionTableSerializerProtocol
6
8
  from xp.utils.serialization import de_nibbles, nibbles
7
9
 
8
10
 
9
- class Xp24MsActionTableSerializer:
11
+ class Xp24MsActionTableSerializer(ActionTableSerializerProtocol):
10
12
  """Handles serialization/deserialization of XP24 action tables to/from telegrams."""
11
13
 
12
14
  @staticmethod
13
- def format_decoded_output(action_table: Xp24MsActionTable) -> list[str]:
14
- """Serialize XP24 action table to humane compact readable format.
15
+ def download_type() -> SystemFunction:
16
+ """
17
+ Get the download system function type.
18
+
19
+ Returns:
20
+ The download system function: DOWNLOAD_MSACTIONTABLE
21
+ """
22
+ return SystemFunction.DOWNLOAD_MSACTIONTABLE
23
+
24
+ @staticmethod
25
+ def from_encoded_string(encoded_data: str) -> Xp24MsActionTable:
26
+ """
27
+ Deserialize action table from raw data parts.
15
28
 
16
29
  Args:
17
- action_table: XP24 action table to serialize
30
+ encoded_data: Raw action table data string.
18
31
 
19
32
  Returns:
20
- Human-readable string describing XP24 action table
33
+ Deserialized XP24 MS action table.
34
+
35
+ Raises:
36
+ ValueError: If data length is not 68 bytes.
21
37
  """
22
- return action_table.to_short_format()
38
+ raw_length = len(encoded_data)
39
+ if raw_length != 64:
40
+ raise ValueError(
41
+ f"Msactiontable is not 64 bytes long ({raw_length}): {encoded_data}"
42
+ )
43
+
44
+ # Convert hex string to bytes using deNibble (A-P encoding)
45
+ data = de_nibbles(encoded_data)
46
+
47
+ # Decode input actions from positions 0-3 (2 bytes each)
48
+ input_actions = []
49
+ for pos in range(4):
50
+ input_action = Xp24MsActionTableSerializer._decode_input_action(data, pos)
51
+ input_actions.append(input_action)
52
+
53
+ action_table = Xp24MsActionTable(
54
+ input1_action=input_actions[0],
55
+ input2_action=input_actions[1],
56
+ input3_action=input_actions[2],
57
+ input4_action=input_actions[3],
58
+ mutex12=data[8] != 0, # With A-P encoding: AA=0 (False), AB=1 (True)
59
+ mutex34=data[9] != 0,
60
+ mutual_deadtime=data[10],
61
+ curtain12=data[11] != 0,
62
+ curtain34=data[12] != 0,
63
+ )
64
+ return action_table
23
65
 
24
66
  @staticmethod
25
- def to_data(action_table: Xp24MsActionTable) -> str:
26
- """Serialize action table to telegram format.
67
+ def to_encoded_string(action_table: Xp24MsActionTable) -> str:
68
+ """
69
+ Serialize action table to telegram format.
27
70
 
28
71
  Args:
29
72
  action_table: XP24 MS action table to serialize.
30
73
 
31
74
  Returns:
32
- Serialized action table data string (68 characters).
75
+ Serialized action table data string (64 characters).
33
76
  """
34
77
  # Build byte array for the action table (32 bytes total)
35
78
  raw_bytes = bytearray()
@@ -56,64 +99,40 @@ class Xp24MsActionTableSerializer:
56
99
  # Add padding to reach 32 bytes (19 more bytes needed)
57
100
  raw_bytes.extend([0x00] * 19)
58
101
 
59
- # Encode to A-P nibbles (32 bytes -> 64 chars)
60
- encoded_data = nibbles(bytes(raw_bytes))
61
-
102
+ # Build byte array for the action table (32 bytes total)
62
103
  # Prepend action table count "AAAA" (4 chars) -> total 68 chars
63
- return "AAAA" + encoded_data
104
+ return nibbles(raw_bytes)
64
105
 
65
106
  @staticmethod
66
- def from_data(msactiontable_rawdata: str) -> Xp24MsActionTable:
67
- """Deserialize action table from raw data parts.
107
+ def to_short_string(action_table: Xp24MsActionTable) -> list[str]:
108
+ """
109
+ Serialize XP24 action table to humane compact readable format.
68
110
 
69
111
  Args:
70
- msactiontable_rawdata: Raw action table data string.
112
+ action_table: XP24 action table to serialize
71
113
 
72
114
  Returns:
73
- Deserialized XP24 MS action table.
74
-
75
- Raises:
76
- ValueError: If data length is not 68 bytes.
115
+ Human-readable string describing XP24 action table
77
116
  """
78
- raw_length = len(msactiontable_rawdata)
79
- if raw_length != 68:
80
- raise ValueError(
81
- f"Msactiontable is not 68 bytes long ({raw_length}): {msactiontable_rawdata}"
82
- )
83
-
84
- # Remove action table count AAAA, AAAB .
85
- data = msactiontable_rawdata[4:]
86
-
87
- # Take first 64 chars (32 bytes) as per pseudocode
88
- hex_data = data[:64]
117
+ return action_table.to_short_format()
89
118
 
90
- # Convert hex string to bytes using deNibble (A-P encoding)
91
- raw_bytes = de_nibbles(hex_data)
119
+ @staticmethod
120
+ def from_short_string(action_strings: list[str]) -> Xp24MsActionTable:
121
+ """
122
+ Serialize XP24 action table to humane compact readable format.
92
123
 
93
- # Decode input actions from positions 0-3 (2 bytes each)
94
- input_actions = []
95
- for pos in range(4):
96
- input_action = Xp24MsActionTableSerializer._decode_input_action(
97
- raw_bytes, pos
98
- )
99
- input_actions.append(input_action)
124
+ Args:
125
+ action_strings: XP24 action table to serialize
100
126
 
101
- action_table = Xp24MsActionTable(
102
- input1_action=input_actions[0],
103
- input2_action=input_actions[1],
104
- input3_action=input_actions[2],
105
- input4_action=input_actions[3],
106
- mutex12=raw_bytes[8] != 0, # With A-P encoding: AA=0 (False), AB=1 (True)
107
- mutex34=raw_bytes[9] != 0,
108
- mutual_deadtime=raw_bytes[10],
109
- curtain12=raw_bytes[11] != 0,
110
- curtain34=raw_bytes[12] != 0,
111
- )
112
- return action_table
127
+ Returns:
128
+ Human-readable string describing XP24 action table
129
+ """
130
+ return Xp24MsActionTable.from_short_format(action_strings)
113
131
 
114
132
  @staticmethod
115
- def _decode_input_action(raw_bytes: bytearray, pos: int) -> InputAction:
116
- """Decode input action from raw bytes.
133
+ def _decode_input_action(raw_bytes: bytes, pos: int) -> InputAction:
134
+ """
135
+ Decode input action from raw bytes.
117
136
 
118
137
  Args:
119
138
  raw_bytes: Raw byte array containing action data.
@@ -5,16 +5,29 @@ from xp.models.actiontable.msactiontable_xp33 import (
5
5
  Xp33Output,
6
6
  Xp33Scene,
7
7
  )
8
+ from xp.models.telegram.system_function import SystemFunction
8
9
  from xp.models.telegram.timeparam_type import TimeParam
10
+ from xp.services.actiontable.serializer_protocol import ActionTableSerializerProtocol
9
11
  from xp.utils.serialization import bits_to_byte, byte_to_bits, de_nibbles, nibbles
10
12
 
11
13
 
12
- class Xp33MsActionTableSerializer:
14
+ class Xp33MsActionTableSerializer(ActionTableSerializerProtocol):
13
15
  """Handles serialization/deserialization of XP33 action tables to/from telegrams."""
14
16
 
15
17
  @staticmethod
16
- def format_decoded_output(action_table: Xp33MsActionTable) -> list[str]:
17
- """Serialize XP33 action table to humane compact readable format.
18
+ def download_type() -> SystemFunction:
19
+ """
20
+ Get the download system function type.
21
+
22
+ Returns:
23
+ The download system function: DOWNLOAD_MSACTIONTABLE
24
+ """
25
+ return SystemFunction.DOWNLOAD_MSACTIONTABLE
26
+
27
+ @staticmethod
28
+ def to_short_string(action_table: Xp33MsActionTable) -> list[str]:
29
+ """
30
+ Serialize XP33 action table to humane compact readable format.
18
31
 
19
32
  Args:
20
33
  action_table: XP33 action table to serialize
@@ -24,6 +37,19 @@ class Xp33MsActionTableSerializer:
24
37
  """
25
38
  return action_table.to_short_format()
26
39
 
40
+ @staticmethod
41
+ def from_short_string(action_strings: list[str]) -> Xp33MsActionTable:
42
+ """
43
+ Serialize XP33 action table to humane compact readable format.
44
+
45
+ Args:
46
+ action_strings: XP33 action table to serialize
47
+
48
+ Returns:
49
+ Human-readable string describing XP33 action table
50
+ """
51
+ return Xp33MsActionTable.from_short_format(action_strings)
52
+
27
53
  @staticmethod
28
54
  def _percentage_to_byte(percentage: int) -> int:
29
55
  """Convert percentage (0-100) to byte value for telegram encoding."""
@@ -48,8 +74,9 @@ class Xp33MsActionTableSerializer:
48
74
  return TimeParam.NONE
49
75
 
50
76
  @staticmethod
51
- def to_data(action_table: Xp33MsActionTable) -> str:
52
- """Serialize action table to telegram format.
77
+ def to_encoded_string(action_table: Xp33MsActionTable) -> str:
78
+ """
79
+ Serialize action table to telegram format.
53
80
 
54
81
  Args:
55
82
  action_table: XP33 MS action table to serialize.
@@ -112,11 +139,12 @@ class Xp33MsActionTableSerializer:
112
139
  encoded_data = nibbles(raw_bytes)
113
140
 
114
141
  # Convert raw bytes to hex string with A-P encoding
115
- return "AAAA" + encoded_data
142
+ return encoded_data
116
143
 
117
144
  @staticmethod
118
- def from_data(msactiontable_rawdata: str) -> Xp33MsActionTable:
119
- """Deserialize action table from raw data parts.
145
+ def from_encoded_string(msactiontable_rawdata: str) -> Xp33MsActionTable:
146
+ """
147
+ Deserialize action table from raw data parts.
120
148
 
121
149
  Args:
122
150
  msactiontable_rawdata: Raw action table data string.
@@ -125,22 +153,16 @@ class Xp33MsActionTableSerializer:
125
153
  Deserialized XP33 MS action table.
126
154
 
127
155
  Raises:
128
- ValueError: If data length is less than 68 characters.
156
+ ValueError: If data length is less than 64 characters.
129
157
  """
130
158
  raw_length = len(msactiontable_rawdata)
131
- if raw_length < 68: # Minimum: 4 char prefix + 64 chars data
159
+ if raw_length < 64: # Minimum: 4 char prefix + 64 chars data
132
160
  raise ValueError(
133
- f"Msactiontable is too short ({raw_length}), minimum 68 characters required"
161
+ f"Msactiontable is too short ({raw_length}), minimum 64 characters required"
134
162
  )
135
163
 
136
- # Remove action table count prefix (first 4 characters: AAAA, AAAB, etc.)
137
- data = msactiontable_rawdata[4:]
138
-
139
- # Take first 64 chars (32 bytes) as per pseudocode
140
- hex_data = data[:64]
141
-
142
164
  # Convert hex string to bytes using deNibble (A-P encoding)
143
- raw_bytes = de_nibbles(hex_data)
165
+ raw_bytes = de_nibbles(msactiontable_rawdata)
144
166
 
145
167
  # Decode outputs
146
168
  output1 = Xp33MsActionTableSerializer._decode_output(raw_bytes, 0)
@@ -165,7 +187,8 @@ class Xp33MsActionTableSerializer:
165
187
 
166
188
  @staticmethod
167
189
  def _decode_output(raw_bytes: bytearray, output_index: int) -> Xp33Output:
168
- """Extract output configuration from raw bytes.
190
+ """
191
+ Extract output configuration from raw bytes.
169
192
 
170
193
  Args:
171
194
  raw_bytes: Raw byte array containing output data.
@@ -219,7 +242,8 @@ class Xp33MsActionTableSerializer:
219
242
 
220
243
  @staticmethod
221
244
  def _decode_scene(raw_bytes: bytearray, scene_index: int) -> Xp33Scene:
222
- """Extract scene configuration from raw bytes.
245
+ """
246
+ Extract scene configuration from raw bytes.
223
247
 
224
248
  Args:
225
249
  raw_bytes: Raw byte array containing scene data.