conson-xp 1.47.0__py3-none-any.whl → 1.49.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {conson_xp-1.47.0.dist-info → conson_xp-1.49.0.dist-info}/METADATA +1 -1
- {conson_xp-1.47.0.dist-info → conson_xp-1.49.0.dist-info}/RECORD +16 -17
- xp/__init__.py +1 -1
- xp/cli/commands/conbus/conbus_actiontable_commands.py +0 -4
- xp/models/protocol/conbus_protocol.py +1 -2
- xp/services/actiontable/msactiontable_xp20_serializer.py +4 -9
- xp/services/actiontable/msactiontable_xp24_serializer.py +5 -11
- xp/services/actiontable/msactiontable_xp33_serializer.py +5 -11
- xp/services/conbus/actiontable/actiontable_download_service.py +6 -3
- xp/services/conbus/conbus_discover_service.py +2 -2
- xp/services/conbus/msactiontable/msactiontable_upload_service.py +2 -2
- xp/services/protocol/__init__.py +2 -3
- xp/services/protocol/conbus_event_protocol.py +6 -3
- xp/services/protocol/conbus_protocol.py +0 -335
- {conson_xp-1.47.0.dist-info → conson_xp-1.49.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.47.0.dist-info → conson_xp-1.49.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.47.0.dist-info → conson_xp-1.49.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
conson_xp-1.
|
|
2
|
-
conson_xp-1.
|
|
3
|
-
conson_xp-1.
|
|
4
|
-
conson_xp-1.
|
|
5
|
-
xp/__init__.py,sha256=
|
|
1
|
+
conson_xp-1.49.0.dist-info/METADATA,sha256=TynMPgKlwxBap1KLaYahYSNDF4YOS_iO1kNhUFR78io,11403
|
|
2
|
+
conson_xp-1.49.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
|
|
3
|
+
conson_xp-1.49.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
|
|
4
|
+
conson_xp-1.49.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
|
|
5
|
+
xp/__init__.py,sha256=KJJ4ZcIuwzCmqcxnfXWmH4RNucpuxrNrvbjN0cEnmaw,182
|
|
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=G7A1KFRSV0CEeDTqr_khu-K9_sc01CTI2KSfkFcaBRM,4949
|
|
9
9
|
xp/cli/commands/conbus/__init__.py,sha256=HYaX2__XxwD3Xaw4CzflvL8CwoUa4yR6wOyH8wwyofM,535
|
|
10
10
|
xp/cli/commands/conbus/conbus.py,sha256=GpWq1QS3mKfQFG77nTED-_hsvcnXb3ZIrC6DhfyhzBE,3538
|
|
11
|
-
xp/cli/commands/conbus/conbus_actiontable_commands.py,sha256=
|
|
11
|
+
xp/cli/commands/conbus/conbus_actiontable_commands.py,sha256=9kLP-ISIDWXVUtELCjTAs0JIT3DNGcYGBRnZEo-tp3g,7743
|
|
12
12
|
xp/cli/commands/conbus/conbus_autoreport_commands.py,sha256=dO3rxgII_xZVhbkxlMqTJCarrUveQCs_7-qf4li0UkQ,3962
|
|
13
13
|
xp/cli/commands/conbus/conbus_blink_commands.py,sha256=1zLiXV8Nn4XSDI-z7F3f2RN2IO-yxeMkGQ1PCknAkWY,5398
|
|
14
14
|
xp/cli/commands/conbus/conbus_config_commands.py,sha256=jeZi7J973DrDCyP59Qrvk6tonduj97KnV4fq9zbei2k,721
|
|
@@ -88,7 +88,7 @@ xp/models/homekit/homekit_accessory.py,sha256=ANjDWlFxeNTstl7lKdmf6vMOC0wc005vpi
|
|
|
88
88
|
xp/models/homekit/homekit_config.py,sha256=OMq0eayAJ6NRr8PXANvQzgEYGW9RN_ycyEmnTlTlHrQ,2938
|
|
89
89
|
xp/models/log_entry.py,sha256=tAiNwouCP2d4jKiHJY9a-2iAi8LWTpG-TZsOPDIstlA,4423
|
|
90
90
|
xp/models/protocol/__init__.py,sha256=TJ_CJKchA-xgQiv5vCo_ndBBZjrcaTmjT74bR0T-5Cw,38
|
|
91
|
-
xp/models/protocol/conbus_protocol.py,sha256=
|
|
91
|
+
xp/models/protocol/conbus_protocol.py,sha256=hF78N5xvBzMiyWoKd8i_avA8kJ1As_9Pplkw1GMqKzk,9145
|
|
92
92
|
xp/models/response.py,sha256=z0376hEVNh1Z_R9CtzmKNbJBzCo_Aun2ImI9dTtrVe4,1254
|
|
93
93
|
xp/models/telegram/__init__.py,sha256=-_exhjlRLbBNuPxHC4tLA2SAgf8M0yHJMeyEoQIk9PI,53
|
|
94
94
|
xp/models/telegram/action_type.py,sha256=ys3yInmD97bKotB-nlPdfo8ZPXKaYxzG0CxV9wHK-zo,797
|
|
@@ -118,13 +118,13 @@ xp/services/actiontable/__init__.py,sha256=z6js4EuJ6xKHaseTEhuEvKo1tr9K1XyQiruRe
|
|
|
118
118
|
xp/services/actiontable/actiontable_serializer.py,sha256=AZpqxfq8-o7FGGtseW52MS6z647x3Ucc1RjR3GLUb20,8335
|
|
119
119
|
xp/services/actiontable/download_state_machine.py,sha256=lqNYN9LGGK2KiVUsmvyRfryWRB4-NOfsp7-9GrFubK4,9978
|
|
120
120
|
xp/services/actiontable/msactiontable_serializer.py,sha256=RRL6TZ1gpSQw81kAiw2BV3jTqm4fCJC0pWIcO26Cmos,174
|
|
121
|
-
xp/services/actiontable/msactiontable_xp20_serializer.py,sha256=
|
|
122
|
-
xp/services/actiontable/msactiontable_xp24_serializer.py,sha256=
|
|
123
|
-
xp/services/actiontable/msactiontable_xp33_serializer.py,sha256=
|
|
121
|
+
xp/services/actiontable/msactiontable_xp20_serializer.py,sha256=5K6FxgbV2F4brumNaOH6M8qPyCxIfaqCGOPIYDmFdnk,6998
|
|
122
|
+
xp/services/actiontable/msactiontable_xp24_serializer.py,sha256=NBsRViz-J1Zx7DtO9flRRsiZtOsB5rpQVplJ-I43stg,5192
|
|
123
|
+
xp/services/actiontable/msactiontable_xp33_serializer.py,sha256=9JzG9dOrfamu8UjsWztveX5n4o29Rm3ki81Ot0a38KQ,9490
|
|
124
124
|
xp/services/actiontable/serializer_protocol.py,sha256=PMsZbPwPQD1MJYo_KpZSgpnVQCtXFXSfzXFpCiA6Xi8,2002
|
|
125
125
|
xp/services/conbus/__init__.py,sha256=Hi35sMKu9o6LpYoi2tQDaQoMb8M5sOt_-LUTxxaCU_0,28
|
|
126
126
|
xp/services/conbus/actiontable/__init__.py,sha256=oD6vRk_Ye-eZ9s_hldAgtRJFu4mfAnODqpkJUGHHszk,40
|
|
127
|
-
xp/services/conbus/actiontable/actiontable_download_service.py,sha256=
|
|
127
|
+
xp/services/conbus/actiontable/actiontable_download_service.py,sha256=ZTQCC1D9TLmyDu3b5GAO4VxUVGSr6HuqZI9fxHB1qW8,14992
|
|
128
128
|
xp/services/conbus/actiontable/actiontable_list_service.py,sha256=oTDSpBkp-MJeaF5bhRnwkSy3na55xqQ4e2ykJzbMCUo,3236
|
|
129
129
|
xp/services/conbus/actiontable/actiontable_show_service.py,sha256=WISY2VsmSlceGa5_9lpFO-gs5TnTjv6YidQksUjCapk,3058
|
|
130
130
|
xp/services/conbus/actiontable/actiontable_upload_service.py,sha256=4crX4OZeTLYC_9ZjhV1Cqqc-WOhhBlXDmEHToLS6N9g,9772
|
|
@@ -133,7 +133,7 @@ xp/services/conbus/conbus_blink_service.py,sha256=ggLuzeq_UsgCoxRxg2bsNs9p8Lw_sh
|
|
|
133
133
|
xp/services/conbus/conbus_custom_service.py,sha256=9OIRC2CG_rN96vbv_EZXf7BrX_abhqi5MZx0Se8fEhU,7826
|
|
134
134
|
xp/services/conbus/conbus_datapoint_queryall_service.py,sha256=YCeieYgS73HAplAB1NpyEFary8L3yJXm2z9XOuUOLkI,8468
|
|
135
135
|
xp/services/conbus/conbus_datapoint_service.py,sha256=WBQC42-6xuPWhMKKRtHtRzwEmVfYFt7cmS5uS-iv3P0,8095
|
|
136
|
-
xp/services/conbus/conbus_discover_service.py,sha256=
|
|
136
|
+
xp/services/conbus/conbus_discover_service.py,sha256=mvqjHFMmEkQjHD9YDIk9gE8MowPMkOIJRmyjX96G5pw,12868
|
|
137
137
|
xp/services/conbus/conbus_event_list_service.py,sha256=-jl3WHpyidbh-h4NMK2gERqu48mTNFD6rpPo2EyGxeg,3641
|
|
138
138
|
xp/services/conbus/conbus_event_raw_service.py,sha256=viXuEXw165-RytdqC76wQShJLD7Yd0rtURxWZZ8hyKA,7060
|
|
139
139
|
xp/services/conbus/conbus_export_service.py,sha256=coUbwdx3eG7ILhvUvlY3dkxdzZUksjnZHBGPvDDrTdk,17420
|
|
@@ -142,7 +142,7 @@ xp/services/conbus/conbus_raw_service.py,sha256=OQuV521VOQraf2PGF2B9868vh7sDgmfc
|
|
|
142
142
|
xp/services/conbus/conbus_receive_service.py,sha256=TFf3W65brGsy6QZICpIs0Xy9bgqyL1vgQuhS_eHuIZs,5416
|
|
143
143
|
xp/services/conbus/conbus_scan_service.py,sha256=_Ka0OUDNYhDgZIR49Q0P5GTxJq6RcAAX2DVqEDdtb5U,6888
|
|
144
144
|
xp/services/conbus/msactiontable/__init__.py,sha256=rDYzumPSfcTjDADHxjE7bXQoeWtZTDGaYzFTYdVl_9g,42
|
|
145
|
-
xp/services/conbus/msactiontable/msactiontable_upload_service.py,sha256=
|
|
145
|
+
xp/services/conbus/msactiontable/msactiontable_upload_service.py,sha256=LB9pv0VOUDmyTHKUcY894fmBAqINO7qpM8mW1E3s8aU,12423
|
|
146
146
|
xp/services/conbus/write_config_service.py,sha256=BCfmLNPRDpwSwRMRYJvx2FXA8IZsdgmyeTXIYvmb4ys,9004
|
|
147
147
|
xp/services/homekit/__init__.py,sha256=xAMKmln_AmEFdOOJGKWYi96seRlKDQpKx3-hm7XbdIo,36
|
|
148
148
|
xp/services/homekit/homekit_cache_service.py,sha256=z1TB6icEqd1paoilVTewuFL0lXVCQbvrOJkJvvQECJY,11060
|
|
@@ -160,9 +160,8 @@ xp/services/homekit/homekit_outlet_service.py,sha256=VVeuUas5HROaMwfbc_FO-gVp_mX
|
|
|
160
160
|
xp/services/homekit/homekit_service.py,sha256=mmO9dHd1PAkOLkM7wwbpC0iBnULN4XZxeHUnCMKOL7g,14127
|
|
161
161
|
xp/services/log_file_service.py,sha256=y775InPlpG8Z_uitHt7yrgRG-3vlIuCfdJjiZzPc_MA,10578
|
|
162
162
|
xp/services/module_type_service.py,sha256=ga4WO7dmJEaDKrXPmrJ-72n4UaEZonead23lHBkFN1E,7505
|
|
163
|
-
xp/services/protocol/__init__.py,sha256=
|
|
164
|
-
xp/services/protocol/conbus_event_protocol.py,sha256=
|
|
165
|
-
xp/services/protocol/conbus_protocol.py,sha256=3RY1dva3XHLyvcBqwV4nuenaVpRnT-ppPz45EXNGCIs,10417
|
|
163
|
+
xp/services/protocol/__init__.py,sha256=4zC__3e-Hma_IrmFCvsL44wJRd5LLHLSI_tahqdaTF8,741
|
|
164
|
+
xp/services/protocol/conbus_event_protocol.py,sha256=7AMn_5jr8lWPbf3C2s0P-r-w4PgdsmsBVcK0UMmrdaw,19572
|
|
166
165
|
xp/services/protocol/protocol_factory.py,sha256=gWXQtOdgtOyn0Tu_aydYFd_cTdU-jtsKVuMl-ru133s,2512
|
|
167
166
|
xp/services/protocol/telegram_protocol.py,sha256=hyEMJUUhgQe_2R8CDio8xT5tpLtgoXS8W0aDd7wN48E,9538
|
|
168
167
|
xp/services/reverse_proxy_service.py,sha256=Xuxo2ajjmTXLg5yd6gRGKHpFPX4-6kpOUg10doZB8vw,15161
|
|
@@ -207,4 +206,4 @@ xp/utils/logging.py,sha256=wJ1d-yg97NiZUrt2F8iDMcmnHVwC-PErcI-7dpyiRDc,3777
|
|
|
207
206
|
xp/utils/serialization.py,sha256=TS1OwpTOemSvXsCGw3js4JkYYFEqkzrPe8V9QYQefdw,4684
|
|
208
207
|
xp/utils/state_machine.py,sha256=W9AY4ntRZnFeHAa5d43hm37j53uJPlqkRvWTPiBhJ_0,2464
|
|
209
208
|
xp/utils/time_utils.py,sha256=K17godWpL18VEypbTlvNOEDG6R3huYnf29yjkcnwRpU,3796
|
|
210
|
-
conson_xp-1.
|
|
209
|
+
conson_xp-1.49.0.dist-info/RECORD,,
|
xp/__init__.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import json
|
|
4
4
|
from contextlib import suppress
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Any, Dict
|
|
7
6
|
|
|
8
7
|
import click
|
|
9
8
|
from click import Context
|
|
@@ -66,7 +65,6 @@ def conbus_download_actiontable(ctx: Context, serial_number: str) -> None:
|
|
|
66
65
|
|
|
67
66
|
def on_actiontable_received(
|
|
68
67
|
_actiontable: ActionTable,
|
|
69
|
-
actiontable_dict: Dict[str, Any],
|
|
70
68
|
actiontable_short: list[str],
|
|
71
69
|
) -> None:
|
|
72
70
|
"""
|
|
@@ -74,13 +72,11 @@ def conbus_download_actiontable(ctx: Context, serial_number: str) -> None:
|
|
|
74
72
|
|
|
75
73
|
Args:
|
|
76
74
|
_actiontable: a list of ActionTableEntries.
|
|
77
|
-
actiontable_dict: action table in a dictionary.
|
|
78
75
|
actiontable_short: short representation of action table.
|
|
79
76
|
"""
|
|
80
77
|
output = {
|
|
81
78
|
"serial_number": serial_number,
|
|
82
79
|
"actiontable_short": actiontable_short,
|
|
83
|
-
"actiontable": actiontable_dict,
|
|
84
80
|
}
|
|
85
81
|
click.echo(json.dumps(output, indent=2, default=str))
|
|
86
82
|
|
|
@@ -13,7 +13,6 @@ from xp.models.telegram.datapoint_type import DataPointType
|
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
15
|
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
16
|
-
from xp.services.protocol.conbus_protocol import ConbusProtocol
|
|
17
16
|
from xp.services.protocol.telegram_protocol import TelegramProtocol
|
|
18
17
|
|
|
19
18
|
|
|
@@ -266,7 +265,7 @@ class TelegramEvent(BaseEvent):
|
|
|
266
265
|
checksum_valid: Checksum valid true or false.
|
|
267
266
|
"""
|
|
268
267
|
|
|
269
|
-
protocol: Union[TelegramProtocol,
|
|
268
|
+
protocol: Union[TelegramProtocol, ConbusEventProtocol] = Field(
|
|
270
269
|
description="TelegramProtocol instance"
|
|
271
270
|
)
|
|
272
271
|
frame: str = Field(description="Frame <S0123450001F02D12FK>")
|
|
@@ -42,17 +42,12 @@ class Xp20MsActionTableSerializer(ActionTableSerializerProtocol):
|
|
|
42
42
|
ValueError: If input length is not 64 characters
|
|
43
43
|
"""
|
|
44
44
|
raw_length = len(encoded_data)
|
|
45
|
-
if raw_length <
|
|
45
|
+
if raw_length < 64: # Minimum: 4 char prefix + 64 chars data
|
|
46
46
|
raise ValueError(
|
|
47
|
-
f"XP20 action table data must be
|
|
47
|
+
f"XP20 action table data must be 64 characters long, got {len(encoded_data)}"
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
data = encoded_data[4:]
|
|
52
|
-
|
|
53
|
-
# Take first 64 chars (32 bytes) as per pseudocode
|
|
54
|
-
hex_data = data[:64]
|
|
55
|
-
raw_bytes = de_nibbles(hex_data)
|
|
50
|
+
raw_bytes = de_nibbles(encoded_data)
|
|
56
51
|
|
|
57
52
|
# Decode input channels
|
|
58
53
|
input_channels = []
|
|
@@ -108,7 +103,7 @@ class Xp20MsActionTableSerializer(ActionTableSerializerProtocol):
|
|
|
108
103
|
|
|
109
104
|
encoded_data = nibbles(raw_bytes)
|
|
110
105
|
# Convert raw bytes to hex string with A-P encoding
|
|
111
|
-
return
|
|
106
|
+
return encoded_data
|
|
112
107
|
|
|
113
108
|
@staticmethod
|
|
114
109
|
def to_short_string(action_table: Xp20MsActionTable) -> list[str]:
|
|
@@ -36,19 +36,13 @@ class Xp24MsActionTableSerializer(ActionTableSerializerProtocol):
|
|
|
36
36
|
ValueError: If data length is not 68 bytes.
|
|
37
37
|
"""
|
|
38
38
|
raw_length = len(encoded_data)
|
|
39
|
-
if raw_length !=
|
|
39
|
+
if raw_length != 64:
|
|
40
40
|
raise ValueError(
|
|
41
|
-
f"Msactiontable is not
|
|
41
|
+
f"Msactiontable is not 64 bytes long ({raw_length}): {encoded_data}"
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
-
# Remove action table count AAAA, AAAB .
|
|
45
|
-
stripped_data = encoded_data[4:]
|
|
46
|
-
|
|
47
|
-
# Take first 64 chars (32 bytes) as per pseudocode
|
|
48
|
-
hex_data = stripped_data[:64]
|
|
49
|
-
|
|
50
44
|
# Convert hex string to bytes using deNibble (A-P encoding)
|
|
51
|
-
data = de_nibbles(
|
|
45
|
+
data = de_nibbles(encoded_data)
|
|
52
46
|
|
|
53
47
|
# Decode input actions from positions 0-3 (2 bytes each)
|
|
54
48
|
input_actions = []
|
|
@@ -78,7 +72,7 @@ class Xp24MsActionTableSerializer(ActionTableSerializerProtocol):
|
|
|
78
72
|
action_table: XP24 MS action table to serialize.
|
|
79
73
|
|
|
80
74
|
Returns:
|
|
81
|
-
Serialized action table data string (
|
|
75
|
+
Serialized action table data string (64 characters).
|
|
82
76
|
"""
|
|
83
77
|
# Build byte array for the action table (32 bytes total)
|
|
84
78
|
raw_bytes = bytearray()
|
|
@@ -107,7 +101,7 @@ class Xp24MsActionTableSerializer(ActionTableSerializerProtocol):
|
|
|
107
101
|
|
|
108
102
|
# Build byte array for the action table (32 bytes total)
|
|
109
103
|
# Prepend action table count "AAAA" (4 chars) -> total 68 chars
|
|
110
|
-
return
|
|
104
|
+
return nibbles(raw_bytes)
|
|
111
105
|
|
|
112
106
|
@staticmethod
|
|
113
107
|
def to_short_string(action_table: Xp24MsActionTable) -> list[str]:
|
|
@@ -139,7 +139,7 @@ class Xp33MsActionTableSerializer(ActionTableSerializerProtocol):
|
|
|
139
139
|
encoded_data = nibbles(raw_bytes)
|
|
140
140
|
|
|
141
141
|
# Convert raw bytes to hex string with A-P encoding
|
|
142
|
-
return
|
|
142
|
+
return encoded_data
|
|
143
143
|
|
|
144
144
|
@staticmethod
|
|
145
145
|
def from_encoded_string(msactiontable_rawdata: str) -> Xp33MsActionTable:
|
|
@@ -153,22 +153,16 @@ class Xp33MsActionTableSerializer(ActionTableSerializerProtocol):
|
|
|
153
153
|
Deserialized XP33 MS action table.
|
|
154
154
|
|
|
155
155
|
Raises:
|
|
156
|
-
ValueError: If data length is less than
|
|
156
|
+
ValueError: If data length is less than 64 characters.
|
|
157
157
|
"""
|
|
158
158
|
raw_length = len(msactiontable_rawdata)
|
|
159
|
-
if raw_length <
|
|
159
|
+
if raw_length < 64: # Minimum: 4 char prefix + 64 chars data
|
|
160
160
|
raise ValueError(
|
|
161
|
-
f"Msactiontable is too short ({raw_length}), minimum
|
|
161
|
+
f"Msactiontable is too short ({raw_length}), minimum 64 characters required"
|
|
162
162
|
)
|
|
163
163
|
|
|
164
|
-
# Remove action table count prefix (first 4 characters: AAAA, AAAB, etc.)
|
|
165
|
-
data = msactiontable_rawdata[4:]
|
|
166
|
-
|
|
167
|
-
# Take first 64 chars (32 bytes) as per pseudocode
|
|
168
|
-
hex_data = data[:64]
|
|
169
|
-
|
|
170
164
|
# Convert hex string to bytes using deNibble (A-P encoding)
|
|
171
|
-
raw_bytes = de_nibbles(
|
|
165
|
+
raw_bytes = de_nibbles(msactiontable_rawdata)
|
|
172
166
|
|
|
173
167
|
# Decode outputs
|
|
174
168
|
output1 = Xp33MsActionTableSerializer._decode_output(raw_bytes, 0)
|
|
@@ -51,7 +51,7 @@ class ActionTableDownloadService(DownloadStateMachine):
|
|
|
51
51
|
Attributes:
|
|
52
52
|
on_progress: Signal emitted with "." for each chunk received.
|
|
53
53
|
on_error: Signal emitted with error message string.
|
|
54
|
-
on_actiontable_received: Signal emitted with (ActionTable,
|
|
54
|
+
on_actiontable_received: Signal emitted with (ActionTable, list).
|
|
55
55
|
on_finish: Signal emitted when download and cleanup completed.
|
|
56
56
|
|
|
57
57
|
Example:
|
|
@@ -65,7 +65,7 @@ class ActionTableDownloadService(DownloadStateMachine):
|
|
|
65
65
|
on_progress: Signal = Signal(str)
|
|
66
66
|
on_error: Signal = Signal(str)
|
|
67
67
|
on_finish: Signal = Signal()
|
|
68
|
-
on_actiontable_received: Signal = Signal(Any,
|
|
68
|
+
on_actiontable_received: Signal = Signal(Any, list[str])
|
|
69
69
|
|
|
70
70
|
def __init__(
|
|
71
71
|
self,
|
|
@@ -126,7 +126,10 @@ class ActionTableDownloadService(DownloadStateMachine):
|
|
|
126
126
|
def on_enter_requesting(self) -> None:
|
|
127
127
|
"""Enter requesting state - send download request."""
|
|
128
128
|
self.enter_download_phase() # Sets phase to DOWNLOAD
|
|
129
|
-
self.conbus_protocol.send_download_request(
|
|
129
|
+
self.conbus_protocol.send_download_request(
|
|
130
|
+
serial_number=self.serial_number,
|
|
131
|
+
actiontable_type=self.serializer.download_type(),
|
|
132
|
+
)
|
|
130
133
|
self.send_download()
|
|
131
134
|
|
|
132
135
|
def on_enter_waiting_data(self) -> None:
|
|
@@ -25,7 +25,7 @@ class ConbusDiscoverService:
|
|
|
25
25
|
"""
|
|
26
26
|
Service for discovering modules on Conbus servers.
|
|
27
27
|
|
|
28
|
-
Uses
|
|
28
|
+
Uses ConbusEventProtocol to provide discovery functionality for finding
|
|
29
29
|
modules connected to the Conbus network.
|
|
30
30
|
|
|
31
31
|
Attributes:
|
|
@@ -45,7 +45,7 @@ class ConbusDiscoverService:
|
|
|
45
45
|
Initialize the Conbus discover service.
|
|
46
46
|
|
|
47
47
|
Args:
|
|
48
|
-
conbus_protocol:
|
|
48
|
+
conbus_protocol: ConbusEventProtocol instance for communication.
|
|
49
49
|
"""
|
|
50
50
|
self.conbus_protocol: ConbusEventProtocol = conbus_protocol
|
|
51
51
|
self.conbus_protocol.on_connection_made.connect(self.connection_made)
|
|
@@ -279,8 +279,8 @@ class MsActionTableUploadService:
|
|
|
279
279
|
self.failed(f"Invalid msactiontable format: {e}")
|
|
280
280
|
return
|
|
281
281
|
|
|
282
|
-
# Serialize to telegram data (
|
|
283
|
-
self.upload_data = self.serializer.to_encoded_string(msactiontable) # type: ignore[arg-type]
|
|
282
|
+
# Serialize to telegram data (64 characters: AAAA + 64 data chars)
|
|
283
|
+
self.upload_data = "AAAA" + self.serializer.to_encoded_string(msactiontable) # type: ignore[arg-type]
|
|
284
284
|
|
|
285
285
|
self.logger.debug(
|
|
286
286
|
f"Upload data encoded: {len(self.upload_data)} chars (single chunk)"
|
xp/services/protocol/__init__.py
CHANGED
|
@@ -8,12 +8,11 @@ from xp.models.protocol.conbus_protocol import (
|
|
|
8
8
|
TelegramReceivedEvent,
|
|
9
9
|
)
|
|
10
10
|
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
11
|
-
from xp.services.protocol.conbus_protocol import ConbusProtocol
|
|
12
11
|
from xp.services.protocol.telegram_protocol import TelegramProtocol
|
|
13
12
|
|
|
14
|
-
__all__ = ["TelegramProtocol", "
|
|
13
|
+
__all__ = ["TelegramProtocol", "ConbusEventProtocol"]
|
|
15
14
|
|
|
16
|
-
# Rebuild models after TelegramProtocol and
|
|
15
|
+
# Rebuild models after TelegramProtocol and ConbusEventProtocol are imported to resolve forward references
|
|
17
16
|
ConnectionMadeEvent.model_rebuild()
|
|
18
17
|
InvalidTelegramReceivedEvent.model_rebuild()
|
|
19
18
|
ModuleDiscoveredEvent.model_rebuild()
|
|
@@ -98,7 +98,7 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
98
98
|
telegram_service: TelegramService,
|
|
99
99
|
) -> None:
|
|
100
100
|
"""
|
|
101
|
-
Initialize
|
|
101
|
+
Initialize ConbusEventProtocol.
|
|
102
102
|
|
|
103
103
|
Args:
|
|
104
104
|
cli_config: Configuration for Conbus client connection.
|
|
@@ -326,17 +326,20 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
326
326
|
data_value=DataPointType.MODULE_ERROR_CODE.value,
|
|
327
327
|
)
|
|
328
328
|
|
|
329
|
-
def send_download_request(
|
|
329
|
+
def send_download_request(
|
|
330
|
+
self, serial_number: str, actiontable_type: SystemFunction
|
|
331
|
+
) -> None:
|
|
330
332
|
"""
|
|
331
333
|
Send download request telegram.
|
|
332
334
|
|
|
333
335
|
Args:
|
|
334
336
|
serial_number: Device serial number.
|
|
337
|
+
actiontable_type: DOWNLOAD_ACTIONTABLE or DOWNLOAD_MSACTIONTABLE.
|
|
335
338
|
"""
|
|
336
339
|
self.send_telegram(
|
|
337
340
|
telegram_type=TelegramType.SYSTEM,
|
|
338
341
|
serial_number=serial_number,
|
|
339
|
-
system_function=
|
|
342
|
+
system_function=actiontable_type,
|
|
340
343
|
data_value=NO_ERROR_CODE,
|
|
341
344
|
)
|
|
342
345
|
|
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Conbus Protocol for XP telegram communication.
|
|
3
|
-
|
|
4
|
-
This module implements the Twisted protocol for Conbus communication.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import logging
|
|
8
|
-
from typing import Any, Optional
|
|
9
|
-
|
|
10
|
-
from twisted.internet import protocol
|
|
11
|
-
from twisted.internet.base import DelayedCall
|
|
12
|
-
from twisted.internet.interfaces import IAddress, IConnector
|
|
13
|
-
from twisted.internet.posixbase import PosixReactorBase
|
|
14
|
-
from twisted.python.failure import Failure
|
|
15
|
-
|
|
16
|
-
from xp.models import ConbusClientConfig
|
|
17
|
-
from xp.models.protocol.conbus_protocol import (
|
|
18
|
-
TelegramReceivedEvent,
|
|
19
|
-
)
|
|
20
|
-
from xp.models.telegram.system_function import SystemFunction
|
|
21
|
-
from xp.models.telegram.telegram_type import TelegramType
|
|
22
|
-
from xp.utils import calculate_checksum
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
26
|
-
"""
|
|
27
|
-
Twisted protocol for XP telegram communication.
|
|
28
|
-
|
|
29
|
-
Attributes:
|
|
30
|
-
buffer: Buffer for incoming telegram data.
|
|
31
|
-
logger: Logger instance for this protocol.
|
|
32
|
-
cli_config: Conbus configuration settings.
|
|
33
|
-
reactor: Twisted reactor instance.
|
|
34
|
-
timeout_seconds: Timeout duration in seconds.
|
|
35
|
-
timeout_call: Delayed call handle for timeout management.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
buffer: bytes
|
|
39
|
-
|
|
40
|
-
def __init__(
|
|
41
|
-
self,
|
|
42
|
-
cli_config: ConbusClientConfig,
|
|
43
|
-
reactor: PosixReactorBase,
|
|
44
|
-
) -> None:
|
|
45
|
-
"""
|
|
46
|
-
Initialize ConbusProtocol.
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
cli_config: Configuration for Conbus client connection.
|
|
50
|
-
reactor: Twisted reactor for event handling.
|
|
51
|
-
"""
|
|
52
|
-
self.buffer = b""
|
|
53
|
-
self.logger = logging.getLogger(__name__)
|
|
54
|
-
self.cli_config = cli_config.conbus
|
|
55
|
-
self.reactor = reactor
|
|
56
|
-
self.timeout_seconds = self.cli_config.timeout
|
|
57
|
-
self.timeout_call: Optional[DelayedCall] = None
|
|
58
|
-
|
|
59
|
-
def connectionMade(self) -> None:
|
|
60
|
-
"""
|
|
61
|
-
Handle connection established event.
|
|
62
|
-
|
|
63
|
-
Called when TCP connection is successfully established. Starts inactivity
|
|
64
|
-
timeout monitoring.
|
|
65
|
-
"""
|
|
66
|
-
self.logger.debug("connectionMade")
|
|
67
|
-
self.connection_established()
|
|
68
|
-
# Start inactivity timeout
|
|
69
|
-
self._reset_timeout()
|
|
70
|
-
|
|
71
|
-
def dataReceived(self, data: bytes) -> None:
|
|
72
|
-
"""
|
|
73
|
-
Handle received data from TCP connection.
|
|
74
|
-
|
|
75
|
-
Parses incoming telegram frames and dispatches events.
|
|
76
|
-
|
|
77
|
-
Args:
|
|
78
|
-
data: Raw bytes received from connection.
|
|
79
|
-
"""
|
|
80
|
-
self.logger.debug("dataReceived")
|
|
81
|
-
self.buffer += data
|
|
82
|
-
|
|
83
|
-
while True:
|
|
84
|
-
start = self.buffer.find(b"<")
|
|
85
|
-
if start == -1:
|
|
86
|
-
break
|
|
87
|
-
|
|
88
|
-
end = self.buffer.find(b">", start)
|
|
89
|
-
if end == -1:
|
|
90
|
-
break
|
|
91
|
-
|
|
92
|
-
# <S0123450001F02D12FK>
|
|
93
|
-
# <R0123450001F02D12FK>
|
|
94
|
-
# <E12L01I08MAK>
|
|
95
|
-
frame = self.buffer[start : end + 1] # <S0123450001F02D12FK>
|
|
96
|
-
self.buffer = self.buffer[end + 1 :]
|
|
97
|
-
telegram = frame[1:-1] # S0123450001F02D12FK
|
|
98
|
-
telegram_type = telegram[0:1].decode() # S
|
|
99
|
-
payload = telegram[:-2] # S0123450001F02D12
|
|
100
|
-
checksum = telegram[-2:].decode() # FK
|
|
101
|
-
serial_number = (
|
|
102
|
-
telegram[1:11] if telegram_type in ("S", "R") else b""
|
|
103
|
-
) # 0123450001
|
|
104
|
-
calculated_checksum = calculate_checksum(payload.decode(encoding="latin-1"))
|
|
105
|
-
|
|
106
|
-
checksum_valid = checksum == calculated_checksum
|
|
107
|
-
if not checksum_valid:
|
|
108
|
-
self.logger.debug(
|
|
109
|
-
f"Invalid checksum: {checksum}, calculated: {calculated_checksum}"
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
self.logger.debug(
|
|
113
|
-
f"frameReceived payload: {payload.decode('latin-1')}, checksum: {checksum}"
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
# Reset timeout on activity
|
|
117
|
-
self._reset_timeout()
|
|
118
|
-
|
|
119
|
-
telegram_received = TelegramReceivedEvent(
|
|
120
|
-
protocol=self,
|
|
121
|
-
frame=frame.decode("latin-1"),
|
|
122
|
-
telegram=telegram.decode("latin-1"),
|
|
123
|
-
payload=payload.decode("latin-1"),
|
|
124
|
-
telegram_type=telegram_type,
|
|
125
|
-
serial_number=serial_number,
|
|
126
|
-
checksum=checksum,
|
|
127
|
-
checksum_valid=checksum_valid,
|
|
128
|
-
)
|
|
129
|
-
self.telegram_received(telegram_received)
|
|
130
|
-
|
|
131
|
-
def sendFrame(self, data: bytes) -> None:
|
|
132
|
-
"""
|
|
133
|
-
Send telegram frame.
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
data: Raw telegram payload (without checksum/framing).
|
|
137
|
-
|
|
138
|
-
Raises:
|
|
139
|
-
IOError: If transport is not open.
|
|
140
|
-
"""
|
|
141
|
-
# Calculate full frame (add checksum and brackets)
|
|
142
|
-
checksum = calculate_checksum(data.decode())
|
|
143
|
-
frame_data = data.decode() + checksum
|
|
144
|
-
frame = b"<" + frame_data.encode() + b">"
|
|
145
|
-
|
|
146
|
-
if not self.transport:
|
|
147
|
-
self.logger.info("Invalid transport")
|
|
148
|
-
raise IOError("Transport is not open")
|
|
149
|
-
|
|
150
|
-
self.logger.debug(f"Sending frame: {frame.decode()}")
|
|
151
|
-
self.transport.write(frame) # type: ignore
|
|
152
|
-
self.telegram_sent(frame.decode())
|
|
153
|
-
self._reset_timeout()
|
|
154
|
-
|
|
155
|
-
def send_telegram(
|
|
156
|
-
self,
|
|
157
|
-
telegram_type: TelegramType,
|
|
158
|
-
serial_number: str,
|
|
159
|
-
system_function: SystemFunction,
|
|
160
|
-
data_value: str,
|
|
161
|
-
) -> None:
|
|
162
|
-
"""
|
|
163
|
-
Send telegram with specified parameters.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
telegram_type: Type of telegram to send.
|
|
167
|
-
serial_number: Device serial number.
|
|
168
|
-
system_function: System function code.
|
|
169
|
-
data_value: Data value to send.
|
|
170
|
-
"""
|
|
171
|
-
payload = (
|
|
172
|
-
f"{telegram_type.value}"
|
|
173
|
-
f"{serial_number}"
|
|
174
|
-
f"F{system_function.value}"
|
|
175
|
-
f"D{data_value}"
|
|
176
|
-
)
|
|
177
|
-
self.sendFrame(payload.encode())
|
|
178
|
-
|
|
179
|
-
def buildProtocol(self, addr: IAddress) -> protocol.Protocol:
|
|
180
|
-
"""
|
|
181
|
-
Build protocol instance for connection.
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
addr: Address of the connection.
|
|
185
|
-
|
|
186
|
-
Returns:
|
|
187
|
-
Protocol instance for this connection.
|
|
188
|
-
"""
|
|
189
|
-
self.logger.debug(f"buildProtocol: {addr}")
|
|
190
|
-
return self
|
|
191
|
-
|
|
192
|
-
def clientConnectionFailed(self, connector: IConnector, reason: Failure) -> None:
|
|
193
|
-
"""
|
|
194
|
-
Handle client connection failure.
|
|
195
|
-
|
|
196
|
-
Args:
|
|
197
|
-
connector: Connection connector instance.
|
|
198
|
-
reason: Failure reason details.
|
|
199
|
-
"""
|
|
200
|
-
self.logger.debug(f"clientConnectionFailed: {reason}")
|
|
201
|
-
self.connection_failed(reason)
|
|
202
|
-
self._cancel_timeout()
|
|
203
|
-
self._stop_reactor()
|
|
204
|
-
|
|
205
|
-
def clientConnectionLost(self, connector: IConnector, reason: Failure) -> None:
|
|
206
|
-
"""
|
|
207
|
-
Handle client connection lost event.
|
|
208
|
-
|
|
209
|
-
Args:
|
|
210
|
-
connector: Connection connector instance.
|
|
211
|
-
reason: Reason for connection loss.
|
|
212
|
-
"""
|
|
213
|
-
self.logger.debug(f"clientConnectionLost: {reason}")
|
|
214
|
-
self.connection_lost(reason)
|
|
215
|
-
self._cancel_timeout()
|
|
216
|
-
self._stop_reactor()
|
|
217
|
-
|
|
218
|
-
def timeout(self) -> bool:
|
|
219
|
-
"""
|
|
220
|
-
Handle timeout event.
|
|
221
|
-
|
|
222
|
-
Returns:
|
|
223
|
-
True to continue waiting for next timeout, False to stop.
|
|
224
|
-
"""
|
|
225
|
-
self.logger.info("Timeout after: %ss", self.timeout_seconds)
|
|
226
|
-
self.failed(f"Timeout after: {self.timeout_seconds}s")
|
|
227
|
-
return False
|
|
228
|
-
|
|
229
|
-
def connection_failed(self, reason: Failure) -> None:
|
|
230
|
-
"""
|
|
231
|
-
Handle connection failure.
|
|
232
|
-
|
|
233
|
-
Args:
|
|
234
|
-
reason: Failure reason details.
|
|
235
|
-
"""
|
|
236
|
-
self.logger.debug(f"Client connection failed: {reason}")
|
|
237
|
-
self.failed(reason.getErrorMessage())
|
|
238
|
-
|
|
239
|
-
def _reset_timeout(self) -> None:
|
|
240
|
-
"""Reset the inactivity timeout."""
|
|
241
|
-
self._cancel_timeout()
|
|
242
|
-
self.timeout_call = self.reactor.callLater(
|
|
243
|
-
self.timeout_seconds, self._on_timeout
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
def _cancel_timeout(self) -> None:
|
|
247
|
-
"""Cancel the inactivity timeout."""
|
|
248
|
-
if self.timeout_call and self.timeout_call.active():
|
|
249
|
-
self.timeout_call.cancel()
|
|
250
|
-
|
|
251
|
-
def _on_timeout(self) -> None:
|
|
252
|
-
"""Handle inactivity timeout expiration."""
|
|
253
|
-
self.logger.debug(f"Conbus timeout after {self.timeout_seconds} seconds")
|
|
254
|
-
continue_work = self.timeout()
|
|
255
|
-
if not continue_work:
|
|
256
|
-
self._stop_reactor()
|
|
257
|
-
|
|
258
|
-
def _stop_reactor(self) -> None:
|
|
259
|
-
"""Stop the reactor if it's running."""
|
|
260
|
-
if self.reactor.running:
|
|
261
|
-
self.logger.info("Stopping reactor")
|
|
262
|
-
self.reactor.stop()
|
|
263
|
-
|
|
264
|
-
def start_reactor(self) -> None:
|
|
265
|
-
"""Start the reactor if it's running."""
|
|
266
|
-
# Connect to TCP server
|
|
267
|
-
self.logger.info(
|
|
268
|
-
f"Connecting to TCP server {self.cli_config.ip}:{self.cli_config.port}"
|
|
269
|
-
)
|
|
270
|
-
self.reactor.connectTCP(self.cli_config.ip, self.cli_config.port, self)
|
|
271
|
-
|
|
272
|
-
# Run the reactor (which now uses asyncio underneath)
|
|
273
|
-
self.logger.info("Starting reactor event loop.")
|
|
274
|
-
self.reactor.run()
|
|
275
|
-
|
|
276
|
-
def __enter__(self) -> "ConbusProtocol":
|
|
277
|
-
"""
|
|
278
|
-
Enter context manager.
|
|
279
|
-
|
|
280
|
-
Returns:
|
|
281
|
-
Self for context management.
|
|
282
|
-
"""
|
|
283
|
-
return self
|
|
284
|
-
|
|
285
|
-
def __exit__(
|
|
286
|
-
self,
|
|
287
|
-
_exc_type: Optional[type],
|
|
288
|
-
_exc_val: Optional[BaseException],
|
|
289
|
-
_exc_tb: Optional[Any],
|
|
290
|
-
) -> None:
|
|
291
|
-
"""Context manager exit - ensure connection is closed."""
|
|
292
|
-
self.logger.debug("Exiting the event loop.")
|
|
293
|
-
self._stop_reactor()
|
|
294
|
-
|
|
295
|
-
"""Override methods."""
|
|
296
|
-
|
|
297
|
-
def telegram_sent(self, telegram_sent: str) -> None:
|
|
298
|
-
"""
|
|
299
|
-
Override callback when telegram has been sent.
|
|
300
|
-
|
|
301
|
-
Args:
|
|
302
|
-
telegram_sent: The telegram that was sent.
|
|
303
|
-
"""
|
|
304
|
-
pass
|
|
305
|
-
|
|
306
|
-
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
307
|
-
"""
|
|
308
|
-
Override callback when telegram is received.
|
|
309
|
-
|
|
310
|
-
Args:
|
|
311
|
-
telegram_received: Event containing received telegram details.
|
|
312
|
-
"""
|
|
313
|
-
pass
|
|
314
|
-
|
|
315
|
-
def connection_established(self) -> None:
|
|
316
|
-
"""Override callback when connection established."""
|
|
317
|
-
pass
|
|
318
|
-
|
|
319
|
-
def connection_lost(self, reason: Failure) -> None:
|
|
320
|
-
"""
|
|
321
|
-
Override callback when connection is lost.
|
|
322
|
-
|
|
323
|
-
Args:
|
|
324
|
-
reason: Reason for connection loss.
|
|
325
|
-
"""
|
|
326
|
-
pass
|
|
327
|
-
|
|
328
|
-
def failed(self, message: str) -> None:
|
|
329
|
-
"""
|
|
330
|
-
Override callback when connection failed.
|
|
331
|
-
|
|
332
|
-
Args:
|
|
333
|
-
message: Error message describing the failure.
|
|
334
|
-
"""
|
|
335
|
-
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|