别说废话,直接进入正题。
如果你做无人机二次开发,或者正在学ROS2机器人开发,我猜你一定动过“在一个干净的Ubuntu里把PX4、ROS2、Gazebo全装好”的念头。然后你大概率被劝退过:依赖一个接一个,版本不兼容就崩,重装系统还要从头再来。Docker包装环境恰恰能解决这套“环境地狱”。
这篇文章给你一套可以直接抄作业的方案:用Docker容器承载PX4固件编译、SITL(软件在环仿真)和ROS2开发,在宿主机上只安装Docker和地面站软件,就能跑通PX4模拟器到ROS2的话题通信。文章会把底层原理、端口规划、常见坑全部讲清楚,无论你是刚接触PX4的学生,还是想快速出demo的机器人工程师,都能照着做。
1. 为什么我推荐在Docker里做PX4与ROS2联合开发
1.1 PX4、ROS2、Docker三者是什么关系
很多新手容易把PX4和ROS2理解为“两个软件”,其实它们更像是两个“世界”。
PX4是无人机飞控固件,运行在飞控硬件上。但它也支持在普通电脑上以SITL的方式纯软件运行,也就是模拟出一整架无人机。PX4内部有一套轻量级通信机制,叫uORB,飞控里的姿态、位置、速度、传感器数据都通过uORB消息流动。
ROS2是面向机器人的分布式通信框架,核心是DDS(数据分发服务),话题(topic)、服务(service)、动作(action)都是DDS上的抽象。ROS2生态里有很多成熟算法,比如路径规划、SLAM、目标检测,它们期望拿到的是标准的ROS2话题数据。
Docker是容器化技术,它像给一套开发环境打了“快照包”——镜像里已经把操作系统、依赖、源码、编译工具都配好了。你拉取一个镜像,就是拉到一套别人已经验证过能跑的环境。这个特性对PX4+ROS2这种依赖极多的开发场景来说,价值几乎是颠覆性的。
1.2 这套方案解决的三个核心痛点
第一,环境隔离。PX4编译要求GCC版本、CMake版本、Python依赖都对得上,ROS2安装又会影响系统级Python包,Gazebo仿真还需要图形库。这些放在一起,稍有不慎就把系统搞乱。用Docker后,PX4和ROS2共享同一个容器,宿主机完全不受牵连,重装成本约等于零。
第二,版本一致性。你写的代码在公司跑了三个月,换台新电脑编译报错,这种事我在PX4开发里见过太多次。用Docker后,把镜像tag固定下来,保证队友、服务器、CI构建环境跟你的环境完全一致,从根上消灭“在我电脑上明明是好的”这句幽灵台词。
第三,快速迁移。仿真联调需要GPU、需要特定系统版本、需要多个软件协作,手把手教人装环境少说半天。而Docker镜像一条命令拉下来即可,几十秒就能进入与本地一致的环境。这对团队协作、服务器部署、持续集成来说都是降维打击。
注意:我说的是“开发用容器”,不是“飞控硬件跑容器”。Docker方案主要用在仿真、编译、离线数据回放这类场景。真机上PX4还是刷到飞控硬件里,容器只负责开发和验证代码逻辑。
1.3 这套环境的通信链路长什么样
为了让你后面操作不迷路,我先把链路在脑子里画出来。
PX4 SITL模拟器在容器内部启动后,会虚拟出一个“飞控”,uORB消息在飞控内部流动。要让ROS2拿到这些消息,需要一个桥梁组件——micro-ROS Agent(早期版本叫micrortps_agent)。PX4固件在编译时开启RTPS功能后,容器内的Agent会通过UDP端口与SITL建立连接,把uORB消息封装成DDS/RTPS包,ROS2节点订阅这些话题就能收到。
反过来也一样,ROS2节点发布目标位置、offboard控制指令,经过Agent桥接到uORB,PX4模拟飞控就能执行。
所以整条链路是:PX4 SITL -> uORB -> RTPS桥 -> micro-ROS Agent -> DDS -> ROS2节点。Docker容器负责把PX4、Gazebo、Agent、ROS2环境全包在同一局域网内,端口映射决定宿主机和外部工具怎么连进来。
2. 开工前准备:Docker安装与镜像选型
2.1 Ubuntu宿主机装Docker的正确姿势
这里跳过“官网下载安装包”这种傻瓜式说明,重点说几个容易踩坑的细节。
安装Docker Engine的第一步是换源。国内服务器直接拉官方源会非常慢,我习惯用清华或阿里云的镜像源。以Ubuntu 22.04为例,先执行以下命令安装依赖并添加GPG密钥:
sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg然后写入软件源列表:
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin安装完之后,有个非常常见的坑:直接用docker命令会报permission denied。这是因为当前用户不在docker组里。执行:
sudo usermod -aG docker $USER然后重新登录终端(或者执行newgrp docker),docker命令才能免sudo运行。
注意:如果直接sudo usermod后不重新登录,下一步拉镜像时会一直提示权限不足,很多人会在这里卡很久。
2.2 选镜像别只看名字:px4-dev-ros2全系梳理
PX4官方在Docker Hub上有专门的开发镜像,命名非常直白。我经常用的是px4io/px4-dev-ros2-foxy,它基于Ubuntu 20.04 + ROS2 Foxy + Gazebo Classic,适合老项目。如果是新项目,建议用px4io/px4-dev-ros2-humble,对应Ubuntu 22.04 + ROS2 Humble,也是目前ROS2生态里最稳的组合之一。
这里有一个需要记住的版本匹配逻辑:ROS2的Foxy对应Ubuntu 20.04,Humble对应Ubuntu 22.04,Jazzy对应Ubuntu 24.04。PX4官方镜像会把这些绑定在一起,你直接拉对应tag就行,不要自己组装“Ubuntu 22.04 + ROS2 Foxy”,那样底层依赖大概率对不上。
拉镜像命令:
docker pull px4io/px4-dev-ros2-humble:latest启动一个测试容器:
docker run -it --rm px4io/px4-dev-ros2-humble:latest bash进去之后,你先执行ros2 topic list,如果能看到话题列表(哪怕为空,命令不报错),说明ROS2环境正常;再执行cd PX4-Autopilot,确认固件源码已在镜像里。官方镜像基本都预先拉好源码,省去git clone的等待。
2.3 网络模式与端口规划
容器跟宿主机之间的网络交互,是PX4仿真联调最容易被忽略的一环。
PX4 SITL默认通过UDP 14540端口对外发送MAVLink数据包,QGroundControl地面站就是靠这个端口与仿真飞控通信。micro-ROS Agent与PX4固件之间,老版本走UDP 2019端口,PX4 1.14之后默认改成UDP 8888。这两个端口如果没规划好,ROS2侧就永远收不到无人机数据。
我个人的经验是:开发机上优先使用host网络模式,也就是容器直接共享宿主机网络栈。这样做的好处是,容器内部启动任何UDP服务,宿主机和局域网内的其他设备都能直接访问,省去端口映射的麻烦,而且延迟最低。
docker run -it --rm --network host px4io/px4-dev-ros2-humble:latest bash代价是host模式下端口完全暴露,不适合多服务在同一宿主机且端口冲突明显的场景。如果必须用bridge网络加端口映射,至少保证以下端口被映射出来:
- 14540/udp:MAVLink主链路(QGC地面站)
- 14550/udp:QGC备用链路
- 8888/udp:micro-ROS Agent与PX4 SITL之间的RTPS链路(1.14+)
- 18570/udp:HIL仿真链路(如果做硬件在环)
3. 一条命令跑通PX4 SITL + ROS2仿真
3.1 启动PX4模拟器容器
正式开始跑仿真之前,我建议你把工作目录先建好,把需要持久化的源码挂在宿主机上。这样容器删了重建,源码和编译产物还在,不会肉疼。
举个例子,我把PX4源码、ROS2工作区都放到了~/px4_ws目录下,然后启动容器时把它挂载进去:
docker run -it --rm \ --network host \ -v ~/px4_ws:/workspace \ -v /dev/dri:/dev/dri \ -e DISPLAY=$DISPLAY \ px4io/px4-dev-ros2-humble:latest bash这里有两个容易忽略的细节。第一个是-v /dev/dri:/dev/dri,它把宿主机的GPU渲染节点挂进容器,Gazebo仿真世界才能正常渲染画面。如果你用NVIDIA显卡,建议再加上--gpus all参数。第二个是-e DISPLAY=$DISPLAY,容器里的图形程序要显示到宿主机桌面,需要这个环境变量,同时还要挂载X11的socket:-v /tmp/.X11-unix:/tmp/.X11-unix。
注意:如果容器启动后Gazebo黑屏或者报X Error,先检查DISPLAY变量和X11 socket有没有挂对。在无显示器服务器上,可以加-e LIBGL_ALWAYS_SOFTWARE=1强制软件渲染,牺牲一点流畅度换稳定性。
3.2 在容器里编译/运行PX4固件
进入容器后,先看PX4-Autopilot源码是否存在:
ls /root/PX4-Autopilot如果镜像里没有源码(有些精简镜像只装了工具链),git clone官方仓库后,记得切到与镜像配套的release分支。比如镜像基于1.14,就用v1.14.3 tag:
cd PX4-Autopilot git checkout v1.14.3编译PX4 SITL固件时,关键是要指定仿真器后端。1.14及之前的版本用:
make px4_sitl gazebo-classic1.15之后默认仿真器换成了Gazebo(Garden),命令变成:
make px4_sitl gz_x500我第一次跑的时候就吃过亏:用旧命令编译1.15版本,报错找不到gazebo-classic target。所以先做一件事,在源码根目录执行make list_config_targets,把当前版本支持的编译目标看清楚,比什么都重要。
编译时间取决于机器配置,首次编译通常需要10到20分钟。编译过程中会看到很多底层的编译输出,不要慌,只要最后出现SITL已启动、终端进入pxh> shell交互接口,说明PX4模拟器已经起来了。
3.3 启动micro-ROS Agent,连通两个“世界”
PX4 SITL跑起来之后,ROS2侧还拿不到数据,因为中间还没有桥接。
在1.14及以后,PX4官方推荐使用Micro-XRCE-DDS代理,也叫micro-ROS Agent。你需要另开一个终端,也进入相同镜像的容器,启动Agent:
cd /root/px4_ros_com_ws/src/px4_ros_com/scripts ./build_ros2_agent.sh脚本跑完后,Agent可执行文件会生成,启动命令:
MicroXRCEAgent udp4 -p 8888注意这个8888就是前面说的RTPS端口。如果一切正常,Agent会输出连接建立成功的日志。此时PX4侧和ROS2侧的“桥”就算打通了。
关于这个环节我想多提醒一句:老教程里反复出现micrortps_agent -t UDP,对应的其实是PX4 1.13及更早的版本。新版本直接把micrortps_agent换成了MicroXRCEAgent,命令格式也不一样了。你如果在1.14+固件上找micrortps,大概率会浪费时间。
3.4 验证话题:从uORB到DDS的首次握手
桥打通后,进入另一个容器终端(或者同一个容器新开会话),执行:
ros2 topic list如果你看到类似/fmu/out/vehicle_attitude、/fmu/out/vehicle_odometry、/fmu/out/battery_status这样的话题,说明PX4的uORB数据已经成功变成ROS2话题了。订阅一条看看:
ros2 topic echo /fmu/out/vehicle_attitude只要看到滚转、俯仰、偏航四元数数据持续输出,链路的完整性就算验证通过。此时PX4 SITL里敲命令让飞机起飞,比如commander takeoff,ROS2侧会立刻看到姿态变化。
这里我再补充一个判断技巧:如果ros2 topic list里有/fmu/out/xx,但没有/fmu/in/xx,说明PX4发布方向正常,但控制指令方向(ROS2到PX4)可能没通;如果什么都看不到,先回到PX4端确认SITL启动日志里有没有RTPS桥接失败的关键字,再检查Agent端口是否与PX4默认端口一致。
4. 进阶玩法:用docker compose管理整套开发环境
4.1 compose文件的完整示例与逐段讲解
单容器方案适合快速验证,但要达到“一键起整套环境”,我会用docker compose。把PX4 SITL、micro-ROS Agent、自己的ROS2应用拆成多个服务,统一编排。
下面是一个我在项目中实际使用的compose文件,稍作简化:
version: "3.8" services: px4_sitl: image: px4io/px4-dev-ros2-humble:latest container_name: px4_sitl network_mode: host working_dir: /root/PX4-Autopilot volumes: - ~/px4_ws:/workspace - /tmp/.X11-unix:/tmp/.X11-unix environment: - DISPLAY=${DISPLAY} - LIBGL_ALWAYS_SOFTWARE=1 tty: true stdin_open: true command: bash -c "git pull --ff-only || true; make px4_sitl gazebo-classic" micro_ros_agent: image: px4io/px4-dev-ros2-humble:latest container_name: micro_ros_agent network_mode: host depends_on: - px4_sitl tty: true stdin_open: true command: bash -c "cd /root/px4_ros_com_ws/src/px4_ros_com/scripts && ./build_ros2_agent.sh && MicroXRCEAgent udp4 -p 8888"这个文件里有几个值得说透的点。
第一,network_mode: host让我不需要做端口映射,PX4的14540、Agent的8888直接暴露在宿主机网络上,最简单也最贴近真实联调环境。
第二,command里我用bash -c做了一个组合启动,把git pull、编译、启动写在一起。实际使用中建议把编译放到单独步骤执行,避免每次起服务都重新编译,我这里只是为了演示一条命令跑通的思路。
第三,两个服务共享同一镜像,但角色不同,所以即便PX4容器在跑Gazebo,Agent容器也只会执行自己的逻辑,互不干扰。depends_on保证PX4 SITL先启动,但你仍可能需要sleep几秒等待端口就绪。
执行起来就一行:
docker compose up如果一切正常,你应该能看到两个容器的交替日志。这种结构化编排的好处是后续加自己的ROS2节点、再加一个VNC或者noVNC容器(方便在浏览器看仿真画面),只需在services下增加条目,不用改别的配置。
4.2 让宿主机QGroundControl连上容器内的飞控
仿真跑起来之后,很多人的需求不是马上写代码,而是先用QGroundControl看飞控状态、参数、日志。QGroundControl不需要装进容器,直接装在宿主机上。
前提是你选了host网络模式。因为PX4 SITL会同时向UDP 14540和14550发送MAVLink广播包,QGroundControl检测到同一宿主机网络上有MAVLink数据流,就会自动连接。
如果QGC连不上,我会按下面顺序排查:
- 确认容器内PX4 SITL确实启动了,pxh命令行还在;
- 宿主机上执行sudo netstat -ulnp | grep -E "14540|14550",看有没有进程监听UDP;
- QGC的Comm Link设置里确认UDP端口是14550,且开启了AutoConnect;
- 如果用的是bridge模式加端口映射,记得QGC连接的是localhost:14550,而不是容器IP。
其实99%连不上的原因就是端口没监听到。只要host网络模式下PX4 SITL活着,QGC的自动连接基本不会出差错。
4.3 扩展你的第一次PX4-ROS2应用开发
链路通了,接下来就该写自己的节点了。我建议第一次练习从offboard模式开始,也就是ROS2命令飞机起飞、悬停、飞行。
PX4官方仓库px4_ros_com里有一个offboard示例,原理是:ROS2节点以offboard模式发出Armed(解锁)和Takeoff(起飞)指令到/fmu/in/offboard_control_mode和/fmu/in/vehicle_command等话题,PX4收到后在模拟器内执行对应动作。
在开发机上编译示例:
cd /root/px4_ros_com_ws colcon build --packages-select px4_ros_com source install/setup.bash ros2 run px4_ros_com offboard_control执行后,注意终端里输出的状态切换日志。如果发现PX4拒绝解锁或者armer返回reject,通常是因为offboard模式没有正确启用,或者模式切换命令频率不满足PX4要求。PX4要求offboard控制指令至少以2Hz以上频率持续发送,否则会退出offboard模式。我写代码时会在循环里加入ros2::Rate循环,确保频率达标。
这也是为什么我强调要按官方示例的结构来写,因为PX4对消息的时序、字段填充都很敏感,自己拍脑袋发消息很容易被拒绝。
5. 常见问题与排查技巧实录
5.1 容器里连不上仿真、看不到话题的3个高频原因
我在各种交流群里回答最多的问题,几乎逃不出下面三种。
第一,PX4固件版本和Agent版本不匹配。新版固件用MicroXRCEAgent,老版本用micrortps_agent,两者不能混用。而且1.14以后PX4默认RTPS端口从2019变成了8888,Agent端口没跟着改的话,连接永远建立不起来。
第二,RMW实现不一致。宿主机或其他节点的ROS2如果通过环境变量RMW_IMPLEMENTATION指定了Cyclone DDS,而PX4 Agent用的是Fast DDS(默认),两边发现不了对方的话题。这时候要么统一RMW,要么在所有节点环境里加export RMW_IMPLEMENTATION=rmw_fastrtps_cpp。
第三,host网络模式下防火墙拦截UDP。Ubuntu自带的ufw偶尔会把UDP组播或广播拦截掉,导致同一台机器上的容器和宿主机都收不到话题。排查时先临时sudo ufw disable,如果正常了再回去配置白名单端口。
5.2 Docker权限与Windows虚拟化报错
如果是在服务器上大家共用一个账号,经常遇到的问题就是当前用户不在docker组。网上最常见的回答是“加个sudo”,但sudo之后容器内文件权限会变成root,在挂载卷里产生一堆root用户文件,后面改代码很痛苦。遇到权限问题正确做法是把自己加入docker用户组,重新登录,让docker在非root用户下运行。
Windows用户用Docker Desktop时,最容易碰到的是“virtualisation support wasn’t detected”。这个报错几乎都和BIOS里没开启虚拟化(VT-x/AMD-V)有关。解决办法是进入BIOS开启虚拟化选项,同时确保Windows下的虚拟机平台和WSL2功能已启用。开启后彻底重启Docker Desktop,不要在没重启的情况下反复尝试。
提示:我自己在Windows上做PX4开发时,更推荐在Windows里装WSL2,然后直接使用WSL2作为Docker后端。这样整个项目的挂载、文件权限、性能都要比Docker Desktop自带的Hyper-V后端舒服,Gazebo画面也能通过WSLg显示出来。
5.3 编译卡死、进度停滞与磁盘空间问题
PX4首次编译时经常看起来像是卡住了。其实GCC在编译大型C++项目时,某个编译单元可能持续几十秒不输出新日志。我的判断标准是:看CPU占用,如果编译进程的CPU一直100%,那就是正常的,耐心等;如果CPU掉到0,多半是真的卡死或者死锁,直接Ctrl+C清掉重新编译,不要在卡住的进程上浪费时间。
磁盘空间是另一个隐形杀手。PX4编译会下载依赖、生成中间文件,加上Gazebo模型缓存,通常要10GB以上空间。执行df -h /var/lib/docker检查Docker数据目录剩余空间,如果满了,用docker system prune -a清理不用的镜像,或者把Docker数据目录迁移到更大的磁盘分区。我见过太多人编译到一半报“No space left on device”,全程白等。
5.4 版本匹配速查表(强烈建议先收藏)
这里我把目前主流的版本组合整理成一张表,方便你选型时对照。
| 场景 | 镜像tag | 基础系统 | ROS2版本 | PX4版本建议 | 仿真后端 |
|---|---|---|---|---|---|
| 老项目/教学课件 | px4io/px4-dev-ros2-foxy | Ubuntu 20.04 | Foxy | 1.13及以下 | Gazebo Classic |
| 目前最稳定的组合 | px4io/px4-dev-ros2-humble | Ubuntu 22.04 | Humble | 1.14.3 | Gazebo Classic |
| 新硬件/新特性 | 自行构建或官方main镜像 | Ubuntu 22.04+ | Humble/Jazzy | 1.15+(main) | Gazebo (Garden) |
| 硬件在环HIL | px4io/px4-dev-base | 按硬件需求 | 不强制 | 与实测固件一致 | 无 |
选型的核心原则是:不要追求最新,要追求“官方教程和示例文档用了什么,你就用什么”。因为PX4生态迭代很快,网上很多教程写的是一条命令,实际版本早就变了,按官方release分支配套去做,能少踩90%的坑。
6. 一些我踩过之后才明白的小经验
6.1 尽量不要再把环境装到宿主机上
我早期喜欢“容器里试一下,宿主机再装一遍”,结果宿主机上被各种依赖搞得乱七八糟。后来我彻底放弃在宿主机装ROS2,只保留QGroundControl这一个MAVLink客户端。事实证明,这世界没了宿主机版ROS2,天塌不下来。容器里已经完全具备开发条件,挂载目录又是共享的,代码在宿主机编辑、在容器里编译运行,体验反而更顺。
6.2 注意容器与宿主机的时钟同步
这个坑藏得很深。如果宿主机用了休眠或者长时间不关机,容器的系统时钟偶尔会跑偏。PX4的MAVLink协议对时间戳非常敏感,时钟一偏,地面上看飞机的轨迹就会出现诡异的漂移,排查半天还以为是控制算法出问题。遇到这种怪异现象,先在容器里执行date,再对比宿主机时间,差得多了就同步一下,问题往往立刻消失。
6.3 把容器当“开发环境模板”而不是“一次性玩具”
用Docker做PX4+ROS2开发,最高级的用法是沉淀成自己的开发镜像。比如在官方镜像基础上把常用的colcon_ws、PX4-Autopilot的release分支、常用工具vim/git/ssh都提前打好,然后提交成私有仓库。以后不管是换电脑还是新同学入职,拉一下镜像,十分钟内就有一个完全统一、开箱即用的PX4-ROS2开发环境。
我在团队里就是这样推的,大家再也不用把时间浪费在“帮我看看我这里为什么编译不过”上。环境一致之后,问题定位的颗粒度真正落到了代码逻辑本身,这大概才是我推荐Docker做PX4-ROS2开发最根本的原因。
行了,这就是我在Docker里做PX4-ROS2开发的全部心得。如果你照着这套流程跑通了自己的第一个仿真demo,后续无论是接视觉SLAM、做目标跟踪还是编队控制,底层这套环境都不会再拖你后腿。祝起飞顺利。