How to Publish and Subscribe to a topic from a launch file

How to Publish and Subscribe to a topic from a launch file

In this post, you will learn how to publish and subscribe to a topic from a launch file. This post is a response to this question posted on Gazebo Answers.

Step 1: Fire up a system with ROS installation

“Hey, do you mean I have to install ROS first?” Absolutely not! Just log in to The Construct to get access to virtual machines pre-installed with ROS.

Once logged in, click on My Rosjects, then Create a New Rosject, supply the information as shown in the image below, and click Create. Then RUN the rosject.

Create a new Rosject

You might also want to try this on a local PC if you have ROS installed. However, please note that we don’t support local PCs and you will have to fix any errors you run into on your own. The rest of the instruction assumes that you are working on The Construct; please adapt them to your local PC and ROS installation.

PS: we are using ROS Noetic. You should be able to replicate the same on any ROS 1 distro.

Step 2: Create a package to demonstrate how to publish and subscribe to a topic from a launch file

Open Code Editor
Open a web shell

Open a web shell and run the following commands to create the package.

cd catkin_ws/src
source /opt/ros/noetic/setup.bash
catkin_create_pkg test_rostopic

Next, we create the launch directory and the launch files.

cd test_rostopic
mkdir launch
cd launch
touch test_pub.launch
touch test_echo.launch

Nice done! Now head over to the Code Editor to make changes to the launch files. Check the image below for how to open the Code Editor.

Open the Code Editor

Locate the launch folder in the code editor: catkin_ws > src > test_rostopic > launch and open the two launch files.

Paste the following lines into the test_pub.launch file. This is equivalent to rostopic pub /test_topic std_msgs/String "data: 'Hello there!'"

<launch>
    <arg name="topic_name" default="/test_topic" />
    <arg name="message" default="Hello there!" />
    <node pkg="rostopic" type="rostopic" name="rostopic_pub_node" output="screen" args="pub $(arg topic_name) std_msgs/String 'data: $(arg message)'" />
</launch>

Next, your test_echo.launch should be made up of the following lines. This is equivalent to rostopic echo /test_topic.

<launch>
    <arg name="topic_name" default="/test_topic" />
    <node pkg="rostopic" type="rostopic" name="rostopic_echo_node" output="screen" args="echo $(arg topic_name)" />
</launch>

Fantastic! Now, let’s compile the package.

cd ~/catkin_ws
catkin_make
source devel/setup.bash

Success! We’ll test it in the next step.

PS: if your code did not compile correctly, please go over the instructions and ensure you have created the files in the exact locations specified.

Step 3: Test the package to demonstrate how to publish and subscribe to a topic from a launch file

Start the test_pub.launch file in the current terminal.

roslaunch test_rostopic test_pub.launch

When done, open another terminal and run the test_echo.launch file.

roslaunch test_rostopic test_echo.launch

Now you should see something similar to the following on the terminal where test_echo.launch is running:

process[rostopic_echo_node-1]: started with pid [xxxx]
data: "Hello there!"
---

Sweet! So we are able to publish with one launch file and echo what we publish in another launch file. And…we are done here, congrats!

Take home: try combining both into a single launch file. After trying, check the solution in this gist.

Step 4: Check your learning

Do you understand how to publish and subscribe to a topic from a launch file? If you don’t know it yet, please go over the post again, more carefully this time.

(Extra) Step 5: Watch the video to understand how to publish and subscribe to a topic from a launch file

Here you go:

Feedback

Did you like this post? Do you have any questions about how to publish and subscribe to a topic from a launch file? Please leave a comment in the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS topics, please let us know in the comments area and we will do a video or post about it.

How to write a ROS publisher and subscriber with a custom message

How to write a ROS publisher and subscriber with a custom message

In this post, you will learn how to write a ROS publisher and subscriber with a custom message, by following the Writing a Publisher and Subscriber with a Custom Message (Python) tutorial. This post is a response to this question posted on Gazebo Answers.

Step 1: Fire up a system with ROS installation

“Hey, do you mean I have to install ROS first?” Absolutely not! Just log in to The Construct to get access to virtual machines pre-installed with ROS.

Once logged in, click on My Rosjects, then Create a New Rosject, supply the information as shown in the image below, and click Create. Then RUN the rosject.

Create a new Rosject

You might also want to try this on a local PC if you have ROS installed. However, please note that we don’t support local PCs and you will have to fix any errors you run into on your own. The rest of the instruction assumes that you are working on The Construct; please adapt them to your local PC and ROS installation.

PS:

  • We are using ROS Kinetic here because that’s what the user on ROS Answers used.
  • We could put the publisher and the subscriber in a single package, but we’ll create two packages as the user did.
  • We could put the custom message in any of the two packages, but we’ll put it in the subscriber, just like the user.

Step 2: Create the first package that contains the subscriber and the custom message

Open Code Editor
Open a web shell

Create the package and custom message

Open a web shell and run the following commands to create the package. When creating a custom message, you need extra dependencies message_generation and message_runtime, in addition to the main ROS API, rospy (for Python) in this case, and the types needed for the message, in this case std_msgs.

cd catkin_ws/src
source /opt/ros/kinetic/setup.bash
catkin_create_pkg quadrotor_receive rospy std_msgs message_generation message_runtime

Next, we create the message file, Person.msg. Run the following command in the same terminal you just used.

cd quadrotor_receive
mkdir msg
cd msg
touch Person.msg

Nice done! Now head over to the Code Editor to make changes to the Person.msg file. Check the image below for how to open the Code Editor.

Open the Code Editor

Locate the file in the code editor: catkin_ws > src > quadrotor_receive > msg > Person.msg and paste in the following code.

string name
int32 age

Edit package files

Replace CMakeLists.txt with the one below (where the following changes have been made).

  • Removed message_runtime from the find_package function.
  • Uncommented the add_message_files function, and added Person.msg below FILES.
  • Uncommented the generate_messages function, and left it intact.
  • Uncommented the line CATKIN_DEPENDS in the catkin_package function.
  • Removed all commented lines to keep the file short and sweet.
cmake_minimum_required(VERSION 3.0.2)
project(quadrotor_receive)

## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
  message_runtime
  rospy
  std_msgs
)

## Generate messages in the 'msg' folder
add_message_files(
  FILES
  Person.msg
  # Message2.msg
)

## Generate added messages and services with any dependencies listed here
generate_messages(
  DEPENDENCIES
  std_msgs
)

###################################
## catkin specific configuration ##
###################################
## The catkin_package macro generates cmake config files for your package
## Declare things to be passed to dependent projects
## INCLUDE_DIRS: uncomment this if your package contains header files
## LIBRARIES: libraries you create in this project that dependent projects also need
## CATKIN_DEPENDS: catkin_packages dependent projects also need
## DEPENDS: system dependencies of this project that dependent projects also need
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES quadrotor_receive
CATKIN_DEPENDS message_generation message_runtime rospy std_msgs
#  DEPENDS system_lib
)

###########
## Build ##
###########

## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
# include
  ${catkin_INCLUDE_DIRS}
)

Next, add a single line to package.xml so that it looks like the one below. Can you identify the line that was added?

<?xml version="1.0"?>
<package format="2">
  <name>quadrotor_receive</name>
  <version>0.0.0</version>
  <description>The quadrotor_receive package</description>

  <!-- One maintainer tag required, multiple allowed, one person per tag -->
  <!-- Example:  -->
  <!-- <maintainer email="jane.doe@example.com">Jane Doe</maintainer> -->
  <maintainer email="user@todo.todo">user</maintainer>


  <!-- One license tag required, multiple allowed, one license per tag -->
  <!-- Commonly used license strings: -->
  <!--   BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3 -->
  <license>TODO</license>

  <!--   <doc_depend>doxygen</doc_depend> -->
  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>message_generation</build_depend>
  <build_depend>rospy</build_depend>
  <build_depend>std_msgs</build_depend>
  <build_export_depend>rospy</build_export_depend>
  <build_export_depend>std_msgs</build_export_depend>
  <build_export_depend>message_generation</build_export_depend>
  <exec_depend>message_runtime</exec_depend>
  <exec_depend>rospy</exec_depend>
  <exec_depend>std_msgs</exec_depend>


  <!-- The export tag contains other, unspecified, tags -->
  <export>
    <!-- Other tools can request additional information be placed here -->

  </export>
</package>

Finally, let’s create the receiver Python node, just as the ROS Answers user created it. Run the following in the same terminal as before. Notice that we’re making the Python script executable.

cd ~/catkin_ws/src/quadrotor_receive/src
touch custom_listener.py
chmod +x custom_listener.py

Open custom_listener.py in the IDE and paste in the following code.

#!/usr/bin/env python

import rospy
from quadrotor_receive.msg import Person 

def callback(data):
    rospy.loginfo("%s is age: %d" % (data.name, data.age))

def listener():

    rospy.init_node('custom_listener', anonymous=True)
    rospy.Subscriber("custom_chatter", Person , callback)

    # spin() simply keeps python from exiting until this node is stopped
    rospy.spin()

if __name__=='__main__':
    listener()

So much for all the hard work – let’s see if it works. Time to compile the code. In the same web shell, run the following commands:

Compile and test

cd ~/catkin_ws
catkin_make
source devel/setup.bash

Success! We have successfully created a package with a custom message. Let’s see if we can find the message. You should get an output similar to the one below.

user:~/catkin_ws$ rosmsg show quadrotor_receive/Person
string name
int32 age

Let’s also test receiver node at this point. Though there’s nothing to receive yet, it should not throw any error. First, we need to start roscore.

roscore

Then try to run your subscriber in another terminal. Leave it running!

rosrun quadrotor_receive custom_listener.py

PS: if your code did not compile correctly, please go over the instructions and ensure you have created the files in the exact locations specified.

Step 3: Create the second package that contains the publisher

Create a new folder package. This should be easy-peasy, as most of the work has been done in the previous step. Do the following in a new terminal.

cd ~/catkin_ws/src/
catkin_create_pkg transmit_thrust rospy

Next, create the custom_talker.py node.

cd transmit_thrust/src
touch custom_talker.py
chmod +x custom_talker.py

Open up custom_talker.py in the IDE and paste in the following.

#!/usr/bin/env python
import rospy
from quadrotor_receive.msg import Person

def talker():
    pub = rospy.Publisher('custom_chatter', Person)
    rospy.init_node('custom_talker', anonymous=True)
    r = rospy.Rate(10) #10hz
    msg = Person()
    msg.name = "ROS User"
    msg.age = 4

    while not rospy.is_shutdown():
        rospy.loginfo(msg)
        pub.publish(msg)
        r.sleep()

if __name__ == '__main__':
    try:
        talker()
    except rospy.ROSInterruptException: pass

Please note that:

  • We are importing the custom message from the quadrotor_receive package.
  • We are creating a publisher for the same topic we created a subscriber for in the quadrotor_receive package.

Nothing more is needed. Let’s compile our new package.

cd ~/catkin_ws
catkin_make
source devel/setup.bash

That done, let’s start the custom talker node.

rosrun transmit_thrust custom_talker.py

You should see some output similar to these on the terminal.

[INFO] [1680728823.955004]: name: "ROS User"
age: 4
[INFO] [1680728824.055152]: name: "ROS User"

And…on the terminal where you have the listener tunning, you should see something similar to:

[INFO] [1680728823.956347]: ROS User is age: 4
[INFO] [1680728824.056833]: ROS User is age: 4

And we are done here!

Step 4: Check your learning

Do you understand how to write a ROS publisher and subscriber with a custom message? If you don’t know it yet, please go over the post again, more carefully this time.

(Extra) Step 5: Watch the video to understand how to write a ROS publisher and subscriber with a custom message

Here you go:

Feedback

Did you like this post? Do you have any questions about how to write a ROS publisher and subscriber with a custom message? Please leave a comment in the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS topics, please let us know in the comments area and we will do a video or post about it.

Learn how to enable live parameter updates in ROS 2 (C++)

Learn how to enable live parameter updates in ROS 2 (C++)

What we are going to learn

  1. The limitations of having a minimal ROS2 parameter implementation
  2. How to add a parameter callback method

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/l/53e75e28/ 
  2. The Construct: https://app.theconstructsim.com/
  3. Original ROS2 Documentation: Monitoring for parameter changes (C++)
  4. ROS2 Courses –▸
    1. ROS2 Basics in 5 Days Humble (Python): https://app.theconstructsim.com/Course/132
    2. ROS2 Basics in 5 Days Humble (C++): https://app.theconstructsim.com/Course/133

Overview

ROS2 parameters are great for configurable nodes that you can adapt to your robot configuration simply by changing a configuration file or a launch file. However, if we just implemented the basics you will have to re-run your node each time you change a parameter. You can’t change the parameter on-the-fly and have it updated in the robot. But by adding a callback function that updates the variables in our code, it is possible to do a live parameter update while a program is running, removing the need for a tedious node restart. Learn how to do it in this post.

ROS Inside!

ROS Inside

ROS Inside

Before anything else, if you want to use the logo above on your own robot or computer, feel free to download it and attach it to your robot. It is really free. Find it in the link below:

ROS Inside logo

Opening the rosject

In order to follow this tutorial, we need to have ROS2 installed in our system, and ideally a ros2_ws (ROS2 Workspace). To make your life easier, we have already prepared a rosject with a simulation for that: https://app.theconstructsim.com/l/53e75e28/.

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 how to enable live parameter updates (C++) – Run rosject (example of the RUN button)

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

Starting the simulation

After having opened the rosject, let’s start a simulation. For that, let’s open a terminal by clicking the Open a new terminal button.

Open a new Terminal

Open a new Terminal

Once inside the terminal, let’s run the commands below:

cd ~/ros2_ws
colcon build --packages-select rule_based_obstacle_avoidance

 

Now that our workspace is built. let’s source it so that ROS can find our packages:

source install/setup.bash

 

After the workspace has been sourced, we can launch our simulation:

ros2 launch rule_based_obstacle_avoidance simulation.launch.py

If everything went well, you should have a simulation loaded and opened automatically in a few seconds. The simulation will open from a top view, but you can use the mouse to move the simulation to a different perspective.

 How to enable live parameter updates (C++)

How to enable live parameter updates (C++)

 

Understanding the problem

Below is an example node in which we have only implemented the barebone basics of parameters in ROS2.

Let’s see it in action and see how it behaves, especially when we update its parameter values.

In a second terminal, let’s run the node for Obstacle Avoidance.

source ~/ros2_ws/install/setup.bash

ros2 run rule_based_obstacle_avoidance obstacle_avoidance

If you watch the simulation for a while, you will see that when the robot detects the wall, it rotates and moves forward until it detects another wall, and repeats the process.

The node name is obstacle_avoidance_node (you can check it in a third terminal by running: ros2 node list)

Now, let’s list the parameters of the node in a third terminal:

ros2 param list /obstacle_avoidance_node

We should see the following output:

  angular_z_velocity
  linear_x_velocity
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  safety_distance
  use_sim_time

 

Now, still in the third terminal, let’s check the value of the safety_distance parameter:

ros2 param get /obstacle_avoidance_node safety_distance

The output we should have got should be the following:

Double value is: 1.5

 

Now, let’s set the parameter to a new value:

ros2 param set /obstacle_avoidance_node safety_distance 1.0

The expected output is:

Set parameter successful

 

Ok, so far so good. But with the new value, we expect the robot to get closer to the wall before turning around because now the safe distance was set from 1.5 meters to 1.0. The problem is that the robot is not considering the new value that we just set.

We can follow the same idea to try to make the robot move faster. Let’s check the current velocity of the robot:

ros2 param get /obstacle_avoidance_node linear_x_velocity

The output we should have got should be the following:

Double value is: 0.2

 

If we increase the speed:

ros2 param set /obstacle_avoidance_node linear_x_velocity 0.5

The expected output is:

Set parameter successful

The parameter was reported as successfully set, yet the robot does not move faster, because it still uses the value loaded when the node started.

In the current code, parameter values are fixed. As such, every time a parameter value changes, the parameter value in the code stays the same even though you may have expected it to update based on the latest value set.

In order to solve this, we must add a parameter callback function to your code so that the variable in the code gets the freshest data.

Before moving to the next section, please kill the simulation and all nodes running by pressing Ctrl+C on all terminals.

 

Solution: Add a parameter callback method

Alright, have you closed all programs by pressing CTRL+C on all terminals?

If so, let’s move forward, then.

We need to modify the following file:

  • ros2_ws/src/rule_based_obstacle_avoidance/src/obstacle_avoidance.cpp

Let’s open that file using the IDE (Code Editor):

Open the IDE - Code Editor

Open the IDE – Code Editor

 

Once the Code Editor is open, you should be able to see the ros2_ws folder (ROS2 workspace) and navigate to the file we mentioned above:

  • ros2_ws/src/rule_based_obstacle_avoidance/src/obstacle_avoidance.cpp

If you check around line 45 on that file, you will find the “private:” section, where we define the private variables of our class, something like the following:

private:
  rclcpp::TimerBase::SharedPtr timer_;

  double linear_x_velocity_;
  double angular_z_velocity_;
  
   // ...

 

Let’s modify that part, in order to add two variables below right after the definition of “timer_”, so that our code looks like:

private:
  rclcpp::TimerBase::SharedPtr timer_;

  std::shared_ptr<rclcpp::ParameterEventHandler> param_subscriber_;
  std::shared_ptr<rclcpp::ParameterCallbackHandle> cb_handle_; 

  double linear_x_velocity_;
  double angular_z_velocity_;
  
   // ...

 

Now, above that “private:” section, around line 38, let’s add the following code to instantiate a ParameterEventHandler, providing the current ROS node to use to create the required subscriptions:

param_subscriber_ = std::make_shared<rclcpp::ParameterEventHandler>(this);

Below the param_subscriber_ we have to set a callback method, in this case, a lambda function:

// Set a callback for this node's parameter, "linear_x_velocity"
    auto callback_linear_x = [this](const rclcpp::Parameter &p) {
      RCLCPP_INFO(this->get_logger(),
                  "callback_linear_x: Received an update to parameter \"%s\" "
                  "of type %s: \"%f\"",
                  p.get_name().c_str(), p.get_type_name().c_str(),
                  p.as_double());
      linear_x_velocity_ = p.as_double();
    };

 

Then we set “callback_linear_x” as the callback to invoke whenever linear_x_velocity is updated. We store the handle that is returned by “add_parameter_callback“; otherwise, the callback will not be properly registered.

cb_handle_ = param_subscriber_->add_parameter_callback("linear_x_velocity", callback_linear_x);

 

After those changes, the “public:” section of our class should look like this:

class ObstacleAvoidance : public rclcpp::Node {
public:
  ObstacleAvoidance() : Node("obstacle_avoidance_node") {

   auto default_qos = rclcpp::QoS(rclcpp::SystemDefaultsQoS());
    subscription_ = this->create_subscription<sensor_msgs::msg::LaserScan>(
        "scan", default_qos,
        std::bind(&ObstacleAvoidance::topic_callback, this, _1));
    vel_msg_publisher_ =
        this->create_publisher<geometry_msgs::msg::Twist>("cmd_vel", 10);

    // declare parameters and set default values
    this->declare_parameter("linear_x_velocity", 0.2);
    this->declare_parameter("angular_z_velocity", 0.2);
    this->declare_parameter("safety_distance", 1.5);
    // get parameters values
    this->get_parameter("linear_x_velocity", linear_x_velocity_);
    this->get_parameter("angular_z_velocity", angular_z_velocity_);
    this->get_parameter("safety_distance", d);

   param_subscriber_ = std::make_shared<rclcpp::ParameterEventHandler>(this);

    // Set a callback for this node's parameter, "linear_x_velocity"
    auto callback_linear_x = [this](const rclcpp::Parameter &p) {
      RCLCPP_INFO(this->get_logger(),
                  "callback_linear_x: Received an update to parameter \"%s\" "
                  "of type %s: \"%f\"",
                  p.get_name().c_str(), p.get_type_name().c_str(),
                  p.as_double());
      linear_x_velocity_ = p.as_double();
    };

    cb_handle_ = param_subscriber_->add_parameter_callback("linear_x_velocity",

    RCLCPP_INFO(this->get_logger(), "Obstacle avoidance running");

    timer_ = this->create_wall_timer(
        1000ms, std::bind(&ObstacleAvoidance::timerCallback, this));
  }

  ~ObstacleAvoidance() {}

private:
    // ...

 

Now that everything is in place, let’s build our package again and source it, using the first terminal:

cd ~/ros2_ws/
colcon build --packages-select rule_based_obstacle_avoidance
source install/setup.bash

The package should have been built with no problems:

user:~/ros2_ws$ colcon build --packages-select rule_based_obstacle_avoidance
Starting >>> rule_based_obstacle_avoidance
Finished <<< rule_based_obstacle_avoidance [27.3s]

Summary: 1 package finished [27.7s]

 

Now that our package has been rebuilt and sourced, let’s launch the simulation again:

ros2 launch neo_simulation2 simulation.launch.py

The simulation should have been opened just like before, but now we will see the parameters affecting the simulation in “real-time”.

Before changing the parameters, let’s also launch the Obstacle Avoidance node, just like before, using the second terminal:

ros2 run rule_based_obstacle_avoidance obstacle_avoidance

You should see the robot approaching the wall, and turning around when getting close to it.

Changing the x velocity using ROS 2 Parameters

Ok, now that the robot is moving, let’s retrieve again the current value of the linear x velocity using the third terminal:

ros2 param get /obstacle_avoidance_node linear_x_velocity

Just like before, the expected output is:

Double value is: 0.2

 

Now let’s change that value:

ros2 param set /obstacle_avoidance_node  linear_x_velocity  1.0

We expect a successful output:

Set parameter successful

 

If you look at the simulation now, you should see that when the robot is moving forward (not turning around), it moves really faster. So, as we can see, we are now able to make ROS2 Parameters be reflected “instantly”.

This opens up really many different possibilities.

We hope this post was really helpful to you. If you want a live version of this post with more details, 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:

113. Awesome robotics company with no ROS

113. Awesome robotics company with no ROS

Related links

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Subscribe to the podcast using any of the following methods

Or watch the video interview

 

How to create ros2 XML launch files

How to create ros2 XML launch files

In this post, you will learn how to create ros2 XML launch files. You’ll discover how ros2 XML launch files are similar to and different from their ros1 counterparts.

Step 1: Get a Copy of the ROS package containing the code used in the post

Click here to copy the project. It would be copied to your cloud account at The Construct. That done, open the project using the Run button. This might take a few moments, please be patient.

Run rosject

PS: If you don’t have an account on the The Construct, you would need to create one. Once you create an account or log in, you will be able to follow the steps to read and write parameters in ros1 and ros2.

You might also want to try this on a local PC if you have ROS installed. In that case you need to read on and duplicate the source code of the package in your own local workspace. However, please note that we cannot support local PCs and you will have to fix any errors you run into on your own.

Step 2: Explore the source code using the IDE

Open Code Editor

Open the IDE by clicking on the icon as shown above. You should now see something similar to the image below:

ros2 XML launch file

The main file we will work with in this post is the one highlighted in red in the image above:

  1. ~/turtlebot3_ws/src/turtlebot3_simulations/turtlebot3_gazebo/launch/turtlebot3_world_action_server.launch.xml

Double-click on the file in the IDE to open and study the contents. We will discuss this file in the following steps.

Step 3: Understand how to create ros2 XML launch files

Wait first! I thought ros2 launch files are only written in Python! Yes, Python is one of the options when writing ros2 launch files; we can also use XML files, especially if we are writing simple launch files that do not need to leverage Python’s powerful API.

Let’s have a look at the XML file:

<launch>
    <arg name="use_sim_time" default="true"/>
    <include file="$(find-pkg-share turtlebot3_gazebo)/launch/turtlebot3_world.launch.py">
        <arg name="use_sim_time" value="$(var use_sim_time)"/>
    </include>
    <node pkg="patrol_action_server" exec="patrol_action_server_exe" name="patrol_action_server">
    </node>
</launch>

What exactly is happening in the launch file?

  • On line 2, we define an argument (variable) use_sim_time with a default value of true.
  • On line 3, we include another launch file, turtlebot3_world.launch.py for launching the TurtleBot3 world. The launch file can be found in the turtlebot3_gazebo package.
  • On line 4, we pass a required argument use_sim_time to the included launch file, assigning it the value of the use_sim_time defined on line 2.
  • On line 6, we define a node to be started by the launch file. This node can be found in the package patrol_action_server, the node executable is patrol_action_server_exe and the name of the node would appear as patrol_action_server.

In short, this launch file launches the TurtleBot3 world and starts the Patrol action server. The same launch file can be written in Python, but this XML looks simpler and easier to understand. And it does all we want it to do, and we can even include Python-based launch files!

Step 4: Understand how ros2 XML launch files are similar to/different from ros1 XML launch files

If you are familiar with ros1 launch files, you should already notice some similarities:

  • The same <launch> tag.
  • Similar <arg> tag.
  • Familiar <include> tag.
  • Similar <node> tag.

Now if we were writing the same launch file in ros1, it would be something like this (PS: we can’t include ros2 launch file in a ros1 launch file in reality):

<launch>
    <arg name="use_sim_time" default="true"/>
    <include file="$(find turtlebot3_gazebo)/launch/turtlebot3_world.launch.py">
        <arg name="use_sim_time" value="$(arg use_sim_time)"/>
    </include>
    <node pkg="patrol_action_server" type="patrol_action_server_exe" name="patrol_action_server">
    </node>
</launch>

Can you spot the differences? Compare what you find with the list of differences below:

  • In file attribute of the the <include> tag on line 3, ros2 uses find-pkg-share while ros1 uses find.
  • In the value attribute of the <arg> tag on line 4, ros2 uses var to get the value of the argument while ros1 uses arg.
  • In the <node> tag, ros2 uses the exec attribute to specify the executable file to run while ros1 uses the type attribute.

Interesting, isn’t it? Now let’s see if the ros2 XML launch file works.

Step 5: Launch the ros2 XML launch file!

It’s time to see the ros2 XML launch file in action! Open a web shell and run the following commands:

cd ~/turtlebot3_ws
source install/setup.bash
export TURTLEBOT3_MODEL=burger
ros2 launch turtlebot3_gazebo turtlebot3_world_action_server.launch.xml

You should see the following simulation come up.

TurtleBot3 simulation
TurtleBot3 Simulation

Next, run the following command in the web another web shell

user:~$ ros2 node list

You should see /patrol_action_server (the node specified in the ros2 XML launch file) listed as one of the nodes.

Finally, let’s call the action server and see what happens to the simulation. Run the following in the last web shell used:

ros2 action send_goal --feedback /patrol custom_interfaces/action/Patrol radius:\ 0.2\

And that’s it – you have your ros2 XML launch file working!

Step 6: Check your learning

  1. Do you understand how to create ros2 XML launch files?
  2. Do you remember the similarities and differences in ros1 and ros2 XML launch files?

If you didn’t get any of the points above, please go over the post again, more carefully this time.

(Extra) Step 7: Watch the video to understand how to create ros2 XML launch files

Here you go:

https://www.youtube.com/video/yyUwKcWQ4DU

Feedback

Did you like this post? Do you have any questions about how to read and write parameters in ros1 and ros2? Whatever the case, please leave a comment on the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS topics, please let us know in the comments area and we will do a video or post about it.

How to read and write parameters in ros1 and ros2

How to read and write parameters in ros1 and ros2

In this post, you will learn how to read and write parameters in ros1 and ros2, using C++ nodes. You will see the slight differences in the ros1 and ros2 nodes and parameter files.

Step 1: Get a Copy of the ROS package containing the code used in the post

Click here to copy the project. It would be copied to your cloud account at The Construct. That done, open the project using the Run button. This might take a few moments, please be patient.

Run rosject

PS: If you don’t have an account on the The Construct, you would need to create one. Once you create an account or log in, you will be able to follow the steps to read and write parameters in ros1 and ros2.

You might also want to try this on a local PC if you have ROS installed. In that case you need to read on and duplicate the source code of the package in your own local workspace. However, please note that we cannot support local PCs and you will have to fix any errors you run into on your own.

Step 2: Explore the source code using the IDE

Open Code Editor

Open the IDE by clicking on the icon as shown above. You should now see something similar to the image below:

Yaml Params

The six main files we will work with in this post are highlighted in red in the image above. These files are:

ROS1:

  1. catkin_ws/src/yaml_parameters_ros1/config/params_demo_ros1.yaml
  2. catkin_ws/src/yaml_parameters_ros1/launch/ros1_params_cpp_demo.launch
  3. catkin_ws/src/yaml_parameters_ros1/src/yaml_params_ros1.cpp

ROS2:

  1. ros2_ws/src/yaml_parameters/config/params_demo_ros2.yaml
  2. ros2_ws/src/yaml_parameters/launch/yaml_parameters.launch.py
  3. ros2_ws/src/yaml_parameters/src/yaml_params_ros2.cpp

Double-click on each of the files in the IDE to open and study the contents. We will examine some of these files in the following steps.

Step 3: Understand how to read and write (load) parameters in ROS1

Now it’s time to see how to read and write parameters in ros1, working in the ros1 workspace.

Open a web shell and run the following commands:

Open webshell
cd ~/catkin_ws
source /opt/ros/noetic/setup.bash
source devel/setup.bash
roscore

The code block above changes to the ros1 workspace, sources it, and then starts the roscore (needed for ros1). Now let’s see a list of the current ros1 parameters available. Open another web shell and type the following:

user:~/catkin_ws$ rosparam list

Your output should be similar to the following.

/rosdistro
/roslaunch/uris/host_1_xterm__41731
/rosversion
/run_id

On the same web shell, run the following command to print out the ros parameters:

rosrun yaml_parameters_ros1 yaml_parameters_ros1_node

Your output should be the following:

[ INFO] [1657670307.494093666]: Integer parameter: 1
[ INFO] [1657670307.495689754]: Double parameter: 0.100000
[ INFO] [1657670307.495741256]: String parameter: default
[ INFO] [1657670307.495773876]: Nested integer parameter: 1
[ INFO] [1657670307.495797588]: Nested string parameter: default
[ INFO] [1657670307.495819891]: Boolean parameter: 0

The logic that produced the output above in contained in the catkin_ws/src/yaml_parameters_ros1/src/yaml_params_ros1.cpp file. Let’s see its content.

#include "ros/ros.h"
#include <string>

int main(int argc, char **argv) {
  ros::init(argc, argv, "my_node");

  ros::NodeHandle nh;

  int param0 = 1;
  double param1 = 0.1;
  std::string param2 = "default";
  int p3weight = 1;
  std::string p3name = "default";
  bool param4 = false;
  std::vector<bool> param5{false, false, false};
  std::vector<int> param6{1, 1, 1};
  std::vector<double> param7{0.1, 0.1, 0.1};
  std::vector<std::string> param8{"default", "default", "default"};

  nh.getParam("param0", param0);
  nh.getParam("param1", param1);
  nh.getParam("param2", param2);
  nh.getParam("param3/weight", p3weight);
  nh.getParam("param3/name", p3name);
  nh.getParam("param4", param4);
  nh.getParam("param5", param5);
  nh.getParam("param6", param6);
  nh.getParam("param7", param7);
  nh.getParam("param8", param8);

  ROS_INFO("Integer parameter: %d", param0);
  ROS_INFO("Double parameter: %f", param1);
  ROS_INFO("String parameter: %s", param2.c_str());
  ROS_INFO("Nested integer parameter: %d", p3weight);
  ROS_INFO("Nested string parameter: %s", p3name.c_str());
  ROS_INFO("Boolean parameter: %d", param4);
  ROS_INFO("Boolean vector parameter [0]: %d", static_cast<int>(param5[0]));
  ROS_INFO("Integer vector parameter [0]: %d", static_cast<int>(param6[0]));
  ROS_INFO("Double vector parameter [0]: %f", static_cast<double>(param7[0]));
  ROS_INFO("String vector parameter [0]: %s", param8[0].c_str());

  return 0;
}

But wait…are we getting the parameters in the YAML file (catkin_ws/src/yaml_parameters_ros1/config/params_demo_ros1.yaml) and their correct values? Let’s see what’s the in there!

# interger array
param0: 2
# double
param1: 0.2
# string
param2: "R2-D2"
# nested parameters
param3: 
  weight: 2
  name: "wood"
# boolean
param4: true
# boolean array
param5: [true, true, true]
# interger array
param6: [5,6,7,8]
# double array
param7: [0.2, 0.3, 0.4, 0.5]
# string array
param8: ["Bedroom", "Bathroom", "Laundry room", "Kitchen", "Living room"]

Gosh, we are not getting these parameters nor their values, and you probably know why! So far we have been reading the parameters but have loaded them. Now let’s get that done: enter the launch file catkin_ws/src/yaml_parameters_ros1/launch/ros1_params_cpp_demo.launch.

<launch>
    <rosparam file="$(find yaml_parameters_ros1)/config/params_demo_ros1.yaml" />
</launch>

This file, when launched, loads the YAML parameter file. Let’s see that in action. Run the following command in the open web shell:

roslaunch yaml_parameters_ros1 ros1_params_cpp_demo.launch

You should get something like the following:

... logging to /home/user/.ros/log/c5f51462-023c-11ed-bb5c-0242ac180007/roslaunch-1_xterm-13868.log
Checking log directory for disk usage. This may take a while.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://1_xterm:45437/

SUMMARY
========

PARAMETERS
 * /param0: 2
 * /param1: 0.2
 * /param2: R2-D2
 * /param3/name: wood
 * /param3/weight: 2
 * /param4: True
 * /param5: [True, True, True]
 * /param6: [5, 6, 7, 8]
 * /param7: [0.2, 0.3, 0.4, 0.5]
 * /param8: ['Bedroom', 'Bath...
 * /rosdistro: noetic
 * /rosversion: 1.15.11

NODES

ROS_MASTER_URI=http://1_xterm:11311

No processes to monitor
shutting down processing monitor...
... shutting down processing monitor complete

Well we have some fancy output there, but what has changed? Let’s see that by running two previous commands:

rosparam list
rosrun yaml_parameters_ros1 yaml_parameters_ros1_node

Your output should now look like this:

user:~/catkin_ws$ rosparam list
/param0
/param1
/param2
/param3/name
/param3/weight
/param4
/param5
/param6
/param7
/param8
/rosdistro
/roslaunch/uris/host_1_xterm__41731
/roslaunch/uris/host_1_xterm__45437
/rosversion
/run_id
user:~/catkin_ws$ rosrun yaml_parameters_ros1 yaml_parameters_ros1_node
[ INFO] [1657671513.558912623]: Integer parameter: 2
[ INFO] [1657671513.560352415]: Double parameter: 0.200000
[ INFO] [1657671513.560386238]: String parameter: R2-D2
[ INFO] [1657671513.560404606]: Nested integer parameter: 2
[ INFO] [1657671513.560420530]: Nested string parameter: wood
[ INFO] [1657671513.560435749]: Boolean parameter: 1
[ INFO] [1657671513.560450679]: Boolean vector parameter [0]: 1
[ INFO] [1657671513.560465819]: Integer vector parameter [0]: 5
[ INFO] [1657671513.560484622]: Double vector parameter [0]: 0.200000
[ INFO] [1657671513.560497372]: String vector parameter [0]: Bedroom

Can you spot the differences between the formal and the latter outputs of these commands? Sure you can! So, well, that’s how to read and load parameter in ros1!

Step 4: Understand how to read and write (load) parameters in ROS2

Now let’s change to the ros2 workspace.

cd ~/ros2_ws
source /opt/ros/foxy/setup.bash
source install/setup.bash

In ros2 we need to have a node running before we can check for parameters, because there is no parameter server in ros2. Let’s try running the node then. The logic behind this node is contained in the ros2_ws/src/yaml_parameters/src/yaml_params_ros2.cpp file:

#include <rclcpp/rclcpp.hpp>

class MainNode : public rclcpp::Node {
public:
  MainNode() : rclcpp::Node("node", rclcpp::NodeOptions()) {

    // example: declare parameters, default value given
    declare_parameter("param0", 1);
    declare_parameter("param1", 0.1);
    declare_parameter("param2", "default");
    declare_parameter("param3.weight", 1);
    declare_parameter("param3.name", "default");
    declare_parameter("param4", false);
    // example: declare a variable when declaring a parameter
    declare_parameter("param5", std::vector<bool>(3, false));
    declare_parameter("param6", std::vector<int64_t>(4, 1));
    declare_parameter("param7", std::vector<double>(4, 0.1));
    declare_parameter("param8", std::vector<std::string>(5, "default"));

    // Get parameter values one by one
    auto p0 = get_parameter("param0").as_int();
    auto p1 = get_parameter("param1").as_double();
    auto p2 = get_parameter("param2").as_string();
    auto p3weight = get_parameter("param3.weight").as_int();
    auto p3name = get_parameter("param3.name").as_string();
    auto p4 = get_parameter("param4").as_bool();
    auto p5 = get_parameter("param5").as_bool_array();
    auto p6 = get_parameter("param6").as_integer_array();
    auto p7 = get_parameter("param7").as_double_array();
    auto p8 = get_parameter("param8").as_string_array();

    // Print parameters
    RCLCPP_INFO(get_logger(), "Integer parameter: %ld", p0);
    RCLCPP_INFO(get_logger(), "Double parameter: %f", p1);
    RCLCPP_INFO(get_logger(), "String parameter: %s", p2.c_str());
    RCLCPP_INFO(get_logger(), "Nested integer parameter: %ld", p3weight);
    RCLCPP_INFO(get_logger(), "Nested string parameter: %s", p3name.c_str());
    RCLCPP_INFO(get_logger(), "Boolean parameter: %d", p4);
    RCLCPP_INFO(get_logger(), "Boolean vector parameter [0]: %d",
                static_cast<int>(p5[0]));
    RCLCPP_INFO(get_logger(), "Integer vector parameter [0]: %d",
                static_cast<int>(p6[0]));
    RCLCPP_INFO(get_logger(), "Double vector parameter [0]: %f",
                static_cast<double>(p7[0]));
    RCLCPP_INFO(get_logger(), "String vector parameter [0]: %s", p8[0].c_str());
  }
};

int main(int argc, char **argv) {
  rclcpp::init(argc, argv);

  rclcpp::spin(std::make_shared<MainNode>());
  rclcpp::shutdown();
  return 0;
}

Go for it: run the node:

ros2 run yaml_parameters main_node

The output will be something like:

INFO] [1657672243.344585940] [node]: Integer parameter: 1
[INFO] [1657672243.344674910] [node]: Double parameter: 0.100000
[INFO] [1657672243.344704157] [node]: String parameter: default
[INFO] [1657672243.344720646] [node]: Nested integer parameter: 1
[INFO] [1657672243.344730700] [node]: Nested string parameter: default
[INFO] [1657672243.344745410] [node]: Boolean parameter: 0
[INFO] [1657672243.344755485] [node]: Boolean vector parameter [0]: 0
[INFO] [1657672243.344769947] [node]: Integer vector parameter [0]: 1
[INFO] [1657672243.344780509] [node]: Double vector parameter [0]: 0.100000
[INFO] [1657672243.344795534] [node]: String vector parameter [0]: default

Next, let’s get the list of ROS2 parameters:

user:~$ ros2 param list
/node:
  param0
  param1
  param2
  param3.name
  param3.weight
  param4
  param5
  param6
  param7
  param8
  use_sim_time

Buff…are we getting the values of the parameters in the YAML file ros2_ws/src/yaml_parameters/config/params_demo_ros2.yaml?

# if a namespace is specified
# ns_name: 
# node name
parameter_types_example: 
  ros__parameters:
    # int
    param0: 2
    # double
    param1: 0.2
    # string
    param2: "R2-D2"
    # nested parameters
    param3: 
      weight: 2
      name: "wood"
    # boolean
    param4: true
    # boolean array
    param5: [true, true, true]
    # interger array
    param6: [5,6,7,8]
    # double array
    param7: [0.2, 0.3, 0.4, 0.5]
    # string array
    param8: ["Bedroom", "Bathroom", "Laundry room", "Kitchen", "Living room"]

No, we are not :(. But not to worry, the launch file ros2_ws/src/yaml_parameters/launch/yaml_parameters.launch.py comes to the rescue! Let’s examine its content.

#!/usr/bin/env python3

import os
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory


def generate_launch_description():
    return LaunchDescription([
        Node(
            package='yaml_parameters',
            executable='main_node',
            name='parameter_types_example',
            parameters=[os.path.join(
                get_package_share_directory('yaml_parameters'),
                'config', 'params_demo_ros2.yaml')],
            output='screen'),
    ])

Oh my, it’s a Python! Let’s set it loose and see what happens! Stop the currently running program with Ctrl + C and run the following in its place and check that your output is similar.

user:~/ros2_ws$ ros2 launch yaml_parameters yaml_parameters.launch.py
[INFO] [launch]: All log files can be found below /home/user/.ros/log/2022-07-13-00-40-35-558545-1_xterm-18432
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [main_node-1]: process started with pid [18434]
[main_node-1] [INFO] [1657672835.736702673] [parameter_types_example]: Integer parameter: 2
[main_node-1] [INFO] [1657672835.736792219] [parameter_types_example]: Double parameter: 0.200000
[main_node-1] [INFO] [1657672835.736810397] [parameter_types_example]: String parameter: R2-D2
[main_node-1] [INFO] [1657672835.736842588] [parameter_types_example]: Nested integer parameter: 2
[main_node-1] [INFO] [1657672835.736847188] [parameter_types_example]: Nested string parameter: wood
[main_node-1] [INFO] [1657672835.736855303] [parameter_types_example]: Boolean parameter: 1
[main_node-1] [INFO] [1657672835.736863129] [parameter_types_example]: Boolean vector parameter [0]: 1
[main_node-1] [INFO] [1657672835.736870819] [parameter_types_example]: Integer vector parameter [0]: 5
[main_node-1] [INFO] [1657672835.736878422] [parameter_types_example]: Double vector parameter [0]: 0.200000
[main_node-1] [INFO] [1657672835.736887246] [parameter_types_example]: String vector parameter [0]: Bedroom

The launch file simply loads the parameters in the YAML file and also runs the node we run earlier.

Well, that’s it!

Step 5: Check your learning

  1. Do you understand how to read and write parameters in ros1 and ros2, using C++ nodes?
  2. Did you notice the slight differences in the format of the YAML files for ros1 and ros2?
  3. Did you notice that you the ros2 parameters are tied to specific nodes vs existing in a parameter server in ros1?

If you didn’t get any of the points above, please go over the post again, more carefully this time.

Extra Step: Watch the video to understand how to read and write parameters in ros1 and ros2

Here you go:

Feedback

Did you like this post? Do you have any questions about how to read and write parameters in ros1 and ros2? Whatever the case, please leave a comment on the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS topics, please let us know in the comments area and we will do a video or post about it.

Pin It on Pinterest