4. 数据采集脚本编写:Python pymavlink库入门、连接SITL仿真、订阅并打印MAVLink消息
好,到了真正动手写代码的时候了。
前面几章我们把SITL跑起来了,也看到了MAVLink消息在终端里刷刷地过。但光看不够,咱们得把数据抓到自己手里,存下来,才能做后续的质量分析。这一章,我就带你从零开始,用Python和pymavlink库,把飞控的数据“接”过来。
4.1 为什么选pymavlink?
说白了,pymavlink就是MAVLink协议的Python实现。你想想看,飞控和地面站之间通信,靠的就是MAVLink协议。pymavlink能帮我们做两件事:解析和生成MAVLink消息。
我个人习惯用它来做数据采集,原因有三:
- 纯Python:跨平台,装个pip就能用,不用折腾C++编译。
- 协议全覆盖:从ArduPilot到PX4,常见的消息类型它都支持。
- 自带解析器:你不需要自己手动去拆二进制包,它直接给你转成Python对象。
4.2 安装与第一个连接脚本
安装很简单,一行命令搞定:
pip install pymavlink
装好之后,我们来写第一个脚本。它的任务很简单:连上SITL,然后打印出飞控的心跳包(HEARTBEAT)。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
第一个pymavlink脚本:连接SITL并接收心跳
"""
import time
from pymavlink import mavutil
# 1. 创建一个连接
# SITL默认的UDP端口是14550
master = mavutil.mavlink_connection('udp:127.0.0.1:14550')
# 2. 等待心跳包
# 这一步很重要,它确认了飞控和脚本之间的通信是通的
print("等待飞控心跳...")
master.wait_heartbeat()
print("心跳已收到!")
print(f"飞控系统ID: {master.target_system}, 组件ID: {master.target_component}")
# 3. 持续接收并打印消息
print("开始接收消息,按Ctrl+C退出...")
try:
while True:
# 接收一个消息,超时时间1秒
msg = master.recv_match(blocking=True, timeout=1)
if msg:
print(f"收到消息: {msg.get_type()}")
# 如果是心跳包,打印详细信息
if msg.get_type() == 'HEARTBEAT':
print(f" 类型: {msg.type}, 状态: {msg.system_status}")
else:
print("等待消息...")
time.sleep(0.1)
except KeyboardInterrupt:
print("用户中断,退出。")
运行这个脚本之前,记得先启动SITL。我在项目中遇到过好几次,新手最容易犯的错就是忘了开SITL,然后脚本卡在wait_heartbeat()那里不动。
4.3 理解连接方式:UDP vs TCP vs 串口
刚才我们用udp:127.0.0.1:14550连接。但实际场景中,连接方式不止这一种。我整理了一个表格,方便你对照:
| 连接方式 | 连接字符串示例 | 适用场景 | 特点 |
|---|---|---|---|
| UDP | udp:127.0.0.1:14550 |
SITL仿真、本地地面站 | 速度快,无连接状态,丢包不重传 |
| TCP | tcp:127.0.0.1:5760 |
远程连接、需要可靠传输 | 有握手,保证数据顺序,延迟略高 |
| 串口 | /dev/ttyUSB0:57600 |
真实飞控、数传电台 | 波特率需匹配,容易受干扰 |
4.4 订阅特定消息:别什么都收
刚才的脚本是“来什么收什么”。但在实际数据采集中,我们只关心特定的消息,比如GPS位置、姿态角、电池电压等。如果什么都收,数据量太大,而且大部分是没用的。
pymavlink提供了recv_match方法,可以按条件过滤。看这个例子:
# 只接收GPS_RAW_INT消息
msg = master.recv_match(type='GPS_RAW_INT', blocking=True)
if msg:
print(f"纬度: {msg.lat}, 经度: {msg.lon}, 海拔: {msg.alt}")
# 接收多个类型
msg = master.recv_match(type=['GPS_RAW_INT', 'ATTITUDE'], blocking=True)
if msg:
if msg.get_type() == 'GPS_RAW_INT':
print(f"GPS: {msg.lat}, {msg.lon}")
elif msg.get_type() == 'ATTITUDE':
print(f"姿态: 滚转{msg.roll:.2f}, 俯仰{msg.pitch:.2f}, 偏航{msg.yaw:.2f}")
这里有个小技巧:blocking=True表示如果没有收到消息,脚本会一直等。如果你不想等,可以设成False,然后自己加个循环和超时判断。
4.5 数据采集的核心逻辑:循环 + 过滤 + 存储
一个合格的数据采集脚本,结构应该是这样的:
- 初始化连接:建立与SITL的通信链路。
- 等待心跳:确认飞控在线。
- 进入主循环:持续接收消息。
- 消息过滤:只保留我们关心的消息类型。
- 数据提取:从消息对象中取出字段值。
- 数据存储:写入文件、数据库或打印到终端。
下面是一个更完整的示例,它订阅了GPS、姿态和电池消息,并打印到终端:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
数据采集脚本:订阅GPS、姿态、电池消息
"""
import time
from pymavlink import mavutil
# 连接SITL
master = mavutil.mavlink_connection('udp:127.0.0.1:14550')
master.wait_heartbeat()
print("连接成功,开始采集...")
# 定义我们关心的消息类型
TARGET_TYPES = ['GPS_RAW_INT', 'ATTITUDE', 'BATTERY_STATUS']
try:
while True:
# 只接收目标类型,阻塞等待
msg = master.recv_match(type=TARGET_TYPES, blocking=True)
if msg is None:
continue
msg_type = msg.get_type()
timestamp = time.time()
if msg_type == 'GPS_RAW_INT':
print(f"[{timestamp:.2f}] GPS: lat={msg.lat/1e7:.6f}, "
f"lon={msg.lon/1e7:.6f}, alt={msg.alt/1e3:.1f}m, "
f"hdop={msg.eph:.1f}")
elif msg_type == 'ATTITUDE':
print(f"[{timestamp:.2f}] ATT: roll={msg.roll:.3f}, "
f"pitch={msg.pitch:.3f}, yaw={msg.yaw:.3f}")
elif msg_type == 'BATTERY_STATUS':
voltage = msg.voltages[0] / 1000.0 # 毫伏转伏
current = msg.current_battery / 100.0 # 毫安转安
print(f"[{timestamp:.2f}] BAT: voltage={voltage:.2f}V, "
f"current={current:.2f}A, "
f"remaining={msg.battery_remaining}%")
except KeyboardInterrupt:
print("采集结束。")
你运行一下试试。如果SITL在跑,你会看到类似这样的输出:
[1678901234.56] GPS: lat=47.397742, lon=8.545594, alt=488.0m, hdop=0.8
[1678901234.57] ATT: roll=0.001, pitch=-0.002, yaw=1.234
[1678901234.58] BAT: voltage=12.50V, current=0.00A, remaining=100%
嗯,看到数据在刷新,心里就踏实了。
4.6 避坑指南:我踩过的三个坑
写采集脚本的时候,有几个地方特别容易出问题。我把自己踩过的坑列出来,你注意避开:
- 坑一:端口被占用。如果你同时开了Mission Planner或者QGroundControl,它们会占用14550端口。解决办法是:关掉地面站,或者让SITL输出到另一个端口(比如加个
-O 14551参数)。 - 坑二:消息频率太高。SITL默认的消息频率很高,尤其是IMU数据。如果你不加过滤地接收所有消息,Python脚本可能处理不过来,导致数据积压。我建议你只订阅需要的消息,并且可以在循环里加个
time.sleep(0.01)来降速。 - 坑三:忘记处理异常。网络断开、飞控重启,都可能导致连接中断。脚本里最好加个
try-except,捕获ConnectionError或OSError,然后自动重连。
4.7 本章知识体系
为了让你更直观地理解本章的内容结构,我画了一张流程图:
4.8 本章小结
这一章我们干了三件事:
- 学会了用pymavlink连接SITL仿真。
- 掌握了如何订阅并过滤MAVLink消息。
- 写了一个能跑的数据采集脚本雏形。
你现在应该能自己动手,把飞控的GPS、姿态、电池数据抓到手了。别小看这一步,它是后面所有数据质量分析的基础。数据都拿不到,分析个啥?