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.
Files changed (176) hide show
  1. conson_xp-1.18.0.dist-info/METADATA +412 -0
  2. conson_xp-1.18.0.dist-info/RECORD +176 -0
  3. conson_xp-1.18.0.dist-info/WHEEL +4 -0
  4. conson_xp-1.18.0.dist-info/entry_points.txt +5 -0
  5. conson_xp-1.18.0.dist-info/licenses/LICENSE +29 -0
  6. xp/__init__.py +9 -0
  7. xp/cli/__init__.py +5 -0
  8. xp/cli/__main__.py +6 -0
  9. xp/cli/commands/__init__.py +153 -0
  10. xp/cli/commands/conbus/__init__.py +25 -0
  11. xp/cli/commands/conbus/conbus.py +128 -0
  12. xp/cli/commands/conbus/conbus_actiontable_commands.py +233 -0
  13. xp/cli/commands/conbus/conbus_autoreport_commands.py +108 -0
  14. xp/cli/commands/conbus/conbus_blink_commands.py +163 -0
  15. xp/cli/commands/conbus/conbus_config_commands.py +29 -0
  16. xp/cli/commands/conbus/conbus_custom_commands.py +57 -0
  17. xp/cli/commands/conbus/conbus_datapoint_commands.py +113 -0
  18. xp/cli/commands/conbus/conbus_discover_commands.py +61 -0
  19. xp/cli/commands/conbus/conbus_event_commands.py +81 -0
  20. xp/cli/commands/conbus/conbus_lightlevel_commands.py +207 -0
  21. xp/cli/commands/conbus/conbus_linknumber_commands.py +102 -0
  22. xp/cli/commands/conbus/conbus_modulenumber_commands.py +104 -0
  23. xp/cli/commands/conbus/conbus_msactiontable_commands.py +94 -0
  24. xp/cli/commands/conbus/conbus_output_commands.py +163 -0
  25. xp/cli/commands/conbus/conbus_raw_commands.py +62 -0
  26. xp/cli/commands/conbus/conbus_receive_commands.py +59 -0
  27. xp/cli/commands/conbus/conbus_scan_commands.py +58 -0
  28. xp/cli/commands/file_commands.py +186 -0
  29. xp/cli/commands/homekit/__init__.py +3 -0
  30. xp/cli/commands/homekit/homekit.py +118 -0
  31. xp/cli/commands/homekit/homekit_start_commands.py +43 -0
  32. xp/cli/commands/module_commands.py +187 -0
  33. xp/cli/commands/reverse_proxy_commands.py +178 -0
  34. xp/cli/commands/server/__init__.py +3 -0
  35. xp/cli/commands/server/server_commands.py +135 -0
  36. xp/cli/commands/telegram/__init__.py +5 -0
  37. xp/cli/commands/telegram/telegram.py +41 -0
  38. xp/cli/commands/telegram/telegram_blink_commands.py +79 -0
  39. xp/cli/commands/telegram/telegram_checksum_commands.py +112 -0
  40. xp/cli/commands/telegram/telegram_discover_commands.py +41 -0
  41. xp/cli/commands/telegram/telegram_linknumber_commands.py +86 -0
  42. xp/cli/commands/telegram/telegram_parse_commands.py +75 -0
  43. xp/cli/commands/telegram/telegram_version_commands.py +52 -0
  44. xp/cli/main.py +87 -0
  45. xp/cli/utils/__init__.py +1 -0
  46. xp/cli/utils/click_tree.py +57 -0
  47. xp/cli/utils/datapoint_type_choice.py +57 -0
  48. xp/cli/utils/decorators.py +351 -0
  49. xp/cli/utils/error_handlers.py +201 -0
  50. xp/cli/utils/formatters.py +312 -0
  51. xp/cli/utils/module_type_choice.py +56 -0
  52. xp/cli/utils/serial_number_type.py +52 -0
  53. xp/cli/utils/system_function_choice.py +57 -0
  54. xp/cli/utils/xp_module_type.py +53 -0
  55. xp/connection/__init__.py +13 -0
  56. xp/connection/exceptions.py +22 -0
  57. xp/models/__init__.py +36 -0
  58. xp/models/actiontable/__init__.py +1 -0
  59. xp/models/actiontable/actiontable.py +43 -0
  60. xp/models/actiontable/msactiontable_xp20.py +53 -0
  61. xp/models/actiontable/msactiontable_xp24.py +58 -0
  62. xp/models/actiontable/msactiontable_xp33.py +65 -0
  63. xp/models/conbus/__init__.py +1 -0
  64. xp/models/conbus/conbus.py +87 -0
  65. xp/models/conbus/conbus_autoreport.py +67 -0
  66. xp/models/conbus/conbus_blink.py +80 -0
  67. xp/models/conbus/conbus_client_config.py +55 -0
  68. xp/models/conbus/conbus_connection_status.py +40 -0
  69. xp/models/conbus/conbus_custom.py +58 -0
  70. xp/models/conbus/conbus_datapoint.py +89 -0
  71. xp/models/conbus/conbus_discover.py +64 -0
  72. xp/models/conbus/conbus_event_raw.py +47 -0
  73. xp/models/conbus/conbus_lightlevel.py +52 -0
  74. xp/models/conbus/conbus_linknumber.py +54 -0
  75. xp/models/conbus/conbus_output.py +57 -0
  76. xp/models/conbus/conbus_raw.py +45 -0
  77. xp/models/conbus/conbus_receive.py +42 -0
  78. xp/models/conbus/conbus_writeconfig.py +60 -0
  79. xp/models/homekit/__init__.py +1 -0
  80. xp/models/homekit/homekit_accessory.py +35 -0
  81. xp/models/homekit/homekit_config.py +106 -0
  82. xp/models/homekit/homekit_conson_config.py +86 -0
  83. xp/models/log_entry.py +130 -0
  84. xp/models/protocol/__init__.py +1 -0
  85. xp/models/protocol/conbus_protocol.py +312 -0
  86. xp/models/response.py +42 -0
  87. xp/models/telegram/__init__.py +1 -0
  88. xp/models/telegram/action_type.py +31 -0
  89. xp/models/telegram/datapoint_type.py +82 -0
  90. xp/models/telegram/event_telegram.py +140 -0
  91. xp/models/telegram/event_type.py +15 -0
  92. xp/models/telegram/input_action_type.py +69 -0
  93. xp/models/telegram/input_type.py +17 -0
  94. xp/models/telegram/module_type.py +188 -0
  95. xp/models/telegram/module_type_code.py +205 -0
  96. xp/models/telegram/output_telegram.py +103 -0
  97. xp/models/telegram/reply_telegram.py +297 -0
  98. xp/models/telegram/system_function.py +116 -0
  99. xp/models/telegram/system_telegram.py +94 -0
  100. xp/models/telegram/telegram.py +28 -0
  101. xp/models/telegram/telegram_type.py +19 -0
  102. xp/models/telegram/timeparam_type.py +51 -0
  103. xp/models/write_config_type.py +33 -0
  104. xp/services/__init__.py +26 -0
  105. xp/services/actiontable/__init__.py +1 -0
  106. xp/services/actiontable/actiontable_serializer.py +273 -0
  107. xp/services/actiontable/msactiontable_serializer.py +7 -0
  108. xp/services/actiontable/msactiontable_xp20_serializer.py +169 -0
  109. xp/services/actiontable/msactiontable_xp24_serializer.py +120 -0
  110. xp/services/actiontable/msactiontable_xp33_serializer.py +239 -0
  111. xp/services/conbus/__init__.py +1 -0
  112. xp/services/conbus/actiontable/__init__.py +1 -0
  113. xp/services/conbus/actiontable/actiontable_download_service.py +158 -0
  114. xp/services/conbus/actiontable/actiontable_list_service.py +91 -0
  115. xp/services/conbus/actiontable/actiontable_show_service.py +89 -0
  116. xp/services/conbus/actiontable/actiontable_upload_service.py +211 -0
  117. xp/services/conbus/actiontable/msactiontable_service.py +232 -0
  118. xp/services/conbus/conbus_blink_all_service.py +181 -0
  119. xp/services/conbus/conbus_blink_service.py +158 -0
  120. xp/services/conbus/conbus_custom_service.py +156 -0
  121. xp/services/conbus/conbus_datapoint_queryall_service.py +182 -0
  122. xp/services/conbus/conbus_datapoint_service.py +170 -0
  123. xp/services/conbus/conbus_discover_service.py +312 -0
  124. xp/services/conbus/conbus_event_raw_service.py +181 -0
  125. xp/services/conbus/conbus_output_service.py +194 -0
  126. xp/services/conbus/conbus_raw_service.py +122 -0
  127. xp/services/conbus/conbus_receive_service.py +115 -0
  128. xp/services/conbus/conbus_scan_service.py +150 -0
  129. xp/services/conbus/write_config_service.py +194 -0
  130. xp/services/homekit/__init__.py +1 -0
  131. xp/services/homekit/homekit_cache_service.py +307 -0
  132. xp/services/homekit/homekit_conbus_service.py +93 -0
  133. xp/services/homekit/homekit_config_validator.py +310 -0
  134. xp/services/homekit/homekit_conson_validator.py +121 -0
  135. xp/services/homekit/homekit_dimminglight.py +182 -0
  136. xp/services/homekit/homekit_dimminglight_service.py +148 -0
  137. xp/services/homekit/homekit_hap_service.py +342 -0
  138. xp/services/homekit/homekit_lightbulb.py +120 -0
  139. xp/services/homekit/homekit_lightbulb_service.py +86 -0
  140. xp/services/homekit/homekit_module_service.py +56 -0
  141. xp/services/homekit/homekit_outlet.py +168 -0
  142. xp/services/homekit/homekit_outlet_service.py +121 -0
  143. xp/services/homekit/homekit_service.py +359 -0
  144. xp/services/log_file_service.py +309 -0
  145. xp/services/module_type_service.py +257 -0
  146. xp/services/protocol/__init__.py +21 -0
  147. xp/services/protocol/conbus_event_protocol.py +360 -0
  148. xp/services/protocol/conbus_protocol.py +318 -0
  149. xp/services/protocol/protocol_factory.py +78 -0
  150. xp/services/protocol/telegram_protocol.py +264 -0
  151. xp/services/reverse_proxy_service.py +435 -0
  152. xp/services/server/__init__.py +1 -0
  153. xp/services/server/base_server_service.py +366 -0
  154. xp/services/server/cp20_server_service.py +65 -0
  155. xp/services/server/device_service_factory.py +94 -0
  156. xp/services/server/server_service.py +428 -0
  157. xp/services/server/xp130_server_service.py +67 -0
  158. xp/services/server/xp20_server_service.py +92 -0
  159. xp/services/server/xp230_server_service.py +58 -0
  160. xp/services/server/xp24_server_service.py +245 -0
  161. xp/services/server/xp33_server_service.py +535 -0
  162. xp/services/telegram/__init__.py +1 -0
  163. xp/services/telegram/telegram_blink_service.py +138 -0
  164. xp/services/telegram/telegram_checksum_service.py +149 -0
  165. xp/services/telegram/telegram_datapoint_service.py +82 -0
  166. xp/services/telegram/telegram_discover_service.py +277 -0
  167. xp/services/telegram/telegram_link_number_service.py +216 -0
  168. xp/services/telegram/telegram_output_service.py +322 -0
  169. xp/services/telegram/telegram_service.py +380 -0
  170. xp/services/telegram/telegram_version_service.py +288 -0
  171. xp/utils/__init__.py +12 -0
  172. xp/utils/checksum.py +61 -0
  173. xp/utils/dependencies.py +531 -0
  174. xp/utils/event_helper.py +31 -0
  175. xp/utils/serialization.py +205 -0
  176. 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."""