ROS2 Message Interfaces: Ano at Paano Gamitin ang mga Ito – ROS Filipino Tutorial

ROS2 Message Interfaces: Ano at Paano Gamitin ang mga Ito – ROS Filipino Tutorial

This tutorial is created by Robotics Ambassador Christian



Unit 1 Introduksyon sa Kurso

– Buod –

Tinatayang Oras na Kakailanganin: 30 minuto

Mabuhay!

Sa maikling gabay na ito, ating dadaluban kung ano nga ba ang mga ROS2 Messages at paano natin magagamit ang mga ito sa ating paggawa ng mga robots.

Bilang gabay sa mga tatalakayin ng proyektong ito, sila ay nakalista bilang mga sumusunod:

1. Introduksyon sa Kurso
1.1 Mga Pangangailangan sa Kurso at mga Sanggunian
2. Pagpapahayag ng Konsepto ng ROS2 Messages
3. Praktikal na Halimbawa: Paggawa ng Sariling Message at Paggamit nito sa uganayang publisher-subscriber

– Dulo ng Buod –

Unit 1.1 Mga Pangangailangan sa Kurso at mga Sanggunian

Para sa kursong ito, tinataya ng may-akda na may kaalaman ka na sa pangunahing operasyong pang-terminal ng mga sistemang gumagana sa Ubuntu 22.04 LTS na OS at maging sa pangunahing nabigasyon and commands na ginagamit sa mga Linux terminals. Kung nais mo pang matuto ng mga programming fundamentals na kailangan para sa pag-aral ng ROS, mangyaring tumungo at tignan ang kursong Code Foundation for ROS Learning Path ng The Construct! Tiyak na matututunan mo ang lahat ng kailangan mong kaalaman upang masimulan mo na ang paggamit ng ROS!

Mga Akdang Kasama sa Code Foundation for ROS Learning Path:
* Linux for Robotics
* Python 3 for Robotics
*Examination

Iba pang mga Sanggunian:
* The Construct Blog
* Course Support of The Construct and Robot Ignite Academy
* The Official Rosbotics Ambassadors Channel

Kung nais mong matuto hinggil sa kung paano gumawa at mag-ayos ng workspace directories sa ROS1 at ROs2, maaari mong tignan ang aking nakarrang gabay na ito: How to Create and Organize Workspaces in ROS1 and ROS2

At kung interesado ka na rin sa kung paano gumawa ng mga packages at nodes sa ROS2, mangyaring tignan mo na rin ang gabay na ito: How to Create Packages and Nodes in ROS2

Sa tutorial na ito, Gagamitin natin ang ROSject platform na handog ng TheConstruct! Subalit, kung nais niyong gumamit ng sariling virtual machine o computer, mangyaring gawing gabay ang OS-to-Distribution compatibilities na siyang ibinahagi ng opisyal na dokumentasyon ng ros.org para sa ROS2 Humble Hawksbill

* ROS2 Humble Hawksbill: PC o Virtual Machine na may Ubuntu 22.04 LTS

Unit 2: ROS2 Messages – Ano nga ba sila?

Sa kasalukuyan ay dapat pamilyar na tayo sa konsepto ng mga topics sa ROS2. Kung ating babalikan ang naging halimbawa noong nakaraan, ang mga ROS2 topics ay siyang mga nagsisilbing lagusan ng impormasyon kung saan maaari itong gamitin ng mga subscriber para mapatakbo ang isang feature ng robot. Ngayon naman’y ating dadaluban ang isa sa mga mahalagang aspeto ng mga ito at ito ang mga messages!

Sa ROS2, may tatlong pangunahing uri ng interface — ang messages, services, at actions. Sa gabay na ito, ating tatalakayin ang mga messages. Sa madaling salita, ang mga dokumentong ito ang siyang naglalaman ng mga variables na gagamitin ng ating robot o program hinggil sa pagpapagana ng naturang feature. Isang magandang halimbawa na nito ay ang Twist() message na gamit natin upang bigyan ng velocities and ating turtlesim.

Kung ating sisiyasatin ang laman ng Twist() message type, ito ang ating masisilayan:
Vector3  linear
Vector3  angular

At sa loob ng bawat Vector, ito naman ang laman nila:

linear: 
x: 0.0 
y: 0.0 
z: 0.0
angular: 
x: 0.0 
y: 0.0 
z: 0.0

Ibigsabihin nito ay mayroong tatlong posibleng velocities na pwedeng ipublish gamit ang message na ito. Ang mga linear velocities ay siyang tumutkoy sa paharap at palikod, pagilid, at pataas na galaw ng robot; habang ang mga angular naman ay tinatawag na pitch, yaw, at roll. Para sa mga groundbot, ang madalas gamitin na angular velocity ay ang yaw; habang para sa mga robot na panghimpapawid ay ginagamit ang anim na yan dahil mas marami ang kanilang Degrees of Freedom (DoF).

Ngayon, kung ating papansinin ang pagsulat ng mga messages sa ROS2, masasabi nating napakasimple ng kanilang istruktura. Ang tanging kailangan lamang na ilagay ay yung klase ng variable at yung pangalan nito — at hinggil naman sa pagpapangalan, dapat ay masimulan lamang ito sa kapital ng letra at ang file extension ay ‘.msg’.

<variable type> <variable name>   

Ngayon at alam na natin kung ano sila, halina’t ating simulan ang paggawa at paggamit ng ating mga customized na ROS2 messages!

Unit 3: Paggawa ng Sariling Message at Paggamit nito sa Uganayang Publisher-Subscriber

Bilang panimula, tayo muna ay gumawa ng ating Workspaces folder na siyang maglalaman ng lahat ng ating workspace para sa ROS2.

# Sa Terminal 1
user:~$ mkdir Workspaces

Matapos niyan ay gumawa tayo ng workspace na ating papangalanang sample_ws. Mangyaring patakbuhin na rin ang ‘colcon build’ pagkatapos sa loob nito.

# Sa Terminal 1
user:~/Workspaces$ mkdir sample_ws
user:~/Workspaces$ colcon build

Para sa gabay na ito, gagawa tayo ng dalawang packages — una ay ating tatawaging ‘my_interface_pkg’ na siyang maglalaman ng ating custom message at ang isa naman ay ating tatawagin ‘use_custom_interface_pkg’ kung saan natin gagawin ang mga publisher at subscriber na gagamit ng ating nagawang interface.

Ngayon ay ating unahin muna ang ‘my_interface_pkg’

# Sa Terminal 1

user:~/Workspaces$ cd sample_ws
user:~/Workspaces/sample_ws$ mkdir src
user:~/Workspaces/sample_ws$ cd src
user:~/Workspaces/sample_ws/src$ ros2 pkg create my_interface_pkg --build-type ament_cmake --dependencies std_msgs rclcpp

Isang mahalagang konseptong alalahanin ay sa tuwing gagawa ng package para sa mga interfaces ay dapat na ament_cmake ang build type ng mga ito at nakadepende sa rclcpp. Subalit para sa paggawa ng nodes ay malaya tayong pumili kung nais nating gumamit ng python o cpp na environment.

Kasunod ng paggawa ng package ay gumawa tayo ng folder na ating papangalanang ‘msg’ sa loob ng my_interface_pkg. Mula rito ay gumawa na rin tayo ng ating message file na ating papangalanang ‘Numbers.msg’

# Sa Terminal 1

user:~/Workspaces/sample_ws/src$ cd my_interface_pkg
user:~/Workspaces/sample_ws/src/my_interface_pkg$ mkdir msg
user:~/Workspaces/sample_ws/src/my_interface_pkg$ cd msg
user:~/Workspaces/sample_ws/src/my_interface_pkg/msg$ touch Numbers.msg

Mangyaring kopyahin ang naturang code sa ilalim na siyang magiging laman ng Numbers.msg

int32 a
int32 b

Tumungo sa CMakeList.txt na file at idagdag ang sumusunod na mga linya:

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/num.msg"
)

Ganito ang kalalabasan ng iyong CMakeList.txt

cmake_minimum_required(VERSION 3.8)
project(my_interface_pkg)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(std_msgs REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rosidl_default_generators REQUIRED)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/Numbers.msg"
)

ament_package()

Ngayon naman ay tumungo sa package.xml at idagdag ang mga sumusunod na sipi ng code:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>my_interface_pkg</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="user@todo.todo">user</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>std_msgs</depend>
  <depend>rclcpp</depend>

  <build_depend>rosidl_default_generators</build_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

Siguraduhing na-save ang lahat ng mga file at i-compile ang naturang package

# Terminal 1

user:~/Workspaces/sample_ws/src/my_interface_pkg/msg$ cd ../../../
user:~/Workspaces/sample_ws$ colcon build

Ngayon naman ay tumungo tayo sa paggawa ng publisher at subscriber na gagamit ng ating custom message.

Una, gumawa ng python package na ating papangalanang ‘use_custom_interface_pkg’

# Sa Terminal 1

user:~/Workspaces/sample_ws$ cd src
user:~/Workspaces/sample_ws/src$ ros2 pkg create use_custom_interface_pkg --build-type ament_python --dependencies rclpy std_msgs geometry_msgs my_interface_pkg

Para sa ating mga codes, atin itong ilalagay sa loob ng /use_custom_interface_pkg/use_custom_interface_pkg na directory at papangalanan natin ang publisher at subscriber bilang sumusunod:

Publisher: custom_publisher.py
Subscriber: custom_subscriber.py

# Sa Terminal 1

user:~/Workspaces/sample_ws/src$ cd use_custom_interface_pkg/cd use_custom_interface_pkg/
user:~/Workspaces/sample_ws/src/use_custom_interface_pkg/cd use_custom_interface_pkg$ touch custom_subscriber.py custom_publisher.py

Siguraduhing executable na rin ang mga ito.

# Sa Terminal 1

user:~/Workspaces/sample_ws/src/use_custom_interface_pkg/cd use_custom_interface_pkg$ chmod +x custom_subscriber.py custom_publisher.py

Ngayon ay simulan na natin silang i-program!

Code para sa custom_publisher.py

import rclpy
from rclpy.node import Node
from my_interface_pkg.msg import Numbers
import random
import time

class CustomPublisher(Node):
    def __init__(self):
        super().__init__('custom_publisher')
        self.custom_publisher_ = self.create_publisher(Numbers, 'custom_topic_sample', 10)
        self.msg = Numbers()
        self.timer = self.create_timer(1, self.timer_callback)
    
    def timer_callback(self):
        
        self.msg.a = random.randint(1,10)
        self.msg.b = random.randint(1,10)
        self.custom_publisher_.publish(self.msg)
        self.get_logger().info(f"Publishing numbers: a={self.msg.a}, b={self.msg.b}")

def main(args=None):
    rclpy.init(args=args)
    custom_publisher = CustomPublisher()
    rclpy.spin(custom_publisher)
    custom_publisher.destroy_node()
    rclpy.shutdown()

if __name__=='__main__':
    main()

Code para sa custom_subscriber.py

import rclpy
from rclpy.node import Node
from my_interface_pkg.msg import Numbers

class CustomSubscriber(Node):
    def __init__(self):
        super().__init__('custom_subscriber')
        self.subscription = self.create_subscription(
            Numbers,
            'custom_topic_sample',
            self.listener_callback,
            10)

    def listener_callback(self, msg):
        sum_numbers = msg.a + msg.b
        self.get_logger().info(f"Received numbers: a={msg.a}, b={msg.b}. Sum: {sum_numbers}")

def main(args=None):
    rclpy.init(args=args)
    custom_subscriber_node = CustomSubscriber()
    rclpy.spin(custom_subscriber_node)
    custom_subscriber_node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Matapos nating maprogram ang mga ito, tumungo tayo sa ‘setup.py’ at idagdag ang mga sumusunod na linya:

from glob import glob
import os


(os.path.join('share', package_name), glob('launch/*.launch.py'))

Sa loob naman ng ‘console_scripts’ ay gawin natin ang ating mga node:

'custom_publisher = use_custom_interface_pkg.custom_publisher:main',
'custom_subscriber = use_custom_interface_pkg.custom_subscriber:main'

Sa kalaunan ay ganito dapat ang ating setup.py:

from setuptools import setup
from glob import glob
import os

package_name = 'use_custom_interface_pkg'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name), glob('launch/*.launch.py'))
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='user',
    maintainer_email='user@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'custom_publisher = use_custom_interface_pkg.custom_publisher:main',
            'custom_subscriber = use_custom_interface_pkg.custom_subscriber:main'
        ],
    },
)

Matapos i-code ang mga ito, mangyaring muling i-compile ang kabuuan ng ‘sample_ws’. At ngayon, atin nang patakbuhin ang ating mga nagawang node na gumagamit ng ating custom message!

# Sa Terminal 1

user:~/Workspaces/sample_ws/src/use_custom_interface_pkg/cd use_custom_interface_pkg$ cd ../../../
user:~/Workspaces/sample_ws$ colcon build

Para sa bahaging ito, i-source ang terminal 2 and 3 para sa sample_ws

# Sa Terminal 2 at 3

user:~$ cd Workspaces/sample_ws
user:~/Workspaces/sample_ws$ source install/setup.bash

Patakbuhin ang publisher at subscriber sa Terminal 2 at 3

# Sa Terminal 2
user:~/Workspaces/sample_ws$ ros2 run use_custom_interface_pkg custom_publisher
# Sa Terminal 3
user:~/Workspaces/sample_ws$ ros2 run use_custom_interface_pkg custom_subscriber

Ganito dapat ang lalabas sa iyong pagpapatakbo:

[INFO] [1711264604.204593355] [custom_subscriber]: Received Numbers: a=3, b=8, sum=11
[INFO] [1711264604.204593546] [custom_subscriber]: Received Numbers: a=3, b=5, sum=8
[INFO] [1711264604.187348953] [custom_subscriber]: Received Numbers: a=2, b=3, sum=5
[INFO] [1711264604.237378829] [custom_subscriber]: Received Numbers: a=4, b=4, sum=8
[INFO] [1711264604.142367827] [custom_subscriber]: Received Numbers: a=1, b=6, sum=7
...
[INFO] [1711264604.204593355] [custom_subscriber]: Received Numbers: a=7, b=9, sum=16
[INFO] [1711264604.204593355] [custom_subscriber]: Received Numbers: a=3, b=1, sum=4

At ayan!

Matagumpay nating nagawa ang ating custom message sa ROS2 at magamit ang mga ito sa publisher at subscriber! Kapag tapos ka nang magsiyasat, maaarin mong pindutin ang “ctrl + c” upang patayin na ang naturang node at maging ang simulation. At diyan nagtatapos ang ating maikling gabay hinggil sa paggawa ng custom messages! Nawa’y may natutunan kayong bago na makatutulong sa inyong pag-aaral ng ROS!

Para sa iba pang mga ROSject na tulad nito, mangyaring bisitahin ang The Construct. Nag-aalok sila ng napakaraming praktikal na mga gabay sa ROS mula sa mga payak hanggang sa mga konseptong pangbihasa na!

Hanggang sa muli! Ito si Christian C. Anabeza, ang inyong Filipino ROSbotics Ambassador!

Video Tutorial

[ROS2 How-to] #2 – Create a ROS2 action server

[ROS2 How-to] #2 – Create a ROS2 action server

What we are going to learn

  1. – How to create a custom action message
  2. How to create an action server

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/#/l/4a1c58c5/
  2. The Construct: https://app.theconstructsim.com/
  3. ROS2 Courses –▸
    1. ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/#/Course/73
    2. ROS2 Basics in 5 Days (C++): https://app.theconstructsim.com/#/Course/61
    3. ROS2 Navigation training: https://www.theconstruct.ai/ros2-navigation-training/

What is a ROS2 Action

Let’s assume you wish to wash your clothing. There are two possible ways you could go about it:

  1. Go to the Laundry service provider
    1. Put your clothes to wash.
    2. Wait until the clothes are washed.
    3. Get your clothes.
  2. If you have a washing machine at home:
    1. Put your clothes to wash
    2. Instead of waiting, you can do other things and leave the watching machine doing its jobs
    3. Check once in a while if the clothes are finished
    4. Do other things.
    5. Clothes are washed.

Option 1 is a blocking activity because you have to wait (in theory not able to do anything else) for the clothes to be washed, while option 2 is non-blocking because you can do some other things while your clothes are being washed.

This non-blocking is what defines an Action. If ROS2 Services are for instant request-responses, an Action is a task that may take a lot of time to be finished, and in the meantime, a robot (or you) is free to do other things and is also able to constantly check the status of the action.

Opening the rosject

In order to learn how to create an and use an Action Server in ROS2, we need to have ROS2 installed in our system, and it is also useful to have some simulations. To make your life easier, we already prepared a rosject with a simulation for that: https://app.theconstructsim.com/#/l/4a1c58c5/.

You can download the rosject on your own computer if you want to work locally, but just by copying the rosject (clicking the link), you will have a setup already prepared for you.

After the rosject has been successfully copied to your own area, you should see a Run button. Just click that button to launch the rosject (below you have a rosject example).

Learn ROS2 Parameters - Run rosject

Learn ROS2 – Run rosject (example of the RUN button)

After pressing the Run button, you should have the rosject loaded. Let’s now head to the next section to really get some real practice.

Launching the simulation

The rosject we provided contains the packages needed to run a TurtleBot3 simulation in ROS2. The TurtleBot3 has the following sensors:

  • Lidar
  • IMU

Feel free to use this rosject to test your mobile robot programs.

The rosject is structured the following way:

  • turtlebot3_ws: this workspace contains the TurtleBot3 packages provided by ROBOTIS. Don’t modify this unless you know what you are doing and want to change something from the simulation
  • Use this workspace to develop your programs

Assuming you have opened the rosject by clicking the Run button, we can launch the simulation with:

cd
source turtlebot3_ws/install/setup.bash
source turtlebot3_ws/install/setup.bash
export TURTLEBOT3_MODEL=burger
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py
After a few seconds, the simulation should have opened automatically:

[ROS2 How-to] #2 - Create a ROS2 action server - Simulation running

[ROS2 How-to] #2 – Create a ROS2 action server – Simulation running

 

In case the simulation does not pop up automatically, you can easily click the Open Gazebo button like in the example below (bear in mind that the simulation below is not the one used in this tutorial. Its main purpose is to show the Open Gazebo button):

Open Gazebo by clicking Open Gazebo

Open Gazebo by clicking Open Gazebo

 

Creating our ROS2 package (later used to create our Action Server)

Let’s create our ROS2 Package. For that, let’s start by opening a new terminal:

Open a new Terminal

Open a new Terminal

 

In the terminal that was just open, by running the “ls”  command you can see that we have at least the following folders:

ros2_ws  turtlebot3_ws

 

The turtlebot3_ws contains the simulation, and the ros2_ws is where we are going to place our code.

Before you continue, it is worth mentioning that in the rosject that we shared with you, the custom_interfaces package that we are going to create here already exists. We are going to create it here basically for learning purposes. You would actually not need it since the package was already created for you:

Let’s create a package named custom_interfaces with the action_msgs std_msgs rosids_default_generators  packages as dependencies:

cd ~/ros2_ws/src/

ros2 pkg create custom_interfaces2 --build-type ament_cmake --dependencies action_msgs std_msgs rosidl_default_generators

If everything went ok, you should see the following:

going to create a new package
package name: custom_interfaces
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['user <user@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: ['action_msgs', 'std_msgs', 'rosidl_default_generators']
creating folder ./custom_interfaces
creating ./custom_interfaces/package.xml
creating source and include folder
creating folder ./custom_interfaces/src
creating folder ./custom_interfaces/include/custom_interfaces2
creating ./custom_interfaces/CMakeLists.txt

 

After the package was created, let’s create a folder called action:

mkdir -p custom_interfaces/action/

and also create the action/Patrol.action file.

touch custom_interfaces/action/Patrol.action

This is the file/Interface that we will use in our Action Server for patrolling.

Let’s now open that Patrol.action file. You can open it in the Code Editor. If you do not know how to open the Code Editor, please check the image below:

Open the IDE - Code Editor

Open the IDE – Code Editor

You can now open the custom_interfaces/action/Patrol.action file and paste the following content on it:

#Goal
float32 radius
---
#Result
bool success
---
#Feedback
float32 time_left

 

Now, to be able to compile our message file, we have to open the custom_interfaces/CMakeLists.txt file and paste the following content around line 14:

set(action_files
    "action/Patrol.action"
)

rosidl_generate_interfaces(${PROJECT_NAME}
    ${action_files}
    DEPENDENCIES action_msgs std_msgs
)
In the end, the final CMakeLiss.txt file would look like the following:
cmake_minimum_required(VERSION 3.8)
project(custom_interfaces)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(action_msgs REQUIRED)
find_package(std_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)

set(action_files
  "action/Patrol.action"
)

rosidl_generate_interfaces(${PROJECT_NAME}
  ${action_files}
  DEPENDENCIES action_msgs std_msgs
)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

 

And for the file custom_interfaces/package.xml we also have to add the following code before the <export> tag:

<depend>builtin_interfaces</depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

In the end, our package.xml would look like the following:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>custom_interfaces</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="user@todo.todo">user</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>action_msgs</depend>
  <depend>std_msgs</depend>
  <depend>rosidl_default_generators</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <depend>builtin_interfaces</depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

Please make sure you save the files with Ctrl+S after making the modifications.

Compiling our custom Action interface

Now that we defined our Custom Action interface, let’s compile it.

Let’s go to the first terminal we opened previously and run the following commands:

cd ~/ros2_ws/

colcon build

The package should have been compiled with no errors:

Starting >>> custom_interfaces
Finished <<< custom_interfaces [9.53s]

Summary: 1 package finished [9.53s]

 

Let’s now make sure ROS2 can find our Action interface:

source install/setup.bash

ros2 interface show custom_interfaces/action/Patrol

It should show:

#Goal
float32 radius
---
#Result
bool success
---
#Feedback
float32 time_left

 

So far so good. ROS is able to find our custom interface.

The time has now come for us to create the Action Server.

Creating our ROS2 Action Server

Let’s create a different package for the Action Server, just to keep things separated. Since we are not doing to create Interfaces in this new package, just use existing interfaces, let’s use the ament_python build type. Again, bear in mind that if you are using the rosject that we provided, the package already exists in the ~/ros2_ws/src folder:

cd ~/ros2_ws/src/
ros2 pkg create --build-type ament_python patrol_action_server --dependencies rclpy geometry_mgs custom_interfaces

The logs should be similar to the following:

going to create a new package
package name: patrol_action_server
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['user <user@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: ['rclpy', 'geometry_mgs', 'custom_interfaces']
creating folder ./patrol_action_server
creating ./patrol_action_server/package.xml
creating source folder
creating folder ./patrol_action_server/patrol_action_server2
creating ./patrol_action_server/setup.py
creating ./patrol_action_server/setup.cfg
creating folder ./patrol_action_server/resource
creating ./patrol_action_server/resource/patrol_action_server
creating ./patrol_action_server/patrol_action_server/__init__.py
creating folder ./patrol_action_server/test
creating ./patrol_action_server/test/test_copyright.py
creating ./patrol_action_server/test/test_flake8.py
creating ./patrol_action_server/test/test_pep257.py

Now that our package is created, let’s create a file patrol_action_server.py that will have the code of our Action Server:

touch patrol_action_server/patrol_action_server/patrol_action_server.py

Let’s now open that file using the Code Editor, and paste the following content to it:

#!/usr/bin/env python3
#
# Copyright 2019 ROBOTIS CO., LTD.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Authors: Ryan Shim, Gilbert

import math
import time

import rclpy

from geometry_msgs.msg import Twist

from rclpy.action import ActionServer
from rclpy.action import CancelResponse
from rclpy.action import GoalResponse
from rclpy.callback_groups import ReentrantCallbackGroup
from rclpy.duration import Duration
from rclpy.node import Node
from rclpy.qos import QoSProfile

from rclpy.executors import MultiThreadedExecutor

from custom_interfaces.action import Patrol


class Turtlebot3PatrolServer(Node):

    def __init__(self):
        super().__init__('turtlebot3_patrol_server')


        self.goal = Patrol.Goal()


        qos = QoSProfile(depth=10)

        # Initialise publishers
        self.cmd_vel_pub = self.create_publisher(Twist, 'cmd_vel', qos)

        # Initialise servers
        self._action_server = ActionServer(
            self,
            Patrol,
            'patrol',
            execute_callback=self.execute_callback,
            callback_group=ReentrantCallbackGroup(),
            goal_callback=self.goal_callback,
            cancel_callback=self.cancel_callback)

        self.get_logger().info("Turtlebot3 patrol action server has been initialised.")


    def destroy(self):
        self._action_server.destroy()
        super().destroy_node()

    def goal_callback(self, goal_request):
        # Accepts or rejects a client request to begin an action
        self.get_logger().info('Received goal request :)')
        self.goal = goal_request
        return GoalResponse.ACCEPT

    def cancel_callback(self, goal_handle):
        # Accepts or rejects a client request to cancel an action
        self.get_logger().info('Received cancel request :(')
        return CancelResponse.ACCEPT

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

        radius = self.goal.radius  # unit: m
        speed = 0.5  # unit: m/s

        feedback_msg = Patrol.Feedback()
        total_driving_time = 2 * math.pi * radius / speed
        feedback_msg.time_left = total_driving_time
        last_time = self.get_clock().now()

        # Start executing an action
        while (feedback_msg.time_left > 0):
            if goal_handle.is_cancel_requested:
                goal_handle.canceled()
                self.get_logger().info('Goal canceled')
                return Patrol.Result()

            curr_time = self.get_clock().now()
            duration = Duration()
            duration = (curr_time - last_time).nanoseconds / 1e9  # unit: s

            feedback_msg.time_left = total_driving_time - duration
            self.get_logger().info('Time left until the robot stops: {0}'.format(feedback_msg.time_left))
            goal_handle.publish_feedback(feedback_msg)

            # Give vel_cmd to Turtlebot3
            twist = Twist()
            twist = self.drive_circle(radius, speed)
            self.cmd_vel_pub.publish(twist)

            # Process rate
            time.sleep(0.010)  # unit: s

        # When the action is completed
        twist = Twist()
        self.cmd_vel_pub.publish(twist)

        goal_handle.succeed()
        result = Patrol.Result()
        result.success = True
        self.get_logger().info('Returning result: {0}'.format(result.success))

        return result

    def drive_circle(self, radius, velocity):
        self.twist = Twist()
        self.linear_velocity = velocity  # unit: m/s
        self.angular_velocity = self.linear_velocity / radius  # unit: rad/s

        self.twist.linear.x = self.linear_velocity
        self.twist.angular.z = self.angular_velocity

        return self.twist

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

    patrol_action_server = Turtlebot3PatrolServer()

    # Use a MultiThreadedExecutor to enable processing goals concurrently
    executor = MultiThreadedExecutor()

    rclpy.spin(patrol_action_server, executor=executor)

    patrol_action_server.destroy()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

The code used above is just an adaptation of a code already provided by ROBOTIS.
Before we compile our code, we also have to open the patrol_action_server/setup.py file and modify the entry_points section to define our executable called patrol_action_server_exe in the following way:
entry_points={
        'console_scripts': [
            'patrol_action_server_exe = patrol_action_server.patrol_action_server:main',
        ],
    },

 

In the end, the complete ~/ros2_ws/src/patrol_action_server/setup.py would be as follows:

from setuptools import setup

package_name = 'patrol_action_server'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='user',
    maintainer_email='user@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'patrol_action_server_exe = patrol_action_server.patrol_action_server:main',
        ],
    },
)

 

Compiling our ROS2 Action Server

With everything in place, we compile our package just as before:

cd ~/ros2_ws/

colcon build
Starting >>> custom_interfaces
Finished <<< custom_interfaces [9.53s]
Starting >>> patrol_action_server
Finished <<< patrol_action_server [5.33s]

Summary: 2 packages finished [15.2s]
We can now run our server with the following commands:
source install/setup.bash

ros2 run patrol_action_server patrol_action_server_exe
The server should start with no problems:
[INFO] [1651528559.914166370] [turtlebot3_patrol_server]: Turtlebot3 patrol action server has been initialised

Calling our ROS2 Action Server

Ok, if you did not kill the Action Server launched in the previous section, please open a second terminal that we will use to call the Action Server.

With “ros2 node list” we should be able to find our node running:

ros2 node list

/turtlebot3_patrol_server
And with ros2 action list, we should be able to see the /patrol action:
ros2 action list

/patrol
We can now call our Action Server. If you remember when we created the Patrol.action, we defined a radius. Let’s them call the Action Server passing a radius of 0.5. The robot will be basically rotating:
ros2 action send_goal --feedback /patrol custom_interfaces/action/Patrol radius:\ 0.5\
You should now see the feedback sent by the action server:
Waiting for an action server to become available...
Sending goal:
     radius: 0.5

Goal accepted with ID: dd32bc835d7a4ef5ae854d0bfb4b119f

Feedback:
    time_left: 6.2831525802612305

Feedback:
    time_left: 6.271763801574707

Feedback:
    time_left: 6.260392665863037

Feedback:
    time_left: 6.2484917640686035

Feedback:
    time_left: 6.237414836883545

Feedback:
    time_left: 6.2265496253967285

Feedback:
    time_left: 6.215761661529541

...
^CCanceling goal...
Feedback:
time_left: 5.634908676147461
Goal canceled.

Remember that you can easily cancel the call to the action server by pressing CTRL+C.

If you look at the simulation after sending a goal to the Action Server, you should see the robot spinning around 0.5 meters.

Congratulations. You now know how to create a ROS2 Action Server from scratch. If you want more details about the code of the Action Server, please check the video in the next section.

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

Related Courses & Training

If you want to learn more about ROS and ROS2, we recommend the following courses:

ROS2 Concepts in Practice #4 – Interfaces

ROS2 Concepts in Practice #4 – Interfaces

What we are going to learn

In this video, you’ll understand what is a ROS2 interface, the common language behind ROS2 messages, services, and actions.

You will also learn:

  • How to create and compile your own ROS2 Message Interfaces
  • How to create and compile your own ROS2 Service Interfaces

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/#/l/4a5c5215/
  2. ROS Development Studio (ROSDS) —▸ http://rosds.online
  3. ROS2 Courses –▸
    1. ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/#/Course/73
    2. ROS2 Basics in 5 Days (C++): https://app.theconstructsim.com/#/Course/61

Opening the rosject

In order to better understand ROS2 Interfaces, we need to have ROS2 installed in our system, and sometimes it is also useful to have some simulations. To make your life easier, we already prepared a rosject that you can use, with ROS2 already installed: https://app.theconstructsim.com/#/l/4a5c5215/.

You can download the rosject on your own computer if you want to work locally, but just by copying the rosject (clicking the link), you will have a setup already prepared for you.

After the rosject has been successfully copied to your own area, you should see a Run button. Just click that button to launch the rosject (below you have a rosject example).

Learn ROS2 Parameters - Run rosject

Learn ROS2 – Run rosject (example of the RUN button)

 

After pressing the Run button, you should have the rosject loaded. Let’s now head to the next section to really get some real practice.

ROS2 Interfaces overview

ROS2 Interfaces is a common language behind ROS2 messages, services, and actions.

Let’s start listing the interfaces. For that, let’s first open a terminal:

Open a new Terminal

Open a new Terminal

After the terminal is open, you can list the interfaces with the command ros2 interface list. The output would be something similar to the following:

ros2 interface list

Messages:
    action_msgs/msg/GoalInfo
    action_msgs/msg/GoalStatus
    action_msgs/msg/GoalStatusArray
    actionlib_msgs/msg/GoalID
    actionlib_msgs/msg/GoalStatus
    actionlib_msgs/msg/GoalStatusArray
    bond/msg/Constants
    bond/msg/Status
    builtin_interfaces/msg/Duration
    builtin_interfaces/msg/Time
    cartographer_ros_msgs/msg/LandmarkEntry
    cartographer_ros_msgs/msg/LandmarkList
    cartographer_ros_msgs/msg/SensorTopics
    cartographer_ros_msgs/msg/StatusCode
    cartographer_ros_msgs/msg/StatusResponse
    cartographer_ros_msgs/msg/SubmapEntry
    cartographer_ros_msgs/msg/SubmapList
    cartographer_ros_msgs/msg/SubmapTexture
    cartographer_ros_msgs/msg/TrajectoryOptions
    checking_interfaces/msg/NewMsg
    control_msgs/msg/DynamicJointState
    control_msgs/msg/GripperCommand
    control_msgs/msg/InterfaceValue
    control_msgs/msg/JointComponentTolerance
    control_msgs/msg/JointControllerState
    control_msgs/msg/JointJog
    control_msgs/msg/JointTolerance
    control_msgs/msg/JointTrajectoryControllerState
    control_msgs/msg/PidState
    controller_manager_msgs/msg/ControllerState
    controller_manager_msgs/msg/HardwareInterface
    diagnostic_msgs/msg/DiagnosticArray
    diagnostic_msgs/msg/DiagnosticStatus
    diagnostic_msgs/msg/KeyValue
    dwb_msgs/msg/CriticScore
    dwb_msgs/msg/LocalPlanEvaluation
    dwb_msgs/msg/Trajectory2D
    dwb_msgs/msg/TrajectoryScore
    example_interfaces/msg/Bool
    example_interfaces/msg/Byte
    example_interfaces/msg/ByteMultiArray
    example_interfaces/msg/Char
    example_interfaces/msg/Empty
    example_interfaces/msg/Float32
    example_interfaces/msg/Float32MultiArray
    example_interfaces/msg/Float64
    example_interfaces/msg/Float64MultiArray
    example_interfaces/msg/Int16
    example_interfaces/msg/Int16MultiArray
    example_interfaces/msg/Int32
    example_interfaces/msg/Int32MultiArray
    example_interfaces/msg/Int64
    example_interfaces/msg/Int64MultiArray
    example_interfaces/msg/Int8
    example_interfaces/msg/Int8MultiArray
    example_interfaces/msg/MultiArrayDimension
    example_interfaces/msg/MultiArrayLayout
    example_interfaces/msg/String
    example_interfaces/msg/UInt16
    example_interfaces/msg/UInt16MultiArray
    example_interfaces/msg/UInt32
    example_interfaces/msg/UInt32MultiArray
    example_interfaces/msg/UInt64
    example_interfaces/msg/UInt64MultiArray
    example_interfaces/msg/UInt8
    example_interfaces/msg/UInt8MultiArray
    example_interfaces/msg/WString
    gazebo_msgs/msg/ContactState
    gazebo_msgs/msg/ContactsState
    gazebo_msgs/msg/EntityState
    gazebo_msgs/msg/LinkState
    gazebo_msgs/msg/LinkStates
    gazebo_msgs/msg/ModelState
    gazebo_msgs/msg/ModelStates
    gazebo_msgs/msg/ODEJointProperties
    gazebo_msgs/msg/ODEPhysics
    gazebo_msgs/msg/PerformanceMetrics
    gazebo_msgs/msg/SensorPerformanceMetric
    gazebo_msgs/msg/WorldState
    geometry_msgs/msg/Accel
    geometry_msgs/msg/AccelStamped
    geometry_msgs/msg/AccelWithCovariance
    geometry_msgs/msg/AccelWithCovarianceStamped
    geometry_msgs/msg/Inertia
    geometry_msgs/msg/InertiaStamped
    geometry_msgs/msg/Point
    geometry_msgs/msg/Point32
    geometry_msgs/msg/PointStamped
    geometry_msgs/msg/Polygon
    geometry_msgs/msg/PolygonStamped
    geometry_msgs/msg/Pose
    geometry_msgs/msg/Pose2D
    geometry_msgs/msg/PoseArray
    geometry_msgs/msg/PoseStamped
    geometry_msgs/msg/PoseWithCovariance
    geometry_msgs/msg/PoseWithCovarianceStamped
    geometry_msgs/msg/Quaternion
    geometry_msgs/msg/QuaternionStamped
    geometry_msgs/msg/Transform
    geometry_msgs/msg/TransformStamped
    geometry_msgs/msg/Twist
    geometry_msgs/msg/TwistStamped
    geometry_msgs/msg/TwistWithCovariance
    geometry_msgs/msg/TwistWithCovarianceStamped
    geometry_msgs/msg/Vector3
    geometry_msgs/msg/Vector3Stamped
    geometry_msgs/msg/Wrench
    geometry_msgs/msg/WrenchStamped
    libstatistics_collector/msg/DummyMessage
    lifecycle_msgs/msg/State
    lifecycle_msgs/msg/Transition
    lifecycle_msgs/msg/TransitionDescription
    lifecycle_msgs/msg/TransitionEvent
    map_msgs/msg/OccupancyGridUpdate
    map_msgs/msg/PointCloud2Update
    map_msgs/msg/ProjectedMap
    map_msgs/msg/ProjectedMapInfo
    nav2_msgs/msg/BehaviorTreeLog
    nav2_msgs/msg/BehaviorTreeStatusChange
    nav2_msgs/msg/Costmap
    nav2_msgs/msg/CostmapFilterInfo
    nav2_msgs/msg/CostmapMetaData
    nav2_msgs/msg/Particle
    nav2_msgs/msg/ParticleCloud
    nav2_msgs/msg/SpeedLimit
    nav2_msgs/msg/VoxelGrid
    nav_2d_msgs/msg/Path2D
    nav_2d_msgs/msg/Pose2D32
    nav_2d_msgs/msg/Pose2DStamped
    nav_2d_msgs/msg/Twist2D
    nav_2d_msgs/msg/Twist2D32
    nav_2d_msgs/msg/Twist2DStamped
    nav_msgs/msg/GridCells
    nav_msgs/msg/MapMetaData
    nav_msgs/msg/OccupancyGrid
    nav_msgs/msg/Odometry
    nav_msgs/msg/Path
    pcl_msgs/msg/ModelCoefficients
    pcl_msgs/msg/PointIndices
    pcl_msgs/msg/PolygonMesh
    pcl_msgs/msg/Vertices
    pendulum_msgs/msg/JointCommand
    pendulum_msgs/msg/JointState
    pendulum_msgs/msg/RttestResults
    rcl_interfaces/msg/FloatingPointRange
    rcl_interfaces/msg/IntegerRange
    rcl_interfaces/msg/ListParametersResult
    rcl_interfaces/msg/Log
    rcl_interfaces/msg/Parameter
    rcl_interfaces/msg/ParameterDescriptor
    rcl_interfaces/msg/ParameterEvent
    rcl_interfaces/msg/ParameterEventDescriptors
    rcl_interfaces/msg/ParameterType
    rcl_interfaces/msg/ParameterValue
    rcl_interfaces/msg/SetParametersResult
    rmw_dds_common/msg/Gid
    rmw_dds_common/msg/NodeEntitiesInfo
    rmw_dds_common/msg/ParticipantEntitiesInfo
    rosgraph_msgs/msg/Clock
    sensor_msgs/msg/BatteryState
    sensor_msgs/msg/CameraInfo
    sensor_msgs/msg/ChannelFloat32
    sensor_msgs/msg/CompressedImage
    sensor_msgs/msg/FluidPressure
    sensor_msgs/msg/Illuminance
    sensor_msgs/msg/Image
    sensor_msgs/msg/Imu
    sensor_msgs/msg/JointState
    sensor_msgs/msg/Joy
    sensor_msgs/msg/JoyFeedback
    sensor_msgs/msg/JoyFeedbackArray
    sensor_msgs/msg/LaserEcho
    sensor_msgs/msg/LaserScan
    sensor_msgs/msg/MagneticField
    sensor_msgs/msg/MultiDOFJointState
    sensor_msgs/msg/MultiEchoLaserScan
    sensor_msgs/msg/NavSatFix
    sensor_msgs/msg/NavSatStatus
    sensor_msgs/msg/PointCloud
    sensor_msgs/msg/PointCloud2
    sensor_msgs/msg/PointField
    sensor_msgs/msg/Range
    sensor_msgs/msg/RegionOfInterest
    sensor_msgs/msg/RelativeHumidity
    sensor_msgs/msg/Temperature
    sensor_msgs/msg/TimeReference
    shape_msgs/msg/Mesh
    shape_msgs/msg/MeshTriangle
    shape_msgs/msg/Plane
    shape_msgs/msg/SolidPrimitive
    statistics_msgs/msg/MetricsMessage
    statistics_msgs/msg/StatisticDataPoint
    statistics_msgs/msg/StatisticDataType
    std_msgs/msg/Bool
    std_msgs/msg/Byte
    std_msgs/msg/ByteMultiArray
    std_msgs/msg/Char
    std_msgs/msg/ColorRGBA
    std_msgs/msg/Empty
    std_msgs/msg/Float32
    std_msgs/msg/Float32MultiArray
    std_msgs/msg/Float64
    std_msgs/msg/Float64MultiArray
    std_msgs/msg/Header
    std_msgs/msg/Int16
    std_msgs/msg/Int16MultiArray
    std_msgs/msg/Int32
    std_msgs/msg/Int32MultiArray
    std_msgs/msg/Int64
    std_msgs/msg/Int64MultiArray
    std_msgs/msg/Int8
    std_msgs/msg/Int8MultiArray
    std_msgs/msg/MultiArrayDimension
    std_msgs/msg/MultiArrayLayout
    std_msgs/msg/String
    std_msgs/msg/UInt16
    std_msgs/msg/UInt16MultiArray
    std_msgs/msg/UInt32
    std_msgs/msg/UInt32MultiArray
    std_msgs/msg/UInt64
    std_msgs/msg/UInt64MultiArray
    std_msgs/msg/UInt8
    std_msgs/msg/UInt8MultiArray
    stereo_msgs/msg/DisparityImage
    test_msgs/msg/Arrays
    test_msgs/msg/BasicTypes
    test_msgs/msg/BoundedSequences
    test_msgs/msg/Builtins
    test_msgs/msg/Constants
    test_msgs/msg/Defaults
    test_msgs/msg/Empty
    test_msgs/msg/MultiNested
    test_msgs/msg/Nested
    test_msgs/msg/Strings
    test_msgs/msg/UnboundedSequences
    test_msgs/msg/WStrings
    tf2_msgs/msg/TF2Error
    tf2_msgs/msg/TFMessage
    trajectory_msgs/msg/JointTrajectory
    trajectory_msgs/msg/JointTrajectoryPoint
    trajectory_msgs/msg/MultiDOFJointTrajectory
    trajectory_msgs/msg/MultiDOFJointTrajectoryPoint
    turtlesim/msg/Color
    turtlesim/msg/Pose
    unique_identifier_msgs/msg/UUID
    visualization_msgs/msg/ImageMarker
    visualization_msgs/msg/InteractiveMarker
    visualization_msgs/msg/InteractiveMarkerControl
    visualization_msgs/msg/InteractiveMarkerFeedback
    visualization_msgs/msg/InteractiveMarkerInit
    visualization_msgs/msg/InteractiveMarkerPose
    visualization_msgs/msg/InteractiveMarkerUpdate
    visualization_msgs/msg/Marker
    visualization_msgs/msg/MarkerArray
    visualization_msgs/msg/MenuEntry
Services:
    action_msgs/srv/CancelGoal
    cartographer_ros_msgs/srv/FinishTrajectory
    cartographer_ros_msgs/srv/StartTrajectory
    cartographer_ros_msgs/srv/SubmapQuery
    cartographer_ros_msgs/srv/WriteState
    checking_interfaces/srv/NewServiceMessage
    composition_interfaces/srv/ListNodes
    composition_interfaces/srv/LoadNode
    composition_interfaces/srv/UnloadNode
    control_msgs/srv/QueryCalibrationState
    control_msgs/srv/QueryTrajectoryState
    controller_manager_msgs/srv/ConfigureController
    controller_manager_msgs/srv/ConfigureStartController
    controller_manager_msgs/srv/ListControllerTypes
    controller_manager_msgs/srv/ListControllers
    controller_manager_msgs/srv/ListHardwareInterfaces
    controller_manager_msgs/srv/LoadConfigureController
    controller_manager_msgs/srv/LoadController
    controller_manager_msgs/srv/LoadStartController
    controller_manager_msgs/srv/ReloadControllerLibraries
    controller_manager_msgs/srv/SwitchController
    controller_manager_msgs/srv/UnloadController
    diagnostic_msgs/srv/AddDiagnostics
    diagnostic_msgs/srv/SelfTest
    dwb_msgs/srv/DebugLocalPlan
    dwb_msgs/srv/GenerateTrajectory
    dwb_msgs/srv/GenerateTwists
    dwb_msgs/srv/GetCriticScore
    dwb_msgs/srv/ScoreTrajectory
    example_interfaces/srv/AddTwoInts
    example_interfaces/srv/SetBool
    example_interfaces/srv/Trigger
    gazebo_msgs/srv/ApplyBodyWrench
    gazebo_msgs/srv/ApplyJointEffort
    gazebo_msgs/srv/ApplyLinkWrench
    gazebo_msgs/srv/BodyRequest
    gazebo_msgs/srv/DeleteEntity
    gazebo_msgs/srv/DeleteLight
    gazebo_msgs/srv/DeleteModel
    gazebo_msgs/srv/GetEntityState
    gazebo_msgs/srv/GetJointProperties
    gazebo_msgs/srv/GetLightProperties
    gazebo_msgs/srv/GetLinkProperties
    gazebo_msgs/srv/GetLinkState
    gazebo_msgs/srv/GetModelList
    gazebo_msgs/srv/GetModelProperties
    gazebo_msgs/srv/GetModelState
    gazebo_msgs/srv/GetPhysicsProperties
    gazebo_msgs/srv/GetWorldProperties
    gazebo_msgs/srv/JointRequest
    gazebo_msgs/srv/LinkRequest
    gazebo_msgs/srv/SetEntityState
    gazebo_msgs/srv/SetJointProperties
    gazebo_msgs/srv/SetJointTrajectory
    gazebo_msgs/srv/SetLightProperties
    gazebo_msgs/srv/SetLinkProperties
    gazebo_msgs/srv/SetLinkState
    gazebo_msgs/srv/SetModelConfiguration
    gazebo_msgs/srv/SetModelState
    gazebo_msgs/srv/SetPhysicsProperties
    gazebo_msgs/srv/SpawnEntity
    gazebo_msgs/srv/SpawnModel
    lifecycle_msgs/srv/ChangeState
    lifecycle_msgs/srv/GetAvailableStates
    lifecycle_msgs/srv/GetAvailableTransitions
    lifecycle_msgs/srv/GetState
    logging_demo/srv/ConfigLogger
    map_msgs/srv/GetMapROI
    map_msgs/srv/GetPointMap
    map_msgs/srv/GetPointMapROI
    map_msgs/srv/ProjectedMapsInfo
    map_msgs/srv/SaveMap
    map_msgs/srv/SetMapProjections
    nav2_msgs/srv/ClearCostmapAroundRobot
    nav2_msgs/srv/ClearCostmapExceptRegion
    nav2_msgs/srv/ClearEntireCostmap
    nav2_msgs/srv/GetCostmap
    nav2_msgs/srv/LoadMap
    nav2_msgs/srv/ManageLifecycleNodes
    nav2_msgs/srv/SaveMap
    nav_msgs/srv/GetMap
    nav_msgs/srv/GetPlan
    nav_msgs/srv/LoadMap
    nav_msgs/srv/SetMap
    pcl_msgs/srv/UpdateFilename
    rcl_interfaces/srv/DescribeParameters
    rcl_interfaces/srv/GetParameterTypes
    rcl_interfaces/srv/GetParameters
    rcl_interfaces/srv/ListParameters
    rcl_interfaces/srv/SetParameters
    rcl_interfaces/srv/SetParametersAtomically
    rosbag2_interfaces/srv/GetRate
    rosbag2_interfaces/srv/IsPaused
    rosbag2_interfaces/srv/Pause
    rosbag2_interfaces/srv/PlayNext
    rosbag2_interfaces/srv/Resume
    rosbag2_interfaces/srv/Seek
    rosbag2_interfaces/srv/SetRate
    rosbag2_interfaces/srv/TogglePaused
    sensor_msgs/srv/SetCameraInfo
    slam_toolbox/srv/AddSubmap
    slam_toolbox/srv/Clear
    slam_toolbox/srv/ClearQueue
    slam_toolbox/srv/DeserializePoseGraph
    slam_toolbox/srv/LoopClosure
    slam_toolbox/srv/MergeMaps
    slam_toolbox/srv/Pause
    slam_toolbox/srv/SaveMap
    slam_toolbox/srv/SerializePoseGraph
    slam_toolbox/srv/ToggleInteractive
    std_srvs/srv/Empty
    std_srvs/srv/SetBool
    std_srvs/srv/Trigger
    test_bond/srv/TestBond
    test_msgs/srv/Arrays
    test_msgs/srv/BasicTypes
    test_msgs/srv/Empty
    tf2_msgs/srv/FrameGraph
    turtlesim/srv/Kill
    turtlesim/srv/SetPen
    turtlesim/srv/Spawn
    turtlesim/srv/TeleportAbsolute
    turtlesim/srv/TeleportRelative
    visualization_msgs/srv/GetInteractiveMarkers
Actions:
    action_tutorials_interfaces/action/Fibonacci
    control_msgs/action/FollowJointTrajectory
    control_msgs/action/GripperCommand
    control_msgs/action/JointTrajectory
    control_msgs/action/PointHead
    control_msgs/action/SingleJointPosition
    example_interfaces/action/Fibonacci
    nav2_msgs/action/BackUp
    nav2_msgs/action/ComputePathThroughPoses
    nav2_msgs/action/ComputePathToPose
    nav2_msgs/action/DummyRecovery
    nav2_msgs/action/FollowPath
    nav2_msgs/action/FollowWaypoints
    nav2_msgs/action/NavigateThroughPoses
    nav2_msgs/action/NavigateToPose
    nav2_msgs/action/Spin
    nav2_msgs/action/Wait
    test_msgs/action/Fibonacci
    test_msgs/action/NestedMessage
    tf2_msgs/action/LookupTransform
    turtlesim/action/RotateAbsolute

 

As you can see in the output above, the same command returned Messages, Actions, and Services.

Creating our first ROS2 Interface (a message)

If you are using the rosject provided at the beginning of this post, there is already a ROS2 Package called checking_interfaces on the /home/user/ros2_ws/src/checking_interfaces path. The package also already contains a message on the ~/ros2_ws/src/checking_interfaces/msg/NewMsg.msg path.

If you are not using the provided rosject, you can create a package with:

mkdir -p /home/user/ros2_ws/src

cd /home/user/ros2_ws/src

ros2 pkg create --build-type ament_cmake checking_interfaces

The output would be similar to the following:

going to create a new package
package name: checking_interfaces
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['user <user@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: []
creating folder ./checking_interfaces
creating ./checking_interfaces/package.xml
creating source and include folder
creating folder ./checking_interfaces/src
creating folder ./checking_interfaces/include/checking_interfaces
creating ./checking_interfaces/CMakeLists.txt

 

If you are using the rosject you do not need to create the message msg/NewMsg.msg, but for learning purposes, let’s see how to create it.

~/ros2_ws/src/checking_interfaces$ ls ~/ros2_ws/src/checking_interfaces$ ls msg/NewMsg.msg

cd ~/ros2_ws/src/checking_interfaces

mkdir msg

cd msg

touch NewMsg.msg

Then, paste the following content on the NewMsg.msg file:

bool check
int16 number
string text

If you are wondering how to know which types are accepted for the properties of the new message, the types available are:

ROS2 build-in-types

Type name C++ Python DDS type
bool bool builtins.bool boolean
byte uint8_t builtins.bytes* octet
char char builtins.str* char
float32 float builtins.float* float
float64 double builtins.float* double
int8 int8_t builtins.int* octet
uint8 uint8_t builtins.int* octet
int16 int16_t builtins.int* short
uint16 uint16_t builtins.int* unsigned short
int32 int32_t builtins.int* long
uint32 uint32_t builtins.int* unsigned long
int64 int64_t builtins.int* long long
uint64 uint64_t builtins.int* unsigned long long
string std::string builtins.str string
wstring std::u16string builtins.str string

Compiling our ROS2 Interface

In order to compile our ROS2 Interface, we have to touch the ~/ros2_ws/src/checking_interfaces/CMakeLists.txt file. Again, if you are using the rosject we provided, everything is already prepared for you. For learning purposes, let’s open the file to modify it. Let’s start by opening the Code Editor:

Open the IDE - Code Editor

Open the IDE – Code Editor

 

After the Code Editor is open, feel free to open the checking_interfaces/CMakeListst.txt file.

Around line 13 of the CMakeListst.txt file, we have to add the following lines:

find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
	"msg/NewMsg.msg"
)

The first line mentioned above includes the rosidl_default_generators package, which will be used to “compile” our message to make it available for Python and C++, so that our nodes can use the interface.

In the rosidl_generate_interfaces we add the name of the interface (message, service, or action) that we want to compile, which in this case is “msg/NewMsg.msg”. 

After setting up the CMakeLists.txt file, you also have to set up the checking_interfaces/package.xml file. We have to add rosidl_default_generators as a build dependency, we also need to add the rosidl_default_runtime execution dependency, and we also need the rosidl_interface_packages “member of group”. In the end, the lines you would need to add are:

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

In the end, the final package.xml file would be as follows:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
  <package format="3">
  <name>checking_interfaces</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintaineremail="user@todo.todo">user</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  <build_depend>rosidl_default_generators</build_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>
After saving the files, you can compile the interface with:
cd ~/ros2_ws/

colcon build --packages-select checking_interfaces

 

The package should compile with no errors:

...
This may be promoted to an error in a future release of colcon-core.
Starting >>> checking_interfaces
Finished <<< checking_interfaces [5.27s]

Summary: 1 package finished [5.87s]

 

After the package is compiled, we can now source the installation folder so that ROS can find what we have compiled so far:

source install/setup.bash

 

If we now list the interfaces again and search for our NewMsg, we should be able to find it:

ros2 interface list | grep New

# ...
checking_interfaces/msg/NewMsg
# ...

As we can see in the output above, we have a message called NewMsg.

Using the interface we just created

If we have just created the checking_interfaces/msg/NewMsg, we should be able to use it. We can check its definition with:

ros2 interface show checking_interfaces/msg/NewMsg

which shows the exact thing we defined in the NewMsg.msg file:

bool check
int16 number
string text

 

Let’s now create a publisher in a topic called /testing using this message:

ros2 topic pub /testing checking_interfaces/msg/NewMsg

After pressing ENTER, we should see the message being published with default values:

 

publisher: beginning loop
publishing #1: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #2: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #3: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #4: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #5: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #6: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #7: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
...

You can of course set the values of the message:

ros2 topic pub /testing checking_interfaces/msg/NewMsg "{check: true, number: 7, text: 'Be perfect' }"

# WHICH OUTPUTS

publisher: beginning loop
publishing #1: checking_interfaces.msg.NewMsg(check=True, number=7, text='Be perfect')
publishing #2: checking_interfaces.msg.NewMsg(check=True, number=7, text='Be perfect')
publishing #3: checking_interfaces.msg.NewMsg(check=True, number=7, text='Be perfect')
...

 

Creating our second ROS2 Interface (a service)

Similar to when defining our message, we already have a service defined on ~/ros2_ws/src/checking_interfaces/srv/NewServiceMessage.srv, but we will be following the process for learning purposes in case you are not using the rosject we provided, or wants to learn with hands-on.

cd ~/ros2_ws/src/checking_interfaces

mkdir srv

cd srv

touch NewServiceMessage.srv

You can now open the NewServiceMessage.srv file with the Code Editor and paste the following content on it:

bool check
---
int16 number
string text

In order to compile the service interface, you have to open the CMakeLists.txt file again and add “srv/NewServiceMessage.srv” right after “msg/NewMsg.msg” that we added earlier around line 15 of our CMakeLists.txt file. The rosidl_generate_interfaces section of the file would be like:

find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
    "msg/NewMsg.msg"
    "srv/NewServiceMessage.srv"
)
As you can see so far, the same rosidl_generate_interfaces is used to compile Messages and Services.
You may have noticed that we have a line containing “—“ in the NewServiceMessage.srv file. These three “—” basically separate the Service Request from the Service Response.
The request will be “bool check“, and the fields used in a response will be “int16 number” and “string text“.
After saving the file changes, we can just compile the service interface in the same way that we compiled the message interface:
cd ~/ros2_ws/

colcon build --packages-select checking_interfaces

 source install/setup.bash

If you now list the interfaces, you should be able to easily find our NewServiceMessage.

ros2 interface list | grep New


checking_interfaces/msg/NewMsg
checking_interfaces/srv/NewServiceMessage

 

Congratulations. You now know how to easily create ROS2 Interfaces. Please go ahead and try to create ROS2 Action Interfaces yourself, or you can also check the courses that we have listed at the end of this post.

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

Related Courses & Training

If you want to learn more about ROS and ROS2, we recommend the following courses:

Pin It on Pinterest