ROS Create Custom Action

In this tutorial you’ll learn how to create a custom ROS action, and import it in your code (Python and Cpp).

Using ROS actions is quite handy when you need an asynchronous client/server mechanism. You can of course use some of the existing Action definitions, but oftentimes you’ll need some sort of customization.

Setup the package for creating a custom ROS Action

You’ll have to create your Action definition inside a package. To keep things organized with ROS it’s better to create one package solely dedicated to Messages, Services, and Actions. Once you have this ROS package, it will be easier not to mix up dependencies between packages, and you’ll have one place where you can find all your custom Messages, Services, and Actions.

Let’s do that now.

First, create a new package.

$ cd ~/catkin_ws/src/
$ catkin_create_pkg my_robot_msgs std_msgs actionlib_msgs
Created file my_robot_msgs/package.xml
Created file my_robot_msgs/CMakeLists.txt
Successfully created files in /home/ros/catkin_ws/src/my_robot_msgs. Please adjust the values in package.xml.

For the package’s name, using the suffix “_msgs” to your robot’s name is a common practice. We also add dependencies to std_msgs and actionlib_msgs.

Then, go into your newly created package and create 3 folders: msg/, srv/, and action/.

$ mkdir msg
$ mkdir srv
$ mkdir action
$ ls
action/  CMakeLists.txt  msg/  package.xml  srv/

You now have the correct package structure. It’s time to create the custom Action definition, and fill the CMakeLists.txt and package.xml files.

Create your custom ROS Action definition

Message definition

Go into the action/ directory of your my_robot_msgs package, and create a new Action file. For this example, we’ll create an Action that counts until a given number. “CountUntil” is a good way to describe the Action.

$ cd action/
$ touch CountUntil.action

Note: don’t use the word “Action” inside your action name, as it makes it redundant. Don’t write “CountUntilAction.action”. Also, use PascalCase: start each word with an uppercase.

Now, open the “CountUntil.action” file and write the Action definition.

#goal         
int64 max_number
float64 wait_duration
---
#result
int64 count
---
#feedback
float64 percentage

You’ll have to define 3 Messages inside the Action, separated by 3 dashes:

  • Goal: the goal that the server should try to reach. Here we give a max_number to reach for the counter, starting from 0. We also give the wait_duration between 2 ticks of the counter.
  • Result: when the server terminates (either success, failure, or abortion), it will send a result to the client. Here we use the current number reached by the counter.
  • Feedback: at any time during the execution, the server can send some feedback to the client. Here we send the progress made as a percentage (you can keep this field empty if there is no use of execution feedback inside your application).

As you can see we’ve only used standard ROS data types for messages. But you can use any composition of standard data types or other messages: std_msgs, custom messages you’ve created, etc.

CMakeLists.txt

cmake_minimum_required(VERSION 2.8.3)
project(my_robot_msgs)          

find_package(catkin REQUIRED COMPONENTS
  actionlib_msgs                
  std_msgs                      
  message_generation            
)
  
add_action_files(
  FILES
  CountUntil.action
)

generate_messages(
  DEPENDENCIES
  actionlib_msgs
  std_msgs
)

catkin_package(
  CATKIN_DEPENDS actionlib_msgs std_msgs message_runtime 
)

include_directories(
  ${catkin_INCLUDE_DIRS}
)

This is all you need in your CMakeLists.txt to create your custom Action. Let’s break this down step by step.

find_package(catkin REQUIRED COMPONENTS
  actionlib_msgs                
  std_msgs                      
  message_generation            
)

In the find_package() you’ll have to also add message_generation. Creating an Action is no different than creating Messages. The Action will be split into different Messages for the goal, result, feedback.

We also find the dependencies that we’ve added when creating the package: std_msgs and actionlib_msgs.

add_action_files(
  FILES
  CountUntil.action
)

This will find any Action definition listed here, from the action/ folder of your package. It’s important that you name the folder “action” and not something else, otherwise the command add_action_files() won’t find any Action definition.

Under “FILES” you’ll simply write the name of the file you created – “CountUntil.action”.

generate_messages(
  DEPENDENCIES
  actionlib_msgs
  std_msgs
)

This will tell the compiler, when using catkin_make, to actually generate messages from the definitions added with add_action_files().

catkin_package(
  CATKIN_DEPENDS actionlib_msgs std_msgs message_runtime 
)

Here we add a dependency to message_runtime.

include_directories(
  ${catkin_INCLUDE_DIRS}
)

Finally no change here, we keep this line as it was.

package.xml

<?xml version="1.0"?>           
<package format="2">            
  <name>my_robot_msgs</name>
  <version>0.0.0</version>
  <description>The my_robot_msgs package</description>
  <maintainer email="ros@todo.todo">ros</maintainer>
  <license>TODO</license>       
  
  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>actionlib_msgs</build_depend>
  <build_depend>std_msgs</build_depend>
  <build_export_depend>actionlib_msgs</build_export_depend>
  <build_export_depend>std_msgs</build_export_depend>
  <exec_depend>actionlib_msgs</exec_depend>
  <exec_depend>std_msgs</exec_depend>
  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

  <export>
  </export>
</package>

And here’s the package.xml content.

The only things that we manually add are (the dependencies to std_msgs and actionlib_msgs were automatically generated, from when we created the package in the terminal):

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

Those 2 lines ensure that the message generation will go well.

Compile your custom ROS Action

Simply run catkin_make into your catkin workspace.

$ cd ~/catkin_ws
$ catkin_make

I won’t paste all the compilation logs because it’s too long, but you should see some lines looking like this:

...
Scanning dependencies of target my_robot_msgs_generate_messages_cpp
[ 35%] Generating C++ code from my_robot_msgs/CountUntilGoal.msg
[ 40%] Generating C++ code from my_robot_msgs/CountUntilAction.msg
[ 48%] Generating C++ code from my_robot_msgs/CountUntilActionGoal.msg
[ 51%] Generating C++ code from my_robot_msgs/CountUntilActionFeedback.msg
[ 54%] Generating C++ code from my_robot_msgs/CountUntilResult.msg
[ 56%] Generating C++ code from my_robot_msgs/CountUntilFeedback.msg
[ 59%] Generating C++ code from my_robot_msgs/CountUntilActionResult.msg
...
Scanning dependencies of target my_robot_msgs_generate_messages_py
[ 81%] Generating Python from MSG my_robot_msgs/CountUntilGoal
[ 83%] Generating Python from MSG my_robot_msgs/CountUntilAction
[ 86%] Generating Python from MSG my_robot_msgs/CountUntilActionGoal
[ 89%] Generating Python from MSG my_robot_msgs/CountUntilActionFeedback
[ 91%] Generating Python from MSG my_robot_msgs/CountUntilResult
[ 94%] Generating Python from MSG my_robot_msgs/CountUntilFeedback
[ 97%] Generating Python from MSG my_robot_msgs/CountUntilActionResult
[100%] Generating Python msg __init__.py for my_robot_msgs
...

If you get a compilation error, something is wrong in your CMakeLists.txt, package.xml or Action definition. Make sure you’ve followed all the steps above.

If the compilation is successful, but you don’t see those lines, then your messages weren’t generated. Check if you didn’t forget the generate_messages() command in the CMakeLists.txt, and that you’ve added “CountUntil.action” in add_action_files().

Now, and this is important: in order to be able to find and use this newly created Action, make sure to source your .bashrc (which should contain source ~/catkin_ws/devel/setup.bash). You can either source it manually in any already open terminal (source ~/.bashrc) or open a new terminal.

When creating any other new Action

When creating any other custom Action, the whole process will only take a few seconds.

If you correctly set up your package and configuration to create the first custom Action, then you only have to:

  • Create a new “.action” file inside the action/ folder, and fill it with a goal, result, feedback.
  • Add the name of the file inside add_action_files(), in the CMakeLists.txt.
  • Compile your message with catkin_make.

And that’s it! From now on you don’t need to bother with any ROS configuration anymore. You can focus on your application and create custom Actions quickly for your needs.

Monitor your custom ROS Action with rosmsg

The whole Action system is actually based on Topics. When you create an Action, many Messages are created, which will be exchanged between the Action client and Action server – though Topics.

To see all the messages created during the compilation, use the rosmsg command line tool.

$ rosmsg list | grep CountUntil
my_robot_msgs/CountUntilAction
my_robot_msgs/CountUntilActionFeedback
my_robot_msgs/CountUntilActionGoal
my_robot_msgs/CountUntilActionResult
my_robot_msgs/CountUntilFeedback
my_robot_msgs/CountUntilGoal
my_robot_msgs/CountUntilResult

7 messages were created!

The 3 that you’ll mostly use in your code are CountUntilGoal, CountUntilResult, and CountUntilFeedback. Then, the actionlib mechanism will take care of the rest.

You can check what’s inside those messages (and by the same occasion, make sure you didn’t make any mistake in your Action definition):

$ rosmsg show my_robot_msgs/CountUntilGoal
int64 max_number
float64 wait_duration

$ rosmsg show my_robot_msgs/CountUntilResult
int64 count

$ rosmsg show my_robot_msgs/CountUntilFeedback
float64 percentage

Import your custom ROS Action in your code

Package dependencies

Important note: don’t create an Action client or server directly into your my_robot_msgs. This package should only contain Message, Service and Action definitions.

For any other package that uses one of your custom Actions, you’ll have to add the following to your CMakeLists.txt.

...
find_package(catkin REQUIRED COMPONENTS
  my_robot_msgs                 
  actionlib
  ...
)
...
catkin_package(
  CATKIN_DEPENDS my_robot_msgs ...
)
...

Basically you’re adding a dependency to the package where your Action definition is located, as well as a dependency to the actionlib library, in order to be able to use the ROS Action mechanisms.

And for the package.xml, add a <depend>my_robot_msgs</depend> line after the main dependencies.

With those additions, you can use your custom Action in any of your nodes inside this package.

Import custom ROS Action in Python node

Just add those import lines into your code:

import rospy
import actionlib

from my_robot_msgs.msg import CountUntilAction
from my_robot_msgs.msg import CountUntilGoal
from my_robot_msgs.msg import CountUntilResult
from my_robot_msgs.msg import CountUntilFeedback

To test if it’s working, simply add those lines into a Python script, and execute it. If you get an error like “ImportError: No module named my_robot_msgs.msg”, it probably means that you didn’t source your environment! Make sure to source your .bashrc or open a new terminal so your environment can find the messages.

Import custom ROS Action in Cpp node

Add those import lines into your code:

#include <ros/ros.h>  
#include <actionlib/server/simple_action_server.h>

#include <my_robot_msgs/CountUntilAction.h>
#include <my_robot_msgs/CountUntilGoal.h>
#include <my_robot_msgs/CountUntilResult.h>
#include <my_robot_msgs/CountUntilFeedback.h>

For Cpp, if you get an import error, it will be during the compilation.

Conclusion

In this tutorial you’ve seen how to create and import custom Actions, along with some best practices.

A few important points to remember:

  • Create a dedicated package (named YOUR_ROBOT_msgs) for all your custom Messages, Services, and Actions.
  • Don’t forget to source your catkin workspace after you generate the messages (catkin_make) or else you won’t find them in your environment.
  • Make sure to double check your CMakeLists.txt and package.xml. Almost all errors will come from those files. Once you’ve set them up right, everything will run smoothly.

FREE Mini-Course

Don't miss this opportunity:

ROS2 Core Concepts in 1h


>> ROS2 Core Concepts Overview in 1h <<