@@ -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+
180480def 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