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,366 @@
|
|
|
1
|
+
"""Base Server Service with shared functionality.
|
|
2
|
+
|
|
3
|
+
This module provides a base class for all XP device server services,
|
|
4
|
+
containing common functionality like module type response generation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import threading
|
|
9
|
+
from abc import ABC
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
from xp.models import ModuleTypeCode
|
|
13
|
+
from xp.models.telegram.datapoint_type import DataPointType
|
|
14
|
+
from xp.models.telegram.system_function import SystemFunction
|
|
15
|
+
from xp.models.telegram.system_telegram import SystemTelegram
|
|
16
|
+
from xp.utils.checksum import calculate_checksum
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseServerService(ABC):
|
|
20
|
+
"""
|
|
21
|
+
Base class for all XP device server services.
|
|
22
|
+
|
|
23
|
+
Provides common functionality that is shared across all device types,
|
|
24
|
+
such as module type response generation.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, serial_number: str):
|
|
28
|
+
"""Initialize base server service.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
serial_number: The device serial number.
|
|
32
|
+
"""
|
|
33
|
+
self.serial_number = serial_number
|
|
34
|
+
self.logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
# Must be set by subclasses
|
|
37
|
+
self.device_type: str = ""
|
|
38
|
+
self.module_type_code: ModuleTypeCode = ModuleTypeCode.NOMOD
|
|
39
|
+
self.hardware_version: str = ""
|
|
40
|
+
self.software_version: str = ""
|
|
41
|
+
self.device_status: str = "OK"
|
|
42
|
+
self.link_number: int = 1
|
|
43
|
+
self.temperature: str = "+23,5§C"
|
|
44
|
+
self.voltage: str = "+12,5§V"
|
|
45
|
+
|
|
46
|
+
self.telegram_buffer: list[str] = []
|
|
47
|
+
self.telegram_buffer_lock = threading.Lock() # Lock for socket set
|
|
48
|
+
|
|
49
|
+
# MsActionTable download state (None, "ack_sent", "data_sent")
|
|
50
|
+
self.msactiontable_download_state: Optional[str] = None
|
|
51
|
+
|
|
52
|
+
def generate_datapoint_type_response(
|
|
53
|
+
self, datapoint_type: DataPointType
|
|
54
|
+
) -> Optional[str]:
|
|
55
|
+
"""Generate datapoint_type response telegram.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
datapoint_type: The type of datapoint to query.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
The response telegram string, or None if generation fails.
|
|
62
|
+
"""
|
|
63
|
+
datapoint_values = {
|
|
64
|
+
DataPointType.TEMPERATURE: self.temperature,
|
|
65
|
+
DataPointType.MODULE_TYPE_CODE: f"{self.module_type_code.value:02}",
|
|
66
|
+
DataPointType.SW_VERSION: self.software_version,
|
|
67
|
+
DataPointType.MODULE_STATE: self.device_status,
|
|
68
|
+
DataPointType.MODULE_TYPE: self.device_type,
|
|
69
|
+
DataPointType.LINK_NUMBER: f"{self.link_number:02}",
|
|
70
|
+
DataPointType.VOLTAGE: self.voltage,
|
|
71
|
+
DataPointType.HW_VERSION: self.hardware_version,
|
|
72
|
+
DataPointType.MODULE_ERROR_CODE: "00",
|
|
73
|
+
}
|
|
74
|
+
data_value = datapoint_values.get(datapoint_type) or "00"
|
|
75
|
+
data_part = f"R{self.serial_number}F02D{datapoint_type.value}{data_value}"
|
|
76
|
+
telegram = self._build_response_telegram(data_part)
|
|
77
|
+
|
|
78
|
+
self.logger.debug(
|
|
79
|
+
f"Generated {self.device_type} module type response: {telegram}"
|
|
80
|
+
)
|
|
81
|
+
return telegram
|
|
82
|
+
|
|
83
|
+
def _check_request_for_device(self, request: SystemTelegram) -> bool:
|
|
84
|
+
"""Check if request is for this device (including broadcast).
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
request: The system telegram request to check.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
True if request is for this device or broadcast, False otherwise.
|
|
91
|
+
"""
|
|
92
|
+
return request.serial_number in (self.serial_number, "0000000000")
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _build_response_telegram(data_part: str) -> str:
|
|
96
|
+
"""Build a complete response telegram with checksum.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
data_part: The data part of the telegram without checksum.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
The complete telegram with checksum enclosed in angle brackets.
|
|
103
|
+
"""
|
|
104
|
+
checksum = calculate_checksum(data_part)
|
|
105
|
+
return f"<{data_part}{checksum}>"
|
|
106
|
+
|
|
107
|
+
def _log_response(self, response_type: str, telegram: str) -> None:
|
|
108
|
+
"""Log response generation.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
response_type: The type of response being generated.
|
|
112
|
+
telegram: The telegram string being logged.
|
|
113
|
+
"""
|
|
114
|
+
self.logger.debug(
|
|
115
|
+
f"Generated {self.device_type} {response_type} response: {telegram}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def generate_discover_response(self) -> str:
|
|
119
|
+
"""Generate discover response telegram.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
The discover response telegram string.
|
|
123
|
+
"""
|
|
124
|
+
data_part = f"R{self.serial_number}F01D"
|
|
125
|
+
telegram = self._build_response_telegram(data_part)
|
|
126
|
+
self._log_response("discover", telegram)
|
|
127
|
+
return telegram
|
|
128
|
+
|
|
129
|
+
def set_link_number(
|
|
130
|
+
self, request: SystemTelegram, new_link_number: int
|
|
131
|
+
) -> Optional[str]:
|
|
132
|
+
"""Set link number and generate ACK response.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
request: The system telegram request.
|
|
136
|
+
new_link_number: The new link number to set.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
The ACK response telegram string, or None if request is invalid.
|
|
140
|
+
"""
|
|
141
|
+
if (
|
|
142
|
+
request.system_function == SystemFunction.WRITE_CONFIG
|
|
143
|
+
and request.datapoint_type == DataPointType.LINK_NUMBER
|
|
144
|
+
):
|
|
145
|
+
# Update internal link number
|
|
146
|
+
self.link_number = new_link_number
|
|
147
|
+
|
|
148
|
+
# Generate ACK response
|
|
149
|
+
data_part = f"R{self.serial_number}F18D"
|
|
150
|
+
telegram = self._build_response_telegram(data_part)
|
|
151
|
+
|
|
152
|
+
self.logger.info(f"{self.device_type} link number set to {new_link_number}")
|
|
153
|
+
return telegram
|
|
154
|
+
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
def _get_msactiontable_serializer(self) -> Optional[Any]:
|
|
158
|
+
"""Get the MsActionTable serializer for this device.
|
|
159
|
+
|
|
160
|
+
Subclasses should override this to return their specific serializer.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
The serializer instance, or None if not supported.
|
|
164
|
+
"""
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
def _get_msactiontable(self) -> Optional[Any]:
|
|
168
|
+
"""Get the MsActionTable for this device.
|
|
169
|
+
|
|
170
|
+
Subclasses should override this to return their msactiontable instance.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
The msactiontable instance, or None if not supported.
|
|
174
|
+
"""
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
def _handle_download_msactiontable_request(
|
|
178
|
+
self, request: SystemTelegram
|
|
179
|
+
) -> Optional[str]:
|
|
180
|
+
"""Handle F13D - DOWNLOAD_MSACTIONTABLE request.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
request: The system telegram request to process.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
ACK telegram if request is valid, NAK otherwise.
|
|
187
|
+
"""
|
|
188
|
+
serializer = self._get_msactiontable_serializer()
|
|
189
|
+
msactiontable = self._get_msactiontable()
|
|
190
|
+
|
|
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
|
|
207
|
+
|
|
208
|
+
def _handle_download_msactiontable_ack_request(
|
|
209
|
+
self, _request: SystemTelegram
|
|
210
|
+
) -> Optional[str]:
|
|
211
|
+
"""Handle MsActionTable download ACK protocol.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
_request: The system telegram request (unused, kept for signature consistency).
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Data telegram, EOF telegram, or NAK if state is invalid.
|
|
218
|
+
"""
|
|
219
|
+
if self.msactiontable_download_state == "data_sent":
|
|
220
|
+
# Send EOF
|
|
221
|
+
eof_telegram = self._build_response_telegram(f"R{self.serial_number}F16D")
|
|
222
|
+
self.msactiontable_download_state = None
|
|
223
|
+
return eof_telegram
|
|
224
|
+
|
|
225
|
+
return self._build_response_telegram(f"R{self.serial_number}F19D") # NAK
|
|
226
|
+
|
|
227
|
+
def process_system_telegram(self, request: SystemTelegram) -> Optional[str]:
|
|
228
|
+
"""Template method for processing system telegrams.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
request: The system telegram request to process.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
The response telegram string, or None if request cannot be handled.
|
|
235
|
+
"""
|
|
236
|
+
# Check if request is for this device
|
|
237
|
+
if not self._check_request_for_device(request):
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
# Handle different system functions
|
|
241
|
+
if request.system_function == SystemFunction.DISCOVERY:
|
|
242
|
+
return self.generate_discover_response()
|
|
243
|
+
|
|
244
|
+
elif request.system_function == SystemFunction.READ_DATAPOINT:
|
|
245
|
+
return self._handle_return_data_request(request)
|
|
246
|
+
|
|
247
|
+
elif request.system_function == SystemFunction.WRITE_CONFIG:
|
|
248
|
+
return self._handle_write_config_request(request)
|
|
249
|
+
|
|
250
|
+
elif request.system_function == SystemFunction.ACTION:
|
|
251
|
+
return self._handle_action_request(request)
|
|
252
|
+
|
|
253
|
+
elif request.system_function == SystemFunction.DOWNLOAD_MSACTIONTABLE:
|
|
254
|
+
return self._handle_download_msactiontable_request(request)
|
|
255
|
+
|
|
256
|
+
elif (
|
|
257
|
+
request.system_function == SystemFunction.ACK
|
|
258
|
+
and self.msactiontable_download_state
|
|
259
|
+
):
|
|
260
|
+
return self._handle_download_msactiontable_ack_request(request)
|
|
261
|
+
|
|
262
|
+
self.logger.warning(f"Unhandled {self.device_type} request: {request}")
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
def _handle_return_data_request(self, request: SystemTelegram) -> Optional[str]:
|
|
266
|
+
"""Handle RETURN_DATA requests - can be overridden by subclasses.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
request: The system telegram request.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
The response telegram string, or None if request cannot be handled.
|
|
273
|
+
"""
|
|
274
|
+
self.logger.debug(
|
|
275
|
+
f"_handle_return_data_request {self.device_type} request: {request}"
|
|
276
|
+
)
|
|
277
|
+
module_specific = self._handle_device_specific_data_request(request)
|
|
278
|
+
if module_specific:
|
|
279
|
+
return module_specific
|
|
280
|
+
|
|
281
|
+
if request.datapoint_type:
|
|
282
|
+
return self.generate_datapoint_type_response(request.datapoint_type)
|
|
283
|
+
|
|
284
|
+
# Allow device-specific handlers
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
def _handle_device_specific_data_request(
|
|
288
|
+
self, request: SystemTelegram
|
|
289
|
+
) -> Optional[str]:
|
|
290
|
+
"""Override in subclasses for device-specific data requests.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
request: The system telegram request.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
The response telegram string, or None if request cannot be handled.
|
|
297
|
+
"""
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
def _handle_write_config_request(self, request: SystemTelegram) -> Optional[str]:
|
|
301
|
+
"""Handle WRITE_CONFIG requests.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
request: The system telegram request.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
The response telegram string, or None if request cannot be handled.
|
|
308
|
+
"""
|
|
309
|
+
if request.datapoint_type == DataPointType.LINK_NUMBER:
|
|
310
|
+
return self.set_link_number(request, 1) # Default implementation
|
|
311
|
+
|
|
312
|
+
return self._handle_device_specific_config_request()
|
|
313
|
+
|
|
314
|
+
def _handle_action_request(self, request: SystemTelegram) -> Optional[str]:
|
|
315
|
+
"""Handle ACTION requests.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
request: The system telegram request.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
The response telegram string, or None if request cannot be handled.
|
|
322
|
+
"""
|
|
323
|
+
return self._handle_device_specific_action_request(request)
|
|
324
|
+
|
|
325
|
+
def _handle_device_specific_action_request(
|
|
326
|
+
self, request: SystemTelegram
|
|
327
|
+
) -> Optional[str]:
|
|
328
|
+
"""Override in subclasses for device-specific data requests.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
request: The system telegram request.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
The response telegram string, or None if request cannot be handled.
|
|
335
|
+
"""
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
@staticmethod
|
|
339
|
+
def _handle_device_specific_config_request() -> Optional[str]:
|
|
340
|
+
"""Override in subclasses for device-specific config requests.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
The response telegram string, or None if request cannot be handled.
|
|
344
|
+
"""
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
def add_telegram_buffer(self, telegram: str) -> None:
|
|
348
|
+
"""Add telegram to the buffer.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
telegram: The telegram string to add to the buffer.
|
|
352
|
+
"""
|
|
353
|
+
self.logger.debug(f"Add telegram to the buffer: {telegram}")
|
|
354
|
+
with self.telegram_buffer_lock:
|
|
355
|
+
self.telegram_buffer.append(telegram)
|
|
356
|
+
|
|
357
|
+
def collect_telegram_buffer(self) -> list[str]:
|
|
358
|
+
"""Collecting telegrams from the buffer.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
List of telegram strings from the buffer. The buffer is cleared after collection.
|
|
362
|
+
"""
|
|
363
|
+
with self.telegram_buffer_lock:
|
|
364
|
+
result = self.telegram_buffer.copy()
|
|
365
|
+
self.telegram_buffer.clear()
|
|
366
|
+
return result
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""CP20 Server Service for device emulation.
|
|
2
|
+
|
|
3
|
+
This service provides CP20-specific device emulation functionality,
|
|
4
|
+
including response generation and device configuration handling.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
|
|
9
|
+
from xp.models import ModuleTypeCode
|
|
10
|
+
from xp.models.telegram.system_telegram import SystemTelegram
|
|
11
|
+
from xp.services.actiontable.msactiontable_serializer import MsActionTableSerializer
|
|
12
|
+
from xp.services.server.base_server_service import BaseServerService
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CP20ServerError(Exception):
|
|
16
|
+
"""Raised when CP20 server operations fail."""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CP20ServerService(BaseServerService):
|
|
22
|
+
"""
|
|
23
|
+
CP20 device emulation service.
|
|
24
|
+
|
|
25
|
+
Generates CP20-specific responses, handles CP20 device configuration,
|
|
26
|
+
and implements CP20 telegram format.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
serial_number: str,
|
|
32
|
+
_variant: str = "",
|
|
33
|
+
_msactiontable_serializer: Optional[MsActionTableSerializer] = None,
|
|
34
|
+
):
|
|
35
|
+
"""Initialize CP20 server service.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
serial_number: The device serial number.
|
|
39
|
+
_variant: Reserved parameter for consistency (unused).
|
|
40
|
+
_msactiontable_serializer: Generic MsActionTable serializer (unused).
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(serial_number)
|
|
43
|
+
self.device_type = "CP20"
|
|
44
|
+
self.module_type_code = ModuleTypeCode.CP20 # CP20 module type from registry
|
|
45
|
+
self.firmware_version = "CP20_V0.01.05"
|
|
46
|
+
|
|
47
|
+
def _handle_device_specific_data_request(
|
|
48
|
+
self, request: SystemTelegram
|
|
49
|
+
) -> Optional[str]:
|
|
50
|
+
"""Handle CP20-specific data requests."""
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def get_device_info(self) -> Dict:
|
|
54
|
+
"""Get CP20 device information.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Dictionary containing device information.
|
|
58
|
+
"""
|
|
59
|
+
return {
|
|
60
|
+
"serial_number": self.serial_number,
|
|
61
|
+
"device_type": self.device_type,
|
|
62
|
+
"firmware_version": self.firmware_version,
|
|
63
|
+
"status": self.device_status,
|
|
64
|
+
"link_number": self.link_number,
|
|
65
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Device Service Factory for creating device instances.
|
|
2
|
+
|
|
3
|
+
This module provides a factory for creating device service instances
|
|
4
|
+
with proper dependency injection of serializers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from xp.services.actiontable.msactiontable_serializer import MsActionTableSerializer
|
|
8
|
+
from xp.services.actiontable.msactiontable_xp20_serializer import (
|
|
9
|
+
Xp20MsActionTableSerializer,
|
|
10
|
+
)
|
|
11
|
+
from xp.services.actiontable.msactiontable_xp24_serializer import (
|
|
12
|
+
Xp24MsActionTableSerializer,
|
|
13
|
+
)
|
|
14
|
+
from xp.services.actiontable.msactiontable_xp33_serializer import (
|
|
15
|
+
Xp33MsActionTableSerializer,
|
|
16
|
+
)
|
|
17
|
+
from xp.services.server.base_server_service import BaseServerService
|
|
18
|
+
from xp.services.server.cp20_server_service import CP20ServerService
|
|
19
|
+
from xp.services.server.xp20_server_service import XP20ServerService
|
|
20
|
+
from xp.services.server.xp24_server_service import XP24ServerService
|
|
21
|
+
from xp.services.server.xp33_server_service import XP33ServerService
|
|
22
|
+
from xp.services.server.xp130_server_service import XP130ServerService
|
|
23
|
+
from xp.services.server.xp230_server_service import XP230ServerService
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DeviceServiceFactory:
|
|
27
|
+
"""Factory for creating device service instances.
|
|
28
|
+
|
|
29
|
+
Encapsulates device creation logic and handles serializer injection
|
|
30
|
+
for different device types.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
xp20ms_serializer: Xp20MsActionTableSerializer,
|
|
36
|
+
xp24ms_serializer: Xp24MsActionTableSerializer,
|
|
37
|
+
xp33ms_serializer: Xp33MsActionTableSerializer,
|
|
38
|
+
ms_serializer: MsActionTableSerializer,
|
|
39
|
+
):
|
|
40
|
+
"""Initialize device service factory.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
xp20ms_serializer: XP20 MsActionTable serializer (injected via DI).
|
|
44
|
+
xp24ms_serializer: XP24 MsActionTable serializer (injected via DI).
|
|
45
|
+
xp33ms_serializer: XP33 MsActionTable serializer (injected via DI).
|
|
46
|
+
ms_serializer: Generic MsActionTable serializer (injected via DI).
|
|
47
|
+
"""
|
|
48
|
+
self.xp20ms_serializer = xp20ms_serializer
|
|
49
|
+
self.xp24ms_serializer = xp24ms_serializer
|
|
50
|
+
self.xp33ms_serializer = xp33ms_serializer
|
|
51
|
+
self.ms_serializer = ms_serializer
|
|
52
|
+
|
|
53
|
+
def create_device(self, module_type: str, serial_number: str) -> BaseServerService:
|
|
54
|
+
"""Create device instance for given module type.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
module_type: Module type code (e.g., "XP20", "XP33LR").
|
|
58
|
+
serial_number: Device serial number.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Device service instance configured with appropriate serializer.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ValueError: If module_type is unknown or unsupported.
|
|
65
|
+
"""
|
|
66
|
+
# Map module types to their constructors and parameters
|
|
67
|
+
if module_type == "CP20":
|
|
68
|
+
return CP20ServerService(serial_number, "CP20", self.ms_serializer)
|
|
69
|
+
|
|
70
|
+
elif module_type == "XP24":
|
|
71
|
+
return XP24ServerService(serial_number, "XP24", self.xp24ms_serializer)
|
|
72
|
+
|
|
73
|
+
elif module_type == "XP33":
|
|
74
|
+
return XP33ServerService(serial_number, "XP33", self.xp33ms_serializer)
|
|
75
|
+
|
|
76
|
+
elif module_type == "XP33LR":
|
|
77
|
+
return XP33ServerService(serial_number, "XP33LR", self.xp33ms_serializer)
|
|
78
|
+
|
|
79
|
+
elif module_type == "XP33LED":
|
|
80
|
+
return XP33ServerService(serial_number, "XP33LED", self.xp33ms_serializer)
|
|
81
|
+
|
|
82
|
+
elif module_type == "XP20":
|
|
83
|
+
return XP20ServerService(serial_number, "XP20", self.xp20ms_serializer)
|
|
84
|
+
|
|
85
|
+
elif module_type == "XP130":
|
|
86
|
+
return XP130ServerService(serial_number, "XP130", self.ms_serializer)
|
|
87
|
+
|
|
88
|
+
elif module_type == "XP230":
|
|
89
|
+
return XP230ServerService(serial_number, "XP230", self.ms_serializer)
|
|
90
|
+
|
|
91
|
+
else:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
f"Unknown device type '{module_type}' for serial {serial_number}"
|
|
94
|
+
)
|