conson-xp 1.6.0__py3-none-any.whl → 1.7.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conson-xp
3
- Version: 1.6.0
3
+ Version: 1.7.0
4
4
  Summary: XP Protocol Communication Tools
5
5
  Author-Email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT License
@@ -1,8 +1,8 @@
1
- conson_xp-1.6.0.dist-info/METADATA,sha256=NeCIj-sEltfweIjEbTTiJMkUhm6_JDk3o4qqgj2AQy4,9274
2
- conson_xp-1.6.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
- conson_xp-1.6.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
- conson_xp-1.6.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
- xp/__init__.py,sha256=ch-TVxJdSXvFaMGwGhp04iVA5GRAiAhsU9Ehnb0Y3qM,180
1
+ conson_xp-1.7.0.dist-info/METADATA,sha256=zJcFXnX0j-cYc8sCjdUkPuDsGp0-ji8KCGwE79oxZNQ,9274
2
+ conson_xp-1.7.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
+ conson_xp-1.7.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
+ conson_xp-1.7.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
+ xp/__init__.py,sha256=IyP3xj9rnVfGLnpDwGvX8oBbLbm-WT5Qx42Tf8OD9I4,180
6
6
  xp/cli/__init__.py,sha256=QjnKB1KaI2aIyKlzrnvCwfbBuUj8HNgwNMvNJVQofbI,81
7
7
  xp/cli/__main__.py,sha256=l2iKwMdat5rTGd3JWs-uGksnYYDDffp_Npz05QdKEeU,117
8
8
  xp/cli/commands/__init__.py,sha256=02CbZoKmNX-fn5etX4Hdgg2lUt1MsLFPYx2VkXZyFJ8,4394
@@ -17,7 +17,7 @@ xp/cli/commands/conbus/conbus_datapoint_commands.py,sha256=r36OuTjREtbGKL-bskAGa
17
17
  xp/cli/commands/conbus/conbus_discover_commands.py,sha256=Jt-OjARc-QXCr453bhKG0zXRqvOMVKZr328zNxSDulY,1373
18
18
  xp/cli/commands/conbus/conbus_lightlevel_commands.py,sha256=FpCwogdxa7yFUjlrxM7e8Q2Ut32tKAHabngQQChvtJI,6763
19
19
  xp/cli/commands/conbus/conbus_linknumber_commands.py,sha256=pn491Iy_LcwZ5XLtvNTWu8qG_Vdaaxr7le1tTuVK7Qw,3392
20
- xp/cli/commands/conbus/conbus_msactiontable_commands.py,sha256=kKWX9S0Ip9eX9EyWPwmyKMtt6q-jRXcOaicMAKt84Ls,2652
20
+ xp/cli/commands/conbus/conbus_msactiontable_commands.py,sha256=fb9MQ4O04H0Dinpt7vSF5GtfntTZHelQ5TuUmSBbCTg,2899
21
21
  xp/cli/commands/conbus/conbus_output_commands.py,sha256=zdRVbHzVhMbZpG2x5WXtujc3wKTsoQUV4IgkVIbJbCc,5019
22
22
  xp/cli/commands/conbus/conbus_raw_commands.py,sha256=8BKUarwvHgz-sxML7n99YVsb8B1HJNExjQpRsuY_tQw,1829
23
23
  xp/cli/commands/conbus/conbus_receive_commands.py,sha256=WdH7fYcbjweIGxD2uxrTRD8lJzViMSVdsaHTvSDJNCQ,1757
@@ -100,13 +100,13 @@ xp/services/__init__.py,sha256=W9YZyrkh7vm--ZHhAXNQiOYQs5yhhmUHXP5I0Lf1XBg,782
100
100
  xp/services/actiontable/__init__.py,sha256=z6js4EuJ6xKHaseTEhuEvKo1tr9K1XyQiruReJtBiPY,26
101
101
  xp/services/actiontable/actiontable_serializer.py,sha256=x45-8d5Ba9l3hX2TFC5nqKv-g_244g-VTWhXvVXL8Jg,5159
102
102
  xp/services/actiontable/msactiontable_serializer.py,sha256=RRL6TZ1gpSQw81kAiw2BV3jTqm4fCJC0pWIcO26Cmos,174
103
- xp/services/actiontable/msactiontable_xp20_serializer.py,sha256=EYspooOdi0Z8oaXGxpazwnUoTmh-d7U9auhu11iBgmU,6527
104
- xp/services/actiontable/msactiontable_xp24_serializer.py,sha256=30qsk9UKje1n32PPc4YoGV1lw_ZvgxNqqd8ZDgzMJpg,4504
105
- xp/services/actiontable/msactiontable_xp33_serializer.py,sha256=nuWfka4U9W4lpTcS8uD6azXFcryPb0CUO5O7Z28G1k8,8901
103
+ xp/services/actiontable/msactiontable_xp20_serializer.py,sha256=3Lz6t3uRYhoeMRhjDAO1XuWPJzH-ML13t05UQLFUW-s,6057
104
+ xp/services/actiontable/msactiontable_xp24_serializer.py,sha256=zdKzcrKqD41POqj_1c4B4why_Jp9mNXncajsnXXBtPw,4215
105
+ xp/services/actiontable/msactiontable_xp33_serializer.py,sha256=xoZBA38pBSUPA9nn7HgaH1ZM5sR2heQbJ6JVlPVbzUY,8400
106
106
  xp/services/conbus/__init__.py,sha256=Hi35sMKu9o6LpYoi2tQDaQoMb8M5sOt_-LUTxxaCU_0,28
107
107
  xp/services/conbus/actiontable/__init__.py,sha256=oD6vRk_Ye-eZ9s_hldAgtRJFu4mfAnODqpkJUGHHszk,40
108
108
  xp/services/conbus/actiontable/actiontable_service.py,sha256=uy-BFCsjDoe1ZuZy9cTwRSIfMSxznLEN-iMtTsPW3EI,5626
109
- xp/services/conbus/actiontable/msactiontable_service.py,sha256=wVYFBqj7gngZ-6Kk5AsNiTYeNe3xZoPMr4-lmcbAUAE,7430
109
+ xp/services/conbus/actiontable/msactiontable_service.py,sha256=yeMJRHVJe2s_-MquOdZDDym3g7135J3CzhxfEa6Qmkc,8357
110
110
  xp/services/conbus/conbus_blink_all_service.py,sha256=OaEg4b8AEiEruHSkZ5jDtaoI81vwwxLq4KWXO7zBdD0,6582
111
111
  xp/services/conbus/conbus_blink_service.py,sha256=x9uM-sLnIEV8wSNsvJgo08E042g-Hh2ZF3rXkz-k_9s,5824
112
112
  xp/services/conbus/conbus_custom_service.py,sha256=4aneYdPObiZOGxPFYg5Wr70cl_xFxlQIdJBPQSa0enI,5826
@@ -135,15 +135,15 @@ xp/services/homekit/homekit_service.py,sha256=0lW-hg40ETco3gDBEYkR_sX-UIYsLSKCD4
135
135
  xp/services/log_file_service.py,sha256=fvPcZQj8XOKUA-4ep5R8n0PelvwvRlTLlVxvIWM5KR4,10506
136
136
  xp/services/module_type_service.py,sha256=xWhr1EAZMykL5uNWHWdpa5T8yNruGKH43XRTOS8GwZg,7477
137
137
  xp/services/protocol/__init__.py,sha256=WuYn2iEcvsOIXnn5HCrU9kD3PjuMX1sIh0ljKISDoJw,720
138
- xp/services/protocol/conbus_protocol.py,sha256=G39YPMpwhvvhFPYrzNxx6y2Is6DSP2UyCLm4T7RLPVc,10404
138
+ xp/services/protocol/conbus_protocol.py,sha256=JO7yLkD_ohPT0ETjnAIx4CGpZyobf4LdbuozM_43btE,10276
139
139
  xp/services/protocol/protocol_factory.py,sha256=PmjN9AtW9sxNo3voqUiNgQA-pTvX1RW4XXFlHKfFr5Q,2470
140
140
  xp/services/protocol/telegram_protocol.py,sha256=Ki5DrXsKxiaqLcdP9WWUuhUI7cPu2DfwyZkh-Gv9Lb8,9496
141
141
  xp/services/reverse_proxy_service.py,sha256=BUOlcLlTU-R5iuC_96rasug21xo19wK9_4fMQXxc0QM,15061
142
142
  xp/services/server/__init__.py,sha256=QEcCj-jK0goAukJCe15TKYFQfSAzWsduPT_wW0HxZU8,48
143
- xp/services/server/base_server_service.py,sha256=Yt1gfepRt7GLo_hsBqIzNMUye_JtEM8FupP9uTYJBUA,13294
143
+ xp/services/server/base_server_service.py,sha256=B-ntxp3swbwuri-9_2EuvBDi-4Uo9AH-AA4iAFGWIS4,12682
144
144
  xp/services/server/cp20_server_service.py,sha256=SXdI6Jt400T9sLdw86ovEqKRGeV3nYVaHEA9Gcj6W2A,2041
145
145
  xp/services/server/device_service_factory.py,sha256=Y4TvSFALeq0zYzHfCwcbikSpmIyYbLcvm9756n5Jm7Q,3744
146
- xp/services/server/server_service.py,sha256=N-_WVNG2J2tH-12ZLAo5SLGIHuEseZObJ6X7PhQt8Yo,16216
146
+ xp/services/server/server_service.py,sha256=JPRFMto2l956dW7vfSclQugu2vdF0fssxxUOYjHNtA4,15833
147
147
  xp/services/server/xp130_server_service.py,sha256=YnvetDp72-QzkyDGB4qfZZIwFs03HuibUOz2zb9XR0c,2191
148
148
  xp/services/server/xp20_server_service.py,sha256=1wJ7A-bRkN9O5Spu3q3LNDW31mNtNF2eNMQ5E6O2ltA,2928
149
149
  xp/services/server/xp230_server_service.py,sha256=k9ftCY5tjLFP31mKVCspq283RVaPkGx-Yq61Urk8JLs,1815
@@ -156,12 +156,12 @@ xp/services/telegram/telegram_datapoint_service.py,sha256=A3ERuFSWc22uJUoymH-2dW
156
156
  xp/services/telegram/telegram_discover_service.py,sha256=oTpiq-yzP_UmC0xVOMMFeHO-rIlK1pF3aG-Kq4SeiBI,9025
157
157
  xp/services/telegram/telegram_link_number_service.py,sha256=1_c-_QCRPTHYn3BmMElrBJqGG6vnoIst8CB-N42hazk,6862
158
158
  xp/services/telegram/telegram_output_service.py,sha256=UaUv_14fR8o5K2PxQBXrCzx-Hohnk-gzbev_oLw_Clc,10799
159
- xp/services/telegram/telegram_service.py,sha256=CQKmwV0Jmlr1WwrshaANyp_e77DjBzXzuFL1U5DRgFI,13092
159
+ xp/services/telegram/telegram_service.py,sha256=XrP1CPi0ckxoKBaNwLA6lo-TogWxXgmXDOsU4Xl8BlY,13237
160
160
  xp/services/telegram/telegram_version_service.py,sha256=M5HdOTsLdcwo122FP-jW6R740ktLrtKf2TiMDVz23h8,10528
161
161
  xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
162
162
  xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
163
- xp/utils/dependencies.py,sha256=xNnwVWxMThq8V9fGRgqMUJABMBaZ88NF7rW3iYBQlDE,18505
163
+ xp/utils/dependencies.py,sha256=1XDwIg3OsmLvOazMQ3qaktcsitYW8E400RxihNWgyt0,18894
164
164
  xp/utils/event_helper.py,sha256=W-A_xmoXlpWZBbJH6qdaN50o3-XrwFsDgvAGMJDiAgo,1001
165
165
  xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
166
166
  xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
167
- conson_xp-1.6.0.dist-info/RECORD,,
167
+ conson_xp-1.7.0.dist-info/RECORD,,
xp/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  conson-xp package.
4
4
  """
5
5
 
6
- __version__ = "1.6.0"
6
+ __version__ = "1.7.0"
7
7
  __manufacturer__ = "salchichon"
8
8
  __model__ = "xp.cli"
9
9
  __serial__ = "2025.09.23.000"
@@ -49,13 +49,22 @@ def conbus_download_msactiontable(
49
49
  click.echo(progress, nl=False)
50
50
 
51
51
  def on_finish(
52
- action_table: Union[Xp20MsActionTable | Xp24MsActionTable | Xp33MsActionTable],
52
+ action_table: Union[
53
+ Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable, None
54
+ ],
53
55
  ) -> None:
54
56
  """Handle successful completion of MS action table download.
55
57
 
56
58
  Args:
57
- action_table: Downloaded MS action table object.
59
+ action_table: Downloaded MS action table object or None if failed.
60
+
61
+ Raises:
62
+ Abort: If action table download failed.
58
63
  """
64
+ if action_table is None:
65
+ click.echo("Error: Failed to download MS action table")
66
+ raise click.Abort()
67
+
59
68
  output = {
60
69
  "serial_number": serial_number,
61
70
  "xpmoduletype": xpmoduletype,
@@ -46,8 +46,9 @@ class Xp20MsActionTableSerializer:
46
46
  input_channel, input_index, raw_bytes
47
47
  )
48
48
 
49
+ encoded_data = nibbles(raw_bytes)
49
50
  # Convert raw bytes to hex string with A-P encoding
50
- return nibbles(raw_bytes)
51
+ return "AAAA" + encoded_data
51
52
 
52
53
  @staticmethod
53
54
  def from_data(msactiontable_rawdata: str) -> Xp20MsActionTable:
@@ -62,13 +63,20 @@ class Xp20MsActionTableSerializer:
62
63
  Raises:
63
64
  ValueError: If input length is not 64 characters
64
65
  """
65
- if len(msactiontable_rawdata) != 64:
66
+ raw_length = len(msactiontable_rawdata)
67
+ if raw_length < 68: # Minimum: 4 char prefix + 64 chars data
66
68
  raise ValueError(
67
- f"XP20 action table data must be 64 characters long, got {len(msactiontable_rawdata)}"
69
+ f"XP20 action table data must be 68 characters long, got {len(msactiontable_rawdata)}"
68
70
  )
69
71
 
70
- # Convert hex string to bytes using de_nibble (A-P encoding)
71
- raw_bytes = de_nibbles(msactiontable_rawdata)
72
+ # Remove action table count prefix (first 4 characters: AAAA, AAAB, etc.)
73
+ data = msactiontable_rawdata[4:]
74
+
75
+ # Take first 64 chars (32 bytes) as per pseudocode
76
+ hex_data = data[:64]
77
+
78
+ # Convert hex string to bytes using deNibble (A-P encoding)
79
+ raw_bytes = de_nibbles(hex_data)
72
80
 
73
81
  # Decode input channels
74
82
  input_channels = []
@@ -159,23 +167,3 @@ class Xp20MsActionTableSerializer:
159
167
  and_functions_byte |= 1 << bit_index
160
168
 
161
169
  raw_bytes[AND_FUNCTIONS_INDEX + input_index] = and_functions_byte
162
-
163
- @staticmethod
164
- def from_telegrams(ms_telegrams: str) -> Xp20MsActionTable:
165
- """Legacy method for backward compatibility. Use from_data() instead.
166
-
167
- Args:
168
- ms_telegrams: Full telegram string
169
-
170
- Returns:
171
- Decoded XP20 action table
172
- """
173
- # Extract data portion from telegram (skip header, take action table data)
174
- # Based on XP24 pattern: telegram[16:84] gives us the 68-char data portion
175
- # For XP20, we need 64 chars, so we take the first 64 chars after removing count
176
- data_parts = ms_telegrams[16:84]
177
-
178
- # Remove action table count (first 4 chars: AAAA, AAAB, etc.)
179
- hex_data = data_parts[4:68] # Take 64 chars after count
180
-
181
- return Xp20MsActionTableSerializer.from_data(hex_data)
@@ -3,7 +3,7 @@
3
3
  from xp.models.actiontable.msactiontable_xp24 import InputAction, Xp24MsActionTable
4
4
  from xp.models.telegram.input_action_type import InputActionType
5
5
  from xp.models.telegram.timeparam_type import TimeParam
6
- from xp.utils.serialization import de_nibbles
6
+ from xp.utils.serialization import de_nibbles, nibbles
7
7
 
8
8
 
9
9
  class Xp24MsActionTableSerializer:
@@ -17,11 +17,12 @@ class Xp24MsActionTableSerializer:
17
17
  action_table: XP24 MS action table to serialize.
18
18
 
19
19
  Returns:
20
- Serialized action table data string.
20
+ Serialized action table data string (68 characters).
21
21
  """
22
- data_parts: list[str] = []
22
+ # Build byte array for the action table (32 bytes total)
23
+ raw_bytes = bytearray()
23
24
 
24
- # Encode all 4 input actions
25
+ # Encode all 4 input actions (2 bytes each = 8 bytes total)
25
26
  input_actions = [
26
27
  action_table.input1_action,
27
28
  action_table.input2_action,
@@ -30,26 +31,24 @@ class Xp24MsActionTableSerializer:
30
31
  ]
31
32
 
32
33
  for action in input_actions:
33
- # Use enum value directly as function ID
34
- function_id = action.type.value
35
- # Convert parameter to int (None becomes 0)
36
- param_id = action.param.value
37
- data_parts.append(f"{function_id:02X}{param_id:02X}")
38
-
39
- # Add settings as hex values
40
- data_parts.extend(
41
- [
42
- "AB" if action_table.mutex12 else "AA",
43
- "AB" if action_table.mutex34 else "AA",
44
- f"{action_table.mutual_deadtime:02X}",
45
- "AB" if action_table.curtain12 else "AA",
46
- "AB" if action_table.curtain34 else "AA",
47
- "A" * 38, # padding
48
- ]
49
- )
34
+ raw_bytes.append(action.type.value)
35
+ raw_bytes.append(action.param.value)
36
+
37
+ # Add settings (5 bytes)
38
+ raw_bytes.append(0x01 if action_table.mutex12 else 0x00)
39
+ raw_bytes.append(0x01 if action_table.mutex34 else 0x00)
40
+ raw_bytes.append(action_table.mutual_deadtime)
41
+ raw_bytes.append(0x01 if action_table.curtain12 else 0x00)
42
+ raw_bytes.append(0x01 if action_table.curtain34 else 0x00)
43
+
44
+ # Add padding to reach 32 bytes (19 more bytes needed)
45
+ raw_bytes.extend([0x00] * 19)
46
+
47
+ # Encode to A-P nibbles (32 bytes -> 64 chars)
48
+ encoded_data = nibbles(bytes(raw_bytes))
50
49
 
51
- data = "AAAA".join(data_parts)
52
- return data
50
+ # Prepend action table count "AAAA" (4 chars) -> total 68 chars
51
+ return "AAAA" + encoded_data
53
52
 
54
53
  @staticmethod
55
54
  def from_data(msactiontable_rawdata: str) -> Xp24MsActionTable:
@@ -66,7 +65,9 @@ class Xp24MsActionTableSerializer:
66
65
  """
67
66
  raw_length = len(msactiontable_rawdata)
68
67
  if raw_length != 68:
69
- raise ValueError(f"Msactiontable is not 68 bytes long ({raw_length})")
68
+ raise ValueError(
69
+ f"Msactiontable is not 68 bytes long ({raw_length}): {msactiontable_rawdata}"
70
+ )
70
71
 
71
72
  # Remove action table count AAAA, AAAB .
72
73
  data = msactiontable_rawdata[4:]
@@ -117,18 +118,3 @@ class Xp24MsActionTableSerializer:
117
118
  param_type = TimeParam(param_id)
118
119
 
119
120
  return InputAction(action_type, param_type)
120
-
121
- @staticmethod
122
- def from_telegrams(ms_telegrams: str) -> Xp24MsActionTable:
123
- """Legacy method for backward compatibility. Use from_data() instead.
124
-
125
- Args:
126
- ms_telegrams: Telegram data string.
127
-
128
- Returns:
129
- Deserialized XP24 MS action table.
130
- """
131
- # For backward compatibility, assume full telegrams and extract data
132
- data_parts = ms_telegrams[16:84]
133
-
134
- return Xp24MsActionTableSerializer.from_data(data_parts)
@@ -6,7 +6,7 @@ from xp.models.actiontable.msactiontable_xp33 import (
6
6
  Xp33Scene,
7
7
  )
8
8
  from xp.models.telegram.timeparam_type import TimeParam
9
- from xp.utils.serialization import bits_to_byte, byte_to_bits, de_nibbles, nibble
9
+ from xp.utils.serialization import bits_to_byte, byte_to_bits, de_nibbles, nibbles
10
10
 
11
11
 
12
12
  class Xp33MsActionTableSerializer:
@@ -96,13 +96,11 @@ class Xp33MsActionTableSerializer:
96
96
  raw_bytes[24] = bits_to_byte(leading_edge_bits)
97
97
 
98
98
  # Bytes 25-31 are padding (already 0)
99
-
100
99
  # Convert to hex string using nibble encoding
101
- hex_data = "AAAA"
102
- for byte_val in raw_bytes:
103
- hex_data += nibble(byte_val)
100
+ encoded_data = nibbles(raw_bytes)
104
101
 
105
- return hex_data
102
+ # Convert raw bytes to hex string with A-P encoding
103
+ return "AAAA" + encoded_data
106
104
 
107
105
  @staticmethod
108
106
  def from_data(msactiontable_rawdata: str) -> Xp33MsActionTable:
@@ -239,18 +237,3 @@ class Xp33MsActionTableSerializer:
239
237
  output3_level=output3_level,
240
238
  time=time_param,
241
239
  )
242
-
243
- @staticmethod
244
- def from_telegrams(ms_telegrams: str) -> Xp33MsActionTable:
245
- """Legacy method for backward compatibility. Use from_data() instead.
246
-
247
- Args:
248
- ms_telegrams: Telegram data string.
249
-
250
- Returns:
251
- Deserialized XP33 MS action table.
252
- """
253
- # For backward compatibility, assume full telegrams and extract data
254
- data_parts = ms_telegrams[16:152] # Adjusted for XP33 length
255
-
256
- return Xp33MsActionTableSerializer.from_data(data_parts)
@@ -74,7 +74,8 @@ class MsActionTableService(ConbusProtocol):
74
74
  self.error_callback: Optional[Callable[[str], None]] = None
75
75
  self.finish_callback: Optional[
76
76
  Callable[
77
- [Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable]], None
77
+ [Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable, None]],
78
+ None,
78
79
  ]
79
80
  ] = None
80
81
  self.msactiontable_data: list[str] = []
@@ -114,17 +115,32 @@ class MsActionTableService(ConbusProtocol):
114
115
  self.logger.debug("Not a reply response")
115
116
  return
116
117
 
117
- reply_telegram = self.telegram_service.parse_reply_telegram(telegram_received)
118
+ reply_telegram = self.telegram_service.parse_reply_telegram(
119
+ telegram_received.frame
120
+ )
118
121
  if reply_telegram.system_function not in (
119
122
  SystemFunction.MSACTIONTABLE,
123
+ SystemFunction.ACK,
124
+ SystemFunction.NAK,
120
125
  SystemFunction.EOF,
121
126
  ):
122
127
  self.logger.debug("Not a msactiontable response")
123
128
  return
124
129
 
125
- if reply_telegram.system_function == SystemFunction.ACTIONTABLE:
126
- self.logger.debug("Saving msactiontable response")
127
- self.msactiontable_data.append(reply_telegram.data_value)
130
+ if reply_telegram.system_function == SystemFunction.ACK:
131
+ self.logger.debug("Received ACK")
132
+ return
133
+
134
+ if reply_telegram.system_function == SystemFunction.NAK:
135
+ self.logger.debug("Received NAK")
136
+ self.failed("Received NAK")
137
+ return
138
+
139
+ if reply_telegram.system_function == SystemFunction.MSACTIONTABLE:
140
+ self.logger.debug("Received MSACTIONTABLE")
141
+ self.msactiontable_data.extend(
142
+ (reply_telegram.data, reply_telegram.data_value)
143
+ )
128
144
  if self.progress_callback:
129
145
  self.progress_callback(".")
130
146
 
@@ -137,11 +153,14 @@ class MsActionTableService(ConbusProtocol):
137
153
  return
138
154
 
139
155
  if reply_telegram.system_function == SystemFunction.EOF:
156
+ self.logger.debug("Received EOF")
140
157
  all_data = "".join(self.msactiontable_data)
141
158
  # Deserialize from received data
142
159
  msactiontable = self.serializer.from_data(all_data)
143
- if self.finish_callback:
144
- self.finish_callback(msactiontable)
160
+ self.succeed(msactiontable)
161
+ return
162
+
163
+ self.logger.debug("Invalid msactiontable response")
145
164
 
146
165
  def failed(self, message: str) -> None:
147
166
  """Handle failed connection event.
@@ -152,6 +171,20 @@ class MsActionTableService(ConbusProtocol):
152
171
  self.logger.debug(f"Failed: {message}")
153
172
  if self.error_callback:
154
173
  self.error_callback(message)
174
+ self._stop_reactor()
175
+
176
+ def succeed(
177
+ self,
178
+ msactiontable: Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable],
179
+ ) -> None:
180
+ """Handle succeed connection event.
181
+
182
+ Args:
183
+ msactiontable: result.
184
+ """
185
+ if self.finish_callback:
186
+ self.finish_callback(msactiontable)
187
+ self._stop_reactor()
155
188
 
156
189
  def start(
157
190
  self,
@@ -160,7 +193,7 @@ class MsActionTableService(ConbusProtocol):
160
193
  progress_callback: Callable[[str], None],
161
194
  error_callback: Callable[[str], None],
162
195
  finish_callback: Callable[
163
- [Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable]], None
196
+ [Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable, None]], None
164
197
  ],
165
198
  timeout_seconds: Optional[float] = None,
166
199
  ) -> None:
@@ -230,13 +230,11 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
230
230
  self.timeout_call = self.reactor.callLater(
231
231
  self.timeout_seconds, self._on_timeout
232
232
  )
233
- self.logger.debug(f"Timeout set for {self.timeout_seconds} seconds")
234
233
 
235
234
  def _cancel_timeout(self) -> None:
236
235
  """Cancel the inactivity timeout."""
237
236
  if self.timeout_call and self.timeout_call.active():
238
237
  self.timeout_call.cancel()
239
- self.logger.debug("Timeout cancelled")
240
238
 
241
239
  def _on_timeout(self) -> None:
242
240
  """Handle inactivity timeout expiration."""
@@ -185,15 +185,25 @@ class BaseServerService(ABC):
185
185
  Returns:
186
186
  ACK telegram if request is valid, NAK otherwise.
187
187
  """
188
- if (
189
- request.system_function == SystemFunction.DOWNLOAD_MSACTIONTABLE
190
- and self.msactiontable_download_state is None
191
- ):
192
- self.msactiontable_download_state = "ack_sent"
193
- # Send ACK and queue data telegram
194
- return self._build_response_telegram(f"R{self.serial_number}F18D") # ACK
188
+ serializer = self._get_msactiontable_serializer()
189
+ msactiontable = self._get_msactiontable()
195
190
 
196
- return self._build_response_telegram(f"R{self.serial_number}F19D") # NAK
191
+ # Only handle if serializer and msactiontable are available
192
+ if not serializer or msactiontable is None:
193
+ return None
194
+
195
+ # Send ACK and queue data telegram
196
+ ack_data = self._build_response_telegram(f"R{self.serial_number}F18D") # ACK
197
+
198
+ # Send MsActionTable data
199
+ encoded_data = serializer.to_data(msactiontable)
200
+ data_telegram = self._build_response_telegram(
201
+ f"R{self.serial_number}F17D{encoded_data}"
202
+ )
203
+ self.msactiontable_download_state = "data_sent"
204
+
205
+ # Return ACK and TABLE
206
+ return ack_data + data_telegram
197
207
 
198
208
  def _handle_download_msactiontable_ack_request(
199
209
  self, _request: SystemTelegram
@@ -206,24 +216,7 @@ class BaseServerService(ABC):
206
216
  Returns:
207
217
  Data telegram, EOF telegram, or NAK if state is invalid.
208
218
  """
209
- serializer = self._get_msactiontable_serializer()
210
- msactiontable = self._get_msactiontable()
211
-
212
- # Only handle if serializer and msactiontable are available
213
- if not serializer or msactiontable is None:
214
- return None
215
-
216
- # Handle F18D - CONTINUE (after ACK or data)
217
- if self.msactiontable_download_state == "ack_sent":
218
- # Send MsActionTable data
219
- encoded_data = serializer.to_data(msactiontable)
220
- data_telegram = self._build_response_telegram(
221
- f"R{self.serial_number}F17D{encoded_data}"
222
- )
223
- self.msactiontable_download_state = "data_sent"
224
- return data_telegram
225
-
226
- elif self.msactiontable_download_state == "data_sent":
219
+ if self.msactiontable_download_state == "data_sent":
227
220
  # Send EOF
228
221
  eof_telegram = self._build_response_telegram(f"R{self.serial_number}F16D")
229
222
  self.msactiontable_download_state = None
@@ -367,11 +360,7 @@ class BaseServerService(ABC):
367
360
  Returns:
368
361
  List of telegram strings from the buffer. The buffer is cleared after collection.
369
362
  """
370
- self.logger.debug(
371
- f"Collecting {self.serial_number} telegrams from buffer: {len(self.telegram_buffer)}"
372
- )
373
363
  with self.telegram_buffer_lock:
374
364
  result = self.telegram_buffer.copy()
375
- self.logger.debug(f"Resetting {self.serial_number} buffer")
376
365
  self.telegram_buffer.clear()
377
366
  return result
@@ -209,14 +209,11 @@ class ServerService:
209
209
  self.logger.debug(f"Sent buffer to {client_address}")
210
210
 
211
211
  # Receive data from client
212
- self.logger.debug(f"Receiving data {client_address}")
213
212
  data = None
214
213
  try:
215
214
  data = client_socket.recv(1024)
216
215
  except socket.timeout:
217
- self.logger.debug(
218
- f"Timeout receiving data {client_address} ({timeout})"
219
- )
216
+ pass
220
217
  finally:
221
218
  timeout -= 1
222
219
 
@@ -421,9 +418,6 @@ class ServerService:
421
418
  self.logger.info("Collector thread starting")
422
419
 
423
420
  while True:
424
- self.logger.debug(
425
- f"Collector thread collecting ({len(self.collector_buffer)})"
426
- )
427
421
  collected = 0
428
422
  for device_service in self.device_services.values():
429
423
  telegram_buffer = device_service.collect_telegram_buffer()
@@ -431,5 +425,4 @@ class ServerService:
431
425
  collected += len(telegram_buffer)
432
426
 
433
427
  # Wait a bit before checking again
434
- self.logger.debug(f"Collector thread collected ({collected})")
435
428
  self.collector_stop_event.wait(timeout=1)
@@ -3,6 +3,7 @@
3
3
  This module provides telegram parsing functionality for event, system, and reply telegrams.
4
4
  """
5
5
 
6
+ import logging
6
7
  import re
7
8
  from typing import Union
8
9
 
@@ -48,7 +49,8 @@ class TelegramService:
48
49
 
49
50
  def __init__(self) -> None:
50
51
  """Initialize the telegram service."""
51
- pass
52
+ # Set up logging
53
+ self.logger = logging.getLogger(__name__)
52
54
 
53
55
  def parse_event_telegram(self, raw_telegram: str) -> EventTelegram:
54
56
  """Parse a raw telegram string into an EventTelegram object.
@@ -242,6 +244,7 @@ class TelegramService:
242
244
  raise TelegramParsingError("Empty telegram string")
243
245
 
244
246
  # Validate and parse using regex
247
+ self.logger.debug(f"Parsing reply telegram {raw_telegram}")
245
248
  match = self.REPLY_TELEGRAM_PATTERN.match(raw_telegram.strip())
246
249
  if not match:
247
250
  raise TelegramParsingError(f"Invalid reply telegram format: {raw_telegram}")
xp/utils/dependencies.py CHANGED
@@ -326,6 +326,12 @@ class ServiceContainer:
326
326
  # Module type services layer
327
327
  self.container.register(ModuleTypeService, scope=punq.Scope.singleton)
328
328
 
329
+ # MsActionTable serializers
330
+ self.container.register(MsActionTableSerializer, scope=punq.Scope.singleton)
331
+ self.container.register(Xp20MsActionTableSerializer, scope=punq.Scope.singleton)
332
+ self.container.register(Xp24MsActionTableSerializer, scope=punq.Scope.singleton)
333
+ self.container.register(Xp33MsActionTableSerializer, scope=punq.Scope.singleton)
334
+
329
335
  # Device service factory
330
336
  self.container.register(
331
337
  DeviceServiceFactory,