Skip to content

Commit 7d78ccc

Browse files
refactor: separated parsing and extraction
Signed-off-by: Omkar Sarkar <omkarsarkar24@gmail.com>
1 parent 3289a61 commit 7d78ccc

3 files changed

Lines changed: 251 additions & 275 deletions

File tree

ardupilot_methodic_configurator/log_analysis/backend_log_extraction.py

Lines changed: 45 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import re
1212
from typing import Any
1313

14-
from pymavlink import mavutil
14+
from ardupilot_methodic_configurator.log_analysis.backend_log_parsing import parse_log
1515

1616
PARAM_NAME_REGEX = r"^[A-Z][A-Z_0-9]*$"
1717
PARAM_NAME_MAX_LEN = 16
@@ -27,37 +27,6 @@ def is_param_name_format_valid(pname: str) -> bool:
2727
return bool(re.match(PARAM_NAME_REGEX, pname))
2828

2929

30-
def open_log(logfile: str) -> mavutil.mavfile:
31-
"""
32-
Open an ArduPilot log file.
33-
34-
Args:
35-
logfile: The path to an Ardupilot .bin log file.
36-
37-
Returns:
38-
A mavutil.mavfile connection object.
39-
40-
"""
41-
try:
42-
mlog = mavutil.mavlink_connection(logfile)
43-
except Exception as e:
44-
msg = f"Error opening the {logfile} logfile: {e!s}"
45-
raise OSError(msg) from e
46-
return mlog # pyright: ignore[reportReturnType] # pymavlink stubs include CSVReader which doesn't extend mavfile
47-
48-
49-
def close_log(mlog: mavutil.mavfile) -> None:
50-
"""
51-
Close an ArduPilot log file.
52-
53-
Args:
54-
mlog: The mavutil.mavfile connection to close.
55-
56-
"""
57-
with contextlib.suppress(OSError):
58-
mlog.close()
59-
60-
6130
class BatteryData: # pylint: disable=too-many-instance-attributes,too-few-public-methods
6231
"""Stores battery telemetry data extracted from BAT log messages."""
6332

@@ -212,78 +181,70 @@ def extract_log(logfile: str) -> LogData: # pylint: disable=too-many-branches
212181
logfile: The path to an ArduPilot .bin log file.
213182
214183
Returns:
215-
A populated LogData object containing parameters, firmware info, message counts, and frame type.
184+
A populated LogData object containing parameters, firmware info,
185+
message counts, and frame type.
216186
217187
"""
218188
log_data = LogData()
219189
message_counts: dict[str, int] = {}
220190
firmware_from_ver: tuple[str, int, int, int] | None = None
221191
firmware_from_msg: tuple[str, int, int, int] | None = None
222192

223-
mlog = open_log(logfile)
224-
225-
try:
226-
while True:
227-
msg = mlog.recv_match()
228-
if msg is None:
229-
break
230-
msg_type = msg.get_type()
231-
message_counts[msg_type] = message_counts.get(msg_type, 0) + 1
193+
for msg in parse_log(logfile):
194+
msg_type = msg.get_type()
195+
message_counts[msg_type] = message_counts.get(msg_type, 0) + 1
232196

233-
# Extract PARM messages and store them, also is used in extract_param_defaults.py
234-
if msg_type == "PARM":
235-
process_param(msg, log_data)
197+
# Extract PARM messages and store them, also is used in extract_param_defaults.py
198+
if msg_type == "PARM":
199+
process_param(msg, log_data)
236200

237-
# Extract the Version with Vehicle_type, Major, Minor and Patch.
238-
elif msg_type == "VER":
239-
firmware_from_ver = process_ver(msg)
201+
# Extract the Version with Vehicle_type, Major, Minor and Patch.
202+
elif msg_type == "VER":
203+
firmware_from_ver = process_ver(msg)
240204

241-
# Fallback to MSG if version is not available.
242-
elif msg_type == "MSG":
243-
firmware_from_msg = process_msg_version_fallback(msg, firmware_from_msg)
205+
# Fallback to MSG if version is not available.
206+
elif msg_type == "MSG":
207+
firmware_from_msg = process_msg_version_fallback(msg, firmware_from_msg)
244208

245-
# Extract the BAT messages and store them in BatteryData
246-
elif msg_type == "BAT":
247-
process_bat(msg, log_data)
209+
# Extract the BAT messages and store them in BatteryData
210+
elif msg_type == "BAT":
211+
process_bat(msg, log_data)
248212

249-
# Extract the PM messages and store them in PMData
250-
elif msg_type == "PM":
251-
process_performance(msg, log_data)
213+
# Extract the PM messages and store them in PMData
214+
elif msg_type == "PM":
215+
process_performance(msg, log_data)
252216

253-
# Extract IMU messages and store them in IMUData
254-
elif msg_type == "IMU":
255-
process_imu(msg, log_data)
217+
# Extract IMU messages and store them in IMUData
218+
elif msg_type == "IMU":
219+
process_imu(msg, log_data)
256220

257-
# Extract VIBE messages and store them in VibeData
258-
elif msg_type == "VIBE":
259-
process_vibe(msg, log_data)
221+
# Extract VIBE messages and store them in VibeData
222+
elif msg_type == "VIBE":
223+
process_vibe(msg, log_data)
260224

261-
# Extract GPS messages and store them in GPSData
262-
elif msg_type == "GPS":
263-
process_gps(msg, log_data)
225+
# Extract GPS messages and store them in GPSData
226+
elif msg_type == "GPS":
227+
process_gps(msg, log_data)
264228

265-
# Extract ERR message and store them in ERRData
266-
elif msg_type == "ERR":
267-
process_err(msg, log_data)
229+
# Extract ERR message and store them in ERRData
230+
elif msg_type == "ERR":
231+
process_err(msg, log_data)
268232

269-
# Extract ARM messages and store them in ARMStat
270-
elif msg_type == "ARM":
271-
process_arm_stat(msg, log_data)
233+
# Extract ARM messages and store them in ARMStat
234+
elif msg_type == "ARM":
235+
process_arm_stat(msg, log_data)
272236

273-
# Extract MODE messages and store them in Mode
274-
elif msg_type == "MODE":
275-
process_mode(msg, log_data)
237+
# Extract MODE messages and store them in Mode
238+
elif msg_type == "MODE":
239+
process_mode(msg, log_data)
276240

277-
if firmware_from_ver is not None:
278-
log_data.firmware_info = firmware_from_ver
279-
else:
280-
log_data.firmware_info = firmware_from_msg
281-
282-
process_frame_type(log_data)
283-
log_data.messages = message_counts
241+
if firmware_from_ver is not None:
242+
log_data.firmware_info = firmware_from_ver
243+
else:
244+
log_data.firmware_info = firmware_from_msg
284245

285-
finally:
286-
close_log(mlog)
246+
process_frame_type(log_data)
247+
log_data.messages = message_counts
287248

288249
return log_data
289250

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""
2+
A parser for ArduPilot .bin log files.
3+
4+
The ArduPilot .bin format is self contained: FMT messages define the schema of every message type.
5+
Pymavlink reads those FMT definitions and decodes each message accordingly.
6+
7+
SPDX-FileCopyrightText: 2024-2026 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
8+
9+
SPDX-License-Identifier: GPL-3.0-or-later
10+
"""
11+
12+
import contextlib
13+
from typing import Any
14+
15+
from pymavlink import mavutil
16+
17+
18+
def open_log(logfile: str) -> mavutil.mavfile:
19+
"""
20+
Open an ArduPilot log file.
21+
22+
Args:
23+
logfile: The path to an ArduPilot .bin log file.
24+
25+
Returns:
26+
A mavutil.mavfile connection object.
27+
28+
"""
29+
try:
30+
mlog = mavutil.mavlink_connection(logfile)
31+
except Exception as e:
32+
msg = f"Error opening the {logfile} logfile: {e!s}"
33+
raise OSError(msg) from e
34+
return mlog # pyright: ignore[reportReturnType]
35+
36+
37+
def close_log(mlog: mavutil.mavfile) -> None:
38+
"""
39+
Close an ArduPilot log file.
40+
41+
Args:
42+
mlog: The mavutil.mavfile connection to close.
43+
44+
"""
45+
with contextlib.suppress(OSError):
46+
mlog.close()
47+
48+
49+
def parse_log(logfile: str) -> Any: # noqa: ANN401
50+
"""
51+
Open a log and yield each DataFlash message.
52+
53+
Each message is yielded as decoded by pymavlink (using the file's own FMT
54+
definitions) and discarded.
55+
56+
Args:
57+
logfile: The path to an ArduPilot .bin log file.
58+
59+
Yields:
60+
One DataFlash log message per iteration.
61+
62+
"""
63+
mlog = open_log(logfile)
64+
try:
65+
while True:
66+
msg = mlog.recv_match()
67+
if msg is None:
68+
break
69+
yield msg
70+
finally:
71+
close_log(mlog)

0 commit comments

Comments
 (0)