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:

How to use persistent parameters in ROS2

How to use persistent parameters in ROS2

What we are going to learn

  1. What persistent parameters are
  2. How to install the ros2_persist_parameter_server package
  3. How to run the ros2_persist_parameter_server package
  4. How to set and get persistent parameters from the command line
  5. How to get persistent parameters programmatically

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/l/51ef510a/
  2. The Construct: https://app.theconstructsim.com/
  3. A repository from https://github.com/fujitatomoya
  4. 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

Overview

In this post, we will have a look at the ros2_persist_parameter_server package by the GitHub user fujitatomoya. With this package, you can have parameters saved to the disk. This way they are not lost when the machine is powered off or reset. The package we are going to use can be found at:

ROS Inside!

ROS Inside

ROS Inside

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

ROS Inside logo

How to set and get persistent parameters in ROS2

You might already know that in ROS 2, all parameters are node-specific. A far less discussed aspect of parameters is their non-persistent nature. Non-persistent parameters are those parameters whose values are not saved to the disk. They are lost when the machine is powered off or reset.

In this post, we will have a look at the ros2_persist_parameter_server package aforementioned. We would like to thank Tomoya Fujita for making it Open Source.

 

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 already prepared a rosject with a simulation for that: https://app.theconstructsim.com/l/51ef510a/.

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 Topics vs Service vs Action – 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.

Get the ros2_persist_parameter_server package and compile it

After having opened the rosject, let’s close the ros2_persist_parameter_server package. 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/src
git clone https://github.com/fujitatomoya/ros2_persist_parameter_server.git
cd ~/ros2_ws/

colcon build
source install/setup.bash

 

Running the “parameter_server server” node

Still in the first terminal, let’s run the parameter server, specifying where to save the parameters using the “-f” parameter:

ros2 run parameter_server server -f /home/user/ros2_ws/persistent_parameters.yaml

 

Now, let’s set a ROS2 parameter using the following command in a second terminal:

ros2 param set /parameter_server persistent.some_int 81

We should see the following output:

Set parameter successful

 

One thing worth noticing in the command above is the “persistent.” prefix in the some_int parameter. The parameters need that prefix in order to be saved automatically in the /home/user/ros2_ws/persistent_parameters.yaml file.

We can now set more parameters, which should also be successfully set:

ros2 param set /parameter_server persistent.a_string Konnichiwa
ros2 param set /parameter_server persistent.pi 3.14159265359
ros2 param set /parameter_server persistent.some_lists.some_integers 81,82,83,84

 

After the parameters are set, we can list them using the following command:

ros2 param list
How to use persistent parameters in ROS2

How to use persistent parameters in ROS2

 

We can also retrieve the persistent.pi parameter, for example, just in case you want to be really sure that it is there:

ros2 param get /parameter_server persistent.pi

 

You can now kill the node running in the first terminal by pressing CTRL+C. You can also make sure there are no nodes running by running:

ros2 node list

 

Now, let’s start the node again to see that the parameters are loaded:

ros2 run parameter_server server -f /home/user/ros2_ws/persistent_parameters.yaml

 

And again, in another terminal you can list the parameters again, to confirm that the parameters from the previous “session” were successfully loaded:

ros2 param list

ros2 param get /parameter_server persistent.pi

Get those persistent parameters programmatically from another node

Alright. We have saved the parameters but they are related to a node that does nothing “nothing”. We need to be able to use those values in a ROS 2 node that really does some hard work.

In ROS 2, parameters are available via service interfaces. We could implement a service client ourselves and retrieve the parameters associated with any individual node. However, we could also use the rclpcpp library includes this functionality built in.

The rclcpp library implements the classes SyncParametersClient and AsyncParametersClient on which you can call functions like get_parameters that will handle the service calls to retrieve values about one or more parameters.

Let’s create another package called my_app_node using a third terminal (make sure you still have the server running in the first terminal):

cd ~/ros2_ws/src
ros2 pkg create my_app_node --build-type ament_cmake --dependencies rclcpp
cd ~/ros2_ws/src/my_app_node/src
touch my_app_node.cpp

 

You can now open the ~/ros2_ws/src/my_app_node/src/my_app_node.cpp file using the Code Editor to paste some content on it. You can open the Code Editor by clicking on the second link of the bottom bar:

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 my_app_node.cpp file we just created.

After having the file open, let’s past the following content to it to read the value of the persistent.a_string variable:

#include "rclcpp/rclcpp.hpp"

class MyAppNode : public rclcpp::Node
{
public:
    MyAppNode() : Node("my_app_node")
    {
        parameters_client = 
            std::make_shared(this, "/parameter_server");
        parameters_client->wait_for_service();
        auto parameters_future = parameters_client->get_parameters(
            {"persistent.a_string"},
            std::bind(&MyAppNode::callbackParamServer, this, std::placeholders::_1));
    }
    void callbackParamServer(std::shared_future<std::vector> future)
    {
        auto result = future.get();
        auto param = result.at(0);
        RCLCPP_INFO(this->get_logger(), "Got persistent parameter: %s", param.as_string().c_str());
    }
private:
    std::shared_ptr parameters_client;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared());
  rclcpp::shutdown();
  return 0;
}

 

You can now save the file by pressing CTRL+S.

Let us now open the CMakeLists.txt file found at ~/ros2_ws/src/my_app_node. Below the existing dependency find_package(ament_cmake REQUIRED), add the following lines:

find_package(rclcpp REQUIRED)

add_executable(my_app_node
  src/my_app_node.cpp
)
ament_target_dependencies(my_app_node
  rclcpp
)

install(TARGETS
  my_app_node
  DESTINATION lib/${PROJECT_NAME}
)

 

We can now compile our package using the commands below:

cd ~/ros2_ws/
colcon build
source install/setup.bash
ros2 run my_app_node my_app_node

 

If everything went well, you should see the value of persistent.a_string printed.

Congratulations!!! You now know how to persist ROS 2 Parameters, and retrieve them programmatically using a ROS 2 Node.

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:

Get ROS2 Industrial Ready- Hands-On Training by The Construct cover.png

Get ROS2 Industrial Ready- Hands-On Training by The Construct cover.png

How to introspect ROS 2 executables

How to introspect ROS 2 executables

In this post, you will learn how to introspect ROS 2 executables by identifying publishers and subscribers in the executables. This answers this question posted on ROS Answers.

Step 1: Copy sample packages containing ROS 2 executables

“Hey, do I have to install ros2 first?” Absolutely not! We will be using The Construct to get access to virtual machines pre-installed with ROS.

Click here to copy the ROS2 TurtleBot3 sandbox packages. Once copied, click the red RUN button to launch the packages in a virtual machine. Please be patient while the environment loads.

PS: You will need to login or create an account to copy the packages.


You might also want to try this on a local PC if you have ros2 and some executables installed. However, please note that we cannot 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 ros2 installation.

Step 2: Explore the source code of an ament_python package

You can know what topics are being used by an executable (that is, introspect it) without running it by checking its source code, looking for publishers and subscribers.

Now head over to the Code Editor to make to explore the source code of the packages you just copied.

Open the Code Editor

First, we look at the turtlebot3_teleop package.

Turtblebot3 Teleop package
turtlebot3_teleop package

Let’s look for the executable file for this package. We can find that in the setup.py file.

turtlebot3_teleop/setup.py

from setuptools import find_packages
from setuptools import setup

package_name = 'turtlebot3_teleop'

setup(
    name=package_name,
    version='2.1.2',
    packages=find_packages(exclude=[]),
    data_files=[
        ('share/ament_index/resource_index/packages', ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=[
        'setuptools',
    ],
    zip_safe=True,
    author='Darby Lim',
    author_email='thlim@robotis.com',
    maintainer='Will Son',
    maintainer_email='willson@robotis.com',
    keywords=['ROS'],
    classifiers=[
        'Intended Audience :: Developers',
        'License :: OSI Approved :: Apache Software License',
        'Programming Language :: Python',
        'Topic :: Software Development',
    ],
    description=(
        'Teleoperation node using keyboard for TurtleBot3.'
    ),
    license='Apache License, Version 2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'teleop_keyboard = turtlebot3_teleop.script.teleop_keyboard:main'
        ],
    },
)

Looking at the entry_points part of the file, and then the console_scripts, we find the executable file is in the turtlebot3_teleop/script/teleop_keyboard.py file. Let’s find the publishers and/subscribers for this executable.

turtlebot3_teleop/script/teleop_keyboard.py

def main():
    settings = None
    if os.name != 'nt':
        settings = termios.tcgetattr(sys.stdin)

    rclpy.init()

    qos = QoSProfile(depth=10)
    node = rclpy.create_node('teleop_keyboard')
    pub = node.create_publisher(Twist, 'cmd_vel', qos)

    status = 0
    target_linear_velocity = 0.0
    target_angular_velocity = 0.0
    control_linear_velocity = 0.0
    control_angular_velocity = 0.0

    try:
        print(msg)
        while(1):
            key = get_key(settings)
            if key == 'w':
                target_linear_velocity =\
                    check_linear_limit_velocity(target_linear_velocity + LIN_VEL_STEP_SIZE)
                status = status + 1
                print_vels(target_linear_velocity, target_angular_velocity)
            elif key == 'x':
                target_linear_velocity =\
                    check_linear_limit_velocity(target_linear_velocity - LIN_VEL_STEP_SIZE)
                status = status + 1
                print_vels(target_linear_velocity, target_angular_velocity)
            elif key == 'a':
                target_angular_velocity =\
                    check_angular_limit_velocity(target_angular_velocity + ANG_VEL_STEP_SIZE)
                status = status + 1
                print_vels(target_linear_velocity, target_angular_velocity)
            elif key == 'd':
                target_angular_velocity =\
                    check_angular_limit_velocity(target_angular_velocity - ANG_VEL_STEP_SIZE)
                status = status + 1
                print_vels(target_linear_velocity, target_angular_velocity)
            elif key == ' ' or key == 's':
                target_linear_velocity = 0.0
                control_linear_velocity = 0.0
                target_angular_velocity = 0.0
                control_angular_velocity = 0.0
                print_vels(target_linear_velocity, target_angular_velocity)
            else:
                if (key == '\x03'):
                    break

            if status == 20:
                print(msg)
                status = 0

            twist = Twist()

            control_linear_velocity = make_simple_profile(
                control_linear_velocity,
                target_linear_velocity,
                (LIN_VEL_STEP_SIZE / 2.0))

            twist.linear.x = control_linear_velocity
            twist.linear.y = 0.0
            twist.linear.z = 0.0

            control_angular_velocity = make_simple_profile(
                control_angular_velocity,
                target_angular_velocity,
                (ANG_VEL_STEP_SIZE / 2.0))

            twist.angular.x = 0.0
            twist.angular.y = 0.0
            twist.angular.z = control_angular_velocity

            pub.publish(twist)

    except Exception as e:
        print(e)

    finally:
        twist = Twist()
        twist.linear.x = 0.0
        twist.linear.y = 0.0
        twist.linear.z = 0.0

        twist.angular.x = 0.0
        twist.angular.y = 0.0
        twist.angular.z = 0.0

        pub.publish(twist)

        if os.name != 'nt':
            termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)


if __name__ == '__main__':
    main()

Success! On line 148 we can find that the executable creates a publisher to the /cmd_vel topic, so we know this is a topic used by the executable.

Are there other topics used by this executable? Find out!

Step 3: Explore the source code of an ament_cmake package

Let explore the turtlebot3_node package.

Turtlebot3_node package
turtlebot3_node package

We can find the main executable in the CMakeLists.txt file in the add_executable line (line 75).

turtlebot3_node/CMakeLists.txt

################################################################################
# Set minimum required version of cmake, project name and compile options
################################################################################
cmake_minimum_required(VERSION 3.5)
project(turtlebot3_node)

if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

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

################################################################################
# Find ament packages and libraries for ament and system dependencies
################################################################################
find_package(ament_cmake REQUIRED)
find_package(dynamixel_sdk REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(message_filters REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rcutils REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(std_msgs REQUIRED)
find_package(std_srvs REQUIRED)
find_package(tf2 REQUIRED)
find_package(tf2_ros REQUIRED)
find_package(turtlebot3_msgs REQUIRED)

################################################################################
# Build
################################################################################
include_directories(
  include
)

add_library(${PROJECT_NAME}_lib
  "src/devices/motor_power.cpp"
  "src/devices/sound.cpp"
  "src/devices/reset.cpp"

  "src/diff_drive_controller.cpp"
  "src/dynamixel_sdk_wrapper.cpp"
  "src/odometry.cpp"
  "src/turtlebot3.cpp"

  "src/sensors/battery_state.cpp"
  "src/sensors/imu.cpp"
  "src/sensors/joint_state.cpp"
  "src/sensors/sensor_state.cpp"
)

set(DEPENDENCIES
  "dynamixel_sdk"
  "geometry_msgs"
  "message_filters"
  "nav_msgs"
  "rclcpp"
  "rcutils"
  "sensor_msgs"
  "std_msgs"
  "std_srvs"
  "tf2"
  "tf2_ros"
  "turtlebot3_msgs"
)

target_link_libraries(${PROJECT_NAME}_lib)
ament_target_dependencies(${PROJECT_NAME}_lib ${DEPENDENCIES})

set(EXECUTABLE_NAME "turtlebot3_ros")

add_executable(${EXECUTABLE_NAME} src/node_main.cpp)
target_link_libraries(${EXECUTABLE_NAME} ${PROJECT_NAME}_lib)
ament_target_dependencies(${EXECUTABLE_NAME} ${DEPENDENCIES})

################################################################################
# Install
################################################################################
install(DIRECTORY param
  DESTINATION share/${PROJECT_NAME}
)

install(TARGETS ${EXECUTABLE_NAME}
  DESTINATION lib/${PROJECT_NAME}
)

################################################################################
# Macro for ament package
################################################################################
ament_export_include_directories(include)
ament_package()

So we see that the main executable is src/node_main.cpp. Let’s examine it.

turtlebot3_node/src/node_main.cpp

// 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.
//
// Author: Darby Lim

#include <rcutils/cmdline_parser.h>
#include <rclcpp/rclcpp.hpp>

#include <chrono>
#include <memory>
#include <string>

#include "turtlebot3_node/diff_drive_controller.hpp"
#include "turtlebot3_node/turtlebot3.hpp"

void help_print()
{
  printf("For turtlebot3 node : \n");
  printf("turtlebot3_node [-i usb_port] [-h]\n");
  printf("options:\n");
  printf("-h : Print this help function.\n");
  printf("-i usb_port: Connected USB port with OpenCR.");
}

int main(int argc, char * argv[])
{
  setvbuf(stdout, NULL, _IONBF, BUFSIZ);

  if (rcutils_cli_option_exist(argv, argv + argc, "-h")) {
    help_print();
    return 0;
  }

  rclcpp::init(argc, argv);

  std::string usb_port = "/dev/ttyACM0";
  char * cli_options;
  cli_options = rcutils_cli_get_option(argv, argv + argc, "-i");
  if (nullptr != cli_options) {
    usb_port = std::string(cli_options);
  }

  rclcpp::executors::SingleThreadedExecutor executor;

  auto turtlebot3 = std::make_shared<robotis::turtlebot3::TurtleBot3>(usb_port);
  auto diff_drive_controller =
    std::make_shared<robotis::turtlebot3::DiffDriveController>(
    turtlebot3->get_wheels()->separation,
    turtlebot3->get_wheels()->radius);

  executor.add_node(turtlebot3);
  executor.add_node(diff_drive_controller);
  executor.spin();

  rclcpp::shutdown();

  return 0;
}

There’s no reference to a publisher or subscriber in this file, but it references some other files. Let’s look at the turtlebot3_node/turtlebot3.cpp file referenced on line 25.

turtlebot3_node/src/turtlebot3.cpp

void TurtleBot3::cmd_vel_callback()
{
  auto qos = rclcpp::QoS(rclcpp::KeepLast(10));
  cmd_vel_sub_ = this->create_subscription<geometry_msgs::msg::Twist>(
    "cmd_vel",
    qos,
    [this](const geometry_msgs::msg::Twist::SharedPtr msg) -> void
    {
      std::string sdk_msg;

      union Data {
        int32_t dword[6];
        uint8_t byte[4 * 6];
      } data;

      data.dword[0] = static_cast<int32_t>(msg->linear.x * 100);
      data.dword[1] = 0;
      data.dword[2] = 0;
      data.dword[3] = 0;
      data.dword[4] = 0;
      data.dword[5] = static_cast<int32_t>(msg->angular.z * 100);

      uint16_t start_addr = extern_control_table.cmd_velocity_linear_x.addr;
      uint16_t addr_length =
      (extern_control_table.cmd_velocity_angular_z.addr -
      extern_control_table.cmd_velocity_linear_x.addr) +
      extern_control_table.cmd_velocity_angular_z.length;

      uint8_t * p_data = &amp;data.byte[0];

      dxl_sdk_wrapper_->set_data_to_device(start_addr, addr_length, p_data, &amp;sdk_msg);

      RCLCPP_DEBUG(
        this->get_logger(),
        "lin_vel: %f ang_vel: %f msg : %s", msg->linear.x, msg->angular.z, sdk_msg.c_str());
    }
  );
}

Success! We see the /cmd_vel is also referenced on line 313. Can you find other files linked to the main file and it’s linked files, where other topics are referenced?

Step 4: Check your learning

Do you understand how to introspect ROS 2 executables? 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 introspect ROS 2 executables

Here you go:

Feedback

Did you like this post? Do you have any questions about how to introspect ROS 2 executables? 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 ROS2 topics, please let us know in the comments area and we will do a video or post about it.

How to build a local Debian ROS2 package

How to build a local Debian ROS2 package

What we are going to learn

  1. What is a Debian package and what is it used for
  2. How to get the building dependencies
  3. How to initialize rosdep
  4. How to create a Debian installable package
  5. How to install a ROS2 package from a local Debian installable file

List of resources used in this post

  1. The previous part of this series: https://www.theconstruct.ai/how-to-release-a-ros-2-binary-package-part-2-2/
  2. Use the rosject: https://app.theconstructsim.com/l/5142d6f5/
  3. The Construct: https://app.theconstructsim.com/
  4. 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 (Galactic): https://app.theconstructsim.com/Course/109
    4. ROS2 Navigation training: https://www.theconstruct.ai/ros2-navigation-training/

Overview

ROS2, the improved version of ROS (ROS1), is quickly becoming the standard for developing robotics applications.

In this post, we will take a look at how to build a local Debian package out of a ROS 2 package that you have in your system. Private Debian packages (*.deb files) are easy to share, install, upgrade and uninstall without you having to share any source code.

You can easily install them with sudo dpkg -i pkg-name.deb, for example.

It is highly recommended that you follow the previous posts of this series:

ROS Inside!

ROS Inside

ROS Inside

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

ROS Inside logo

Acknowledgment

This post is based on ROS2’s official documentation, check it out here:

This notebook is based on the official instructions.

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 already prepared a rosject with a simulation for that: https://app.theconstructsim.com/l/5142d6f5/.

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 Topics vs Service vs Action – 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.

Compiling the ros2_ws (ROS 2 Workspace)

In the rosject link shared above, we have the ros2_draw_squares package developed by GitHub user samialperen. The package will be used in our example and can be found at https://github.com/samialperen/ros2_move_robot.

Let’s start by compiling the ros2_draw_squares package that is inside the ~/ros2_ws/src folder. In order to do that, we need a terminal. You can open one by clicking the Open a new terminal button.

Open a new Terminal

Open a new Terminal

Once the terminal is open, we can compile the package using the following commands:

cd ~/ros2_ws
colcon build
source install/setup.bash

 

Starting the simulation

Before showing the process of creating the Debian file, let’s see it in action first through a robot simulation.

Let’s start by exporting the GAZEBO_MODEL_PATH variable to the location of the turtlebot3 model files, so that the Gazebo Simulator can show the robot properly:

source install/setup.bash
export GAZEBO_MODEL_PATH=/home/user/ros2_ws/src/turtlebot3_simulations/turtlebot3_gazebo/models:${GAZEBO_MODEL_PATH}
export GAZEBO_RESOURCE_PATH=/home/user/ros2_ws/src/turtlebot3_simulations/turtlebot3_gazebo:${GAZEBO_RESOURCE_PATH}

 

Now let’s set the TURTLEBOT3_MODEL to waffle:

export TURTLEBOT3_MODEL=waffle
ros2 launch turtlebot3_gazebo empty_world.launch.py

You should see the Gazebo simulator pop up with an empty world.

Now, open a second Terminal, and run the service server node using the command below:

ros2 launch ros2_draw_squares move_robot_in_square_service_server.launch.py

 

And from a third terminal, we can run the service move_robot_in_square.

ros2 service call /move_robot_in_square std_srvs/srv/Trigger '{}'

If everything went ok, in the simulation window you should have seen the Turtlebot robot moving in a square-like format.

Turblebot robot moving in a square-like format

Turblebot robot moving in a square-like format

 

Alright, we already saw the package in action. Now let us see the process of converting it into a Debian file.

Feel free to kill the existing simulation by pressing CTRL+C in the three terminals.

What is a Debian package and what is it used for?

.deb file in essence it’s a very simple and easy way of distributing software in Debian-based Linux distributions. You can think of it as a file similar to a .tar file which is a format used to store multiple files in one single file. Using a Debian package we can install a ROS2 package in our environment without having to have the source files.

So for instance the example project contains the following files and directories:
-ros2_draw_squares
 |-package.xml
 |-launch
 | |-call_move_robot_in_square_service_server.launch.py
 | |-move_robot_in_square_service_server.launch.py
 |-include
 | |-ros2_draw_squares
 | | |-move_robot.hpp
 |-README.md
 |-src
 | |-move_robot.cpp
 | |-move_robot_in_square_service_server.cpp
 | |-move_robot_in_square_service_client.cpp
 | |-move_robot_node.cpp
 |-CMakeLists.txt
The Debian file will consist of only one single file.

Deb files also hold metadata about the software they provide. By the use of metadata, Debian packaging handles the installation, removal, and dependencies of packages.

In the context of robotics, Debian files provide a convenient way to deploy and remotely manage robotic software on real robots. This includes installing, updating, and removing packages through an SSH (Secure Shell) connection, for example.

Get the building dependencies

After having stopped everything by pressing CTRL+C in the three terminals, the next thing we need to do is to actually install the building tools needed by running the following commands in the first terminal:
sudo apt update
sudo apt install python3-bloom python3-rosdep fakeroot dh-make

This will install the tools python3-bloom python3-rosdep, fakeroot, and dh-make packages.

Since this can take a while to install, you just have to wait a bit for it to finish.

Check the ROS2 package dependencies

All of the dependencies of the package should be properly declared in the package.xml file of the ROS 2 package.

Still in the first terminal, let’s see the dependencies that we have for our package:

cat ~/ros2_ws/src/ros2_draw_squares/package.xml

We just have to make sure all package dependencies are listed using the correct dependency tag. If when creating your own package you are not sure what kind of dependency a package is, you can use <depend> tag to specify that the dependency is a build, export, and execution one.

Initialize rosdep

Let’s initialize rosdep. That can still be done in the first terminal:

sudo rosdep init

You might see the following error if rosdep has already been initialized in the past:

ERROR: default sources list file already exists:
        /etc/ros/rosdep/sources.list.d/20-default.list
Please delete if you wish to re-initialize

Create a Debian installable package

cd ~/ros2_ws/src/ros2_draw_squares

After that, let’s use the bloom-generate command:

bloom-generate rosdebian

The expected output is something similar to the following:

bloom-generate rosdebian output

bloom-generate rosdebian output

 

If for whatever reason you get an error message like the one below:

Could not resolve rosdep key

Could not resolve rosdep key

This means that rosdep could not find that particular package dependency. Verify if the package must be indexed in the rosdistro by creating a PR.

After successfully running the above command, you should get a directory named debian in the current directory, which contains the compiled files and rules required to create the Debian file. The “ls” command output should be like this:

 CMakeLists.txt  README.md  debian  include  launch  package.xml  src

 

Use the following command to create a Debian package:

fakeroot debian/rules binary

We should see something similar to the following animation:

fakeroot expected output

fakeroot expected output

After a while, the command should finish with no errors.

The command we just executed should have created a .deb file in the directory that is one level above. We can check it with:

cd ..

ls

Which should show us the following:

Debian file created

Debian file created

Install a ROS2 package from a local Debian installable file

Alright, our Debian file has been successfully generated.

Let us install this Debian package on our own machine by using the package management program called dpkg:

sudo dpkg --install ros-galactic-ros2-draw-squares_0.0.0-0focal_amd64.deb

This will install the Debian package (including unpacking and configuring) onto the file system of the hard disk.

 

dpkg install output

dpkg install output

After having installed the Debian package, it is ready to be used in our environment. Let’s find out where the package got installed. To do so we first need to get the package name:

dpkg --info ros-galactic-ros2-draw-squares_0.0.0-0focal_amd64.deb

 

Once we have the name, we can display where the files in the deb package got installed to:

dpkg -L ros-galactic-ros2-draw-squares

 

We can see that this package is now installed in the same location where the main ROS 2 installation is located.

Finally, in case you want to uninstall this package, we can easily do it also with dpkg:

sudo dpkg -P ros-galactic-ros2-draw-squares

 

And if you want to take it further from here, you can even create your own apt repository to host your private ROS2 .deb packages, but that’s a story for another time.

 

Congratulations on reaching the end of this tutorial. 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:

[ROS2 Q&A] How to spawn robot from sam_bot nav2 tutorial #237

[ROS2 Q&A] How to spawn robot from sam_bot nav2 tutorial #237

What we are going to learn

  1. How to create a ROS 2 package
  2. How to load a URDF robot for the Gazebo simulator

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/l/50fbfe47/
  2. The Construct: https://app.theconstructsim.com/
  3. Nav2 documentation: https://navigation.ros.org/setup_guides/index.html
  4. 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 (Galactic): https://app.theconstructsim.com/Course/109
    4. ROS2 Navigation training: https://www.theconstruct.ai/ros2-navigation-training/

Overview

This post answers the following question posted at ROS answers:

ROS Inside!

ROS inside

ROS inside

Before anything else, in case you want to use the logo above on your own robot or computer, feel free to download it for free 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 already prepared a rosject with a simulation for that: https://app.theconstructsim.com/l/50fbfe47/.

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 Topics vs Service vs Action – 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.

Opening the Code Editor

Now that the rosject is open, let’s open the Code Editor by clicking on the second link:

Open the IDE - Code Editor

Open the IDE – Code Editor

 

Once the Code Editor is open, you should be able to see that we have a ros2_ws folder (ROS2 workspace)

The sam_bot_description.urdf file

Now that you have the Code Editor open, let’s have a look at the file that contains the robot description. The file is inside the ros2_ws folder. The full path is:

/home/user/ros2_ws/src/sam_bot_description/src/description/sam_bot_description.urdf

 

In case you want to see the content using the terminal, you can open it by clicking the Open a new terminal button.

Open a new Terminal

Open a new Terminal

Once inside the terminal, just type the command below (although it is better to see the file using the Code Editor):

cat /home/user/ros2_ws/src/sam_bot_description/src/description/sam_bot_description.urdf

 

Starting the simulation

As we saw in the overview, the question asked in ROS Answers was saying that the robot wheels were not working. That happened probably because the person forgot to add something to the URDF file.

To better understand the URDF file, you can refer to the Nav 2 Documentation: https://navigation.ros.org/setup_guides/urdf/setup_urdf.html and https://navigation.ros.org/setup_guides/odom/setup_odom.html

Now, go to the terminal you have open and run the following command to run the simulation:

ros2 launch  sam_bot_description display.launch.py

 

If everything went well, you should have Gazebo as well as RViz open, like in the image below (If the Graphical Tools don’t open automatically, just click click the 4th and 5th buttons on the bottom left bar to Open Gazebo and Open the Graphical Tools):

 

You can also see the Gazebo simulation by clicking the 4th icon named Open Gazebo. There you will see that the wheels are in place.

Well, that is pretty much it.

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:

Pin It on Pinterest