conson-xp 1.1.0__py3-none-any.whl → 1.2.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 (167) hide show
  1. {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/METADATA +1 -1
  2. conson_xp-1.2.0.dist-info/RECORD +181 -0
  3. xp/__init__.py +4 -3
  4. xp/api/main.py +18 -3
  5. xp/api/models/api.py +13 -2
  6. xp/api/models/discover.py +12 -2
  7. xp/api/routers/conbus_blink.py +18 -6
  8. xp/api/routers/conbus_custom.py +11 -3
  9. xp/api/routers/conbus_datapoint.py +10 -3
  10. xp/api/routers/conbus_output.py +29 -9
  11. xp/api/routers/errors.py +6 -5
  12. xp/cli/__init__.py +1 -1
  13. xp/cli/commands/__init__.py +1 -0
  14. xp/cli/commands/api.py +1 -5
  15. xp/cli/commands/api_start_commands.py +14 -8
  16. xp/cli/commands/conbus/conbus.py +9 -37
  17. xp/cli/commands/conbus/conbus_actiontable_commands.py +21 -1
  18. xp/cli/commands/conbus/conbus_autoreport_commands.py +21 -11
  19. xp/cli/commands/conbus/conbus_blink_commands.py +53 -21
  20. xp/cli/commands/conbus/conbus_config_commands.py +7 -4
  21. xp/cli/commands/conbus/conbus_custom_commands.py +13 -4
  22. xp/cli/commands/conbus/conbus_datapoint_commands.py +28 -8
  23. xp/cli/commands/conbus/conbus_discover_commands.py +15 -4
  24. xp/cli/commands/conbus/conbus_lightlevel_commands.py +50 -11
  25. xp/cli/commands/conbus/conbus_linknumber_commands.py +21 -11
  26. xp/cli/commands/conbus/conbus_msactiontable_commands.py +25 -1
  27. xp/cli/commands/conbus/conbus_output_commands.py +46 -12
  28. xp/cli/commands/conbus/conbus_raw_commands.py +17 -6
  29. xp/cli/commands/conbus/conbus_receive_commands.py +15 -7
  30. xp/cli/commands/conbus/conbus_scan_commands.py +17 -4
  31. xp/cli/commands/file_commands.py +26 -15
  32. xp/cli/commands/homekit/homekit.py +14 -8
  33. xp/cli/commands/homekit/homekit_start_commands.py +5 -5
  34. xp/cli/commands/module_commands.py +26 -19
  35. xp/cli/commands/reverse_proxy_commands.py +24 -18
  36. xp/cli/commands/server/server_commands.py +18 -18
  37. xp/cli/commands/telegram/telegram.py +4 -12
  38. xp/cli/commands/telegram/telegram_blink_commands.py +10 -8
  39. xp/cli/commands/telegram/telegram_checksum_commands.py +19 -8
  40. xp/cli/commands/telegram/telegram_discover_commands.py +2 -4
  41. xp/cli/commands/telegram/telegram_linknumber_commands.py +11 -8
  42. xp/cli/commands/telegram/telegram_parse_commands.py +10 -9
  43. xp/cli/commands/telegram/telegram_version_commands.py +8 -4
  44. xp/cli/main.py +5 -1
  45. xp/cli/utils/click_tree.py +23 -3
  46. xp/cli/utils/datapoint_type_choice.py +20 -0
  47. xp/cli/utils/decorators.py +165 -14
  48. xp/cli/utils/error_handlers.py +49 -18
  49. xp/cli/utils/formatters.py +95 -10
  50. xp/cli/utils/serial_number_type.py +18 -0
  51. xp/cli/utils/system_function_choice.py +20 -0
  52. xp/cli/utils/xp_module_type.py +20 -0
  53. xp/connection/__init__.py +1 -1
  54. xp/connection/exceptions.py +5 -5
  55. xp/models/__init__.py +1 -1
  56. xp/models/actiontable/__init__.py +1 -0
  57. xp/models/actiontable/actiontable.py +17 -1
  58. xp/models/actiontable/msactiontable_xp20.py +10 -0
  59. xp/models/actiontable/msactiontable_xp24.py +20 -3
  60. xp/models/actiontable/msactiontable_xp33.py +27 -4
  61. xp/models/conbus/__init__.py +1 -0
  62. xp/models/conbus/conbus.py +34 -4
  63. xp/models/conbus/conbus_autoreport.py +20 -2
  64. xp/models/conbus/conbus_blink.py +22 -2
  65. xp/models/conbus/conbus_client_config.py +22 -1
  66. xp/models/conbus/conbus_connection_status.py +16 -2
  67. xp/models/conbus/conbus_custom.py +21 -2
  68. xp/models/conbus/conbus_datapoint.py +22 -2
  69. xp/models/conbus/conbus_discover.py +18 -2
  70. xp/models/conbus/conbus_lightlevel.py +20 -2
  71. xp/models/conbus/conbus_linknumber.py +20 -2
  72. xp/models/conbus/conbus_output.py +22 -2
  73. xp/models/conbus/conbus_raw.py +17 -2
  74. xp/models/conbus/conbus_receive.py +16 -2
  75. xp/models/homekit/__init__.py +1 -0
  76. xp/models/homekit/homekit_accessory.py +15 -1
  77. xp/models/homekit/homekit_config.py +52 -0
  78. xp/models/homekit/homekit_conson_config.py +32 -0
  79. xp/models/log_entry.py +49 -9
  80. xp/models/protocol/__init__.py +1 -0
  81. xp/models/protocol/conbus_protocol.py +130 -21
  82. xp/models/telegram/__init__.py +1 -0
  83. xp/models/telegram/action_type.py +16 -2
  84. xp/models/telegram/datapoint_type.py +36 -2
  85. xp/models/telegram/event_telegram.py +46 -10
  86. xp/models/telegram/event_type.py +8 -1
  87. xp/models/telegram/input_action_type.py +34 -2
  88. xp/models/telegram/input_type.py +9 -1
  89. xp/models/telegram/module_type.py +69 -19
  90. xp/models/telegram/module_type_code.py +43 -1
  91. xp/models/telegram/output_telegram.py +30 -6
  92. xp/models/telegram/reply_telegram.py +56 -11
  93. xp/models/telegram/system_function.py +35 -3
  94. xp/models/telegram/system_telegram.py +18 -4
  95. xp/models/telegram/telegram.py +12 -3
  96. xp/models/telegram/telegram_type.py +8 -1
  97. xp/models/telegram/timeparam_type.py +27 -0
  98. xp/models/write_config_type.py +17 -2
  99. xp/services/__init__.py +1 -1
  100. xp/services/conbus/__init__.py +1 -0
  101. xp/services/conbus/actiontable/__init__.py +1 -0
  102. xp/services/conbus/actiontable/actiontable_service.py +33 -2
  103. xp/services/conbus/actiontable/msactiontable_service.py +40 -3
  104. xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +36 -4
  105. xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +45 -5
  106. xp/services/conbus/conbus_autoreport_get_service.py +17 -8
  107. xp/services/conbus/conbus_autoreport_set_service.py +29 -16
  108. xp/services/conbus/conbus_blink_all_service.py +40 -21
  109. xp/services/conbus/conbus_blink_service.py +37 -13
  110. xp/services/conbus/conbus_custom_service.py +29 -13
  111. xp/services/conbus/conbus_datapoint_queryall_service.py +40 -16
  112. xp/services/conbus/conbus_datapoint_service.py +33 -12
  113. xp/services/conbus/conbus_discover_service.py +43 -7
  114. xp/services/conbus/conbus_lightlevel_get_service.py +22 -14
  115. xp/services/conbus/conbus_lightlevel_set_service.py +40 -20
  116. xp/services/conbus/conbus_linknumber_get_service.py +18 -10
  117. xp/services/conbus/conbus_linknumber_set_service.py +34 -8
  118. xp/services/conbus/conbus_output_service.py +33 -13
  119. xp/services/conbus/conbus_raw_service.py +36 -16
  120. xp/services/conbus/conbus_receive_service.py +38 -6
  121. xp/services/conbus/conbus_scan_service.py +44 -18
  122. xp/services/homekit/__init__.py +1 -0
  123. xp/services/homekit/homekit_cache_service.py +31 -6
  124. xp/services/homekit/homekit_conbus_service.py +33 -2
  125. xp/services/homekit/homekit_config_validator.py +97 -15
  126. xp/services/homekit/homekit_conson_validator.py +51 -7
  127. xp/services/homekit/homekit_dimminglight.py +47 -1
  128. xp/services/homekit/homekit_dimminglight_service.py +35 -1
  129. xp/services/homekit/homekit_hap_service.py +71 -18
  130. xp/services/homekit/homekit_lightbulb.py +35 -1
  131. xp/services/homekit/homekit_lightbulb_service.py +30 -2
  132. xp/services/homekit/homekit_module_service.py +23 -1
  133. xp/services/homekit/homekit_outlet.py +47 -1
  134. xp/services/homekit/homekit_outlet_service.py +44 -2
  135. xp/services/homekit/homekit_service.py +113 -19
  136. xp/services/log_file_service.py +37 -41
  137. xp/services/module_type_service.py +26 -5
  138. xp/services/protocol/__init__.py +1 -1
  139. xp/services/protocol/conbus_protocol.py +110 -16
  140. xp/services/protocol/protocol_factory.py +40 -0
  141. xp/services/protocol/telegram_protocol.py +38 -7
  142. xp/services/reverse_proxy_service.py +79 -14
  143. xp/services/server/__init__.py +1 -0
  144. xp/services/server/base_server_service.py +102 -14
  145. xp/services/server/cp20_server_service.py +12 -4
  146. xp/services/server/server_service.py +26 -11
  147. xp/services/server/xp130_server_service.py +11 -3
  148. xp/services/server/xp20_server_service.py +11 -3
  149. xp/services/server/xp230_server_service.py +11 -3
  150. xp/services/server/xp24_server_service.py +33 -6
  151. xp/services/server/xp33_server_service.py +41 -8
  152. xp/services/telegram/__init__.py +1 -0
  153. xp/services/telegram/telegram_blink_service.py +19 -31
  154. xp/services/telegram/telegram_checksum_service.py +10 -10
  155. xp/services/telegram/telegram_discover_service.py +58 -29
  156. xp/services/telegram/telegram_link_number_service.py +27 -40
  157. xp/services/telegram/telegram_output_service.py +46 -49
  158. xp/services/telegram/telegram_service.py +41 -41
  159. xp/services/telegram/telegram_version_service.py +4 -2
  160. xp/utils/__init__.py +1 -1
  161. xp/utils/dependencies.py +0 -1
  162. xp/utils/serialization.py +6 -0
  163. xp/utils/time_utils.py +6 -11
  164. conson_xp-1.1.0.dist-info/RECORD +0 -181
  165. {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/WHEEL +0 -0
  166. {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/entry_points.txt +0 -0
  167. {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,3 +1,5 @@
1
+ """Module type models for the XP system."""
2
+
1
3
  from dataclasses import dataclass
2
4
  from typing import Dict, List, Optional
3
5
 
@@ -6,25 +8,35 @@ from xp.models.telegram.module_type_code import MODULE_TYPE_REGISTRY
6
8
 
7
9
  @dataclass
8
10
  class ModuleType:
9
- """
10
- Represents a module type in the XP system.
11
+ """Represents a module type in the XP system.
12
+
11
13
  Contains the module code, name, and description.
14
+
15
+ Attributes:
16
+ code: Numeric module type code.
17
+ name: Module name.
18
+ description: Module description.
19
+ module_category: Module category.
20
+ is_reserved: True if module type is reserved.
21
+ is_push_button_panel: True if module is a push button panel.
22
+ is_ir_capable: True if module has IR capabilities.
23
+ category: Module category based on its type.
12
24
  """
13
25
 
14
26
  code: int
15
27
  name: str
16
28
  description: str
29
+ module_category: str = ""
17
30
 
18
31
  @classmethod
19
32
  def from_code(cls, code: int) -> Optional["ModuleType"]:
20
- """
21
- Create ModuleType from a numeric code.
33
+ """Create ModuleType from a numeric code.
22
34
 
23
35
  Args:
24
- code: The numeric module type code
36
+ code: The numeric module type code.
25
37
 
26
38
  Returns:
27
- ModuleType instance or None if code is invalid
39
+ ModuleType instance or None if code is invalid.
28
40
  """
29
41
  module_info = MODULE_TYPE_REGISTRY.get(code)
30
42
  if module_info:
@@ -33,14 +45,13 @@ class ModuleType:
33
45
 
34
46
  @classmethod
35
47
  def from_name(cls, name: str) -> Optional["ModuleType"]:
36
- """
37
- Create ModuleType from a module name.
48
+ """Create ModuleType from a module name.
38
49
 
39
50
  Args:
40
- name: The module name (case-insensitive)
51
+ name: The module name (case-insensitive).
41
52
 
42
53
  Returns:
43
- ModuleType instance or None if name is invalid
54
+ ModuleType instance or None if name is invalid.
44
55
  """
45
56
  name_upper = name.upper()
46
57
  for code, info in MODULE_TYPE_REGISTRY.items():
@@ -50,12 +61,20 @@ class ModuleType:
50
61
 
51
62
  @property
52
63
  def is_reserved(self) -> bool:
53
- """Check if this module type is reserved"""
64
+ """Check if this module type is reserved.
65
+
66
+ Returns:
67
+ True if module type is reserved, False otherwise.
68
+ """
54
69
  return self.name in ("XP26X1", "XP26X2")
55
70
 
56
71
  @property
57
72
  def is_push_button_panel(self) -> bool:
58
- """Check if this module type is a push button panel"""
73
+ """Check if this module type is a push button panel.
74
+
75
+ Returns:
76
+ True if module is a push button panel, False otherwise.
77
+ """
59
78
  return self.name in (
60
79
  "XP2606",
61
80
  "XP2606A",
@@ -68,7 +87,11 @@ class ModuleType:
68
87
 
69
88
  @property
70
89
  def is_ir_capable(self) -> bool:
71
- """Check if this module type has IR capabilities"""
90
+ """Check if this module type has IR capabilities.
91
+
92
+ Returns:
93
+ True if module has IR capabilities, False otherwise.
94
+ """
72
95
  return any(ir_type in self.name for ir_type in ("38kHz", "B&O")) or any(
73
96
  ir_code in self.name
74
97
  for ir_code in (
@@ -83,7 +106,11 @@ class ModuleType:
83
106
 
84
107
  @property
85
108
  def category(self) -> str:
86
- """Get the module category based on its type"""
109
+ """Get the module category based on its type.
110
+
111
+ Returns:
112
+ Module category string.
113
+ """
87
114
  if self.code <= 1:
88
115
  return "System"
89
116
  elif 2 <= self.code <= 6:
@@ -95,7 +122,11 @@ class ModuleType:
95
122
  return "Unknown"
96
123
 
97
124
  def to_dict(self) -> Dict:
98
- """Convert to dictionary for JSON serialization"""
125
+ """Convert to dictionary for JSON serialization.
126
+
127
+ Returns:
128
+ Dictionary representation of the module type.
129
+ """
99
130
  return {
100
131
  "code": self.code,
101
132
  "name": self.name,
@@ -107,12 +138,20 @@ class ModuleType:
107
138
  }
108
139
 
109
140
  def __str__(self) -> str:
110
- """Human-readable string representation"""
141
+ """Return human-readable string representation.
142
+
143
+ Returns:
144
+ Formatted string representation.
145
+ """
111
146
  return f"{self.name} (Code {self.code}): {self.description}"
112
147
 
113
148
 
114
149
  def get_all_module_types() -> List[ModuleType]:
115
- """Get all available module types"""
150
+ """Get all available module types.
151
+
152
+ Returns:
153
+ List of all ModuleType instances.
154
+ """
116
155
  return [
117
156
  module_type
118
157
  for module_type in [
@@ -123,7 +162,11 @@ def get_all_module_types() -> List[ModuleType]:
123
162
 
124
163
 
125
164
  def get_module_types_by_category() -> Dict[str, List[ModuleType]]:
126
- """Get module types grouped by category"""
165
+ """Get module types grouped by category.
166
+
167
+ Returns:
168
+ Dictionary mapping category names to lists of ModuleType instances.
169
+ """
127
170
  categories: Dict[str, List[ModuleType]] = {}
128
171
  for module_type in get_all_module_types():
129
172
  category = module_type.category
@@ -134,5 +177,12 @@ def get_module_types_by_category() -> Dict[str, List[ModuleType]]:
134
177
 
135
178
 
136
179
  def is_valid_module_code(code: int) -> bool:
137
- """Check if a module code is valid"""
180
+ """Check if a module code is valid.
181
+
182
+ Args:
183
+ code: Module code to validate.
184
+
185
+ Returns:
186
+ True if code is valid, False otherwise.
187
+ """
138
188
  return code in MODULE_TYPE_REGISTRY
@@ -1,9 +1,51 @@
1
+ """Module type code enumeration for XP system modules."""
2
+
1
3
  from enum import Enum
2
4
  from typing import Dict
3
5
 
4
6
 
5
7
  class ModuleTypeCode(Enum):
6
- """Enum representing all XP system module type codes"""
8
+ """Enum representing all XP system module type codes.
9
+
10
+ Attributes:
11
+ NOMOD: No module (code 0).
12
+ ALLMOD: Matches every module type (code 1).
13
+ CP20: CP switch link module (code 2).
14
+ CP70A: CP 38kHz IR link module (code 3).
15
+ CP70B: CP B&O IR link module (code 4).
16
+ CP70C: CP UHF link module (code 5).
17
+ CP70D: CP timer link module (code 6).
18
+ XP24: XP relay module (code 7).
19
+ XP31UNI: XP universal load light dimmer (code 8).
20
+ XP31BCU: XP ballast controller 0-10V (code 9).
21
+ XP31DD: XP ballast controller DSI/DALI (code 10).
22
+ XP33: XP 3-channel light dimmer (code 11).
23
+ CP485: CP RS485 interface module (code 12).
24
+ XP130: Ethernet/TCP/IP interface module (code 13).
25
+ XP2606: 5-way push button panel with sesam (code 14).
26
+ XP2606A: 5-way push button with IR receiver (code 15).
27
+ XP2606B: 5-way push button with B&O IR (code 16).
28
+ XP26X1: Reserved (code 17).
29
+ XP26X2: Reserved (code 18).
30
+ XP2506: 5-way push button panel with sesam Conson design (code 19).
31
+ XP2506A: 5-way push button panel with sesam and 38kHz IR Conson design (code 20).
32
+ XP2506B: 5-way push button panel with sesam and B&O IR Conson design (code 21).
33
+ XPX1_8: 8-way push button panel interface (code 22).
34
+ XP134: Junctionbox interlink (code 23).
35
+ XP24P: XP24P module (code 24).
36
+ XP28A: XP28A module (code 25).
37
+ XP28B: XP28B module (code 26).
38
+ CONTOOL: CONTOOL module (code 27).
39
+ XP28: XP28 module (code 28).
40
+ XP31LR: XP 1-channel lightdimmer (code 29).
41
+ XP33LR: XP 33 3-channel lightdimmer (code 30).
42
+ XP31CR: XP 31 1-channel dimmer (code 31).
43
+ XP31BC: XP 31 1-channel dimmer (code 32).
44
+ XP20: XP switch link module (code 33).
45
+ XP230: Ethernet/TCPIP interface module (code 34).
46
+ XP33LED: XP 3-channel LED dimmer (code 35).
47
+ XP31LED: XP 1-channel LED dimmer (code 36).
48
+ """
7
49
 
8
50
  NOMOD = 0 # No module
9
51
  ALLMOD = 1 # Code matching every moduletype
@@ -17,11 +17,18 @@ from xp.models.telegram.telegram import Telegram
17
17
 
18
18
  @dataclass
19
19
  class OutputTelegram(Telegram):
20
- """
21
- Represents a parsed XP output telegram from the console bus.
20
+ """Represent a parsed XP output telegram from the console bus.
22
21
 
23
22
  Format: <S{serial_number}F27D{input:02d}{action}{checksum}>
24
23
  Examples: <S0012345008F27D00AAFN>
24
+
25
+ Attributes:
26
+ serial_number: Serial number of the device.
27
+ output_number: Output number (0-3 for XP24, 0-2 for XP33, 0 for XP31).
28
+ action_type: Type of action to perform.
29
+ system_function: System function code.
30
+ action_description: Human-readable action description.
31
+ input_description: Human-readable input description.
25
32
  """
26
33
 
27
34
  serial_number: str = ""
@@ -32,12 +39,17 @@ class OutputTelegram(Telegram):
32
39
  system_function: SystemFunction = SystemFunction.ACTION
33
40
 
34
41
  def __post_init__(self) -> None:
42
+ """Initialize timestamp if not provided."""
35
43
  if self.timestamp is None:
36
44
  self.timestamp = datetime.now()
37
45
 
38
46
  @property
39
47
  def action_description(self) -> str:
40
- """Get human-readable action description"""
48
+ """Get human-readable action description.
49
+
50
+ Returns:
51
+ Human-readable description of the action.
52
+ """
41
53
  descriptions = {
42
54
  ActionType.OFF_PRESS: "Press (Make)",
43
55
  ActionType.ON_RELEASE: "Release (Break)",
@@ -50,11 +62,19 @@ class OutputTelegram(Telegram):
50
62
 
51
63
  @property
52
64
  def input_description(self) -> str:
53
- """Get human-readable input description"""
65
+ """Get human-readable input description.
66
+
67
+ Returns:
68
+ Description of the input/output number.
69
+ """
54
70
  return f"Input {self.output_number}"
55
71
 
56
72
  def to_dict(self) -> dict:
57
- """Convert to dictionary for JSON serialization"""
73
+ """Convert to dictionary for JSON serialization.
74
+
75
+ Returns:
76
+ Dictionary representation of the output telegram.
77
+ """
58
78
  return {
59
79
  "serial_number": self.serial_number,
60
80
  "system_function": self.system_function,
@@ -71,7 +91,11 @@ class OutputTelegram(Telegram):
71
91
  }
72
92
 
73
93
  def __str__(self) -> str:
74
- """Human-readable string representation"""
94
+ """Return human-readable string representation.
95
+
96
+ Returns:
97
+ Formatted string representation.
98
+ """
75
99
  return (
76
100
  f"XP Output: {self.action_description} "
77
101
  f"on {self.input_description} "
@@ -16,11 +16,11 @@ from xp.models.telegram.telegram_type import TelegramType
16
16
 
17
17
  @dataclass
18
18
  class ReplyTelegram(Telegram):
19
- """
20
- Represents a parsed reply telegram from the console bus.
19
+ """Represents a parsed reply telegram from the console bus.
21
20
 
22
21
  Format: <R{serial_number}F{function_code}D{data}{checksum}>
23
22
  Format: <R{serial_number}F{function_code}D{datapoint_type}{data_value}{checksum}>
23
+
24
24
  Examples:
25
25
  - raw_telegram: <R0020012521F02D18+26,0§CIL>
26
26
  - telegram_type : ReplyTelegram (R)
@@ -30,6 +30,14 @@ class ReplyTelegram(Telegram):
30
30
  - datapoint_type: 18
31
31
  - data_value: +26,0§C
32
32
  - checksum: IL
33
+
34
+ Attributes:
35
+ serial_number: Serial number of the device.
36
+ system_function: System function code.
37
+ data: Raw data payload.
38
+ datapoint_type: Type of datapoint.
39
+ data_value: Parsed data value.
40
+ parse_datapoint_value: Parsed value based on datapoint type.
33
41
  """
34
42
 
35
43
  serial_number: str = ""
@@ -39,13 +47,18 @@ class ReplyTelegram(Telegram):
39
47
  data_value: str = ""
40
48
 
41
49
  def __post_init__(self) -> None:
50
+ """Initialize timestamp and telegram type."""
42
51
  if self.timestamp is None:
43
52
  self.timestamp = datetime.now()
44
53
  self.telegram_type = TelegramType.REPLY
45
54
 
46
55
  @property
47
56
  def parse_datapoint_value(self) -> dict[str, Any]:
48
- """Parse the data value based on data point type"""
57
+ """Parse the data value based on data point type.
58
+
59
+ Returns:
60
+ Dictionary containing parsed value and metadata.
61
+ """
49
62
  if self.datapoint_type == DataPointType.TEMPERATURE:
50
63
  return self._parse_temperature_value()
51
64
  elif self.datapoint_type == DataPointType.SW_TOP_VERSION:
@@ -61,7 +74,11 @@ class ReplyTelegram(Telegram):
61
74
  return {"raw_value": self.data_value, "parsed": False}
62
75
 
63
76
  def _parse_temperature_value(self) -> dict:
64
- """Parse temperature value like '+26,0§C'"""
77
+ """Parse temperature value like '+26,0§C'.
78
+
79
+ Returns:
80
+ Dictionary containing parsed temperature value and metadata.
81
+ """
65
82
  try:
66
83
  # Remove unit indicator (§C)
67
84
  value_part = self.data_value.replace("§C", "")
@@ -84,7 +101,11 @@ class ReplyTelegram(Telegram):
84
101
  }
85
102
 
86
103
  def _parse_humidity_value(self) -> dict:
87
- """Parse humidity value like '+65,5§H'"""
104
+ """Parse humidity value like '+65,5§H'.
105
+
106
+ Returns:
107
+ Dictionary containing parsed humidity value and metadata.
108
+ """
88
109
  try:
89
110
  # Remove unit indicator (§H)
90
111
  value_part = self.data_value.replace("§RH", "")
@@ -107,7 +128,11 @@ class ReplyTelegram(Telegram):
107
128
  }
108
129
 
109
130
  def _parse_voltage_value(self) -> dict:
110
- """Parse voltage value like '+12,5§V'"""
131
+ """Parse voltage value like '+12,5§V'.
132
+
133
+ Returns:
134
+ Dictionary containing parsed voltage value and metadata.
135
+ """
111
136
  try:
112
137
  # Remove unit indicator (§V)
113
138
  value_part = self.data_value.replace("§V", "")
@@ -130,7 +155,11 @@ class ReplyTelegram(Telegram):
130
155
  }
131
156
 
132
157
  def _parse_current_value(self) -> dict:
133
- """Parse current value like '+0,25§A'"""
158
+ """Parse current value like '+0,25§A'.
159
+
160
+ Returns:
161
+ Dictionary containing parsed current value and metadata.
162
+ """
134
163
  try:
135
164
  # Remove unit indicator (§A)
136
165
  value_part = self.data_value.replace("§A", "")
@@ -153,7 +182,11 @@ class ReplyTelegram(Telegram):
153
182
  }
154
183
 
155
184
  def _parse_module_type_value(self) -> dict:
156
- """Parse status value"""
185
+ """Parse status value.
186
+
187
+ Returns:
188
+ Dictionary containing parsed module type value.
189
+ """
157
190
  # Status values are typically alphanumeric codes
158
191
  return {
159
192
  "module_type": self.data_value,
@@ -162,7 +195,11 @@ class ReplyTelegram(Telegram):
162
195
  }
163
196
 
164
197
  def _parse_sw_version_value(self) -> dict:
165
- """Parse version value like 'XP230_V1.00.04'"""
198
+ """Parse version value like 'XP230_V1.00.04'.
199
+
200
+ Returns:
201
+ Dictionary containing parsed version information.
202
+ """
166
203
  try:
167
204
  # Version format: {PRODUCT}_{VERSION}
168
205
  # Examples: XP230_V1.00.04, XP20_V0.01.05, XP33LR_V0.04.02, XP24_V0.34.03
@@ -198,7 +235,11 @@ class ReplyTelegram(Telegram):
198
235
  }
199
236
 
200
237
  def to_dict(self) -> dict[str, Any]:
201
- """Convert to dictionary for JSON serialization"""
238
+ """Convert to dictionary for JSON serialization.
239
+
240
+ Returns:
241
+ Dictionary representation of the reply telegram.
242
+ """
202
243
  parsed_data = self.parse_datapoint_value
203
244
 
204
245
  return {
@@ -234,7 +275,11 @@ class ReplyTelegram(Telegram):
234
275
  }
235
276
 
236
277
  def __str__(self) -> str:
237
- """Human-readable string representation"""
278
+ """Human-readable string representation.
279
+
280
+ Returns:
281
+ Formatted string representation.
282
+ """
238
283
  parsed = self.parse_datapoint_value
239
284
  if parsed.get("parsed", False) and "formatted" in parsed:
240
285
  value_display = parsed["formatted"]
@@ -1,9 +1,30 @@
1
+ """System function enumeration for system telegrams."""
2
+
1
3
  from enum import Enum
2
4
  from typing import Optional
3
5
 
4
6
 
5
7
  class SystemFunction(str, Enum):
6
- """System function codes for system telegrams"""
8
+ """System function codes for system telegrams.
9
+
10
+ Attributes:
11
+ NONE: Undefined function.
12
+ DISCOVERY: Discover function.
13
+ READ_DATAPOINT: Read datapoint.
14
+ READ_CONFIG: Read configuration.
15
+ WRITE_CONFIG: Write configuration.
16
+ BLINK: Blink LED function.
17
+ UNBLINK: Unblink LED function.
18
+ DOWNLOAD_MSACTIONTABLE: Download module specific action table.
19
+ DOWNLOAD_ACTIONTABLE: Download ActionTable.
20
+ EOF: End of msactiontable response.
21
+ MSACTIONTABLE: Module specific action table response.
22
+ ACTIONTABLE: Module specific action table response.
23
+ ACK: Acknowledge response.
24
+ NAK: Not acknowledge response.
25
+ UNKNOWN_26: Used after discover, unknown purpose.
26
+ ACTION: Action function.
27
+ """
7
28
 
8
29
  NONE = "00" # Undefined
9
30
  DISCOVERY = "01" # Discover function
@@ -25,7 +46,11 @@ class SystemFunction(str, Enum):
25
46
  ACTION = "27" # Action function
26
47
 
27
48
  def get_description(self) -> str:
28
- """Get the description of the SystemFunction"""
49
+ """Get the description of the SystemFunction.
50
+
51
+ Returns:
52
+ Human-readable description of the function.
53
+ """
29
54
  return (
30
55
  {
31
56
  self.DISCOVERY: "Discover function",
@@ -47,7 +72,14 @@ class SystemFunction(str, Enum):
47
72
 
48
73
  @classmethod
49
74
  def from_code(cls, code: str) -> Optional["SystemFunction"]:
50
- """Get SystemFunction from code string"""
75
+ """Get SystemFunction from code string.
76
+
77
+ Args:
78
+ code: Function code string.
79
+
80
+ Returns:
81
+ SystemFunction instance if found, None otherwise.
82
+ """
51
83
  for func in cls:
52
84
  if func.value.lower() == code.lower():
53
85
  return func
@@ -16,11 +16,16 @@ from xp.models.telegram.telegram_type import TelegramType
16
16
 
17
17
  @dataclass
18
18
  class SystemTelegram(Telegram):
19
- """
20
- Represents a parsed system telegram from the console bus.
19
+ """Represents a parsed system telegram from the console bus.
21
20
 
22
21
  Format: <S{serial_number}F{function_code}D{datapoint_type}{checksum}>
23
22
  Examples: <S0020012521F02D18FN>
23
+
24
+ Attributes:
25
+ serial_number: Serial number of the device.
26
+ system_function: System function code.
27
+ data: Data payload.
28
+ datapoint_type: Type of datapoint.
24
29
  """
25
30
 
26
31
  serial_number: str = ""
@@ -29,12 +34,17 @@ class SystemTelegram(Telegram):
29
34
  datapoint_type: Optional[DataPointType] = None
30
35
 
31
36
  def __post_init__(self) -> None:
37
+ """Initialize timestamp and telegram type."""
32
38
  if self.timestamp is None:
33
39
  self.timestamp = datetime.now()
34
40
  self.telegram_type = TelegramType.SYSTEM
35
41
 
36
42
  def to_dict(self) -> dict[str, Any]:
37
- """Convert to dictionary for JSON serialization"""
43
+ """Convert to dictionary for JSON serialization.
44
+
45
+ Returns:
46
+ Dictionary representation of the system telegram.
47
+ """
38
48
  return {
39
49
  "serial_number": self.serial_number,
40
50
  "system_function": (
@@ -67,7 +77,11 @@ class SystemTelegram(Telegram):
67
77
  }
68
78
 
69
79
  def __str__(self) -> str:
70
- """Human-readable string representation"""
80
+ """Human-readable string representation.
81
+
82
+ Returns:
83
+ Formatted string representation.
84
+ """
71
85
  system_func_name = (
72
86
  self.system_function.name if self.system_function else "Unknown"
73
87
  )
@@ -1,3 +1,5 @@
1
+ """Base telegram model for console bus communication."""
2
+
1
3
  from dataclasses import dataclass
2
4
  from datetime import datetime
3
5
  from typing import Optional
@@ -7,9 +9,16 @@ from xp.models.telegram.telegram_type import TelegramType
7
9
 
8
10
  @dataclass
9
11
  class Telegram:
10
- """
11
- Represents an abstract telegram from the console bus.
12
- Can be an EventTelegram, SystemTelegram or ReplyTelegram
12
+ """Represents an abstract telegram from the console bus.
13
+
14
+ Can be an EventTelegram, SystemTelegram or ReplyTelegram.
15
+
16
+ Attributes:
17
+ checksum: Telegram checksum value.
18
+ raw_telegram: Raw telegram string.
19
+ checksum_validated: Whether checksum validation passed.
20
+ timestamp: Timestamp when telegram was received.
21
+ telegram_type: Type of telegram (EVENT, SYSTEM, or REPLY).
13
22
  """
14
23
 
15
24
  checksum: str
@@ -4,7 +4,14 @@ from enum import Enum
4
4
 
5
5
 
6
6
  class TelegramType(str, Enum):
7
- """Enumeration of telegram types in the console bus system."""
7
+ """Enumeration of telegram types in the console bus system.
8
+
9
+ Attributes:
10
+ EVENT: Event telegram (E).
11
+ REPLY: Reply telegram (R).
12
+ SYSTEM: System telegram (S).
13
+ CPEVENT: CP event telegram (O).
14
+ """
8
15
 
9
16
  EVENT = "E"
10
17
  REPLY = "R"
@@ -1,7 +1,34 @@
1
+ """Time parameter enumeration for telegram actions."""
2
+
1
3
  from enum import IntEnum
2
4
 
3
5
 
4
6
  class TimeParam(IntEnum):
7
+ """Time parameter values for action timing.
8
+
9
+ Attributes:
10
+ NONE: No time parameter.
11
+ T05SEC: 0.5 second delay.
12
+ T1SEC: 1 second delay.
13
+ T2SEC: 2 second delay.
14
+ T5SEC: 5 second delay.
15
+ T10SEC: 10 second delay.
16
+ T15SEC: 15 second delay.
17
+ T20SEC: 20 second delay.
18
+ T30SEC: 30 second delay.
19
+ T45SEC: 45 second delay.
20
+ T1MIN: 1 minute delay.
21
+ T2MIN: 2 minute delay.
22
+ T5MIN: 5 minute delay.
23
+ T10MIN: 10 minute delay.
24
+ T15MIN: 15 minute delay.
25
+ T20MIN: 20 minute delay.
26
+ T30MIN: 30 minute delay.
27
+ T45MIN: 45 minute delay.
28
+ T60MIN: 60 minute delay.
29
+ T120MIN: 120 minute delay.
30
+ """
31
+
5
32
  NONE = 0
6
33
  T05SEC = 1
7
34
  T1SEC = 2
@@ -1,9 +1,17 @@
1
+ """Write config type enumeration."""
2
+
1
3
  from enum import Enum
2
4
  from typing import Optional
3
5
 
4
6
 
5
7
  class WriteConfigType(str, Enum):
6
- """Write Config types for system telegrams"""
8
+ """Write Config types for system telegrams.
9
+
10
+ Attributes:
11
+ LINK_NUMBER: Link number configuration (code 04).
12
+ MODULE_NUMBER: Module number configuration (code 05).
13
+ SYSTEM_TYPE: System type configuration (code 06).
14
+ """
7
15
 
8
16
  LINK_NUMBER = "04"
9
17
  MODULE_NUMBER = "05"
@@ -11,7 +19,14 @@ class WriteConfigType(str, Enum):
11
19
 
12
20
  @classmethod
13
21
  def from_code(cls, code: str) -> Optional["WriteConfigType"]:
14
- """Get DataPointType from code string"""
22
+ """Get WriteConfigType from code string.
23
+
24
+ Args:
25
+ code: Configuration type code string.
26
+
27
+ Returns:
28
+ WriteConfigType instance if found, None otherwise.
29
+ """
15
30
  for dp_type in cls:
16
31
  if dp_type.value == code:
17
32
  return dp_type
xp/services/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- """Service layer for XP CLI tool"""
1
+ """Service layer for XP CLI tool."""
2
2
 
3
3
  from xp.services.log_file_service import LogFileParsingError, LogFileService
4
4
  from xp.services.module_type_service import ModuleTypeNotFoundError, ModuleTypeService
@@ -0,0 +1 @@
1
+ """Conbus service layer."""