Mobile Manipulation with Quadratic Programming

Building a full-stack ROS 2 mobile manipulation system from scratch. Clearpath Husky A200 base, Universal Robots UR5e arm, and Robotiq 2F-85 gripper controlled through a unified QP-based whole-body motion planner that coordinates the base and arm simultaneously. Modular xacro architecture with sim-to-real transfer via ros2_control. Status: ongoing.

Mobile Manipulation with Quadratic Programming

Overview

Building a full-stack ROS 2 mobile manipulation system from scratch. Clearpath Husky A200 base, Universal Robots UR5e arm, and Robotiq 2F-85 gripper controlled through a unified QP-based whole-body motion planner that coordinates the base and arm simultaneously. Modular xacro architecture with sim-to-real transfer via ros2_control. Status: ongoing.

This project is ongoing. I’m building a complete mobile manipulation system from the ground up in ROS 2 Humble. Not a nav2-to-MoveIt handoff. Not a “drive then reach” pipeline. The full stack: perception, whole-body QP planning, ros2_control hardware abstraction, and coordinated base-arm execution. Three subsystems, one unified planner.

The robot pairs a Clearpath Husky A200 mobile base with a Universal Robots UR5e 6-DOF arm and a Robotiq 2F-85 adaptive gripper. I chose these specifically because they all have solid ROS 2 driver support and well-documented hardware interfaces, which matters when the goal is seamless sim-to-real transfer.

Why This Project

Most mobile manipulation systems treat the base and arm as separate problems. Drive to a position, stop, then plan and execute the arm motion. That works, but it’s slow, and it throws away a huge chunk of the robot’s reachable workspace. The base and arm form a single kinematic chain with 8+ degrees of freedom, and if you plan over all of them simultaneously, the robot can navigate and manipulate in one coordinated motion. That’s the idea behind the QP formulation here.

This builds directly on my work with the Cartesian controller, where I implemented damped least-squares Jacobian IK for the UR5e at 500Hz. The mobile manipulation problem is the same math extended to a larger kinematic chain: now the Jacobian includes the base velocity terms alongside the arm joints, and the QP solver handles the joint limits, velocity bounds, and collision constraints that come with coordinating two very different actuator systems.

System Architecture

flowchart LR
    subgraph Perception["Perception"]
        A["IMU / GPS"]
        B["Encoders"]
        C["F/T Sensor"]
    end

    subgraph Planning["QP Motion Planner"]
        D["Task-Space Objective"]
        E["Joint & Velocity Constraints"]
        F["Collision Avoidance"]
    end

    subgraph Control["ros2_control"]
        G["Joint Trajectory Controller"]
        H["Diff Drive Controller"]
    end

    subgraph Hardware["Hardware Interface"]
        I["UR5e Driver"]
        J["Husky Driver"]
        K["Robotiq Driver"]
    end

    A --> D
    B --> D
    C --> D
    D --> G
    E --> G
    F --> H
    G --> I
    G --> K
    H --> J

Hardware Platform

Component Model Role
Mobile Base Clearpath Husky A200 4-wheel differential drive, all-terrain UGV
Manipulator Universal Robots UR5e 6-DOF collaborative arm, force/torque sensing
Gripper Robotiq 2F-85 Adaptive parallel-jaw, 85mm stroke
Compute Onboard PC ROS 2 Humble, Ubuntu 22.04

How It Works

Whole-Body QP Formulation

I treat the mobile base and arm as a single kinematic chain. At each control cycle, the QP solver takes the desired end-effector pose and computes optimal joint velocities for all actuated joints (base wheels + arm joints) simultaneously:

\[\min_{\dot{q}} \; \| J(q)\dot{q} - \dot{x}_{\text{des}} \|^2 + \lambda \|\dot{q}\|^2\]

subject to joint position limits, velocity bounds, and collision constraints. The regularization term $\lambda$ prevents excessive joint velocities near singularities. This is the same Tikhonov damping idea from my Cartesian controller, but the Jacobian is now a wider matrix that spans both the base and arm DOFs. The cost function balances tracking accuracy against joint effort, and the constraints keep everything physically safe.

Modular URDF Architecture

Each subsystem (base, arm, gripper) lives in its own xacro module. The top-level bringup xacro composes them through fixed joints: the UR5e mounts to the Husky’s top_plate_link, and the Robotiq attaches to the arm’s tool0 flange. This gives a single unified URDF tree with consistent TF frames across simulation and real hardware.

The modularity is the point. Swapping the arm model, gripper, or sensor payload means changing one xacro include and one mount joint. Everything downstream, the controller configs, the planning scene, the launch files, adapts automatically. I learned from the Aurelia project how much time you waste when the URDF is tangled up with the rest of the stack, so I structured this one to be cleanly separable from the start.

Sim-to-Real via ros2_control

The system uses ros2_control hardware abstractions so the same controller code runs in Gazebo Ignition and on the physical robot. A launch argument (use_sim:=true/false) switches between the Gazebo hardware interface and real drivers. Same pattern I used on the Aurelia X4, where use_sim and use_real toggled the full stack between SITL and the physical drone.

I built the Gazebo simulation with accurate physics for the Husky drivetrain, UR5e dynamics, and gripper contact forces. If the sim doesn’t match the real robot, the whole point of simulation is lost. I treated the physics modeling as a first-class engineering task, not an afterthought.

Dev Container Workflow

The entire development environment runs in a Docker container with ROS 2 Humble, pre-configured VS Code extensions, X11 forwarding for RViz and Gazebo, and GPU passthrough for simulation performance. Clone the repo, build the container, and everything works. No dependency hunting, no version conflicts.

Tech Stack

ROS 2 Humble · Gazebo Ignition · ros2_control · colcon · xacro / URDF · vcstool · Docker · C++17 · Python 3 · CMake · ament · QP Solver

Code Files
Composable URDF: Top-Level Bringup
xml
<robot name="mobile_manipulation_robot"
       xmlns:xacro="http://www.ros.org/wiki/xacro">

  <!-- Include subsystem descriptions -->
  <xacro:include filename="$(find husky_description)/
    urdf/husky.urdf.xacro"/>
  <xacro:include filename="$(find arm_description)/
    urdf/ur5e.urdf.xacro"/>
  <xacro:include filename="$(find gripper_description)/
    urdf/gripper.urdf.xacro"/>

  <!-- Mount arm to mobile base top plate -->
  <joint name="arm_mount_joint" type="fixed">
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <parent link="top_plate_link"/>
    <child  link="arm_world"/>
  </joint>

  <!-- Attach gripper to arm tool flange -->
  <joint name="gripper_mount_joint" type="fixed">
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <parent link="tool0"/>
    <child  link="gripper_base_link"/>
  </joint>

</robot>
Mobile Robotics Manipulation Motion Planning ROS2 C++ Python