conson-xp 1.18.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- conson_xp-1.18.0.dist-info/METADATA +412 -0
- conson_xp-1.18.0.dist-info/RECORD +176 -0
- conson_xp-1.18.0.dist-info/WHEEL +4 -0
- conson_xp-1.18.0.dist-info/entry_points.txt +5 -0
- conson_xp-1.18.0.dist-info/licenses/LICENSE +29 -0
- xp/__init__.py +9 -0
- xp/cli/__init__.py +5 -0
- xp/cli/__main__.py +6 -0
- xp/cli/commands/__init__.py +153 -0
- xp/cli/commands/conbus/__init__.py +25 -0
- xp/cli/commands/conbus/conbus.py +128 -0
- xp/cli/commands/conbus/conbus_actiontable_commands.py +233 -0
- xp/cli/commands/conbus/conbus_autoreport_commands.py +108 -0
- xp/cli/commands/conbus/conbus_blink_commands.py +163 -0
- xp/cli/commands/conbus/conbus_config_commands.py +29 -0
- xp/cli/commands/conbus/conbus_custom_commands.py +57 -0
- xp/cli/commands/conbus/conbus_datapoint_commands.py +113 -0
- xp/cli/commands/conbus/conbus_discover_commands.py +61 -0
- xp/cli/commands/conbus/conbus_event_commands.py +81 -0
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +207 -0
- xp/cli/commands/conbus/conbus_linknumber_commands.py +102 -0
- xp/cli/commands/conbus/conbus_modulenumber_commands.py +104 -0
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +94 -0
- xp/cli/commands/conbus/conbus_output_commands.py +163 -0
- xp/cli/commands/conbus/conbus_raw_commands.py +62 -0
- xp/cli/commands/conbus/conbus_receive_commands.py +59 -0
- xp/cli/commands/conbus/conbus_scan_commands.py +58 -0
- xp/cli/commands/file_commands.py +186 -0
- xp/cli/commands/homekit/__init__.py +3 -0
- xp/cli/commands/homekit/homekit.py +118 -0
- xp/cli/commands/homekit/homekit_start_commands.py +43 -0
- xp/cli/commands/module_commands.py +187 -0
- xp/cli/commands/reverse_proxy_commands.py +178 -0
- xp/cli/commands/server/__init__.py +3 -0
- xp/cli/commands/server/server_commands.py +135 -0
- xp/cli/commands/telegram/__init__.py +5 -0
- xp/cli/commands/telegram/telegram.py +41 -0
- xp/cli/commands/telegram/telegram_blink_commands.py +79 -0
- xp/cli/commands/telegram/telegram_checksum_commands.py +112 -0
- xp/cli/commands/telegram/telegram_discover_commands.py +41 -0
- xp/cli/commands/telegram/telegram_linknumber_commands.py +86 -0
- xp/cli/commands/telegram/telegram_parse_commands.py +75 -0
- xp/cli/commands/telegram/telegram_version_commands.py +52 -0
- xp/cli/main.py +87 -0
- xp/cli/utils/__init__.py +1 -0
- xp/cli/utils/click_tree.py +57 -0
- xp/cli/utils/datapoint_type_choice.py +57 -0
- xp/cli/utils/decorators.py +351 -0
- xp/cli/utils/error_handlers.py +201 -0
- xp/cli/utils/formatters.py +312 -0
- xp/cli/utils/module_type_choice.py +56 -0
- xp/cli/utils/serial_number_type.py +52 -0
- xp/cli/utils/system_function_choice.py +57 -0
- xp/cli/utils/xp_module_type.py +53 -0
- xp/connection/__init__.py +13 -0
- xp/connection/exceptions.py +22 -0
- xp/models/__init__.py +36 -0
- xp/models/actiontable/__init__.py +1 -0
- xp/models/actiontable/actiontable.py +43 -0
- xp/models/actiontable/msactiontable_xp20.py +53 -0
- xp/models/actiontable/msactiontable_xp24.py +58 -0
- xp/models/actiontable/msactiontable_xp33.py +65 -0
- xp/models/conbus/__init__.py +1 -0
- xp/models/conbus/conbus.py +87 -0
- xp/models/conbus/conbus_autoreport.py +67 -0
- xp/models/conbus/conbus_blink.py +80 -0
- xp/models/conbus/conbus_client_config.py +55 -0
- xp/models/conbus/conbus_connection_status.py +40 -0
- xp/models/conbus/conbus_custom.py +58 -0
- xp/models/conbus/conbus_datapoint.py +89 -0
- xp/models/conbus/conbus_discover.py +64 -0
- xp/models/conbus/conbus_event_raw.py +47 -0
- xp/models/conbus/conbus_lightlevel.py +52 -0
- xp/models/conbus/conbus_linknumber.py +54 -0
- xp/models/conbus/conbus_output.py +57 -0
- xp/models/conbus/conbus_raw.py +45 -0
- xp/models/conbus/conbus_receive.py +42 -0
- xp/models/conbus/conbus_writeconfig.py +60 -0
- xp/models/homekit/__init__.py +1 -0
- xp/models/homekit/homekit_accessory.py +35 -0
- xp/models/homekit/homekit_config.py +106 -0
- xp/models/homekit/homekit_conson_config.py +86 -0
- xp/models/log_entry.py +130 -0
- xp/models/protocol/__init__.py +1 -0
- xp/models/protocol/conbus_protocol.py +312 -0
- xp/models/response.py +42 -0
- xp/models/telegram/__init__.py +1 -0
- xp/models/telegram/action_type.py +31 -0
- xp/models/telegram/datapoint_type.py +82 -0
- xp/models/telegram/event_telegram.py +140 -0
- xp/models/telegram/event_type.py +15 -0
- xp/models/telegram/input_action_type.py +69 -0
- xp/models/telegram/input_type.py +17 -0
- xp/models/telegram/module_type.py +188 -0
- xp/models/telegram/module_type_code.py +205 -0
- xp/models/telegram/output_telegram.py +103 -0
- xp/models/telegram/reply_telegram.py +297 -0
- xp/models/telegram/system_function.py +116 -0
- xp/models/telegram/system_telegram.py +94 -0
- xp/models/telegram/telegram.py +28 -0
- xp/models/telegram/telegram_type.py +19 -0
- xp/models/telegram/timeparam_type.py +51 -0
- xp/models/write_config_type.py +33 -0
- xp/services/__init__.py +26 -0
- xp/services/actiontable/__init__.py +1 -0
- xp/services/actiontable/actiontable_serializer.py +273 -0
- xp/services/actiontable/msactiontable_serializer.py +7 -0
- xp/services/actiontable/msactiontable_xp20_serializer.py +169 -0
- xp/services/actiontable/msactiontable_xp24_serializer.py +120 -0
- xp/services/actiontable/msactiontable_xp33_serializer.py +239 -0
- xp/services/conbus/__init__.py +1 -0
- xp/services/conbus/actiontable/__init__.py +1 -0
- xp/services/conbus/actiontable/actiontable_download_service.py +158 -0
- xp/services/conbus/actiontable/actiontable_list_service.py +91 -0
- xp/services/conbus/actiontable/actiontable_show_service.py +89 -0
- xp/services/conbus/actiontable/actiontable_upload_service.py +211 -0
- xp/services/conbus/actiontable/msactiontable_service.py +232 -0
- xp/services/conbus/conbus_blink_all_service.py +181 -0
- xp/services/conbus/conbus_blink_service.py +158 -0
- xp/services/conbus/conbus_custom_service.py +156 -0
- xp/services/conbus/conbus_datapoint_queryall_service.py +182 -0
- xp/services/conbus/conbus_datapoint_service.py +170 -0
- xp/services/conbus/conbus_discover_service.py +312 -0
- xp/services/conbus/conbus_event_raw_service.py +181 -0
- xp/services/conbus/conbus_output_service.py +194 -0
- xp/services/conbus/conbus_raw_service.py +122 -0
- xp/services/conbus/conbus_receive_service.py +115 -0
- xp/services/conbus/conbus_scan_service.py +150 -0
- xp/services/conbus/write_config_service.py +194 -0
- xp/services/homekit/__init__.py +1 -0
- xp/services/homekit/homekit_cache_service.py +307 -0
- xp/services/homekit/homekit_conbus_service.py +93 -0
- xp/services/homekit/homekit_config_validator.py +310 -0
- xp/services/homekit/homekit_conson_validator.py +121 -0
- xp/services/homekit/homekit_dimminglight.py +182 -0
- xp/services/homekit/homekit_dimminglight_service.py +148 -0
- xp/services/homekit/homekit_hap_service.py +342 -0
- xp/services/homekit/homekit_lightbulb.py +120 -0
- xp/services/homekit/homekit_lightbulb_service.py +86 -0
- xp/services/homekit/homekit_module_service.py +56 -0
- xp/services/homekit/homekit_outlet.py +168 -0
- xp/services/homekit/homekit_outlet_service.py +121 -0
- xp/services/homekit/homekit_service.py +359 -0
- xp/services/log_file_service.py +309 -0
- xp/services/module_type_service.py +257 -0
- xp/services/protocol/__init__.py +21 -0
- xp/services/protocol/conbus_event_protocol.py +360 -0
- xp/services/protocol/conbus_protocol.py +318 -0
- xp/services/protocol/protocol_factory.py +78 -0
- xp/services/protocol/telegram_protocol.py +264 -0
- xp/services/reverse_proxy_service.py +435 -0
- xp/services/server/__init__.py +1 -0
- xp/services/server/base_server_service.py +366 -0
- xp/services/server/cp20_server_service.py +65 -0
- xp/services/server/device_service_factory.py +94 -0
- xp/services/server/server_service.py +428 -0
- xp/services/server/xp130_server_service.py +67 -0
- xp/services/server/xp20_server_service.py +92 -0
- xp/services/server/xp230_server_service.py +58 -0
- xp/services/server/xp24_server_service.py +245 -0
- xp/services/server/xp33_server_service.py +535 -0
- xp/services/telegram/__init__.py +1 -0
- xp/services/telegram/telegram_blink_service.py +138 -0
- xp/services/telegram/telegram_checksum_service.py +149 -0
- xp/services/telegram/telegram_datapoint_service.py +82 -0
- xp/services/telegram/telegram_discover_service.py +277 -0
- xp/services/telegram/telegram_link_number_service.py +216 -0
- xp/services/telegram/telegram_output_service.py +322 -0
- xp/services/telegram/telegram_service.py +380 -0
- xp/services/telegram/telegram_version_service.py +288 -0
- xp/utils/__init__.py +12 -0
- xp/utils/checksum.py +61 -0
- xp/utils/dependencies.py +531 -0
- xp/utils/event_helper.py +31 -0
- xp/utils/serialization.py +205 -0
- xp/utils/time_utils.py +134 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Service for uploading ActionTable via Conbus protocol."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Callable, Optional
|
|
5
|
+
|
|
6
|
+
from twisted.internet.posixbase import PosixReactorBase
|
|
7
|
+
|
|
8
|
+
from xp.models import ConbusClientConfig
|
|
9
|
+
from xp.models.homekit.homekit_conson_config import ConsonModuleListConfig
|
|
10
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
11
|
+
from xp.models.telegram.system_function import SystemFunction
|
|
12
|
+
from xp.models.telegram.telegram_type import TelegramType
|
|
13
|
+
from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
|
|
14
|
+
from xp.services.protocol import ConbusProtocol
|
|
15
|
+
from xp.services.telegram.telegram_service import TelegramService
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ActionTableUploadService(ConbusProtocol):
|
|
19
|
+
"""TCP client service for uploading action tables to Conbus modules.
|
|
20
|
+
|
|
21
|
+
Manages TCP socket connections, handles telegram generation and transmission,
|
|
22
|
+
and processes server responses for action table uploads.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
cli_config: ConbusClientConfig,
|
|
28
|
+
reactor: PosixReactorBase,
|
|
29
|
+
actiontable_serializer: ActionTableSerializer,
|
|
30
|
+
telegram_service: TelegramService,
|
|
31
|
+
conson_config: ConsonModuleListConfig,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""Initialize the action table upload service.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
cli_config: Conbus client configuration.
|
|
37
|
+
reactor: Twisted reactor instance.
|
|
38
|
+
actiontable_serializer: Action table serializer.
|
|
39
|
+
telegram_service: Telegram service for parsing.
|
|
40
|
+
conson_config: Conson module list configuration.
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(cli_config, reactor)
|
|
43
|
+
self.serializer = actiontable_serializer
|
|
44
|
+
self.telegram_service = telegram_service
|
|
45
|
+
self.conson_config = conson_config
|
|
46
|
+
self.serial_number: str = ""
|
|
47
|
+
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
48
|
+
self.error_callback: Optional[Callable[[str], None]] = None
|
|
49
|
+
self.success_callback: Optional[Callable[[], None]] = None
|
|
50
|
+
|
|
51
|
+
# Upload state
|
|
52
|
+
self.upload_data_chunks: list[str] = []
|
|
53
|
+
self.current_chunk_index: int = 0
|
|
54
|
+
|
|
55
|
+
# Set up logging
|
|
56
|
+
self.logger = logging.getLogger(__name__)
|
|
57
|
+
|
|
58
|
+
def connection_established(self) -> None:
|
|
59
|
+
"""Handle connection established event."""
|
|
60
|
+
self.logger.debug("Connection established, sending upload actiontable telegram")
|
|
61
|
+
self.send_telegram(
|
|
62
|
+
telegram_type=TelegramType.SYSTEM,
|
|
63
|
+
serial_number=self.serial_number,
|
|
64
|
+
system_function=SystemFunction.UPLOAD_ACTIONTABLE,
|
|
65
|
+
data_value="00",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def telegram_sent(self, telegram_sent: str) -> None:
|
|
69
|
+
"""Handle telegram sent event.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
telegram_sent: The telegram that was sent.
|
|
73
|
+
"""
|
|
74
|
+
self.logger.debug(f"Telegram sent: {telegram_sent}")
|
|
75
|
+
|
|
76
|
+
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
77
|
+
"""Handle telegram received event.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
telegram_received: The telegram received event.
|
|
81
|
+
"""
|
|
82
|
+
self.logger.debug(f"Telegram received: {telegram_received}")
|
|
83
|
+
if (
|
|
84
|
+
not telegram_received.checksum_valid
|
|
85
|
+
or telegram_received.telegram_type != TelegramType.REPLY.value
|
|
86
|
+
or telegram_received.serial_number != self.serial_number
|
|
87
|
+
):
|
|
88
|
+
self.logger.debug("Not a reply response")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
reply_telegram = self.telegram_service.parse_reply_telegram(
|
|
92
|
+
telegram_received.frame
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self._handle_upload_response(reply_telegram)
|
|
96
|
+
|
|
97
|
+
def _handle_upload_response(self, reply_telegram: Any) -> None:
|
|
98
|
+
"""Handle telegram responses during upload.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
reply_telegram: Parsed reply telegram.
|
|
102
|
+
"""
|
|
103
|
+
if reply_telegram.system_function == SystemFunction.ACK:
|
|
104
|
+
self.logger.debug("Received ACK for upload")
|
|
105
|
+
# Send next chunk or EOF
|
|
106
|
+
if self.current_chunk_index < len(self.upload_data_chunks):
|
|
107
|
+
chunk = self.upload_data_chunks[self.current_chunk_index]
|
|
108
|
+
self.logger.debug(f"Sending chunk {self.current_chunk_index + 1}")
|
|
109
|
+
|
|
110
|
+
# Calculate prefix: AA, AB, AC, AD, AE, AF, AG, AH, AI, AJ, AK, AL, AM, AN, AO
|
|
111
|
+
# First character: 'A' (fixed)
|
|
112
|
+
# Second character: 'A' + chunk_index (sequential counter A-O for 15 chunks)
|
|
113
|
+
prefix_hex = f"AAA{ord('A') + self.current_chunk_index:c}"
|
|
114
|
+
|
|
115
|
+
self.send_telegram(
|
|
116
|
+
telegram_type=TelegramType.SYSTEM,
|
|
117
|
+
serial_number=self.serial_number,
|
|
118
|
+
system_function=SystemFunction.ACTIONTABLE,
|
|
119
|
+
data_value=f"{prefix_hex}{chunk}",
|
|
120
|
+
)
|
|
121
|
+
self.current_chunk_index += 1
|
|
122
|
+
if self.progress_callback:
|
|
123
|
+
self.progress_callback(".")
|
|
124
|
+
else:
|
|
125
|
+
# All chunks sent, send EOF
|
|
126
|
+
self.logger.debug("All chunks sent, sending EOF")
|
|
127
|
+
self.send_telegram(
|
|
128
|
+
telegram_type=TelegramType.SYSTEM,
|
|
129
|
+
serial_number=self.serial_number,
|
|
130
|
+
system_function=SystemFunction.EOF,
|
|
131
|
+
data_value="00",
|
|
132
|
+
)
|
|
133
|
+
if self.success_callback:
|
|
134
|
+
self.success_callback()
|
|
135
|
+
self._stop_reactor()
|
|
136
|
+
elif reply_telegram.system_function == SystemFunction.NAK:
|
|
137
|
+
self.logger.debug("Received NAK during upload")
|
|
138
|
+
self.failed("Upload failed: NAK received")
|
|
139
|
+
else:
|
|
140
|
+
self.logger.debug(f"Unexpected response during upload: {reply_telegram}")
|
|
141
|
+
|
|
142
|
+
def failed(self, message: str) -> None:
|
|
143
|
+
"""Handle failed connection event.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
message: Failure message.
|
|
147
|
+
"""
|
|
148
|
+
self.logger.debug(f"Failed: {message}")
|
|
149
|
+
if self.error_callback:
|
|
150
|
+
self.error_callback(message)
|
|
151
|
+
self._stop_reactor()
|
|
152
|
+
|
|
153
|
+
def start(
|
|
154
|
+
self,
|
|
155
|
+
serial_number: str,
|
|
156
|
+
progress_callback: Callable[[str], None],
|
|
157
|
+
error_callback: Callable[[str], None],
|
|
158
|
+
success_callback: Callable[[], None],
|
|
159
|
+
timeout_seconds: Optional[float] = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Upload action table to module.
|
|
162
|
+
|
|
163
|
+
Uploads the action table configuration to the specified module.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
serial_number: Module serial number.
|
|
167
|
+
progress_callback: Callback for progress updates.
|
|
168
|
+
error_callback: Callback for errors.
|
|
169
|
+
success_callback: Callback when upload completes successfully.
|
|
170
|
+
timeout_seconds: Optional timeout in seconds.
|
|
171
|
+
"""
|
|
172
|
+
self.logger.info("Starting actiontable upload")
|
|
173
|
+
self.serial_number = serial_number
|
|
174
|
+
if timeout_seconds:
|
|
175
|
+
self.timeout_seconds = timeout_seconds
|
|
176
|
+
self.progress_callback = progress_callback
|
|
177
|
+
self.error_callback = error_callback
|
|
178
|
+
self.success_callback = success_callback
|
|
179
|
+
|
|
180
|
+
# Find module
|
|
181
|
+
module = self.conson_config.find_module(serial_number)
|
|
182
|
+
if not module:
|
|
183
|
+
self.failed(f"Module {serial_number} not found in conson.yml")
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
# Parse action table strings to ActionTable object
|
|
187
|
+
try:
|
|
188
|
+
module_action_table = module.action_table or []
|
|
189
|
+
action_table = self.serializer.parse_action_table(module_action_table)
|
|
190
|
+
except ValueError as e:
|
|
191
|
+
self.logger.error(f"Invalid action table format: {e}")
|
|
192
|
+
self.failed(f"Invalid action table format: {e}")
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
# Encode action table to hex string
|
|
196
|
+
encoded_data = self.serializer.to_encoded_string(action_table)
|
|
197
|
+
|
|
198
|
+
# Chunk the data into 64 byte chunks
|
|
199
|
+
chunk_size = 64
|
|
200
|
+
self.upload_data_chunks = [
|
|
201
|
+
encoded_data[i : i + chunk_size]
|
|
202
|
+
for i in range(0, len(encoded_data), chunk_size)
|
|
203
|
+
]
|
|
204
|
+
self.current_chunk_index = 0
|
|
205
|
+
|
|
206
|
+
self.logger.debug(
|
|
207
|
+
f"Upload data encoded: {len(encoded_data)} chars, "
|
|
208
|
+
f"{len(self.upload_data_chunks)} chunks"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
self.start_reactor()
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Service for downloading XP24 action tables via Conbus protocol."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Callable, Optional, Union
|
|
5
|
+
|
|
6
|
+
from twisted.internet.posixbase import PosixReactorBase
|
|
7
|
+
|
|
8
|
+
from xp.models import ConbusClientConfig
|
|
9
|
+
from xp.models.actiontable.msactiontable_xp20 import Xp20MsActionTable
|
|
10
|
+
from xp.models.actiontable.msactiontable_xp24 import Xp24MsActionTable
|
|
11
|
+
from xp.models.actiontable.msactiontable_xp33 import Xp33MsActionTable
|
|
12
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
13
|
+
from xp.models.telegram.system_function import SystemFunction
|
|
14
|
+
from xp.models.telegram.telegram_type import TelegramType
|
|
15
|
+
from xp.services.actiontable.msactiontable_xp20_serializer import (
|
|
16
|
+
Xp20MsActionTableSerializer,
|
|
17
|
+
)
|
|
18
|
+
from xp.services.actiontable.msactiontable_xp24_serializer import (
|
|
19
|
+
Xp24MsActionTableSerializer,
|
|
20
|
+
)
|
|
21
|
+
from xp.services.actiontable.msactiontable_xp33_serializer import (
|
|
22
|
+
Xp33MsActionTableSerializer,
|
|
23
|
+
)
|
|
24
|
+
from xp.services.protocol import ConbusProtocol
|
|
25
|
+
from xp.services.telegram.telegram_service import TelegramService
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MsActionTableError(Exception):
|
|
29
|
+
"""Raised when XP24 action table operations fail."""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MsActionTableService(ConbusProtocol):
|
|
35
|
+
"""
|
|
36
|
+
TCP client service for sending telegrams to Conbus servers.
|
|
37
|
+
|
|
38
|
+
Manages TCP socket connections, handles telegram generation and transmission,
|
|
39
|
+
and processes server responses.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
cli_config: ConbusClientConfig,
|
|
45
|
+
reactor: PosixReactorBase,
|
|
46
|
+
xp20ms_serializer: Xp20MsActionTableSerializer,
|
|
47
|
+
xp24ms_serializer: Xp24MsActionTableSerializer,
|
|
48
|
+
xp33ms_serializer: Xp33MsActionTableSerializer,
|
|
49
|
+
telegram_service: TelegramService,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Initialize the Conbus client send service.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
cli_config: Conbus client configuration.
|
|
55
|
+
reactor: Twisted reactor instance.
|
|
56
|
+
xp20ms_serializer: XP20 MS action table serializer.
|
|
57
|
+
xp24ms_serializer: XP24 MS action table serializer.
|
|
58
|
+
xp33ms_serializer: XP33 MS action table serializer.
|
|
59
|
+
telegram_service: Telegram service for parsing.
|
|
60
|
+
"""
|
|
61
|
+
super().__init__(cli_config, reactor)
|
|
62
|
+
self.xp20ms_serializer = xp20ms_serializer
|
|
63
|
+
self.xp24ms_serializer = xp24ms_serializer
|
|
64
|
+
self.xp33ms_serializer = xp33ms_serializer
|
|
65
|
+
self.serializer: Union[
|
|
66
|
+
Xp20MsActionTableSerializer,
|
|
67
|
+
Xp24MsActionTableSerializer,
|
|
68
|
+
Xp33MsActionTableSerializer,
|
|
69
|
+
] = xp20ms_serializer
|
|
70
|
+
self.telegram_service = telegram_service
|
|
71
|
+
self.serial_number: str = ""
|
|
72
|
+
self.xpmoduletype: str = ""
|
|
73
|
+
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
74
|
+
self.error_callback: Optional[Callable[[str], None]] = None
|
|
75
|
+
self.finish_callback: Optional[
|
|
76
|
+
Callable[
|
|
77
|
+
[Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable, None]],
|
|
78
|
+
None,
|
|
79
|
+
]
|
|
80
|
+
] = None
|
|
81
|
+
self.msactiontable_data: list[str] = []
|
|
82
|
+
# Set up logging
|
|
83
|
+
self.logger = logging.getLogger(__name__)
|
|
84
|
+
|
|
85
|
+
def connection_established(self) -> None:
|
|
86
|
+
"""Handle connection established event."""
|
|
87
|
+
self.logger.debug(
|
|
88
|
+
"Connection established, sending download msactiontable telegram"
|
|
89
|
+
)
|
|
90
|
+
self.send_telegram(
|
|
91
|
+
telegram_type=TelegramType.SYSTEM,
|
|
92
|
+
serial_number=self.serial_number,
|
|
93
|
+
system_function=SystemFunction.DOWNLOAD_MSACTIONTABLE,
|
|
94
|
+
data_value="00",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def telegram_sent(self, telegram_sent: str) -> None:
|
|
98
|
+
"""Handle telegram sent event.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
telegram_sent: The telegram that was sent.
|
|
102
|
+
"""
|
|
103
|
+
self.logger.debug(f"Telegram sent: {telegram_sent}")
|
|
104
|
+
|
|
105
|
+
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
106
|
+
"""Handle telegram received event.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
telegram_received: The telegram received event.
|
|
110
|
+
"""
|
|
111
|
+
self.logger.debug(f"Telegram received: {telegram_received}")
|
|
112
|
+
if (
|
|
113
|
+
not telegram_received.checksum_valid
|
|
114
|
+
or telegram_received.telegram_type != TelegramType.REPLY.value
|
|
115
|
+
or telegram_received.serial_number != self.serial_number
|
|
116
|
+
):
|
|
117
|
+
self.logger.debug("Not a reply response")
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
reply_telegram = self.telegram_service.parse_reply_telegram(
|
|
121
|
+
telegram_received.frame
|
|
122
|
+
)
|
|
123
|
+
if reply_telegram.system_function not in (
|
|
124
|
+
SystemFunction.MSACTIONTABLE,
|
|
125
|
+
SystemFunction.ACK,
|
|
126
|
+
SystemFunction.NAK,
|
|
127
|
+
SystemFunction.EOF,
|
|
128
|
+
):
|
|
129
|
+
self.logger.debug("Not a msactiontable response")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
if reply_telegram.system_function == SystemFunction.ACK:
|
|
133
|
+
self.logger.debug("Received ACK")
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
if reply_telegram.system_function == SystemFunction.NAK:
|
|
137
|
+
self.logger.debug("Received NAK")
|
|
138
|
+
self.failed("Received NAK")
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
if reply_telegram.system_function == SystemFunction.MSACTIONTABLE:
|
|
142
|
+
self.logger.debug("Received MSACTIONTABLE")
|
|
143
|
+
self.msactiontable_data.extend(
|
|
144
|
+
(reply_telegram.data, reply_telegram.data_value)
|
|
145
|
+
)
|
|
146
|
+
if self.progress_callback:
|
|
147
|
+
self.progress_callback(".")
|
|
148
|
+
|
|
149
|
+
self.send_telegram(
|
|
150
|
+
telegram_type=TelegramType.SYSTEM,
|
|
151
|
+
serial_number=self.serial_number,
|
|
152
|
+
system_function=SystemFunction.ACK,
|
|
153
|
+
data_value="00",
|
|
154
|
+
)
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
if reply_telegram.system_function == SystemFunction.EOF:
|
|
158
|
+
self.logger.debug("Received EOF")
|
|
159
|
+
all_data = "".join(self.msactiontable_data)
|
|
160
|
+
# Deserialize from received data
|
|
161
|
+
msactiontable = self.serializer.from_data(all_data)
|
|
162
|
+
self.succeed(msactiontable)
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
self.logger.debug("Invalid msactiontable response")
|
|
166
|
+
|
|
167
|
+
def failed(self, message: str) -> None:
|
|
168
|
+
"""Handle failed connection event.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
message: Failure message.
|
|
172
|
+
"""
|
|
173
|
+
self.logger.debug(f"Failed: {message}")
|
|
174
|
+
if self.error_callback:
|
|
175
|
+
self.error_callback(message)
|
|
176
|
+
self._stop_reactor()
|
|
177
|
+
|
|
178
|
+
def succeed(
|
|
179
|
+
self,
|
|
180
|
+
msactiontable: Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable],
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Handle succeed connection event.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
msactiontable: result.
|
|
186
|
+
"""
|
|
187
|
+
if self.finish_callback:
|
|
188
|
+
self.finish_callback(msactiontable)
|
|
189
|
+
self._stop_reactor()
|
|
190
|
+
|
|
191
|
+
def start(
|
|
192
|
+
self,
|
|
193
|
+
serial_number: str,
|
|
194
|
+
xpmoduletype: str,
|
|
195
|
+
progress_callback: Callable[[str], None],
|
|
196
|
+
error_callback: Callable[[str], None],
|
|
197
|
+
finish_callback: Callable[
|
|
198
|
+
[Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable, None]], None
|
|
199
|
+
],
|
|
200
|
+
timeout_seconds: Optional[float] = None,
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Run reactor in dedicated thread with its own event loop.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
serial_number: Module serial number.
|
|
206
|
+
xpmoduletype: XP module type (xp20, xp24, xp33).
|
|
207
|
+
progress_callback: Callback for progress updates.
|
|
208
|
+
error_callback: Callback for errors.
|
|
209
|
+
finish_callback: Callback when download completes.
|
|
210
|
+
timeout_seconds: Optional timeout in seconds.
|
|
211
|
+
|
|
212
|
+
Raises:
|
|
213
|
+
MsActionTableError: If unsupported module type is provided.
|
|
214
|
+
"""
|
|
215
|
+
self.logger.info("Starting msactiontable")
|
|
216
|
+
self.serial_number = serial_number
|
|
217
|
+
self.xpmoduletype = xpmoduletype
|
|
218
|
+
if xpmoduletype == "xp20":
|
|
219
|
+
self.serializer = self.xp20ms_serializer
|
|
220
|
+
elif xpmoduletype == "xp24":
|
|
221
|
+
self.serializer = self.xp24ms_serializer
|
|
222
|
+
elif xpmoduletype == "xp33":
|
|
223
|
+
self.serializer = self.xp33ms_serializer
|
|
224
|
+
else:
|
|
225
|
+
raise MsActionTableError(f"Unsupported module type: {xpmoduletype}")
|
|
226
|
+
|
|
227
|
+
if timeout_seconds:
|
|
228
|
+
self.timeout_seconds = timeout_seconds
|
|
229
|
+
self.progress_callback = progress_callback
|
|
230
|
+
self.error_callback = error_callback
|
|
231
|
+
self.finish_callback = finish_callback
|
|
232
|
+
self.start_reactor()
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Conbus Blink All Service for TCP communication with Conbus servers.
|
|
2
|
+
|
|
3
|
+
This service implements a TCP client that connects to Conbus servers and sends
|
|
4
|
+
blink/unblink telegrams to all discovered modules on the network.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Callable, Optional
|
|
10
|
+
|
|
11
|
+
from twisted.internet.posixbase import PosixReactorBase
|
|
12
|
+
|
|
13
|
+
from xp.models import ConbusClientConfig
|
|
14
|
+
from xp.models.conbus.conbus_blink import ConbusBlinkResponse
|
|
15
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
16
|
+
from xp.models.telegram.system_function import SystemFunction
|
|
17
|
+
from xp.models.telegram.telegram_type import TelegramType
|
|
18
|
+
from xp.services.protocol import ConbusProtocol
|
|
19
|
+
from xp.services.telegram.telegram_service import TelegramService
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ConbusBlinkAllService(ConbusProtocol):
|
|
23
|
+
"""
|
|
24
|
+
Service for blinking all modules on Conbus servers.
|
|
25
|
+
|
|
26
|
+
Uses ConbusProtocol to provide blink/unblink functionality
|
|
27
|
+
for all discovered modules on the network.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
telegram_service: TelegramService,
|
|
33
|
+
cli_config: ConbusClientConfig,
|
|
34
|
+
reactor: PosixReactorBase,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Initialize the Conbus blink all service.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
telegram_service: Service for parsing telegrams.
|
|
40
|
+
cli_config: Configuration for Conbus client connection.
|
|
41
|
+
reactor: Twisted reactor for event loop.
|
|
42
|
+
"""
|
|
43
|
+
super().__init__(cli_config, reactor)
|
|
44
|
+
self.telegram_service = telegram_service
|
|
45
|
+
self.serial_number: str = ""
|
|
46
|
+
self.on_or_off = "none"
|
|
47
|
+
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
48
|
+
self.finish_callback: Optional[Callable[[ConbusBlinkResponse], None]] = None
|
|
49
|
+
self.service_response: ConbusBlinkResponse = ConbusBlinkResponse(
|
|
50
|
+
success=False,
|
|
51
|
+
serial_number=self.serial_number,
|
|
52
|
+
system_function=SystemFunction.NONE,
|
|
53
|
+
operation=self.on_or_off,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Set up logging
|
|
57
|
+
self.logger = logging.getLogger(__name__)
|
|
58
|
+
|
|
59
|
+
def connection_established(self) -> None:
|
|
60
|
+
"""Handle connection established event."""
|
|
61
|
+
self.logger.debug("Connection established, send discover telegram.")
|
|
62
|
+
self.send_telegram(
|
|
63
|
+
telegram_type=TelegramType.SYSTEM,
|
|
64
|
+
serial_number="0000000000",
|
|
65
|
+
system_function=SystemFunction.DISCOVERY,
|
|
66
|
+
data_value="00",
|
|
67
|
+
)
|
|
68
|
+
if self.progress_callback:
|
|
69
|
+
self.progress_callback(".")
|
|
70
|
+
|
|
71
|
+
def send_blink(self, serial_number: str) -> None:
|
|
72
|
+
"""Send blink or unblink telegram to a discovered module.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
serial_number: 10-digit module serial number.
|
|
76
|
+
"""
|
|
77
|
+
self.logger.debug("Device discovered, send blink.")
|
|
78
|
+
|
|
79
|
+
# Blink is 05, Unblink is 06
|
|
80
|
+
system_function = SystemFunction.UNBLINK
|
|
81
|
+
if self.on_or_off.lower() == "on":
|
|
82
|
+
system_function = SystemFunction.BLINK
|
|
83
|
+
|
|
84
|
+
self.send_telegram(
|
|
85
|
+
telegram_type=TelegramType.SYSTEM,
|
|
86
|
+
serial_number=serial_number,
|
|
87
|
+
system_function=system_function,
|
|
88
|
+
data_value="00",
|
|
89
|
+
)
|
|
90
|
+
self.service_response.system_function = system_function
|
|
91
|
+
self.service_response.operation = self.on_or_off
|
|
92
|
+
|
|
93
|
+
if self.progress_callback:
|
|
94
|
+
self.progress_callback(".")
|
|
95
|
+
|
|
96
|
+
def telegram_sent(self, telegram_sent: str) -> None:
|
|
97
|
+
"""Handle telegram sent event.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
telegram_sent: The telegram that was sent.
|
|
101
|
+
"""
|
|
102
|
+
system_telegram = self.telegram_service.parse_system_telegram(telegram_sent)
|
|
103
|
+
self.service_response.sent_telegram = system_telegram
|
|
104
|
+
|
|
105
|
+
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
106
|
+
"""Handle telegram received event.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
telegram_received: The telegram received event.
|
|
110
|
+
"""
|
|
111
|
+
self.logger.debug(f"Telegram received: {telegram_received}")
|
|
112
|
+
if not self.service_response.received_telegrams:
|
|
113
|
+
self.service_response.received_telegrams = []
|
|
114
|
+
self.service_response.received_telegrams.append(telegram_received.frame)
|
|
115
|
+
|
|
116
|
+
if (
|
|
117
|
+
not telegram_received.checksum_valid
|
|
118
|
+
or telegram_received.telegram_type != TelegramType.REPLY
|
|
119
|
+
):
|
|
120
|
+
self.logger.debug("Not a reply")
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
reply_telegram = self.telegram_service.parse_reply_telegram(
|
|
124
|
+
telegram_received.frame
|
|
125
|
+
)
|
|
126
|
+
if (
|
|
127
|
+
reply_telegram
|
|
128
|
+
and reply_telegram.system_function == SystemFunction.DISCOVERY
|
|
129
|
+
):
|
|
130
|
+
self.logger.debug("Received discovery response")
|
|
131
|
+
self.send_blink(reply_telegram.serial_number)
|
|
132
|
+
if self.progress_callback:
|
|
133
|
+
self.progress_callback(".")
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
if reply_telegram and reply_telegram.system_function in (
|
|
137
|
+
SystemFunction.BLINK,
|
|
138
|
+
SystemFunction.UNBLINK,
|
|
139
|
+
):
|
|
140
|
+
self.logger.debug("Received blink response")
|
|
141
|
+
if self.progress_callback:
|
|
142
|
+
self.progress_callback(".")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
self.logger.debug("Received unexpected response")
|
|
146
|
+
|
|
147
|
+
def failed(self, message: str) -> None:
|
|
148
|
+
"""Handle failed connection event.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
message: Failure message.
|
|
152
|
+
"""
|
|
153
|
+
self.logger.debug(f"Failed with message: {message}")
|
|
154
|
+
self.service_response.success = False
|
|
155
|
+
self.service_response.timestamp = datetime.now()
|
|
156
|
+
self.service_response.error = message
|
|
157
|
+
if self.finish_callback:
|
|
158
|
+
self.finish_callback(self.service_response)
|
|
159
|
+
|
|
160
|
+
def send_blink_all_telegram(
|
|
161
|
+
self,
|
|
162
|
+
on_or_off: str,
|
|
163
|
+
progress_callback: Callable[[str], None],
|
|
164
|
+
finish_callback: Callable[[ConbusBlinkResponse], None],
|
|
165
|
+
timeout_seconds: Optional[float] = None,
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Send blink command to all discovered modules.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
on_or_off: "on" to blink or "off" to unblink all devices.
|
|
171
|
+
progress_callback: Callback function to call with progress updates.
|
|
172
|
+
finish_callback: Callback function to call when the operation completes.
|
|
173
|
+
timeout_seconds: Timeout in seconds.
|
|
174
|
+
"""
|
|
175
|
+
self.logger.info("Starting send_blink_all_telegram")
|
|
176
|
+
if timeout_seconds:
|
|
177
|
+
self.timeout_seconds = timeout_seconds
|
|
178
|
+
self.progress_callback = progress_callback
|
|
179
|
+
self.finish_callback = finish_callback
|
|
180
|
+
self.on_or_off = on_or_off
|
|
181
|
+
self.start_reactor()
|