Eng_Lab 02 - ROS2 Actions

Lab. 02 - ROS2 Actions

ROS2 Actions visualisation graph

1. What are ROS2 Actions?

Actions are one of the communication types in ROS2, meant for tasks that take longer. They consist of three parts: a goal, feedback and a result.

Actions are built on topics and services. They work similarly to services, with two differences:

Goal - used to call the action. It works like a service: a call and a response saying whether the action was started.

Feedback - data published by the action server while it runs. It usually reports the current progress, e.g. the distance left to travel, the estimated remaining time or the percentage of the work done.

Result - the final response of the action server about the outcome. It usually contains a binary answer saying whether the requested task succeeded, or how accurately the action was carried out.

2. Environment setup

Before you start, prepare the environment: Environment setup instructions. The image is based on osrf/ros:lyrical-desktop-full (ROS 2 Lyrical Luth).

By default the container is named ARM_02.

NOTE! When run, the scripts remove a container with this name before creating a new one.

Using the container

After every computer restart, remember to run:

xhost +local:root

A new terminal can be attached to the container with:

docker exec -it ARM_02 bash

ROS_DOMAIN_ID

When several computers work in the same local network (eduroam may require this too), you may need to set the ROS_DOMAIN_ID environment variable to different values. Choose any integer from 0 to 100. Set this value in the ~/.bashrc file:

export ROS_DOMAIN_ID=10  # example value

or directly in the terminal (it then applies only to that terminal):

export ROS_DOMAIN_ID=10  # example value

It is suggested to use the last two digits of the computer’s IP address as the ROS_DOMAIN_ID value, to avoid conflicts with other users in the network. To check your IP address, you can use:

ip addr show

The IP address is shown under the network interface, e.g. wlan0 or eth0.

It may be convenient to add this export to bashrc:

echo 'export ROS_DOMAIN_ID=<chosen_value>' >> ~/.bashrc 

Gazebo: GZ_IP and GZ_PARTITION

Gazebo communicates independently of ROS 2 (through the gz-transport library), so ROS_DOMAIN_ID does not separate simulations. By default, Gazebo connects simulations started by the same user on computers with the same name (hostname), and a container started with --network=host takes the computer’s name and runs as root. If computers in the network have the same names, simulations of different people can interfere with each other.

That is why the image sets GZ_IP=127.0.0.1 in the ~/.bashrc file, so Gazebo communicates only within one computer. In containers created from an older version of the image (the echo $GZ_IP command prints nothing), add it yourself:

echo 'export GZ_IP=127.0.0.1' >> ~/.bashrc

If several simulations run on one computer (e.g. in several containers), you can also separate them by setting a different GZ_PARTITION value in each container (Gazebo nodes only see each other within the same partition):

echo 'export GZ_PARTITION=<chosen_name>' >> ~/.bashrc  # e.g. arm_10

The GZ_PARTITION value must be the same in all terminals where you use Gazebo (starting the simulation, gz commands), so it is better to set it in ~/.bashrc than in a single terminal. Changes in ~/.bashrc apply to newly opened terminals, so restart the simulation after making them.

3. Goal of the class

The final task of this class is to create an action that moves the robot to a given distance from the obstacle in front of it, also passing the time allowed for the task and the minimum required accuracy. The feedback will publish the current distance to the obstacle and the estimated time left until the action finishes. As the result, the server will report whether the requirements were met, the final accuracy and distance to the goal, and how long the procedure took.

4. Creating an action

Before working on the task above, it is worth learning how to define actions on a simpler example - computing the Fibonacci sequence.

Creating a new package

To create a new package, run the following command from /arm_ws/src:

ros2 pkg create --build-type ament_cmake fibonacci_sequence_action

You also need to create an action directory and define the action structure in it:

cd /arm_ws/src/fibonacci_sequence_action
mkdir action && cd action
touch Fibonacci.action

Edit the Fibonacci.action file (e.g. with nano, gedit or VS code):

int32 order
---
int32[] sequence
---
int32[] partial_sequence

where order is the goal of the action, sequence is the result and partial_sequence is the feedback.

With the action structure defined, we can add it to rosidl, the ROS2 pipeline that generates the interface code. To do this, modify the CMakeLists.txt file (before the ament_package() line) so that it contains:

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "action/Fibonacci.action"
)

Also add the required dependencies to the package.xml file:

<buildtool_depend>rosidl_default_generators</buildtool_depend>

<depend>action_msgs</depend>

<member_of_group>rosidl_interface_packages</member_of_group>

At this point it should be possible to build the action definition:

cd /arm_ws
source /opt/ros/lyrical/setup.bash
colcon build --symlink-install

If the build succeeded, you can view the action definition through the ROS2 interface tools:

source /arm_ws/install/setup.bash
ros2 interface show fibonacci_sequence_action/action/Fibonacci

Preparing the action server script

Now that the new package builds and ROS2 provides the action structure, we can start writing the action server script.

Start by creating a scripts folder and a fibonacci_action_server.py file:

cd /arm_ws/src/fibonacci_sequence_action/
mkdir scripts && cd scripts
touch fibonacci_action_server.py

The fibonacci_action_server.py file should look as follows:

#!/usr/bin/env python3
import time

import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node

from fibonacci_sequence_action.action import Fibonacci


class FibonacciActionServer(Node):

    def __init__(self):
        super().__init__('fibonacci_action_server')
        self._action_server = ActionServer(
            self,
            Fibonacci,
            'fibonacci',
            self.execute_callback)
        self.get_logger().info('Fibonacci Action Server is running...')

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')

        feedback_msg = Fibonacci.Feedback()
        feedback_msg.partial_sequence = [0, 1]

        for i in range(1, goal_handle.request.order):
            feedback_msg.partial_sequence.append(
                feedback_msg.partial_sequence[i] + feedback_msg.partial_sequence[i-1])
            self.get_logger().info('Feedback: {0}'.format(feedback_msg.partial_sequence))
            goal_handle.publish_feedback(feedback_msg)
            time.sleep(1)

        goal_handle.succeed()

        result = Fibonacci.Result()
        result.sequence = feedback_msg.partial_sequence
        return result


def main(args=None):
    rclpy.init(args=args)

    fibonacci_action_server = FibonacciActionServer()

    rclpy.spin(fibonacci_action_server)


if __name__ == '__main__':
    main()

The file should also be marked as executable:

chmod +x fibonacci_action_server.py

You also need to add the scripts/fibonacci_action_server.py path and the rclpy and ament_cmake_python packages to the CMakeLists.txt file:

find_package(ament_cmake_python REQUIRED)
find_package(rclpy REQUIRED)

install(PROGRAMS
  scripts/fibonacci_action_server.py
  DESTINATION lib/${PROJECT_NAME}
)

and the corresponding dependencies to the package.xml file:

  <buildtool_depend>ament_cmake_python</buildtool_depend>

  <depend>rclpy</depend>

The action server is done. You can now build the package and run it:

cd /arm_ws
colcon build --symlink-install
source install/setup.bash
ros2 run fibonacci_sequence_action fibonacci_action_server.py

In a separate terminal you can check the list of available actions:

ros2 action list

and send a goal to the action:

ros2 action send_goal --feedback /fibonacci fibonacci_sequence_action/action/Fibonacci "{order: 10}"

If you get an error about a missing executable, remove the build, install and log folders and rebuild the workspace:

rm -r /arm_ws/build /arm_ws/install /arm_ws/log
source /opt/ros/lyrical/setup.bash
colcon build --symlink-install

5. Independent task

Your task is to prepare an action that meets the requirements from section 3. Use the simulation for this:

cd /arm_ws
source install/setup.bash
ros2 launch arm02 arm02_world.launch.py

It provides two topics:

The first one gives the distance to the obstacle, the second one should be used for motion (linear, along the x axis).

Help with writing subscribers in ROS2 can be found here.

The action should be defined as below:

float32 goal_distance
float32 timeout
float32 precision
---
bool succeeded
float32 final_precision
float32 total_time
---
float32 current_distance
float32 estimated_time

NOTE! The action file name must start with a capital letter and contain only letters and digits.

Hint: how to subscribe to a topic and execute an action at the same time?

Putting a loop in the method that executes the action will make the program stop reading messages from the /laser_scan topic.

To deal with this, you can use a multithreaded executor (MultiThreadedExecutor) instead of the default one provided by rclpy, and assign the subscriber and the action server to a ReentrantCallbackGroup. Messages from the topic will then be received while the action is running. More information and an example can be found here and here.

NOTE! Calling rclpy.spin_once(self) inside the method that executes the action, as recommended by older tutorials, fails in ROS 2 Lyrical with the Executor is already spinning error.

Upload to eKursy:


Author: Kamil Młodzikowski
Based on: Creating an action and Writing an action server and client (Python) from docs.ros.org.