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.
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/METADATA +1 -5
- conson_xp-1.3.0.dist-info/RECORD +164 -0
- xp/__init__.py +4 -3
- xp/cli/__init__.py +1 -1
- xp/cli/commands/__init__.py +1 -2
- xp/cli/commands/conbus/conbus.py +9 -37
- xp/cli/commands/conbus/conbus_actiontable_commands.py +26 -4
- xp/cli/commands/conbus/conbus_autoreport_commands.py +58 -30
- xp/cli/commands/conbus/conbus_blink_commands.py +61 -29
- xp/cli/commands/conbus/conbus_config_commands.py +10 -5
- xp/cli/commands/conbus/conbus_custom_commands.py +16 -5
- xp/cli/commands/conbus/conbus_datapoint_commands.py +32 -10
- xp/cli/commands/conbus/conbus_discover_commands.py +20 -7
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +114 -39
- xp/cli/commands/conbus/conbus_linknumber_commands.py +50 -25
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +36 -5
- xp/cli/commands/conbus/conbus_output_commands.py +52 -14
- xp/cli/commands/conbus/conbus_raw_commands.py +17 -6
- xp/cli/commands/conbus/conbus_receive_commands.py +20 -10
- xp/cli/commands/conbus/conbus_scan_commands.py +17 -4
- xp/cli/commands/file_commands.py +35 -18
- xp/cli/commands/homekit/homekit.py +14 -8
- xp/cli/commands/homekit/homekit_start_commands.py +8 -6
- xp/cli/commands/module_commands.py +38 -23
- xp/cli/commands/reverse_proxy_commands.py +27 -19
- xp/cli/commands/server/server_commands.py +18 -18
- xp/cli/commands/telegram/telegram.py +4 -12
- xp/cli/commands/telegram/telegram_blink_commands.py +10 -8
- xp/cli/commands/telegram/telegram_checksum_commands.py +19 -8
- xp/cli/commands/telegram/telegram_discover_commands.py +2 -4
- xp/cli/commands/telegram/telegram_linknumber_commands.py +11 -8
- xp/cli/commands/telegram/telegram_parse_commands.py +10 -9
- xp/cli/commands/telegram/telegram_version_commands.py +8 -4
- xp/cli/main.py +5 -3
- xp/cli/utils/click_tree.py +23 -3
- xp/cli/utils/datapoint_type_choice.py +20 -0
- xp/cli/utils/decorators.py +165 -14
- xp/cli/utils/error_handlers.py +49 -18
- xp/cli/utils/formatters.py +95 -10
- xp/cli/utils/serial_number_type.py +18 -0
- xp/cli/utils/system_function_choice.py +20 -0
- xp/cli/utils/xp_module_type.py +20 -0
- xp/connection/__init__.py +1 -1
- xp/connection/exceptions.py +5 -5
- xp/models/__init__.py +1 -1
- xp/models/actiontable/__init__.py +1 -0
- xp/models/actiontable/actiontable.py +17 -1
- xp/models/actiontable/msactiontable_xp20.py +10 -0
- xp/models/actiontable/msactiontable_xp24.py +20 -3
- xp/models/actiontable/msactiontable_xp33.py +27 -4
- xp/models/conbus/__init__.py +1 -0
- xp/models/conbus/conbus.py +34 -4
- xp/models/conbus/conbus_autoreport.py +20 -2
- xp/models/conbus/conbus_blink.py +22 -2
- xp/models/conbus/conbus_client_config.py +22 -1
- xp/models/conbus/conbus_connection_status.py +16 -2
- xp/models/conbus/conbus_custom.py +21 -2
- xp/models/conbus/conbus_datapoint.py +25 -2
- xp/models/conbus/conbus_discover.py +18 -2
- xp/models/conbus/conbus_lightlevel.py +20 -2
- xp/models/conbus/conbus_linknumber.py +20 -2
- xp/models/conbus/conbus_output.py +22 -2
- xp/models/conbus/conbus_raw.py +17 -2
- xp/models/conbus/conbus_receive.py +16 -2
- xp/models/conbus/conbus_writeconfig.py +60 -0
- xp/models/homekit/__init__.py +1 -0
- xp/models/homekit/homekit_accessory.py +15 -1
- xp/models/homekit/homekit_config.py +52 -0
- xp/models/homekit/homekit_conson_config.py +32 -0
- xp/models/log_entry.py +49 -9
- xp/models/protocol/__init__.py +1 -0
- xp/models/protocol/conbus_protocol.py +130 -21
- xp/models/telegram/__init__.py +1 -0
- xp/models/telegram/action_type.py +16 -2
- xp/models/telegram/datapoint_type.py +36 -2
- xp/models/telegram/event_telegram.py +46 -10
- xp/models/telegram/event_type.py +8 -1
- xp/models/telegram/input_action_type.py +34 -2
- xp/models/telegram/input_type.py +9 -1
- xp/models/telegram/module_type.py +69 -19
- xp/models/telegram/module_type_code.py +43 -1
- xp/models/telegram/output_telegram.py +30 -6
- xp/models/telegram/reply_telegram.py +56 -11
- xp/models/telegram/system_function.py +35 -3
- xp/models/telegram/system_telegram.py +18 -4
- xp/models/telegram/telegram.py +12 -3
- xp/models/telegram/telegram_type.py +8 -1
- xp/models/telegram/timeparam_type.py +27 -0
- xp/models/write_config_type.py +17 -2
- xp/services/__init__.py +1 -1
- xp/services/conbus/__init__.py +1 -0
- xp/services/conbus/actiontable/__init__.py +1 -0
- xp/services/conbus/actiontable/actiontable_service.py +33 -2
- xp/services/conbus/actiontable/msactiontable_service.py +40 -3
- xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +36 -4
- xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +45 -5
- xp/services/conbus/conbus_blink_all_service.py +40 -21
- xp/services/conbus/conbus_blink_service.py +37 -13
- xp/services/conbus/conbus_custom_service.py +29 -13
- xp/services/conbus/conbus_datapoint_queryall_service.py +40 -16
- xp/services/conbus/conbus_datapoint_service.py +42 -18
- xp/services/conbus/conbus_discover_service.py +43 -7
- xp/services/conbus/conbus_output_service.py +33 -13
- xp/services/conbus/conbus_raw_service.py +36 -16
- xp/services/conbus/conbus_receive_service.py +38 -6
- xp/services/conbus/conbus_scan_service.py +44 -18
- xp/services/conbus/write_config_service.py +193 -0
- xp/services/homekit/__init__.py +1 -0
- xp/services/homekit/homekit_cache_service.py +31 -6
- xp/services/homekit/homekit_conbus_service.py +33 -2
- xp/services/homekit/homekit_config_validator.py +97 -15
- xp/services/homekit/homekit_conson_validator.py +51 -7
- xp/services/homekit/homekit_dimminglight.py +47 -1
- xp/services/homekit/homekit_dimminglight_service.py +35 -1
- xp/services/homekit/homekit_hap_service.py +71 -18
- xp/services/homekit/homekit_lightbulb.py +35 -1
- xp/services/homekit/homekit_lightbulb_service.py +30 -2
- xp/services/homekit/homekit_module_service.py +23 -1
- xp/services/homekit/homekit_outlet.py +47 -1
- xp/services/homekit/homekit_outlet_service.py +44 -2
- xp/services/homekit/homekit_service.py +113 -19
- xp/services/log_file_service.py +37 -41
- xp/services/module_type_service.py +26 -5
- xp/services/protocol/__init__.py +1 -1
- xp/services/protocol/conbus_protocol.py +110 -16
- xp/services/protocol/protocol_factory.py +40 -0
- xp/services/protocol/telegram_protocol.py +38 -7
- xp/services/reverse_proxy_service.py +79 -14
- xp/services/server/__init__.py +1 -0
- xp/services/server/base_server_service.py +102 -14
- xp/services/server/cp20_server_service.py +12 -4
- xp/services/server/server_service.py +26 -11
- xp/services/server/xp130_server_service.py +11 -3
- xp/services/server/xp20_server_service.py +11 -3
- xp/services/server/xp230_server_service.py +11 -3
- xp/services/server/xp24_server_service.py +33 -6
- xp/services/server/xp33_server_service.py +41 -8
- xp/services/telegram/__init__.py +1 -0
- xp/services/telegram/telegram_blink_service.py +19 -31
- xp/services/telegram/telegram_checksum_service.py +10 -10
- xp/services/telegram/telegram_datapoint_service.py +70 -0
- xp/services/telegram/telegram_discover_service.py +58 -29
- xp/services/telegram/telegram_link_number_service.py +27 -40
- xp/services/telegram/telegram_output_service.py +46 -49
- xp/services/telegram/telegram_service.py +41 -41
- xp/services/telegram/telegram_version_service.py +4 -2
- xp/utils/__init__.py +1 -1
- xp/utils/dependencies.py +4 -47
- xp/utils/serialization.py +6 -0
- xp/utils/time_utils.py +6 -11
- conson_xp-1.1.0.dist-info/RECORD +0 -181
- xp/api/__init__.py +0 -1
- xp/api/main.py +0 -110
- xp/api/models/__init__.py +0 -1
- xp/api/models/api.py +0 -20
- xp/api/models/discover.py +0 -21
- xp/api/routers/__init__.py +0 -17
- xp/api/routers/conbus.py +0 -5
- xp/api/routers/conbus_blink.py +0 -105
- xp/api/routers/conbus_custom.py +0 -63
- xp/api/routers/conbus_datapoint.py +0 -67
- xp/api/routers/conbus_output.py +0 -147
- xp/api/routers/errors.py +0 -37
- xp/cli/commands/api.py +0 -16
- xp/cli/commands/api_start_commands.py +0 -126
- xp/services/conbus/conbus_autoreport_get_service.py +0 -85
- xp/services/conbus/conbus_autoreport_set_service.py +0 -128
- xp/services/conbus/conbus_lightlevel_get_service.py +0 -101
- xp/services/conbus/conbus_lightlevel_set_service.py +0 -205
- xp/services/conbus/conbus_linknumber_get_service.py +0 -86
- xp/services/conbus/conbus_linknumber_set_service.py +0 -155
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/entry_points.txt +0 -0
- {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 =
|
|
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 =
|
|
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",
|
xp/services/telegram/__init__.py
CHANGED
|
@@ -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"
|