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,158 @@
1
+ """Conbus Blink 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 control module LED indicators.
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 ConbusBlinkService(ConbusProtocol):
23
+ """
24
+ Service for blinking module LEDs on Conbus servers.
25
+
26
+ Uses ConbusProtocol to provide blink/unblink functionality
27
+ for controlling module LED indicators.
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 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.finish_callback: Optional[Callable[[ConbusBlinkResponse], None]] = None
48
+ self.service_response: ConbusBlinkResponse = ConbusBlinkResponse(
49
+ success=False,
50
+ serial_number=self.serial_number,
51
+ system_function=SystemFunction.NONE,
52
+ operation=self.on_or_off,
53
+ )
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 blink command.")
61
+ # Blink is 05, Unblink is 06
62
+ system_function = SystemFunction.UNBLINK
63
+ if self.on_or_off.lower() == "on":
64
+ system_function = SystemFunction.BLINK
65
+
66
+ self.send_telegram(
67
+ telegram_type=TelegramType.SYSTEM,
68
+ serial_number=self.serial_number,
69
+ system_function=system_function,
70
+ data_value="00",
71
+ )
72
+ self.service_response.system_function = system_function
73
+ self.service_response.operation = self.on_or_off
74
+
75
+ def telegram_sent(self, telegram_sent: str) -> None:
76
+ """Handle telegram sent event.
77
+
78
+ Args:
79
+ telegram_sent: The telegram that was sent.
80
+ """
81
+ system_telegram = self.telegram_service.parse_system_telegram(telegram_sent)
82
+ self.service_response.sent_telegram = system_telegram
83
+
84
+ def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
85
+ """Handle telegram received event.
86
+
87
+ Args:
88
+ telegram_received: The telegram received event.
89
+ """
90
+ self.logger.debug(f"Telegram received: {telegram_received}")
91
+ if not self.service_response.received_telegrams:
92
+ self.service_response.received_telegrams = []
93
+ self.service_response.received_telegrams.append(telegram_received.frame)
94
+
95
+ if (
96
+ not telegram_received.checksum_valid
97
+ or telegram_received.telegram_type != TelegramType.REPLY
98
+ or telegram_received.serial_number != self.serial_number
99
+ ):
100
+ self.logger.debug("Not a reply")
101
+ return
102
+
103
+ reply_telegram = self.telegram_service.parse_reply_telegram(
104
+ telegram_received.frame
105
+ )
106
+ if reply_telegram is not None and reply_telegram.system_function in (
107
+ SystemFunction.ACK,
108
+ SystemFunction.NAK,
109
+ ):
110
+ self.logger.debug("Received blink response")
111
+ self.service_response.success = True
112
+ self.service_response.timestamp = datetime.now()
113
+ self.service_response.serial_number = self.serial_number
114
+ self.service_response.reply_telegram = reply_telegram
115
+
116
+ if self.finish_callback:
117
+ self.finish_callback(self.service_response)
118
+
119
+ def failed(self, message: str) -> None:
120
+ """Handle failed connection event.
121
+
122
+ Args:
123
+ message: Failure message.
124
+ """
125
+ self.logger.debug(f"Failed with message: {message}")
126
+ self.service_response.success = False
127
+ self.service_response.timestamp = datetime.now()
128
+ self.service_response.error = message
129
+ if self.finish_callback:
130
+ self.finish_callback(self.service_response)
131
+
132
+ def send_blink_telegram(
133
+ self,
134
+ serial_number: str,
135
+ on_or_off: str,
136
+ finish_callback: Callable[[ConbusBlinkResponse], None],
137
+ timeout_seconds: Optional[float] = None,
138
+ ) -> None:
139
+ r"""Send blink command to start blinking module LED.
140
+
141
+ Args:
142
+ serial_number: 10-digit module serial number.
143
+ on_or_off: "on" to blink or "off" to unblink.
144
+ finish_callback: Callback function to call when the reply is received.
145
+ timeout_seconds: Timeout in seconds.
146
+
147
+ Examples:
148
+ \b
149
+ xp conbus blink 0012345008 on
150
+ xp conbus blink 0012345008 off
151
+ """
152
+ self.logger.info("Starting send_blink_telegram")
153
+ if timeout_seconds:
154
+ self.timeout_seconds = timeout_seconds
155
+ self.finish_callback = finish_callback
156
+ self.serial_number = serial_number
157
+ self.on_or_off = on_or_off
158
+ self.start_reactor()
@@ -0,0 +1,156 @@
1
+ """Conbus Custom Service for sending custom telegrams to modules.
2
+
3
+ This service handles custom telegram operations 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_custom import ConbusCustomResponse
14
+ from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
15
+ from xp.models.telegram.reply_telegram import ReplyTelegram
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 ConbusCustomService(ConbusProtocol):
23
+ """
24
+ Service for sending custom telegrams to Conbus modules.
25
+
26
+ Uses ConbusProtocol to provide custom telegram functionality
27
+ for sending arbitrary function codes and data to 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 custom 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.function_code: str = ""
47
+ self.data: str = ""
48
+ self.finish_callback: Optional[Callable[[ConbusCustomResponse], None]] = None
49
+ self.service_response: ConbusCustomResponse = ConbusCustomResponse(
50
+ success=False,
51
+ serial_number=self.serial_number,
52
+ )
53
+
54
+ # Set up logging
55
+ self.logger = logging.getLogger(__name__)
56
+
57
+ def connection_established(self) -> None:
58
+ """Handle connection established event."""
59
+ self.logger.debug(
60
+ f"Connection established, sending custom telegram F{self.function_code}D{self.data}."
61
+ )
62
+ system_function = SystemFunction.from_code(self.function_code)
63
+ if not system_function:
64
+ self.logger.debug(f"Invalid function code F{self.function_code}")
65
+ self.failed(f"Invalid function code {self.function_code}")
66
+ return
67
+
68
+ self.send_telegram(
69
+ serial_number=self.serial_number,
70
+ telegram_type=TelegramType.SYSTEM,
71
+ system_function=system_function,
72
+ data_value=self.data,
73
+ )
74
+
75
+ def telegram_sent(self, telegram_sent: str) -> None:
76
+ """Handle telegram sent event.
77
+
78
+ Args:
79
+ telegram_sent: The telegram that was sent.
80
+ """
81
+ self.service_response.sent_telegram = telegram_sent
82
+
83
+ def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
84
+ """Handle telegram received event.
85
+
86
+ Args:
87
+ telegram_received: The telegram received event.
88
+ """
89
+ self.logger.debug(f"Telegram received: {telegram_received}")
90
+ if not self.service_response.received_telegrams:
91
+ self.service_response.received_telegrams = []
92
+ self.service_response.received_telegrams.append(telegram_received.frame)
93
+
94
+ if (
95
+ not telegram_received.checksum_valid
96
+ or telegram_received.telegram_type != TelegramType.REPLY
97
+ or telegram_received.serial_number != self.serial_number
98
+ ):
99
+ self.logger.debug("Not a reply for our serial number")
100
+ return
101
+
102
+ # Parse the reply telegram
103
+ parsed_telegram = self.telegram_service.parse_telegram(telegram_received.frame)
104
+ reply_telegram = None
105
+ if isinstance(parsed_telegram, ReplyTelegram):
106
+ reply_telegram = parsed_telegram
107
+
108
+ self.logger.debug("Received reply telegram")
109
+ self.service_response.success = True
110
+ self.service_response.timestamp = datetime.now()
111
+ self.service_response.serial_number = self.serial_number
112
+ self.service_response.function_code = self.function_code
113
+ self.service_response.data = self.data
114
+ self.service_response.reply_telegram = reply_telegram
115
+
116
+ if self.finish_callback:
117
+ self.finish_callback(self.service_response)
118
+
119
+ def failed(self, message: str) -> None:
120
+ """Handle failed connection event.
121
+
122
+ Args:
123
+ message: Failure message.
124
+ """
125
+ self.logger.debug(f"Failed with message: {message}")
126
+ self.service_response.success = False
127
+ self.service_response.timestamp = datetime.now()
128
+ self.service_response.error = message
129
+ if self.finish_callback:
130
+ self.finish_callback(self.service_response)
131
+
132
+ def send_custom_telegram(
133
+ self,
134
+ serial_number: str,
135
+ function_code: str,
136
+ data: str,
137
+ finish_callback: Callable[[ConbusCustomResponse], None],
138
+ timeout_seconds: Optional[float] = None,
139
+ ) -> None:
140
+ """Send a custom telegram to a module.
141
+
142
+ Args:
143
+ serial_number: 10-digit module serial number.
144
+ function_code: Function code (e.g., "02", "17").
145
+ data: Data code (e.g., "E2", "AA").
146
+ finish_callback: Callback function to call when the reply is received.
147
+ timeout_seconds: Timeout in seconds.
148
+ """
149
+ self.logger.info("Starting send_custom_telegram")
150
+ if timeout_seconds:
151
+ self.timeout_seconds = timeout_seconds
152
+ self.finish_callback = finish_callback
153
+ self.serial_number = serial_number
154
+ self.function_code = function_code
155
+ self.data = data
156
+ self.start_reactor()
@@ -0,0 +1,182 @@
1
+ """Conbus DataPoint Query All Service.
2
+
3
+ This module provides service for querying all datapoint types from a module.
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, ConbusDatapointResponse
13
+ from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
14
+ from xp.models.telegram.datapoint_type import DataPointType
15
+ from xp.models.telegram.reply_telegram import ReplyTelegram
16
+ from xp.models.telegram.system_function import SystemFunction
17
+ from xp.models.telegram.telegram_type import TelegramType
18
+ from xp.services import TelegramService
19
+ from xp.services.protocol import ConbusProtocol
20
+
21
+
22
+ class ConbusDatapointQueryAllService(ConbusProtocol):
23
+ """
24
+ Utility service for querying all datapoints from a module.
25
+
26
+ This service orchestrates multiple ConbusDatapointService calls to query
27
+ all available datapoint types sequentially.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ telegram_service: TelegramService,
33
+ cli_config: ConbusClientConfig,
34
+ reactor: PosixReactorBase,
35
+ ) -> None:
36
+ """Initialize the query all service.
37
+
38
+ Args:
39
+ telegram_service: TelegramService for dependency injection.
40
+ cli_config: ConbusClientConfig for connection settings.
41
+ reactor: PosixReactorBase for async operations.
42
+ """
43
+ super().__init__(cli_config, reactor)
44
+ self.telegram_service = telegram_service
45
+ self.serial_number: str = ""
46
+ self.finish_callback: Optional[Callable[[ConbusDatapointResponse], None]] = None
47
+ self.progress_callback: Optional[Callable[[ReplyTelegram], None]] = None
48
+ self.service_response: ConbusDatapointResponse = ConbusDatapointResponse(
49
+ success=False,
50
+ serial_number=self.serial_number,
51
+ )
52
+ self.datapoint_types = list(DataPointType)
53
+ self.current_index = 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, querying datapoints.")
61
+ self.next_datapoint()
62
+
63
+ def next_datapoint(self) -> bool:
64
+ """Query the next datapoint type.
65
+
66
+ Returns:
67
+ True if there are more datapoints to query, False otherwise.
68
+ """
69
+ self.logger.debug("Querying next datapoint")
70
+
71
+ if self.current_index >= len(self.datapoint_types):
72
+ return False
73
+
74
+ datapoint_type_code = self.datapoint_types[self.current_index]
75
+ datapoint_type = DataPointType(datapoint_type_code)
76
+
77
+ self.logger.debug(f"Datapoint: {datapoint_type}")
78
+ self.send_telegram(
79
+ telegram_type=TelegramType.SYSTEM,
80
+ serial_number=self.serial_number,
81
+ system_function=SystemFunction.READ_DATAPOINT,
82
+ data_value=str(datapoint_type.value),
83
+ )
84
+ self.current_index += 1
85
+ return True
86
+
87
+ def timeout(self) -> bool:
88
+ """Handle timeout event by querying next datapoint.
89
+
90
+ Returns:
91
+ True to continue, False to stop the reactor.
92
+ """
93
+ self.logger.debug("Timeout, querying next datapoint")
94
+ query_next_datapoint = self.next_datapoint()
95
+ if not query_next_datapoint:
96
+ if self.finish_callback:
97
+ self.logger.debug("Received all datapoints telegram")
98
+ self.service_response.success = True
99
+ self.service_response.timestamp = datetime.now()
100
+ self.service_response.serial_number = self.serial_number
101
+ self.service_response.system_function = SystemFunction.READ_DATAPOINT
102
+ self.finish_callback(self.service_response)
103
+ return False
104
+ return True
105
+
106
+ def telegram_sent(self, telegram_sent: str) -> None:
107
+ """Handle telegram sent event.
108
+
109
+ Args:
110
+ telegram_sent: The telegram that was sent.
111
+ """
112
+ self.service_response.sent_telegram = telegram_sent
113
+
114
+ def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
115
+ """Handle telegram received event.
116
+
117
+ Args:
118
+ telegram_received: The telegram received event.
119
+ """
120
+ self.logger.debug(f"Telegram received: {telegram_received}")
121
+ if not self.service_response.received_telegrams:
122
+ self.service_response.received_telegrams = []
123
+ self.service_response.received_telegrams.append(telegram_received.frame)
124
+
125
+ if (
126
+ not telegram_received.checksum_valid
127
+ or telegram_received.telegram_type != TelegramType.REPLY
128
+ or telegram_received.serial_number != self.serial_number
129
+ ):
130
+ self.logger.debug("Not a reply for our serial number")
131
+ return
132
+
133
+ # Parse the reply telegram
134
+ datapoint_telegram = self.telegram_service.parse_reply_telegram(
135
+ telegram_received.frame
136
+ )
137
+ if (
138
+ not datapoint_telegram
139
+ or datapoint_telegram.system_function != SystemFunction.READ_DATAPOINT
140
+ ):
141
+ self.logger.debug("Not a reply for our datapoint type")
142
+ return
143
+
144
+ self.logger.debug("Received a datapoint telegram")
145
+ if self.progress_callback:
146
+ self.progress_callback(datapoint_telegram)
147
+
148
+ def failed(self, message: str) -> None:
149
+ """Handle failed connection event.
150
+
151
+ Args:
152
+ message: Failure message.
153
+ """
154
+ self.logger.debug(f"Failed with message: {message}")
155
+ self.service_response.success = False
156
+ self.service_response.timestamp = datetime.now()
157
+ self.service_response.error = message
158
+ if self.finish_callback:
159
+ self.finish_callback(self.service_response)
160
+
161
+ def query_all_datapoints(
162
+ self,
163
+ serial_number: str,
164
+ finish_callback: Callable[[ConbusDatapointResponse], None],
165
+ progress_callback: Callable[[ReplyTelegram], None],
166
+ timeout_seconds: Optional[float] = None,
167
+ ) -> None:
168
+ """Query all datapoints from a module.
169
+
170
+ Args:
171
+ serial_number: 10-digit module serial number.
172
+ finish_callback: Callback function to call when all datapoints are received.
173
+ progress_callback: Callback function to call when each datapoint is received.
174
+ timeout_seconds: Timeout in seconds.
175
+ """
176
+ self.logger.info("Starting query_all_datapoints")
177
+ if timeout_seconds:
178
+ self.timeout_seconds = timeout_seconds
179
+ self.finish_callback = finish_callback
180
+ self.progress_callback = progress_callback
181
+ self.serial_number = serial_number
182
+ self.start_reactor()
@@ -0,0 +1,170 @@
1
+ """Conbus Datapoint Service for querying module datapoints.
2
+
3
+ This service handles datapoint query operations 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, ConbusDatapointResponse
13
+ from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
14
+ from xp.models.telegram.datapoint_type import DataPointType
15
+ from xp.models.telegram.reply_telegram import ReplyTelegram
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 ConbusDatapointService(ConbusProtocol):
23
+ """
24
+ Service for querying datapoints from Conbus modules.
25
+
26
+ Uses ConbusProtocol to provide datapoint query functionality
27
+ for reading sensor data and module information.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ telegram_service: TelegramService,
33
+ cli_config: ConbusClientConfig,
34
+ reactor: PosixReactorBase,
35
+ ) -> None:
36
+ """Initialize the Conbus datapoint 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.datapoint_type: Optional[DataPointType] = None
47
+ self.datapoint_finished_callback: Optional[
48
+ Callable[[ConbusDatapointResponse], None]
49
+ ] = None
50
+ self.service_response: ConbusDatapointResponse = ConbusDatapointResponse(
51
+ success=False,
52
+ serial_number=self.serial_number,
53
+ )
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(
61
+ f"Connection established, querying datapoint {self.datapoint_type}."
62
+ )
63
+ if self.datapoint_type is None:
64
+ self.failed("Datapoint type not set")
65
+ return
66
+
67
+ self.send_telegram(
68
+ telegram_type=TelegramType.SYSTEM,
69
+ serial_number=self.serial_number,
70
+ system_function=SystemFunction.READ_DATAPOINT,
71
+ data_value=str(self.datapoint_type.value),
72
+ )
73
+
74
+ def telegram_sent(self, telegram_sent: str) -> None:
75
+ """Handle telegram sent event.
76
+
77
+ Args:
78
+ telegram_sent: The telegram that was sent.
79
+ """
80
+ self.service_response.sent_telegram = telegram_sent
81
+
82
+ def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
83
+ """Handle telegram received event.
84
+
85
+ Args:
86
+ telegram_received: The telegram received event.
87
+ """
88
+ self.logger.debug(f"Telegram received: {telegram_received}")
89
+ if not self.service_response.received_telegrams:
90
+ self.service_response.received_telegrams = []
91
+ self.service_response.received_telegrams.append(telegram_received.frame)
92
+
93
+ if (
94
+ not telegram_received.checksum_valid
95
+ or telegram_received.telegram_type != TelegramType.REPLY
96
+ or telegram_received.serial_number != self.serial_number
97
+ ):
98
+ self.logger.debug("Not a reply for our serial number")
99
+ return
100
+
101
+ # Parse the reply telegram
102
+ datapoint_telegram = self.telegram_service.parse_reply_telegram(
103
+ telegram_received.frame
104
+ )
105
+
106
+ if (
107
+ not datapoint_telegram
108
+ or datapoint_telegram.system_function != SystemFunction.READ_DATAPOINT
109
+ or datapoint_telegram.datapoint_type != self.datapoint_type
110
+ ):
111
+ self.logger.debug("Not a reply for our datapoint type")
112
+ return
113
+
114
+ self.logger.debug("Received datapoint telegram")
115
+ self.succeed(datapoint_telegram)
116
+
117
+ def succeed(self, datapoint_telegram: ReplyTelegram) -> None:
118
+ """Handle successful datapoint query.
119
+
120
+ Args:
121
+ datapoint_telegram: The parsed datapoint telegram.
122
+ """
123
+ self.logger.debug("Succeed querying datapoint")
124
+ self.service_response.success = True
125
+ self.service_response.timestamp = datetime.now()
126
+ self.service_response.serial_number = self.serial_number
127
+ self.service_response.system_function = SystemFunction.READ_DATAPOINT
128
+ self.service_response.datapoint_type = self.datapoint_type
129
+ self.service_response.datapoint_telegram = datapoint_telegram
130
+ self.service_response.data_value = datapoint_telegram.data_value
131
+ if self.datapoint_finished_callback:
132
+ self.datapoint_finished_callback(self.service_response)
133
+ self._stop_reactor()
134
+
135
+ def failed(self, message: str) -> None:
136
+ """Handle failed connection event.
137
+
138
+ Args:
139
+ message: Failure message.
140
+ """
141
+ self.logger.debug(f"Failed with message: {message}")
142
+ self.service_response.success = False
143
+ self.service_response.timestamp = datetime.now()
144
+ self.service_response.serial_number = self.serial_number
145
+ self.service_response.error = message
146
+ if self.datapoint_finished_callback:
147
+ self.datapoint_finished_callback(self.service_response)
148
+
149
+ def query_datapoint(
150
+ self,
151
+ serial_number: str,
152
+ datapoint_type: DataPointType,
153
+ finish_callback: Callable[[ConbusDatapointResponse], None],
154
+ timeout_seconds: Optional[float] = None,
155
+ ) -> None:
156
+ """Query a specific datapoint from a module.
157
+
158
+ Args:
159
+ serial_number: 10-digit module serial number.
160
+ datapoint_type: Type of datapoint to query.
161
+ finish_callback: Callback function to call when the datapoint is received.
162
+ timeout_seconds: Timeout in seconds.
163
+ """
164
+ self.logger.info("Starting query_datapoint")
165
+ if timeout_seconds:
166
+ self.timeout_seconds = timeout_seconds
167
+ self.datapoint_finished_callback = finish_callback
168
+ self.serial_number = serial_number
169
+ self.datapoint_type = datapoint_type
170
+ self.start_reactor()