Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 49 additions & 6 deletions docs/ur5-ws/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
![Docker image version](https://img.shields.io/docker/v/j3soon/ros2-ur5-ws)
![Docker image size](https://img.shields.io/docker/image-size/j3soon/ros2-ur5-ws)

This workspace provides a ROS 2 Humble environment for controlling **Universal Robots UR5** manipulators. It includes the official [Universal_Robots_ROS2_Driver](https://github.com/UniversalRobots/Universal_Robots_ROS2_Driver), description packages, **OnRobot RG2** gripper support, **servo control**, and **pose tracking**.
This workspace provides a ROS 2 Humble environment for controlling **Universal Robots UR5** and **UR5e** manipulators. It includes the official [Universal_Robots_ROS2_Driver](https://github.com/UniversalRobots/Universal_Robots_ROS2_Driver), description packages, **OnRobot RG2** gripper support, **servo control**, **pose tracking**, and **Isaac Sim / Isaac Lab** for high-fidelity simulation and policy learning.

> Please note that this workspace has only been tested on the UR5 (CB3 version). In theory, it can support other models with minor configuration adjustments; please feel free to modify the settings as needed for your specific use case.
> Please note that this workspace has been primarily tested on the UR5 (CB3 version) and UR5e. The bundled `Universal_Robots_ROS2_Description` covers the rest of the UR family, so other models can be supported with minor configuration adjustments.
>
> See [Last tested](../last-tested.md) for the latest validation status.

Expand Down Expand Up @@ -71,6 +71,33 @@ Regarding the RG2 gripper: although it is not explicitly modeled in URSim, our c

Since URSim aims to faithfully replicate the real robot's behavior, you should follow all the steps outlined in the subsequent sections, including calibration and bring-up.

## Simulation with Isaac Sim

The container ships with Isaac Sim 5.1.0 (enabled via `ISAAC_SIM_VERSION` in `docker/compose.yaml`). Two helper scripts spawn a UR5 or UR5e in Isaac Sim:

```sh
cd /home/ros2-essentials/ur5_ws
./scripts/isaac_sim_ur5.sh # UR5
./scripts/isaac_sim_ur5e.sh # UR5e
```

Both wrappers call `${ISAACSIM_PYTHON_EXE} scripts/isaac/spawn_ur.py` with the appropriate `--ur_type`. The script loads the bundled UR USD asset from `Isaac/Robots/UniversalRobots/<ur_type>/<ur_type>.usd`, drops the robot onto a ground plane, and steps the simulator until the window is closed. Add `--headless` for offscreen rendering.

Isaac Sim/Lab assume a desktop X11 environment. If you are on a remote server, use a remote-desktop session (VNC, NoMachine, etc.) — the same caveats described in the [SO-101 workspace docs](../so101-ws/index.md) apply here.

## Imitation Learning / RL with Isaac Lab

Isaac Lab 2.3.2 is also pre-installed (see `ISAAC_LAB_VERSION` in `docker/compose.yaml`). A minimal scaffold loads UR5/UR5e as an Isaac Lab `Articulation`:

```sh
cd /home/ros2-essentials/ur5_ws
./scripts/isaac_lab_ur_minimal.sh --ur_type ur5e
```

This is a starting point, not a full task. To build a Reach / Pick / RL task, copy Isaac Lab's existing UR10 manager-based config (under `$ISAACLAB_PATH/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/ur_10/`) and swap the USD path for `ur5` or `ur5e`. UR robots share joint names across the family, so most of the config carries over unchanged.

See `scripts/isaac/README.md` for more notes.

## Setup Robot

> For URSim users, the settings are already pre-configured in the docker compose file, so you can skip this step.
Expand All @@ -81,25 +108,41 @@ For network setup, please refer to [this section](https://docs.universal-robots.

## Robot Calibration

To improve accuracy, run the calibration correction and save the result to the workspace description config:
To improve accuracy, run the calibration correction and save the result to the workspace description config.

For **UR5**:

```sh
cd /home/ros2-essentials/ur5_ws
./scripts/ur5_calibration.sh
```

You only need to run this once for each robot. This creates `default_kinematics.yaml` which is required for the subsequent steps.
For **UR5e**:

```sh
cd /home/ros2-essentials/ur5_ws
./scripts/ur5e_calibration.sh
```

You only need to run this once for each robot. This creates `default_kinematics.yaml` (under `src/Universal_Robots_ROS2_Description/config/<ur_type>/`) which is required for the subsequent steps.

## Bring Up Robot

Use the script:
For **UR5**:

```sh
cd /home/ros2-essentials/ur5_ws
./scripts/ur5_bringup.sh
```

Or run the launch file directly:
For **UR5e**:

```sh
cd /home/ros2-essentials/ur5_ws
./scripts/ur5e_bringup.sh
```

Or run the launch file directly (swap `ur_type:=ur5` for `ur5e` as needed):

```sh
ros2 launch ur_robot_driver ur_control.launch.py \
Expand Down
4 changes: 2 additions & 2 deletions ur5_ws/docker/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ services:
# TODO: Set CUDA Toolkit version or set to "" if not using CUDA Toolkit
# CUDA_TOOLKIT_VERSION: ""
# TODO: Set Isaac Sim version, set to "" if not using Isaac Sim, set to "5.1.0" for the binary install, or set to "develop" to build upstream IsaacSim from source
# ISAAC_SIM_VERSION: "5.1.0" # "develop"
ISAAC_SIM_VERSION: "5.1.0" # "develop"
# TODO: Set Isaac Lab version or set to "" if not using Isaac Lab, set to "2.3.2" for the latest stable version, or set to "develop" to build upstream Isaac Lab from source
# ISAAC_LAB_VERSION: "2.3.2" # "develop"
ISAAC_LAB_VERSION: "2.3.2" # "develop"
# TODO: Set to "YES" if using Isaac ROS or set to "" if not using Isaac ROS
# ISAAC_ROS: ""
# TODO: Set to "YES" if using Cartographer or set to "" if not using Cartographer
Expand Down
34 changes: 34 additions & 0 deletions ur5_ws/scripts/isaac/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Isaac Sim / Isaac Lab examples for ur5_ws

Minimal scripts that load a Universal Robots UR5 or UR5e in Isaac Sim and Isaac Lab.

| Script | Runtime | Purpose |
| --- | --- | --- |
| `spawn_ur.py` | Isaac Sim standalone | Load UR5/UR5e USD, run physics, render. Good first sanity check. |
| `ur_isaaclab_minimal.py` | Isaac Lab | Load UR5/UR5e as an `Articulation` with `InteractiveScene`. Scaffold for RL / Reach / Pick. |

## How to run

Inside the `ur5_ws` container (Isaac Sim and Isaac Lab installed via the build args in `docker/compose.yaml`):

```sh
# Convenience wrappers in ur5_ws/scripts/
./scripts/isaac_sim_ur5.sh
./scripts/isaac_sim_ur5e.sh

# Or directly:
${ISAACSIM_PYTHON_EXE} scripts/isaac/spawn_ur.py --ur_type ur5e
~/IsaacLab/isaaclab.sh -p scripts/isaac/ur_isaaclab_minimal.py --ur_type ur5
```

Pass `--headless` to the Isaac Sim script for offscreen rendering (e.g., on a cluster without a desktop).

## Extending to RL / manipulation tasks

`ur_isaaclab_minimal.py` is intentionally bare. To turn it into a full Reach or Pick task, copy the UR10 manager-based config in Isaac Lab — typically at:

```
$ISAACLAB_PATH/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/ur_10/
```
Comment on lines +30 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced code block.

The fence starting on Line 30 has no language, which triggers markdownlint MD040.

Suggested fix
-```
+```text
 $ISAACLAB_PATH/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/ur_10/
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 30-30: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ur5_ws/scripts/isaac/README.md` around lines 30 - 32, The fenced code block
containing the path string
"$ISAACLAB_PATH/source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/ur_10/"
is missing a language tag; update the fence from "```" to include a language
identifier (e.g., "```text" or "```bash") so the block becomes "```text"
followed by the path and a closing "```" to satisfy markdownlint MD040.


Swap the `usd_path` and joint init values for UR5/UR5e (joint names are identical across the UR family, so the joint-name lists in the config don't need to change).
54 changes: 54 additions & 0 deletions ur5_ws/scripts/isaac/spawn_ur.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Spawn a UR5 or UR5e in Isaac Sim and step the physics loop.

Usage (inside the ur5_ws container):
${ISAACSIM_PYTHON_EXE} ur5_ws/scripts/isaac/spawn_ur.py --ur_type ur5
${ISAACSIM_PYTHON_EXE} ur5_ws/scripts/isaac/spawn_ur.py --ur_type ur5e --headless

The convenience wrappers ur5_ws/scripts/isaac_sim_ur5.sh and isaac_sim_ur5e.sh
just call this script with the appropriate --ur_type.
"""

import argparse

parser = argparse.ArgumentParser(description="Spawn a UR robot in Isaac Sim")
parser.add_argument("--ur_type", choices=["ur5", "ur5e"], default="ur5")
parser.add_argument("--headless", action="store_true")
parser.add_argument(
"--steps",
type=int,
default=0,
help="number of physics steps to run; 0 = until the window is closed",
)
args = parser.parse_args()

from isaacsim import SimulationApp

simulation_app = SimulationApp({"headless": args.headless})

from isaacsim.core.api import World
from isaacsim.core.utils.stage import add_reference_to_stage
from isaacsim.storage.native import get_assets_root_path

assets_root = get_assets_root_path()
if assets_root is None:
raise RuntimeError(
"Could not resolve Isaac Sim assets root. "
"Check Nucleus connectivity or set ISAAC_NUCLEUS_DIR."
)

usd_path = f"{assets_root}/Isaac/Robots/UniversalRobots/{args.ur_type}/{args.ur_type}.usd"
prim_path = f"/World/{args.ur_type.upper()}"

world = World(stage_units_in_meters=1.0)
world.scene.add_default_ground_plane()
add_reference_to_stage(usd_path=usd_path, prim_path=prim_path)
world.reset()

step = 0
while simulation_app.is_running():
world.step(render=True)
step += 1
if args.steps > 0 and step >= args.steps:
break

simulation_app.close()
104 changes: 104 additions & 0 deletions ur5_ws/scripts/isaac/ur_isaaclab_minimal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Minimal Isaac Lab scene with a UR5 or UR5e articulation.

Usage (inside the ur5_ws container):
~/IsaacLab/isaaclab.sh -p ur5_ws/scripts/isaac/ur_isaaclab_minimal.py --ur_type ur5e

This is a scaffold: it loads the chosen UR USD into an Isaac Lab scene,
applies small random joint targets, and steps the simulator. Use it as a
starting point for Reach / Pick / RL tasks by swapping the USD path and
joint names into Isaac Lab's manager-based task templates under
isaaclab_tasks.manager_based.manipulation.
"""

import argparse

from isaaclab.app import AppLauncher

parser = argparse.ArgumentParser(description="Minimal UR articulation in Isaac Lab")
parser.add_argument("--ur_type", choices=["ur5", "ur5e"], default="ur5")
parser.add_argument("--num_envs", type=int, default=1)
parser.add_argument(
"--steps",
type=int,
default=0,
help="number of sim steps to run; 0 = until the window is closed or Ctrl-C",
)
AppLauncher.add_app_launcher_args(parser)
args = parser.parse_args()

app_launcher = AppLauncher(args)
simulation_app = app_launcher.app

import torch

import isaaclab.sim as sim_utils
from isaaclab.assets import Articulation, ArticulationCfg, AssetBaseCfg
from isaaclab.actuators import ImplicitActuatorCfg
from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
from isaaclab.utils import configclass
from isaacsim.storage.native import get_assets_root_path

assets_root = get_assets_root_path()
if assets_root is None:
raise RuntimeError("Could not resolve Isaac Sim assets root.")

usd_path = f"{assets_root}/Isaac/Robots/UniversalRobots/{args.ur_type}/{args.ur_type}.usd"

UR_CFG = ArticulationCfg(
prim_path="{ENV_REGEX_NS}/Robot",
spawn=sim_utils.UsdFileCfg(usd_path=usd_path),
init_state=ArticulationCfg.InitialStateCfg(
joint_pos={
"shoulder_pan_joint": 0.0,
"shoulder_lift_joint": -1.57,
"elbow_joint": 1.57,
"wrist_1_joint": -1.57,
"wrist_2_joint": -1.57,
"wrist_3_joint": 0.0,
},
),
actuators={
"arm": ImplicitActuatorCfg(
joint_names_expr=[".*"], stiffness=400.0, damping=40.0
),
},
)


@configclass
class URSceneCfg(InteractiveSceneCfg):
ground = AssetBaseCfg(
prim_path="/World/defaultGroundPlane",
spawn=sim_utils.GroundPlaneCfg(),
)
light = AssetBaseCfg(
prim_path="/World/Light",
spawn=sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)),
)
robot: ArticulationCfg = UR_CFG


sim_cfg = sim_utils.SimulationCfg(dt=1.0 / 120.0)
sim = sim_utils.SimulationContext(sim_cfg)
sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0])

scene = InteractiveScene(URSceneCfg(num_envs=args.num_envs, env_spacing=2.0))
sim.reset()

robot: Articulation = scene["robot"]
default_joint_pos = robot.data.default_joint_pos.clone()

step = 0
while simulation_app.is_running():
target = default_joint_pos + 0.2 * torch.sin(
torch.tensor(step * 0.01, device=sim.device)
)
robot.set_joint_position_target(target)
scene.write_data_to_sim()
sim.step()
scene.update(sim.get_physics_dt())
step += 1
if args.steps > 0 and step >= args.steps:
break

simulation_app.close()
5 changes: 5 additions & 0 deletions ur5_ws/scripts/isaac_lab_ur_minimal.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)"

"${ISAACLAB_PATH}/isaaclab.sh" -p "${SCRIPT_DIR}/isaac/ur_isaaclab_minimal.py" "$@"
5 changes: 5 additions & 0 deletions ur5_ws/scripts/isaac_sim_ur5.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)"

"${ISAACSIM_PYTHON_EXE}" "${SCRIPT_DIR}/isaac/spawn_ur.py" --ur_type ur5 "$@"
5 changes: 5 additions & 0 deletions ur5_ws/scripts/isaac_sim_ur5e.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)"

"${ISAACSIM_PYTHON_EXE}" "${SCRIPT_DIR}/isaac/spawn_ur.py" --ur_type ur5e "$@"
7 changes: 7 additions & 0 deletions ur5_ws/scripts/ur5e_bringup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash -e

ros2 launch ur_robot_driver ur_control.launch.py \
ur_type:=ur5e \
use_rg2_gripper:=true \
robot_ip:=192.168.56.101 \
launch_rviz:=true
12 changes: 12 additions & 0 deletions ur5_ws/scripts/ur5e_calibration.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash -e

# Get the directory of this script.
# Reference: https://stackoverflow.com/q/59895
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)"
UR5_WORKSPACE_DIR="${SCRIPT_DIR}/.."

# Reference:
# - https://docs.universal-robots.com/Universal_Robots_ROS2_Documentation/doc/ur_robot_driver/ur_calibration/doc/usage.html
ros2 launch ur_calibration calibration_correction.launch.py \
robot_ip:=192.168.56.101 \
target_filename:="${UR5_WORKSPACE_DIR}/src/Universal_Robots_ROS2_Description/config/ur5e/default_kinematics.yaml"