conson-xp 1.1.0__py3-none-any.whl → 1.2.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.1.0.dist-info → conson_xp-1.2.0.dist-info}/METADATA +1 -1
- conson_xp-1.2.0.dist-info/RECORD +181 -0
- xp/__init__.py +4 -3
- xp/api/main.py +18 -3
- xp/api/models/api.py +13 -2
- xp/api/models/discover.py +12 -2
- xp/api/routers/conbus_blink.py +18 -6
- xp/api/routers/conbus_custom.py +11 -3
- xp/api/routers/conbus_datapoint.py +10 -3
- xp/api/routers/conbus_output.py +29 -9
- xp/api/routers/errors.py +6 -5
- xp/cli/__init__.py +1 -1
- xp/cli/commands/__init__.py +1 -0
- xp/cli/commands/api.py +1 -5
- xp/cli/commands/api_start_commands.py +14 -8
- xp/cli/commands/conbus/conbus.py +9 -37
- xp/cli/commands/conbus/conbus_actiontable_commands.py +21 -1
- xp/cli/commands/conbus/conbus_autoreport_commands.py +21 -11
- xp/cli/commands/conbus/conbus_blink_commands.py +53 -21
- xp/cli/commands/conbus/conbus_config_commands.py +7 -4
- xp/cli/commands/conbus/conbus_custom_commands.py +13 -4
- xp/cli/commands/conbus/conbus_datapoint_commands.py +28 -8
- xp/cli/commands/conbus/conbus_discover_commands.py +15 -4
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +50 -11
- xp/cli/commands/conbus/conbus_linknumber_commands.py +21 -11
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +25 -1
- xp/cli/commands/conbus/conbus_output_commands.py +46 -12
- xp/cli/commands/conbus/conbus_raw_commands.py +17 -6
- xp/cli/commands/conbus/conbus_receive_commands.py +15 -7
- xp/cli/commands/conbus/conbus_scan_commands.py +17 -4
- xp/cli/commands/file_commands.py +26 -15
- xp/cli/commands/homekit/homekit.py +14 -8
- xp/cli/commands/homekit/homekit_start_commands.py +5 -5
- xp/cli/commands/module_commands.py +26 -19
- xp/cli/commands/reverse_proxy_commands.py +24 -18
- xp/cli/commands/server/server_commands.py +18 -18
- xp/cli/commands/telegram/telegram.py +4 -12
- xp/cli/commands/telegram/telegram_blink_commands.py +10 -8
- xp/cli/commands/telegram/telegram_checksum_commands.py +19 -8
- xp/cli/commands/telegram/telegram_discover_commands.py +2 -4
- xp/cli/commands/telegram/telegram_linknumber_commands.py +11 -8
- xp/cli/commands/telegram/telegram_parse_commands.py +10 -9
- xp/cli/commands/telegram/telegram_version_commands.py +8 -4
- xp/cli/main.py +5 -1
- xp/cli/utils/click_tree.py +23 -3
- xp/cli/utils/datapoint_type_choice.py +20 -0
- xp/cli/utils/decorators.py +165 -14
- xp/cli/utils/error_handlers.py +49 -18
- xp/cli/utils/formatters.py +95 -10
- xp/cli/utils/serial_number_type.py +18 -0
- xp/cli/utils/system_function_choice.py +20 -0
- xp/cli/utils/xp_module_type.py +20 -0
- xp/connection/__init__.py +1 -1
- xp/connection/exceptions.py +5 -5
- xp/models/__init__.py +1 -1
- xp/models/actiontable/__init__.py +1 -0
- xp/models/actiontable/actiontable.py +17 -1
- xp/models/actiontable/msactiontable_xp20.py +10 -0
- xp/models/actiontable/msactiontable_xp24.py +20 -3
- xp/models/actiontable/msactiontable_xp33.py +27 -4
- xp/models/conbus/__init__.py +1 -0
- xp/models/conbus/conbus.py +34 -4
- xp/models/conbus/conbus_autoreport.py +20 -2
- xp/models/conbus/conbus_blink.py +22 -2
- xp/models/conbus/conbus_client_config.py +22 -1
- xp/models/conbus/conbus_connection_status.py +16 -2
- xp/models/conbus/conbus_custom.py +21 -2
- xp/models/conbus/conbus_datapoint.py +22 -2
- xp/models/conbus/conbus_discover.py +18 -2
- xp/models/conbus/conbus_lightlevel.py +20 -2
- xp/models/conbus/conbus_linknumber.py +20 -2
- xp/models/conbus/conbus_output.py +22 -2
- xp/models/conbus/conbus_raw.py +17 -2
- xp/models/conbus/conbus_receive.py +16 -2
- xp/models/homekit/__init__.py +1 -0
- xp/models/homekit/homekit_accessory.py +15 -1
- xp/models/homekit/homekit_config.py +52 -0
- xp/models/homekit/homekit_conson_config.py +32 -0
- xp/models/log_entry.py +49 -9
- xp/models/protocol/__init__.py +1 -0
- xp/models/protocol/conbus_protocol.py +130 -21
- xp/models/telegram/__init__.py +1 -0
- xp/models/telegram/action_type.py +16 -2
- xp/models/telegram/datapoint_type.py +36 -2
- xp/models/telegram/event_telegram.py +46 -10
- xp/models/telegram/event_type.py +8 -1
- xp/models/telegram/input_action_type.py +34 -2
- xp/models/telegram/input_type.py +9 -1
- xp/models/telegram/module_type.py +69 -19
- xp/models/telegram/module_type_code.py +43 -1
- xp/models/telegram/output_telegram.py +30 -6
- xp/models/telegram/reply_telegram.py +56 -11
- xp/models/telegram/system_function.py +35 -3
- xp/models/telegram/system_telegram.py +18 -4
- xp/models/telegram/telegram.py +12 -3
- xp/models/telegram/telegram_type.py +8 -1
- xp/models/telegram/timeparam_type.py +27 -0
- xp/models/write_config_type.py +17 -2
- xp/services/__init__.py +1 -1
- xp/services/conbus/__init__.py +1 -0
- xp/services/conbus/actiontable/__init__.py +1 -0
- xp/services/conbus/actiontable/actiontable_service.py +33 -2
- xp/services/conbus/actiontable/msactiontable_service.py +40 -3
- xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +36 -4
- xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +45 -5
- xp/services/conbus/conbus_autoreport_get_service.py +17 -8
- xp/services/conbus/conbus_autoreport_set_service.py +29 -16
- xp/services/conbus/conbus_blink_all_service.py +40 -21
- xp/services/conbus/conbus_blink_service.py +37 -13
- xp/services/conbus/conbus_custom_service.py +29 -13
- xp/services/conbus/conbus_datapoint_queryall_service.py +40 -16
- xp/services/conbus/conbus_datapoint_service.py +33 -12
- xp/services/conbus/conbus_discover_service.py +43 -7
- xp/services/conbus/conbus_lightlevel_get_service.py +22 -14
- xp/services/conbus/conbus_lightlevel_set_service.py +40 -20
- xp/services/conbus/conbus_linknumber_get_service.py +18 -10
- xp/services/conbus/conbus_linknumber_set_service.py +34 -8
- xp/services/conbus/conbus_output_service.py +33 -13
- xp/services/conbus/conbus_raw_service.py +36 -16
- xp/services/conbus/conbus_receive_service.py +38 -6
- xp/services/conbus/conbus_scan_service.py +44 -18
- xp/services/homekit/__init__.py +1 -0
- xp/services/homekit/homekit_cache_service.py +31 -6
- xp/services/homekit/homekit_conbus_service.py +33 -2
- xp/services/homekit/homekit_config_validator.py +97 -15
- xp/services/homekit/homekit_conson_validator.py +51 -7
- xp/services/homekit/homekit_dimminglight.py +47 -1
- xp/services/homekit/homekit_dimminglight_service.py +35 -1
- xp/services/homekit/homekit_hap_service.py +71 -18
- xp/services/homekit/homekit_lightbulb.py +35 -1
- xp/services/homekit/homekit_lightbulb_service.py +30 -2
- xp/services/homekit/homekit_module_service.py +23 -1
- xp/services/homekit/homekit_outlet.py +47 -1
- xp/services/homekit/homekit_outlet_service.py +44 -2
- xp/services/homekit/homekit_service.py +113 -19
- xp/services/log_file_service.py +37 -41
- xp/services/module_type_service.py +26 -5
- xp/services/protocol/__init__.py +1 -1
- xp/services/protocol/conbus_protocol.py +110 -16
- xp/services/protocol/protocol_factory.py +40 -0
- xp/services/protocol/telegram_protocol.py +38 -7
- xp/services/reverse_proxy_service.py +79 -14
- xp/services/server/__init__.py +1 -0
- xp/services/server/base_server_service.py +102 -14
- xp/services/server/cp20_server_service.py +12 -4
- xp/services/server/server_service.py +26 -11
- xp/services/server/xp130_server_service.py +11 -3
- xp/services/server/xp20_server_service.py +11 -3
- xp/services/server/xp230_server_service.py +11 -3
- xp/services/server/xp24_server_service.py +33 -6
- xp/services/server/xp33_server_service.py +41 -8
- xp/services/telegram/__init__.py +1 -0
- xp/services/telegram/telegram_blink_service.py +19 -31
- xp/services/telegram/telegram_checksum_service.py +10 -10
- xp/services/telegram/telegram_discover_service.py +58 -29
- xp/services/telegram/telegram_link_number_service.py +27 -40
- xp/services/telegram/telegram_output_service.py +46 -49
- xp/services/telegram/telegram_service.py +41 -41
- xp/services/telegram/telegram_version_service.py +4 -2
- xp/utils/__init__.py +1 -1
- xp/utils/dependencies.py +0 -1
- xp/utils/serialization.py +6 -0
- xp/utils/time_utils.py +6 -11
- conson_xp-1.1.0.dist-info/RECORD +0 -181
- {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -23,7 +23,11 @@ class BaseServerService(ABC):
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
def __init__(self, serial_number: str):
|
|
26
|
-
"""Initialize base server service
|
|
26
|
+
"""Initialize base server service.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
serial_number: The device serial number.
|
|
30
|
+
"""
|
|
27
31
|
self.serial_number = serial_number
|
|
28
32
|
self.logger = logging.getLogger(__name__)
|
|
29
33
|
|
|
@@ -40,7 +44,14 @@ class BaseServerService(ABC):
|
|
|
40
44
|
def generate_datapoint_type_response(
|
|
41
45
|
self, datapoint_type: DataPointType
|
|
42
46
|
) -> Optional[str]:
|
|
43
|
-
"""Generate datapoint_type response telegram
|
|
47
|
+
"""Generate datapoint_type response telegram.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
datapoint_type: The type of datapoint to query.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
The response telegram string, or None if generation fails.
|
|
54
|
+
"""
|
|
44
55
|
datapoint_values = {
|
|
45
56
|
DataPointType.TEMPERATURE: self.temperature,
|
|
46
57
|
DataPointType.MODULE_TYPE_CODE: f"{self.module_type_code:02X}",
|
|
@@ -62,23 +73,46 @@ class BaseServerService(ABC):
|
|
|
62
73
|
return telegram
|
|
63
74
|
|
|
64
75
|
def _check_request_for_device(self, request: SystemTelegram) -> bool:
|
|
65
|
-
"""Check if request is for this device (including broadcast)
|
|
76
|
+
"""Check if request is for this device (including broadcast).
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
request: The system telegram request to check.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
True if request is for this device or broadcast, False otherwise.
|
|
83
|
+
"""
|
|
66
84
|
return request.serial_number in (self.serial_number, "0000000000")
|
|
67
85
|
|
|
68
86
|
@staticmethod
|
|
69
87
|
def _build_response_telegram(data_part: str) -> str:
|
|
70
|
-
"""Build a complete response telegram with checksum
|
|
88
|
+
"""Build a complete response telegram with checksum.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
data_part: The data part of the telegram without checksum.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
The complete telegram with checksum enclosed in angle brackets.
|
|
95
|
+
"""
|
|
71
96
|
checksum = calculate_checksum(data_part)
|
|
72
97
|
return f"<{data_part}{checksum}>"
|
|
73
98
|
|
|
74
99
|
def _log_response(self, response_type: str, telegram: str) -> None:
|
|
75
|
-
"""Log response generation
|
|
100
|
+
"""Log response generation.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
response_type: The type of response being generated.
|
|
104
|
+
telegram: The telegram string being logged.
|
|
105
|
+
"""
|
|
76
106
|
self.logger.debug(
|
|
77
107
|
f"Generated {self.device_type} {response_type} response: {telegram}"
|
|
78
108
|
)
|
|
79
109
|
|
|
80
110
|
def generate_discover_response(self) -> str:
|
|
81
|
-
"""Generate discover response telegram
|
|
111
|
+
"""Generate discover response telegram.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
The discover response telegram string.
|
|
115
|
+
"""
|
|
82
116
|
data_part = f"R{self.serial_number}F01D"
|
|
83
117
|
telegram = self._build_response_telegram(data_part)
|
|
84
118
|
self._log_response("discover", telegram)
|
|
@@ -87,7 +121,15 @@ class BaseServerService(ABC):
|
|
|
87
121
|
def set_link_number(
|
|
88
122
|
self, request: SystemTelegram, new_link_number: int
|
|
89
123
|
) -> Optional[str]:
|
|
90
|
-
"""Set link number and generate ACK response
|
|
124
|
+
"""Set link number and generate ACK response.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
request: The system telegram request.
|
|
128
|
+
new_link_number: The new link number to set.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
The ACK response telegram string, or None if request is invalid.
|
|
132
|
+
"""
|
|
91
133
|
if (
|
|
92
134
|
request.system_function == SystemFunction.WRITE_CONFIG
|
|
93
135
|
and request.datapoint_type == DataPointType.LINK_NUMBER
|
|
@@ -105,7 +147,14 @@ class BaseServerService(ABC):
|
|
|
105
147
|
return None
|
|
106
148
|
|
|
107
149
|
def process_system_telegram(self, request: SystemTelegram) -> Optional[str]:
|
|
108
|
-
"""Template method for processing system telegrams
|
|
150
|
+
"""Template method for processing system telegrams.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
request: The system telegram request to process.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
The response telegram string, or None if request cannot be handled.
|
|
157
|
+
"""
|
|
109
158
|
# Check if request is for this device
|
|
110
159
|
if not self._check_request_for_device(request):
|
|
111
160
|
return None
|
|
@@ -127,7 +176,14 @@ class BaseServerService(ABC):
|
|
|
127
176
|
return None
|
|
128
177
|
|
|
129
178
|
def _handle_return_data_request(self, request: SystemTelegram) -> Optional[str]:
|
|
130
|
-
"""Handle RETURN_DATA requests - can be overridden by subclasses
|
|
179
|
+
"""Handle RETURN_DATA requests - can be overridden by subclasses.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
request: The system telegram request.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
The response telegram string, or None if request cannot be handled.
|
|
186
|
+
"""
|
|
131
187
|
self.logger.debug(
|
|
132
188
|
f"_handle_return_data_request {self.device_type} request: {request}"
|
|
133
189
|
)
|
|
@@ -140,27 +196,59 @@ class BaseServerService(ABC):
|
|
|
140
196
|
def _handle_device_specific_data_request(
|
|
141
197
|
self, request: SystemTelegram
|
|
142
198
|
) -> Optional[str]:
|
|
143
|
-
"""Override in subclasses for device-specific data requests
|
|
199
|
+
"""Override in subclasses for device-specific data requests.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
request: The system telegram request.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
The response telegram string, or None if request cannot be handled.
|
|
206
|
+
"""
|
|
144
207
|
return None
|
|
145
208
|
|
|
146
209
|
def _handle_write_config_request(self, request: SystemTelegram) -> Optional[str]:
|
|
147
|
-
"""Handle WRITE_CONFIG requests
|
|
210
|
+
"""Handle WRITE_CONFIG requests.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
request: The system telegram request.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
The response telegram string, or None if request cannot be handled.
|
|
217
|
+
"""
|
|
148
218
|
if request.datapoint_type == DataPointType.LINK_NUMBER:
|
|
149
219
|
return self.set_link_number(request, 1) # Default implementation
|
|
150
220
|
|
|
151
221
|
return self._handle_device_specific_config_request()
|
|
152
222
|
|
|
153
223
|
def _handle_action_request(self, request: SystemTelegram) -> Optional[str]:
|
|
154
|
-
"""Handle ACTION requests
|
|
224
|
+
"""Handle ACTION requests.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
request: The system telegram request.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
The response telegram string, or None if request cannot be handled.
|
|
231
|
+
"""
|
|
155
232
|
return self._handle_device_specific_action_request(request)
|
|
156
233
|
|
|
157
234
|
def _handle_device_specific_action_request(
|
|
158
235
|
self, request: SystemTelegram
|
|
159
236
|
) -> Optional[str]:
|
|
160
|
-
"""Override in subclasses for device-specific data requests
|
|
237
|
+
"""Override in subclasses for device-specific data requests.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
request: The system telegram request.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
The response telegram string, or None if request cannot be handled.
|
|
244
|
+
"""
|
|
161
245
|
return None
|
|
162
246
|
|
|
163
247
|
@staticmethod
|
|
164
248
|
def _handle_device_specific_config_request() -> Optional[str]:
|
|
165
|
-
"""Override in subclasses for device-specific config requests
|
|
249
|
+
"""Override in subclasses for device-specific config requests.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
The response telegram string, or None if request cannot be handled.
|
|
253
|
+
"""
|
|
166
254
|
return None
|
|
@@ -11,7 +11,7 @@ from xp.services.server.base_server_service import BaseServerService
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class CP20ServerError(Exception):
|
|
14
|
-
"""Raised when CP20 server operations fail"""
|
|
14
|
+
"""Raised when CP20 server operations fail."""
|
|
15
15
|
|
|
16
16
|
pass
|
|
17
17
|
|
|
@@ -25,7 +25,11 @@ class CP20ServerService(BaseServerService):
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
def __init__(self, serial_number: str):
|
|
28
|
-
"""Initialize CP20 server service
|
|
28
|
+
"""Initialize CP20 server service.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
serial_number: The device serial number.
|
|
32
|
+
"""
|
|
29
33
|
super().__init__(serial_number)
|
|
30
34
|
self.device_type = "CP20"
|
|
31
35
|
self.module_type_code = 2 # CP20 module type from registry
|
|
@@ -34,11 +38,15 @@ class CP20ServerService(BaseServerService):
|
|
|
34
38
|
def _handle_device_specific_data_request(
|
|
35
39
|
self, request: SystemTelegram
|
|
36
40
|
) -> Optional[str]:
|
|
37
|
-
"""Handle CP20-specific data requests"""
|
|
41
|
+
"""Handle CP20-specific data requests."""
|
|
38
42
|
return None
|
|
39
43
|
|
|
40
44
|
def get_device_info(self) -> Dict:
|
|
41
|
-
"""Get CP20 device information
|
|
45
|
+
"""Get CP20 device information.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dictionary containing device information.
|
|
49
|
+
"""
|
|
42
50
|
return {
|
|
43
51
|
"serial_number": self.serial_number,
|
|
44
52
|
"device_type": self.device_type,
|
|
@@ -26,7 +26,7 @@ from xp.services.telegram.telegram_service import TelegramService
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class ServerError(Exception):
|
|
29
|
-
"""Raised when Conbus server operations fail"""
|
|
29
|
+
"""Raised when Conbus server operations fail."""
|
|
30
30
|
|
|
31
31
|
pass
|
|
32
32
|
|
|
@@ -46,7 +46,14 @@ class ServerService:
|
|
|
46
46
|
config_path: str = "server.yml",
|
|
47
47
|
port: int = 10001,
|
|
48
48
|
):
|
|
49
|
-
"""Initialize the Conbus server service
|
|
49
|
+
"""Initialize the Conbus server service.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
telegram_service: Service for parsing system telegrams.
|
|
53
|
+
discover_service: Service for handling discover requests.
|
|
54
|
+
config_path: Path to the server configuration file.
|
|
55
|
+
port: TCP port to listen on.
|
|
56
|
+
"""
|
|
50
57
|
self.telegram_service = telegram_service
|
|
51
58
|
self.discover_service = discover_service
|
|
52
59
|
self.config_path = config_path
|
|
@@ -71,7 +78,7 @@ class ServerService:
|
|
|
71
78
|
self._load_device_config()
|
|
72
79
|
|
|
73
80
|
def _load_device_config(self) -> None:
|
|
74
|
-
"""Load device configurations from server.yml"""
|
|
81
|
+
"""Load device configurations from server.yml."""
|
|
75
82
|
try:
|
|
76
83
|
if Path(self.config_path).exists():
|
|
77
84
|
config = ConsonModuleListConfig.from_yaml(self.config_path)
|
|
@@ -90,7 +97,7 @@ class ServerService:
|
|
|
90
97
|
self.device_services = {}
|
|
91
98
|
|
|
92
99
|
def _create_device_services(self) -> None:
|
|
93
|
-
"""Create device service instances based on device configuration"""
|
|
100
|
+
"""Create device service instances based on device configuration."""
|
|
94
101
|
self.device_services = {}
|
|
95
102
|
|
|
96
103
|
for module in self.devices:
|
|
@@ -143,7 +150,11 @@ class ServerService:
|
|
|
143
150
|
)
|
|
144
151
|
|
|
145
152
|
def start_server(self) -> None:
|
|
146
|
-
"""Start the TCP server on port 10001
|
|
153
|
+
"""Start the TCP server on port 10001.
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
ServerError: If server is already running or fails to start.
|
|
157
|
+
"""
|
|
147
158
|
if self.is_running:
|
|
148
159
|
raise ServerError("Server is already running")
|
|
149
160
|
|
|
@@ -170,7 +181,7 @@ class ServerService:
|
|
|
170
181
|
raise ServerError(f"Failed to start server: {e}")
|
|
171
182
|
|
|
172
183
|
def stop_server(self) -> None:
|
|
173
|
-
"""Stop the TCP server"""
|
|
184
|
+
"""Stop the TCP server."""
|
|
174
185
|
if not self.is_running:
|
|
175
186
|
return
|
|
176
187
|
|
|
@@ -184,7 +195,7 @@ class ServerService:
|
|
|
184
195
|
self.logger.error(f"Error closing server socket: {e}")
|
|
185
196
|
|
|
186
197
|
def _accept_connections(self) -> None:
|
|
187
|
-
"""Accept and handle client connections"""
|
|
198
|
+
"""Accept and handle client connections."""
|
|
188
199
|
while self.is_running:
|
|
189
200
|
try:
|
|
190
201
|
# Accept connection
|
|
@@ -208,7 +219,7 @@ class ServerService:
|
|
|
208
219
|
def _handle_client(
|
|
209
220
|
self, client_socket: socket.socket, client_address: tuple[str, int]
|
|
210
221
|
) -> None:
|
|
211
|
-
"""Handle individual client connection"""
|
|
222
|
+
"""Handle individual client connection."""
|
|
212
223
|
try:
|
|
213
224
|
# Set timeout for idle connections (30 seconds as per spec)
|
|
214
225
|
client_socket.settimeout(300.0)
|
|
@@ -242,7 +253,7 @@ class ServerService:
|
|
|
242
253
|
self.logger.error(f"Error closing client socket: {e}")
|
|
243
254
|
|
|
244
255
|
def _process_request(self, message: str) -> List[str]:
|
|
245
|
-
"""Process incoming request and generate responses"""
|
|
256
|
+
"""Process incoming request and generate responses."""
|
|
246
257
|
responses: list[str] = []
|
|
247
258
|
|
|
248
259
|
try:
|
|
@@ -290,7 +301,11 @@ class ServerService:
|
|
|
290
301
|
return responses
|
|
291
302
|
|
|
292
303
|
def get_server_status(self) -> dict:
|
|
293
|
-
"""Get current server status
|
|
304
|
+
"""Get current server status.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Dictionary containing server status information.
|
|
308
|
+
"""
|
|
294
309
|
return {
|
|
295
310
|
"running": self.is_running,
|
|
296
311
|
"port": self.port,
|
|
@@ -299,7 +314,7 @@ class ServerService:
|
|
|
299
314
|
}
|
|
300
315
|
|
|
301
316
|
def reload_config(self) -> None:
|
|
302
|
-
"""Reload device configuration from file"""
|
|
317
|
+
"""Reload device configuration from file."""
|
|
303
318
|
self._load_device_config()
|
|
304
319
|
self.logger.info(
|
|
305
320
|
f"Configuration reloaded: {len(self.devices)} devices, {len(self.device_services)} services"
|
|
@@ -11,7 +11,7 @@ from xp.services.server.base_server_service import BaseServerService
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class XP130ServerError(Exception):
|
|
14
|
-
"""Raised when XP130 server operations fail"""
|
|
14
|
+
"""Raised when XP130 server operations fail."""
|
|
15
15
|
|
|
16
16
|
pass
|
|
17
17
|
|
|
@@ -25,7 +25,11 @@ class XP130ServerService(BaseServerService):
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
def __init__(self, serial_number: str):
|
|
28
|
-
"""Initialize XP130 server service
|
|
28
|
+
"""Initialize XP130 server service.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
serial_number: The device serial number.
|
|
32
|
+
"""
|
|
29
33
|
super().__init__(serial_number)
|
|
30
34
|
self.device_type = "XP130"
|
|
31
35
|
self.module_type_code = 13 # XP130 module type from registry
|
|
@@ -37,7 +41,11 @@ class XP130ServerService(BaseServerService):
|
|
|
37
41
|
self.gateway = "192.168.1.1"
|
|
38
42
|
|
|
39
43
|
def get_device_info(self) -> Dict:
|
|
40
|
-
"""Get XP130 device information
|
|
44
|
+
"""Get XP130 device information.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Dictionary containing device information.
|
|
48
|
+
"""
|
|
41
49
|
return {
|
|
42
50
|
"serial_number": self.serial_number,
|
|
43
51
|
"device_type": self.device_type,
|
|
@@ -10,7 +10,7 @@ from xp.services.server.base_server_service import BaseServerService
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class XP20ServerError(Exception):
|
|
13
|
-
"""Raised when XP20 server operations fail"""
|
|
13
|
+
"""Raised when XP20 server operations fail."""
|
|
14
14
|
|
|
15
15
|
pass
|
|
16
16
|
|
|
@@ -24,14 +24,22 @@ class XP20ServerService(BaseServerService):
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
def __init__(self, serial_number: str):
|
|
27
|
-
"""Initialize XP20 server service
|
|
27
|
+
"""Initialize XP20 server service.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
serial_number: The device serial number.
|
|
31
|
+
"""
|
|
28
32
|
super().__init__(serial_number)
|
|
29
33
|
self.device_type = "XP20"
|
|
30
34
|
self.module_type_code = 33 # XP20 module type from registry
|
|
31
35
|
self.firmware_version = "XP20_V0.01.05"
|
|
32
36
|
|
|
33
37
|
def get_device_info(self) -> Dict:
|
|
34
|
-
"""Get XP20 device information
|
|
38
|
+
"""Get XP20 device information.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Dictionary containing device information.
|
|
42
|
+
"""
|
|
35
43
|
return {
|
|
36
44
|
"serial_number": self.serial_number,
|
|
37
45
|
"device_type": self.device_type,
|
|
@@ -10,7 +10,7 @@ from xp.services.server.base_server_service import BaseServerService
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class XP230ServerError(Exception):
|
|
13
|
-
"""Raised when XP230 server operations fail"""
|
|
13
|
+
"""Raised when XP230 server operations fail."""
|
|
14
14
|
|
|
15
15
|
pass
|
|
16
16
|
|
|
@@ -24,14 +24,22 @@ class XP230ServerService(BaseServerService):
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
def __init__(self, serial_number: str):
|
|
27
|
-
"""Initialize XP230 server service
|
|
27
|
+
"""Initialize XP230 server service.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
serial_number: The device serial number.
|
|
31
|
+
"""
|
|
28
32
|
super().__init__(serial_number)
|
|
29
33
|
self.device_type = "XP230"
|
|
30
34
|
self.module_type_code = 34 # XP230 module type from registry
|
|
31
35
|
self.firmware_version = "XP230_V1.00.04"
|
|
32
36
|
|
|
33
37
|
def get_device_info(self) -> Dict:
|
|
34
|
-
"""Get XP230 device information
|
|
38
|
+
"""Get XP230 device information.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Dictionary containing device information.
|
|
42
|
+
"""
|
|
35
43
|
return {
|
|
36
44
|
"serial_number": self.serial_number,
|
|
37
45
|
"device_type": self.device_type,
|
|
@@ -13,7 +13,7 @@ from xp.services.server.base_server_service import BaseServerService
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class XP24ServerError(Exception):
|
|
16
|
-
"""Raised when XP24 server operations fail"""
|
|
16
|
+
"""Raised when XP24 server operations fail."""
|
|
17
17
|
|
|
18
18
|
pass
|
|
19
19
|
|
|
@@ -27,7 +27,11 @@ class XP24ServerService(BaseServerService):
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
def __init__(self, serial_number: str):
|
|
30
|
-
"""Initialize XP24 server service
|
|
30
|
+
"""Initialize XP24 server service.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
serial_number: The device serial number.
|
|
34
|
+
"""
|
|
31
35
|
super().__init__(serial_number)
|
|
32
36
|
self.device_type = "XP24"
|
|
33
37
|
self.module_type_code = 7 # XP24 module type from registry
|
|
@@ -36,7 +40,7 @@ class XP24ServerService(BaseServerService):
|
|
|
36
40
|
def _handle_device_specific_data_request(
|
|
37
41
|
self, request: SystemTelegram
|
|
38
42
|
) -> Optional[str]:
|
|
39
|
-
"""Handle XP24-specific data requests"""
|
|
43
|
+
"""Handle XP24-specific data requests."""
|
|
40
44
|
if (
|
|
41
45
|
request.system_function != SystemFunction.READ_DATAPOINT
|
|
42
46
|
or not request.datapoint_type
|
|
@@ -49,7 +53,12 @@ class XP24ServerService(BaseServerService):
|
|
|
49
53
|
DataPointType.MODULE_STATE: "OFF",
|
|
50
54
|
DataPointType.MODULE_OPERATING_HOURS: "00:000[H],01:000[H],02:000[H],03:000[H]",
|
|
51
55
|
}
|
|
52
|
-
data_part =
|
|
56
|
+
data_part = (
|
|
57
|
+
f"R{self.serial_number}"
|
|
58
|
+
f"F02{datapoint_type.value}"
|
|
59
|
+
f"{self.module_type_code}"
|
|
60
|
+
f"{datapoint_values.get(datapoint_type)}"
|
|
61
|
+
)
|
|
53
62
|
telegram = self._build_response_telegram(data_part)
|
|
54
63
|
|
|
55
64
|
self.logger.debug(
|
|
@@ -60,14 +69,25 @@ class XP24ServerService(BaseServerService):
|
|
|
60
69
|
def _handle_device_specific_action_request(
|
|
61
70
|
self, request: SystemTelegram
|
|
62
71
|
) -> Optional[str]:
|
|
72
|
+
"""Handle XP24-specific action requests.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
request: The system telegram request.
|
|
63
76
|
|
|
77
|
+
Returns:
|
|
78
|
+
The response telegram string, or None if request cannot be handled.
|
|
79
|
+
"""
|
|
64
80
|
if request.system_function != SystemFunction.ACTION:
|
|
65
81
|
return None
|
|
66
82
|
|
|
67
83
|
return self.generate_action_response(request)
|
|
68
84
|
|
|
69
85
|
def get_device_info(self) -> Dict:
|
|
70
|
-
"""Get XP24 device information
|
|
86
|
+
"""Get XP24 device information.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Dictionary containing device information.
|
|
90
|
+
"""
|
|
71
91
|
return {
|
|
72
92
|
"serial_number": self.serial_number,
|
|
73
93
|
"device_type": self.device_type,
|
|
@@ -77,7 +97,14 @@ class XP24ServerService(BaseServerService):
|
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
def generate_action_response(self, request: SystemTelegram) -> Optional[str]:
|
|
80
|
-
"""Generate action response telegram (simulated)
|
|
100
|
+
"""Generate action response telegram (simulated).
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
request: The system telegram request.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The ACK or NAK response telegram string.
|
|
107
|
+
"""
|
|
81
108
|
response = "F19D" # NAK
|
|
82
109
|
if (
|
|
83
110
|
request.system_function == SystemFunction.ACTION
|
|
@@ -15,7 +15,7 @@ from xp.utils import calculate_checksum
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class XP33ServerError(Exception):
|
|
18
|
-
"""Raised when XP33 server operations fail"""
|
|
18
|
+
"""Raised when XP33 server operations fail."""
|
|
19
19
|
|
|
20
20
|
pass
|
|
21
21
|
|
|
@@ -29,7 +29,12 @@ class XP33ServerService(BaseServerService):
|
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
31
|
def __init__(self, serial_number: str, variant: str = "XP33LR"):
|
|
32
|
-
"""Initialize XP33 server service
|
|
32
|
+
"""Initialize XP33 server service.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
serial_number: The device serial number.
|
|
36
|
+
variant: Device variant (XP33, XP33LR, or XP33LED).
|
|
37
|
+
"""
|
|
33
38
|
super().__init__(serial_number)
|
|
34
39
|
self.variant = variant # XP33 or XP33LR or XP33LED
|
|
35
40
|
self.device_type = "XP33"
|
|
@@ -69,7 +74,7 @@ class XP33ServerService(BaseServerService):
|
|
|
69
74
|
def _handle_device_specific_data_request(
|
|
70
75
|
self, request: SystemTelegram
|
|
71
76
|
) -> Optional[str]:
|
|
72
|
-
"""Handle XP24-specific data requests"""
|
|
77
|
+
"""Handle XP24-specific data requests."""
|
|
73
78
|
if (
|
|
74
79
|
request.system_function != SystemFunction.READ_DATAPOINT
|
|
75
80
|
or not request.datapoint_type
|
|
@@ -82,7 +87,12 @@ class XP33ServerService(BaseServerService):
|
|
|
82
87
|
DataPointType.MODULE_STATE: "OFF",
|
|
83
88
|
DataPointType.MODULE_OPERATING_HOURS: "00:000[H],01:000[H],02:000[H]",
|
|
84
89
|
}
|
|
85
|
-
data_part =
|
|
90
|
+
data_part = (
|
|
91
|
+
f"R{self.serial_number}"
|
|
92
|
+
f"F02{datapoint_type.value}"
|
|
93
|
+
f"{self.module_type_code}"
|
|
94
|
+
f"{datapoint_values.get(datapoint_type)}"
|
|
95
|
+
)
|
|
86
96
|
checksum = calculate_checksum(data_part)
|
|
87
97
|
telegram = f"<{data_part}{checksum}>"
|
|
88
98
|
|
|
@@ -92,7 +102,15 @@ class XP33ServerService(BaseServerService):
|
|
|
92
102
|
return telegram
|
|
93
103
|
|
|
94
104
|
def set_channel_dimming(self, channel: int, level: int) -> bool:
|
|
95
|
-
"""Set individual channel dimming level
|
|
105
|
+
"""Set individual channel dimming level.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
channel: Channel number (1-3).
|
|
109
|
+
level: Dimming level (0-100 percent).
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
True if channel was set successfully, False otherwise.
|
|
113
|
+
"""
|
|
96
114
|
if 1 <= channel <= 3 and 0 <= level <= 100:
|
|
97
115
|
self.channel_states[channel - 1] = level
|
|
98
116
|
self.logger.info(f"XP33 channel {channel} set to {level}%")
|
|
@@ -100,7 +118,14 @@ class XP33ServerService(BaseServerService):
|
|
|
100
118
|
return False
|
|
101
119
|
|
|
102
120
|
def activate_scene(self, scene: int) -> bool:
|
|
103
|
-
"""Activate a pre-programmed scene
|
|
121
|
+
"""Activate a pre-programmed scene.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
scene: Scene number (1-4).
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
True if scene was activated successfully, False otherwise.
|
|
128
|
+
"""
|
|
104
129
|
if scene in self.scenes:
|
|
105
130
|
self.channel_states = self.scenes[scene].copy()
|
|
106
131
|
self.logger.info(f"XP33 scene {scene} activated: {self.channel_states}")
|
|
@@ -108,7 +133,11 @@ class XP33ServerService(BaseServerService):
|
|
|
108
133
|
return False
|
|
109
134
|
|
|
110
135
|
def get_device_info(self) -> Dict:
|
|
111
|
-
"""Get XP33 device information
|
|
136
|
+
"""Get XP33 device information.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Dictionary containing device information.
|
|
140
|
+
"""
|
|
112
141
|
return {
|
|
113
142
|
"serial_number": self.serial_number,
|
|
114
143
|
"device_type": self.device_type,
|
|
@@ -123,7 +152,11 @@ class XP33ServerService(BaseServerService):
|
|
|
123
152
|
}
|
|
124
153
|
|
|
125
154
|
def get_technical_specs(self) -> Dict:
|
|
126
|
-
"""Get technical specifications
|
|
155
|
+
"""Get technical specifications.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Dictionary containing technical specifications.
|
|
159
|
+
"""
|
|
127
160
|
if self.variant == "XP33LED":
|
|
128
161
|
return {
|
|
129
162
|
"power_per_channel": "100VA",
|
xp/services/telegram/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Telegram parsing and processing services."""
|