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,122 @@
|
|
|
1
|
+
"""Conbus Raw Service for sending raw telegram sequences.
|
|
2
|
+
|
|
3
|
+
This service handles sending raw telegram strings without prior validation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Callable, Optional
|
|
9
|
+
|
|
10
|
+
from twisted.internet.posixbase import PosixReactorBase
|
|
11
|
+
|
|
12
|
+
from xp.models import ConbusClientConfig
|
|
13
|
+
from xp.models.conbus.conbus_raw import ConbusRawResponse
|
|
14
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
15
|
+
from xp.services.protocol import ConbusProtocol
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConbusRawService(ConbusProtocol):
|
|
19
|
+
"""
|
|
20
|
+
Service for sending raw telegram sequences to Conbus modules.
|
|
21
|
+
|
|
22
|
+
Uses ConbusProtocol to provide raw telegram functionality
|
|
23
|
+
for sending arbitrary telegram strings without validation.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
cli_config: ConbusClientConfig,
|
|
29
|
+
reactor: PosixReactorBase,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Initialize the Conbus raw service.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
cli_config: Configuration for Conbus client connection.
|
|
35
|
+
reactor: Twisted reactor for event loop.
|
|
36
|
+
"""
|
|
37
|
+
super().__init__(cli_config, reactor)
|
|
38
|
+
self.raw_input: str = ""
|
|
39
|
+
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
40
|
+
self.finish_callback: Optional[Callable[[ConbusRawResponse], None]] = None
|
|
41
|
+
self.service_response: ConbusRawResponse = ConbusRawResponse(
|
|
42
|
+
success=False,
|
|
43
|
+
)
|
|
44
|
+
# Set up logging
|
|
45
|
+
self.logger = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
def connection_established(self) -> None:
|
|
48
|
+
"""Handle connection established event."""
|
|
49
|
+
self.logger.debug(f"Connection established, sending {self.raw_input}")
|
|
50
|
+
self.sendFrame(self.raw_input.encode())
|
|
51
|
+
|
|
52
|
+
def telegram_sent(self, telegram_sent: str) -> None:
|
|
53
|
+
"""Handle telegram sent event.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
telegram_sent: The telegram that was sent.
|
|
57
|
+
"""
|
|
58
|
+
self.service_response.success = True
|
|
59
|
+
self.service_response.sent_telegrams = telegram_sent
|
|
60
|
+
self.service_response.timestamp = datetime.now()
|
|
61
|
+
self.service_response.received_telegrams = []
|
|
62
|
+
|
|
63
|
+
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
64
|
+
"""Handle telegram received event.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
telegram_received: The telegram received event.
|
|
68
|
+
"""
|
|
69
|
+
self.logger.debug(f"Telegram received: {telegram_received}")
|
|
70
|
+
if not self.service_response.received_telegrams:
|
|
71
|
+
self.service_response.received_telegrams = []
|
|
72
|
+
self.service_response.received_telegrams.append(telegram_received.frame)
|
|
73
|
+
|
|
74
|
+
if self.progress_callback:
|
|
75
|
+
self.progress_callback(telegram_received.frame)
|
|
76
|
+
|
|
77
|
+
def timeout(self) -> bool:
|
|
78
|
+
"""Handle timeout event.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
False to indicate connection should be closed.
|
|
82
|
+
"""
|
|
83
|
+
self.logger.debug(f"Timeout: {self.timeout_seconds}s")
|
|
84
|
+
if self.finish_callback:
|
|
85
|
+
self.finish_callback(self.service_response)
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
def failed(self, message: str) -> None:
|
|
89
|
+
"""Handle failed connection event.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
message: Failure message.
|
|
93
|
+
"""
|
|
94
|
+
self.logger.debug(f"Failed with message: {message}")
|
|
95
|
+
self.service_response.success = False
|
|
96
|
+
self.service_response.timestamp = datetime.now()
|
|
97
|
+
self.service_response.error = message
|
|
98
|
+
if self.finish_callback:
|
|
99
|
+
self.finish_callback(self.service_response)
|
|
100
|
+
|
|
101
|
+
def send_raw_telegram(
|
|
102
|
+
self,
|
|
103
|
+
raw_input: str,
|
|
104
|
+
progress_callback: Callable[[str], None],
|
|
105
|
+
finish_callback: Callable[[ConbusRawResponse], None],
|
|
106
|
+
timeout_seconds: Optional[float] = None,
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Send a raw telegram string to the Conbus server.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
raw_input: Raw telegram string to send.
|
|
112
|
+
progress_callback: Callback to handle progress updates.
|
|
113
|
+
finish_callback: Callback function to call when the operation is complete.
|
|
114
|
+
timeout_seconds: Timeout in seconds.
|
|
115
|
+
"""
|
|
116
|
+
self.logger.info("Starting send_raw_telegram")
|
|
117
|
+
if timeout_seconds:
|
|
118
|
+
self.timeout_seconds = timeout_seconds
|
|
119
|
+
self.progress_callback = progress_callback
|
|
120
|
+
self.finish_callback = finish_callback
|
|
121
|
+
self.raw_input = raw_input
|
|
122
|
+
self.start_reactor()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Conbus Receive Service for receiving telegrams from Conbus servers.
|
|
2
|
+
|
|
3
|
+
This service uses ConbusProtocol to provide receive-only functionality,
|
|
4
|
+
allowing clients to receive waiting event telegrams using empty telegram sends.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Callable, Optional
|
|
9
|
+
|
|
10
|
+
from twisted.internet.posixbase import PosixReactorBase
|
|
11
|
+
|
|
12
|
+
from xp.models import ConbusClientConfig
|
|
13
|
+
from xp.models.conbus.conbus_receive import ConbusReceiveResponse
|
|
14
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
15
|
+
from xp.services.protocol import ConbusProtocol
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConbusReceiveService(ConbusProtocol):
|
|
19
|
+
"""
|
|
20
|
+
Service for receiving telegrams from Conbus servers.
|
|
21
|
+
|
|
22
|
+
Uses ConbusProtocol to provide receive-only functionality
|
|
23
|
+
for collecting waiting event telegrams from the server.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
cli_config: ConbusClientConfig,
|
|
29
|
+
reactor: PosixReactorBase,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Initialize the Conbus receive service.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
cli_config: Conbus client configuration.
|
|
35
|
+
reactor: Twisted reactor instance.
|
|
36
|
+
"""
|
|
37
|
+
super().__init__(cli_config, reactor)
|
|
38
|
+
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
39
|
+
self.finish_callback: Optional[Callable[[ConbusReceiveResponse], None]] = None
|
|
40
|
+
self.receive_response: ConbusReceiveResponse = ConbusReceiveResponse(
|
|
41
|
+
success=True
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Set up logging
|
|
45
|
+
self.logger = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
def connection_established(self) -> None:
|
|
48
|
+
"""Handle connection established event."""
|
|
49
|
+
self.logger.debug("Connection established, waiting for telegrams.")
|
|
50
|
+
|
|
51
|
+
def telegram_sent(self, telegram_sent: str) -> None:
|
|
52
|
+
"""Handle telegram sent event.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
telegram_sent: The telegram that was sent.
|
|
56
|
+
"""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
60
|
+
"""Handle telegram received event.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
telegram_received: The telegram received event.
|
|
64
|
+
"""
|
|
65
|
+
self.logger.debug(f"Telegram received: {telegram_received}")
|
|
66
|
+
if self.progress_callback:
|
|
67
|
+
self.progress_callback(telegram_received.frame)
|
|
68
|
+
|
|
69
|
+
if not self.receive_response.received_telegrams:
|
|
70
|
+
self.receive_response.received_telegrams = []
|
|
71
|
+
self.receive_response.received_telegrams.append(telegram_received.frame)
|
|
72
|
+
|
|
73
|
+
def timeout(self) -> bool:
|
|
74
|
+
"""Handle timeout event to stop receiving.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
False to stop the reactor.
|
|
78
|
+
"""
|
|
79
|
+
self.logger.info("Receive stopped after: %ss", self.timeout_seconds)
|
|
80
|
+
self.receive_response.success = True
|
|
81
|
+
if self.finish_callback:
|
|
82
|
+
self.finish_callback(self.receive_response)
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
def failed(self, message: str) -> None:
|
|
86
|
+
"""Handle failed connection event.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
message: Failure message.
|
|
90
|
+
"""
|
|
91
|
+
self.logger.debug("Failed %s:", message)
|
|
92
|
+
self.receive_response.success = False
|
|
93
|
+
self.receive_response.error = message
|
|
94
|
+
if self.finish_callback:
|
|
95
|
+
self.finish_callback(self.receive_response)
|
|
96
|
+
|
|
97
|
+
def start(
|
|
98
|
+
self,
|
|
99
|
+
progress_callback: Callable[[str], None],
|
|
100
|
+
finish_callback: Callable[[ConbusReceiveResponse], None],
|
|
101
|
+
timeout_seconds: Optional[float] = None,
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Run reactor in dedicated thread with its own event loop.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
progress_callback: Callback for each received telegram.
|
|
107
|
+
finish_callback: Callback when receiving completes.
|
|
108
|
+
timeout_seconds: Optional timeout in seconds.
|
|
109
|
+
"""
|
|
110
|
+
self.logger.info("Starting receive")
|
|
111
|
+
if timeout_seconds:
|
|
112
|
+
self.timeout_seconds = timeout_seconds
|
|
113
|
+
self.progress_callback = progress_callback
|
|
114
|
+
self.finish_callback = finish_callback
|
|
115
|
+
self.start_reactor()
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Conbus Scan Service for TCP communication with Conbus servers.
|
|
2
|
+
|
|
3
|
+
This service implements a TCP client that scans Conbus servers and sends
|
|
4
|
+
telegrams to scan modules for all datapoints by function code.
|
|
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 (
|
|
14
|
+
ConbusClientConfig,
|
|
15
|
+
ConbusResponse,
|
|
16
|
+
)
|
|
17
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
18
|
+
from xp.services.protocol import ConbusProtocol
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ConbusScanService(ConbusProtocol):
|
|
22
|
+
"""
|
|
23
|
+
Service for scanning modules for all datapoints by function code.
|
|
24
|
+
|
|
25
|
+
Uses ConbusProtocol to provide scan functionality for discovering
|
|
26
|
+
all available datapoints on a module.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
cli_config: ConbusClientConfig,
|
|
32
|
+
reactor: PosixReactorBase,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Initialize the Conbus scan service.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
cli_config: Conbus client configuration.
|
|
38
|
+
reactor: Twisted reactor instance.
|
|
39
|
+
"""
|
|
40
|
+
super().__init__(cli_config, reactor)
|
|
41
|
+
self.serial_number: str = ""
|
|
42
|
+
self.function_code: str = ""
|
|
43
|
+
self.datapoint_value: int = -1
|
|
44
|
+
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
45
|
+
self.finish_callback: Optional[Callable[[ConbusResponse], None]] = None
|
|
46
|
+
self.service_response: ConbusResponse = ConbusResponse(
|
|
47
|
+
success=False,
|
|
48
|
+
serial_number=self.serial_number,
|
|
49
|
+
sent_telegrams=[],
|
|
50
|
+
received_telegrams=[],
|
|
51
|
+
timestamp=datetime.now(),
|
|
52
|
+
)
|
|
53
|
+
# Set up logging
|
|
54
|
+
self.logger = logging.getLogger(__name__)
|
|
55
|
+
|
|
56
|
+
def connection_established(self) -> None:
|
|
57
|
+
"""Handle connection established event."""
|
|
58
|
+
self.logger.debug("Connection established, starting scan")
|
|
59
|
+
self.scan_next_datacode()
|
|
60
|
+
|
|
61
|
+
def scan_next_datacode(self) -> bool:
|
|
62
|
+
"""Scan the next data code.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if scanning should continue, False if complete.
|
|
66
|
+
"""
|
|
67
|
+
self.datapoint_value += 1
|
|
68
|
+
if self.datapoint_value >= 100:
|
|
69
|
+
if self.finish_callback:
|
|
70
|
+
self.finish_callback(self.service_response)
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
self.logger.debug(f"Scanning next datacode: {self.datapoint_value:02d}")
|
|
74
|
+
data = f"{self.datapoint_value:02d}"
|
|
75
|
+
telegram_body = f"S{self.serial_number}F{self.function_code}D{data}"
|
|
76
|
+
self.sendFrame(telegram_body.encode())
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
def telegram_sent(self, telegram_sent: str) -> None:
|
|
80
|
+
"""Handle telegram sent event.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
telegram_sent: The telegram that was sent.
|
|
84
|
+
"""
|
|
85
|
+
self.service_response.success = True
|
|
86
|
+
self.service_response.sent_telegrams.append(telegram_sent)
|
|
87
|
+
|
|
88
|
+
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
89
|
+
"""Handle telegram received event.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
telegram_received: The telegram received event.
|
|
93
|
+
"""
|
|
94
|
+
self.logger.debug(f"Telegram received: {telegram_received}")
|
|
95
|
+
if not self.service_response.received_telegrams:
|
|
96
|
+
self.service_response.received_telegrams = []
|
|
97
|
+
self.service_response.received_telegrams.append(telegram_received.frame)
|
|
98
|
+
|
|
99
|
+
if self.progress_callback:
|
|
100
|
+
self.progress_callback(telegram_received.frame)
|
|
101
|
+
|
|
102
|
+
def timeout(self) -> bool:
|
|
103
|
+
"""Handle timeout event by scanning next data code.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True to continue scanning, False to stop.
|
|
107
|
+
"""
|
|
108
|
+
self.logger.debug(f"Timeout: {self.timeout_seconds}s")
|
|
109
|
+
continue_scan = self.scan_next_datacode()
|
|
110
|
+
return continue_scan
|
|
111
|
+
|
|
112
|
+
def failed(self, message: str) -> None:
|
|
113
|
+
"""Handle failed connection event.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
message: Failure message.
|
|
117
|
+
"""
|
|
118
|
+
self.logger.debug(f"Failed with message: {message}")
|
|
119
|
+
self.service_response.success = False
|
|
120
|
+
self.service_response.timestamp = datetime.now()
|
|
121
|
+
self.service_response.error = message
|
|
122
|
+
if self.finish_callback:
|
|
123
|
+
self.finish_callback(self.service_response)
|
|
124
|
+
|
|
125
|
+
def scan_module(
|
|
126
|
+
self,
|
|
127
|
+
serial_number: str,
|
|
128
|
+
function_code: str,
|
|
129
|
+
progress_callback: Callable[[str], None],
|
|
130
|
+
finish_callback: Callable[[ConbusResponse], None],
|
|
131
|
+
timeout_seconds: float = 0.25,
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Scan a module for all datapoints by function code.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
serial_number: 10-digit module serial number.
|
|
137
|
+
function_code: The function code to scan.
|
|
138
|
+
progress_callback: Callback to handle progress.
|
|
139
|
+
finish_callback: Callback function to call when the scan is complete.
|
|
140
|
+
timeout_seconds: Timeout in seconds.
|
|
141
|
+
"""
|
|
142
|
+
self.logger.info("Starting scan_module")
|
|
143
|
+
if timeout_seconds:
|
|
144
|
+
self.timeout_seconds = timeout_seconds
|
|
145
|
+
|
|
146
|
+
self.serial_number = serial_number
|
|
147
|
+
self.function_code = function_code
|
|
148
|
+
self.progress_callback = progress_callback
|
|
149
|
+
self.finish_callback = finish_callback
|
|
150
|
+
self.start_reactor()
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Conbus Link Number Service for setting module link numbers.
|
|
2
|
+
|
|
3
|
+
This service handles setting link numbers for modules through Conbus telegrams.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Callable, Optional
|
|
9
|
+
|
|
10
|
+
from twisted.internet.posixbase import PosixReactorBase
|
|
11
|
+
|
|
12
|
+
from xp.models import ConbusClientConfig
|
|
13
|
+
from xp.models.conbus.conbus_writeconfig import ConbusWriteConfigResponse
|
|
14
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
15
|
+
from xp.models.telegram.datapoint_type import DataPointType
|
|
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 WriteConfigService(ConbusProtocol):
|
|
23
|
+
"""
|
|
24
|
+
Service for writing module settings via Conbus telegrams.
|
|
25
|
+
|
|
26
|
+
Handles setting assignment by sending F04DXX telegrams and processing
|
|
27
|
+
ACK/NAK responses from modules.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
telegram_service: TelegramService,
|
|
33
|
+
cli_config: ConbusClientConfig,
|
|
34
|
+
reactor: PosixReactorBase,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Initialize the Conbus link number set 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.datapoint_type: Optional[DataPointType] = None
|
|
46
|
+
self.serial_number: str = ""
|
|
47
|
+
self.data_value: str = ""
|
|
48
|
+
self.write_config_finished_callback: Optional[
|
|
49
|
+
Callable[[ConbusWriteConfigResponse], None]
|
|
50
|
+
] = None
|
|
51
|
+
self.write_config_response: ConbusWriteConfigResponse = (
|
|
52
|
+
ConbusWriteConfigResponse(
|
|
53
|
+
success=False,
|
|
54
|
+
serial_number=self.serial_number,
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Set up logging
|
|
59
|
+
self.logger = logging.getLogger(__name__)
|
|
60
|
+
|
|
61
|
+
def connection_established(self) -> None:
|
|
62
|
+
"""Handle connection established event."""
|
|
63
|
+
self.logger.debug(f"Connection established, writing config {self.data_value}.")
|
|
64
|
+
|
|
65
|
+
# Validate parameters before sending
|
|
66
|
+
if not self.serial_number or len(self.serial_number) != 10:
|
|
67
|
+
self.failed(f"Serial number must be 10 digits, got: {self.serial_number}")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if len(self.data_value) < 2:
|
|
71
|
+
self.failed(f"data_value must be at least 2 bytes, got: {self.data_value}")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
if not self.datapoint_type:
|
|
75
|
+
self.failed(f"datapoint_type must be defined, got: {self.datapoint_type}")
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
# Send WRITE_CONFIG telegram
|
|
79
|
+
# Function F04 = WRITE_CONFIG,
|
|
80
|
+
# Datapoint = D datapoint_type
|
|
81
|
+
# Data = XX
|
|
82
|
+
self.send_telegram(
|
|
83
|
+
telegram_type=TelegramType.SYSTEM,
|
|
84
|
+
serial_number=self.serial_number,
|
|
85
|
+
system_function=SystemFunction.WRITE_CONFIG,
|
|
86
|
+
data_value=f"{self.datapoint_type.value}{self.data_value}",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def telegram_sent(self, telegram_sent: str) -> None:
|
|
90
|
+
"""Handle telegram sent event.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
telegram_sent: The telegram that was sent.
|
|
94
|
+
"""
|
|
95
|
+
self.write_config_response.sent_telegram = telegram_sent
|
|
96
|
+
|
|
97
|
+
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
98
|
+
"""Handle telegram received event.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
telegram_received: The telegram received event.
|
|
102
|
+
"""
|
|
103
|
+
self.logger.debug(f"Telegram received: {telegram_received}")
|
|
104
|
+
|
|
105
|
+
if not self.write_config_response.received_telegrams:
|
|
106
|
+
self.write_config_response.received_telegrams = []
|
|
107
|
+
self.write_config_response.received_telegrams.append(telegram_received.frame)
|
|
108
|
+
|
|
109
|
+
if (
|
|
110
|
+
not telegram_received.checksum_valid
|
|
111
|
+
or telegram_received.telegram_type != TelegramType.REPLY
|
|
112
|
+
or telegram_received.serial_number != self.serial_number
|
|
113
|
+
):
|
|
114
|
+
self.logger.debug("Not a reply for our serial number")
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
# Parse the reply telegram
|
|
118
|
+
reply_telegram = self.telegram_service.parse_reply_telegram(
|
|
119
|
+
telegram_received.frame
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if not reply_telegram or reply_telegram.system_function not in (
|
|
123
|
+
SystemFunction.ACK,
|
|
124
|
+
SystemFunction.NAK,
|
|
125
|
+
):
|
|
126
|
+
self.logger.debug("Not a write config reply")
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
succeed = (
|
|
130
|
+
True if reply_telegram.system_function == SystemFunction.ACK else False
|
|
131
|
+
)
|
|
132
|
+
self.finished(
|
|
133
|
+
succeed_or_failed=succeed, system_function=reply_telegram.system_function
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def failed(self, message: str) -> None:
|
|
137
|
+
"""Handle telegram failed event.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
message: The error message.
|
|
141
|
+
"""
|
|
142
|
+
self.logger.debug("Failed to send telegram")
|
|
143
|
+
self.finished(succeed_or_failed=False, message=message)
|
|
144
|
+
|
|
145
|
+
def finished(
|
|
146
|
+
self,
|
|
147
|
+
succeed_or_failed: bool,
|
|
148
|
+
message: Optional[str] = None,
|
|
149
|
+
system_function: Optional[SystemFunction] = None,
|
|
150
|
+
) -> None:
|
|
151
|
+
"""Handle successful link number set operation.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
succeed_or_failed: succeed true, failed false.
|
|
155
|
+
message: error message if any.
|
|
156
|
+
system_function: The system function from the reply telegram.
|
|
157
|
+
"""
|
|
158
|
+
self.logger.debug("finished writing config")
|
|
159
|
+
self.write_config_response.success = succeed_or_failed
|
|
160
|
+
self.write_config_response.error = message
|
|
161
|
+
self.write_config_response.timestamp = datetime.now()
|
|
162
|
+
self.write_config_response.serial_number = self.serial_number
|
|
163
|
+
self.write_config_response.system_function = system_function
|
|
164
|
+
self.write_config_response.datapoint_type = self.datapoint_type
|
|
165
|
+
self.write_config_response.data_value = self.data_value
|
|
166
|
+
if self.write_config_finished_callback:
|
|
167
|
+
self.write_config_finished_callback(self.write_config_response)
|
|
168
|
+
self._stop_reactor()
|
|
169
|
+
|
|
170
|
+
def write_config(
|
|
171
|
+
self,
|
|
172
|
+
serial_number: str,
|
|
173
|
+
datapoint_type: DataPointType,
|
|
174
|
+
data_value: str,
|
|
175
|
+
finish_callback: Callable[[ConbusWriteConfigResponse], None],
|
|
176
|
+
timeout_seconds: Optional[float] = None,
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Write config to a specific module.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
serial_number: 10-digit module serial number.
|
|
182
|
+
datapoint_type: the datapoint type to write to.
|
|
183
|
+
data_value: the data to write.
|
|
184
|
+
finish_callback: Callback function to call when operation completes.
|
|
185
|
+
timeout_seconds: Optional timeout in seconds.
|
|
186
|
+
"""
|
|
187
|
+
self.logger.info("Starting write_config")
|
|
188
|
+
if timeout_seconds:
|
|
189
|
+
self.timeout_seconds = timeout_seconds
|
|
190
|
+
self.serial_number = serial_number
|
|
191
|
+
self.datapoint_type = datapoint_type
|
|
192
|
+
self.data_value = data_value
|
|
193
|
+
self.write_config_finished_callback = finish_callback
|
|
194
|
+
self.start_reactor()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""HomeKit integration services."""
|