conson-xp 1.1.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 (174) hide show
  1. {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/METADATA +1 -5
  2. conson_xp-1.3.0.dist-info/RECORD +164 -0
  3. xp/__init__.py +4 -3
  4. xp/cli/__init__.py +1 -1
  5. xp/cli/commands/__init__.py +1 -2
  6. xp/cli/commands/conbus/conbus.py +9 -37
  7. xp/cli/commands/conbus/conbus_actiontable_commands.py +26 -4
  8. xp/cli/commands/conbus/conbus_autoreport_commands.py +58 -30
  9. xp/cli/commands/conbus/conbus_blink_commands.py +61 -29
  10. xp/cli/commands/conbus/conbus_config_commands.py +10 -5
  11. xp/cli/commands/conbus/conbus_custom_commands.py +16 -5
  12. xp/cli/commands/conbus/conbus_datapoint_commands.py +32 -10
  13. xp/cli/commands/conbus/conbus_discover_commands.py +20 -7
  14. xp/cli/commands/conbus/conbus_lightlevel_commands.py +114 -39
  15. xp/cli/commands/conbus/conbus_linknumber_commands.py +50 -25
  16. xp/cli/commands/conbus/conbus_msactiontable_commands.py +36 -5
  17. xp/cli/commands/conbus/conbus_output_commands.py +52 -14
  18. xp/cli/commands/conbus/conbus_raw_commands.py +17 -6
  19. xp/cli/commands/conbus/conbus_receive_commands.py +20 -10
  20. xp/cli/commands/conbus/conbus_scan_commands.py +17 -4
  21. xp/cli/commands/file_commands.py +35 -18
  22. xp/cli/commands/homekit/homekit.py +14 -8
  23. xp/cli/commands/homekit/homekit_start_commands.py +8 -6
  24. xp/cli/commands/module_commands.py +38 -23
  25. xp/cli/commands/reverse_proxy_commands.py +27 -19
  26. xp/cli/commands/server/server_commands.py +18 -18
  27. xp/cli/commands/telegram/telegram.py +4 -12
  28. xp/cli/commands/telegram/telegram_blink_commands.py +10 -8
  29. xp/cli/commands/telegram/telegram_checksum_commands.py +19 -8
  30. xp/cli/commands/telegram/telegram_discover_commands.py +2 -4
  31. xp/cli/commands/telegram/telegram_linknumber_commands.py +11 -8
  32. xp/cli/commands/telegram/telegram_parse_commands.py +10 -9
  33. xp/cli/commands/telegram/telegram_version_commands.py +8 -4
  34. xp/cli/main.py +5 -3
  35. xp/cli/utils/click_tree.py +23 -3
  36. xp/cli/utils/datapoint_type_choice.py +20 -0
  37. xp/cli/utils/decorators.py +165 -14
  38. xp/cli/utils/error_handlers.py +49 -18
  39. xp/cli/utils/formatters.py +95 -10
  40. xp/cli/utils/serial_number_type.py +18 -0
  41. xp/cli/utils/system_function_choice.py +20 -0
  42. xp/cli/utils/xp_module_type.py +20 -0
  43. xp/connection/__init__.py +1 -1
  44. xp/connection/exceptions.py +5 -5
  45. xp/models/__init__.py +1 -1
  46. xp/models/actiontable/__init__.py +1 -0
  47. xp/models/actiontable/actiontable.py +17 -1
  48. xp/models/actiontable/msactiontable_xp20.py +10 -0
  49. xp/models/actiontable/msactiontable_xp24.py +20 -3
  50. xp/models/actiontable/msactiontable_xp33.py +27 -4
  51. xp/models/conbus/__init__.py +1 -0
  52. xp/models/conbus/conbus.py +34 -4
  53. xp/models/conbus/conbus_autoreport.py +20 -2
  54. xp/models/conbus/conbus_blink.py +22 -2
  55. xp/models/conbus/conbus_client_config.py +22 -1
  56. xp/models/conbus/conbus_connection_status.py +16 -2
  57. xp/models/conbus/conbus_custom.py +21 -2
  58. xp/models/conbus/conbus_datapoint.py +25 -2
  59. xp/models/conbus/conbus_discover.py +18 -2
  60. xp/models/conbus/conbus_lightlevel.py +20 -2
  61. xp/models/conbus/conbus_linknumber.py +20 -2
  62. xp/models/conbus/conbus_output.py +22 -2
  63. xp/models/conbus/conbus_raw.py +17 -2
  64. xp/models/conbus/conbus_receive.py +16 -2
  65. xp/models/conbus/conbus_writeconfig.py +60 -0
  66. xp/models/homekit/__init__.py +1 -0
  67. xp/models/homekit/homekit_accessory.py +15 -1
  68. xp/models/homekit/homekit_config.py +52 -0
  69. xp/models/homekit/homekit_conson_config.py +32 -0
  70. xp/models/log_entry.py +49 -9
  71. xp/models/protocol/__init__.py +1 -0
  72. xp/models/protocol/conbus_protocol.py +130 -21
  73. xp/models/telegram/__init__.py +1 -0
  74. xp/models/telegram/action_type.py +16 -2
  75. xp/models/telegram/datapoint_type.py +36 -2
  76. xp/models/telegram/event_telegram.py +46 -10
  77. xp/models/telegram/event_type.py +8 -1
  78. xp/models/telegram/input_action_type.py +34 -2
  79. xp/models/telegram/input_type.py +9 -1
  80. xp/models/telegram/module_type.py +69 -19
  81. xp/models/telegram/module_type_code.py +43 -1
  82. xp/models/telegram/output_telegram.py +30 -6
  83. xp/models/telegram/reply_telegram.py +56 -11
  84. xp/models/telegram/system_function.py +35 -3
  85. xp/models/telegram/system_telegram.py +18 -4
  86. xp/models/telegram/telegram.py +12 -3
  87. xp/models/telegram/telegram_type.py +8 -1
  88. xp/models/telegram/timeparam_type.py +27 -0
  89. xp/models/write_config_type.py +17 -2
  90. xp/services/__init__.py +1 -1
  91. xp/services/conbus/__init__.py +1 -0
  92. xp/services/conbus/actiontable/__init__.py +1 -0
  93. xp/services/conbus/actiontable/actiontable_service.py +33 -2
  94. xp/services/conbus/actiontable/msactiontable_service.py +40 -3
  95. xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +36 -4
  96. xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +45 -5
  97. xp/services/conbus/conbus_blink_all_service.py +40 -21
  98. xp/services/conbus/conbus_blink_service.py +37 -13
  99. xp/services/conbus/conbus_custom_service.py +29 -13
  100. xp/services/conbus/conbus_datapoint_queryall_service.py +40 -16
  101. xp/services/conbus/conbus_datapoint_service.py +42 -18
  102. xp/services/conbus/conbus_discover_service.py +43 -7
  103. xp/services/conbus/conbus_output_service.py +33 -13
  104. xp/services/conbus/conbus_raw_service.py +36 -16
  105. xp/services/conbus/conbus_receive_service.py +38 -6
  106. xp/services/conbus/conbus_scan_service.py +44 -18
  107. xp/services/conbus/write_config_service.py +193 -0
  108. xp/services/homekit/__init__.py +1 -0
  109. xp/services/homekit/homekit_cache_service.py +31 -6
  110. xp/services/homekit/homekit_conbus_service.py +33 -2
  111. xp/services/homekit/homekit_config_validator.py +97 -15
  112. xp/services/homekit/homekit_conson_validator.py +51 -7
  113. xp/services/homekit/homekit_dimminglight.py +47 -1
  114. xp/services/homekit/homekit_dimminglight_service.py +35 -1
  115. xp/services/homekit/homekit_hap_service.py +71 -18
  116. xp/services/homekit/homekit_lightbulb.py +35 -1
  117. xp/services/homekit/homekit_lightbulb_service.py +30 -2
  118. xp/services/homekit/homekit_module_service.py +23 -1
  119. xp/services/homekit/homekit_outlet.py +47 -1
  120. xp/services/homekit/homekit_outlet_service.py +44 -2
  121. xp/services/homekit/homekit_service.py +113 -19
  122. xp/services/log_file_service.py +37 -41
  123. xp/services/module_type_service.py +26 -5
  124. xp/services/protocol/__init__.py +1 -1
  125. xp/services/protocol/conbus_protocol.py +110 -16
  126. xp/services/protocol/protocol_factory.py +40 -0
  127. xp/services/protocol/telegram_protocol.py +38 -7
  128. xp/services/reverse_proxy_service.py +79 -14
  129. xp/services/server/__init__.py +1 -0
  130. xp/services/server/base_server_service.py +102 -14
  131. xp/services/server/cp20_server_service.py +12 -4
  132. xp/services/server/server_service.py +26 -11
  133. xp/services/server/xp130_server_service.py +11 -3
  134. xp/services/server/xp20_server_service.py +11 -3
  135. xp/services/server/xp230_server_service.py +11 -3
  136. xp/services/server/xp24_server_service.py +33 -6
  137. xp/services/server/xp33_server_service.py +41 -8
  138. xp/services/telegram/__init__.py +1 -0
  139. xp/services/telegram/telegram_blink_service.py +19 -31
  140. xp/services/telegram/telegram_checksum_service.py +10 -10
  141. xp/services/telegram/telegram_datapoint_service.py +70 -0
  142. xp/services/telegram/telegram_discover_service.py +58 -29
  143. xp/services/telegram/telegram_link_number_service.py +27 -40
  144. xp/services/telegram/telegram_output_service.py +46 -49
  145. xp/services/telegram/telegram_service.py +41 -41
  146. xp/services/telegram/telegram_version_service.py +4 -2
  147. xp/utils/__init__.py +1 -1
  148. xp/utils/dependencies.py +4 -47
  149. xp/utils/serialization.py +6 -0
  150. xp/utils/time_utils.py +6 -11
  151. conson_xp-1.1.0.dist-info/RECORD +0 -181
  152. xp/api/__init__.py +0 -1
  153. xp/api/main.py +0 -110
  154. xp/api/models/__init__.py +0 -1
  155. xp/api/models/api.py +0 -20
  156. xp/api/models/discover.py +0 -21
  157. xp/api/routers/__init__.py +0 -17
  158. xp/api/routers/conbus.py +0 -5
  159. xp/api/routers/conbus_blink.py +0 -105
  160. xp/api/routers/conbus_custom.py +0 -63
  161. xp/api/routers/conbus_datapoint.py +0 -67
  162. xp/api/routers/conbus_output.py +0 -147
  163. xp/api/routers/errors.py +0 -37
  164. xp/cli/commands/api.py +0 -16
  165. xp/cli/commands/api_start_commands.py +0 -126
  166. xp/services/conbus/conbus_autoreport_get_service.py +0 -85
  167. xp/services/conbus/conbus_autoreport_set_service.py +0 -128
  168. xp/services/conbus/conbus_lightlevel_get_service.py +0 -101
  169. xp/services/conbus/conbus_lightlevel_set_service.py +0 -205
  170. xp/services/conbus/conbus_linknumber_get_service.py +0 -86
  171. xp/services/conbus/conbus_linknumber_set_service.py +0 -155
  172. {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/WHEEL +0 -0
  173. {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/entry_points.txt +0 -0
  174. {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -13,7 +13,7 @@ from xp.services.server.base_server_service import BaseServerService
13
13
 
14
14
 
15
15
  class XP24ServerError(Exception):
16
- """Raised when XP24 server operations fail"""
16
+ """Raised when XP24 server operations fail."""
17
17
 
18
18
  pass
19
19
 
@@ -27,7 +27,11 @@ class XP24ServerService(BaseServerService):
27
27
  """
28
28
 
29
29
  def __init__(self, serial_number: str):
30
- """Initialize XP24 server service"""
30
+ """Initialize XP24 server service.
31
+
32
+ Args:
33
+ serial_number: The device serial number.
34
+ """
31
35
  super().__init__(serial_number)
32
36
  self.device_type = "XP24"
33
37
  self.module_type_code = 7 # XP24 module type from registry
@@ -36,7 +40,7 @@ class XP24ServerService(BaseServerService):
36
40
  def _handle_device_specific_data_request(
37
41
  self, request: SystemTelegram
38
42
  ) -> Optional[str]:
39
- """Handle XP24-specific data requests"""
43
+ """Handle XP24-specific data requests."""
40
44
  if (
41
45
  request.system_function != SystemFunction.READ_DATAPOINT
42
46
  or not request.datapoint_type
@@ -49,7 +53,12 @@ class XP24ServerService(BaseServerService):
49
53
  DataPointType.MODULE_STATE: "OFF",
50
54
  DataPointType.MODULE_OPERATING_HOURS: "00:000[H],01:000[H],02:000[H],03:000[H]",
51
55
  }
52
- data_part = f"R{self.serial_number}F02{datapoint_type.value}{self.module_type_code}{datapoint_values.get(datapoint_type)}"
56
+ data_part = (
57
+ f"R{self.serial_number}"
58
+ f"F02{datapoint_type.value}"
59
+ f"{self.module_type_code}"
60
+ f"{datapoint_values.get(datapoint_type)}"
61
+ )
53
62
  telegram = self._build_response_telegram(data_part)
54
63
 
55
64
  self.logger.debug(
@@ -60,14 +69,25 @@ class XP24ServerService(BaseServerService):
60
69
  def _handle_device_specific_action_request(
61
70
  self, request: SystemTelegram
62
71
  ) -> Optional[str]:
72
+ """Handle XP24-specific action requests.
73
+
74
+ Args:
75
+ request: The system telegram request.
63
76
 
77
+ Returns:
78
+ The response telegram string, or None if request cannot be handled.
79
+ """
64
80
  if request.system_function != SystemFunction.ACTION:
65
81
  return None
66
82
 
67
83
  return self.generate_action_response(request)
68
84
 
69
85
  def get_device_info(self) -> Dict:
70
- """Get XP24 device information"""
86
+ """Get XP24 device information.
87
+
88
+ Returns:
89
+ Dictionary containing device information.
90
+ """
71
91
  return {
72
92
  "serial_number": self.serial_number,
73
93
  "device_type": self.device_type,
@@ -77,7 +97,14 @@ class XP24ServerService(BaseServerService):
77
97
  }
78
98
 
79
99
  def generate_action_response(self, request: SystemTelegram) -> Optional[str]:
80
- """Generate action response telegram (simulated)"""
100
+ """Generate action response telegram (simulated).
101
+
102
+ Args:
103
+ request: The system telegram request.
104
+
105
+ Returns:
106
+ The ACK or NAK response telegram string.
107
+ """
81
108
  response = "F19D" # NAK
82
109
  if (
83
110
  request.system_function == SystemFunction.ACTION
@@ -15,7 +15,7 @@ from xp.utils import calculate_checksum
15
15
 
16
16
 
17
17
  class XP33ServerError(Exception):
18
- """Raised when XP33 server operations fail"""
18
+ """Raised when XP33 server operations fail."""
19
19
 
20
20
  pass
21
21
 
@@ -29,7 +29,12 @@ class XP33ServerService(BaseServerService):
29
29
  """
30
30
 
31
31
  def __init__(self, serial_number: str, variant: str = "XP33LR"):
32
- """Initialize XP33 server service"""
32
+ """Initialize XP33 server service.
33
+
34
+ Args:
35
+ serial_number: The device serial number.
36
+ variant: Device variant (XP33, XP33LR, or XP33LED).
37
+ """
33
38
  super().__init__(serial_number)
34
39
  self.variant = variant # XP33 or XP33LR or XP33LED
35
40
  self.device_type = "XP33"
@@ -69,7 +74,7 @@ class XP33ServerService(BaseServerService):
69
74
  def _handle_device_specific_data_request(
70
75
  self, request: SystemTelegram
71
76
  ) -> Optional[str]:
72
- """Handle XP24-specific data requests"""
77
+ """Handle XP24-specific data requests."""
73
78
  if (
74
79
  request.system_function != SystemFunction.READ_DATAPOINT
75
80
  or not request.datapoint_type
@@ -82,7 +87,12 @@ class XP33ServerService(BaseServerService):
82
87
  DataPointType.MODULE_STATE: "OFF",
83
88
  DataPointType.MODULE_OPERATING_HOURS: "00:000[H],01:000[H],02:000[H]",
84
89
  }
85
- data_part = f"R{self.serial_number}F02{datapoint_type.value}{self.module_type_code}{datapoint_values.get(datapoint_type)}"
90
+ data_part = (
91
+ f"R{self.serial_number}"
92
+ f"F02{datapoint_type.value}"
93
+ f"{self.module_type_code}"
94
+ f"{datapoint_values.get(datapoint_type)}"
95
+ )
86
96
  checksum = calculate_checksum(data_part)
87
97
  telegram = f"<{data_part}{checksum}>"
88
98
 
@@ -92,7 +102,15 @@ class XP33ServerService(BaseServerService):
92
102
  return telegram
93
103
 
94
104
  def set_channel_dimming(self, channel: int, level: int) -> bool:
95
- """Set individual channel dimming level"""
105
+ """Set individual channel dimming level.
106
+
107
+ Args:
108
+ channel: Channel number (1-3).
109
+ level: Dimming level (0-100 percent).
110
+
111
+ Returns:
112
+ True if channel was set successfully, False otherwise.
113
+ """
96
114
  if 1 <= channel <= 3 and 0 <= level <= 100:
97
115
  self.channel_states[channel - 1] = level
98
116
  self.logger.info(f"XP33 channel {channel} set to {level}%")
@@ -100,7 +118,14 @@ class XP33ServerService(BaseServerService):
100
118
  return False
101
119
 
102
120
  def activate_scene(self, scene: int) -> bool:
103
- """Activate a pre-programmed scene"""
121
+ """Activate a pre-programmed scene.
122
+
123
+ Args:
124
+ scene: Scene number (1-4).
125
+
126
+ Returns:
127
+ True if scene was activated successfully, False otherwise.
128
+ """
104
129
  if scene in self.scenes:
105
130
  self.channel_states = self.scenes[scene].copy()
106
131
  self.logger.info(f"XP33 scene {scene} activated: {self.channel_states}")
@@ -108,7 +133,11 @@ class XP33ServerService(BaseServerService):
108
133
  return False
109
134
 
110
135
  def get_device_info(self) -> Dict:
111
- """Get XP33 device information"""
136
+ """Get XP33 device information.
137
+
138
+ Returns:
139
+ Dictionary containing device information.
140
+ """
112
141
  return {
113
142
  "serial_number": self.serial_number,
114
143
  "device_type": self.device_type,
@@ -123,7 +152,11 @@ class XP33ServerService(BaseServerService):
123
152
  }
124
153
 
125
154
  def get_technical_specs(self) -> Dict:
126
- """Get technical specifications"""
155
+ """Get technical specifications.
156
+
157
+ Returns:
158
+ Dictionary containing technical specifications.
159
+ """
127
160
  if self.variant == "XP33LED":
128
161
  return {
129
162
  "power_per_channel": "100VA",
@@ -0,0 +1 @@
1
+ """Telegram parsing and processing services."""
@@ -11,7 +11,7 @@ from xp.utils.checksum import calculate_checksum
11
11
 
12
12
 
13
13
  class BlinkError(Exception):
14
- """Raised when blink/unblink operations fail"""
14
+ """Raised when blink/unblink operations fail."""
15
15
 
16
16
  pass
17
17
 
@@ -26,24 +26,22 @@ class TelegramBlinkService:
26
26
  """
27
27
 
28
28
  def __init__(self) -> None:
29
- """Initialize the blink service"""
29
+ """Initialize the blink service."""
30
30
  pass
31
31
 
32
32
  @staticmethod
33
33
  def generate_blink_telegram(serial_number: str, on_or_off: str) -> str:
34
- """
35
- Generate a telegram to start blinking a module's LED.
34
+ """Generate a telegram to start blinking a module's LED.
36
35
 
37
36
  Args:
38
- serial_number: The 10-digit module serial number
37
+ serial_number: The 10-digit module serial number.
38
+ on_or_off: The action to perform ('on' for blink, 'off' for unblink).
39
39
 
40
40
  Returns:
41
- Formatted telegram string (e.g., "<S0012345008F05D00FN>")
41
+ Formatted telegram string (e.g., "<S0012345008F05D00FN>").
42
42
 
43
43
  Raises:
44
- BlinkError: If parameters are invalid
45
- :param serial_number:
46
- :param on_or_off:
44
+ BlinkError: If parameters are invalid.
47
45
  """
48
46
  # Validate serial number
49
47
  if not serial_number or len(serial_number) != 10:
@@ -68,17 +66,13 @@ class TelegramBlinkService:
68
66
  return telegram
69
67
 
70
68
  def create_blink_telegram_object(self, serial_number: str) -> SystemTelegram:
71
- """
72
- Create a SystemTelegram object for blinking LED.
69
+ """Create a SystemTelegram object for blinking LED.
73
70
 
74
71
  Args:
75
- serial_number: The 10-digit module serial number
72
+ serial_number: The 10-digit module serial number.
76
73
 
77
74
  Returns:
78
- SystemTelegram object representing the blink command
79
-
80
- Raises:
81
- BlinkError: If parameters are invalid
75
+ SystemTelegram object representing the blink command.
82
76
  """
83
77
  raw_telegram = self.generate_blink_telegram(serial_number, "on")
84
78
 
@@ -96,17 +90,13 @@ class TelegramBlinkService:
96
90
  return telegram
97
91
 
98
92
  def create_unblink_telegram_object(self, serial_number: str) -> SystemTelegram:
99
- """
100
- Create a SystemTelegram object for unblink LED.
93
+ """Create a SystemTelegram object for unblink LED.
101
94
 
102
95
  Args:
103
- serial_number: The 10-digit module serial number
96
+ serial_number: The 10-digit module serial number.
104
97
 
105
98
  Returns:
106
- SystemTelegram object representing the unblink command
107
-
108
- Raises:
109
- BlinkError: If parameters are invalid
99
+ SystemTelegram object representing the unblink command.
110
100
  """
111
101
  raw_telegram = self.generate_blink_telegram(serial_number, "off")
112
102
 
@@ -125,26 +115,24 @@ class TelegramBlinkService:
125
115
 
126
116
  @staticmethod
127
117
  def is_ack_response(reply_telegram: ReplyTelegram) -> bool:
128
- """
129
- Check if a reply telegram is an ACK response.
118
+ """Check if a reply telegram is an ACK response.
130
119
 
131
120
  Args:
132
- reply_telegram: Reply telegram to check
121
+ reply_telegram: Reply telegram to check.
133
122
 
134
123
  Returns:
135
- True if this is an ACK response (F18D), False otherwise
124
+ True if this is an ACK response (F18D), False otherwise.
136
125
  """
137
126
  return reply_telegram.system_function == SystemFunction.ACK
138
127
 
139
128
  @staticmethod
140
129
  def is_nak_response(reply_telegram: ReplyTelegram) -> bool:
141
- """
142
- Check if a reply telegram is a NAK response.
130
+ """Check if a reply telegram is a NAK response.
143
131
 
144
132
  Args:
145
- reply_telegram: Reply telegram to check
133
+ reply_telegram: Reply telegram to check.
146
134
 
147
135
  Returns:
148
- True if this is a NAK response (F19D), False otherwise
136
+ True if this is a NAK response (F19D), False otherwise.
149
137
  """
150
138
  return reply_telegram.system_function == SystemFunction.NAK
@@ -22,10 +22,10 @@ class TelegramChecksumService:
22
22
  """Calculate simple XOR checksum for string data.
23
23
 
24
24
  Args:
25
- data: String data to calculate checksum for
25
+ data: String data to calculate checksum for.
26
26
 
27
27
  Returns:
28
- Response object with checksum result
28
+ Response object with checksum result.
29
29
  """
30
30
  try:
31
31
  checksum = calculate_checksum(data)
@@ -45,10 +45,10 @@ class TelegramChecksumService:
45
45
  """Calculate CRC32 checksum for data.
46
46
 
47
47
  Args:
48
- data: String or bytes data to calculate checksum for
48
+ data: String or bytes data to calculate checksum for.
49
49
 
50
50
  Returns:
51
- Response object with checksum result
51
+ Response object with checksum result.
52
52
  """
53
53
  try:
54
54
  # Convert string to bytes if needed
@@ -82,11 +82,11 @@ class TelegramChecksumService:
82
82
  """Validate data against expected simple checksum.
83
83
 
84
84
  Args:
85
- data: Original data
86
- expected_checksum: Expected checksum value
85
+ data: Original data.
86
+ expected_checksum: Expected checksum value.
87
87
 
88
88
  Returns:
89
- Response object with validation result
89
+ Response object with validation result.
90
90
  """
91
91
  try:
92
92
  calculated_checksum = calculate_checksum(data)
@@ -114,11 +114,11 @@ class TelegramChecksumService:
114
114
  """Validate data against expected CRC32 checksum.
115
115
 
116
116
  Args:
117
- data: Original data (string or bytes)
118
- expected_checksum: Expected CRC32 checksum value
117
+ data: Original data (string or bytes).
118
+ expected_checksum: Expected CRC32 checksum value.
119
119
 
120
120
  Returns:
121
- Response object with validation result
121
+ Response object with validation result.
122
122
  """
123
123
  try:
124
124
  # Convert string to bytes if needed
@@ -0,0 +1,70 @@
1
+ """Service for processing Telegram protocol datapoint values."""
2
+
3
+
4
+ class TelegramDatapointService:
5
+ """Service for processing Telegram protocol datapoint values.
6
+
7
+ Provides methods to parse and extract values from different types of
8
+ Telegram datapoints including autoreport status, light level outputs,
9
+ and link number values.
10
+ """
11
+
12
+ def get_autoreport_status(self, data_value: str) -> bool:
13
+ """Get the autoreport status value.
14
+
15
+ Args:
16
+ data_value: The raw autoreport status data value (PP or AA).
17
+
18
+ Returns:
19
+ The autoreport status: Enable (True) or disable (False).
20
+ """
21
+ status_value = True if data_value == "PP" else False
22
+ return status_value
23
+
24
+ def get_autoreport_status_data_value(self, status_value: bool) -> str:
25
+ """Get the autoreport status data_value.
26
+
27
+ Args:
28
+ status_value: Enable (True) or disable (False).
29
+
30
+ Returns:
31
+ data_value: The raw autoreport status data value (PP or AA).
32
+ """
33
+ data_value = "PP" if status_value else "AA"
34
+ return data_value
35
+
36
+ def get_lightlevel(self, data_value: str, output_number: int) -> int:
37
+ """Extract the light level for a specific output number.
38
+
39
+ Parses comma-separated output data in the format "output:level[%]"
40
+ and returns the level for the requested output number.
41
+
42
+ Args:
43
+ data_value: Comma-separated string of output:level pairs
44
+ (e.g., "1:50[%],2:75[%]").
45
+ output_number: The output number to get the level for.
46
+
47
+ Returns:
48
+ The light level as an integer (0 if output not found).
49
+ """
50
+ level = 0
51
+ for output_data in data_value.split(","):
52
+ if ":" in output_data:
53
+ output_str, level_str = output_data.split(":")
54
+ if int(output_str) == output_number:
55
+ level_str = level_str.replace("[%]", "")
56
+ level = int(level_str)
57
+ break
58
+ return level
59
+
60
+ def get_linknumber(self, data_value: str) -> int:
61
+ """Parse and return the link number value.
62
+
63
+ Args:
64
+ data_value: The raw link number data value as a string.
65
+
66
+ Returns:
67
+ The link number as an integer.
68
+ """
69
+ link_number_value = int(data_value)
70
+ return link_number_value
@@ -13,30 +13,51 @@ from xp.utils.checksum import calculate_checksum
13
13
 
14
14
 
15
15
  class DiscoverError(Exception):
16
- """Raised when discover operations fail"""
16
+ """Raised when discover operations fail."""
17
17
 
18
18
  pass
19
19
 
20
20
 
21
21
  class DeviceInfo:
22
- """Information about a discovered device"""
22
+ """Information about a discovered device."""
23
23
 
24
24
  def __init__(
25
25
  self, serial_number: str, checksum_valid: bool = True, raw_telegram: str = ""
26
26
  ):
27
+ """Initialize device info.
28
+
29
+ Args:
30
+ serial_number: 10-digit module serial number.
31
+ checksum_valid: Whether the telegram checksum is valid.
32
+ raw_telegram: Raw telegram string.
33
+ """
27
34
  self.serial_number = serial_number
28
35
  self.checksum_valid = checksum_valid
29
36
  self.raw_telegram = raw_telegram
30
37
 
31
38
  def __str__(self) -> str:
39
+ """Return string representation of device.
40
+
41
+ Returns:
42
+ String with serial number and checksum status.
43
+ """
32
44
  status = "✓" if self.checksum_valid else "✗"
33
45
  return f"Device {self.serial_number} ({status})"
34
46
 
35
47
  def __repr__(self) -> str:
48
+ """Return repr representation of device.
49
+
50
+ Returns:
51
+ DeviceInfo constructor representation.
52
+ """
36
53
  return f"DeviceInfo(serial='{self.serial_number}', checksum_valid={self.checksum_valid})"
37
54
 
38
55
  def to_dict(self) -> dict:
39
- """Convert to dictionary for JSON serialization"""
56
+ """Convert to dictionary for JSON serialization.
57
+
58
+ Returns:
59
+ Dictionary with device information.
60
+ """
40
61
  return {
41
62
  "serial_number": self.serial_number,
42
63
  "checksum_valid": self.checksum_valid,
@@ -54,7 +75,7 @@ class TelegramDiscoverService:
54
75
  """
55
76
 
56
77
  def __init__(self) -> None:
57
- """Initialize the discover service"""
78
+ """Initialize the discover service."""
58
79
  pass
59
80
 
60
81
  @staticmethod
@@ -78,11 +99,10 @@ class TelegramDiscoverService:
78
99
  return telegram
79
100
 
80
101
  def create_discover_telegram_object(self) -> SystemTelegram:
81
- """
82
- Create a SystemTelegram object for discover broadcast.
102
+ """Create a SystemTelegram object for discover broadcast.
83
103
 
84
104
  Returns:
85
- SystemTelegram object representing the discover command
105
+ SystemTelegram object representing the discover command.
86
106
  """
87
107
  raw_telegram = self.generate_discover_telegram()
88
108
 
@@ -101,20 +121,26 @@ class TelegramDiscoverService:
101
121
 
102
122
  @staticmethod
103
123
  def is_discover_response(reply_telegram: ReplyTelegram) -> bool:
104
- """
105
- Check if a reply telegram is a discover response.
124
+ """Check if a reply telegram is a discover response.
106
125
 
107
126
  Args:
108
- reply_telegram: Reply telegram to check
127
+ reply_telegram: Reply telegram to check.
109
128
 
110
129
  Returns:
111
- True if this is a discover response, False otherwise
130
+ True if this is a discover response, False otherwise.
112
131
  """
113
132
  return reply_telegram.system_function == SystemFunction.DISCOVERY
114
133
 
115
134
  @staticmethod
116
135
  def _generate_discover_response(serial_number: str) -> str:
117
- """Generate discover response telegram for a device"""
136
+ """Generate discover response telegram for a device.
137
+
138
+ Args:
139
+ serial_number: 10-digit module serial number.
140
+
141
+ Returns:
142
+ Formatted discover response telegram.
143
+ """
118
144
  # Format: <R{serial}F01D{checksum}>
119
145
  data_part = f"R{serial_number}F01D"
120
146
  checksum = calculate_checksum(data_part)
@@ -123,14 +149,13 @@ class TelegramDiscoverService:
123
149
 
124
150
  @staticmethod
125
151
  def get_unique_devices(devices: List[DeviceInfo]) -> List[DeviceInfo]:
126
- """
127
- Filter out duplicate devices based on serial number.
152
+ """Filter out duplicate devices based on serial number.
128
153
 
129
154
  Args:
130
- devices: List of discovered devices
155
+ devices: List of discovered devices.
131
156
 
132
157
  Returns:
133
- List of unique devices (first occurrence of each serial number)
158
+ List of unique devices (first occurrence of each serial number).
134
159
  """
135
160
  seen_serials: Set[str] = set()
136
161
  unique_devices = []
@@ -144,14 +169,13 @@ class TelegramDiscoverService:
144
169
 
145
170
  @staticmethod
146
171
  def validate_discover_response_format(raw_telegram: str) -> bool:
147
- """
148
- Validate if a raw telegram matches discover response format.
172
+ """Validate if a raw telegram matches discover response format.
149
173
 
150
174
  Args:
151
- raw_telegram: Raw telegram string to validate
175
+ raw_telegram: Raw telegram string to validate.
152
176
 
153
177
  Returns:
154
- True if format matches discover response pattern
178
+ True if format matches discover response pattern.
155
179
  """
156
180
  # Discover response format: <R{10-digit-serial}F01D{2-char-checksum}>
157
181
  import re
@@ -163,14 +187,13 @@ class TelegramDiscoverService:
163
187
  return match is not None
164
188
 
165
189
  def generate_discover_summary(self, devices: List[DeviceInfo]) -> dict:
166
- """
167
- Generate a summary of a discover results.
190
+ """Generate a summary of a discover results.
168
191
 
169
192
  Args:
170
- devices: List of discovered devices
193
+ devices: List of discovered devices.
171
194
 
172
195
  Returns:
173
- Dictionary with discover statistics
196
+ Dictionary with discover statistics.
174
197
  """
175
198
  unique_devices = self.get_unique_devices(devices)
176
199
  valid_devices = [d for d in unique_devices if d.checksum_valid]
@@ -200,14 +223,13 @@ class TelegramDiscoverService:
200
223
  }
201
224
 
202
225
  def format_discover_results(self, devices: List[DeviceInfo]) -> str:
203
- """
204
- Format discover results for human-readable output.
226
+ """Format discover results for human-readable output.
205
227
 
206
228
  Args:
207
- devices: List of discovered devices
229
+ devices: List of discovered devices.
208
230
 
209
231
  Returns:
210
- Formatted string summary
232
+ Formatted string summary.
211
233
  """
212
234
  if not devices:
213
235
  return "No devices discovered"
@@ -241,7 +263,14 @@ class TelegramDiscoverService:
241
263
 
242
264
  @staticmethod
243
265
  def is_discover_request(telegram: SystemTelegram) -> bool:
244
- """Check if telegram is a discover request"""
266
+ """Check if telegram is a discover request.
267
+
268
+ Args:
269
+ telegram: System telegram to check.
270
+
271
+ Returns:
272
+ True if this is a discover request, False otherwise.
273
+ """
245
274
  return (
246
275
  telegram.system_function == SystemFunction.DISCOVERY
247
276
  and telegram.serial_number == "0000000000"