4. Hello MAVLink:使用 pymavlink 发送第一条 HEARTBEAT 消息,验证通信链路
好了,到了真正动手的时候了。
前面几章我们聊了 MAVLink 的协议结构、消息格式,还有 ArduPilot 里那些消息怎么流转的。说实话,光看理论容易犯困。我当年学的时候也是这样,看了三天文档,脑子里全是「嗯,懂了」,一打开终端就懵了。
所以这一章,咱们直接干点实在的——用 Python 写一段代码,让电脑和飞控(或者 SITL 仿真)互相发一条 HEARTBEAT 消息。只要这条消息能通,后面的所有解析、转发、地面站开发,就都有了底气。
本章核心目标: 搭建 pymavlink 环境 → 编写发送 HEARTBEAT 的脚本 → 验证链路是否正常。
4.1 为什么第一条消息必须是 HEARTBEAT?
MAVLink 系统里,HEARTBEAT(消息 ID: 0) 是最基础、最核心的消息。你可以把它理解成「我还活着」的信号。
每个 MAVLink 设备(飞控、地面站、摄像头、云台……)都必须定期广播 HEARTBEAT。接收方只要收到它,就能知道:
- 对方的系统类型(是飞控还是地面站?)
- 对方的飞控类型(ArduPilot 还是 PX4?)
- 当前的飞行模式
- 系统状态(是否就绪、是否有错误)
说白了,HEARTBEAT 就是 MAVLink 世界的「握手协议」。你连 HEARTBEAT 都收不到,后面那些 GPS、姿态、遥控器数据就更别想了。
我的经验: 有一次在野外调试,地面站死活连不上飞控。折腾了半小时,最后发现是串口 TX/RX 接反了。HEARTBEAT 收不到,所有消息都白搭。所以,先确认 HEARTBEAT 能通,再谈别的——这是我个人的铁律。
4.2 环境准备:装好 pymavlink
pymavlink 是 MAVLink 的 Python 实现库,由 ArduPilot 社区维护。它支持 MAVLink 1.0 和 2.0,能解析、生成、发送各种消息。
安装很简单,一行命令:
pip install pymavlink
如果你用的是 Python 虚拟环境(我强烈建议这么做),记得先激活环境再装。
注意: pymavlink 依赖 future 和 lxml。如果安装报错,先手动装这两个依赖:pip install future lxml。
装好之后,验证一下:
python -c "from pymavlink import mavutil; print('pymavlink imported successfully')"
如果没报错,恭喜,环境就绪了。
4.3 连接方式:串口 vs UDP
实际项目中,连接飞控通常有两种方式:
| 连接方式 | 适用场景 | 示例地址 |
|---|---|---|
| 串口(Serial) | 物理连接飞控(USB 转串口 / 数传电台) | /dev/ttyUSB0(Linux)COM3(Windows) |
| UDP | SITL 仿真 / 局域网通信 | udp:127.0.0.1:14550 |
| TCP | 远程连接 / 桥接 | tcp:192.168.1.100:5760 |
这一章,我建议你用 UDP 连接 SITL。为什么呢?因为不用插硬件,电脑上就能跑。等你把逻辑调通了,再换串口连真飞控,心里更有底。
小技巧: 如果你用串口连 Pixhawk,Linux 下记得把用户加到 dialout 组:sudo usermod -a -G dialout $USER。不然会报权限错误——我当年被这个坑了整整一个下午。
4.4 启动 SITL 仿真
如果你没有真飞控,先启动 ArduPilot 的 SITL(软件在环仿真)。
假设你已经装好了 ArduPilot 源码,在终端里运行:
cd ~/ardupilot/ArduCopter
sim_vehicle.py --console --map
这会启动一架四旋翼仿真,并打开控制台和地图。默认的 UDP 端口是 14550(地面站端口)。
你可以用 Mission Planner 或 QGroundControl 连上去看看,确认仿真跑起来了。
4.5 写代码:发送 HEARTBEAT
好,重头戏来了。
我们要写一个 Python 脚本,做两件事:
- 连接到 SITL(或飞控)
- 发送一条 HEARTBEAT 消息,并打印接收到的 HEARTBEAT
先看完整代码:
#!/usr/bin/env python3
"""
hello_mavlink.py - 发送并接收 HEARTBEAT 消息
"""
import time
from pymavlink import mavutil
# 1. 创建 MAVLink 连接
# 这里用 UDP 连接 SITL,端口 14550
master = mavutil.mavlink_connection('udp:127.0.0.1:14550')
# 2. 等待接收第一条 HEARTBEAT(来自飞控)
print("等待 HEARTBEAT...")
master.wait_heartbeat()
print(f"收到 HEARTBEAT!")
print(f" 系统 ID: {master.target_system}")
print(f" 组件 ID: {master.target_component}")
print(f" 飞控类型: {master.mavlink20() leve}") # 简化示意
# 3. 构造并发送我们自己的 HEARTBEAT
# 我们模拟一个地面站(GCS)
print("发送 HEARTBEAT...")
master.mav.heartbeat_send(
mavutil.mavlink.MAV_TYPE_GCS, # 类型:地面站
mavutil.mavlink.MAV_AUTOPILOT_INVALID, # 自驾仪类型:无
0, # 基础模式
0, # 自定义模式
0 # 系统状态
)
# 4. 持续接收并打印 HEARTBEAT
print("开始监听 HEARTBEAT(按 Ctrl+C 退出)...")
try:
while True:
msg = master.recv_match(type='HEARTBEAT', blocking=True)
if msg:
print(f"[{time.strftime('%H:%M:%S')}] 收到 HEARTBEAT:")
print(f" 类型: {msg.type}")
print(f" 自驾仪: {msg.autopilot}")
print(f" 模式: {msg.custom_mode}")
print(f" 状态: {msg.system_status}")
except KeyboardInterrupt:
print("\n退出。")
关键点说明:
mavutil.mavlink_connection():统一连接接口,自动识别串口/UDP/TCP。wait_heartbeat():阻塞等待,直到收到第一条 HEARTBEAT。这是验证链路是否通的最快方法。heartbeat_send():发送 HEARTBEAT。参数里MAV_TYPE_GCS表示我们是一个地面站。recv_match(type='HEARTBEAT'):只接收 HEARTBEAT 消息,过滤掉其他消息。
4.6 运行与验证
确保 SITL 已经跑起来,然后在另一个终端运行脚本:
python hello_mavlink.py
如果一切正常,你会看到类似这样的输出:
等待 HEARTBEAT...
收到 HEARTBEAT!
系统 ID: 1
组件 ID: 1
飞控类型: 1
发送 HEARTBEAT...
开始监听 HEARTBEAT(按 Ctrl+C 退出)...
[14:23:45] 收到 HEARTBEAT:
类型: 2
自驾仪: 3
模式: 0
状态: 4
[14:23:46] 收到 HEARTBEAT:
...
看到没?飞控每隔 1 秒就会发一条 HEARTBEAT。我们的脚本也成功发送了一条 HEARTBEAT(虽然飞控不一定处理它,但链路已经双向打通了)。
常见问题:
- 卡在「等待 HEARTBEAT...」: 检查 SITL 是否启动,UDP 端口是否写对。或者试试
udp:127.0.0.1:14550?source_system=255。 - 权限错误: 如果是串口,检查用户组和端口权限。
- 收不到自己的 HEARTBEAT: 正常。飞控不会转发你的 HEARTBEAT,但你可以用 Wireshark 抓包确认它发出去了。
4.7 知识体系:HEARTBEAT 通信链路全景
为了让你更直观地理解这一章的核心逻辑,我画了一张流程图:
这张图把整个流程拆成了四个步骤:
- 等待:脚本阻塞等待飞控的第一条 HEARTBEAT。
- 接收:收到后打印系统 ID 和组件 ID,确认链路下行正常。
- 发送:脚本主动发送一条 HEARTBEAT,验证链路上行正常。
- 持续监听:进入循环,实时打印后续的 HEARTBEAT。
说白了,这就是一个「你发我收,我发你收」的闭环。只要这个闭环能跑通,MAVLink 通信的基础就夯实了。
4.8 避坑指南:我踩过的三个坑
最后,分享几个我当年踩过的坑,希望能帮你省点时间:
- 坑一:UDP 端口写成了
14551。SITL 默认地面站端口是14550,但很多人(包括我)会记成14551(那是遥测端口)。连不上?先检查端口号。 - 坑二:忘了设置
source_system。如果你同时跑多个脚本,系统 ID 冲突会导致 HEARTBEAT 被忽略。建议每个脚本指定不同的source_system,比如mavlink_connection(..., source_system=255)。 - 坑三:串口波特率不匹配。真飞控的串口波特率通常是
115200或57600。如果mavlink_connection()里没指定波特率,默认是115200。不匹配的话,HEARTBEAT 永远收不到。
我的习惯: 每次新项目,我都会先跑一遍这个 HEARTBEAT 脚本,确认物理链路没问题。这就像写代码先写 print("Hello World")——简单,但能排除 80% 的底层问题。
好了,这一章的内容就到这里。你现在应该已经亲手发送并接收了第一条 MAVLink 消息。别小看这一步——它意味着你已经掌握了 MAVLink 通信的「呼吸」和「心跳」。后面的所有内容,都建立在这个基础之上。