An autonomous mobile manipulation system built on ROS 2 Jazzy and Gazebo Harmonic. The robot — a MiR 100 differential drive base carrying a UR5e 6-DOF arm with a parallel-jaw gripper — can locate a target object, navigate to an optimal pick pose, grasp the object, carry it to a drop location, and place it, all under the supervision of a BehaviorTree.CPP mission executive.
Watch the full, autonomous 12-step pick-and-place mission in action:
FuLL_Working_pickplace_example.mp4
- Demo / Showcasing
- System Overview
- Hardware & Simulation Stack
- Repository Layout
- Package Reference
- Quick Start
- Launch Reference
- Architecture
- Development Workflow
- Prerequisites
📄 All launch commands with full argument tables → LAUNCH.md
┌──────────────────────────────────────────────────────────┐
│ pick_place_orchestrator │
│ BehaviorTree.CPP ──► 12-step pick-and-place mission │
│ TargetAcquisition │ OptimizePose │ NavigateToPose │
│ MoveArm │ GripperControl │ Attach/DetachPayload │
└────────┬─────────┬──────────────┬────────────┬────────────┘
│ │ │ │
▼ ▼ ▼ ▼
[Nav2 stack] [MoveIt 2] [Base Placement] [Gazebo Bridge]
autonomous motion Optimizer ros_gz_bridge
navigation planning (IK + costmap topic bridging
(Nav2 + SLAM (MoveIt 2 scoring)
Toolbox) + ros2_control)
The 12-step mission (6 pick + 6 place) is encoded in a single BehaviorTree XML file. A fallback recovery branch detaches any held payload and stows the arm if any step fails.
| Layer | Technology |
|---|---|
| Mobile Base | MiR 100 (differential drive) |
| Manipulator | Universal Robots UR5e (6-DOF) |
| Gripper | Parallel-jaw (prismatic fingers) |
| Simulator | Gazebo Harmonic (ros_gz_sim) |
| Motion Planning | MoveIt 2 |
| Navigation | Nav2 |
| SLAM | slam_toolbox (online async) |
| Mission Logic | BehaviorTree.CPP 4 |
| Build System | ROS 2 Jazzy · ament_cmake |
mobile_manipulator/
├── src/
│ ├── mir_description/ # MiR 100 URDF / meshes
│ ├── ur_description/ # UR5e URDF macros (upstream)
│ ├── mobile_manipulator_description/# Combined robot xacro + gripper
│ ├── mobile_manipulator_gazebo/ # Worlds, simulation launch
│ ├── mobile_manipulator_slam/ # slam_toolbox bringup + maps
│ ├── mobile_manipulator_nav/ # Nav2 bringup + arm-stow condition
│ ├── mobile_manipulator_moveit_config/ # MoveIt 2 SRDF + config
│ ├── base_placement_optimizer/ # IK-aware base pose action server
│ └── pick_place_orchestrator/ # BT engine + mission action server
├── specs/ # Feature specs & implementation plans
│ ├── 001-mir-ur5e-description/
│ ├── …
│ └── 012-unified-launch/
└── .agents/ # Spec Kit agent skills
Provides the unified robot URDF/xacro.
mobile_manipulator.urdf.xacro— Assembles the MiR 100 base, UR5e arm (mounted on thesurfacelink), safety collision volume, parallel-jaw gripper, and an Intel RealSense D435 camera mounted on the end-effector (ur5e_tool0) for visual servoing alignment.mobile_manipulator.gazebo.xacro— Adds Gazebo plugins (ros2_control, differential drive, wrist depth camera, IMU, etc.).
Simulation world and launch infrastructure.
- World:
worlds/nav_workspace.sdf— Table, shelving, and target workpiece in a realistic warehouse scene. Includes 2 static monitoring cameras and a top-down overview camera to visualize navigation and manipulation. - Gazebo GUI: Configured via
config/gazebo_gui.configto dock the 3D View and display live monitoring feeds of the workspace and robot. simulation.launch.py— Brings up Gazebo Harmonic, spawns the robot, starts allros_gz_bridgetopics (cmd_vel, odom, tf, joint_states, camera/points), and sequences controller spawners via event handlers.
| Launch arg | Default | Description |
|---|---|---|
world |
nav_workspace.sdf |
SDF world file name |
headless |
false |
Run Gazebo without GUI |
x_pose |
-4.0 |
Robot spawn X |
y_pose |
0.0 |
Robot spawn Y |
z_pose |
0.15 |
Robot spawn Z |
Online SLAM using slam_toolbox.
- Configured for async online mapping (
mapper_params_online_async.yaml). - RViz preset included for live map visualization.
Nav2 bringup and a custom lifecycle condition node.
is_arm_stowed_condition— Monitors the arm joint state and prevents navigation when the arm is not in its safe travel pose.- Launch variants: full Nav2, nav2 without collision monitor, and a minimal nav_bringup.
Auto-generated (and hand-tuned) MoveIt 2 configuration.
- Planning group:
robot_arm(UR5e joints). - SRDF defines
stowednamed state for safe travel. -
Deterministic Motion Planning: Configured with the Pilz Industrial Motion Planner pipeline (
CommandPlanner) for deterministic named (PTP) and straight-line Cartesian (LIN) trajectories. Enforces strict end-effector roll/pitch locking ($\pm 0.02$ rad) during Cartesian approaches/retreats, with automatic fallback to OMPL only for free-space/named transitions. Aborts immediately on linear approach planning failure to ensure safety.
ROS 2 Action Server that computes a reachable, collision-safe base pose around a target object.
Action: optimize_placement (OptimizePlacement.action)
Algorithm:
- Sample
angular_samplescandidate poses atreach_radiusaround the target. - Filter by Nav2 OccupancyGrid (obstacle check).
- Score each pose via IK feasibility + manipulability.
- Score =
alpha × manipulability + (1 − alpha) × (1 / path_distance).
See src/base_placement_optimizer/README.md for full parameter reference, and src/base_placement_optimizer/ARCHITECTURE.md for a deep dive into the system architecture, algorithm flowcharts, and how to run the end-to-end task example.
The mission executive. Exposes a PickPlaceMission action server and drives execution through a BehaviorTree.CPP tree.
Action: pick_place_mission (PickPlaceMission.action)
BT Nodes:
| Node | Type | Description |
|---|---|---|
TargetAcquisition |
Action | Resolve pick/place pose from parameter server |
OptimizePose |
Action | Call optimize_placement action |
NavigateToPose |
Action | Send Nav2 NavigateToPose goal |
MoveArm |
Action | Execute MoveIt 2 Cartesian or named-pose goal |
VisualServo |
Action | Refine end-effector alignment using wrist depth camera feedback |
GripperControl |
Action | Open / close gripper via gripper_controller |
AttachPayload |
Action | Attach collision object to ur5e_tool0 in MoveIt |
DetachPayload |
Action | Detach collision object from ur5e_tool0 |
CheckPoseCondition |
Condition | Guard: skip place phase if no place pose set |
Mission flow (behavior_trees/pick_place_mission.xml):
Steps 1-6 (PICK_PHASE):
1. TargetAcquisition(pick)
2. OptimizePose(pick) → optimized_pick_base_pose
3. NavigateToPose(optimized_pick_base_pose)
4. Execute Pick:
- GripperOpen
- MoveArm(grasp_ready) [PTP]
- MoveArm(pick_pose + 20cm standoff) [PTP]
- VisualServo (align wrist camera to cylinder side contours)
- MoveArm(pick_pose + 3cm advance) [PTP]
- GripperClose
5. AttachPayload
- MoveArm(pick_pose + 15cm lift) [PTP]
6. MoveArm(stowed) [PTP]
Steps 7-12 (PLACE_PHASE, optional):
7. TargetAcquisition(place)
8. OptimizePose(place) → optimized_place_base_pose
9. NavigateToPose(optimized_place_base_pose)
10. Execute Place:
- MoveArm(place_pose + 15cm approach) [PTP]
- MoveArm(place_pose) [LIN]
- GripperOpen
11. DetachPayload
- MoveArm(place_pose + 15cm retract) [PTP]
12. MoveArm(stowed) [PTP]
Recovery (on any failure):
DetachPayload → MoveArm(stowed) → AlwaysFailure
📺 See the Demo / Showcasing section at the top of the page for a video of the full mission in action.
# ROS 2 Jazzy + Gazebo Harmonic
sudo apt install ros-jazzy-desktop ros-jazzy-ros-gz*
# MoveIt 2
sudo apt install ros-jazzy-moveit
# Nav2
sudo apt install ros-jazzy-navigation2 ros-jazzy-nav2-bringup
# BehaviorTree.CPP 4
sudo apt install ros-jazzy-behaviortree-cpp
# slam_toolbox
sudo apt install ros-jazzy-slam-toolbox
# ros2_control
sudo apt install ros-jazzy-ros2-control ros-jazzy-ros2-controllersgit clone <repo-url> mobile_manipulator
cd mobile_manipulator
rosdep install --from-paths src --ignore-src -r -y
colcon build --symlink-install
source install/setup.bashYou can launch the entire stack (Gazebo simulation, Nav2, MoveIt 2, Base Placement Optimizer, BT Orchestrator) and automatically trigger the 12-step pick-and-place mission:
-
Via the Quickstart Script:
./run_pick_place_mission.sh
-
Via ROS 2 Launch:
ros2 launch pick_place_orchestrator pick_place_mission.launch.py
Options:
# Run Gazebo in headless mode (no GUI)
ros2 launch pick_place_orchestrator pick_place_mission.launch.py headless:=true
# Launch without auto-starting the mission (manual trigger)
ros2 launch pick_place_orchestrator pick_place_mission.launch.py auto_start:=false
# Pass custom pick and place locations
ros2 launch pick_place_orchestrator pick_place_mission.launch.py \
pick_x:=4.5 pick_y:=4.0 pick_z:=0.80 \
place_x:=4.5 place_y:=-4.0 place_z:=0.75The mission trigger node will send the action goal after an 80-second delay, allowing all system layers (localization, costmaps, planning scenes) to fully initialize.
Alternatively, bring up the entire stack without auto-starting the mission immediately:
ros2 launch pick_place_orchestrator validation.launch.pyThen, send a mission goal manually in another terminal:
ros2 action send_goal /pick_place_mission \
pick_place_orchestrator/action/PickPlaceMission \
"{pick_pose: {header: {frame_id: 'map'}, pose: {position: {x: 4.5, y: 4.0, z: 0.80}, orientation: {w: 1.0}}}, \
place_pose: {header: {frame_id: 'map'}, pose: {position: {x: 4.5, y: -4.0, z: 0.75}, orientation: {w: 1.0}}}}"All launch files across all packages are documented in LAUNCH.md, including every argument, its default value, and concrete ros2 launch examples.
Quick summary of the most-used files:
| Launch File | Package | What it starts |
|---|---|---|
pick_place_mission.launch.py |
pick_place_orchestrator |
🚀 Complete Mission — brings up the full stack and auto-starts the pick-and-place sequence |
validation.launch.py |
pick_place_orchestrator |
⭐ Full stack — sim + Nav2 + MoveIt + optimizer + orchestrator (no auto-start) |
orchestrator.launch.py |
pick_place_orchestrator |
Orchestrator node only (assumes stack is up) |
optimizer_demo.launch.py |
base_placement_optimizer |
Sim + Nav2 + MoveIt + optimizer (no orchestrator) |
simulation.launch.py |
mobile_manipulator_gazebo |
Gazebo + robot spawn + all bridges + controllers |
nav_bringup.launch.py |
mobile_manipulator_nav |
Sim + full Nav2 stack (map server, AMCL, planners) |
navigation.launch.py |
mobile_manipulator_nav |
Nav2 stack only (no Gazebo) |
demo.launch.py |
mobile_manipulator_moveit_config |
Sim + MoveIt move_group + RViz MotionPlanning panel |
move_group.launch.py |
mobile_manipulator_moveit_config |
MoveIt move_group only |
slam_mapping.launch.py |
mobile_manipulator_slam |
Sim + slam_toolbox (map building session) |
display.launch.py |
mobile_manipulator_description |
RViz URDF viewer + joint sliders |
See LAUNCH.md for the remaining files (gazebo.launch.py, optimizer.launch.py, rviz.launch.py, spawn_controllers.launch.py, nav2_no_collision_monitor.launch.py, mir_description/display.launch.py, and the UR5e standalone demos).
map
└── odom
└── base_link (MiR 100)
└── surface
├── ur5e_mount_link
│ └── ur5e_base_link → … → ur5e_tool0
│ ├── finger_left
│ └── finger_right
└── safety_collision_volume
| Topic | Type | Direction |
|---|---|---|
/cmd_vel |
geometry_msgs/Twist |
ROS → Gazebo |
/odom |
nav_msgs/Odometry |
Gazebo → ROS |
/tf |
tf2_msgs/TFMessage |
Gazebo → ROS |
/joint_states |
sensor_msgs/JointState |
Gazebo → ROS |
/camera/points |
sensor_msgs/PointCloud2 |
Gazebo → ROS |
/optimize_placement |
OptimizePlacement action |
ROS action |
/pick_place_mission |
PickPlaceMission action |
ROS action |
| Controller | Joints |
|---|---|
joint_state_broadcaster |
All joints (read-only) |
joint_trajectory_controller |
UR5e arm joints |
gripper_controller |
finger_left_joint, finger_right_joint |
Apache-2.0 — see individual package.xml files for per-package declarations.
