Skip to content

Commit 871c7f6

Browse files
feat: added PM, IMU, VIBE, GPS, ERR, MODE, ARM messages
Signed-off-by: Omkar Sarkar <omkarsarkar24@gmail.com>
1 parent 62b3f9d commit 871c7f6

1 file changed

Lines changed: 308 additions & 8 deletions

File tree

ardupilot_methodic_configurator/log_analysis/backend_log_extraction.py

Lines changed: 308 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,111 @@ def __init__(self) -> None:
7777
self.state_health: list[int] = []
7878

7979

80-
class LogData: # pylint: disable=too-few-public-methods
80+
class PMData: # pylint: disable=too-few-public-methods
81+
"""Stores Flight Controller's CPU performance telemetry data extracted from PM messages."""
82+
83+
def __init__(self) -> None:
84+
self.time_us: list[int] = []
85+
self.load: list[int] = []
86+
self.mem: list[int] = []
87+
self.loop_rate: list[int] = []
88+
self.int_err_bitmask: list[int] = []
89+
self.long_loops: list[int] = []
90+
91+
92+
class IMUData: # pylint: disable=too-many-instance-attributes,too-few-public-methods
93+
"""
94+
Stores Inertial Measurement Unit data extracted from IMU messages.
95+
96+
Note:
97+
GyrX,Y,Z are in rad/s.
98+
99+
"""
100+
101+
def __init__(self) -> None:
102+
self.time_us: list[int] = []
103+
self.gyr_x: list[float] = []
104+
self.gyr_y: list[float] = []
105+
self.gyr_z: list[float] = []
106+
self.acc_x: list[float] = []
107+
self.acc_y: list[float] = []
108+
self.acc_z: list[float] = []
109+
self.err_gyro: list[int] = []
110+
self.err_acc: list[int] = []
111+
self.temp: list[float] = []
112+
self.gyro_hlt: list[int] = []
113+
self.acc_hlt: list[int] = []
114+
self.gyro_rate: list[int] = []
115+
self.acc_rate: list[int] = []
116+
117+
118+
class VibeData: # pylint: disable=too-few-public-methods
119+
"""Stores Processed Vibration Information data extracted from VIBE messages."""
120+
121+
def __init__(self) -> None:
122+
self.time_us: list[int] = []
123+
self.vibe_x: list[float] = []
124+
self.vibe_y: list[float] = []
125+
self.vibe_z: list[float] = []
126+
self.clip: list[int] = []
127+
128+
129+
class GPSData: # pylint: disable=too-many-instance-attributes,too-few-public-methods
130+
"""
131+
Stores GPS/GNSS data extracted from GPS messages.
132+
133+
Note:
134+
GCrs and Yaw are in degrees, unlike GyrX,Y,Z. Have to convert before any operation.
135+
136+
"""
137+
138+
def __init__(self) -> None:
139+
self.time_us: list[int] = []
140+
self.status: list[int] = []
141+
self.num_sats: list[int] = []
142+
self.hor_dop: list[float] = []
143+
self.lat: list[float] = []
144+
self.lng: list[float] = []
145+
self.alt: list[float] = []
146+
self.spd: list[float] = []
147+
self.gcrs: list[float] = []
148+
self.vz: list[float] = []
149+
self.yaw: list[float] = []
150+
self.in_use: list[int] = [] # Boolean value
151+
# GMS/GWk not captured; TimeUS covers rate for analysis needs
152+
153+
154+
class ERRMsg: # pylint: disable=too-few-public-methods
155+
"""Stores subsystem error events extracted from ERR messages."""
156+
157+
# A log might not contain the ERR field
158+
def __init__(self) -> None:
159+
self.time_us: list[int] = []
160+
self.sub_sys: list[int] = []
161+
self.err_code: list[int] = []
162+
163+
164+
class ModeInfo: # pylint: disable=too-few-public-methods
165+
"""Stores flight mode transition events extracted from MODE messages."""
166+
167+
def __init__(self) -> None:
168+
self.time_us: list[int] = []
169+
self.mode_num: list[int] = []
170+
self.rsn: list[int] = [] # reason for mode change
171+
172+
173+
class ARMStat: # pylint: disable=too-few-public-methods
174+
"""Stores arming and disarming events extracted from ARM messages."""
175+
176+
def __init__(self) -> None:
177+
self.time_us: list[int] = []
178+
self.arm_state: list[int] = []
179+
self.arm_chks: list[int] = []
180+
self.forced: list[bool] = []
181+
self.method: list[int] = []
182+
183+
184+
class LogData: # pylint: disable=too-many-instance-attributes,too-few-public-methods
81185
"""Contains all data extracted from an ArduPilot .bin log."""
82186

83187
def __init__(self) -> None:
@@ -88,9 +192,19 @@ def __init__(self) -> None:
88192
self.frame_type: int | None = None
89193
# There could be multiple batteries in the vehicle, so create a separate dict for them.
90194
self.batteries: dict[int, BatteryData] = {}
91-
92-
93-
def extract_log(logfile: str) -> LogData:
195+
self.performance_monitor = PMData()
196+
# There could be multiple IMU sensors in the vehicle, so create a separate dict for them.
197+
self.imu_data: dict[int, IMUData] = {}
198+
# There could be multiple VIBE instances, so create a separate dict for them.
199+
self.vibe_data: dict[int, VibeData] = {}
200+
# There could be multiple GPS modules in the vehicle, so create a separate dict for them.
201+
self.gps_data: dict[int, GPSData] = {}
202+
self.err_data = ERRMsg()
203+
self.arm_info = ARMStat()
204+
self.mode_info = ModeInfo()
205+
206+
207+
def extract_log(logfile: str) -> LogData: # pylint: disable=too-many-branches
94208
"""
95209
Open the log file, scan every message, and return the LogData.
96210
@@ -132,6 +246,34 @@ def extract_log(logfile: str) -> LogData:
132246
elif msg_type == "BAT":
133247
process_bat(msg, log_data)
134248

249+
# Extract the PM messages and store them in PMData
250+
elif msg_type == "PM":
251+
process_performance(msg, log_data)
252+
253+
# Extract IMU messages and store them in IMUData
254+
elif msg_type == "IMU":
255+
process_imu(msg, log_data)
256+
257+
# Extract VIBE messages and store them in VibeData
258+
elif msg_type == "VIBE":
259+
process_vibe(msg, log_data)
260+
261+
# Extract GPS messages and store them in GPSData
262+
elif msg_type == "GPS":
263+
process_gps(msg, log_data)
264+
265+
# Extract ERR message and store them in ERRData
266+
elif msg_type == "ERR":
267+
process_err(msg, log_data)
268+
269+
# Extract ARM messages and store them in ARMStat
270+
elif msg_type == "ARM":
271+
process_arm_stat(msg, log_data)
272+
273+
# Extract MODE messages and store them in Mode
274+
elif msg_type == "MODE":
275+
process_mode(msg, log_data)
276+
135277
if firmware_from_ver is not None:
136278
log_data.firmware_info = firmware_from_ver
137279
else:
@@ -157,10 +299,10 @@ def process_bat(msg: Any, log_data: LogData) -> None: # noqa: ANN401
157299
158300
"""
159301
# If there are multiple battery instances store them separately
160-
inst = int(msg.Inst)
161-
if inst not in log_data.batteries:
162-
log_data.batteries[inst] = BatteryData()
163-
battery = log_data.batteries[inst]
302+
bat_inst = int(msg.Inst)
303+
if bat_inst not in log_data.batteries:
304+
log_data.batteries[bat_inst] = BatteryData()
305+
battery = log_data.batteries[bat_inst]
164306

165307
battery.time_us.append(int(msg.TimeUS))
166308
battery.volt.append(float(msg.Volt))
@@ -177,6 +319,164 @@ def process_bat(msg: Any, log_data: LogData) -> None: # noqa: ANN401
177319
battery.state_health.append(int(getattr(msg, "SH", 0)))
178320

179321

322+
def process_performance(msg: Any, log_data: LogData) -> None: # noqa: ANN401
323+
"""
324+
Extract performance telemetry from a PM DataFlash log entry.
325+
326+
Args:
327+
msg: A PM log entry object parsed from an ArduPilot .bin file
328+
(returned by mavutil.mavfile.recv_match()).
329+
log_data: The LogData instance to write performance data into.
330+
331+
"""
332+
pm = log_data.performance_monitor
333+
334+
pm.time_us.append(int(msg.TimeUS))
335+
pm.load.append(int(msg.Load))
336+
pm.mem.append(int(msg.Mem))
337+
pm.loop_rate.append(int(msg.LR))
338+
# InE renamed from IntE between firmware 4.5.4 and 4.7.0. # codespell:ignore
339+
pm.int_err_bitmask.append(int(getattr(msg, "InE", getattr(msg, "IntE", 0)))) # codespell:ignore
340+
pm.long_loops.append(int(msg.NLon))
341+
342+
343+
def process_imu(msg: Any, log_data: LogData) -> None: # noqa: ANN401
344+
"""
345+
Extract inertial Data from an IMU DataFlash log entry.
346+
347+
Args:
348+
msg: An IMU log entry object parsed from an ArduPilot .bin file
349+
(returned by mavutil.mavfile.recv_match()).
350+
log_data: The LogData instance to write IMU data into.
351+
352+
"""
353+
# If there are multiple IMU sensors
354+
imu_inst = int(msg.I)
355+
if imu_inst not in log_data.imu_data:
356+
log_data.imu_data[imu_inst] = IMUData()
357+
imu = log_data.imu_data[imu_inst]
358+
359+
imu.time_us.append(int(msg.TimeUS))
360+
imu.gyr_x.append(float(msg.GyrX))
361+
imu.gyr_y.append(float(msg.GyrY))
362+
imu.gyr_z.append(float(msg.GyrZ))
363+
imu.acc_x.append(float(msg.AccX))
364+
imu.acc_y.append(float(msg.AccY))
365+
imu.acc_z.append(float(msg.AccZ))
366+
imu.err_gyro.append(int(msg.EG))
367+
imu.err_acc.append(int(msg.EA))
368+
imu.temp.append(float(msg.T))
369+
imu.gyro_hlt.append(int(getattr(msg, "GH", 0)))
370+
imu.acc_hlt.append(int(getattr(msg, "AH", 0)))
371+
imu.gyro_rate.append(int(msg.GHz))
372+
imu.acc_rate.append(int(msg.AHz))
373+
374+
375+
def process_vibe(msg: Any, log_data: LogData) -> None: # noqa: ANN401
376+
"""
377+
Extract VIBE Data from an IMU DataFlash log entry.
378+
379+
Args:
380+
msg: An VIBE log entry object parsed from an ArduPilot .bin file
381+
(returned by mavutil.mavfile.recv_match()).
382+
log_data: The LogData instance to write VIBE data into.
383+
384+
"""
385+
# If there are multiple instances of IMU
386+
vibe_inst = int(msg.IMU)
387+
if vibe_inst not in log_data.vibe_data:
388+
log_data.vibe_data[vibe_inst] = VibeData()
389+
vibe = log_data.vibe_data[vibe_inst]
390+
391+
vibe.time_us.append(int(msg.TimeUS))
392+
vibe.vibe_x.append(float(msg.VibeX))
393+
vibe.vibe_y.append(float(msg.VibeY))
394+
vibe.vibe_z.append(float(msg.VibeZ))
395+
vibe.clip.append(int(msg.Clip))
396+
397+
398+
def process_gps(msg: Any, log_data: LogData) -> None: # noqa: ANN401
399+
"""
400+
Extract GPS/GNSS telemetry from a GPS DataFlash log entry.
401+
402+
Args:
403+
msg: A GPS log entry object parsed from an ArduPilot .bin file
404+
(returned by mavutil.mavfile.recv_match()).
405+
log_data: The LogData instance to write GPS data into.
406+
407+
"""
408+
gps_inst = int(msg.I)
409+
if gps_inst not in log_data.gps_data:
410+
log_data.gps_data[gps_inst] = GPSData()
411+
gps = log_data.gps_data[gps_inst]
412+
413+
gps.time_us.append(int(msg.TimeUS))
414+
gps.status.append(int(msg.Status))
415+
gps.num_sats.append(int(msg.NSats))
416+
gps.hor_dop.append(float(msg.HDop))
417+
gps.lat.append(float(msg.Lat))
418+
gps.lng.append(float(msg.Lng))
419+
gps.alt.append(float(msg.Alt))
420+
gps.spd.append(float(msg.Spd))
421+
gps.gcrs.append(float(msg.GCrs))
422+
gps.vz.append(float(msg.VZ))
423+
gps.yaw.append(float(msg.Yaw))
424+
gps.in_use.append(int(msg.U))
425+
426+
427+
def process_err(msg: Any, log_data: LogData) -> None: # noqa: ANN401
428+
"""
429+
Extract subsystem error data from an ERR DataFlash log entry.
430+
431+
Args:
432+
msg: An ERR log entry object parsed from an ArduPilot .bin file
433+
(returned by mavutil.mavfile.recv_match()).
434+
log_data: The LogData instance to write error data into.
435+
436+
"""
437+
err = log_data.err_data
438+
439+
err.time_us.append(int(msg.TimeUS))
440+
err.sub_sys.append(int(msg.Subsys))
441+
err.err_code.append(int(msg.ECode))
442+
443+
444+
def process_arm_stat(msg: Any, log_data: LogData) -> None: # noqa: ANN401
445+
"""
446+
Extract arming status from an ARM DataFlash log entry.
447+
448+
Args:
449+
msg: An ARM log entry object parsed from an ArduPilot .bin file
450+
(returned by mavutil.mavfile.recv_match()).
451+
log_data: The LogData instance to write arming data into.
452+
453+
"""
454+
arm = log_data.arm_info
455+
456+
arm.time_us.append(int(msg.TimeUS))
457+
arm.arm_state.append(int(msg.ArmState))
458+
arm.arm_chks.append(int(msg.ArmChecks))
459+
arm.forced.append(bool(msg.Forced))
460+
arm.method.append(int(msg.Method))
461+
462+
463+
def process_mode(msg: Any, log_data: LogData) -> None: # noqa: ANN401
464+
"""
465+
Extract flight mode transition data from a MODE DataFlash log entry.
466+
467+
Args:
468+
msg: A MODE log entry object parsed from an ArduPilot .bin file
469+
(returned by mavutil.mavfile.recv_match()).
470+
log_data: The LogData instance to write mode data into.
471+
472+
"""
473+
mode = log_data.mode_info
474+
475+
mode.time_us.append(int(msg.TimeUS))
476+
mode.mode_num.append(int(msg.ModeNum))
477+
mode.rsn.append(int(msg.Rsn))
478+
479+
180480
def process_param(msg: Any, log_data: LogData) -> None: # noqa: ANN401
181481
"""
182482
Validate and store a single PARM log entry into log_data's parameter dicts.

0 commit comments

Comments
 (0)