Skip to content
Merged
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
113 changes: 113 additions & 0 deletions my_dual_robot_cell/doc/assemble_urdf.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
:github_url: https://github.com/UniversalRobots/Universal_Robots_ROS2_Tutorials/blob/main/my_dual_robot_cell/doc/assemble_urdf.rst

Assembling the URDF
===================

The `ur_description <https://github.com/UniversalRobots/Universal_Robots_ROS2_Description>`_ package provides `macro files <https://github.com/UniversalRobots/Universal_Robots_ROS2_Description/blob/rolling/urdf/ur_macro.xacro>`_ to generate an instance of a Universal Robots arm.
We'll use this to create a custom workcell with two robots in it, both a UR3e (called Alice) and a UR5e (called Bob). In this section we will only go into
detail about the URDF / xacro files, not the complete package structure. Please see the
`description package source code
<https://github.com/UniversalRobots/Universal_Robots_ROS2_Tutorials/blob/main/my_dual_robot_cell/my_dual_robot_cell_description>`_ for all other files assembling the description package.

Workcell description
--------------------

.. literalinclude:: ../my_dual_robot_cell_description/urdf/my_dual_robot_cell.urdf.xacro
:language: xml
:linenos:
:caption: my_robot_cell_description/urdf/my_robot_cell.urdf.xacro

Let's break it down:

First, we'll have to **include** the macro to generate our custom workcell:

.. literalinclude:: ../my_dual_robot_cell_description/urdf/my_dual_robot_cell.urdf.xacro
:language: xml
:start-at: <xacro:include filename="$(find my_dual_robot_cell_description)/urdf/my_dual_robot_cell_macro.xacro"/>
:end-at: <xacro:include filename="$(find my_dual_robot_cell_description)/urdf/my_dual_robot_cell_macro.xacro"/>
:caption: my_dual_robot_cell_description/urdf/my_dual_robot_cell.urdf.xacro

This line only loads the macro for generating the robot workcell.

We will need to provide some parameters to our workcell in order to parametrize the arms. Therefore,
we need to declare certain arguments that must be passed to the macro.

.. literalinclude:: ../my_dual_robot_cell_description/urdf/my_dual_robot_cell.urdf.xacro
:language: xml
:start-at: <xacro:arg name="alice_ur_type" default="ur3e"/>
:end-at: <xacro:arg name="bob_visual_parameters_file" default="$(find ur_description)/config/$(arg bob_ur_type)/visual_parameters.yaml"/>
:caption: my_dual_robot_cell_description/urdf/my_dual_robot_cell.urdf.xacro

The workspace macro contains all items within the workcell including the robot arm. If you are not
experienced in writing URDFs, you may want to refer to this `tutorial
<https://docs.ros.org/en/rolling/Tutorials/Intermediate/URDF/URDF-Main.html>`_. The macro's content
is generated using

.. literalinclude:: ../my_dual_robot_cell_description/urdf/my_dual_robot_cell.urdf.xacro
:language: xml
:start-at: <link name="world"/>
:end-at: </xacro:my_dual_robot_cell>
:caption: my_dual_robot_cell_description/urdf/my_dual_robot_cell.urdf.xacro

Here, a ``world`` link is created, and the robot workcell is created relative to the ``world`` link.

Workcell macro
--------------

The workcell macro is defined in the following manner:

.. literalinclude:: ../my_dual_robot_cell_description/urdf/my_dual_robot_cell_macro.xacro
:language: xml
:linenos:
:caption: my_dual_robot_cell_description/urdf/my_dual_robot_cell_macro.xacro

This macro provides an example of what a custom workcell could resemble. Your workspace will likely
vary from this one. Please feel free to modify this portion of the URDF to match your own setup. In
this instance, our workspace comprises a table in front of a wall, featuring a monitor, and the
two robot arms mounted on top.

Ensure that your custom workcell includes the parent link, which must be passed to the **ur_robot**
macro. In this example, we chose to create two links, one named **alice_robot_mount** and one named **bob_robot_mount**.

.. literalinclude:: ../my_dual_robot_cell_description/urdf/my_dual_robot_cell_macro.xacro
:language: xml
:start-at: <link name="alice_robot_mount"/>
:end-before: <!--Creates the 1st Robot-->
:linenos:
:caption: my_dual_robot_cell_description/urdf/my_dual_robot_cell_macro.xacro

After that we are finally able to actually **create the robot arms** by calling the macro.

.. literalinclude:: ../my_dual_robot_cell_description/urdf/my_dual_robot_cell_macro.xacro
:language: xml
:start-at: <!--Creates the 1st Robot-->
:end-before: </xacro:macro>
:linenos:
:caption: my_dual_robot_cell_description/urdf/my_dual_robot_cell_macro.xacro

Note that the **origin** argument is transmitted in a different manner than the other arguments.

Before we can test our code, it's essential to build and source our Colcon workspace:

.. code-block:: bash

#cd to your colcon workspace root
cd ~/colcon_ws

#source and build your workspace
colcon build
source install/setup.bash


We can view our custom workspace by running:

.. code-block:: bash

#launch rviz
ros2 launch my_dual_robot_cell_description view_robot.launch.py

Use the sliders of the ``joint_state_puplisher_gui`` to move the virtual robots around.
It should look something like this:

.. image:: figures/rviz.png
:alt: RViz window showing the custom workspace
82 changes: 82 additions & 0 deletions my_dual_robot_cell/doc/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
project = "ur_dual_arm_tutorial"
copyright = "2025, Universal Robots A/S"
author = "Jacob Larsen"

# The short X.Y version
version = ""
# The full version, including alpha/beta/rc tags
release = ""

# -- General configuration ---------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
source_suffix = [".rst"]

# The master toctree document.
master_doc = "index"

numfig = True

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "migration/*.rst"]

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None


# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "alabaster"

# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ["_static"]

# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}


# -- Options for HTMLHelp output ---------------------------------------------

# Output file base name for HTML help builder.
htmlhelp_basename = "ur_dual_arm_tutorial_doc"
Binary file added my_dual_robot_cell/doc/figures/rviz.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions my_dual_robot_cell/doc/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
:github_url: https://github.com/UniversalRobots/Universal_Robots_ROS2_Tutorials/blob/main/my_dual_robot_cell/doc/index.rst

.. _dual_arm_tutorial:

===================
Dual robot arm cell
===================

Example about integrating two UR arms into a custom workspace. We will build a custom workcell
description containing two robot arms, start the driver and create a MoveIt config to enable trajectory planning with MoveIt.

Please see the `package source code
<https://github.com/UniversalRobots/Universal_Robots_ROS2_Tutorials/tree/main/my_robot_cell>`_ for
all files relevant for this example.

.. toctree::
:maxdepth: 4
:caption: Contents:

assemble_urdf
start_ur_driver

MoveIt! Configuration
---------------------
For inspiration on how to configure MoveIt! 2 using the setup assistant, please see the `single arm robot cell tutorial
<https://docs.universal-robots.com/Universal_Robots_ROS_Documentation/doc/ur_tutorials/my_robot_cell/doc/build_moveit_config.html>`_
135 changes: 135 additions & 0 deletions my_dual_robot_cell/doc/start_ur_driver.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
:github_url: https://github.com/UniversalRobots/Universal_Robots_ROS2_Tutorials/blob/main/my_dual_robot_cell/doc/start_ur_driver.rst

=========================
Start the ur_robot_driver
=========================

In the last chapter we created a custom scene description containing our two robots. In order to make
that description usable to ``ros2_control`` and hence the ``ur_robot_driver``, we need to add
control information. We will also add a custom launchfile to start up our demonstrator.

We will generate a new package called `my_dual_robot_cell_control <https://github.com/UniversalRobots/Universal_Robots_ROS2_Tutorials/tree/main/my_dual_robot_cell/my_dual_robot_cell_control>`_ for that purpose.

Create a description with ros2_control tag
------------------------------------------

The first step is to create a description containing the control instructions:

.. literalinclude:: ../my_dual_robot_cell_control/urdf/my_dual_robot_cell_controlled.urdf.xacro
:language: xml
:linenos:
:caption: my_dual_robot_cell_control/urdf/my_dual_robot_cell_controlled.urdf.xacro

This URDF is very similar to the one we have already assembled. We simply need to include the ros2_control macro,

.. literalinclude:: ../my_dual_robot_cell_control/urdf/my_dual_robot_cell_controlled.urdf.xacro
:language: xml
:start-at: <xacro:include filename="$(find ur_robot_driver)/urdf/ur.ros2_control.xacro" />
:end-at: <xacro:include filename="$(find ur_robot_driver)/urdf/ur.ros2_control.xacro" />
:caption: my_dual_robot_cell_control/urdf/my_dual_robot_cell_controlled.urdf.xacro

define the necessary arguments that need to be passed to the macro,

.. literalinclude:: ../my_dual_robot_cell_control/urdf/my_dual_robot_cell_controlled.urdf.xacro
:language: xml
:start-at: <xacro:arg name="alice_use_mock_hardware" default="false" />
:end-at: <xacro:arg name="ur_input_recipe_filename" default="$(find ur_robot_driver)/resources/rtde_input_recipe.txt" />
:caption: my_dual_robot_cell_control/urdf/my_dual_robot_cell_controlled.urdf.xacro

and then call the macro by providing all the specified arguments.

.. literalinclude:: ../my_dual_robot_cell_control/urdf/my_dual_robot_cell_controlled.urdf.xacro
:language: xml
:start-at: <xacro:ur_ros2_control
:end-before: </robot>
:caption: my_robot_cell_control/urdf/my_robot_cell_controlled.urdf.xacro

Extract the calibration
-----------------------

One very important step is to extract each robot's specific calibration and save it to our
workcell's startup package. For details, please refer to `our documentation on extracting the calibration information <https://docs.ros.org/en/ros2_packages/rolling/api/ur_robot_driver/installation/robot_setup.html#extract-calibration-information>`_.
For now, we just copy the default ones for the UR3e and UR5e.

.. code-block::

cp $(ros2 pkg prefix ur_description)/share/ur_description/config/ur3e/default_kinematics.yaml \
my_dual_robot_cell_control/config/alice_calibration.yaml

cp $(ros2 pkg prefix ur_description)/share/ur_description/config/ur5e/default_kinematics.yaml \
my_dual_robot_cell_control/config/bob_calibration.yaml


Create robot_state_publisher launchfile
---------------------------------------

To use the custom controlled description, we need to generate a launchfile loading that description
(Since it contains less / potentially different) arguments than the "default" one. In that
launchfile we need to start a ``robot_state_publisher`` (RSP) node that will get the description as a
parameter and redistribute it via the ``robot_description`` topic:

.. literalinclude:: ../my_dual_robot_cell_control/launch/rsp.launch.py
:language: py
:start-at: from launch import LaunchDescription
:linenos:
:caption: my_dual_robot_cell_control/launch/rsp.launch.py

Before we can start our workcell we need to create a launchfile that will launch the two robot descriptions correctly.

Create start_robot launchfile
-----------------------------

Here we create a launch file which will launch the driver with the correct description and robot state publisher, as well as start all of the controllers necessary to use the two robots.

.. literalinclude:: ../my_dual_robot_cell_control/launch/start_robots.launch.py
:language: py
:linenos:
:caption: my_dual_robot_cell_control/launch/start_robots.launch.py

With that we can start the robot using

.. code-block:: bash

ros2 launch my_dual_robot_cell_control start_robots.launch.py alice_use_mock_hardware:=true bob_use_mock_hardware:=true

Testing everything
------------------

Before we can test our code, it's essential to build and source our Colcon workspace:

.. code-block:: bash

#cd to your colcon workspace root, e.g.
cd ~/colcon_ws

#source and build your workspace
colcon build
source install/setup.bash

We can start the system in a mocked simulation

.. code-block:: bash

#start the driver with mocked hardware
ros2 launch my_dual_robot_cell_control start_robots.launch.py alice_use_mock_hardware:=true bob_use_mock_hardware:=true

Or to use it with a real robot:

.. code-block:: bash

#start the driver with real hardware (or the docker simulator included in this example)
ros2 launch my_dual_robot_cell_control start_robots.launch.py

.. note::
We extracted the calibration information from the robot and saved it in the
``my_robot_cell_control`` package. If you have a different robot, you need to extract the
calibration information from that, and pass that to the launch file. Changing the ``ur_type``
parameter only will lead to a wrong model, as it will still be using the example kinematics from
a UR3e and UR5e. To use a UR10e and UR20, for example, you can do

.. code-block:: bash

ros2 launch my_dual_robot_cell_control start_robots.launch.py bob_ur_type:=ur10e bob_use_mock_hardware:=true \
bob_kinematics_parameters_file:=$(ros2 pkg prefix ur_description)/share/ur_description/config/ur10e/default_kinematics.yaml \
alice_ur_type:=ur20 alice_use_mock_hardware:=true \
alice_kinematics_parameters_file:=$(ros2 pkg prefix ur_description)/share/ur_description/config/ur20/default_kinematics.yaml
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def generate_launch_description():
"ur20",
"ur30",
],
default_value="ur3e",
default_value="ur5e",
)
)
declared_arguments.append(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ def generate_launch_description():
description_file,
" ",
"alice_ur_type:=",
"ur3",
"ur3e",
" ",
"bob_ur_type:=",
"ur3",
"ur5e",
]
),
value_type=str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
<joint name="base_to_alice_robot_mount" type="fixed">
<parent link="table"/>
<child link="alice_robot_mount"/>
<origin xyz="0.5 0.3 0" rpy="0 0 ${pi}" />
<origin xyz="0.2 0.3 0" rpy="0 0 ${pi}" />
</joint>

<link name="bob_robot_mount"/>
Expand Down
Loading