4、MAVSDK与多机控制:MAVSDK Python库安装、连接单机与多机、基础指令(起飞、降落、悬停)
好,咱们进入第四章。这一章是真正动手的开始。
前面几章我们聊了PX4的架构、多机协同的逻辑,但那些都是纸上谈兵。从这章开始,我们要让代码真正飞起来。MAVSDK,说白了就是PX4给开发者开的一扇门。你不需要去啃晦涩的MAVLink协议栈,直接用Python就能控制无人机。
我个人习惯把MAVSDK比作「遥控器的API版」。你按一下摇杆,飞机就动;你调一个API,飞机也动。只不过API能做的事情,比摇杆多得多。
本章核心目标: 学会用MAVSDK Python库连接无人机,并完成起飞、悬停、降落这三个最基础的动作。这是后续所有多机任务的基础。
4.1 MAVSDK Python库的安装
先搞定环境。我建议你用Python 3.8以上版本,太老的版本有些依赖会出问题。
# 推荐使用虚拟环境,避免污染全局Python
python3 -m venv px4_env
source px4_env/bin/activate # Linux/Mac
# 或者 Windows: px4_env\Scripts\activate
# 安装MAVSDK
pip install mavsdk
嗯,这里要注意。如果你是在树莓派或者NVIDIA Jetson这类ARM架构的设备上装,可能会遇到编译问题。我曾经在Jetson Nano上折腾了整整一下午,最后发现是缺少libssl-dev。
避坑指南: 如果你遇到 grpcio 安装失败,先装系统依赖:sudo apt install libssl-dev libffi-dev python3-dev。这是MAVSDK底层通信依赖gRPC,编译不过去就全白搭。
装完之后验证一下:
python3 -c "from mavsdk import System; print('MAVSDK installed successfully')"
没报错就说明成了。我每次在新环境配完都会跑这一句,省得后面调试半天发现是库没装好。
4.2 连接单机:从地面站到代码
连接无人机,本质上就是建立一个通信通道。MAVSDK支持多种连接方式,最常用的是UDP和串口。
| 连接方式 | 适用场景 | 连接字符串示例 |
|---|---|---|
| UDP | 仿真环境(Gazebo/SITL) | udp://:14540 |
| 串口 | 真实飞控(Pixhawk等) | serial:///dev/ttyUSB0:57600 |
| TCP | 远程连接或桥接 | tcp://192.168.1.100:5760 |
我个人在开发阶段几乎只用UDP连仿真。为什么呢?因为串口动不动就掉线,而且调试信息不好抓。仿真环境里你可以随便炸机,不心疼。
来看一个完整的单机连接代码:
import asyncio
from mavsdk import System
async def connect_single_drone():
drone = System()
# 连接到仿真环境中的无人机
await drone.connect(system_address="udp://:14540")
print("正在等待无人机连接...")
async for state in drone.core.connection_state():
if state.is_connected:
print(f"无人机已连接!UUID: {state.uuid}")
break
# 运行
asyncio.run(connect_single_drone())
这里有个细节:async for 是MAVSDK的典型用法。它是个异步迭代器,会不断监听状态变化。我第一次用的时候还纳闷,为什么不用普通的 await?后来才明白,连接状态是动态的,你需要持续监听才能知道什么时候真正连上了。
4.3 连接多机:区分每一架飞机
单机连接搞定了,多机呢?说白了就是给每架飞机分配不同的端口。
在PX4 SITL仿真中,启动多机的标准做法是:
# 启动第一架飞机,端口14540
make px4_sitl gazebo-classic_iris__
# 启动第二架飞机,端口14541
PX4_SIM_MODEL=iris ./build/px4_sitl_default/bin/px4 -i 1
# 启动第三架飞机,端口14542
PX4_SIM_MODEL=iris ./build/px4_sitl_default/bin/px4 -i 2
每架飞机的实例ID(-i参数)不同,对应的UDP端口也不同。规则很简单:实例0用14540,实例1用14541,以此类推。
代码里怎么连?看这个:
async def connect_multiple_drones(count=3):
drones = []
for i in range(count):
drone = System()
port = 14540 + i
await drone.connect(system_address=f"udp://:{port}")
# 等待连接确认
async for state in drone.core.connection_state():
if state.is_connected:
print(f"无人机 {i} 已连接,端口: {port}")
drones.append(drone)
break
return drones
注意: 多机连接时,一定要确保每架飞机的端口不冲突。我曾经在演示现场因为端口写重复了,三架飞机同时响应同一个指令,场面一度非常混乱。嗯,从那以后我每次都会在代码里打印端口号做校验。
4.4 基础指令:起飞、悬停、降落
连接上了,该让飞机动起来了。MAVSDK的动作指令封装得非常简洁,核心就三个:arm()、takeoff()、land()。
先看一个完整的起飞-悬停-降落流程:
async def basic_flight(drone):
# 1. 先解锁
print("正在解锁...")
await drone.action.arm()
# 2. 起飞到5米高度
print("起飞中...")
await drone.action.takeoff()
# 3. 悬停5秒
print("悬停中...")
await asyncio.sleep(5)
# 4. 降落
print("降落中...")
await drone.action.land()
print("任务完成!")
这段代码看起来简单,但有几个坑我得提醒你:
- 解锁前检查: 确保GPS信号良好(至少6颗星),否则
arm()会失败。我在室内测试时经常被这个坑到。 - 起飞高度:
takeoff()默认飞到2.5米,但你可以通过drone.param.set_param_float("MIS_TAKEOFF_ALT", 5.0)修改。 - 悬停不是静止: 受风力和GPS精度影响,飞机会在小范围内漂移。别指望它像钉子一样钉在空中。
我的习惯: 每次起飞前,我都会先读取一下无人机的位置和姿态信息,确认数据正常再解锁。代码里加个几秒钟的延迟,给自己留个反应时间。
4.5 多机同时起飞:同步的艺术
单机飞没意思,咱们来点真格的——让三架飞机同时起飞。
这里的关键是「同步」。你不能让飞机A先飞,飞机B后飞,那样就乱套了。正确的做法是:先让所有飞机都准备好,然后同时发送指令。
async def sync_takeoff(drones):
# 第一步:所有飞机解锁
print("正在解锁所有飞机...")
tasks = [drone.action.arm() for drone in drones]
await asyncio.gather(*tasks)
# 第二步:同时起飞
print("所有飞机同时起飞!")
tasks = [drone.action.takeoff() for drone in drones]
await asyncio.gather(*tasks)
# 第三步:同时悬停
print("所有飞机悬停中...")
await asyncio.sleep(10)
# 第四步:同时降落
print("所有飞机同时降落!")
tasks = [drone.action.land() for drone in drones]
await asyncio.gather(*tasks)
asyncio.gather 是Python异步编程里的「并发执行器」。它会同时启动所有协程,然后等待它们全部完成。说白了就是「大家一起上」。
你想想看,如果不用 gather,而是用 for 循环一个个调用,那第二架飞机要等第一架起飞完才开始,这就不是多机协同了,是单机串行表演。
4.6 本章知识体系
下面这张图帮你梳理了本章的核心逻辑:
4.7 常见问题与调试技巧
最后分享几个我踩过的坑,希望能帮你省点时间:
- 连接超时: 检查防火墙是否阻挡了UDP端口。仿真环境下,14540端口必须开放。
- 解锁失败: 大概率是GPS信号不足,或者飞控处于「安全模式」。检查QGroundControl的状态指示灯。
- 多机端口冲突: 用
netstat -tulpn | grep 1454查看端口占用情况。 - 异步代码卡死: 确保事件循环没有嵌套。MAVSDK要求所有操作在同一个事件循环中执行。
安全提醒: 真实飞行时,务必在代码中加入紧急停止逻辑。比如检测到连接断开,自动触发降落。我曾经见过因为WiFi干扰导致无人机失联,幸好有自动返航机制才没炸机。