conson-xp 0.11.21__py3-none-any.whl → 1.0.1__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.
@@ -9,18 +9,14 @@ from typing import Callable, Optional
9
9
 
10
10
  from twisted.internet.posixbase import PosixReactorBase
11
11
 
12
- from xp.models import ConbusClientConfig
12
+ from xp.models import ConbusClientConfig, ConbusDatapointResponse
13
13
  from xp.models.conbus.conbus_lightlevel import ConbusLightlevelResponse
14
- from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
15
14
  from xp.models.telegram.datapoint_type import DataPointType
16
- from xp.models.telegram.reply_telegram import ReplyTelegram
17
- from xp.models.telegram.system_function import SystemFunction
18
- from xp.models.telegram.telegram_type import TelegramType
19
- from xp.services.protocol import ConbusProtocol
15
+ from xp.services.conbus.conbus_datapoint_service import ConbusDatapointService
20
16
  from xp.services.telegram.telegram_service import TelegramService
21
17
 
22
18
 
23
- class ConbusLightlevelGetService(ConbusProtocol):
19
+ class ConbusLightlevelGetService(ConbusDatapointService):
24
20
  """
25
21
  Service for receiving telegrams from Conbus servers.
26
22
 
@@ -35,93 +31,46 @@ class ConbusLightlevelGetService(ConbusProtocol):
35
31
  reactor: PosixReactorBase,
36
32
  ) -> None:
37
33
  """Initialize the Conbus client send service"""
38
- super().__init__(cli_config, reactor)
39
- self.telegram_service = telegram_service
40
- self.serial_number: str = ""
34
+ super().__init__(telegram_service, cli_config, reactor)
41
35
  self.output_number: int = 0
42
- self.finish_callback: Optional[Callable[[ConbusLightlevelResponse], None]] = (
36
+ self.service_callback: Optional[Callable[[ConbusLightlevelResponse], None]] = (
43
37
  None
44
38
  )
45
- self.service_response: ConbusLightlevelResponse = ConbusLightlevelResponse(
46
- success=False,
47
- serial_number=self.serial_number,
48
- output_number=self.output_number,
49
- level=0,
50
- timestamp=datetime.now(),
51
- )
52
39
 
53
40
  # Set up logging
54
41
  self.logger = logging.getLogger(__name__)
55
42
 
56
- def connection_established(self) -> None:
57
- self.logger.debug("Connection established, retrieving lightlevel status...")
58
- self.send_telegram(
59
- telegram_type=TelegramType.SYSTEM,
60
- serial_number=self.serial_number,
61
- system_function=SystemFunction.READ_DATAPOINT,
62
- data_value=str(DataPointType.MODULE_LIGHT_LEVEL.value),
63
- )
64
-
65
- def telegram_sent(self, telegram_sent: str) -> None:
66
- self.service_response.sent_telegram = telegram_sent
67
-
68
- def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
69
-
70
- self.logger.debug(f"Telegram received: {telegram_received}")
71
- if not self.service_response.received_telegrams:
72
- self.service_response.received_telegrams = []
73
- self.service_response.received_telegrams.append(telegram_received.frame)
43
+ def finish_service_callback(
44
+ self, datapoint_response: ConbusDatapointResponse
45
+ ) -> None:
74
46
 
75
- if (
76
- not telegram_received.checksum_valid
77
- or telegram_received.telegram_type != TelegramType.REPLY
78
- or telegram_received.serial_number != self.serial_number
79
- ):
80
- self.logger.debug("Not a reply")
81
- return
47
+ self.logger.debug("Parsing datapoint response")
82
48
 
83
- reply_telegram = self.telegram_service.parse_reply_telegram(
84
- telegram_received.frame
49
+ level = 0
50
+ if datapoint_response.success and datapoint_response.datapoint_telegram:
51
+ for output_data in datapoint_response.datapoint_telegram.data_value.split(
52
+ ","
53
+ ):
54
+ if ":" in output_data:
55
+ output_str, level_str = output_data.split(":")
56
+ if int(output_str) == self.output_number:
57
+ level_str = level_str.replace("[%]", "")
58
+ level = int(level_str)
59
+ break
60
+
61
+ service_response = ConbusLightlevelResponse(
62
+ success=datapoint_response.success,
63
+ serial_number=self.serial_number,
64
+ output_number=self.output_number,
65
+ level=level,
66
+ error=datapoint_response.error,
67
+ sent_telegram=datapoint_response.sent_telegram,
68
+ received_telegrams=datapoint_response.received_telegrams,
69
+ timestamp=datetime.now(),
85
70
  )
86
71
 
87
- if (
88
- not reply_telegram
89
- or reply_telegram.system_function != SystemFunction.READ_DATAPOINT
90
- or reply_telegram.datapoint_type != DataPointType.MODULE_LIGHT_LEVEL
91
- ):
92
- self.logger.debug("Not a lightlevel telegram")
93
- return
94
-
95
- self.logger.debug("Received lightlevel status telegram")
96
- lightlevel = self.extract_lightlevel(reply_telegram)
97
- self.succeed(lightlevel)
98
-
99
- def extract_lightlevel(self, reply_telegram: ReplyTelegram) -> int:
100
- level = 0
101
- for output_data in reply_telegram.data_value.split(","):
102
- if ":" in output_data:
103
- output_str, level_str = output_data.split(":")
104
- if int(output_str) == self.output_number:
105
- level_str = level_str.replace("[%]", "")
106
- level = int(level_str)
107
- break
108
- return level
109
-
110
- def succeed(self, lightlevel: int) -> None:
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.level = lightlevel
115
- if self.finish_callback:
116
- self.finish_callback(self.service_response)
117
-
118
- def failed(self, message: str) -> None:
119
- self.logger.debug(f"Failed with message: {message}")
120
- self.service_response.success = False
121
- self.service_response.timestamp = datetime.now()
122
- self.service_response.error = message
123
- if self.finish_callback:
124
- self.finish_callback(self.service_response)
72
+ if self.service_callback:
73
+ self.service_callback(service_response)
125
74
 
126
75
  def get_light_level(
127
76
  self,
@@ -143,7 +92,10 @@ class ConbusLightlevelGetService(ConbusProtocol):
143
92
  self.logger.info("Starting get_lightlevel_status")
144
93
  if timeout_seconds:
145
94
  self.timeout_seconds = timeout_seconds
146
- self.finish_callback = finish_callback
147
95
  self.serial_number = serial_number
148
96
  self.output_number = output_number
97
+ self.datapoint_type = DataPointType.MODULE_LIGHT_LEVEL
98
+
99
+ self.finish_callback = self.finish_service_callback
100
+ self.service_callback = finish_callback
149
101
  self.start_reactor()
@@ -0,0 +1,86 @@
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 typing import Callable, Optional
8
+
9
+ from twisted.internet.posixbase import PosixReactorBase
10
+
11
+ from xp.models import ConbusClientConfig, ConbusDatapointResponse
12
+ from xp.models.conbus.conbus_linknumber import ConbusLinknumberResponse
13
+ from xp.models.telegram.datapoint_type import DataPointType
14
+ from xp.services.conbus.conbus_datapoint_service import ConbusDatapointService
15
+ from xp.services.telegram.telegram_service import TelegramService
16
+
17
+
18
+ class ConbusLinknumberGetService(ConbusDatapointService):
19
+ """
20
+ Service for receiving telegrams from Conbus servers.
21
+
22
+ Uses composition with ConbusService to provide receive-only functionality
23
+ for collecting waiting event telegrams from the server.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ telegram_service: TelegramService,
29
+ cli_config: ConbusClientConfig,
30
+ reactor: PosixReactorBase,
31
+ ) -> None:
32
+ """Initialize the Conbus client send service"""
33
+ super().__init__(telegram_service, cli_config, reactor)
34
+ self.service_callback: Optional[Callable[[ConbusLinknumberResponse], None]] = (
35
+ None
36
+ )
37
+
38
+ # Set up logging
39
+ self.logger = logging.getLogger(__name__)
40
+
41
+ def finish_service_callback(
42
+ self, datapoint_response: ConbusDatapointResponse
43
+ ) -> None:
44
+
45
+ self.logger.debug("Parsing datapoint response")
46
+ link_number_value = 0
47
+ if datapoint_response.success and datapoint_response.datapoint_telegram:
48
+ link_number_value = int(datapoint_response.datapoint_telegram.data_value)
49
+
50
+ linknumber_response = ConbusLinknumberResponse(
51
+ success=datapoint_response.success,
52
+ result="SUCCESS" if datapoint_response.success else "FAILURE",
53
+ link_number=link_number_value,
54
+ serial_number=self.serial_number,
55
+ error=datapoint_response.error,
56
+ sent_telegram=datapoint_response.sent_telegram,
57
+ received_telegrams=datapoint_response.received_telegrams,
58
+ timestamp=datapoint_response.timestamp,
59
+ )
60
+
61
+ if self.service_callback:
62
+ self.service_callback(linknumber_response)
63
+
64
+ def get_linknumber(
65
+ self,
66
+ serial_number: str,
67
+ finish_callback: Callable[[ConbusLinknumberResponse], None],
68
+ timeout_seconds: Optional[float] = None,
69
+ ) -> None:
70
+ """
71
+ Get the current auto report status for a specific module.
72
+
73
+ Args:
74
+ :param serial_number: 10-digit module serial number
75
+ :param finish_callback: callback function to call when the linknumber status is
76
+ :param timeout_seconds: timeout in seconds
77
+
78
+ """
79
+ self.logger.info("Starting get_linknumber")
80
+ if timeout_seconds:
81
+ self.timeout_seconds = timeout_seconds
82
+ self.serial_number = serial_number
83
+ self.datapoint_type = DataPointType.LINK_NUMBER
84
+ self.finish_callback = self.finish_service_callback
85
+ self.service_callback = finish_callback
86
+ self.start_reactor()
@@ -0,0 +1,155 @@
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_linknumber import ConbusLinknumberResponse
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 ConbusLinknumberSetService(ConbusProtocol):
23
+ """
24
+ Service for setting module link numbers via Conbus telegrams.
25
+
26
+ Handles link number assignment by sending F04D04 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
+ super().__init__(cli_config, reactor)
38
+ self.telegram_service = telegram_service
39
+ self.serial_number: str = ""
40
+ self.link_number: int = 0
41
+ self.finish_callback: Optional[Callable[[ConbusLinknumberResponse], None]] = (
42
+ None
43
+ )
44
+ self.service_response: ConbusLinknumberResponse = ConbusLinknumberResponse(
45
+ success=False, serial_number=self.serial_number, result=""
46
+ )
47
+
48
+ # Set up logging
49
+ self.logger = logging.getLogger(__name__)
50
+
51
+ def connection_established(self) -> None:
52
+ self.logger.debug(
53
+ f"Connection established, setting link number {self.link_number}..."
54
+ )
55
+
56
+ # Validate parameters before sending
57
+ if not self.serial_number or len(self.serial_number) != 10:
58
+ self.failed(f"Serial number must be 10 digits, got: {self.serial_number}")
59
+ return
60
+
61
+ if not (0 <= self.link_number <= 99):
62
+ self.failed(f"Link number must be between 0-99, got: {self.link_number}")
63
+ return
64
+
65
+ # Send F04D04{link_number} telegram
66
+ # F04 = WRITE_CONFIG, D04 = LINK_NUMBER datapoint type
67
+ self.send_telegram(
68
+ telegram_type=TelegramType.SYSTEM,
69
+ serial_number=self.serial_number,
70
+ system_function=SystemFunction.WRITE_CONFIG,
71
+ data_value=f"{DataPointType.LINK_NUMBER.value}{self.link_number:02d}",
72
+ )
73
+
74
+ def telegram_sent(self, telegram_sent: str) -> None:
75
+ self.service_response.sent_telegram = telegram_sent
76
+
77
+ def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
78
+ self.logger.debug(f"Telegram received: {telegram_received}")
79
+
80
+ if not self.service_response.received_telegrams:
81
+ self.service_response.received_telegrams = []
82
+ self.service_response.received_telegrams.append(telegram_received.frame)
83
+
84
+ if (
85
+ not telegram_received.checksum_valid
86
+ or telegram_received.telegram_type != TelegramType.REPLY
87
+ or telegram_received.serial_number != self.serial_number
88
+ ):
89
+ self.logger.debug("Not a reply for our serial number")
90
+ return
91
+
92
+ # Parse the reply telegram
93
+ reply_telegram = self.telegram_service.parse_reply_telegram(
94
+ telegram_received.frame
95
+ )
96
+
97
+ if not reply_telegram:
98
+ self.logger.debug("Failed to parse reply telegram")
99
+ return
100
+
101
+ # Check for ACK or NAK response
102
+ if reply_telegram.system_function == SystemFunction.ACK:
103
+ self.logger.debug("Received ACK response")
104
+ self.succeed(SystemFunction.ACK)
105
+ elif reply_telegram.system_function == SystemFunction.NAK:
106
+ self.logger.debug("Received NAK response")
107
+ self.failed("Module responded with NAK")
108
+ else:
109
+ self.logger.debug(
110
+ f"Unexpected system function: {reply_telegram.system_function}"
111
+ )
112
+
113
+ def succeed(self, system_function: SystemFunction) -> None:
114
+ self.logger.debug("Successfully set link number")
115
+ self.service_response.success = True
116
+ self.service_response.timestamp = datetime.now()
117
+ self.service_response.serial_number = self.serial_number
118
+ self.service_response.result = "ACK"
119
+ self.service_response.link_number = self.link_number
120
+ if self.finish_callback:
121
+ self.finish_callback(self.service_response)
122
+
123
+ def failed(self, message: str) -> None:
124
+ self.logger.debug(f"Failed with message: {message}")
125
+ self.service_response.success = False
126
+ self.service_response.timestamp = datetime.now()
127
+ self.service_response.serial_number = self.serial_number
128
+ self.service_response.result = "NAK"
129
+ self.service_response.error = message
130
+ if self.finish_callback:
131
+ self.finish_callback(self.service_response)
132
+
133
+ def set_linknumber(
134
+ self,
135
+ serial_number: str,
136
+ link_number: int,
137
+ finish_callback: Callable[[ConbusLinknumberResponse], None],
138
+ timeout_seconds: Optional[float] = None,
139
+ ) -> None:
140
+ """
141
+ Set the link number for a specific module.
142
+
143
+ Args:
144
+ serial_number: 10-digit module serial number
145
+ link_number: Link number to set (0-99)
146
+ finish_callback: Callback function to call when operation completes
147
+ timeout_seconds: Optional timeout in seconds
148
+ """
149
+ self.logger.info("Starting set_linknumber")
150
+ if timeout_seconds:
151
+ self.timeout_seconds = timeout_seconds
152
+ self.serial_number = serial_number
153
+ self.link_number = link_number
154
+ self.finish_callback = finish_callback
155
+ self.start_reactor()
@@ -1,137 +1,174 @@
1
- """Conbus Client Send Service for TCP communication with Conbus servers.
1
+ """Conbus Output Service for sending action telegrams to Conbus modules.
2
2
 
3
- This service implements a TCP client that connects to Conbus servers and sends
4
- various types of telegrams including discover, version, and sensor data requests.
3
+ This service handles sending action telegrams (ON/OFF) to module outputs
4
+ and processing ACK/NAK responses.
5
5
  """
6
6
 
7
7
  import logging
8
8
  from datetime import datetime
9
- from typing import Any, Optional
9
+ from typing import Callable, Optional
10
10
 
11
- from xp.models import ConbusDatapointResponse
11
+ from twisted.internet.posixbase import PosixReactorBase
12
+
13
+ from xp.models import ConbusClientConfig
12
14
  from xp.models.conbus.conbus_output import ConbusOutputResponse
15
+ from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
13
16
  from xp.models.telegram.action_type import ActionType
14
- from xp.models.telegram.datapoint_type import DataPointType
17
+ from xp.models.telegram.output_telegram import OutputTelegram
15
18
  from xp.models.telegram.system_function import SystemFunction
16
- from xp.services.conbus.conbus_datapoint_service import ConbusDatapointService
17
- from xp.services.conbus.conbus_service import ConbusService
18
- from xp.services.telegram.telegram_output_service import TelegramOutputService
19
- from xp.services.telegram.telegram_service import TelegramService
19
+ from xp.models.telegram.telegram_type import TelegramType
20
+ from xp.services.protocol import ConbusProtocol
21
+ from xp.services.telegram.telegram_output_service import (
22
+ TelegramOutputService,
23
+ XPOutputError,
24
+ )
20
25
 
21
26
 
22
27
  class ConbusOutputError(Exception):
23
- """Raised when Conbus client send operations fail"""
28
+ """Raised when Conbus output operations fail"""
24
29
 
25
30
  pass
26
31
 
27
32
 
28
- class ConbusOutputService:
33
+ class ConbusOutputService(ConbusProtocol):
29
34
  """
30
- TCP client service for sending telegrams to Conbus servers.
35
+ Service for sending action telegrams to Conbus module outputs.
31
36
 
32
- Manages TCP socket connections, handles telegram generation and transmission,
33
- and processes server responses.
37
+ Manages action telegram transmission (ON/OFF) and processes
38
+ ACK/NAK responses from modules.
34
39
  """
35
40
 
36
41
  def __init__(
37
42
  self,
38
- telegram_service: TelegramService,
39
43
  telegram_output_service: TelegramOutputService,
40
- datapoint_service: ConbusDatapointService,
41
- conbus_service: ConbusService,
44
+ cli_config: ConbusClientConfig,
45
+ reactor: PosixReactorBase,
42
46
  ):
43
- """Initialize the Conbus client send service
47
+ """Initialize the Conbus output service
44
48
 
45
49
  Args:
46
- telegram_service: TelegramService for dependency injection
47
- telegram_output_service: TelegramOutputService for dependency injection
48
- datapoint_service: ConbusDatapointService for dependency injection
49
- conbus_service: ConbusService for dependency injection
50
+ telegram_output_service: TelegramOutputService for telegram generation/parsing
51
+ cli_config: Conbus client configuration
52
+ reactor: Twisted reactor for async operations
50
53
  """
51
-
52
- # Service dependencies
53
- self.telegram_service = telegram_service
54
+ super().__init__(cli_config, reactor)
54
55
  self.telegram_output_service = telegram_output_service
55
- self.datapoint_service = datapoint_service
56
- self.conbus_service = conbus_service
56
+ self.serial_number: str = ""
57
+ self.output_number: int = 0
58
+ self.action_type: ActionType = ActionType.ON_RELEASE
59
+ self.finish_callback: Optional[Callable[[ConbusOutputResponse], None]] = None
60
+ self.service_response: ConbusOutputResponse = ConbusOutputResponse(
61
+ success=False,
62
+ serial_number=self.serial_number,
63
+ output_number=self.output_number,
64
+ action_type=self.action_type,
65
+ timestamp=datetime.now(),
66
+ )
57
67
 
58
68
  # Set up logging
59
69
  self.logger = logging.getLogger(__name__)
60
70
 
61
- def __enter__(self) -> "ConbusOutputService":
62
- return self
63
-
64
- def __exit__(
65
- self,
66
- _exc_type: Optional[type],
67
- _exc_val: Optional[Exception],
68
- _exc_tb: Optional[Any],
69
- ) -> None:
70
- # Cleanup logic if needed
71
- pass
72
-
73
- def get_output_state(self, serial_number: str) -> ConbusDatapointResponse:
74
- # TODO: Migrate to new ConbusDatapointService callback-based API
75
- # Send status query using custom telegram method
76
- response = self.datapoint_service.query_datapoint( # type: ignore[call-arg,func-returns-value]
77
- serial_number=serial_number,
78
- datapoint_type=DataPointType.MODULE_OUTPUT_STATE, # "12"
71
+ def connection_established(self) -> None:
72
+ self.logger.debug(
73
+ f"Connection established, sending action {self.action_type} to output {self.output_number}..."
79
74
  )
80
75
 
81
- return response # type: ignore[no-any-return]
82
-
83
- def get_module_state(self, serial_number: str) -> ConbusDatapointResponse:
84
- # TODO: Migrate to new ConbusDatapointService callback-based API
85
- # Send status query using custom telegram method
86
- response = self.datapoint_service.query_datapoint( # type: ignore[call-arg,func-returns-value]
87
- serial_number=serial_number, datapoint_type=DataPointType.MODULE_STATE
76
+ # Validate parameters before sending
77
+ try:
78
+ self.telegram_output_service.validate_output_number(self.output_number)
79
+ self.telegram_output_service.validate_serial_number(self.serial_number)
80
+ except XPOutputError as e:
81
+ self.failed(str(e))
82
+ return
83
+
84
+ # Send F27D{output:02d}{action} telegram
85
+ # F27 = ACTION, D = data with output number and action type
86
+ self.send_telegram(
87
+ telegram_type=TelegramType.SYSTEM,
88
+ serial_number=self.serial_number,
89
+ system_function=SystemFunction.ACTION,
90
+ data_value=f"{self.output_number:02d}{self.action_type.value}",
88
91
  )
89
92
 
90
- return response # type: ignore[no-any-return]
93
+ def telegram_sent(self, telegram_sent: str) -> None:
94
+ self.service_response.sent_telegram = telegram_sent
91
95
 
92
- def send_action(
93
- self, serial_number: str, output_number: int, action_type: ActionType
94
- ) -> ConbusOutputResponse:
96
+ def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
97
+ self.logger.debug(f"Telegram received: {telegram_received}")
95
98
 
96
- # Parse input number and send action
97
- self.telegram_output_service.validate_output_number(output_number)
99
+ if not self.service_response.received_telegrams:
100
+ self.service_response.received_telegrams = []
101
+ self.service_response.received_telegrams.append(telegram_received.frame)
98
102
 
99
- # Send action telegram using custom telegram method
100
- # Format: F27D{input:02d}AA (Function 27, input number, PRESS action)
101
- action_value = action_type.value
103
+ if (
104
+ not telegram_received.checksum_valid
105
+ or telegram_received.telegram_type != TelegramType.REPLY
106
+ or telegram_received.serial_number != self.serial_number
107
+ ):
108
+ self.logger.debug("Not a reply for our serial number")
109
+ return
102
110
 
103
- input_action = f"{output_number:02d}{action_value}"
104
- response = self.conbus_service.send_telegram(
105
- serial_number,
106
- SystemFunction.ACTION, # "27"
107
- input_action, # "00AA", "01AA", etc.
111
+ # Parse the reply telegram to get ACK/NAK
112
+ output_telegram = self.telegram_output_service.parse_reply_telegram(
113
+ telegram_received.frame
108
114
  )
109
115
 
110
- if (
111
- not response.success
112
- or response.received_telegrams is None
113
- or len(response.received_telegrams) <= 0
116
+ if output_telegram and output_telegram.system_function in (
117
+ SystemFunction.ACK,
118
+ SystemFunction.NAK,
114
119
  ):
115
-
116
- return ConbusOutputResponse(
117
- success=response.success,
118
- serial_number=serial_number,
119
- output_number=output_number,
120
- action_type=action_type,
121
- error=response.error,
122
- timestamp=response.timestamp or datetime.now(),
123
- received_telegrams=response.received_telegrams,
120
+ self.logger.debug(f"Received {output_telegram.system_function} response")
121
+ self.succeed(output_telegram)
122
+ else:
123
+ self.logger.debug(
124
+ f"Unexpected system function: {output_telegram.system_function}"
124
125
  )
125
126
 
126
- telegram = response.received_telegrams[0]
127
- output_telegram = self.telegram_output_service.parse_reply_telegram(telegram)
128
-
129
- return ConbusOutputResponse(
130
- success=response.success,
131
- serial_number=serial_number,
132
- output_number=output_number,
133
- action_type=action_type,
134
- output_telegram=output_telegram,
135
- timestamp=response.timestamp or datetime.now(),
136
- received_telegrams=response.received_telegrams,
137
- )
127
+ def succeed(self, output_telegram: OutputTelegram) -> None:
128
+ self.logger.debug("Successfully sent action to output")
129
+ self.service_response.success = True
130
+ self.service_response.timestamp = datetime.now()
131
+ self.service_response.serial_number = self.serial_number
132
+ self.service_response.output_number = self.output_number
133
+ self.service_response.action_type = self.action_type
134
+ self.service_response.output_telegram = output_telegram
135
+ if self.finish_callback:
136
+ self.finish_callback(self.service_response)
137
+
138
+ def failed(self, message: str) -> None:
139
+ self.logger.debug(f"Failed with message: {message}")
140
+ self.service_response.success = False
141
+ self.service_response.timestamp = datetime.now()
142
+ self.service_response.serial_number = self.serial_number
143
+ self.service_response.output_number = self.output_number
144
+ self.service_response.action_type = self.action_type
145
+ self.service_response.error = message
146
+ if self.finish_callback:
147
+ self.finish_callback(self.service_response)
148
+
149
+ def send_action(
150
+ self,
151
+ serial_number: str,
152
+ output_number: int,
153
+ action_type: ActionType,
154
+ finish_callback: Callable[[ConbusOutputResponse], None],
155
+ timeout_seconds: Optional[float] = None,
156
+ ) -> None:
157
+ """
158
+ Send an action telegram to a module output.
159
+
160
+ Args:
161
+ serial_number: 10-digit module serial number
162
+ output_number: Output number (0-99)
163
+ action_type: Action to perform (ON_RELEASE, OFF_PRESS, etc.)
164
+ finish_callback: Callback function to call when operation completes
165
+ timeout_seconds: Optional timeout in seconds
166
+ """
167
+ self.logger.info("Starting send_action")
168
+ if timeout_seconds:
169
+ self.timeout_seconds = timeout_seconds
170
+ self.serial_number = serial_number
171
+ self.output_number = output_number
172
+ self.action_type = action_type
173
+ self.finish_callback = finish_callback
174
+ self.start_reactor()