conson-xp 1.2.0__py3-none-any.whl → 1.4.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.2.0.dist-info → conson_xp-1.4.0.dist-info}/METADATA +1 -5
- {conson_xp-1.2.0.dist-info → conson_xp-1.4.0.dist-info}/RECORD +43 -60
- xp/__init__.py +1 -1
- xp/cli/commands/__init__.py +0 -2
- xp/cli/commands/conbus/conbus_actiontable_commands.py +5 -3
- xp/cli/commands/conbus/conbus_autoreport_commands.py +39 -21
- xp/cli/commands/conbus/conbus_blink_commands.py +8 -8
- xp/cli/commands/conbus/conbus_config_commands.py +3 -1
- xp/cli/commands/conbus/conbus_custom_commands.py +3 -1
- xp/cli/commands/conbus/conbus_datapoint_commands.py +4 -2
- xp/cli/commands/conbus/conbus_discover_commands.py +5 -3
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +68 -32
- xp/cli/commands/conbus/conbus_linknumber_commands.py +32 -17
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +11 -4
- xp/cli/commands/conbus/conbus_output_commands.py +6 -2
- xp/cli/commands/conbus/conbus_receive_commands.py +5 -3
- xp/cli/commands/file_commands.py +9 -3
- xp/cli/commands/homekit/homekit_start_commands.py +3 -1
- xp/cli/commands/module_commands.py +12 -4
- xp/cli/commands/reverse_proxy_commands.py +3 -1
- xp/cli/main.py +0 -2
- xp/models/conbus/conbus_datapoint.py +3 -0
- xp/models/conbus/conbus_discover.py +19 -3
- xp/models/conbus/conbus_writeconfig.py +60 -0
- xp/models/telegram/system_telegram.py +4 -4
- xp/services/conbus/conbus_datapoint_service.py +9 -6
- xp/services/conbus/conbus_discover_service.py +120 -2
- xp/services/conbus/conbus_scan_service.py +1 -1
- xp/services/conbus/{conbus_linknumber_set_service.py → write_config_service.py} +78 -66
- xp/services/protocol/telegram_protocol.py +4 -4
- xp/services/server/base_server_service.py +9 -4
- xp/services/server/cp20_server_service.py +2 -1
- xp/services/server/server_service.py +75 -4
- xp/services/server/xp130_server_service.py +2 -1
- xp/services/server/xp20_server_service.py +2 -1
- xp/services/server/xp230_server_service.py +2 -1
- xp/services/server/xp24_server_service.py +123 -50
- xp/services/server/xp33_server_service.py +150 -20
- xp/services/telegram/telegram_datapoint_service.py +70 -0
- xp/utils/dependencies.py +4 -46
- xp/api/__init__.py +0 -1
- xp/api/main.py +0 -125
- xp/api/models/__init__.py +0 -1
- xp/api/models/api.py +0 -31
- xp/api/models/discover.py +0 -31
- xp/api/routers/__init__.py +0 -17
- xp/api/routers/conbus.py +0 -5
- xp/api/routers/conbus_blink.py +0 -117
- xp/api/routers/conbus_custom.py +0 -71
- xp/api/routers/conbus_datapoint.py +0 -74
- xp/api/routers/conbus_output.py +0 -167
- xp/api/routers/errors.py +0 -38
- xp/cli/commands/api.py +0 -12
- xp/cli/commands/api_start_commands.py +0 -132
- xp/services/conbus/conbus_autoreport_get_service.py +0 -94
- xp/services/conbus/conbus_autoreport_set_service.py +0 -141
- xp/services/conbus/conbus_lightlevel_get_service.py +0 -109
- xp/services/conbus/conbus_lightlevel_set_service.py +0 -225
- xp/services/conbus/conbus_linknumber_get_service.py +0 -94
- {conson_xp-1.2.0.dist-info → conson_xp-1.4.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.2.0.dist-info → conson_xp-1.4.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.2.0.dist-info → conson_xp-1.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,6 +6,7 @@ including response generation and device configuration handling.
|
|
|
6
6
|
|
|
7
7
|
from typing import Dict
|
|
8
8
|
|
|
9
|
+
from xp.models import ModuleTypeCode
|
|
9
10
|
from xp.services.server.base_server_service import BaseServerService
|
|
10
11
|
|
|
11
12
|
|
|
@@ -31,7 +32,7 @@ class XP230ServerService(BaseServerService):
|
|
|
31
32
|
"""
|
|
32
33
|
super().__init__(serial_number)
|
|
33
34
|
self.device_type = "XP230"
|
|
34
|
-
self.module_type_code =
|
|
35
|
+
self.module_type_code = ModuleTypeCode.XP230 # XP230 module type from registry
|
|
35
36
|
self.firmware_version = "XP230_V1.00.04"
|
|
36
37
|
|
|
37
38
|
def get_device_info(self) -> Dict:
|
|
@@ -6,6 +6,7 @@ including response generation and device configuration handling.
|
|
|
6
6
|
|
|
7
7
|
from typing import Dict, Optional
|
|
8
8
|
|
|
9
|
+
from xp.models import ModuleTypeCode
|
|
9
10
|
from xp.models.telegram.datapoint_type import DataPointType
|
|
10
11
|
from xp.models.telegram.system_function import SystemFunction
|
|
11
12
|
from xp.models.telegram.system_telegram import SystemTelegram
|
|
@@ -18,6 +19,16 @@ class XP24ServerError(Exception):
|
|
|
18
19
|
pass
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
class XP24Output:
|
|
23
|
+
"""Represents an XP24 output state.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
state: Current state of the output (True=on, False=off).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
state: bool = False
|
|
30
|
+
|
|
31
|
+
|
|
21
32
|
class XP24ServerService(BaseServerService):
|
|
22
33
|
"""
|
|
23
34
|
XP24 device emulation service.
|
|
@@ -34,30 +45,107 @@ class XP24ServerService(BaseServerService):
|
|
|
34
45
|
"""
|
|
35
46
|
super().__init__(serial_number)
|
|
36
47
|
self.device_type = "XP24"
|
|
37
|
-
self.module_type_code =
|
|
48
|
+
self.module_type_code = ModuleTypeCode.XP24
|
|
49
|
+
self.autoreport_status = True
|
|
38
50
|
self.firmware_version = "XP24_V0.34.03"
|
|
51
|
+
self.output_0: XP24Output = XP24Output()
|
|
52
|
+
self.output_1: XP24Output = XP24Output()
|
|
53
|
+
self.output_2: XP24Output = XP24Output()
|
|
54
|
+
self.output_3: XP24Output = XP24Output()
|
|
55
|
+
|
|
56
|
+
def _handle_device_specific_action_request(
|
|
57
|
+
self, request: SystemTelegram
|
|
58
|
+
) -> Optional[str]:
|
|
59
|
+
"""Handle XP24-specific data requests."""
|
|
60
|
+
telegrams = self._handle_action_module_output_state(request.data)
|
|
61
|
+
self.logger.debug(
|
|
62
|
+
f"Generated {self.device_type} module type responses: {telegrams}"
|
|
63
|
+
)
|
|
64
|
+
return telegrams
|
|
65
|
+
|
|
66
|
+
def _handle_action_module_output_state(self, data_value: str) -> str:
|
|
67
|
+
"""Handle XP24-specific module output state."""
|
|
68
|
+
output_number = int(data_value[:2])
|
|
69
|
+
output_state = data_value[2:]
|
|
70
|
+
if output_number not in range(0, 4):
|
|
71
|
+
return self._build_ack_nak_response_telegram(False)
|
|
72
|
+
|
|
73
|
+
if output_state not in ("AA", "AB"):
|
|
74
|
+
return self._build_ack_nak_response_telegram(False)
|
|
75
|
+
|
|
76
|
+
output = (self.output_0, self.output_1, self.output_2, self.output_3)[
|
|
77
|
+
output_number
|
|
78
|
+
]
|
|
79
|
+
previous_state = output.state
|
|
80
|
+
output.state = True if output_state == "AB" else False
|
|
81
|
+
state_changed = previous_state != output.state
|
|
82
|
+
|
|
83
|
+
telegrams = self._build_ack_nak_response_telegram(True)
|
|
84
|
+
if state_changed and self.autoreport_status:
|
|
85
|
+
telegrams += self._build_make_break_response_telegram(
|
|
86
|
+
output.state, output_number
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return telegrams
|
|
90
|
+
|
|
91
|
+
def _build_ack_nak_response_telegram(self, ack_or_nak: bool) -> str:
|
|
92
|
+
"""Build a complete ACK or NAK response telegram with checksum.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
ack_or_nak: true: ACK telegram response, false: NAK telegram response.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
The complete telegram with checksum enclosed in angle brackets.
|
|
99
|
+
"""
|
|
100
|
+
data_value = (
|
|
101
|
+
SystemFunction.ACK.value if ack_or_nak else SystemFunction.NAK.value
|
|
102
|
+
)
|
|
103
|
+
data_part = f"R{self.serial_number}" f"F{data_value:02}D"
|
|
104
|
+
return self._build_response_telegram(data_part)
|
|
105
|
+
|
|
106
|
+
def _build_make_break_response_telegram(
|
|
107
|
+
self, make_or_break: bool, output_number: int
|
|
108
|
+
) -> str:
|
|
109
|
+
"""Build a complete ACK or NAK response telegram with checksum.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
make_or_break: true: MAKE event response, false: BREAK event response.
|
|
113
|
+
output_number: output concerned
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
The complete event telegram with checksum enclosed in angle brackets.
|
|
117
|
+
"""
|
|
118
|
+
data_value = "M" if make_or_break else "B"
|
|
119
|
+
data_part = (
|
|
120
|
+
f"E{self.module_type_code.value:02}"
|
|
121
|
+
f"L{self.link_number:02}"
|
|
122
|
+
f"I{output_number:02}"
|
|
123
|
+
f"{data_value}"
|
|
124
|
+
)
|
|
125
|
+
return self._build_response_telegram(data_part)
|
|
39
126
|
|
|
40
127
|
def _handle_device_specific_data_request(
|
|
41
128
|
self, request: SystemTelegram
|
|
42
129
|
) -> Optional[str]:
|
|
43
130
|
"""Handle XP24-specific data requests."""
|
|
44
|
-
if
|
|
45
|
-
request.system_function != SystemFunction.READ_DATAPOINT
|
|
46
|
-
or not request.datapoint_type
|
|
47
|
-
):
|
|
131
|
+
if not request.datapoint_type:
|
|
48
132
|
return None
|
|
49
133
|
|
|
50
134
|
datapoint_type = request.datapoint_type
|
|
51
|
-
|
|
52
|
-
DataPointType.MODULE_OUTPUT_STATE:
|
|
53
|
-
DataPointType.MODULE_STATE:
|
|
54
|
-
DataPointType.MODULE_OPERATING_HOURS:
|
|
55
|
-
}
|
|
135
|
+
handler = {
|
|
136
|
+
DataPointType.MODULE_OUTPUT_STATE: self._handle_read_module_output_state,
|
|
137
|
+
DataPointType.MODULE_STATE: self._handle_read_module_state,
|
|
138
|
+
DataPointType.MODULE_OPERATING_HOURS: self._handle_read_module_operating_hours,
|
|
139
|
+
}.get(datapoint_type)
|
|
140
|
+
if not handler:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
data_value = handler()
|
|
56
144
|
data_part = (
|
|
57
145
|
f"R{self.serial_number}"
|
|
58
|
-
f"
|
|
59
|
-
f"{self.module_type_code}"
|
|
60
|
-
f"{
|
|
146
|
+
f"F02D{datapoint_type.value}"
|
|
147
|
+
f"{self.module_type_code.value:02}"
|
|
148
|
+
f"{data_value}"
|
|
61
149
|
)
|
|
62
150
|
telegram = self._build_response_telegram(data_part)
|
|
63
151
|
|
|
@@ -66,21 +154,26 @@ class XP24ServerService(BaseServerService):
|
|
|
66
154
|
)
|
|
67
155
|
return telegram
|
|
68
156
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
157
|
+
def _handle_read_module_operating_hours(self) -> str:
|
|
158
|
+
"""Handle XP24-specific module operating hours."""
|
|
159
|
+
return "00:000[H],01:000[H],02:000[H],03:000[H]"
|
|
160
|
+
|
|
161
|
+
def _handle_read_module_state(self) -> str:
|
|
162
|
+
"""Handle XP24-specific module state."""
|
|
163
|
+
for output in (self.output_0, self.output_1, self.output_2, self.output_3):
|
|
164
|
+
if output.state:
|
|
165
|
+
return "ON"
|
|
166
|
+
return "OFF"
|
|
167
|
+
|
|
168
|
+
def _handle_read_module_output_state(self) -> str:
|
|
169
|
+
"""Handle XP24-specific module output state."""
|
|
170
|
+
return (
|
|
171
|
+
f"xxxx"
|
|
172
|
+
f"{1 if self.output_0.state else 0}"
|
|
173
|
+
f"{1 if self.output_1.state else 0}"
|
|
174
|
+
f"{1 if self.output_2.state else 0}"
|
|
175
|
+
f"{1 if self.output_3.state else 0}"
|
|
176
|
+
)
|
|
84
177
|
|
|
85
178
|
def get_device_info(self) -> Dict:
|
|
86
179
|
"""Get XP24 device information.
|
|
@@ -91,29 +184,9 @@ class XP24ServerService(BaseServerService):
|
|
|
91
184
|
return {
|
|
92
185
|
"serial_number": self.serial_number,
|
|
93
186
|
"device_type": self.device_type,
|
|
187
|
+
"module_type_code": self.module_type_code.value,
|
|
94
188
|
"firmware_version": self.firmware_version,
|
|
95
189
|
"status": self.device_status,
|
|
96
190
|
"link_number": self.link_number,
|
|
191
|
+
"autoreport_status": self.autoreport_status,
|
|
97
192
|
}
|
|
98
|
-
|
|
99
|
-
def generate_action_response(self, request: SystemTelegram) -> Optional[str]:
|
|
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
|
-
"""
|
|
108
|
-
response = "F19D" # NAK
|
|
109
|
-
if (
|
|
110
|
-
request.system_function == SystemFunction.ACTION
|
|
111
|
-
and request.data[:2] in ("00", "01", "02", "03")
|
|
112
|
-
and request.data[2:] in ("AA", "AB")
|
|
113
|
-
):
|
|
114
|
-
response = "F18D" # ACK
|
|
115
|
-
|
|
116
|
-
data_part = f"R{self.serial_number}{response}"
|
|
117
|
-
telegram = self._build_response_telegram(data_part)
|
|
118
|
-
self._log_response("module_action_response", telegram)
|
|
119
|
-
return telegram
|
|
@@ -7,11 +7,11 @@ including response generation and device configuration handling for
|
|
|
7
7
|
|
|
8
8
|
from typing import Dict, Optional
|
|
9
9
|
|
|
10
|
+
from xp.models import ModuleTypeCode
|
|
10
11
|
from xp.models.telegram.datapoint_type import DataPointType
|
|
11
12
|
from xp.models.telegram.system_function import SystemFunction
|
|
12
13
|
from xp.models.telegram.system_telegram import SystemTelegram
|
|
13
14
|
from xp.services.server.base_server_service import BaseServerService
|
|
14
|
-
from xp.utils import calculate_checksum
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class XP33ServerError(Exception):
|
|
@@ -38,27 +38,28 @@ class XP33ServerService(BaseServerService):
|
|
|
38
38
|
super().__init__(serial_number)
|
|
39
39
|
self.variant = variant # XP33 or XP33LR or XP33LED
|
|
40
40
|
self.device_type = "XP33"
|
|
41
|
-
self.module_type_code =
|
|
41
|
+
self.module_type_code = ModuleTypeCode.XP33 # XP33 module type
|
|
42
42
|
|
|
43
43
|
# XP33 device characteristics (anonymized for interoperability testing)
|
|
44
44
|
if variant == "XP33LED":
|
|
45
45
|
self.firmware_version = "XP33LED_V0.00.00"
|
|
46
46
|
self.ean_code = "1234567890123" # Test EAN - not a real product code
|
|
47
47
|
self.max_power = 300 # 3 x 100VA
|
|
48
|
-
self.module_type_code =
|
|
48
|
+
self.module_type_code = ModuleTypeCode.XP33LED # XP33LR module type
|
|
49
49
|
elif variant == "XP33LR": # XP33LR
|
|
50
50
|
self.firmware_version = "XP33LR_V0.00.00"
|
|
51
51
|
self.ean_code = "1234567890124" # Test EAN - not a real product code
|
|
52
52
|
self.max_power = 640 # Total 640VA
|
|
53
|
-
self.module_type_code =
|
|
53
|
+
self.module_type_code = ModuleTypeCode.XP33LR # XP33LR module type
|
|
54
54
|
else: # XP33
|
|
55
55
|
self.firmware_version = "XP33_V0.04.02"
|
|
56
56
|
self.ean_code = "1234567890125" # Test EAN - not a real product code
|
|
57
57
|
self.max_power = 100 # Total 640VA
|
|
58
|
-
self.module_type_code =
|
|
58
|
+
self.module_type_code = ModuleTypeCode.XP33 # XP33 module type
|
|
59
59
|
|
|
60
60
|
self.device_status = "00" # Normal status
|
|
61
61
|
self.link_number = 4 # 4 links configured
|
|
62
|
+
self.autoreport_status = True
|
|
62
63
|
|
|
63
64
|
# Channel states (3 channels, 0-100% dimming)
|
|
64
65
|
self.channel_states = [0, 0, 0] # All channels at 0%
|
|
@@ -71,36 +72,164 @@ class XP33ServerService(BaseServerService):
|
|
|
71
72
|
4: [0, 0, 0], # Scene 4: Off
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
def _handle_device_specific_action_request(
|
|
76
|
+
self, request: SystemTelegram
|
|
77
|
+
) -> Optional[str]:
|
|
78
|
+
"""Handle XP33-specific action requests."""
|
|
79
|
+
telegrams = self._handle_action_channel_dimming(request.data)
|
|
80
|
+
self.logger.debug(f"Generated {self.device_type} action responses: {telegrams}")
|
|
81
|
+
return telegrams
|
|
82
|
+
|
|
83
|
+
def _handle_action_channel_dimming(self, data_value: str) -> str:
|
|
84
|
+
"""Handle XP33-specific channel dimming action.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
data_value: Action data in format channel_number:dimming_level.
|
|
88
|
+
E.g., "00:050" means channel 0, 50% dimming.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Response telegram(s) - ACK/NAK, optionally with event telegram.
|
|
92
|
+
"""
|
|
93
|
+
if ":" not in data_value or len(data_value) < 6:
|
|
94
|
+
return self._build_ack_nak_response_telegram(False)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
parts = data_value.split(":")
|
|
98
|
+
channel_number = int(parts[0])
|
|
99
|
+
dimming_level = int(parts[1])
|
|
100
|
+
except (ValueError, IndexError):
|
|
101
|
+
return self._build_ack_nak_response_telegram(False)
|
|
102
|
+
|
|
103
|
+
if channel_number not in range(len(self.channel_states)):
|
|
104
|
+
return self._build_ack_nak_response_telegram(False)
|
|
105
|
+
|
|
106
|
+
if dimming_level not in range(0, 101):
|
|
107
|
+
return self._build_ack_nak_response_telegram(False)
|
|
108
|
+
|
|
109
|
+
previous_level = self.channel_states[channel_number]
|
|
110
|
+
self.channel_states[channel_number] = dimming_level
|
|
111
|
+
state_changed = (previous_level == 0 and dimming_level > 0) or (
|
|
112
|
+
previous_level > 0 and dimming_level == 0
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
telegrams = self._build_ack_nak_response_telegram(True)
|
|
116
|
+
if state_changed and self.autoreport_status:
|
|
117
|
+
# Report dimming change event
|
|
118
|
+
telegrams += self._build_dimming_event_telegram(
|
|
119
|
+
dimming_level, channel_number
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return telegrams
|
|
123
|
+
|
|
124
|
+
def _build_ack_nak_response_telegram(self, ack_or_nak: bool) -> str:
|
|
125
|
+
"""Build a complete ACK or NAK response telegram with checksum.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
ack_or_nak: true: ACK telegram response, false: NAK telegram response.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
The complete telegram with checksum enclosed in angle brackets.
|
|
132
|
+
"""
|
|
133
|
+
data_value = (
|
|
134
|
+
SystemFunction.ACK.value if ack_or_nak else SystemFunction.NAK.value
|
|
135
|
+
)
|
|
136
|
+
data_part = f"R{self.serial_number}" f"F{data_value:02}D"
|
|
137
|
+
return self._build_response_telegram(data_part)
|
|
138
|
+
|
|
139
|
+
def _build_dimming_event_telegram(
|
|
140
|
+
self, dimming_level: int, channel_number: int
|
|
141
|
+
) -> str:
|
|
142
|
+
"""Build a complete dimming event telegram with checksum.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
dimming_level: Dimming level 0-100%.
|
|
146
|
+
channel_number: Channel concerned (0-2).
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
The complete event telegram with checksum enclosed in angle brackets.
|
|
150
|
+
"""
|
|
151
|
+
data_value = "M" if dimming_level > 0 else "B"
|
|
152
|
+
data_part = (
|
|
153
|
+
f"E{self.module_type_code.value:02}"
|
|
154
|
+
f"L{self.link_number:02}"
|
|
155
|
+
f"I{channel_number:02}"
|
|
156
|
+
f"{data_value}"
|
|
157
|
+
)
|
|
158
|
+
return self._build_response_telegram(data_part)
|
|
159
|
+
|
|
74
160
|
def _handle_device_specific_data_request(
|
|
75
161
|
self, request: SystemTelegram
|
|
76
162
|
) -> Optional[str]:
|
|
77
|
-
"""Handle
|
|
78
|
-
if
|
|
79
|
-
request.system_function != SystemFunction.READ_DATAPOINT
|
|
80
|
-
or not request.datapoint_type
|
|
81
|
-
):
|
|
163
|
+
"""Handle XP33-specific data requests."""
|
|
164
|
+
if not request.datapoint_type:
|
|
82
165
|
return None
|
|
83
166
|
|
|
84
167
|
datapoint_type = request.datapoint_type
|
|
85
|
-
|
|
86
|
-
DataPointType.MODULE_OUTPUT_STATE:
|
|
87
|
-
DataPointType.MODULE_STATE:
|
|
88
|
-
DataPointType.MODULE_OPERATING_HOURS:
|
|
89
|
-
|
|
168
|
+
handler = {
|
|
169
|
+
DataPointType.MODULE_OUTPUT_STATE: self._handle_read_module_output_state,
|
|
170
|
+
DataPointType.MODULE_STATE: self._handle_read_module_state,
|
|
171
|
+
DataPointType.MODULE_OPERATING_HOURS: self._handle_read_module_operating_hours,
|
|
172
|
+
DataPointType.MODULE_LIGHT_LEVEL: self._handle_read_light_level,
|
|
173
|
+
}.get(datapoint_type)
|
|
174
|
+
if not handler:
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
data_value = handler()
|
|
90
178
|
data_part = (
|
|
91
179
|
f"R{self.serial_number}"
|
|
92
|
-
f"
|
|
93
|
-
f"{self.module_type_code}"
|
|
94
|
-
f"{
|
|
180
|
+
f"F02D{datapoint_type.value}"
|
|
181
|
+
f"{self.module_type_code.value:02}"
|
|
182
|
+
f"{data_value}"
|
|
95
183
|
)
|
|
96
|
-
|
|
97
|
-
telegram = f"<{data_part}{checksum}>"
|
|
184
|
+
telegram = self._build_response_telegram(data_part)
|
|
98
185
|
|
|
99
186
|
self.logger.debug(
|
|
100
187
|
f"Generated {self.device_type} module type response: {telegram}"
|
|
101
188
|
)
|
|
102
189
|
return telegram
|
|
103
190
|
|
|
191
|
+
def _handle_read_module_output_state(self) -> str:
|
|
192
|
+
"""Handle XP33-specific module output state.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
String representation of the output state for 3 channels.
|
|
196
|
+
"""
|
|
197
|
+
return (
|
|
198
|
+
f"xxxxx"
|
|
199
|
+
f"{1 if self.channel_states[0] > 0 else 0}"
|
|
200
|
+
f"{1 if self.channel_states[1] > 0 else 0}"
|
|
201
|
+
f"{1 if self.channel_states[2] > 0 else 0}"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def _handle_read_module_state(self) -> str:
|
|
205
|
+
"""Handle XP33-specific module state.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
'ON' if any channel is active, 'OFF' otherwise.
|
|
209
|
+
"""
|
|
210
|
+
if any(level > 0 for level in self.channel_states):
|
|
211
|
+
return "ON"
|
|
212
|
+
return "OFF"
|
|
213
|
+
|
|
214
|
+
def _handle_read_module_operating_hours(self) -> str:
|
|
215
|
+
"""Handle XP33-specific module operating hours.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Operating hours for all 3 channels.
|
|
219
|
+
"""
|
|
220
|
+
return "00:000[H],01:000[H],02:000[H]"
|
|
221
|
+
|
|
222
|
+
def _handle_read_light_level(self) -> str:
|
|
223
|
+
"""Handle XP33-specific light level reading.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Light levels for all channels in format "00:000[%],01:000[%],02:000[%]".
|
|
227
|
+
"""
|
|
228
|
+
levels = [
|
|
229
|
+
f"{i:02d}:{level:03d}[%]" for i, level in enumerate(self.channel_states)
|
|
230
|
+
]
|
|
231
|
+
return ",".join(levels)
|
|
232
|
+
|
|
104
233
|
def set_channel_dimming(self, channel: int, level: int) -> bool:
|
|
105
234
|
"""Set individual channel dimming level.
|
|
106
235
|
|
|
@@ -147,6 +276,7 @@ class XP33ServerService(BaseServerService):
|
|
|
147
276
|
"max_power": self.max_power,
|
|
148
277
|
"status": self.device_status,
|
|
149
278
|
"link_number": self.link_number,
|
|
279
|
+
"autoreport_status": self.autoreport_status,
|
|
150
280
|
"channel_states": self.channel_states.copy(),
|
|
151
281
|
"available_scenes": list(self.scenes.keys()),
|
|
152
282
|
}
|
|
@@ -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
|
xp/utils/dependencies.py
CHANGED
|
@@ -21,8 +21,6 @@ from xp.services.conbus.actiontable.msactiontable_xp24_serializer import (
|
|
|
21
21
|
from xp.services.conbus.actiontable.msactiontable_xp33_serializer import (
|
|
22
22
|
Xp33MsActionTableSerializer,
|
|
23
23
|
)
|
|
24
|
-
from xp.services.conbus.conbus_autoreport_get_service import ConbusAutoreportGetService
|
|
25
|
-
from xp.services.conbus.conbus_autoreport_set_service import ConbusAutoreportSetService
|
|
26
24
|
from xp.services.conbus.conbus_blink_all_service import ConbusBlinkAllService
|
|
27
25
|
from xp.services.conbus.conbus_blink_service import ConbusBlinkService
|
|
28
26
|
from xp.services.conbus.conbus_custom_service import ConbusCustomService
|
|
@@ -33,13 +31,11 @@ from xp.services.conbus.conbus_datapoint_service import (
|
|
|
33
31
|
ConbusDatapointService,
|
|
34
32
|
)
|
|
35
33
|
from xp.services.conbus.conbus_discover_service import ConbusDiscoverService
|
|
36
|
-
from xp.services.conbus.conbus_lightlevel_set_service import ConbusLightlevelSetService
|
|
37
|
-
from xp.services.conbus.conbus_linknumber_get_service import ConbusLinknumberGetService
|
|
38
|
-
from xp.services.conbus.conbus_linknumber_set_service import ConbusLinknumberSetService
|
|
39
34
|
from xp.services.conbus.conbus_output_service import ConbusOutputService
|
|
40
35
|
from xp.services.conbus.conbus_raw_service import ConbusRawService
|
|
41
36
|
from xp.services.conbus.conbus_receive_service import ConbusReceiveService
|
|
42
37
|
from xp.services.conbus.conbus_scan_service import ConbusScanService
|
|
38
|
+
from xp.services.conbus.write_config_service import WriteConfigService
|
|
43
39
|
from xp.services.homekit.homekit_cache_service import HomeKitCacheService
|
|
44
40
|
from xp.services.homekit.homekit_conbus_service import HomeKitConbusService
|
|
45
41
|
from xp.services.homekit.homekit_dimminglight_service import HomeKitDimmingLightService
|
|
@@ -191,8 +187,9 @@ class ServiceContainer:
|
|
|
191
187
|
)
|
|
192
188
|
|
|
193
189
|
self.container.register(
|
|
194
|
-
|
|
195
|
-
factory=lambda:
|
|
190
|
+
WriteConfigService,
|
|
191
|
+
factory=lambda: WriteConfigService(
|
|
192
|
+
telegram_service=self.container.resolve(TelegramService),
|
|
196
193
|
cli_config=self.container.resolve(ConbusClientConfig),
|
|
197
194
|
reactor=self.container.resolve(PosixReactorBase),
|
|
198
195
|
),
|
|
@@ -247,45 +244,6 @@ class ServiceContainer:
|
|
|
247
244
|
scope=punq.Scope.singleton,
|
|
248
245
|
)
|
|
249
246
|
|
|
250
|
-
self.container.register(
|
|
251
|
-
ConbusAutoreportSetService,
|
|
252
|
-
factory=lambda: ConbusAutoreportSetService(
|
|
253
|
-
cli_config=self.container.resolve(ConbusClientConfig),
|
|
254
|
-
reactor=self.container.resolve(PosixReactorBase),
|
|
255
|
-
),
|
|
256
|
-
scope=punq.Scope.singleton,
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
self.container.register(
|
|
260
|
-
ConbusAutoreportGetService,
|
|
261
|
-
factory=lambda: ConbusAutoreportGetService(
|
|
262
|
-
telegram_service=self.container.resolve(TelegramService),
|
|
263
|
-
cli_config=self.container.resolve(ConbusClientConfig),
|
|
264
|
-
reactor=self.container.resolve(PosixReactorBase),
|
|
265
|
-
),
|
|
266
|
-
scope=punq.Scope.singleton,
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
self.container.register(
|
|
270
|
-
ConbusLinknumberGetService,
|
|
271
|
-
factory=lambda: ConbusLinknumberGetService(
|
|
272
|
-
telegram_service=self.container.resolve(TelegramService),
|
|
273
|
-
cli_config=self.container.resolve(ConbusClientConfig),
|
|
274
|
-
reactor=self.container.resolve(PosixReactorBase),
|
|
275
|
-
),
|
|
276
|
-
scope=punq.Scope.singleton,
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
self.container.register(
|
|
280
|
-
ConbusLinknumberSetService,
|
|
281
|
-
factory=lambda: ConbusLinknumberSetService(
|
|
282
|
-
telegram_service=self.container.resolve(TelegramService),
|
|
283
|
-
cli_config=self.container.resolve(ConbusClientConfig),
|
|
284
|
-
reactor=self.container.resolve(PosixReactorBase),
|
|
285
|
-
),
|
|
286
|
-
scope=punq.Scope.singleton,
|
|
287
|
-
)
|
|
288
|
-
|
|
289
247
|
self.container.register(
|
|
290
248
|
ConbusCustomService,
|
|
291
249
|
factory=lambda: ConbusCustomService(
|
xp/api/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""API module for FastAPI endpoints and models."""
|