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,211 @@
1
+ """Service for uploading ActionTable via Conbus protocol."""
2
+
3
+ import logging
4
+ from typing import Any, Callable, Optional
5
+
6
+ from twisted.internet.posixbase import PosixReactorBase
7
+
8
+ from xp.models import ConbusClientConfig
9
+ from xp.models.homekit.homekit_conson_config import ConsonModuleListConfig
10
+ from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
11
+ from xp.models.telegram.system_function import SystemFunction
12
+ from xp.models.telegram.telegram_type import TelegramType
13
+ from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
14
+ from xp.services.protocol import ConbusProtocol
15
+ from xp.services.telegram.telegram_service import TelegramService
16
+
17
+
18
+ class ActionTableUploadService(ConbusProtocol):
19
+ """TCP client service for uploading action tables to Conbus modules.
20
+
21
+ Manages TCP socket connections, handles telegram generation and transmission,
22
+ and processes server responses for action table uploads.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ cli_config: ConbusClientConfig,
28
+ reactor: PosixReactorBase,
29
+ actiontable_serializer: ActionTableSerializer,
30
+ telegram_service: TelegramService,
31
+ conson_config: ConsonModuleListConfig,
32
+ ) -> None:
33
+ """Initialize the action table upload service.
34
+
35
+ Args:
36
+ cli_config: Conbus client configuration.
37
+ reactor: Twisted reactor instance.
38
+ actiontable_serializer: Action table serializer.
39
+ telegram_service: Telegram service for parsing.
40
+ conson_config: Conson module list configuration.
41
+ """
42
+ super().__init__(cli_config, reactor)
43
+ self.serializer = actiontable_serializer
44
+ self.telegram_service = telegram_service
45
+ self.conson_config = conson_config
46
+ self.serial_number: str = ""
47
+ self.progress_callback: Optional[Callable[[str], None]] = None
48
+ self.error_callback: Optional[Callable[[str], None]] = None
49
+ self.success_callback: Optional[Callable[[], None]] = None
50
+
51
+ # Upload state
52
+ self.upload_data_chunks: list[str] = []
53
+ self.current_chunk_index: int = 0
54
+
55
+ # Set up logging
56
+ self.logger = logging.getLogger(__name__)
57
+
58
+ def connection_established(self) -> None:
59
+ """Handle connection established event."""
60
+ self.logger.debug("Connection established, sending upload actiontable telegram")
61
+ self.send_telegram(
62
+ telegram_type=TelegramType.SYSTEM,
63
+ serial_number=self.serial_number,
64
+ system_function=SystemFunction.UPLOAD_ACTIONTABLE,
65
+ data_value="00",
66
+ )
67
+
68
+ def telegram_sent(self, telegram_sent: str) -> None:
69
+ """Handle telegram sent event.
70
+
71
+ Args:
72
+ telegram_sent: The telegram that was sent.
73
+ """
74
+ self.logger.debug(f"Telegram sent: {telegram_sent}")
75
+
76
+ def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
77
+ """Handle telegram received event.
78
+
79
+ Args:
80
+ telegram_received: The telegram received event.
81
+ """
82
+ self.logger.debug(f"Telegram received: {telegram_received}")
83
+ if (
84
+ not telegram_received.checksum_valid
85
+ or telegram_received.telegram_type != TelegramType.REPLY.value
86
+ or telegram_received.serial_number != self.serial_number
87
+ ):
88
+ self.logger.debug("Not a reply response")
89
+ return
90
+
91
+ reply_telegram = self.telegram_service.parse_reply_telegram(
92
+ telegram_received.frame
93
+ )
94
+
95
+ self._handle_upload_response(reply_telegram)
96
+
97
+ def _handle_upload_response(self, reply_telegram: Any) -> None:
98
+ """Handle telegram responses during upload.
99
+
100
+ Args:
101
+ reply_telegram: Parsed reply telegram.
102
+ """
103
+ if reply_telegram.system_function == SystemFunction.ACK:
104
+ self.logger.debug("Received ACK for upload")
105
+ # Send next chunk or EOF
106
+ if self.current_chunk_index < len(self.upload_data_chunks):
107
+ chunk = self.upload_data_chunks[self.current_chunk_index]
108
+ self.logger.debug(f"Sending chunk {self.current_chunk_index + 1}")
109
+
110
+ # Calculate prefix: AA, AB, AC, AD, AE, AF, AG, AH, AI, AJ, AK, AL, AM, AN, AO
111
+ # First character: 'A' (fixed)
112
+ # Second character: 'A' + chunk_index (sequential counter A-O for 15 chunks)
113
+ prefix_hex = f"AAA{ord('A') + self.current_chunk_index:c}"
114
+
115
+ self.send_telegram(
116
+ telegram_type=TelegramType.SYSTEM,
117
+ serial_number=self.serial_number,
118
+ system_function=SystemFunction.ACTIONTABLE,
119
+ data_value=f"{prefix_hex}{chunk}",
120
+ )
121
+ self.current_chunk_index += 1
122
+ if self.progress_callback:
123
+ self.progress_callback(".")
124
+ else:
125
+ # All chunks sent, send EOF
126
+ self.logger.debug("All chunks sent, sending EOF")
127
+ self.send_telegram(
128
+ telegram_type=TelegramType.SYSTEM,
129
+ serial_number=self.serial_number,
130
+ system_function=SystemFunction.EOF,
131
+ data_value="00",
132
+ )
133
+ if self.success_callback:
134
+ self.success_callback()
135
+ self._stop_reactor()
136
+ elif reply_telegram.system_function == SystemFunction.NAK:
137
+ self.logger.debug("Received NAK during upload")
138
+ self.failed("Upload failed: NAK received")
139
+ else:
140
+ self.logger.debug(f"Unexpected response during upload: {reply_telegram}")
141
+
142
+ def failed(self, message: str) -> None:
143
+ """Handle failed connection event.
144
+
145
+ Args:
146
+ message: Failure message.
147
+ """
148
+ self.logger.debug(f"Failed: {message}")
149
+ if self.error_callback:
150
+ self.error_callback(message)
151
+ self._stop_reactor()
152
+
153
+ def start(
154
+ self,
155
+ serial_number: str,
156
+ progress_callback: Callable[[str], None],
157
+ error_callback: Callable[[str], None],
158
+ success_callback: Callable[[], None],
159
+ timeout_seconds: Optional[float] = None,
160
+ ) -> None:
161
+ """Upload action table to module.
162
+
163
+ Uploads the action table configuration to the specified module.
164
+
165
+ Args:
166
+ serial_number: Module serial number.
167
+ progress_callback: Callback for progress updates.
168
+ error_callback: Callback for errors.
169
+ success_callback: Callback when upload completes successfully.
170
+ timeout_seconds: Optional timeout in seconds.
171
+ """
172
+ self.logger.info("Starting actiontable upload")
173
+ self.serial_number = serial_number
174
+ if timeout_seconds:
175
+ self.timeout_seconds = timeout_seconds
176
+ self.progress_callback = progress_callback
177
+ self.error_callback = error_callback
178
+ self.success_callback = success_callback
179
+
180
+ # Find module
181
+ module = self.conson_config.find_module(serial_number)
182
+ if not module:
183
+ self.failed(f"Module {serial_number} not found in conson.yml")
184
+ return
185
+
186
+ # Parse action table strings to ActionTable object
187
+ try:
188
+ module_action_table = module.action_table or []
189
+ action_table = self.serializer.parse_action_table(module_action_table)
190
+ except ValueError as e:
191
+ self.logger.error(f"Invalid action table format: {e}")
192
+ self.failed(f"Invalid action table format: {e}")
193
+ return
194
+
195
+ # Encode action table to hex string
196
+ encoded_data = self.serializer.to_encoded_string(action_table)
197
+
198
+ # Chunk the data into 64 byte chunks
199
+ chunk_size = 64
200
+ self.upload_data_chunks = [
201
+ encoded_data[i : i + chunk_size]
202
+ for i in range(0, len(encoded_data), chunk_size)
203
+ ]
204
+ self.current_chunk_index = 0
205
+
206
+ self.logger.debug(
207
+ f"Upload data encoded: {len(encoded_data)} chars, "
208
+ f"{len(self.upload_data_chunks)} chunks"
209
+ )
210
+
211
+ self.start_reactor()
@@ -0,0 +1,232 @@
1
+ """Service for downloading XP24 action tables via Conbus protocol."""
2
+
3
+ import logging
4
+ from typing import Callable, Optional, Union
5
+
6
+ from twisted.internet.posixbase import PosixReactorBase
7
+
8
+ from xp.models import ConbusClientConfig
9
+ from xp.models.actiontable.msactiontable_xp20 import Xp20MsActionTable
10
+ from xp.models.actiontable.msactiontable_xp24 import Xp24MsActionTable
11
+ from xp.models.actiontable.msactiontable_xp33 import Xp33MsActionTable
12
+ from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
13
+ from xp.models.telegram.system_function import SystemFunction
14
+ from xp.models.telegram.telegram_type import TelegramType
15
+ from xp.services.actiontable.msactiontable_xp20_serializer import (
16
+ Xp20MsActionTableSerializer,
17
+ )
18
+ from xp.services.actiontable.msactiontable_xp24_serializer import (
19
+ Xp24MsActionTableSerializer,
20
+ )
21
+ from xp.services.actiontable.msactiontable_xp33_serializer import (
22
+ Xp33MsActionTableSerializer,
23
+ )
24
+ from xp.services.protocol import ConbusProtocol
25
+ from xp.services.telegram.telegram_service import TelegramService
26
+
27
+
28
+ class MsActionTableError(Exception):
29
+ """Raised when XP24 action table operations fail."""
30
+
31
+ pass
32
+
33
+
34
+ class MsActionTableService(ConbusProtocol):
35
+ """
36
+ TCP client service for sending telegrams to Conbus servers.
37
+
38
+ Manages TCP socket connections, handles telegram generation and transmission,
39
+ and processes server responses.
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ cli_config: ConbusClientConfig,
45
+ reactor: PosixReactorBase,
46
+ xp20ms_serializer: Xp20MsActionTableSerializer,
47
+ xp24ms_serializer: Xp24MsActionTableSerializer,
48
+ xp33ms_serializer: Xp33MsActionTableSerializer,
49
+ telegram_service: TelegramService,
50
+ ) -> None:
51
+ """Initialize the Conbus client send service.
52
+
53
+ Args:
54
+ cli_config: Conbus client configuration.
55
+ reactor: Twisted reactor instance.
56
+ xp20ms_serializer: XP20 MS action table serializer.
57
+ xp24ms_serializer: XP24 MS action table serializer.
58
+ xp33ms_serializer: XP33 MS action table serializer.
59
+ telegram_service: Telegram service for parsing.
60
+ """
61
+ super().__init__(cli_config, reactor)
62
+ self.xp20ms_serializer = xp20ms_serializer
63
+ self.xp24ms_serializer = xp24ms_serializer
64
+ self.xp33ms_serializer = xp33ms_serializer
65
+ self.serializer: Union[
66
+ Xp20MsActionTableSerializer,
67
+ Xp24MsActionTableSerializer,
68
+ Xp33MsActionTableSerializer,
69
+ ] = xp20ms_serializer
70
+ self.telegram_service = telegram_service
71
+ self.serial_number: str = ""
72
+ self.xpmoduletype: str = ""
73
+ self.progress_callback: Optional[Callable[[str], None]] = None
74
+ self.error_callback: Optional[Callable[[str], None]] = None
75
+ self.finish_callback: Optional[
76
+ Callable[
77
+ [Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable, None]],
78
+ None,
79
+ ]
80
+ ] = None
81
+ self.msactiontable_data: list[str] = []
82
+ # Set up logging
83
+ self.logger = logging.getLogger(__name__)
84
+
85
+ def connection_established(self) -> None:
86
+ """Handle connection established event."""
87
+ self.logger.debug(
88
+ "Connection established, sending download msactiontable telegram"
89
+ )
90
+ self.send_telegram(
91
+ telegram_type=TelegramType.SYSTEM,
92
+ serial_number=self.serial_number,
93
+ system_function=SystemFunction.DOWNLOAD_MSACTIONTABLE,
94
+ data_value="00",
95
+ )
96
+
97
+ def telegram_sent(self, telegram_sent: str) -> None:
98
+ """Handle telegram sent event.
99
+
100
+ Args:
101
+ telegram_sent: The telegram that was sent.
102
+ """
103
+ self.logger.debug(f"Telegram sent: {telegram_sent}")
104
+
105
+ def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
106
+ """Handle telegram received event.
107
+
108
+ Args:
109
+ telegram_received: The telegram received event.
110
+ """
111
+ self.logger.debug(f"Telegram received: {telegram_received}")
112
+ if (
113
+ not telegram_received.checksum_valid
114
+ or telegram_received.telegram_type != TelegramType.REPLY.value
115
+ or telegram_received.serial_number != self.serial_number
116
+ ):
117
+ self.logger.debug("Not a reply response")
118
+ return
119
+
120
+ reply_telegram = self.telegram_service.parse_reply_telegram(
121
+ telegram_received.frame
122
+ )
123
+ if reply_telegram.system_function not in (
124
+ SystemFunction.MSACTIONTABLE,
125
+ SystemFunction.ACK,
126
+ SystemFunction.NAK,
127
+ SystemFunction.EOF,
128
+ ):
129
+ self.logger.debug("Not a msactiontable response")
130
+ return
131
+
132
+ if reply_telegram.system_function == SystemFunction.ACK:
133
+ self.logger.debug("Received ACK")
134
+ return
135
+
136
+ if reply_telegram.system_function == SystemFunction.NAK:
137
+ self.logger.debug("Received NAK")
138
+ self.failed("Received NAK")
139
+ return
140
+
141
+ if reply_telegram.system_function == SystemFunction.MSACTIONTABLE:
142
+ self.logger.debug("Received MSACTIONTABLE")
143
+ self.msactiontable_data.extend(
144
+ (reply_telegram.data, reply_telegram.data_value)
145
+ )
146
+ if self.progress_callback:
147
+ self.progress_callback(".")
148
+
149
+ self.send_telegram(
150
+ telegram_type=TelegramType.SYSTEM,
151
+ serial_number=self.serial_number,
152
+ system_function=SystemFunction.ACK,
153
+ data_value="00",
154
+ )
155
+ return
156
+
157
+ if reply_telegram.system_function == SystemFunction.EOF:
158
+ self.logger.debug("Received EOF")
159
+ all_data = "".join(self.msactiontable_data)
160
+ # Deserialize from received data
161
+ msactiontable = self.serializer.from_data(all_data)
162
+ self.succeed(msactiontable)
163
+ return
164
+
165
+ self.logger.debug("Invalid msactiontable response")
166
+
167
+ def failed(self, message: str) -> None:
168
+ """Handle failed connection event.
169
+
170
+ Args:
171
+ message: Failure message.
172
+ """
173
+ self.logger.debug(f"Failed: {message}")
174
+ if self.error_callback:
175
+ self.error_callback(message)
176
+ self._stop_reactor()
177
+
178
+ def succeed(
179
+ self,
180
+ msactiontable: Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable],
181
+ ) -> None:
182
+ """Handle succeed connection event.
183
+
184
+ Args:
185
+ msactiontable: result.
186
+ """
187
+ if self.finish_callback:
188
+ self.finish_callback(msactiontable)
189
+ self._stop_reactor()
190
+
191
+ def start(
192
+ self,
193
+ serial_number: str,
194
+ xpmoduletype: str,
195
+ progress_callback: Callable[[str], None],
196
+ error_callback: Callable[[str], None],
197
+ finish_callback: Callable[
198
+ [Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable, None]], None
199
+ ],
200
+ timeout_seconds: Optional[float] = None,
201
+ ) -> None:
202
+ """Run reactor in dedicated thread with its own event loop.
203
+
204
+ Args:
205
+ serial_number: Module serial number.
206
+ xpmoduletype: XP module type (xp20, xp24, xp33).
207
+ progress_callback: Callback for progress updates.
208
+ error_callback: Callback for errors.
209
+ finish_callback: Callback when download completes.
210
+ timeout_seconds: Optional timeout in seconds.
211
+
212
+ Raises:
213
+ MsActionTableError: If unsupported module type is provided.
214
+ """
215
+ self.logger.info("Starting msactiontable")
216
+ self.serial_number = serial_number
217
+ self.xpmoduletype = xpmoduletype
218
+ if xpmoduletype == "xp20":
219
+ self.serializer = self.xp20ms_serializer
220
+ elif xpmoduletype == "xp24":
221
+ self.serializer = self.xp24ms_serializer
222
+ elif xpmoduletype == "xp33":
223
+ self.serializer = self.xp33ms_serializer
224
+ else:
225
+ raise MsActionTableError(f"Unsupported module type: {xpmoduletype}")
226
+
227
+ if timeout_seconds:
228
+ self.timeout_seconds = timeout_seconds
229
+ self.progress_callback = progress_callback
230
+ self.error_callback = error_callback
231
+ self.finish_callback = finish_callback
232
+ self.start_reactor()
@@ -0,0 +1,181 @@
1
+ """Conbus Blink All Service for TCP communication with Conbus servers.
2
+
3
+ This service implements a TCP client that connects to Conbus servers and sends
4
+ blink/unblink telegrams to all discovered modules on the network.
5
+ """
6
+
7
+ import logging
8
+ from datetime import datetime
9
+ from typing import Callable, Optional
10
+
11
+ from twisted.internet.posixbase import PosixReactorBase
12
+
13
+ from xp.models import ConbusClientConfig
14
+ from xp.models.conbus.conbus_blink import ConbusBlinkResponse
15
+ from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
16
+ from xp.models.telegram.system_function import SystemFunction
17
+ from xp.models.telegram.telegram_type import TelegramType
18
+ from xp.services.protocol import ConbusProtocol
19
+ from xp.services.telegram.telegram_service import TelegramService
20
+
21
+
22
+ class ConbusBlinkAllService(ConbusProtocol):
23
+ """
24
+ Service for blinking all modules on Conbus servers.
25
+
26
+ Uses ConbusProtocol to provide blink/unblink functionality
27
+ for all discovered modules on the network.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ telegram_service: TelegramService,
33
+ cli_config: ConbusClientConfig,
34
+ reactor: PosixReactorBase,
35
+ ) -> None:
36
+ """Initialize the Conbus blink all service.
37
+
38
+ Args:
39
+ telegram_service: Service for parsing telegrams.
40
+ cli_config: Configuration for Conbus client connection.
41
+ reactor: Twisted reactor for event loop.
42
+ """
43
+ super().__init__(cli_config, reactor)
44
+ self.telegram_service = telegram_service
45
+ self.serial_number: str = ""
46
+ self.on_or_off = "none"
47
+ self.progress_callback: Optional[Callable[[str], None]] = None
48
+ self.finish_callback: Optional[Callable[[ConbusBlinkResponse], None]] = None
49
+ self.service_response: ConbusBlinkResponse = ConbusBlinkResponse(
50
+ success=False,
51
+ serial_number=self.serial_number,
52
+ system_function=SystemFunction.NONE,
53
+ operation=self.on_or_off,
54
+ )
55
+
56
+ # Set up logging
57
+ self.logger = logging.getLogger(__name__)
58
+
59
+ def connection_established(self) -> None:
60
+ """Handle connection established event."""
61
+ self.logger.debug("Connection established, send discover telegram.")
62
+ self.send_telegram(
63
+ telegram_type=TelegramType.SYSTEM,
64
+ serial_number="0000000000",
65
+ system_function=SystemFunction.DISCOVERY,
66
+ data_value="00",
67
+ )
68
+ if self.progress_callback:
69
+ self.progress_callback(".")
70
+
71
+ def send_blink(self, serial_number: str) -> None:
72
+ """Send blink or unblink telegram to a discovered module.
73
+
74
+ Args:
75
+ serial_number: 10-digit module serial number.
76
+ """
77
+ self.logger.debug("Device discovered, send blink.")
78
+
79
+ # Blink is 05, Unblink is 06
80
+ system_function = SystemFunction.UNBLINK
81
+ if self.on_or_off.lower() == "on":
82
+ system_function = SystemFunction.BLINK
83
+
84
+ self.send_telegram(
85
+ telegram_type=TelegramType.SYSTEM,
86
+ serial_number=serial_number,
87
+ system_function=system_function,
88
+ data_value="00",
89
+ )
90
+ self.service_response.system_function = system_function
91
+ self.service_response.operation = self.on_or_off
92
+
93
+ if self.progress_callback:
94
+ self.progress_callback(".")
95
+
96
+ def telegram_sent(self, telegram_sent: str) -> None:
97
+ """Handle telegram sent event.
98
+
99
+ Args:
100
+ telegram_sent: The telegram that was sent.
101
+ """
102
+ system_telegram = self.telegram_service.parse_system_telegram(telegram_sent)
103
+ self.service_response.sent_telegram = system_telegram
104
+
105
+ def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
106
+ """Handle telegram received event.
107
+
108
+ Args:
109
+ telegram_received: The telegram received event.
110
+ """
111
+ self.logger.debug(f"Telegram received: {telegram_received}")
112
+ if not self.service_response.received_telegrams:
113
+ self.service_response.received_telegrams = []
114
+ self.service_response.received_telegrams.append(telegram_received.frame)
115
+
116
+ if (
117
+ not telegram_received.checksum_valid
118
+ or telegram_received.telegram_type != TelegramType.REPLY
119
+ ):
120
+ self.logger.debug("Not a reply")
121
+ return
122
+
123
+ reply_telegram = self.telegram_service.parse_reply_telegram(
124
+ telegram_received.frame
125
+ )
126
+ if (
127
+ reply_telegram
128
+ and reply_telegram.system_function == SystemFunction.DISCOVERY
129
+ ):
130
+ self.logger.debug("Received discovery response")
131
+ self.send_blink(reply_telegram.serial_number)
132
+ if self.progress_callback:
133
+ self.progress_callback(".")
134
+ return
135
+
136
+ if reply_telegram and reply_telegram.system_function in (
137
+ SystemFunction.BLINK,
138
+ SystemFunction.UNBLINK,
139
+ ):
140
+ self.logger.debug("Received blink response")
141
+ if self.progress_callback:
142
+ self.progress_callback(".")
143
+ return
144
+
145
+ self.logger.debug("Received unexpected response")
146
+
147
+ def failed(self, message: str) -> None:
148
+ """Handle failed connection event.
149
+
150
+ Args:
151
+ message: Failure message.
152
+ """
153
+ self.logger.debug(f"Failed with message: {message}")
154
+ self.service_response.success = False
155
+ self.service_response.timestamp = datetime.now()
156
+ self.service_response.error = message
157
+ if self.finish_callback:
158
+ self.finish_callback(self.service_response)
159
+
160
+ def send_blink_all_telegram(
161
+ self,
162
+ on_or_off: str,
163
+ progress_callback: Callable[[str], None],
164
+ finish_callback: Callable[[ConbusBlinkResponse], None],
165
+ timeout_seconds: Optional[float] = None,
166
+ ) -> None:
167
+ """Send blink command to all discovered modules.
168
+
169
+ Args:
170
+ on_or_off: "on" to blink or "off" to unblink all devices.
171
+ progress_callback: Callback function to call with progress updates.
172
+ finish_callback: Callback function to call when the operation completes.
173
+ timeout_seconds: Timeout in seconds.
174
+ """
175
+ self.logger.info("Starting send_blink_all_telegram")
176
+ if timeout_seconds:
177
+ self.timeout_seconds = timeout_seconds
178
+ self.progress_callback = progress_callback
179
+ self.finish_callback = finish_callback
180
+ self.on_or_off = on_or_off
181
+ self.start_reactor()