3. 日志解析:Python读取CSV日志、解析JSON日志、处理二进制日志
飞行器落地后,第一件事是什么?不是看姿态曲线,不是算轨迹偏差。我个人习惯,先看一眼原始日志能不能正常解析。日志打不开,后面全是白搭。
这一章,咱们就聊聊三种最常见的日志格式——CSV、JSON、二进制。每种格式我都踩过坑,也总结了一些实用技巧。你想想看,飞控日志动不动就几百兆,解析效率差一点,调试节奏就全乱了。
3.1 CSV日志:最朴素的表格数据
CSV格式在飞控日志里太常见了。传感器校准数据、电机输出值、GPS时间戳……很多飞控系统默认就输出CSV。说白了,就是逗号分隔的文本表格。
Python自带的csv模块就能搞定。但我建议你用pandas,尤其是数据量大的时候。为什么?因为pandas底层用C语言实现,读取速度能快一个数量级。
核心要点:CSV日志解析的关键在于列名对齐和时间戳处理。飞控日志的CSV文件经常出现列数不一致的情况——某个传感器临时掉线,那一行就少了几列。
来看一段实际代码。这是我处理PX4飞控CSV日志的惯用写法:
import pandas as pd
import numpy as np
def parse_flight_csv(filepath):
"""
读取飞控CSV日志,自动处理列数不一致的问题
"""
# 先读前几行,看看列名
with open(filepath, 'r') as f:
header = f.readline().strip().split(',')
# 用pandas读取,跳过坏行
df = pd.read_csv(
filepath,
skipinitialspace=True,
on_bad_lines='skip', # 跳过格式错误的行
low_memory=False # 避免类型推断警告
)
# 时间戳转成datetime
if 'timestamp' in df.columns:
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='us')
return df
小技巧:飞控日志的时间戳通常是微秒级整数。用pd.to_datetime(..., unit='us')直接转成可读时间,后续画图、对齐数据都方便。
我在项目中遇到过一个问题:某型无人机在高温环境下飞行,CSV日志里偶尔会出现乱码字符。原因是SD卡写入时电压不稳,导致个别字节出错。解决方案就是加一个字符过滤层:
def clean_csv_line(line):
"""过滤掉非ASCII字符,避免解析崩溃"""
return ''.join(c for c in line if ord(c) < 128)
3.2 JSON日志:结构化数据的王者
JSON格式在新型飞控系统里越来越流行。为什么?因为它自带层级结构。一个JSON对象可以同时包含姿态、位置、传感器状态,嵌套关系一目了然。
但JSON也有个毛病——冗余字符太多。同样的数据,JSON文件体积可能是CSV的3到5倍。所以飞行器上的JSON日志通常会被压缩,或者只记录关键事件。
Python解析JSON用json模块就够了。不过要注意,飞控日志里的JSON经常是一行一个JSON对象,而不是一个完整的JSON数组。这叫JSON Lines格式(.jsonl)。
import json
def parse_jsonl_log(filepath):
"""
解析JSON Lines格式的飞控日志
每行一个独立的JSON对象
"""
events = []
with open(filepath, 'r') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
try:
event = json.loads(line)
events.append(event)
except json.JSONDecodeError as e:
print(f"第{line_num}行解析失败: {e}")
# 我曾经遇到过:某次飞行中日志缓冲区溢出,
# 导致最后一行被截断。这里跳过就好,别让一个错误毁了全部数据。
continue
return events
注意:JSON日志里经常出现NaN或Infinity,标准JSON不支持这些值。解析前最好做预处理,或者用json.loads()的parse_float参数自定义解析逻辑。
我个人习惯把JSON日志解析后转成pandas.DataFrame,方便后续分析。但JSON嵌套结构需要先展平:
def flatten_json_event(event, parent_key='', sep='_'):
"""
展平嵌套JSON,把 {'attitude': {'roll': 0.1, 'pitch': 0.2}}
变成 {'attitude_roll': 0.1, 'attitude_pitch': 0.2}
"""
items = []
for k, v in event.items():
new_key = f"{parent_key}{sep}{k}" if parent_key else k
if isinstance(v, dict):
items.extend(flatten_json_event(v, new_key, sep=sep).items())
else:
items.append((new_key, v))
return dict(items)
3.3 二进制日志:效率与复杂度的博弈
二进制日志是飞行器上最常用的格式。为什么?因为体积小、写入快。一个浮点数在CSV里可能占10个字节,在二进制里只占4个字节。对于每秒记录上千条数据的飞控来说,这个差距是致命的。
但二进制日志的解析也最麻烦。没有固定的结构,全靠协议定义。我见过最头疼的情况:同一个飞控的不同固件版本,二进制日志的字段顺序居然不一样。
解析二进制日志的核心是理解数据结构定义。通常飞控厂商会提供结构体定义文档,或者直接给一个解析库。如果没有?那就得自己逆向。
Python的struct模块是解析二进制数据的利器。来看一个实际例子——解析MAVLink协议的二进制日志:
import struct
def parse_mavlink_binary(filepath):
"""
解析MAVLink二进制日志
MAVLink v2 消息头格式: 0xFD + 长度 + 不兼容标志 + 兼容标志 + 序列号 + 系统ID + 组件ID + 消息ID
"""
HEADER_FORMAT = '<BBBBBBB' # 小端序,7个字节
HEADER_SIZE = struct.calcsize(HEADER_FORMAT)
messages = []
with open(filepath, 'rb') as f:
while True:
# 找同步头 0xFD
sync = f.read(1)
if not sync:
break
if sync[0] != 0xFD:
continue
# 读消息头
header_data = f.read(HEADER_SIZE - 1) # 已经读了1个同步字节
if len(header_data) < HEADER_SIZE - 1:
break
# 解析消息头
payload_len, incompat, compat, seq, sys_id, comp_id, msg_id = \
struct.unpack(HEADER_FORMAT, sync + header_data)
# 读负载
payload = f.read(payload_len)
if len(payload) < payload_len:
break
# 读校验和(2字节)
checksum = f.read(2)
messages.append({
'msg_id': msg_id,
'sys_id': sys_id,
'comp_id': comp_id,
'seq': seq,
'payload': payload
})
return messages
避坑指南:我曾经在解析某型飞控的二进制日志时,发现数据总是对不上。折腾了两天,最后发现是字节序问题——飞控用的是大端序,而我默认用了小端序。从此以后,我每次解析二进制日志,第一件事就是确认字节序。
二进制日志的另一个常见问题是对齐填充。C语言结构体为了内存对齐,会在字段之间插入填充字节。比如一个uint8_t后面跟一个uint32_t,中间会有3个填充字节。解析时如果不考虑这个,数据全错。
# 带填充的结构体示例
# C定义: struct { uint8_t flag; uint32_t value; }
# 实际内存布局: flag(1字节) + 填充(3字节) + value(4字节)
# Python解析:
flag, value = struct.unpack('<BxxxI', data) # 'x'表示跳过1个字节
3.4 三种格式对比与选型建议
说了这么多,到底该用哪种格式?我整理了一张对比表,方便你快速决策:
| 特性 | CSV | JSON | 二进制 |
|---|---|---|---|
| 可读性 | 高(文本,可直接打开) | 中(文本,但嵌套结构) | 低(需要工具解析) |
| 存储效率 | 低(冗余逗号和换行) | 低(冗余花括号和引号) | 高(紧凑二进制) |
| 解析速度 | 中(文本解析有开销) | 中(JSON解析有开销) | 高(直接内存映射) |
| 扩展性 | 差(增删列需改解析器) | 好(新增字段不影响旧解析) | 差(结构体定义必须一致) |
| 适用场景 | 地面测试、传感器校准 | 事件日志、配置参数 | 高速实时数据流 |
我的建议是:地面调试用CSV,事件记录用JSON,实时飞行用二进制。三种格式各有优劣,没有银弹。
3.5 本章知识体系
下面这张图总结了本章的核心逻辑。三种格式的解析路径、关键工具、常见陷阱一目了然:
嗯,到这里三种日志格式的解析方法就讲完了。CSV重在对齐,JSON重在展平,二进制重在协议理解。每种格式我都给出了实际可用的代码,你直接拿去用就行。