Multi-Robot with Conflict Based Search¶
Run a fleet of quadrupeds simultaneously with coordinated, conflict-free global plans. The coordinator is the conflict_based_search (CBS) node, which sits above each robot's global_body_planner and asks them to replan when their plans intersect.
What it does¶
flowchart TB
subgraph CBS[Coordinator]
CBSN[conflict_based_search_node<br/>OBB conflict detection]
end
subgraph R1[robot_1]
GP1[global_body_planner<br/>plan_with_constraints svc]
LP1[local_planner]
end
subgraph R2[robot_2]
GP2[global_body_planner<br/>plan_with_constraints svc]
LP2[local_planner]
end
subgraph R3[β¦robot_N]
GPN[global_body_planner<br/>plan_with_constraints svc]
LPN[local_planner]
end
CBSN -- plan_with_constraints --> GP1
CBSN -- plan_with_constraints --> GP2
CBSN -- plan_with_constraints --> GPN
GP1 --> LP1
GP2 --> LP2
GPN --> LPN
CBSN -. publishes coordinated .-> GP1
CBSN -. global_plan topics .-> GP2
CBSN -. once converged .-> GPN
Each robot keeps its own planning + control stack. CBS sits above the global planners and:
- Asks every robot's
plan_with_constraintsservice for a fresh plan - Runs OBB-vs-OBB conflict detection across each timestep of those plans
- On conflict, branches: adds a time-windowed pose constraint to one of the robots and asks it to replan
- Repeats until either a conflict-free plan set is found or
max_iterationsis hit - Publishes the coordinated
/<robot>/global_plantopics simultaneously
This page walks the canonical 3-terminal workflow.
Prerequisites¶
- A successful build of
conflict_based_search,global_body_planner,local_planner, andquad_utils - Per-robot YAML configs in
quad_utils/config/<robot>.yaml(Go2 ships with a working baseline) - A world large enough for your fleet β
big_flat.sdfis the default for the 8-robot demo
1. Spawn the world + N robots¶
This is the multi-robot equivalent of quad_gazebo.py. Defaults to an octagon-swap demo with 8 Go2s on big_flat.sdf. Override via robot_configs:='[...]':
ros2 launch quad_utils quad_multi_gazebo.py robot_configs:='[
{"name":"robot_1","type":"go2","controller":"inverse_dynamics",
"init_pose":"-x -3 -y 0 -z 0.5 -Y 0", "goal_state":"[3.0, 0.0]"},
{"name":"robot_2","type":"go2","controller":"inverse_dynamics",
"init_pose":"-x 3 -y 0 -z 0.5 -Y 3.14159", "goal_state":"[-3.0, 0.0]"}
]'
goal_state is required
Unlike single-robot planning, CBS requires every robot to declare a goal β without distinct goals there is nothing for CBS to resolve. multi_robot.py will fail loudly if a robot is missing it.
2. Stand each robot¶
ros2 topic pub --once /robot_1/control/mode std_msgs/UInt8 "data: 1"
ros2 topic pub --once /robot_2/control/mode std_msgs/UInt8 "data: 1"
# β¦ etc per robot
A small shell loop helps with large fleets:
for n in robot_1 robot_2 robot_3 robot_4 robot_5 robot_6 robot_7 robot_8; do
ros2 topic pub --once /$n/control/mode std_msgs/UInt8 "data: 1"
done
3. Launch per-robot planning + central CBS¶
This is a thin wrapper around quad_plan.py that:
- starts a
global_body_planner+local_plannerper robot under their own namespace - starts the central
conflict_based_search_node - forces every robot's
referencetogbpl(twist-controlled robots can't participate β they don't have theplan_with_constraintsservice) - enforces
goal_stateon each robot's config
4. Walk¶
Switch each robot to walk:
for n in robot_1 robot_2 robot_3 robot_4 robot_5 robot_6 robot_7 robot_8; do
ros2 topic pub --once /$n/control/mode std_msgs/UInt8 "data: 2"
done
CBS publishes coordinated /<robot>/global_plan topics; the per-robot local planners take it from there.
CBS-specific bagging¶
If you're diagnosing tracking failures or weird CBS convergence behaviour, append logging_cbs:=true:
This records a focused per-robot bag (global plan, local plan, ground truth, joint state, GRFs, control commands) under ${QUAD_LOGGER_SRC}/bags/cbs/. It's off by default because the subscribers + serialisation cost a few percent CPU per robot, which on the saturated 8-robot demo can bias the failure pattern.
Tuning¶
Key knobs live in conflict_based_search/config/conflict_based_search.yaml:
| Param | Default | Purpose |
|---|---|---|
robot_names |
["robot_1","robot_2"] |
Robots whose plan_with_constraints services CBS will call |
max_iterations |
500 |
Hard cap on CBS expansions |
service_timeout_s |
30.0 |
Per-call timeout on plan_with_constraints |
warm_start |
true |
Reuse RRT-Connect trees on replan (lazy-prune violators) instead of full rebuild |
body_length/width/height |
0.70/0.35/0.35 m |
OBB extents used for conflict detection |
update_rate |
5.0 Hz |
Polling rate while waiting on pending service futures |
Body extents assume a homogeneous fleet. If you mix platforms, size to the largest.
Common failure modes¶
CBS hits max_iterations and gives up
The coordinated set is infeasible at the current OBB sizing. Either widen the world, lower body_* extents (less conservative), or stagger goals. The "best-known" plan is still published with a warning so things don't lock up.
Per-robot planner takes way longer on replan than on first solve
Likely tree warm-start is finding very few re-usable vertices. Try warm_start: false to confirm β if behaviour is the same, the constraints are deep enough that a rebuild was the right call anyway.
Two robots' plans pass each other but Gazebo still collides them
OBB sweep currently checks aligned timesteps + midpoints. If your plan timestep is large vs robot velocity, narrow gaps can be missed. Reduce dt in the per-robot global-planner config.
robot_N never appears in CBS output
Confirm robot_N is listed in robot_names and that its plan_with_constraints service is alive (ros2 service list | grep plan_with_constraints). A robot whose reference is twist won't have the service.