conson-xp 1.34.0__py3-none-any.whl → 1.35.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 (29) hide show
  1. {conson_xp-1.34.0.dist-info → conson_xp-1.35.0.dist-info}/METADATA +1 -1
  2. {conson_xp-1.34.0.dist-info → conson_xp-1.35.0.dist-info}/RECORD +29 -29
  3. xp/__init__.py +1 -1
  4. xp/cli/commands/conbus/conbus_actiontable_commands.py +34 -35
  5. xp/cli/commands/conbus/conbus_autoreport_commands.py +11 -10
  6. xp/cli/commands/conbus/conbus_custom_commands.py +6 -4
  7. xp/cli/commands/conbus/conbus_datapoint_commands.py +8 -6
  8. xp/cli/commands/conbus/conbus_lightlevel_commands.py +19 -16
  9. xp/cli/commands/conbus/conbus_linknumber_commands.py +7 -6
  10. xp/cli/commands/conbus/conbus_modulenumber_commands.py +7 -6
  11. xp/cli/commands/conbus/conbus_msactiontable_commands.py +7 -9
  12. xp/cli/commands/conbus/conbus_output_commands.py +9 -7
  13. xp/cli/commands/conbus/conbus_raw_commands.py +7 -2
  14. xp/cli/commands/conbus/conbus_scan_commands.py +4 -2
  15. xp/services/conbus/actiontable/actiontable_download_service.py +79 -37
  16. xp/services/conbus/actiontable/actiontable_list_service.py +17 -17
  17. xp/services/conbus/actiontable/actiontable_upload_service.py +78 -36
  18. xp/services/conbus/actiontable/msactiontable_service.py +88 -48
  19. xp/services/conbus/conbus_custom_service.py +81 -26
  20. xp/services/conbus/conbus_datapoint_queryall_service.py +90 -43
  21. xp/services/conbus/conbus_datapoint_service.py +76 -28
  22. xp/services/conbus/conbus_output_service.py +82 -22
  23. xp/services/conbus/conbus_raw_service.py +78 -37
  24. xp/services/conbus/conbus_scan_service.py +86 -42
  25. xp/services/conbus/write_config_service.py +76 -26
  26. xp/utils/dependencies.py +10 -20
  27. {conson_xp-1.34.0.dist-info → conson_xp-1.35.0.dist-info}/WHEEL +0 -0
  28. {conson_xp-1.34.0.dist-info → conson_xp-1.35.0.dist-info}/entry_points.txt +0 -0
  29. {conson_xp-1.34.0.dist-info → conson_xp-1.35.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,48 +5,57 @@ This service handles datapoint query operations for modules through Conbus teleg
5
5
 
6
6
  import logging
7
7
  from datetime import datetime
8
- from typing import Callable, Optional
8
+ from typing import Any, Optional
9
9
 
10
- from twisted.internet.posixbase import PosixReactorBase
10
+ from psygnal import Signal
11
11
 
12
- from xp.models import ConbusClientConfig, ConbusDatapointResponse
12
+ from xp.models import ConbusDatapointResponse
13
13
  from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
14
14
  from xp.models.telegram.datapoint_type import DataPointType
15
15
  from xp.models.telegram.reply_telegram import ReplyTelegram
16
16
  from xp.models.telegram.system_function import SystemFunction
17
17
  from xp.models.telegram.telegram_type import TelegramType
18
- from xp.services.protocol import ConbusProtocol
18
+ from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
19
19
  from xp.services.telegram.telegram_service import TelegramService
20
20
 
21
21
 
22
- class ConbusDatapointService(ConbusProtocol):
23
- """
24
- Service for querying datapoints from Conbus modules.
22
+ class ConbusDatapointService:
23
+ """Service for querying datapoints from Conbus modules.
25
24
 
26
- Uses ConbusProtocol to provide datapoint query functionality
25
+ Uses ConbusEventProtocol to provide datapoint query functionality
27
26
  for reading sensor data and module information.
27
+
28
+ Attributes:
29
+ conbus_protocol: Protocol instance for Conbus communication.
30
+ telegram_service: Service for parsing telegrams.
31
+ on_finish: Signal emitted when datapoint query completes (with response).
28
32
  """
29
33
 
34
+ on_finish: Signal = Signal(ConbusDatapointResponse)
35
+
30
36
  def __init__(
31
37
  self,
38
+ conbus_protocol: ConbusEventProtocol,
32
39
  telegram_service: TelegramService,
33
- cli_config: ConbusClientConfig,
34
- reactor: PosixReactorBase,
35
40
  ) -> None:
36
41
  """Initialize the Conbus datapoint service.
37
42
 
38
43
  Args:
44
+ conbus_protocol: Protocol instance for Conbus communication.
39
45
  telegram_service: Service for parsing telegrams.
40
- cli_config: Configuration for Conbus client connection.
41
- reactor: Twisted reactor for event loop.
42
46
  """
43
- super().__init__(cli_config, reactor)
47
+ self.conbus_protocol = conbus_protocol
44
48
  self.telegram_service = telegram_service
49
+
50
+ # Connect protocol signals
51
+ self.conbus_protocol.on_connection_made.connect(self.connection_made)
52
+ self.conbus_protocol.on_telegram_sent.connect(self.telegram_sent)
53
+ self.conbus_protocol.on_telegram_received.connect(self.telegram_received)
54
+ self.conbus_protocol.on_timeout.connect(self.timeout)
55
+ self.conbus_protocol.on_failed.connect(self.failed)
56
+
45
57
  self.serial_number: str = ""
46
58
  self.datapoint_type: Optional[DataPointType] = None
47
- self.datapoint_finished_callback: Optional[
48
- Callable[[ConbusDatapointResponse], None]
49
- ] = None
50
59
  self.service_response: ConbusDatapointResponse = ConbusDatapointResponse(
51
60
  success=False,
52
61
  serial_number=self.serial_number,
@@ -55,7 +64,7 @@ class ConbusDatapointService(ConbusProtocol):
55
64
  # Set up logging
56
65
  self.logger = logging.getLogger(__name__)
57
66
 
58
- def connection_established(self) -> None:
67
+ def connection_made(self) -> None:
59
68
  """Handle connection established event."""
60
69
  self.logger.debug(
61
70
  f"Connection established, querying datapoint {self.datapoint_type}."
@@ -64,7 +73,7 @@ class ConbusDatapointService(ConbusProtocol):
64
73
  self.failed("Datapoint type not set")
65
74
  return
66
75
 
67
- self.send_telegram(
76
+ self.conbus_protocol.send_telegram(
68
77
  telegram_type=TelegramType.SYSTEM,
69
78
  serial_number=self.serial_number,
70
79
  system_function=SystemFunction.READ_DATAPOINT,
@@ -128,9 +137,14 @@ class ConbusDatapointService(ConbusProtocol):
128
137
  self.service_response.datapoint_type = self.datapoint_type
129
138
  self.service_response.datapoint_telegram = datapoint_telegram
130
139
  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()
140
+
141
+ # Emit finish signal
142
+ self.on_finish.emit(self.service_response)
143
+ self.stop_reactor()
144
+
145
+ def timeout(self) -> None:
146
+ """Handle timeout event."""
147
+ self.failed("Timeout")
134
148
 
135
149
  def failed(self, message: str) -> None:
136
150
  """Handle failed connection event.
@@ -143,14 +157,14 @@ class ConbusDatapointService(ConbusProtocol):
143
157
  self.service_response.timestamp = datetime.now()
144
158
  self.service_response.serial_number = self.serial_number
145
159
  self.service_response.error = message
146
- if self.datapoint_finished_callback:
147
- self.datapoint_finished_callback(self.service_response)
160
+
161
+ # Emit finish signal
162
+ self.on_finish.emit(self.service_response)
148
163
 
149
164
  def query_datapoint(
150
165
  self,
151
166
  serial_number: str,
152
167
  datapoint_type: DataPointType,
153
- finish_callback: Callable[[ConbusDatapointResponse], None],
154
168
  timeout_seconds: Optional[float] = None,
155
169
  ) -> None:
156
170
  """Query a specific datapoint from a module.
@@ -158,13 +172,47 @@ class ConbusDatapointService(ConbusProtocol):
158
172
  Args:
159
173
  serial_number: 10-digit module serial number.
160
174
  datapoint_type: Type of datapoint to query.
161
- finish_callback: Callback function to call when the datapoint is received.
162
175
  timeout_seconds: Timeout in seconds.
163
176
  """
164
177
  self.logger.info("Starting query_datapoint")
165
178
  if timeout_seconds:
166
- self.timeout_seconds = timeout_seconds
167
- self.datapoint_finished_callback = finish_callback
179
+ self.conbus_protocol.timeout_seconds = timeout_seconds
168
180
  self.serial_number = serial_number
169
181
  self.datapoint_type = datapoint_type
170
- self.start_reactor()
182
+
183
+ def set_timeout(self, timeout_seconds: float) -> None:
184
+ """Set operation timeout.
185
+
186
+ Args:
187
+ timeout_seconds: Timeout in seconds.
188
+ """
189
+ self.conbus_protocol.timeout_seconds = timeout_seconds
190
+
191
+ def start_reactor(self) -> None:
192
+ """Start the reactor."""
193
+ self.conbus_protocol.start_reactor()
194
+
195
+ def stop_reactor(self) -> None:
196
+ """Stop the reactor."""
197
+ self.conbus_protocol.stop_reactor()
198
+
199
+ def __enter__(self) -> "ConbusDatapointService":
200
+ """Enter context manager - reset state for singleton reuse.
201
+
202
+ Returns:
203
+ Self for context manager protocol.
204
+ """
205
+ self.datapoint_response = ConbusDatapointResponse(success=False)
206
+ return self
207
+
208
+ def __exit__(
209
+ self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
210
+ ) -> None:
211
+ """Exit context manager and disconnect signals."""
212
+ self.conbus_protocol.on_connection_made.disconnect(self.connection_made)
213
+ self.conbus_protocol.on_telegram_sent.disconnect(self.telegram_sent)
214
+ self.conbus_protocol.on_telegram_received.disconnect(self.telegram_received)
215
+ self.conbus_protocol.on_timeout.disconnect(self.timeout)
216
+ self.conbus_protocol.on_failed.disconnect(self.failed)
217
+ self.on_finish.disconnect()
218
+ self.stop_reactor()
@@ -6,18 +6,17 @@ and processing ACK/NAK responses.
6
6
 
7
7
  import logging
8
8
  from datetime import datetime
9
- from typing import Callable, Optional
9
+ from typing import Any, Optional
10
10
 
11
- from twisted.internet.posixbase import PosixReactorBase
11
+ from psygnal import Signal
12
12
 
13
- from xp.models import ConbusClientConfig
14
13
  from xp.models.conbus.conbus_output import ConbusOutputResponse
15
14
  from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
16
15
  from xp.models.telegram.action_type import ActionType
17
16
  from xp.models.telegram.output_telegram import OutputTelegram
18
17
  from xp.models.telegram.system_function import SystemFunction
19
18
  from xp.models.telegram.telegram_type import TelegramType
20
- from xp.services.protocol import ConbusProtocol
19
+ from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
21
20
  from xp.services.telegram.telegram_output_service import (
22
21
  TelegramOutputService,
23
22
  XPOutputError,
@@ -30,33 +29,47 @@ class ConbusOutputError(Exception):
30
29
  pass
31
30
 
32
31
 
33
- class ConbusOutputService(ConbusProtocol):
32
+ class ConbusOutputService:
34
33
  """
35
34
  Service for sending action telegrams to Conbus module outputs.
36
35
 
37
36
  Manages action telegram transmission (ON/OFF) and processes
38
37
  ACK/NAK responses from modules.
38
+
39
+ Attributes:
40
+ conbus_protocol: Protocol instance for Conbus communication.
41
+ on_finish: Signal emitted when operation finishes (with result).
39
42
  """
40
43
 
44
+ conbus_protocol: ConbusEventProtocol
45
+ on_finish: Signal = Signal(ConbusOutputResponse)
46
+
41
47
  def __init__(
42
48
  self,
49
+ conbus_protocol: ConbusEventProtocol,
43
50
  telegram_output_service: TelegramOutputService,
44
- cli_config: ConbusClientConfig,
45
- reactor: PosixReactorBase,
46
51
  ):
47
52
  """Initialize the Conbus output service.
48
53
 
49
54
  Args:
55
+ conbus_protocol: ConbusEventProtocol for communication.
50
56
  telegram_output_service: TelegramOutputService for telegram generation/parsing.
51
- cli_config: Conbus client configuration.
52
- reactor: Twisted reactor for async operations.
53
57
  """
54
- super().__init__(cli_config, reactor)
58
+ self.conbus_protocol = conbus_protocol
55
59
  self.telegram_output_service = telegram_output_service
60
+
61
+ # Connect protocol signals
62
+ self.conbus_protocol.on_connection_made.connect(self.connection_made)
63
+ self.conbus_protocol.on_telegram_sent.connect(self.telegram_sent)
64
+ self.conbus_protocol.on_telegram_received.connect(self.telegram_received)
65
+ self.conbus_protocol.on_timeout.connect(self.timeout)
66
+ self.conbus_protocol.on_failed.connect(self.failed)
67
+
68
+ # Initialize state
56
69
  self.serial_number: str = ""
57
70
  self.output_number: int = 0
58
71
  self.action_type: ActionType = ActionType.ON_RELEASE
59
- self.finish_callback: Optional[Callable[[ConbusOutputResponse], None]] = None
72
+ self.output_state: str = ""
60
73
  self.service_response: ConbusOutputResponse = ConbusOutputResponse(
61
74
  success=False,
62
75
  serial_number=self.serial_number,
@@ -68,7 +81,7 @@ class ConbusOutputService(ConbusProtocol):
68
81
  # Set up logging
69
82
  self.logger = logging.getLogger(__name__)
70
83
 
71
- def connection_established(self) -> None:
84
+ def connection_made(self) -> None:
72
85
  """Handle connection established event."""
73
86
  self.logger.debug(
74
87
  f"Connection established, sending action {self.action_type} to output {self.output_number}."
@@ -84,7 +97,7 @@ class ConbusOutputService(ConbusProtocol):
84
97
 
85
98
  # Send F27D{output:02d}{action} telegram
86
99
  # F27 = ACTION, D = data with output number and action type
87
- self.send_telegram(
100
+ self.conbus_protocol.send_telegram(
88
101
  telegram_type=TelegramType.SYSTEM,
89
102
  serial_number=self.serial_number,
90
103
  system_function=SystemFunction.ACTION,
@@ -148,8 +161,12 @@ class ConbusOutputService(ConbusProtocol):
148
161
  self.service_response.output_number = self.output_number
149
162
  self.service_response.action_type = self.action_type
150
163
  self.service_response.output_telegram = output_telegram
151
- if self.finish_callback:
152
- self.finish_callback(self.service_response)
164
+ self.on_finish.emit(self.service_response)
165
+
166
+ def timeout(self) -> None:
167
+ """Handle timeout event."""
168
+ self.logger.debug("Timeout occurred")
169
+ self.failed("Timeout")
153
170
 
154
171
  def failed(self, message: str) -> None:
155
172
  """Handle failed connection event.
@@ -164,15 +181,13 @@ class ConbusOutputService(ConbusProtocol):
164
181
  self.service_response.output_number = self.output_number
165
182
  self.service_response.action_type = self.action_type
166
183
  self.service_response.error = message
167
- if self.finish_callback:
168
- self.finish_callback(self.service_response)
184
+ self.on_finish.emit(self.service_response)
169
185
 
170
186
  def send_action(
171
187
  self,
172
188
  serial_number: str,
173
189
  output_number: int,
174
190
  action_type: ActionType,
175
- finish_callback: Callable[[ConbusOutputResponse], None],
176
191
  timeout_seconds: Optional[float] = None,
177
192
  ) -> None:
178
193
  """Send an action telegram to a module output.
@@ -181,14 +196,59 @@ class ConbusOutputService(ConbusProtocol):
181
196
  serial_number: 10-digit module serial number.
182
197
  output_number: Output number (0-99).
183
198
  action_type: Action to perform (ON_RELEASE, OFF_PRESS, etc.).
184
- finish_callback: Callback function to call when operation completes.
185
199
  timeout_seconds: Optional timeout in seconds.
186
200
  """
187
201
  self.logger.info("Starting send_action")
188
202
  if timeout_seconds:
189
- self.timeout_seconds = timeout_seconds
203
+ self.conbus_protocol.timeout_seconds = timeout_seconds
190
204
  self.serial_number = serial_number
191
205
  self.output_number = output_number
192
206
  self.action_type = action_type
193
- self.finish_callback = finish_callback
194
- self.start_reactor()
207
+
208
+ def set_timeout(self, timeout_seconds: float) -> None:
209
+ """Set operation timeout.
210
+
211
+ Args:
212
+ timeout_seconds: Timeout in seconds.
213
+ """
214
+ self.conbus_protocol.timeout_seconds = timeout_seconds
215
+
216
+ def start_reactor(self) -> None:
217
+ """Start the reactor."""
218
+ self.conbus_protocol.start_reactor()
219
+
220
+ def stop_reactor(self) -> None:
221
+ """Stop the reactor."""
222
+ self.conbus_protocol.stop_reactor()
223
+
224
+ def __enter__(self) -> "ConbusOutputService":
225
+ """Enter context manager - reset state for singleton reuse.
226
+
227
+ Returns:
228
+ Self for context manager protocol.
229
+ """
230
+ # Reset state for singleton reuse
231
+ self.service_response = ConbusOutputResponse(
232
+ success=False,
233
+ serial_number="",
234
+ output_number=0,
235
+ action_type=ActionType.ON_RELEASE,
236
+ timestamp=datetime.now(),
237
+ )
238
+ self.output_state = ""
239
+ return self
240
+
241
+ def __exit__(
242
+ self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
243
+ ) -> None:
244
+ """Exit context manager and disconnect signals."""
245
+ # Disconnect protocol signals
246
+ self.conbus_protocol.on_connection_made.disconnect(self.connection_made)
247
+ self.conbus_protocol.on_telegram_sent.disconnect(self.telegram_sent)
248
+ self.conbus_protocol.on_telegram_received.disconnect(self.telegram_received)
249
+ self.conbus_protocol.on_timeout.disconnect(self.timeout)
250
+ self.conbus_protocol.on_failed.disconnect(self.failed)
251
+ # Disconnect service signals
252
+ self.on_finish.disconnect()
253
+ # Stop reactor
254
+ self.stop_reactor()
@@ -5,49 +5,61 @@ This service handles sending raw telegram strings without prior validation.
5
5
 
6
6
  import logging
7
7
  from datetime import datetime
8
- from typing import Callable, Optional
8
+ from typing import Any, Optional
9
9
 
10
- from twisted.internet.posixbase import PosixReactorBase
10
+ from psygnal import Signal
11
11
 
12
- from xp.models import ConbusClientConfig
13
12
  from xp.models.conbus.conbus_raw import ConbusRawResponse
14
13
  from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
15
- from xp.services.protocol import ConbusProtocol
14
+ from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
16
15
 
17
16
 
18
- class ConbusRawService(ConbusProtocol):
17
+ class ConbusRawService:
19
18
  """
20
19
  Service for sending raw telegram sequences to Conbus modules.
21
20
 
22
- Uses ConbusProtocol to provide raw telegram functionality
21
+ Uses ConbusEventProtocol to provide raw telegram functionality
23
22
  for sending arbitrary telegram strings without validation.
23
+
24
+ Attributes:
25
+ conbus_protocol: Protocol instance for Conbus communication.
26
+ on_progress: Signal emitted when telegram is received (with frame).
27
+ on_finish: Signal emitted when operation finishes (with result).
24
28
  """
25
29
 
30
+ conbus_protocol: ConbusEventProtocol
31
+ on_progress: Signal = Signal(str)
32
+ on_finish: Signal = Signal(ConbusRawResponse)
33
+
26
34
  def __init__(
27
35
  self,
28
- cli_config: ConbusClientConfig,
29
- reactor: PosixReactorBase,
36
+ conbus_protocol: ConbusEventProtocol,
30
37
  ) -> None:
31
38
  """Initialize the Conbus raw service.
32
39
 
33
40
  Args:
34
- cli_config: Configuration for Conbus client connection.
35
- reactor: Twisted reactor for event loop.
41
+ conbus_protocol: ConbusEventProtocol instance.
36
42
  """
37
- super().__init__(cli_config, reactor)
43
+ self.conbus_protocol = conbus_protocol
44
+
45
+ # Connect protocol signals
46
+ self.conbus_protocol.on_connection_made.connect(self.connection_made)
47
+ self.conbus_protocol.on_telegram_sent.connect(self.telegram_sent)
48
+ self.conbus_protocol.on_telegram_received.connect(self.telegram_received)
49
+ self.conbus_protocol.on_timeout.connect(self.timeout)
50
+ self.conbus_protocol.on_failed.connect(self.failed)
51
+
38
52
  self.raw_input: str = ""
39
- self.progress_callback: Optional[Callable[[str], None]] = None
40
- self.finish_callback: Optional[Callable[[ConbusRawResponse], None]] = None
41
53
  self.service_response: ConbusRawResponse = ConbusRawResponse(
42
54
  success=False,
43
55
  )
44
56
  # Set up logging
45
57
  self.logger = logging.getLogger(__name__)
46
58
 
47
- def connection_established(self) -> None:
59
+ def connection_made(self) -> None:
48
60
  """Handle connection established event."""
49
61
  self.logger.debug(f"Connection established, sending {self.raw_input}")
50
- self.sendFrame(self.raw_input.encode())
62
+ self.conbus_protocol.send_raw_telegram(self.raw_input)
51
63
 
52
64
  def telegram_sent(self, telegram_sent: str) -> None:
53
65
  """Handle telegram sent event.
@@ -71,19 +83,13 @@ class ConbusRawService(ConbusProtocol):
71
83
  self.service_response.received_telegrams = []
72
84
  self.service_response.received_telegrams.append(telegram_received.frame)
73
85
 
74
- if self.progress_callback:
75
- self.progress_callback(telegram_received.frame)
76
-
77
- def timeout(self) -> bool:
78
- """Handle timeout event.
86
+ self.on_progress.emit(telegram_received.frame)
79
87
 
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
88
+ def timeout(self) -> None:
89
+ """Handle timeout event."""
90
+ timeout_seconds = self.conbus_protocol.timeout_seconds
91
+ self.logger.debug(f"Timeout: {timeout_seconds}s")
92
+ self.on_finish.emit(self.service_response)
87
93
 
88
94
  def failed(self, message: str) -> None:
89
95
  """Handle failed connection event.
@@ -95,28 +101,63 @@ class ConbusRawService(ConbusProtocol):
95
101
  self.service_response.success = False
96
102
  self.service_response.timestamp = datetime.now()
97
103
  self.service_response.error = message
98
- if self.finish_callback:
99
- self.finish_callback(self.service_response)
104
+ self.on_finish.emit(self.service_response)
100
105
 
101
106
  def send_raw_telegram(
102
107
  self,
103
108
  raw_input: str,
104
- progress_callback: Callable[[str], None],
105
- finish_callback: Callable[[ConbusRawResponse], None],
106
109
  timeout_seconds: Optional[float] = None,
107
110
  ) -> None:
108
111
  """Send a raw telegram string to the Conbus server.
109
112
 
110
113
  Args:
111
114
  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
115
  timeout_seconds: Timeout in seconds.
115
116
  """
116
117
  self.logger.info("Starting send_raw_telegram")
117
118
  if timeout_seconds:
118
- self.timeout_seconds = timeout_seconds
119
- self.progress_callback = progress_callback
120
- self.finish_callback = finish_callback
119
+ self.conbus_protocol.timeout_seconds = timeout_seconds
121
120
  self.raw_input = raw_input
122
- self.start_reactor()
121
+
122
+ def set_timeout(self, timeout_seconds: float) -> None:
123
+ """Set operation timeout.
124
+
125
+ Args:
126
+ timeout_seconds: Timeout in seconds.
127
+ """
128
+ self.conbus_protocol.timeout_seconds = timeout_seconds
129
+
130
+ def start_reactor(self) -> None:
131
+ """Start the reactor."""
132
+ self.conbus_protocol.start_reactor()
133
+
134
+ def stop_reactor(self) -> None:
135
+ """Stop the reactor."""
136
+ self.conbus_protocol.stop_reactor()
137
+
138
+ def __enter__(self) -> "ConbusRawService":
139
+ """Enter context manager.
140
+
141
+ Returns:
142
+ Self for context manager protocol.
143
+ """
144
+ # Reset state for singleton reuse
145
+ self.service_response = ConbusRawResponse(success=False)
146
+ self.raw_input = ""
147
+ return self
148
+
149
+ def __exit__(
150
+ self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
151
+ ) -> None:
152
+ """Exit context manager and disconnect signals."""
153
+ # Disconnect protocol signals
154
+ self.conbus_protocol.on_connection_made.disconnect(self.connection_made)
155
+ self.conbus_protocol.on_telegram_sent.disconnect(self.telegram_sent)
156
+ self.conbus_protocol.on_telegram_received.disconnect(self.telegram_received)
157
+ self.conbus_protocol.on_timeout.disconnect(self.timeout)
158
+ self.conbus_protocol.on_failed.disconnect(self.failed)
159
+ # Disconnect service signals
160
+ self.on_progress.disconnect()
161
+ self.on_finish.disconnect()
162
+ # Stop reactor
163
+ self.stop_reactor()