3. MAVLink姿态消息解析与可视化
任务说明
本案例解析 MAVLink 的 ATTITUDE(ID=30)消息,提取 roll / pitch / yaw(弧度)与 rollspeed / pitchspeed / yawspeed(弧度/秒),并实时绘制姿态曲线。为保持最小可复现,这里用纯 Python + matplotlib 做实时曲线,数据源用 pymavlink 从 UDP 端口接收;若没有真实飞控,可用同一脚本内的模拟发送端产生 ATTITUDE 报文,验证解析与绘图链路。
环境与依赖
pip install pymavlink matplotlib
MAVLink 消息定义由 pymavlink 自带,无需额外下载 XML。
核心实现
下面脚本包含两部分:sender() 通过 UDP 持续发送 ATTITUDE 报文(模拟飞控),receiver() 接收并解析,把角度与角速度写入环形缓冲,用 matplotlib 实时刷新曲线。真实使用时把 sender 换成飞控的 UDP/TCP 输出即可,接收端逻辑不变。
import math, threading, time
from collections import deque
from pymavlink import mavutil
import matplotlib.pyplot as plt
import matplotlib.animation as animation
UDP = "udpin:127.0.0.1:14550"
SEND = "udpout:127.0.0.1:14550"
def sender():
m = mavutil.mavlink_connection(SEND, source_system=1)
t = 0.0
while True:
t += 0.05
m.mav.attitude_send(
int(time.time() * 1000) & 0xFFFFFFFF,
0.3 * math.sin(t), # roll
0.2 * math.cos(t * 0.7), # pitch
0.5 * math.sin(t * 0.3), # yaw
0.3 * math.cos(t), # rollspeed
-0.14 * math.sin(t * 0.7), # pitchspeed
0.15 * math.cos(t * 0.3), # yawspeed
)
time.sleep(0.05)
N = 200
buf = {k: deque([0.0] * N, maxlen=N) for k in
("roll", "pitch", "yaw", "rollspeed", "pitchspeed", "yawspeed")}
def receiver():
m = mavutil.mavlink_connection(UDP)
m.wait_heartbeat(timeout=10)
while True:
msg = m.recv_match(type="ATTITUDE", blocking=True)
if msg is None:
continue
buf["roll"].append(msg.roll)
buf["pitch"].append(msg.pitch)
buf["yaw"].append(msg.yaw)
buf["rollspeed"].append(msg.rollspeed)
buf["pitchspeed"].append(msg.pitchspeed)
buf["yawspeed"].append(msg.yawspeed)
threading.Thread(target=sender, daemon=True).start()
threading.Thread(target=receiver, daemon=True).start()
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
fig.suptitle("MAVLink ATTITUDE")
lines = {
"roll": ax1.plot([], [], label="roll")[0],
"pitch": ax1.plot([], [], label="pitch")[0],
"yaw": ax1.plot([], [], label="yaw")[0],
"rollspeed": ax2.plot([], [], label="rollspeed")[0],
"pitchspeed": ax2.plot([], [], label="pitchspeed")[0],
"yawspeed": ax2.plot([], [], label="yawspeed")[0],
}
ax1.set_ylabel("angle (rad)"); ax1.legend(loc="upper right")
ax2.set_ylabel("rate (rad/s)"); ax2.set_xlabel("samples"); ax2.legend(loc="upper right")
for ax in (ax1, ax2):
ax.set_ylim(-1.0, 1.0); ax.grid(True)
def update(_):
for k, ln in lines.items():
ln.set_data(range(N), list(buf[k]))
return list(lines.values())
ani = animation.FuncAnimation(fig, update, interval=50, blit=False)
plt.show()
运行与验证
- 直接运行脚本:
python attitude_plot.py。脚本会先启动模拟发送端,再启动接收端,最后弹出窗口。 - 观察窗口:上方子图应出现三条随时间变化的曲线(roll/pitch/yaw),下方子图出现三条角速度曲线,均围绕 0 上下波动,幅度与发送端正弦幅值一致(约 ±0.3、±0.2、±0.5)。
- 若接入真实飞控:把
sender()线程去掉,将UDP改为飞控实际输出地址(如udpin:0.0.0.0:14550或tcp:192.168.x.x:5760),其余解析与绘图逻辑不变。 - 判定通过:曲线持续刷新、无异常退出、数值范围与发送端设定一致,即说明 ATTITUDE 解析与可视化链路正常。
关键点
recv_match(type="ATTITUDE")只取目标消息,避免其他报文干扰。- 字段单位均为弧度与弧度/秒,如需角度制可乘
180/math.pi。 - 环形缓冲
deque(maxlen=N)保证绘图窗口固定长度,避免内存增长。