4. Python读取MAVLink日志:用pyMavlink打开、遍历、过滤与提取时间戳

上一章我们聊了日志文件长什么样,这一章直接上手——用Python把日志里的数据读出来。

说实话,我见过不少工程师,拿到日志第一件事就是拖进Excel里手动翻。几千几万条消息,翻到眼花。其实用pyMavlink几行代码就能搞定。我自己刚入行时也干过傻事,手动翻了一下午,结果发现看漏了一条关键的错误码……后来就老老实实写脚本了。

4.1 环境准备:装好pyMavlink

先确认你的Python环境里有没有pyMavlink。打开终端,跑一下:

pip install pymavlink

如果已经装过,建议升级到最新版:

pip install --upgrade pymavlink
小提示: 我习惯在虚拟环境里装,避免跟其他项目冲突。尤其是你同时做多个飞控项目时,依赖版本容易打架。

4.2 打开日志文件:从文件到对象

pyMavlink支持多种日志格式,最常见的是.tlog(Telemetry日志)和.bin(二进制日志)。我们以.tlog为例。

核心代码就两行:

from pymavlink import mavutil

# 打开日志文件
mlog = mavutil.mavlink_connection('flight_log.tlog')

嗯,这里要注意:mavlink_connection这个函数名字有点误导,它不光能连串口、UDP,也能读本地文件。我第一次用的时候还以为只能连硬件,绕了一大圈。

打开成功后,mlog就是一个日志对象。你可以把它想象成一个“消息管道”,里面按时间顺序排着所有MAVLink消息。

4.3 遍历消息:一条一条看

最基础的操作就是遍历。用recv_msg()方法,每次取一条:

while True:
    msg = mlog.recv_msg()
    if msg is None:
        break
    # 处理消息
    print(msg)

为什么用while True?因为日志读到末尾时,recv_msg()会返回None。这是pyMavlink的设计习惯,跟读取实时数据流保持一致。

我在项目里遇到过一个问题:日志文件很大,几百万条消息,直接遍历会卡很久。后来我加了个计数器,每10万条打印一次进度,心里踏实多了。

count = 0
while True:
    msg = mlog.recv_msg()
    if msg is None:
        break
    count += 1
    if count % 100000 == 0:
        print(f'已处理 {count} 条消息')
注意: 遍历时不要修改日志对象的状态。pyMavlink内部维护了一个读取指针,你只能往前读,不能回退。如果想重新遍历,得重新打开文件。

4.4 按类型过滤消息:只拿你想要的

日志里消息类型很多——GPS、IMU、姿态、电池、遥控器……全混在一起。你分析问题时通常只关心某几类。

过滤的核心是检查消息的msg_type或name属性:

while True:
    msg = mlog.recv_msg()
    if msg is None:
        break
    
    # 只处理GPS消息
    if msg.get_type() == 'GPS_RAW_INT':
        lat = msg.lat / 1e7
        lon = msg.lon / 1e7
        alt = msg.alt / 1e3
        print(f'GPS: lat={lat}, lon={lon}, alt={alt}')

常用的消息类型有这些:

消息类型 含义 典型用途
GPS_RAW_INT GPS原始数据 定位精度、卫星颗数
ATTITUDE 姿态信息 翻滚、俯仰、偏航
BATTERY_STATUS 电池状态 电压、电流、剩余电量
RC_CHANNELS 遥控器通道值 检查遥控信号是否正常
STATUSTEXT 飞控文本消息 错误提示、状态信息
HEARTBEAT 心跳包 判断飞控是否在线

你想想看,如果日志里有10种消息,你只关心GPS和姿态,过滤后数据量能减少80%以上。分析效率一下子就上来了。

4.5 提取时间戳:给数据打上时间标签

时间戳是分析问题的关键。没有时间,你根本不知道故障发生的先后顺序。

MAVLink消息里有两个时间概念:

  • 系统时间(time_boot_ms):飞控启动后的毫秒数。大部分消息都带这个字段。
  • 日志记录时间:地面站或记录设备收到消息时打的时间戳。存在msg._timestamp里。

提取方式如下:

while True:
    msg = mlog.recv_msg()
    if msg is None:
        break
    
    # 系统时间(毫秒)
    if hasattr(msg, 'time_boot_ms'):
        sys_time_ms = msg.time_boot_ms
        sys_time_s = sys_time_ms / 1000.0
    
    # 日志记录时间(Unix时间戳,秒)
    log_time = msg._timestamp
    
    print(f'系统时间: {sys_time_s:.2f}s, 日志时间: {log_time}')
经验之谈: 我建议优先用time_boot_ms做时间轴。因为它是飞控内部的时间,不受地面站时钟漂移影响。有一次我排查一个GPS丢星问题,用日志时间怎么都对不上,换成系统时间后,发现丢星正好发生在飞控重启后的第47秒——问题一下就定位了。

4.6 完整示例:把GPS和姿态数据导出为CSV

把上面的知识点串起来,写一个实用的脚本:

from pymavlink import mavutil
import csv

mlog = mavutil.mavlink_connection('flight_log.tlog')

with open('extracted_data.csv', 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerow(['time_boot_ms', 'lat', 'lon', 'alt', 'roll', 'pitch', 'yaw'])
    
    while True:
        msg = mlog.recv_msg()
        if msg is None:
            break
        
        msg_type = msg.get_type()
        
        if msg_type == 'GPS_RAW_INT':
            # 暂存GPS数据
            gps_data = {
                'time': msg.time_boot_ms,
                'lat': msg.lat / 1e7,
                'lon': msg.lon / 1e7,
                'alt': msg.alt / 1e3
            }
        
        elif msg_type == 'ATTITUDE':
            # 如果之前有GPS数据,合并写入
            if gps_data and abs(msg.time_boot_ms - gps_data['time']) < 100:
                writer.writerow([
                    msg.time_boot_ms,
                    gps_data['lat'],
                    gps_data['lon'],
                    gps_data['alt'],
                    msg.roll,
                    msg.pitch,
                    msg.yaw
                ])

这个脚本会把时间对齐的GPS和姿态数据写到CSV里。你拖进Excel就能画图分析了。

一个小技巧: 如果日志里消息频率很高(比如姿态200Hz,GPS只有5Hz),可以用插值或者最近邻匹配来对齐时间。我一般用最近邻,简单够用。

4.7 本章知识结构图

下面这张图帮你理清整个流程:

pyMavlink日志读取流程 pyMavlink日志读取流程 打开日志文件 遍历消息 (recv_msg) 判断消息类型 GPS_RAW_INT ATTITUDE 其他消息 提取时间戳 + 数据 mavlink_connection() 返回None表示结束 get_type() 过滤

说白了,整个流程就是:打开 → 遍历 → 过滤 → 提取。每一步都不复杂,但组合起来就能高效处理海量日志数据。