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 本章知识体系

下面这张图帮你梳理了本章的核心逻辑:

MAVSDK多机控制知识体系 1. 环境安装 2. 连接方式 3. 基础指令 pip install mavsdk 虚拟环境隔离 UDP: 仿真环境 串口: 真实飞控 多机: 端口递增 arm() 解锁 takeoff() 起飞 land() 降落 核心目标:单机/多机连接 + 基础动作控制 为后续编队飞行、任务规划打下基础

4.7 常见问题与调试技巧

最后分享几个我踩过的坑,希望能帮你省点时间:

  • 连接超时: 检查防火墙是否阻挡了UDP端口。仿真环境下,14540端口必须开放。
  • 解锁失败: 大概率是GPS信号不足,或者飞控处于「安全模式」。检查QGroundControl的状态指示灯。
  • 多机端口冲突: 用 netstat -tulpn | grep 1454 查看端口占用情况。
  • 异步代码卡死: 确保事件循环没有嵌套。MAVSDK要求所有操作在同一个事件循环中执行。

安全提醒: 真实飞行时,务必在代码中加入紧急停止逻辑。比如检测到连接断开,自动触发降落。我曾经见过因为WiFi干扰导致无人机失联,幸好有自动返航机制才没炸机。