Start the ur_robot_driver

In the last chapter we created a custom scene description containing our robot. 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 startup our demonstrator.

We will generate a new package called my_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:

Listing 9 my_robot_cell_control/urdf/my_robot_cell_controlled.urdf.xacro
 1<?xml version="1.0"?>
 2<robot name="my_robot_cell" xmlns:xacro="http://ros.org/wiki/xacro">
 3
 4  <!--First, include the macro from our custom description-->
 5  <xacro:include filename="$(find my_robot_cell_description)/urdf/my_robot_cell_macro.xacro"/>
 6  <!--The driver already provides a control tag for the UR arm, we can directly include-->
 7  <xacro:include filename="$(find ur_robot_driver)/urdf/ur.ros2_control.xacro"/>
 8
 9  <!--We need to specify the arguments used for parametrizing our description-->
10  <xacro:arg name="ur_type" default="ur20"/>
11  <xacro:arg name="joint_limit_parameters_file" default="$(find ur_description)/config/$(arg ur_type)/joint_limits.yaml"/>
12  <xacro:arg name="kinematics_parameters_file" default="$(find ur_description)/config/$(arg ur_type)/default_kinematics.yaml"/>
13  <xacro:arg name="physical_parameters_file" default="$(find ur_description)/config/$(arg ur_type)/physical_parameters.yaml"/>
14  <xacro:arg name="visual_parameters_file" default="$(find ur_description)/config/$(arg ur_type)/visual_parameters.yaml"/>
15
16  <xacro:arg name="robot_ip" default="0.0.0.0"/>
17  <xacro:arg name="headless_mode" default="false" />
18  <xacro:arg name="ur_script_filename" default="$(find ur_client_library)/resources/external_control.urscript"/>
19  <xacro:arg name="ur_output_recipe_filename" default="$(find ur_robot_driver)/resources/rtde_output_recipe.txt"/>
20  <xacro:arg name="ur_input_recipe_filename" default="$(find ur_robot_driver)/resources/rtde_input_recipe.txt"/>
21  <xacro:arg name="use_mock_hardware" default="false" />
22  <xacro:arg name="mock_sensor_commands" default="false" />
23
24  <link name="world" />
25
26  <!--Create the scene description including the robot-->
27  <xacro:my_robot_cell
28    parent="world"
29    ur_type="$(arg ur_type)"
30    joint_limits_parameters_file="$(arg joint_limit_parameters_file)"
31    kinematics_parameters_file="$(arg kinematics_parameters_file)"
32    physical_parameters_file="$(arg physical_parameters_file)"
33    visual_parameters_file="$(arg visual_parameters_file)"
34  >
35    <origin xyz="0 0 1" rpy="0 0 0" />
36  </xacro:my_robot_cell>
37
38  <!--Create the control tag for the UR robot-->
39  <xacro:ur_ros2_control
40    name="$(arg ur_type)"
41    tf_prefix="$(arg ur_type)_"
42    kinematics_parameters_file="$(arg kinematics_parameters_file)"
43    robot_ip="$(arg robot_ip)"
44    script_filename="$(arg ur_script_filename)"
45    output_recipe_filename="$(arg ur_output_recipe_filename)"
46    input_recipe_filename="$(arg ur_input_recipe_filename)"
47    use_mock_hardware="$(arg use_mock_hardware)"
48    mock_sensor_commands="$(arg mock_sensor_commands)"
49    headless_mode="$(arg headless_mode)"
50  />
51
52  <!--If you had more controlled devices in your description, e.g. a gripper, you can add another ros2_control tag here-->
53
54</robot>

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

Listing 10 my_robot_cell_control/urdf/my_robot_cell_controlled.urdf.xacro
  <xacro:include filename="$(find ur_robot_driver)/urdf/ur.ros2_control.xacro"/>

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

Listing 11 my_robot_cell_control/urdf/my_robot_cell_controlled.urdf.xacro
  <xacro:arg name="robot_ip" default="0.0.0.0"/>
  <xacro:arg name="headless_mode" default="false" />
  <xacro:arg name="ur_script_filename" default="$(find ur_client_library)/resources/external_control.urscript"/>
  <xacro:arg name="ur_output_recipe_filename" default="$(find ur_robot_driver)/resources/rtde_output_recipe.txt"/>
  <xacro:arg name="ur_input_recipe_filename" default="$(find ur_robot_driver)/resources/rtde_input_recipe.txt"/>
  <xacro:arg name="use_mock_hardware" default="false" />
  <xacro:arg name="mock_sensor_commands" default="false" />

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

Listing 12 my_robot_cell_control/urdf/my_robot_cell_controlled.urdf.xacro
  <xacro:ur_ros2_control
    name="$(arg ur_type)"
    tf_prefix="$(arg ur_type)_"
    kinematics_parameters_file="$(arg kinematics_parameters_file)"
    robot_ip="$(arg robot_ip)"
    script_filename="$(arg ur_script_filename)"
    output_recipe_filename="$(arg ur_output_recipe_filename)"
    input_recipe_filename="$(arg ur_input_recipe_filename)"
    use_mock_hardware="$(arg use_mock_hardware)"
    mock_sensor_commands="$(arg mock_sensor_commands)"
    headless_mode="$(arg headless_mode)"
  />

Extract the calibration

One very important step is to extract the 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. For now, we just copy the default one for the ur20.

cp $(ros2 pkg prefix ur_description)/share/ur_description/config/ur20/default_kinematics.yaml \
  my_robot_cell_control/config/my_robot_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:

Listing 13 my_robot_cell_control/launch/rsp.launch.py
  1
  2from launch_ros.actions import Node
  3from launch_ros.substitutions import FindPackageShare
  4
  5from launch import LaunchDescription
  6from launch.actions import DeclareLaunchArgument
  7from launch.substitutions import (
  8    Command,
  9    FindExecutable,
 10    LaunchConfiguration,
 11    PathJoinSubstitution,
 12)
 13
 14
 15def generate_launch_description():
 16    ur_type = LaunchConfiguration("ur_type")
 17    robot_ip = LaunchConfiguration("robot_ip")
 18
 19    use_mock_hardware = LaunchConfiguration("use_mock_hardware")
 20    mock_sensor_commands = LaunchConfiguration("mock_sensor_commands")
 21
 22    headless_mode = LaunchConfiguration("headless_mode")
 23
 24    kinematics_parameters_file = LaunchConfiguration("kinematics_parameters_file")
 25
 26    # Load description with necessary parameters
 27    robot_description_content = Command(
 28        [
 29            PathJoinSubstitution([FindExecutable(name="xacro")]),
 30            " ",
 31            PathJoinSubstitution(
 32                [
 33                    FindPackageShare("my_robot_cell_control"),
 34                    "urdf",
 35                    "my_robot_cell_controlled.urdf.xacro",
 36                ]
 37            ),
 38            " ",
 39            "robot_ip:=",
 40            robot_ip,
 41            " ",
 42            "ur_type:=",
 43            ur_type,
 44            " ",
 45            "kinematics_parameters_file:=",
 46            kinematics_parameters_file,
 47            " ",
 48            "use_mock_hardware:=",
 49            use_mock_hardware,
 50            " ",
 51            "mock_sensor_commands:=",
 52            mock_sensor_commands,
 53            " ",
 54            "headless_mode:=",
 55            headless_mode,
 56        ]
 57    )
 58    robot_description = {"robot_description": robot_description_content}
 59
 60    declared_arguments = []
 61    # UR specific arguments
 62    declared_arguments.append(
 63        DeclareLaunchArgument(
 64            "ur_type",
 65            description="Typo/series of used UR robot.",
 66            choices=[
 67                "ur3",
 68                "ur3e",
 69                "ur5",
 70                "ur5e",
 71                "ur10",
 72                "ur10e",
 73                "ur16e",
 74                "ur20",
 75                "ur30",
 76            ],
 77            default_value="ur20",
 78        )
 79    )
 80    declared_arguments.append(
 81        DeclareLaunchArgument(
 82            "robot_ip", description="IP address by which the robot can be reached."
 83        )
 84    )
 85    declared_arguments.append(
 86        DeclareLaunchArgument(
 87            "kinematics_parameters_file",
 88            default_value=PathJoinSubstitution(
 89                [
 90                    FindPackageShare("my_robot_cell_control"),
 91                    "config",
 92                    "my_robot_calibration.yaml",
 93                ]
 94            ),
 95            description="The calibration configuration of the actual robot used.",
 96        )
 97    )
 98    declared_arguments.append(
 99        DeclareLaunchArgument(
100            "use_mock_hardware",
101            default_value="false",
102            description="Start robot with mock hardware mirroring command to its states.",
103        )
104    )
105    declared_arguments.append(
106        DeclareLaunchArgument(
107            "mock_sensor_commands",
108            default_value="false",
109            description="Enable mock command interfaces for sensors used for simple simulations. "
110            "Used only if 'use_mock_hardware' parameter is true.",
111        )
112    )
113    declared_arguments.append(
114        DeclareLaunchArgument(
115            "headless_mode",
116            default_value="false",
117            description="Enable headless mode for robot control",
118        )
119    )
120
121    return LaunchDescription(
122        declared_arguments
123        + [
124            Node(
125                package="robot_state_publisher",
126                executable="robot_state_publisher",
127                output="both",
128                parameters=[robot_description],
129            ),
130        ]
131    )

With this we could start our workcell using

ros2 launch ur_robot_driver ur_control.launch.py \
  description_launchfile:=$(ros2 pkg prefix my_robot_cell_control)/share/my_robot_cell_control/launch/rsp.launch.py \
  use_mock_hardware:=true \
  robot_ip:=123 \
  ur_type:=ur20 \
  rviz_config_file:=$(ros2 pkg prefix my_robot_cell_description)/share/my_robot_cell_description/rviz/urdf.rviz \
  tf_prefix:=ur20_

Create start_robot launchfile

Since the command above is obviously not very convenient to start our robot, we wrap that into another launchfile that includes the ur_control.launch.py launchfile with the correct description launchfile and prefix:

Listing 14 my_robot_cell_control/launch/start_robot.launch.py
 1from launch_ros.actions import Node
 2from launch_ros.parameter_descriptions import ParameterFile
 3from launch_ros.substitutions import FindPackageShare
 4
 5from launch import LaunchDescription
 6from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription
 7from launch.launch_description_sources import PythonLaunchDescriptionSource
 8from launch.conditions import IfCondition
 9from launch.substitutions import (
10    Command,
11    FindExecutable,
12    LaunchConfiguration,
13    PathJoinSubstitution,
14    TextSubstitution,
15)
16
17
18def generate_launch_description():
19    ur_type = LaunchConfiguration("ur_type")
20    robot_ip = LaunchConfiguration("robot_ip")
21    declared_arguments = []
22    declared_arguments.append(
23        DeclareLaunchArgument(
24            "ur_type",
25            description="Type/series of used UR robot.",
26            choices=[
27                "ur3",
28                "ur3e",
29                "ur5",
30                "ur5e",
31                "ur10",
32                "ur10e",
33                "ur16e",
34                "ur20",
35                "ur30",
36            ],
37            default_value="ur20",
38        )
39    )
40    declared_arguments.append(
41        DeclareLaunchArgument(
42            "robot_ip",
43            default_value="192.168.56.101",  # put your robot's IP address here
44            description="IP address by which the robot can be reached.",
45        )
46    )
47    declared_arguments.append(
48        DeclareLaunchArgument("launch_rviz", default_value="true", description="Launch RViz?")
49    )
50
51    return LaunchDescription(
52        declared_arguments
53        + [
54            IncludeLaunchDescription(
55                PythonLaunchDescriptionSource(
56                    [
57                        PathJoinSubstitution(
58                            [
59                                FindPackageShare("ur_robot_driver"),
60                                "launch",
61                                "ur_control.launch.py",
62                            ]
63                        )
64                    ]
65                ),
66                launch_arguments={
67                    "ur_type": ur_type,
68                    "robot_ip": robot_ip,
69                    "tf_prefix": [LaunchConfiguration("ur_type"), "_"],
70                    "rviz_config_file": PathJoinSubstitution(
71                        [
72                            FindPackageShare("my_robot_cell_description"),
73                            "rviz",
74                            "urdf.rviz",
75                        ]
76                    ),
77                    "description_launchfile": PathJoinSubstitution(
78                        [
79                            FindPackageShare("my_robot_cell_control"),
80                            "launch",
81                            "rsp.launch.py",
82                        ]
83                    ),
84                }.items(),
85            ),
86        ]
87    )

With that we can start the robot using

ros2 launch my_robot_cell_control start_robot.launch.py use_mock_hardware:=true

Testing everything

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

#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

#start the driver with mocked hardware
ros2 launch my_robot_cell_control start_robot.launch.py use_mock_hardware:=true

Or to use it with a real robot:

#start the driver with real hardware
ros2 launch my_robot_cell_control start_robot.launch.py