前言

本文记录在 Docker 容器中搭建 PX4 SITL + ROS1 + Diff-Planner 的流程,并分别跑通深度相机和 Mid360 两条仿真链路。

如果还没有创建 ROS Noetic 容器,可以先参考《Docker使用教程》。

本文默认在容器内 root 用户下操作,~/root

一、环境说明

  • Docker 容器:ros_noetic
  • ROS:Noetic
  • PX4:v1.14.3
  • 仿真:Gazebo classic
  • PX4 目录:~/PX4-Autopilot
  • Catkin 工作空间:~/catkin_ws

整体顺序:

  1. 安装并编译 PX4 SITL
  2. 配置 ROS、Gazebo 和 MAVROS
  3. 编译 Diff-Planner-PX4
  4. 分别启动深度相机和 Mid360 仿真

二、PX4 SITL 基础环境

1. 下载 PX4

1
2
3
4
5
6
7
8
apt update
apt install -y git

cd ~
git clone https://github.com/PX4/PX4-Autopilot.git
cd PX4-Autopilot
git checkout -b dev v1.14.3
git submodule update --init --recursive

2. 安装 PX4 依赖

1
2
3
cd ~/PX4-Autopilot/Tools/setup
chmod +x ubuntu.sh
./ubuntu.sh --no-nuttx --no-sim-tools

这个脚本会补齐 PX4 基础依赖,首次执行会比较久。

如果出现 pandasnumpy 版本冲突,例如:

1
ERROR: pandas 2.0.3 has requirement numpy>=1.20.3; python_version < "3.10", but you'll have numpy 1.17.4 which is incompatible.

可以先清理 pip 中的版本,再使用系统源安装兼容版本:

1
2
3
4
5
6
7
cd ~/PX4-Autopilot/Tools/setup
python3 -m pip uninstall -y pandas numpy

apt update
apt install -y python3-numpy python3-pandas

./ubuntu.sh --no-nuttx --no-sim-tools

3. 安装 Gazebo 相关依赖

下载 Gazebo 模型:

1
2
3
4
mkdir -p ~/.gazebo/models
cd ~
git clone https://gitee.com/tyx6/gazebo_models.git
mv ./gazebo_models/* ~/.gazebo/models/

因为前面使用了 --no-sim-tools,后续编译 Gazebo classic 前需要补充 GStreamer 依赖:

1
2
3
4
5
6
7
apt update
apt install -y \
pkg-config \
libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good

4. 编译 PX4

1
2
cd ~/PX4-Autopilot
make px4_sitl_default gazebo

如果编译时报 GSTREAMER_APP_LIBRARIESGSTREAMER_LIBRARIESNOTFOUND,安装上面的 GStreamer 依赖后清理 Gazebo classic 缓存,再重新编译:

1
2
3
4
5
6
cd ~/PX4-Autopilot

rm -rf build/px4_sitl_default/build_gazebo-classic
rm -rf build/px4_sitl_default/external/Stamp/sitl_gazebo-classic

make px4_sitl_default gazebo

三、ROS 环境和 MAVROS

1. 写入环境变量

建议把环境变量写进 ~/.bashrc。如果容器的 /root 挂载到了宿主机,也可以直接在宿主机编辑:

1
nano ~/docker/ros_root/.bashrc

在文件末尾添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ROS Noetic
[ -f /opt/ros/noetic/setup.bash ] && source /opt/ros/noetic/setup.bash

# Catkin workspace
[ -f ~/catkin_ws/devel/setup.bash ] && source ~/catkin_ws/devel/setup.bash

# PX4 Gazebo classic
if [ -f ~/PX4-Autopilot/Tools/simulation/gazebo-classic/setup_gazebo.bash ]; then
source ~/PX4-Autopilot/Tools/simulation/gazebo-classic/setup_gazebo.bash ~/PX4-Autopilot/ ~/PX4-Autopilot/build/px4_sitl_default > /dev/null
fi

# PX4 ROS package path
export ROS_PACKAGE_PATH=$ROS_PACKAGE_PATH:~/PX4-Autopilot/
export ROS_PACKAGE_PATH=$ROS_PACKAGE_PATH:~/PX4-Autopilot/Tools/simulation/gazebo-classic/sitl_gazebo-classic

当前终端手动加载一次:

1
source ~/.bashrc

2. 安装 MAVROS

PX4 通过 MAVROS 和 ROS 通信。容器内默认是 root 用户,不需要加 sudo

1
2
3
4
5
apt update
apt install -y \
ros-noetic-mavros \
ros-noetic-mavros-extras \
wget

安装 GeographicLib datasets:

1
2
3
4
cd ~
wget https://gitee.com/tyx6/mytools/raw/main/mavros/install_geographiclib_datasets.sh
chmod a+x ./install_geographiclib_datasets.sh
./install_geographiclib_datasets.sh

这个脚本会下载地理坐标数据集,可能需要等待一段时间。

3. 验证 PX4 和 MAVROS

1
roslaunch px4 mavros_posix_sitl.launch

另开一个终端检查连接状态:

1
rostopic echo /mavros/state | grep connected

看到 connected: True 后,再继续后面的步骤。

四、Diff-Planner-PX4 集成

1. 安装依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
apt update
apt install -y \
python3-catkin-tools \
python3-rosinstall-generator \
python3-osrf-pycommon \
libgoogle-glog-dev \
libgflags-dev \
libeigen3-dev \
libarmadillo-dev \
ros-noetic-pcl-ros \
ros-noetic-tf2-geometry-msgs \
ros-noetic-laser-geometry \
ros-noetic-tf2-sensor-msgs

2. 下载并编译

1
2
3
4
5
6
7
8
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
git clone https://github.com/dreamer198/Diff-Planner-PX4.git

cd ~/catkin_ws
source /opt/ros/noetic/setup.bash
catkin init
catkin build

如果工作空间已经初始化过,直接执行 catkin build 即可。

3. 复制仿真配置

把 Diff-Planner-PX4 中的模型、世界文件和 launch 文件复制到 PX4 工程:

1
2
3
4
5
6
7
8
# model and world
cp -r ~/catkin_ws/src/Diff-Planner-PX4/sitl_config/models/* ~/PX4-Autopilot/Tools/simulation/gazebo-classic/sitl_gazebo-classic/models/
cp ~/catkin_ws/src/Diff-Planner-PX4/sitl_config/worlds/* ~/PX4-Autopilot/Tools/simulation/gazebo-classic/sitl_gazebo-classic/worlds/

# launch
cp ~/catkin_ws/src/Diff-Planner-PX4/sitl_config/outdoor_depth_camera.launch ~/PX4-Autopilot/launch/
cp ~/catkin_ws/src/Diff-Planner-PX4/sitl_config/outdoor_mid360.launch ~/PX4-Autopilot/launch/
cp ~/catkin_ws/src/Diff-Planner-PX4/sitl_config/px4_config.yaml ~/PX4-Autopilot/launch/

如果 PX4 目录中已有同名文件,复制前可以先备份。

五、深度相机仿真

这一条链路需要三个终端:Gazebo、se3_controllerdiff_planner

终端一:启动 Gazebo

1
roslaunch px4 outdoor_depth_camera.launch

终端二:启动控制器

1
2
3
cd ~/catkin_ws
source devel/setup.bash
roslaunch se3_controller sitl_se3_controller.launch

控制器启动后,通常会自动进入 offboard 模式、解锁,并起飞到 2 m。

终端三:启动 Diff-Planner

1
2
3
cd ~/catkin_ws
source devel/setup.bash
roslaunch diff_planner run_px4_sitl_gazebo.launch

六、Mid360 仿真

Mid360 需要额外编译 Livox Gazebo 插件,并把插件和模型复制到 PX4。

1. 下载并编译插件

1
2
3
4
5
6
cd ~/catkin_ws/src
git clone https://github.com/Tfly6/Mid360_px4_sim_plugin.git

cd ~/catkin_ws
source /opt/ros/noetic/setup.bash
catkin build

2. 测试插件

1
2
3
source ~/catkin_ws/devel/setup.bash
source ~/catkin_ws/src/Mid360_px4_sim_plugin/gazebo_setup.bash
roslaunch livox_laser_simulation test_pattern.launch

如果能正常看到点云,说明插件加载成功。

3. 复制 Mid360 配置

1
2
3
4
5
cd ~/catkin_ws
cp ./src/Mid360_px4_sim_plugin/livox_laser_simulation/launch/mavros_posix_sitl_mid360.launch ~/PX4-Autopilot/launch/
cp ./devel/lib/liblivox_laser_simulation.so ~/PX4-Autopilot/build/px4_sitl_default/build_gazebo-classic/
cp -r ./src/Mid360_px4_sim_plugin/livox_laser_simulation/models/Mid360 ~/PX4-Autopilot/Tools/simulation/gazebo-classic/sitl_gazebo-classic/models/
cp -r ./src/Mid360_px4_sim_plugin/livox_laser_simulation/models/iris_mid360 ~/PX4-Autopilot/Tools/simulation/gazebo-classic/sitl_gazebo-classic/models/

终端一:启动 Gazebo

1
roslaunch px4 outdoor_mid360.launch

终端二:启动控制器

1
2
3
cd ~/catkin_ws
source devel/setup.bash
roslaunch se3_controller sitl_se3_controller.launch

终端三:启动 Diff-Planner

1
2
3
cd ~/catkin_ws
source devel/setup.bash
roslaunch diff_planner run_px4_sitl_gazebo_mid360.launch

七、宿主机一键启动仿真

上面的三条命令都在容器内执行。如果想从宿主机一键拉起仿真,可以用 tmux + docker exec 管理三个终端。

先在宿主机安装 tmux

1
2
sudo apt update
sudo apt install -y tmux

在宿主机创建脚本:

1
2
mkdir -p ~/code
nano ~/code/start_sim_diff_planner_px4.sh

写入下面内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/usr/bin/env bash
set -euo pipefail

CONTAINER="${CONTAINER:-ros_noetic}"
SESSION="${SESSION:-diff_px4_sitl}"
WAIT_GAZEBO="${WAIT_GAZEBO:-12}"
WAIT_CONTROLLER="${WAIT_CONTROLLER:-5}"

ACTION="${1:-start}"
MODE="${2:-${MODE:-mid360}}"

usage() {
cat <<EOF
Usage:
$0 start [mid360|depth]
$0 restart [mid360|depth]
$0 stop
$0 status
$0 attach

Environment:
CONTAINER=ros_noetic
SESSION=diff_px4_sitl
WAIT_GAZEBO=12
WAIT_CONTROLLER=5
EOF
}

need_tmux() {
if ! command -v tmux >/dev/null 2>&1; then
echo "tmux not found. Install it first: sudo apt install -y tmux"
exit 1
fi
}

require_container() {
if ! docker inspect "$CONTAINER" >/dev/null 2>&1; then
echo "Docker container not found: $CONTAINER"
exit 1
fi
}

ensure_container_running() {
local running
running="$(docker inspect -f '{{.State.Running}}' "$CONTAINER")"
if [ "$running" != "true" ]; then
docker start "$CONTAINER" >/dev/null
fi
}

allow_x11() {
if [ -n "${DISPLAY:-}" ] && command -v xhost >/dev/null 2>&1; then
xhost +SI:localuser:root >/dev/null 2>&1 || true
fi
}

quote() {
printf '%q' "$1"
}

docker_exec_cmd() {
local cmd="$1"
printf 'docker exec -it %q bash -lc %s' "$CONTAINER" "$(quote "$cmd")"
}

build_commands() {
local gazebo_launch planner_launch extra_setup

case "$MODE" in
mid360)
gazebo_launch="outdoor_mid360.launch"
planner_launch="run_px4_sitl_gazebo_mid360.launch"
extra_setup='[ -f ~/catkin_ws/src/Mid360_px4_sim_plugin/gazebo_setup.bash ] && source ~/catkin_ws/src/Mid360_px4_sim_plugin/gazebo_setup.bash'
;;
depth|depth_camera)
MODE="depth"
gazebo_launch="outdoor_depth_camera.launch"
planner_launch="run_px4_sitl_gazebo.launch"
extra_setup='true'
;;
*)
usage
exit 1
;;
esac

# shellcheck disable=SC2016
BASE_SETUP='source /opt/ros/noetic/setup.bash; [ -f ~/catkin_ws/devel/setup.bash ] && source ~/catkin_ws/devel/setup.bash; if [ -f ~/PX4-Autopilot/Tools/simulation/gazebo-classic/setup_gazebo.bash ]; then source ~/PX4-Autopilot/Tools/simulation/gazebo-classic/setup_gazebo.bash ~/PX4-Autopilot ~/PX4-Autopilot/build/px4_sitl_default >/dev/null; fi; export ROS_PACKAGE_PATH=$ROS_PACKAGE_PATH:~/PX4-Autopilot:~/PX4-Autopilot/Tools/simulation/gazebo-classic/sitl_gazebo-classic'

GAZEBO_CMD="$BASE_SETUP; $extra_setup; roslaunch px4 $gazebo_launch; exec bash"
CONTROLLER_CMD="$BASE_SETUP; cd ~/catkin_ws; roslaunch se3_controller sitl_se3_controller.launch; exec bash"
PLANNER_CMD="$BASE_SETUP; cd ~/catkin_ws; roslaunch diff_planner $planner_launch; exec bash"
}

start_stack() {
need_tmux
require_container
ensure_container_running
allow_x11
build_commands

if tmux has-session -t "$SESSION" 2>/dev/null; then
echo "tmux session already exists: $SESSION"
echo "Use '$0 attach' to view it, or '$0 restart $MODE' to restart."
exit 1
fi

tmux new-session -d -s "$SESSION" -n gazebo "$(docker_exec_cmd "$GAZEBO_CMD")"
sleep "$WAIT_GAZEBO"

tmux new-window -t "$SESSION:" -n controller "$(docker_exec_cmd "$CONTROLLER_CMD")"
sleep "$WAIT_CONTROLLER"

tmux new-window -t "$SESSION:" -n diff_planner "$(docker_exec_cmd "$PLANNER_CMD")"
tmux select-window -t "$SESSION:gazebo"

echo "Started $MODE simulation in tmux session: $SESSION"
echo "Attach: $0 attach"
}

stop_stack() {
if command -v tmux >/dev/null 2>&1 && tmux has-session -t "$SESSION" 2>/dev/null; then
tmux kill-session -t "$SESSION"
fi

if docker inspect "$CONTAINER" >/dev/null 2>&1 \
&& [ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null)" = "true" ]; then
docker exec "$CONTAINER" bash -lc '
pkill -INT -f "[r]oslaunch px4 outdoor_.*launch" || true
pkill -INT -f "[r]oslaunch se3_controller sitl_se3_controller.launch" || true
pkill -INT -f "[r]oslaunch diff_planner run_px4_sitl_gazebo" || true
sleep 2
pkill -TERM -f "[g]zserver|[g]zclient|[p]x4|[r]osmaster|[r]osout" || true
' >/dev/null 2>&1 || true
fi

echo "Stopped tmux session and simulation processes."
}

status_stack() {
if command -v tmux >/dev/null 2>&1 && tmux has-session -t "$SESSION" 2>/dev/null; then
tmux list-windows -t "$SESSION"
else
echo "tmux session not running: $SESSION"
fi

docker ps --filter "name=$CONTAINER" --format "container={{.Names}} status={{.Status}}"
}

attach_stack() {
need_tmux
tmux attach -t "$SESSION"
}

case "$ACTION" in
start)
start_stack
;;
restart)
stop_stack
start_stack
;;
stop)
stop_stack
;;
status)
status_stack
;;
attach)
attach_stack
;;
-h|--help|help)
usage
;;
*)
usage
exit 1
;;
esac

赋予执行权限:

1
chmod +x ~/code/start_sim_diff_planner_px4.sh

启动 Mid360 仿真:

1
~/code/start_sim_diff_planner_px4.sh start mid360

启动深度相机仿真:

1
~/code/start_sim_diff_planner_px4.sh start depth

常用命令:

1
2
3
4
~/code/start_sim_diff_planner_px4.sh status
~/code/start_sim_diff_planner_px4.sh attach
~/code/start_sim_diff_planner_px4.sh stop
~/code/start_sim_diff_planner_px4.sh restart mid360

attach 后会进入 tmux 会话,三个窗口分别是:

1
2
3
gazebo
controller
diff_planner

tmux 常用快捷键:

1
2
3
Ctrl-b n    切到下一个窗口
Ctrl-b p 切到上一个窗口
Ctrl-b d 退出 tmux 会话但不停止仿真

如果 Gazebo 窗口没有显示,先确认创建容器时已经挂载 X11,并且宿主机执行过:

1
xhost +SI:localuser:root

八、常见问题

1. roslaunch 找不到 package

确认当前终端已经加载环境:

1
2
source /opt/ros/noetic/setup.bash
source ~/catkin_ws/devel/setup.bash

如果已经写入 ~/.bashrc,重新打开终端或执行:

1
source ~/.bashrc

2. Gazebo 找不到模型

检查模型是否已经复制到:

1
2
~/.gazebo/models/
~/PX4-Autopilot/Tools/simulation/gazebo-classic/sitl_gazebo-classic/models/

3. Mid360 插件没加载出来

重点检查:

  1. 是否执行过 source ~/catkin_ws/src/Mid360_px4_sim_plugin/gazebo_setup.bash
  2. liblivox_laser_simulation.so 是否已经复制到 ~/PX4-Autopilot/build/px4_sitl_default/build_gazebo-classic/
  3. Mid360iris_mid360 模型是否已经复制到 PX4 的 Gazebo models 目录

4. 出现 obs Land enabled

这通常是地理围栏触发,不一定是程序错误。先确认仿真流程是否还能继续,再决定是否调整参数。

总结

这套流程的关键是先跑通 PX4 SITL + MAVROS,再接入 Diff-Planner。深度相机和 Mid360 的差异主要在 Gazebo 模型、插件和对应的 launch 文件。