conson-xp 1.18.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.18.0.dist-info/METADATA +412 -0
- conson_xp-1.18.0.dist-info/RECORD +176 -0
- conson_xp-1.18.0.dist-info/WHEEL +4 -0
- conson_xp-1.18.0.dist-info/entry_points.txt +5 -0
- conson_xp-1.18.0.dist-info/licenses/LICENSE +29 -0
- xp/__init__.py +9 -0
- xp/cli/__init__.py +5 -0
- xp/cli/__main__.py +6 -0
- xp/cli/commands/__init__.py +153 -0
- xp/cli/commands/conbus/__init__.py +25 -0
- xp/cli/commands/conbus/conbus.py +128 -0
- xp/cli/commands/conbus/conbus_actiontable_commands.py +233 -0
- xp/cli/commands/conbus/conbus_autoreport_commands.py +108 -0
- xp/cli/commands/conbus/conbus_blink_commands.py +163 -0
- xp/cli/commands/conbus/conbus_config_commands.py +29 -0
- xp/cli/commands/conbus/conbus_custom_commands.py +57 -0
- xp/cli/commands/conbus/conbus_datapoint_commands.py +113 -0
- xp/cli/commands/conbus/conbus_discover_commands.py +61 -0
- xp/cli/commands/conbus/conbus_event_commands.py +81 -0
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +207 -0
- xp/cli/commands/conbus/conbus_linknumber_commands.py +102 -0
- xp/cli/commands/conbus/conbus_modulenumber_commands.py +104 -0
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +94 -0
- xp/cli/commands/conbus/conbus_output_commands.py +163 -0
- xp/cli/commands/conbus/conbus_raw_commands.py +62 -0
- xp/cli/commands/conbus/conbus_receive_commands.py +59 -0
- xp/cli/commands/conbus/conbus_scan_commands.py +58 -0
- xp/cli/commands/file_commands.py +186 -0
- xp/cli/commands/homekit/__init__.py +3 -0
- xp/cli/commands/homekit/homekit.py +118 -0
- xp/cli/commands/homekit/homekit_start_commands.py +43 -0
- xp/cli/commands/module_commands.py +187 -0
- xp/cli/commands/reverse_proxy_commands.py +178 -0
- xp/cli/commands/server/__init__.py +3 -0
- xp/cli/commands/server/server_commands.py +135 -0
- xp/cli/commands/telegram/__init__.py +5 -0
- xp/cli/commands/telegram/telegram.py +41 -0
- xp/cli/commands/telegram/telegram_blink_commands.py +79 -0
- xp/cli/commands/telegram/telegram_checksum_commands.py +112 -0
- xp/cli/commands/telegram/telegram_discover_commands.py +41 -0
- xp/cli/commands/telegram/telegram_linknumber_commands.py +86 -0
- xp/cli/commands/telegram/telegram_parse_commands.py +75 -0
- xp/cli/commands/telegram/telegram_version_commands.py +52 -0
- xp/cli/main.py +87 -0
- xp/cli/utils/__init__.py +1 -0
- xp/cli/utils/click_tree.py +57 -0
- xp/cli/utils/datapoint_type_choice.py +57 -0
- xp/cli/utils/decorators.py +351 -0
- xp/cli/utils/error_handlers.py +201 -0
- xp/cli/utils/formatters.py +312 -0
- xp/cli/utils/module_type_choice.py +56 -0
- xp/cli/utils/serial_number_type.py +52 -0
- xp/cli/utils/system_function_choice.py +57 -0
- xp/cli/utils/xp_module_type.py +53 -0
- xp/connection/__init__.py +13 -0
- xp/connection/exceptions.py +22 -0
- xp/models/__init__.py +36 -0
- xp/models/actiontable/__init__.py +1 -0
- xp/models/actiontable/actiontable.py +43 -0
- xp/models/actiontable/msactiontable_xp20.py +53 -0
- xp/models/actiontable/msactiontable_xp24.py +58 -0
- xp/models/actiontable/msactiontable_xp33.py +65 -0
- xp/models/conbus/__init__.py +1 -0
- xp/models/conbus/conbus.py +87 -0
- xp/models/conbus/conbus_autoreport.py +67 -0
- xp/models/conbus/conbus_blink.py +80 -0
- xp/models/conbus/conbus_client_config.py +55 -0
- xp/models/conbus/conbus_connection_status.py +40 -0
- xp/models/conbus/conbus_custom.py +58 -0
- xp/models/conbus/conbus_datapoint.py +89 -0
- xp/models/conbus/conbus_discover.py +64 -0
- xp/models/conbus/conbus_event_raw.py +47 -0
- xp/models/conbus/conbus_lightlevel.py +52 -0
- xp/models/conbus/conbus_linknumber.py +54 -0
- xp/models/conbus/conbus_output.py +57 -0
- xp/models/conbus/conbus_raw.py +45 -0
- xp/models/conbus/conbus_receive.py +42 -0
- xp/models/conbus/conbus_writeconfig.py +60 -0
- xp/models/homekit/__init__.py +1 -0
- xp/models/homekit/homekit_accessory.py +35 -0
- xp/models/homekit/homekit_config.py +106 -0
- xp/models/homekit/homekit_conson_config.py +86 -0
- xp/models/log_entry.py +130 -0
- xp/models/protocol/__init__.py +1 -0
- xp/models/protocol/conbus_protocol.py +312 -0
- xp/models/response.py +42 -0
- xp/models/telegram/__init__.py +1 -0
- xp/models/telegram/action_type.py +31 -0
- xp/models/telegram/datapoint_type.py +82 -0
- xp/models/telegram/event_telegram.py +140 -0
- xp/models/telegram/event_type.py +15 -0
- xp/models/telegram/input_action_type.py +69 -0
- xp/models/telegram/input_type.py +17 -0
- xp/models/telegram/module_type.py +188 -0
- xp/models/telegram/module_type_code.py +205 -0
- xp/models/telegram/output_telegram.py +103 -0
- xp/models/telegram/reply_telegram.py +297 -0
- xp/models/telegram/system_function.py +116 -0
- xp/models/telegram/system_telegram.py +94 -0
- xp/models/telegram/telegram.py +28 -0
- xp/models/telegram/telegram_type.py +19 -0
- xp/models/telegram/timeparam_type.py +51 -0
- xp/models/write_config_type.py +33 -0
- xp/services/__init__.py +26 -0
- xp/services/actiontable/__init__.py +1 -0
- xp/services/actiontable/actiontable_serializer.py +273 -0
- xp/services/actiontable/msactiontable_serializer.py +7 -0
- xp/services/actiontable/msactiontable_xp20_serializer.py +169 -0
- xp/services/actiontable/msactiontable_xp24_serializer.py +120 -0
- xp/services/actiontable/msactiontable_xp33_serializer.py +239 -0
- xp/services/conbus/__init__.py +1 -0
- xp/services/conbus/actiontable/__init__.py +1 -0
- xp/services/conbus/actiontable/actiontable_download_service.py +158 -0
- xp/services/conbus/actiontable/actiontable_list_service.py +91 -0
- xp/services/conbus/actiontable/actiontable_show_service.py +89 -0
- xp/services/conbus/actiontable/actiontable_upload_service.py +211 -0
- xp/services/conbus/actiontable/msactiontable_service.py +232 -0
- xp/services/conbus/conbus_blink_all_service.py +181 -0
- xp/services/conbus/conbus_blink_service.py +158 -0
- xp/services/conbus/conbus_custom_service.py +156 -0
- xp/services/conbus/conbus_datapoint_queryall_service.py +182 -0
- xp/services/conbus/conbus_datapoint_service.py +170 -0
- xp/services/conbus/conbus_discover_service.py +312 -0
- xp/services/conbus/conbus_event_raw_service.py +181 -0
- xp/services/conbus/conbus_output_service.py +194 -0
- xp/services/conbus/conbus_raw_service.py +122 -0
- xp/services/conbus/conbus_receive_service.py +115 -0
- xp/services/conbus/conbus_scan_service.py +150 -0
- xp/services/conbus/write_config_service.py +194 -0
- xp/services/homekit/__init__.py +1 -0
- xp/services/homekit/homekit_cache_service.py +307 -0
- xp/services/homekit/homekit_conbus_service.py +93 -0
- xp/services/homekit/homekit_config_validator.py +310 -0
- xp/services/homekit/homekit_conson_validator.py +121 -0
- xp/services/homekit/homekit_dimminglight.py +182 -0
- xp/services/homekit/homekit_dimminglight_service.py +148 -0
- xp/services/homekit/homekit_hap_service.py +342 -0
- xp/services/homekit/homekit_lightbulb.py +120 -0
- xp/services/homekit/homekit_lightbulb_service.py +86 -0
- xp/services/homekit/homekit_module_service.py +56 -0
- xp/services/homekit/homekit_outlet.py +168 -0
- xp/services/homekit/homekit_outlet_service.py +121 -0
- xp/services/homekit/homekit_service.py +359 -0
- xp/services/log_file_service.py +309 -0
- xp/services/module_type_service.py +257 -0
- xp/services/protocol/__init__.py +21 -0
- xp/services/protocol/conbus_event_protocol.py +360 -0
- xp/services/protocol/conbus_protocol.py +318 -0
- xp/services/protocol/protocol_factory.py +78 -0
- xp/services/protocol/telegram_protocol.py +264 -0
- xp/services/reverse_proxy_service.py +435 -0
- xp/services/server/__init__.py +1 -0
- xp/services/server/base_server_service.py +366 -0
- xp/services/server/cp20_server_service.py +65 -0
- xp/services/server/device_service_factory.py +94 -0
- xp/services/server/server_service.py +428 -0
- xp/services/server/xp130_server_service.py +67 -0
- xp/services/server/xp20_server_service.py +92 -0
- xp/services/server/xp230_server_service.py +58 -0
- xp/services/server/xp24_server_service.py +245 -0
- xp/services/server/xp33_server_service.py +535 -0
- xp/services/telegram/__init__.py +1 -0
- xp/services/telegram/telegram_blink_service.py +138 -0
- xp/services/telegram/telegram_checksum_service.py +149 -0
- xp/services/telegram/telegram_datapoint_service.py +82 -0
- xp/services/telegram/telegram_discover_service.py +277 -0
- xp/services/telegram/telegram_link_number_service.py +216 -0
- xp/services/telegram/telegram_output_service.py +322 -0
- xp/services/telegram/telegram_service.py +380 -0
- xp/services/telegram/telegram_version_service.py +288 -0
- xp/utils/__init__.py +12 -0
- xp/utils/checksum.py +61 -0
- xp/utils/dependencies.py +531 -0
- xp/utils/event_helper.py +31 -0
- xp/utils/serialization.py +205 -0
- xp/utils/time_utils.py +134 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Binary serialization utility functions.
|
|
2
|
+
|
|
3
|
+
This module provides common binary manipulation functions used across
|
|
4
|
+
the XP protocol serializers for consistent data encoding/decoding.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
# BCD and bit manipulation constants
|
|
10
|
+
UPPER4 = 240 # 0xF0
|
|
11
|
+
LOWER4 = 15 # 0x0F
|
|
12
|
+
LOWER3 = 7 # 0x07
|
|
13
|
+
UPPER5 = 248 # 0xF8
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def de_bcd(byte_val: int) -> int:
|
|
17
|
+
"""Convert BCD byte to decimal.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
byte_val: BCD encoded byte
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Decimal value
|
|
24
|
+
"""
|
|
25
|
+
return ((UPPER4 & byte_val) >> 4) * 10 + (LOWER4 & byte_val)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def to_bcd(decimal_val: int) -> int:
|
|
29
|
+
"""Convert decimal to BCD byte.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
decimal_val: Decimal value to convert
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
BCD encoded byte
|
|
36
|
+
"""
|
|
37
|
+
tens = (decimal_val // 10) % 10
|
|
38
|
+
ones = decimal_val % 10
|
|
39
|
+
return (tens << 4) | ones
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def lower3(byte_val: int) -> int:
|
|
43
|
+
"""Extract lower 3 bits from byte.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
byte_val: Input byte
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Lower 3 bits as integer
|
|
50
|
+
"""
|
|
51
|
+
return byte_val & LOWER3
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def upper5(byte_val: int) -> int:
|
|
55
|
+
"""Extract upper 5 bits from byte.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
byte_val: Input byte
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Upper 5 bits as integer
|
|
62
|
+
"""
|
|
63
|
+
return (byte_val & UPPER5) >> 3
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def byte_to_bits(byte_value: int) -> List[bool]:
|
|
67
|
+
"""Convert a byte value to 8-bit boolean array.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
byte_value: Byte value to convert
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of 8 boolean values representing the bits
|
|
74
|
+
"""
|
|
75
|
+
return [(byte_value & (1 << n)) != 0 for n in range(8)]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def bits_to_byte(bits: List[bool]) -> int:
|
|
79
|
+
"""Convert boolean array to byte value.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
bits: List of boolean values representing bits
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Byte value
|
|
86
|
+
"""
|
|
87
|
+
byte_val = 0
|
|
88
|
+
for i, bit in enumerate(bits[:8]): # Limit to 8 bits
|
|
89
|
+
if bit:
|
|
90
|
+
byte_val |= 1 << i
|
|
91
|
+
return byte_val
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def highest_bit_set(value: int) -> int:
|
|
95
|
+
"""Remove the high bit (0x80) from a byte value.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
value: Byte value to process
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Value with high bit cleared (XOR with 0x80 if high bit was set)
|
|
102
|
+
"""
|
|
103
|
+
return (value & 0x80) == 128
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def remove_highest_bit(value: int) -> int:
|
|
107
|
+
"""Remove the high bit (0x80) from a byte value.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
value: Byte value to process
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Value with high bit cleared (XOR with 0x80 if high bit was set)
|
|
114
|
+
"""
|
|
115
|
+
return value ^ 0x80 if (value & 0x80) == 128 else value
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def byte_to_unsigned(byte_val: int) -> int:
|
|
119
|
+
"""Convert signed byte to unsigned integer.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
byte_val: Byte value (can be negative)
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Unsigned integer (0-255)
|
|
126
|
+
"""
|
|
127
|
+
if byte_val < 0:
|
|
128
|
+
return byte_val + 256
|
|
129
|
+
return byte_val
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def nibble(byte_val: int) -> str:
|
|
133
|
+
"""Convert byte value to two-character nibble representation.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
byte_val: Byte value (0-255)
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Two-character string representing the nibble
|
|
140
|
+
"""
|
|
141
|
+
low_cc = ((byte_val & 0xF0) >> 4) + 65
|
|
142
|
+
high_cc = (byte_val & 0xF) + 65
|
|
143
|
+
return chr(low_cc) + chr(high_cc)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def de_nibble(nibble_str: str) -> int:
|
|
147
|
+
"""Convert two-character nibble string to byte value.
|
|
148
|
+
|
|
149
|
+
Based on pseudocode: A=0, B=1, C=2, ..., P=15
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
nibble_str: Two-character string with A-P encoding
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Byte value (0-255)
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
ValueError: If nibble string is not exactly 2 characters
|
|
159
|
+
"""
|
|
160
|
+
if len(nibble_str) != 2:
|
|
161
|
+
raise ValueError("Nibble string must be exactly 2 characters")
|
|
162
|
+
|
|
163
|
+
high_char = nibble_str[0]
|
|
164
|
+
low_char = nibble_str[1]
|
|
165
|
+
|
|
166
|
+
# Convert A-P to 0-15 (A=65 in ASCII, so A-65=0)
|
|
167
|
+
high_nibble = (ord(high_char) - 65) << 4
|
|
168
|
+
low_nibble = ord(low_char) - 65
|
|
169
|
+
|
|
170
|
+
return high_nibble + low_nibble
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def de_nibbles(str_val: str) -> bytearray:
|
|
174
|
+
"""Convert hex string with A-P encoding to list of integers.
|
|
175
|
+
|
|
176
|
+
Based on pseudocode: A=0, B=1, C=2, ..., P=15
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
str_val: Hex string with A-P encoding
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
List of integers representing the decoded bytes
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
ValueError: If string length is not even for nibble conversion
|
|
186
|
+
"""
|
|
187
|
+
if len(str_val) % 2 != 0:
|
|
188
|
+
raise ValueError("String length must be even for nibble conversion")
|
|
189
|
+
|
|
190
|
+
result = bytearray()
|
|
191
|
+
for i in range(0, len(str_val), 2):
|
|
192
|
+
result.append(de_nibble(str_val[i : i + 2]))
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def nibbles(data: bytes) -> str:
|
|
197
|
+
"""Convert bytes data to nibble string representation.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
data: Bytes data to convert
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
String representation using A-P encoding
|
|
204
|
+
"""
|
|
205
|
+
return "".join(nibble(byte) for byte in data)
|
xp/utils/time_utils.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Time parsing utilities for console bus logs."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from datetime import datetime, time
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TimeParsingError(Exception):
|
|
9
|
+
"""Raised when time parsing fails."""
|
|
10
|
+
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_log_timestamp(
|
|
15
|
+
timestamp_str: str, base_date: Optional[datetime] = None
|
|
16
|
+
) -> datetime:
|
|
17
|
+
"""Parse timestamp from console bus log format: HH:MM:SS,mmm.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
timestamp_str: Timestamp string (e.g., "22:44:20,352")
|
|
21
|
+
base_date: Base date to use (defaults to today)
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
datetime object with parsed time
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
TimeParsingError: If timestamp format is invalid
|
|
28
|
+
"""
|
|
29
|
+
# Pattern: HH:MM:SS,mmm
|
|
30
|
+
pattern = r"^(\d{2}):(\d{2}):(\d{2}),(\d{3})$"
|
|
31
|
+
match = re.match(pattern, timestamp_str.strip())
|
|
32
|
+
|
|
33
|
+
if not match:
|
|
34
|
+
raise TimeParsingError(f"Invalid timestamp format: {timestamp_str}")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
hour = int(match.group(1))
|
|
38
|
+
minute = int(match.group(2))
|
|
39
|
+
second = int(match.group(3))
|
|
40
|
+
millisecond = int(match.group(4))
|
|
41
|
+
|
|
42
|
+
# Validate ranges
|
|
43
|
+
if not (0 <= hour <= 23):
|
|
44
|
+
raise TimeParsingError(f"Invalid hour: {hour}")
|
|
45
|
+
if not (0 <= minute <= 59):
|
|
46
|
+
raise TimeParsingError(f"Invalid minute: {minute}")
|
|
47
|
+
if not (0 <= second <= 59):
|
|
48
|
+
raise TimeParsingError(f"Invalid second: {second}")
|
|
49
|
+
if not (0 <= millisecond <= 999):
|
|
50
|
+
raise TimeParsingError(f"Invalid millisecond: {millisecond}")
|
|
51
|
+
|
|
52
|
+
# Create time object
|
|
53
|
+
time_obj = time(hour, minute, second, millisecond * 1000) # microseconds
|
|
54
|
+
|
|
55
|
+
# Use base date or today
|
|
56
|
+
if base_date is None:
|
|
57
|
+
date_part = datetime.now().date()
|
|
58
|
+
else:
|
|
59
|
+
date_part = base_date.date()
|
|
60
|
+
|
|
61
|
+
return datetime.combine(date_part, time_obj)
|
|
62
|
+
|
|
63
|
+
except ValueError as e:
|
|
64
|
+
raise TimeParsingError(f"Error parsing timestamp {timestamp_str}: {e}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def format_log_timestamp(dt: datetime) -> str:
|
|
68
|
+
"""Format datetime to console bus log timestamp format: HH:MM:SS,mmm.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
dt: datetime object to format
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Formatted timestamp string
|
|
75
|
+
"""
|
|
76
|
+
return dt.strftime("%H:%M:%S,%f")[:-3] # Remove last 3 digits of microseconds
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def parse_time_range(
|
|
80
|
+
time_range_str: str, base_date: Optional[datetime] = None
|
|
81
|
+
) -> tuple[datetime, datetime]:
|
|
82
|
+
"""Parse time range string like "22:44:20,352-22:44:25,500".
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
time_range_str: Time range string
|
|
86
|
+
base_date: Base date to use
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Tuple of (start_time, end_time)
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
TimeParsingError: If format is invalid
|
|
93
|
+
"""
|
|
94
|
+
parts = time_range_str.split("-")
|
|
95
|
+
if len(parts) != 2:
|
|
96
|
+
raise TimeParsingError(f"Invalid time range format: {time_range_str}")
|
|
97
|
+
|
|
98
|
+
start_time = parse_log_timestamp(parts[0].strip(), base_date)
|
|
99
|
+
end_time = parse_log_timestamp(parts[1].strip(), base_date)
|
|
100
|
+
|
|
101
|
+
if start_time > end_time:
|
|
102
|
+
raise TimeParsingError(f"Start time {parts[0]} is after end time {parts[1]}")
|
|
103
|
+
|
|
104
|
+
return start_time, end_time
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def calculate_duration_ms(start_time: datetime, end_time: datetime) -> int:
|
|
108
|
+
"""Calculate duration between two timestamps in milliseconds.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
start_time: Start timestamp
|
|
112
|
+
end_time: End timestamp
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Duration in milliseconds
|
|
116
|
+
"""
|
|
117
|
+
duration = end_time - start_time
|
|
118
|
+
return int(duration.total_seconds() * 1000)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def is_valid_log_timestamp(timestamp_str: str) -> bool:
|
|
122
|
+
"""Check if timestamp string is valid console bus log format.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
timestamp_str: Timestamp string to validate
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
True if valid format, False otherwise
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
parse_log_timestamp(timestamp_str)
|
|
132
|
+
return True
|
|
133
|
+
except TimeParsingError:
|
|
134
|
+
return False
|