conson-xp 1.2.0__py3-none-any.whl → 1.3.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 (49) hide show
  1. {conson_xp-1.2.0.dist-info → conson_xp-1.3.0.dist-info}/METADATA +1 -5
  2. {conson_xp-1.2.0.dist-info → conson_xp-1.3.0.dist-info}/RECORD +30 -47
  3. xp/__init__.py +1 -1
  4. xp/cli/commands/__init__.py +0 -2
  5. xp/cli/commands/conbus/conbus_actiontable_commands.py +5 -3
  6. xp/cli/commands/conbus/conbus_autoreport_commands.py +39 -21
  7. xp/cli/commands/conbus/conbus_blink_commands.py +8 -8
  8. xp/cli/commands/conbus/conbus_config_commands.py +3 -1
  9. xp/cli/commands/conbus/conbus_custom_commands.py +3 -1
  10. xp/cli/commands/conbus/conbus_datapoint_commands.py +4 -2
  11. xp/cli/commands/conbus/conbus_discover_commands.py +5 -3
  12. xp/cli/commands/conbus/conbus_lightlevel_commands.py +68 -32
  13. xp/cli/commands/conbus/conbus_linknumber_commands.py +32 -17
  14. xp/cli/commands/conbus/conbus_msactiontable_commands.py +11 -4
  15. xp/cli/commands/conbus/conbus_output_commands.py +6 -2
  16. xp/cli/commands/conbus/conbus_receive_commands.py +5 -3
  17. xp/cli/commands/file_commands.py +9 -3
  18. xp/cli/commands/homekit/homekit_start_commands.py +3 -1
  19. xp/cli/commands/module_commands.py +12 -4
  20. xp/cli/commands/reverse_proxy_commands.py +3 -1
  21. xp/cli/main.py +0 -2
  22. xp/models/conbus/conbus_datapoint.py +3 -0
  23. xp/models/conbus/conbus_writeconfig.py +60 -0
  24. xp/services/conbus/conbus_datapoint_service.py +9 -6
  25. xp/services/conbus/{conbus_linknumber_set_service.py → write_config_service.py} +78 -66
  26. xp/services/telegram/telegram_datapoint_service.py +70 -0
  27. xp/utils/dependencies.py +4 -46
  28. xp/api/__init__.py +0 -1
  29. xp/api/main.py +0 -125
  30. xp/api/models/__init__.py +0 -1
  31. xp/api/models/api.py +0 -31
  32. xp/api/models/discover.py +0 -31
  33. xp/api/routers/__init__.py +0 -17
  34. xp/api/routers/conbus.py +0 -5
  35. xp/api/routers/conbus_blink.py +0 -117
  36. xp/api/routers/conbus_custom.py +0 -71
  37. xp/api/routers/conbus_datapoint.py +0 -74
  38. xp/api/routers/conbus_output.py +0 -167
  39. xp/api/routers/errors.py +0 -38
  40. xp/cli/commands/api.py +0 -12
  41. xp/cli/commands/api_start_commands.py +0 -132
  42. xp/services/conbus/conbus_autoreport_get_service.py +0 -94
  43. xp/services/conbus/conbus_autoreport_set_service.py +0 -141
  44. xp/services/conbus/conbus_lightlevel_get_service.py +0 -109
  45. xp/services/conbus/conbus_lightlevel_set_service.py +0 -225
  46. xp/services/conbus/conbus_linknumber_get_service.py +0 -94
  47. {conson_xp-1.2.0.dist-info → conson_xp-1.3.0.dist-info}/WHEEL +0 -0
  48. {conson_xp-1.2.0.dist-info → conson_xp-1.3.0.dist-info}/entry_points.txt +0 -0
  49. {conson_xp-1.2.0.dist-info → conson_xp-1.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,9 +9,12 @@ from xp.cli.utils.decorators import (
9
9
  connection_command,
10
10
  )
11
11
  from xp.cli.utils.serial_number_type import SERIAL
12
- from xp.models.conbus.conbus_linknumber import ConbusLinknumberResponse
13
- from xp.services.conbus.conbus_linknumber_get_service import ConbusLinknumberGetService
14
- from xp.services.conbus.conbus_linknumber_set_service import ConbusLinknumberSetService
12
+ from xp.models import ConbusDatapointResponse
13
+ from xp.models.conbus.conbus_writeconfig import ConbusWriteConfigResponse
14
+ from xp.models.telegram.datapoint_type import DataPointType
15
+ from xp.services.conbus.conbus_datapoint_service import ConbusDatapointService
16
+ from xp.services.conbus.write_config_service import WriteConfigService
17
+ from xp.services.telegram.telegram_datapoint_service import TelegramDatapointService
15
18
 
16
19
 
17
20
  @conbus_linknumber.command("set", short_help="Set link number for a module")
@@ -33,23 +36,27 @@ def set_linknumber_command(
33
36
  \b
34
37
  xp conbus linknumber set 0123450001 25
35
38
  """
36
- service = (
37
- ctx.obj.get("container").get_container().resolve(ConbusLinknumberSetService)
38
- )
39
39
 
40
- def on_finish(response: ConbusLinknumberResponse) -> None:
41
- """Handle successful completion of link number set command.
40
+ def on_finish(response: "ConbusWriteConfigResponse") -> None:
41
+ """Handle successful completion of light level on command.
42
42
 
43
43
  Args:
44
- response: Link number response object.
44
+ response: Light level response object.
45
45
  """
46
46
  click.echo(json.dumps(response.to_dict(), indent=2))
47
47
 
48
+ service: WriteConfigService = (
49
+ ctx.obj.get("container").get_container().resolve(WriteConfigService)
50
+ )
51
+
52
+ data_value = f"{link_number: 02d}"
48
53
  with service:
49
- service.set_linknumber(
54
+ service.write_config(
50
55
  serial_number=serial_number,
51
- link_number=link_number,
56
+ datapoint_type=DataPointType.LINK_NUMBER,
57
+ data_value=data_value,
52
58
  finish_callback=on_finish,
59
+ timeout_seconds=0.5,
53
60
  )
54
61
 
55
62
 
@@ -68,20 +75,28 @@ def get_linknumber_command(ctx: click.Context, serial_number: str) -> None:
68
75
  \b
69
76
  xp conbus linknumber get 0123450001
70
77
  """
71
- service = (
72
- ctx.obj.get("container").get_container().resolve(ConbusLinknumberGetService)
78
+ service: ConbusDatapointService = (
79
+ ctx.obj.get("container").get_container().resolve(ConbusDatapointService)
80
+ )
81
+ telegram_service: TelegramDatapointService = (
82
+ ctx.obj.get("container").get_container().resolve(TelegramDatapointService)
73
83
  )
74
84
 
75
- def on_finish(response: ConbusLinknumberResponse) -> None:
85
+ def on_finish(service_response: ConbusDatapointResponse) -> None:
76
86
  """Handle successful completion of link number get command.
77
87
 
78
88
  Args:
79
- response: Link number response object.
89
+ service_response: Link number response object.
80
90
  """
81
- click.echo(json.dumps(response.to_dict(), indent=2))
91
+ linknumber_value = telegram_service.get_linknumber(service_response.data_value)
92
+ result = service_response.to_dict()
93
+ result["linknumber_value"] = linknumber_value
94
+ click.echo(json.dumps(result, indent=2))
82
95
 
83
96
  with service:
84
- service.get_linknumber(
97
+ service.query_datapoint(
85
98
  serial_number=serial_number,
99
+ datapoint_type=DataPointType.LINK_NUMBER,
86
100
  finish_callback=on_finish,
101
+ timeout_seconds=0.5,
87
102
  )
@@ -2,6 +2,7 @@
2
2
 
3
3
  import json
4
4
  from dataclasses import asdict
5
+ from typing import Union
5
6
 
6
7
  import click
7
8
  from click import Context
@@ -12,7 +13,9 @@ from xp.cli.utils.decorators import (
12
13
  )
13
14
  from xp.cli.utils.serial_number_type import SERIAL
14
15
  from xp.cli.utils.xp_module_type import XP_MODULE_TYPE
15
- from xp.models.actiontable.actiontable import ActionTable
16
+ from xp.models.actiontable.msactiontable_xp20 import Xp20MsActionTable
17
+ from xp.models.actiontable.msactiontable_xp24 import Xp24MsActionTable
18
+ from xp.models.actiontable.msactiontable_xp33 import Xp33MsActionTable
16
19
  from xp.services.conbus.actiontable.msactiontable_service import (
17
20
  MsActionTableService,
18
21
  )
@@ -33,7 +36,9 @@ def conbus_download_msactiontable(
33
36
  serial_number: 10-digit module serial number.
34
37
  xpmoduletype: XP module type.
35
38
  """
36
- service = ctx.obj.get("container").get_container().resolve(MsActionTableService)
39
+ service: MsActionTableService = (
40
+ ctx.obj.get("container").get_container().resolve(MsActionTableService)
41
+ )
37
42
 
38
43
  def progress_callback(progress: str) -> None:
39
44
  """Handle progress updates during MS action table download.
@@ -43,7 +48,9 @@ def conbus_download_msactiontable(
43
48
  """
44
49
  click.echo(progress, nl=False)
45
50
 
46
- def finish_callback(action_table: ActionTable) -> None:
51
+ def on_finish(
52
+ action_table: Union[Xp20MsActionTable | Xp24MsActionTable | Xp33MsActionTable],
53
+ ) -> None:
47
54
  """Handle successful completion of MS action table download.
48
55
 
49
56
  Args:
@@ -73,6 +80,6 @@ def conbus_download_msactiontable(
73
80
  serial_number=serial_number,
74
81
  xpmoduletype=xpmoduletype,
75
82
  progress_callback=progress_callback,
76
- finish_callback=finish_callback,
83
+ finish_callback=on_finish,
77
84
  error_callback=error_callback,
78
85
  )
@@ -34,7 +34,9 @@ def xp_output_on(ctx: click.Context, serial_number: str, output_number: int) ->
34
34
  \b
35
35
  xp conbus output on 0011223344 0 # Turn on output 0
36
36
  """
37
- service = ctx.obj.get("container").get_container().resolve(ConbusOutputService)
37
+ service: ConbusOutputService = (
38
+ ctx.obj.get("container").get_container().resolve(ConbusOutputService)
39
+ )
38
40
 
39
41
  def on_finish(response: ConbusOutputResponse) -> None:
40
42
  """Handle successful completion of output on command.
@@ -70,7 +72,9 @@ def xp_output_off(ctx: click.Context, serial_number: str, output_number: int) ->
70
72
  \b
71
73
  xp conbus output off 0011223344 1 # Turn off output 1
72
74
  """
73
- service = ctx.obj.get("container").get_container().resolve(ConbusOutputService)
75
+ service: ConbusOutputService = (
76
+ ctx.obj.get("container").get_container().resolve(ConbusOutputService)
77
+ )
74
78
 
75
79
  def on_finish(response: ConbusOutputResponse) -> None:
76
80
  """Handle successful completion of output off command.
@@ -36,7 +36,7 @@ def receive_telegrams(ctx: Context, timeout: float) -> None:
36
36
  xp conbus receive 5.0
37
37
  """
38
38
 
39
- def finish(response_received: ConbusReceiveResponse) -> None:
39
+ def on_finish(response_received: ConbusReceiveResponse) -> None:
40
40
  """Handle successful completion of telegram receive operation.
41
41
 
42
42
  Args:
@@ -52,6 +52,8 @@ def receive_telegrams(ctx: Context, timeout: float) -> None:
52
52
  """
53
53
  click.echo(telegram_received)
54
54
 
55
- service = ctx.obj.get("container").get_container().resolve(ConbusReceiveService)
55
+ service: ConbusReceiveService = (
56
+ ctx.obj.get("container").get_container().resolve(ConbusReceiveService)
57
+ )
56
58
  with service:
57
- service.start(progress, finish, timeout)
59
+ service.start(progress, on_finish, timeout)
@@ -56,7 +56,9 @@ def decode_log_file(
56
56
  from xp.services.log_file_service import LogFileService
57
57
  from xp.utils.time_utils import TimeParsingError, parse_time_range
58
58
 
59
- service = ctx.obj.get("container").get_container().resolve(LogFileService)
59
+ service: LogFileService = (
60
+ ctx.obj.get("container").get_container().resolve(LogFileService)
61
+ )
60
62
  StatisticsFormatter(True)
61
63
 
62
64
  try:
@@ -124,7 +126,9 @@ def analyze_log_file(ctx: Context, log_file_path: str) -> None:
124
126
  """
125
127
  from xp.services.log_file_service import LogFileService
126
128
 
127
- service = ctx.obj.get("container").get_container().resolve(LogFileService)
129
+ service: LogFileService = (
130
+ ctx.obj.get("container").get_container().resolve(LogFileService)
131
+ )
128
132
  StatisticsFormatter(True)
129
133
 
130
134
  try:
@@ -156,7 +160,9 @@ def validate_log_file(ctx: Context, log_file_path: str) -> None:
156
160
  """
157
161
  from xp.services.log_file_service import LogFileService
158
162
 
159
- service = ctx.obj.get("container").get_container().resolve(LogFileService)
163
+ service: LogFileService = (
164
+ ctx.obj.get("container").get_container().resolve(LogFileService)
165
+ )
160
166
  OutputFormatter(True)
161
167
 
162
168
  try:
@@ -28,7 +28,9 @@ def homekit_start(ctx: Context) -> None:
28
28
  click.echo("Starting XP Protocol HomeKit server...")
29
29
 
30
30
  try:
31
- service = ctx.obj.get("container").get_container().resolve(HomeKitService)
31
+ service: HomeKitService = (
32
+ ctx.obj.get("container").get_container().resolve(HomeKitService)
33
+ )
32
34
  service.start() # Blocking call - reactor.run() never returns
33
35
 
34
36
  except KeyboardInterrupt:
@@ -37,7 +37,9 @@ def module_info(ctx: Context, identifier: str) -> None:
37
37
  xp module info 14
38
38
  xp module info XP2606
39
39
  """
40
- service = ctx.obj.get("container").get_container().resolve(ModuleTypeService)
40
+ service: ModuleTypeService = (
41
+ ctx.obj.get("container").get_container().resolve(ModuleTypeService)
42
+ )
41
43
  OutputFormatter(True)
42
44
 
43
45
  try:
@@ -76,7 +78,9 @@ def module_list(ctx: Context, category: str, group_by_category: bool) -> None:
76
78
  xp module list --category "Interface Panels"
77
79
  xp module list --group-by-category
78
80
  """
79
- service = ctx.obj.get("container").get_container().resolve(ModuleTypeService)
81
+ service: ModuleTypeService = (
82
+ ctx.obj.get("container").get_container().resolve(ModuleTypeService)
83
+ )
80
84
  ListFormatter(True)
81
85
 
82
86
  try:
@@ -130,7 +134,9 @@ def module_search(ctx: Context, query: str, field: tuple) -> None:
130
134
  xp module search "push button"
131
135
  xp module search --field name "XP"
132
136
  """
133
- service = ctx.obj.get("container").get_container().resolve(ModuleTypeService)
137
+ service: ModuleTypeService = (
138
+ ctx.obj.get("container").get_container().resolve(ModuleTypeService)
139
+ )
134
140
  ListFormatter(True)
135
141
 
136
142
  try:
@@ -162,7 +168,9 @@ def module_categories(ctx: Context) -> None:
162
168
  \b
163
169
  xp module categories
164
170
  """
165
- service = ctx.obj.get("container").get_container().resolve(ModuleTypeService)
171
+ service: ModuleTypeService = (
172
+ ctx.obj.get("container").get_container().resolve(ModuleTypeService)
173
+ )
166
174
  OutputFormatter(True)
167
175
 
168
176
  try:
@@ -73,7 +73,9 @@ def start_proxy(ctx: Context, port: int, config: str) -> None:
73
73
  raise SystemExit(1)
74
74
 
75
75
  # Load configuration and create proxy instance
76
- service = ctx.obj.get("container").get_container().resolve(ReverseProxyService)
76
+ service: ReverseProxyService = (
77
+ ctx.obj.get("container").get_container().resolve(ReverseProxyService)
78
+ )
77
79
  global_proxy_instance = service
78
80
 
79
81
  # Handle graceful shutdown on SIGINT
xp/cli/main.py CHANGED
@@ -6,7 +6,6 @@ import click
6
6
  from click_help_colors import HelpColorsGroup
7
7
 
8
8
  from xp.cli.commands import homekit
9
- from xp.cli.commands.api import api
10
9
  from xp.cli.commands.conbus.conbus import conbus
11
10
  from xp.cli.commands.file_commands import file
12
11
  from xp.cli.commands.module_commands import module
@@ -79,7 +78,6 @@ cli.add_command(telegram)
79
78
  cli.add_command(module)
80
79
  cli.add_command(file)
81
80
  cli.add_command(server)
82
- cli.add_command(api)
83
81
  cli.add_command(reverse_proxy)
84
82
 
85
83
  # Add the tree command
@@ -21,6 +21,7 @@ class ConbusDatapointResponse:
21
21
  sent_telegram: Telegram sent to device.
22
22
  received_telegrams: List of telegrams received.
23
23
  datapoint_telegram: Parsed datapoint telegram.
24
+ data_value: Value of the datapoint telegram.
24
25
  datapoints: List of datapoint values.
25
26
  error: Error message if operation failed.
26
27
  timestamp: Timestamp of the response.
@@ -33,6 +34,7 @@ class ConbusDatapointResponse:
33
34
  sent_telegram: Optional[str] = None
34
35
  received_telegrams: Optional[list] = None
35
36
  datapoint_telegram: Optional[ReplyTelegram] = None
37
+ data_value: str = ""
36
38
  datapoints: Optional[List[Dict[str, str]]] = None
37
39
  error: Optional[str] = None
38
40
  timestamp: Optional[datetime] = None
@@ -55,6 +57,7 @@ class ConbusDatapointResponse:
55
57
  result: Dict[str, Any] = {
56
58
  "success": self.success,
57
59
  "serial_number": self.serial_number,
60
+ "data_value": self.data_value,
58
61
  "error": self.error,
59
62
  "timestamp": self.timestamp.isoformat() if self.timestamp else None,
60
63
  }
@@ -0,0 +1,60 @@
1
+ """Conbus link number response model."""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from typing import Any, Dict, Optional
6
+
7
+ from xp.models.telegram.datapoint_type import DataPointType
8
+ from xp.models.telegram.system_function import SystemFunction
9
+
10
+
11
+ @dataclass
12
+ class ConbusWriteConfigResponse:
13
+ """Represents a response from Conbus write config operations (set/get).
14
+
15
+ Attributes:
16
+ success: Whether the operation was successful.
17
+ serial_number: Serial number of the device.
18
+ datapoint_type: the datapoint to write.
19
+ system_function: ACK or NAK received.
20
+ sent_telegram: Telegram sent to device.
21
+ received_telegrams: List of telegrams received.
22
+ data_value: written value.
23
+ error: Error message if operation failed.
24
+ timestamp: Timestamp of the response.
25
+ """
26
+
27
+ success: bool
28
+ serial_number: str
29
+ datapoint_type: Optional[DataPointType] = None
30
+ system_function: Optional[SystemFunction] = None
31
+ sent_telegram: Optional[str] = None
32
+ received_telegrams: Optional[list] = None
33
+ data_value: Optional[str] = None
34
+ error: Optional[str] = None
35
+ timestamp: Optional[datetime] = None
36
+
37
+ def __post_init__(self) -> None:
38
+ """Initialize timestamp and received_telegrams if not provided."""
39
+ if self.timestamp is None:
40
+ self.timestamp = datetime.now()
41
+ if self.received_telegrams is None:
42
+ self.received_telegrams = []
43
+
44
+ def to_dict(self) -> Dict[str, Any]:
45
+ """Convert to dictionary for JSON serialization.
46
+
47
+ Returns:
48
+ Dictionary representation of the response.
49
+ """
50
+ return {
51
+ "success": self.success,
52
+ "system_function": self.system_function,
53
+ "datapoint_type": self.datapoint_type,
54
+ "data_value": self.data_value,
55
+ "serial_number": self.serial_number,
56
+ "sent_telegram": self.sent_telegram,
57
+ "received_telegrams": self.received_telegrams,
58
+ "error": self.error,
59
+ "timestamp": self.timestamp.isoformat() if self.timestamp else None,
60
+ }
@@ -44,7 +44,9 @@ class ConbusDatapointService(ConbusProtocol):
44
44
  self.telegram_service = telegram_service
45
45
  self.serial_number: str = ""
46
46
  self.datapoint_type: Optional[DataPointType] = None
47
- self.finish_callback: Optional[Callable[[ConbusDatapointResponse], None]] = None
47
+ self.datapoint_finished_callback: Optional[
48
+ Callable[[ConbusDatapointResponse], None]
49
+ ] = None
48
50
  self.service_response: ConbusDatapointResponse = ConbusDatapointResponse(
49
51
  success=False,
50
52
  serial_number=self.serial_number,
@@ -125,8 +127,9 @@ class ConbusDatapointService(ConbusProtocol):
125
127
  self.service_response.system_function = SystemFunction.READ_DATAPOINT
126
128
  self.service_response.datapoint_type = self.datapoint_type
127
129
  self.service_response.datapoint_telegram = datapoint_telegram
128
- if self.finish_callback:
129
- self.finish_callback(self.service_response)
130
+ self.service_response.data_value = datapoint_telegram.data_value
131
+ if self.datapoint_finished_callback:
132
+ self.datapoint_finished_callback(self.service_response)
130
133
 
131
134
  def failed(self, message: str) -> None:
132
135
  """Handle failed connection event.
@@ -139,8 +142,8 @@ class ConbusDatapointService(ConbusProtocol):
139
142
  self.service_response.timestamp = datetime.now()
140
143
  self.service_response.serial_number = self.serial_number
141
144
  self.service_response.error = message
142
- if self.finish_callback:
143
- self.finish_callback(self.service_response)
145
+ if self.datapoint_finished_callback:
146
+ self.datapoint_finished_callback(self.service_response)
144
147
 
145
148
  def query_datapoint(
146
149
  self,
@@ -160,7 +163,7 @@ class ConbusDatapointService(ConbusProtocol):
160
163
  self.logger.info("Starting query_datapoint")
161
164
  if timeout_seconds:
162
165
  self.timeout_seconds = timeout_seconds
163
- self.finish_callback = finish_callback
166
+ self.datapoint_finished_callback = finish_callback
164
167
  self.serial_number = serial_number
165
168
  self.datapoint_type = datapoint_type
166
169
  self.start_reactor()
@@ -10,7 +10,7 @@ from typing import Callable, Optional
10
10
  from twisted.internet.posixbase import PosixReactorBase
11
11
 
12
12
  from xp.models import ConbusClientConfig
13
- from xp.models.conbus.conbus_linknumber import ConbusLinknumberResponse
13
+ from xp.models.conbus.conbus_writeconfig import ConbusWriteConfigResponse
14
14
  from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
15
15
  from xp.models.telegram.datapoint_type import DataPointType
16
16
  from xp.models.telegram.system_function import SystemFunction
@@ -19,11 +19,11 @@ from xp.services.protocol import ConbusProtocol
19
19
  from xp.services.telegram.telegram_service import TelegramService
20
20
 
21
21
 
22
- class ConbusLinknumberSetService(ConbusProtocol):
22
+ class WriteConfigService(ConbusProtocol):
23
23
  """
24
- Service for setting module link numbers via Conbus telegrams.
24
+ Service for writing module settings via Conbus telegrams.
25
25
 
26
- Handles link number assignment by sending F04D04 telegrams and processing
26
+ Handles setting assignment by sending F04DXX telegrams and processing
27
27
  ACK/NAK responses from modules.
28
28
  """
29
29
 
@@ -42,13 +42,17 @@ class ConbusLinknumberSetService(ConbusProtocol):
42
42
  """
43
43
  super().__init__(cli_config, reactor)
44
44
  self.telegram_service = telegram_service
45
+ self.datapoint_type: Optional[DataPointType] = None
45
46
  self.serial_number: str = ""
46
- self.link_number: int = 0
47
- self.finish_callback: Optional[Callable[[ConbusLinknumberResponse], None]] = (
48
- None
49
- )
50
- self.service_response: ConbusLinknumberResponse = ConbusLinknumberResponse(
51
- success=False, serial_number=self.serial_number, result=""
47
+ self.data_value: str = ""
48
+ self.write_config_finished_callback: Optional[
49
+ Callable[[ConbusWriteConfigResponse], None]
50
+ ] = None
51
+ self.write_config_response: ConbusWriteConfigResponse = (
52
+ ConbusWriteConfigResponse(
53
+ success=False,
54
+ serial_number=self.serial_number,
55
+ )
52
56
  )
53
57
 
54
58
  # Set up logging
@@ -56,26 +60,30 @@ class ConbusLinknumberSetService(ConbusProtocol):
56
60
 
57
61
  def connection_established(self) -> None:
58
62
  """Handle connection established event."""
59
- self.logger.debug(
60
- f"Connection established, setting link number {self.link_number}."
61
- )
63
+ self.logger.debug(f"Connection established, writing config {self.data_value}.")
62
64
 
63
65
  # Validate parameters before sending
64
66
  if not self.serial_number or len(self.serial_number) != 10:
65
67
  self.failed(f"Serial number must be 10 digits, got: {self.serial_number}")
66
68
  return
67
69
 
68
- if not (0 <= self.link_number <= 99):
69
- self.failed(f"Link number must be between 0-99, got: {self.link_number}")
70
+ if len(self.data_value) < 2:
71
+ self.failed(f"data_value must be at least 2 bytes, got: {self.data_value}")
72
+ return
73
+
74
+ if not self.datapoint_type:
75
+ self.failed(f"datapoint_type must be defined, got: {self.datapoint_type}")
70
76
  return
71
77
 
72
- # Send F04D04{link_number} telegram
73
- # F04 = WRITE_CONFIG, D04 = LINK_NUMBER datapoint type
78
+ # Send WRITE_CONFIG telegram
79
+ # Function F04 = WRITE_CONFIG,
80
+ # Datapoint = D datapoint_type
81
+ # Data = XX
74
82
  self.send_telegram(
75
83
  telegram_type=TelegramType.SYSTEM,
76
84
  serial_number=self.serial_number,
77
85
  system_function=SystemFunction.WRITE_CONFIG,
78
- data_value=f"{DataPointType.LINK_NUMBER.value}{self.link_number:02d}",
86
+ data_value=f"{self.datapoint_type.value}{self.data_value}",
79
87
  )
80
88
 
81
89
  def telegram_sent(self, telegram_sent: str) -> None:
@@ -84,7 +92,7 @@ class ConbusLinknumberSetService(ConbusProtocol):
84
92
  Args:
85
93
  telegram_sent: The telegram that was sent.
86
94
  """
87
- self.service_response.sent_telegram = telegram_sent
95
+ self.write_config_response.sent_telegram = telegram_sent
88
96
 
89
97
  def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
90
98
  """Handle telegram received event.
@@ -94,9 +102,9 @@ class ConbusLinknumberSetService(ConbusProtocol):
94
102
  """
95
103
  self.logger.debug(f"Telegram received: {telegram_received}")
96
104
 
97
- if not self.service_response.received_telegrams:
98
- self.service_response.received_telegrams = []
99
- self.service_response.received_telegrams.append(telegram_received.frame)
105
+ if not self.write_config_response.received_telegrams:
106
+ self.write_config_response.received_telegrams = []
107
+ self.write_config_response.received_telegrams.append(telegram_received.frame)
100
108
 
101
109
  if (
102
110
  not telegram_received.checksum_valid
@@ -111,71 +119,75 @@ class ConbusLinknumberSetService(ConbusProtocol):
111
119
  telegram_received.frame
112
120
  )
113
121
 
114
- if not reply_telegram:
115
- self.logger.debug("Failed to parse reply telegram")
122
+ if not reply_telegram or reply_telegram.system_function not in (
123
+ SystemFunction.ACK,
124
+ SystemFunction.NAK,
125
+ ):
126
+ self.logger.debug("Not a write config reply")
116
127
  return
117
128
 
118
- # Check for ACK or NAK response
119
- if reply_telegram.system_function == SystemFunction.ACK:
120
- self.logger.debug("Received ACK response")
121
- self.succeed(SystemFunction.ACK)
122
- elif reply_telegram.system_function == SystemFunction.NAK:
123
- self.logger.debug("Received NAK response")
124
- self.failed("Module responded with NAK")
125
- else:
126
- self.logger.debug(
127
- f"Unexpected system function: {reply_telegram.system_function}"
128
- )
129
+ succeed = (
130
+ True if reply_telegram.system_function == SystemFunction.ACK else False
131
+ )
132
+ self.finished(
133
+ succeed_or_failed=succeed, system_function=reply_telegram.system_function
134
+ )
129
135
 
130
- def succeed(self, system_function: SystemFunction) -> None:
131
- """Handle successful link number set operation.
136
+ def failed(self, message: str) -> None:
137
+ """Handle telegram failed event.
132
138
 
133
139
  Args:
134
- system_function: The system function from the reply telegram.
140
+ message: The error message.
135
141
  """
136
- self.logger.debug("Successfully set link number")
137
- self.service_response.success = True
138
- self.service_response.timestamp = datetime.now()
139
- self.service_response.serial_number = self.serial_number
140
- self.service_response.result = "ACK"
141
- self.service_response.link_number = self.link_number
142
- if self.finish_callback:
143
- self.finish_callback(self.service_response)
142
+ self.logger.debug("Failed to send telegram")
143
+ self.finished(succeed_or_failed=False, message=message)
144
144
 
145
- def failed(self, message: str) -> None:
146
- """Handle failed connection event.
145
+ def finished(
146
+ self,
147
+ succeed_or_failed: bool,
148
+ message: Optional[str] = None,
149
+ system_function: Optional[SystemFunction] = None,
150
+ ) -> None:
151
+ """Handle successful link number set operation.
147
152
 
148
153
  Args:
149
- message: Failure message.
154
+ succeed_or_failed: succeed true, failed false.
155
+ message: error message if any.
156
+ system_function: The system function from the reply telegram.
150
157
  """
151
- self.logger.debug(f"Failed with message: {message}")
152
- self.service_response.success = False
153
- self.service_response.timestamp = datetime.now()
154
- self.service_response.serial_number = self.serial_number
155
- self.service_response.result = "NAK"
156
- self.service_response.error = message
157
- if self.finish_callback:
158
- self.finish_callback(self.service_response)
159
-
160
- def set_linknumber(
158
+ self.logger.debug("finished writing config")
159
+ self.write_config_response.success = succeed_or_failed
160
+ self.write_config_response.error = message
161
+ self.write_config_response.timestamp = datetime.now()
162
+ self.write_config_response.serial_number = self.serial_number
163
+ self.write_config_response.system_function = system_function
164
+ self.write_config_response.datapoint_type = self.datapoint_type
165
+ self.write_config_response.data_value = self.data_value
166
+ if self.write_config_finished_callback:
167
+ self.write_config_finished_callback(self.write_config_response)
168
+
169
+ def write_config(
161
170
  self,
162
171
  serial_number: str,
163
- link_number: int,
164
- finish_callback: Callable[[ConbusLinknumberResponse], None],
172
+ datapoint_type: DataPointType,
173
+ data_value: str,
174
+ finish_callback: Callable[[ConbusWriteConfigResponse], None],
165
175
  timeout_seconds: Optional[float] = None,
166
176
  ) -> None:
167
- """Set the link number for a specific module.
177
+ """Write config to a specific module.
168
178
 
169
179
  Args:
170
180
  serial_number: 10-digit module serial number.
171
- link_number: Link number to set (0-99).
181
+ datapoint_type: the datapoint type to write to.
182
+ data_value: the data to write.
172
183
  finish_callback: Callback function to call when operation completes.
173
184
  timeout_seconds: Optional timeout in seconds.
174
185
  """
175
- self.logger.info("Starting set_linknumber")
186
+ self.logger.info("Starting write_config")
176
187
  if timeout_seconds:
177
188
  self.timeout_seconds = timeout_seconds
178
189
  self.serial_number = serial_number
179
- self.link_number = link_number
180
- self.finish_callback = finish_callback
190
+ self.datapoint_type = datapoint_type
191
+ self.data_value = data_value
192
+ self.write_config_finished_callback = finish_callback
181
193
  self.start_reactor()