[ROS2 Q&A] How to follow waypoints using nav2 #232

[ROS2 Q&A] How to follow waypoints using nav2 #232

What we are going to learn

  1. How to launch a functional nav2 system
  2. How to use nav2 simple commander API
  3. How to launch nav2 waypoint follower module

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/l/4da61f89/
  2. The Construct: https://app.theconstructsim.com/
  3. Nav2 simple commander API: https://github.com/ros-planning/navigation2/tree/main/nav2_simple_commander
    1. https://navigation.ros.org/commander_api/index.html
  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

In this post, we’ll be learning how to use nav2 SImple Command API to write a program that makes your program follow waypoints.

What we are going to create is something like a patroling system, in which the robot patrols a given area.

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/4da61f89/.

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

How to follow waypoints using nav2 – 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.

Launching the simulation

As you may imagine, instead of using a real robot, we are going to use a simulation. The simulation package we are using, neo_simulation2 (by Neobotix), comes along with all the new ROS 2 features.

Like its predecessor, the neo_simulation2 package is fully equipped with all the Neobotix robots that are available in the market.

 

By the way, Neobotix is a state-of-the manufacturer of mobile robots and robot systems. We offer robots and manipulators for all applications with full ROS support. Neobotix products range from small mobile robots to mobile robot arms and several omnidirectional robots. They are specialized in designing customized mobile robots to meet your unique requirements.

 

Combining the novelty of ROS 2 and the state-of-the-art Neobotix platforms would allow users to learn and develop various reliable and robust application that caters to their needs in both research and as well as in industries.

Alright, having opened the rosject and explained a little bit about Neobotix, let’s start running some commands in the terminal. 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 first terminal, let’s run the commands below, to launch a simulation

cd ros2_ws
source install/setup.bash
ros2 launch neo_simulation2 simulation.launch.py
There will be countless red error messages on this simulation terminal. Let’s just ignore those messages for now.
If you want to know a bit more about Neobotix robots, they offer:

ROS2 Navigation

In order to move the robot to a desired goal location, pre-defined controllers and planners are available to be readily used. Thanks to Navigation 2, the all-new ROS-2 navigation stack, provides us with localization, global planning, and local planning algorithms, that allow us to jump-start by testing our intended application on the robot real-quick.

Almost all the algorithms found in move_base (ROS-1 Navigation stack) are available in Navigation2. All the Neobotix robots in the simulation for ROS-2 are primed and ready with Navigation2.

Once the simulation is started (seen in the previous tutorial), ROS-2 navigation stack can be launched using the following command

Now, in a second terminal, we can launch the Localization Server using the following command:

ros2 launch localization_server localization.launch.py
And in a third terminal, we can launch the Path Planner Server:
ros2 launch path_planner_server pathplanner.launch.py

The commands above should have launched the simulation, Localization Server, and Path Planner server.

After some seconds, we should have Gazebo (simulation), RViz (Robot Visualization), and Teleop running now. The simulation should be similar to the following:

Simulation - How to follow waypoints using nav2

Simulation – How to follow waypoints using nav2

 

If the Gazebo simulation doesn’t pop up:

  • Please open the Gazebo from the below menu bar
  • RViz would have been loaded as well and can be found in the Graphical tools
  • Also, another terminal would have popped out in the Graphical tools for the teleoperation. Please follow the instruction given in that terminal for moving the robot.

To make sure everything is working so far, you can send a 2D NavGoal in RViz to make sure the navigation system is working.

The files used to launch the Localization Server and Path Planner are found on the following paths:

ls ~/ros2_ws/src/neobotix_mp_400_navigation/localization_server/launch/localization.launch.py
ls ~/ros2_ws/src/neobotix_mp_400_navigation/path_planner_server/launch/pathplanner.launch.py

These files can also be seen in the Code Editor:

Localization Server and Path Planner - How to follow waypoints using nav2

Localization Server and Path Planner – How to follow waypoints using nav2

 

Feel free to localize and send goals to the robot as shown in this video about ROS2 Navigation for Omnidirectional Robots:

 

Global Costmap and Local Costmap in RViz

Assuming you have RViz running, you can add Global and Local costmaps to it. For that, click the Add button on the bottom left side of RViz, then Add by Topic, then select Global Costmap:

Add by topic - Global Costmap - How to follow waypoints using nav2

Add by topic – Global Costmap – How to follow waypoints using nav2

 

To add Local Costmap, click the Add button on the bottom left side of RViz, then Add by Topic, then select the Map under Local Costmap:

Add by topic - Local Costmap - How to follow waypoints using nav2

Add by topic – Local Costmap – How to follow waypoints using nav2

 

Assuming everything went well so far, now we are going to test the waypoint follower.

Waypoint follower

If you forked the rosject (clicking on the link we provided to you earlier), you should have a package named follow_waypoints on your ros2_ws/src folder already, but for documentation purposes, and in case you want to know the baby steps, here is how we created that package.

First, in a fourth terminal we created that package:

cd ~/ros2_ws/src

ros2 pkg create --build-type ament_python follow_waypoints
By listing the content of that ~/ros2_ws/src folder, we see that the package has been created:
ls

# follow_waypoints  neo_local_planner2  neo_simulation2  neobotix_mp_400_navigation

That follow_waypoints package has a folder with the same name on it. On that folder, we created a file named follow_waypoints.py

cd ~/ros2_ws/src/follow_waypoints/follow_waypoints

touch follow_waypoints.py

chmod +x follow_waypoints.py

The touch command was used to create the file, and the chmod +x command was used to give execution permissions to that file (make it executable, basically)

We then pasted some content on the follow_waypoints.py file. You can see the content by opening that file using the Code Editor.

The content we pasted is basically a modified version of https://github.com/ros-planning/navigation2/blob/main/nav2_simple_commander/nav2_simple_commander/example_waypoint_follower.py

Inspection Route - To navigation to - How to follow waypoints using nav2

Inspection Route – To navigation to – How to follow waypoints using nav2

 

On lines 33 to 36 we define an inspection_route variable, which essentially is an array that defines the waypoints (positions in the map) that the robot has to go when patrolling.

#! /usr/bin/env python3
# Copyright 2021 Samsung Research America
#
# 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.

import time
from copy import deepcopy

from geometry_msgs.msg import PoseStamped
from rclpy.duration import Duration
import rclpy

from nav2_simple_commander.robot_navigator import BasicNavigator, NavigationResult


def main():
    rclpy.init()

    navigator = BasicNavigator()

    # Inspection route, probably read in from a file for a real application
    # from either a map or drive and repeat.
    inspection_route = [ # simulation points
        [5.0, 0.0],
        [-5.0, -5.0],
        [-5.0, 5.0]]


    # Set our demo's initial pose
    # initial_pose = PoseStamped()
    # initial_pose.header.frame_id = 'map'
    # initial_pose.header.stamp = navigator.get_clock().now().to_msg()
    # initial_pose.pose.position.x = 3.45
    # initial_pose.pose.position.y = 2.15
    # initial_pose.pose.orientation.z = 1.0
    # initial_pose.pose.orientation.w = 0.0
    # navigator.setInitialPose(initial_pose)

    # Wait for navigation to fully activate
    navigator.waitUntilNav2Active()

    while rclpy.ok():

        # Send our route
        inspection_points = []
        inspection_pose = PoseStamped()
        inspection_pose.header.frame_id = 'map'
        inspection_pose.header.stamp = navigator.get_clock().now().to_msg()
        inspection_pose.pose.orientation.z = 1.0
        inspection_pose.pose.orientation.w = 0.0
        for pt in inspection_route:
            inspection_pose.pose.position.x = pt[0]
            inspection_pose.pose.position.y = pt[1]
            inspection_points.append(deepcopy(inspection_pose))
        nav_start = navigator.get_clock().now()
        navigator.followWaypoints(inspection_points)

        # Do something during our route (e.x. AI to analyze stock information or upload to the cloud)
        # Simply print the current waypoint ID for the demonstation
        i = 0
        while not navigator.isNavComplete():
            i = i + 1
            feedback = navigator.getFeedback()
            if feedback and i % 5 == 0:
                print('Executing current waypoint: ' +
                    str(feedback.current_waypoint + 1) + '/' + str(len(inspection_points)))

        result = navigator.getResult()
        if result == NavigationResult.SUCCEEDED:
            print('Inspection of shelves complete! Returning to start...')
        elif result == NavigationResult.CANCELED:
            print('Inspection of shelving was canceled. Returning to start...')
            exit(1)
        elif result == NavigationResult.FAILED:
            print('Inspection of shelving failed! Returning to start...')

        # go back to start
        # initial_pose.header.stamp = navigator.get_clock().now().to_msg()
        # navigator.goToPose(initial_pose)
        while not navigator.isNavComplete:
            pass


if __name__ == '__main__':
    main()


 

In addition to that follow_waypoints.py file, we also had to create the ~/ros2_ws/src/follow_waypoints/config/follow_waypoints.yaml and ~/ros2_ws/src/follow_waypoints/setup.py files.

  • Please check that files. If you want a deeper explanation about those files, please check the video available at the end of this post.

 

Alright, after having created that package and the required configuration files, the next was was compiling the package:

cd ~/ros2_ws

colcon build; source install/setup.bash

 

Then, to see the robot following the waypoints, we can run:

cd ~/ros2_ws

source install/setup.bash

ros2 run follow_waypoints follow_waypoints_exe

 

Looking at the simulation and at RViz, you should be able to see the robot moving.

Congratulations. You just learned how to make a robot follow waypoints using nav2 (the official ROS 2 Navigation package)

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 release a ROS 2 binary package – Part 3

How to release a ROS 2 binary package – Part 3

What we are going to learn

  1. How to generate a changelog file
  2. How to bump the package version
  3. How to run a first-time release using bloom

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/l/5562c7f1/
  2. The Construct: https://app.theconstructsim.com/
  3. 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

This is the third part of a video that shows how to release a ROS2 package to the ROS build farm using bloom. Be sure to check the first and second parts if you haven’t yet:

Here we will explain how to generate a changelog file, bump the package version, and run a first-time release using bloom.

Acknowledgment

This video is based-off on ROS2’s official documentation, check it out at the link below:

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/5562c7f1/.

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

How to release a ROS 2 binary package – Part 3 – 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.

 

What is bloom?

Bloom is a build automation tool that will guide you through all necessary steps in the process of compiling the source code in the ROS build farms, packaging the code into binaries, and uploading them to the Debian archive. This way any users can easily install and uninstall it using the Debian package tools.

What is a build farm?

A build farm is a collection of one or more servers, which has been set up to compile computer programs remotely.

Assumptions

In order to follow this tutorial, we assume you have:

 

Recap from part 1: Install the tools required on your machine

After having opened the rosject, let’s start running some commands in the terminal. 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, use apt to install bloom together with the python3-catkin-pkg modules if you haven’t done so already.

sudo apt update
sudo apt install python3-bloom python3-catkin-pkg

New addition step: Since we are using a version of bloom older than 0.6.8 we need to use the v4 index URL for releasing:

export ROSDISTRO_INDEX_URL='https://raw.githubusercontent.com/ros/rosdistro/master/index-v4.yaml'

Recap from part 2: Incorporate feedback

If you followed the second part of this video, you created a pull request to add a ros/rosdistro source entry. Remember that you have to make the changes requested by the reviewer to get your pull request merged. This part assumes that your pull request has been successfully merged to the ros/rosdistro repository.

Important: The issues created in part 1 and part 2 must be shown as completed.

 

Verify your repository is up to date and your code builds

This might seem like an obvious one, but it’s easy to overlook. Ensure you have committed your changes and pushed the last commit to your remote.

Additionally, confirm your new code builds and executes right before making it available to the build farm.

cd ~/ros2_ws/src

git clone https://github.com/rfzeg/wall_follower_ros2
cd ~/ros2_ws

colcon build; source install/setup.bash

IMPORTANT!!!

Here we cloned the https://github.com/rfzeg/wall_follower_ros2 package. You cannot release the same package from the same URL because it has been already released by ROS, which means some parts of this tutorial may not work 100% if you close the same repository.

To make sure it works, you have to close your own repository instead of the one above. Please use the same repository you used in the previous posts of this series, for example.

Starting the simulation

After having opened the rosject and making sure the workspace builds with no problems, let’s start a simulation using the same terminal that you have already opened previously.

Let’s run the following commands in order to launch the simulation:

source ~/ros2_ws/install/setup.bash

source /usr/share/gazebo-11/setup.bash

ros2 launch wall_follower_ros2_tests miniworld.launch.py

 

Now, in a second terminal, let’s make sure our wall follower is working properly:

ros2 run wall_follower_ros2 wall_follower_ros2 --ros-args -r scan:=/lidar_1/out

 

If everything went well, you should see the robot moving around, and when it gets close to the wall, it turns and keep moving:

Wall follower using ROS 2

Wall follower using ROS 2

It is always a good idea to verify if there are new changes since the last commit. Let’s try that in the same second terminal. For that, first press CTRL+C to kill the current process, and then run the commands below:

cd ~/ros2_ws/src/wall_follower_ros2
git status

 

When you have added all of the changes to a commit, the Git status command line will inform you ‘nothing to commit, working tree clean‘ :

On branch master 
Your branch is up to date with 'origin/master'. 

nothing to commit, working tree clean

 

Generating the Changelog

We are now going to generate a Changelog. A changelog is a file that contains a condensed list of all important changes made to a project in a chronologically ordered way. Its purpose is to communicate to other developers what features have been added, improved, or removed over time.

To auto-generate the CHANGELOG.rst files, use the "catkin_generate_changelog --all" command.

Make sure to run this command from inside the home directory where the package files are located:

cd ~/ros2_ws/src/wall_follower_ros2
catkin_generate_changelog --all

 

If everything went well, we expect to have the following output:

Found packages: wall_follower_ros2
Querying all tags and commit information...
Generating changelog files with all versions...
- creating './CHANGELOG.rst'
Done.
Please review the extracted commit messages and consolidate the changelog entries before committing the files!

 

A new CHANGELOG.rst file will be automatically created for every package within the repository. The above command will also populate the file with the contents of your commit messages.

The above command will generate a CHANGELOG.rst file. If we open that file using the Code Editor, or by typing “cat CHANGELOG.rst“, we should see something similar to the following, for instance:

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Changelog for package wall_follower_ros2
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


Forthcoming
-----------
* Merge pull request `#1 <https://github.com/rfzeg/wall_follower_ros2/issues/1>`_ from rfzeg/master
  Master to main
* Feat: stop robot when a CTRL+C signal is received
* Fix safety_distance parameter value
* Add parameter to reverse ranges array in case it starts with rays at the left side
* Refactor: rename, reformat and rearrange code
* Add Timer Callback to set movement state, fill vel msg, publish vel msg
* Refactor laser_callback to get the distance reading to the closest object and the ray's position index
* Rename set_drive_state() -> set_drive_logic_state(), RCLCPP_INFO() -> RCLCPP_DEBUG() logs
* Rename set_velocity() -> determine_vel_msg(), rewrite debug logs
* Rename package to 'wall_follower_ros2'. Set version to 0.0.0 in package.xml
* Add info & debug log messages
* Add logical behavoir depending upon 5 zones around the robot, set velocity command
* Add log info & debug statements
* Refactor parameter value retrieval
* Add safety distance parameter
* Add default parameter values
* Add minimal rule based obstacle avoidance node (c++)
* Add initial ROS2 package files, Readme, License & Git configuration
* Contributors: Roberto Zegers, Roberto Zegers R

 

Note: The changelog is basically a list of commits. I you want to compare it with your log messages, have a look at the Git log history using git log –oneline:

git log --oneline

 

So, the changelog is basically a list of commits, but you can definitely remove some of the commits in the changelog if you want, however, you must not remove the  Forthcoming header.

AGAIN!!! Make sure you do not modify the Forthcoming header.

 

In this example I reduced the list of changes to only include the most important things as shown below:

Forthcoming
-----------

* Add initial version with logical behavoir depending upon 5 zones around the robot
* Contributors: Roberto Zegers

 

If you are interested you can consult the following page for additional information: Incorporation of Changelogs into Package Source Tree

Commit

If you have modified the changelog, then you must commit CHANGELOG.rst changes giving it an appropriate commit message such as “Add Changelog”.

git add CHANGELOG.rst
git commit -m "Add Changelog"

 

Bump the package version

Before releasing the package you will also have to increment its version number to a new, unique value because by default its initial version will be “0.0.0″, which is the value defined in the package.xml file of our package.
To confirm that number, you can run the command below:
cat ~/ros2_ws/src/wall_follower_ros2/package.xml | grep version

 

which will output something like this:
 <version>0.0.0</version>

 

Now, in order to automatically bump the package version, we can use the following command, but be aware that the command will ask you for your github credentials. That is why we mentioned previously that you would have to clone your own repository instead of the one we are using in this example (https://github.com/rfzeg/wall_follower_ros2):

cd ~/ros2_ws/src/wall_follower_ros2 

catkin_prepare_release

 

According to the ROS2’s documentation, this command performs the following:

  1. Increases the package version in package.xml. The above command will increment the patch version of the package from its default value of 0.0.0 to 0.0.1.
  2. Replaces the heading Forthcoming with version (date) (eg. 0.0.1 (2022-01-08)) in CHANGELOG.rst
  3. Commits those changes
  4. Creates a Git tag (eg. 0.0.1) to mark this point in the Git history
  5. Pushes the changes and the tag to your remote repository

You can show the changes made to the repository like this:

git diff HEAD^ HEAD

 

If you want to verify the tag that Git just added, run the command below, which will list the available tags in your Git repository:

git tag

 

Sidenote on semantic versioning

A semantic version number has three parts delimited by a dot, for instance:

1.2.5

Where 1 stands for a Major version, 2 represents the Minor version and 5 is the Patch. Let’s understand these labels:

Patch: Patch updates are interchangeable, meaning consumers can upgrade or downgrade freely. Example: Bug fix, performance improvement, or internal tweaks.

Minor: Minor updates are backward compatible, meaning consumers can upgrade freely. For instance new additional API methods, without changing the current methods.

Major: Major updates are non-compatible, meaning consumers can not upgrade without changing the software that uses the API, where applicable. You normally have interface changes breaking backward compatibility, like changes in an API endpoint name or signature, removal of an endpoint, etc.

To increment the major version, for instance in the first major release, run:

catkin_prepare_release --bump major

 

Similarly, the minor version gets incremented when you execute:

catkin_prepare_release --bump minor

 

Run the bloom-release command

Run the following command, the <my_repository> should be replaced with the name of your repository:

bloom-release –new-track –rosdistro humble –track humble <my_repository>

Note that <my_repository> is not an URL, but the repository reference in distribution.yaml.

In this particular example, the command to be executed is:

bloom-release --new-track --rosdistro humble --track humble wall_follower_ros2

 

The above command will create a release for ROS2 Humble. Let’s have a quick breakdown of the flags we used:

  • –new-track is important for a first-time release to create a new track before running bloom. What is a track? Bloom is designed to allow the release of the same package for different ROS distributions and versions in the same release repository. To facilitate this, bloom uses release “tracks” to maintain configurations for different release processes.
  • –rosdistro humble indicates that this release is for the humble distro. Replace as appropriate.
  • –track humble indicates that you want the track name to be humble

 

Note: For later releases, you don’t need the --new-track flag. However, if you want to add a new distribution to an already released package (see documentation) and configure it, you will have to include the --new-track option again.

Once executed, it will look at the distributions.yaml file for your project key. If it doesn’t find it, it’ll look in other distributions. If it finds one, it’ll prompt you to approve it. If not, you will be asked to enter a release repository URL:

Looking for a release of this repository in a different distribution

Looking for a release of this repository in a different distribution

In this particular example, we have to add the following. Note that you should replace this with the name of your repository.

AGAIN!!! Note that you should replace this with the name of your repository.

Our ros2-gbp release repository is at:

https://github.com/ros2-gbp/wall_follower_ros2-release.git

 

What follows are prompts that are meant to configure the new release track.

Configure the new release track

You will be asked to enter some basic information required to configure a new track. In the example shown we should respond to the prompts as follows:

  1. Repository Name: the name of the repository in which the package is: wall_follower_ros2
  2. Upstream Repository URI: Our repository is hosted on GitHub at https://github.com/rfzeg/wall_follower_ros2, but please remember to insert the URL of your own repository
  3. Upstream VCS Type: [Enter]
  4. Version: [Enter]
  5. Release Tag: [Enter]
  6. Upstream Devel Branch: master
  7. ROS Distro: [Enter]
  8. Patches Directory: [Enter]
  9. Release Repository Push URL: [Enter]

The whole process will take a while because it runs quite a few commands, for instance:

==>  git-bloom-release humble

 

==>  bloom-export-upstream

 

==>  git-bloom-import-upstream

 

==>  git-bloom-generate -y rosrelease humble --source upstream -i 1

 

==>  git-bloom-generate -y rosdebian --prefix release/humble humble -i 1--os-name ubuntu

 

It will also check if all dependencies can be satisfied by running rosdep update:

Running rosdep update to release a ros2 binary package

Running rosdep update to release a ros2 binary package

 

You should continue to see lots of dumping messages/logs on the terminal:

==> git-bloom-generate -y rosdebian --prefix release/humble humble -i 1--os-name debian --os-not-required
==> git-bloom-generate -y rosrpm --prefix release/humble humble -i 1 --os-name fedora
==> git-bloom-generate -y rosrpm --prefix release/humble humble -i 1 --os-name rhel
<== Released 'wall_follower_ros2' using release track 'humble' successfully

 

Then you will be asked if you want to push to the release repository. Just type Y and [Enter] to pass this prompt:

Releasing complete, push to release repository?
Continue [Y/n]? Y

 

The script will continue its execution:

==> git push --tags

 

We don’t have documentation information, and that’s okay:

Would you like to add documentation information for this repository? [Y/n]? n

 

You might encounter a prompt message like this one:

Enter an access token prompt

Enter an access token prompt

 

Go to http://github.com/settings/tokens to create a token if you haven’t done it so far.

This token will be saved in the bloom config file in your local machine: ‘/home/user/.config/bloom

Then, finally, you will be asked to confirm the creation of a pull request:

Open a pull request prompt

Open a pull request prompt

 

A few more commands will be run:

==> Pulling latest rosdistro branch

 

==> Writing new distribution file: humble/distribution.yaml

 

==> git add humble/distribution.yaml

 

==> Pushing changes to fork

 

At the very end of the process, you should see a message similar to this:

<== Pull request opened at: https://github.com/ros/rosdistro/pull/xxxxx

In our case, the Pull Request URL was the following:

Note: The release process begins recording logs as soon as you run it. Bloom log files are saved in your local machine to this directory: '/home/user/.bloom_logs'

The pull request page

Click on that link and you will see the current status of your pull request:

ROS ROSDISTRO Pull Request on GitHub

ROS ROSDISTRO Pull Request on GitHub

 

Now you will have to wait for someone from the ROS release team to review/merge the pull request.
This can take some time, so you may have to wait a bit.

If required you will have to update the PR to address the comments. That is all for today’s post.


 

Congratulations. You just learned the third part of how to publish your ROS 2 binary package.

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 use the Gazebo differential drive plugin in ROS 2

How to use the Gazebo differential drive plugin in ROS 2

In this post, you will learn how to use the Gazebo differential drive plugin in ros 2. This post answers the following question posted on ROS Answers.

Step 1: Copy a sample project with a ROS 2 Gazebo simulation using the differential drive plugin

“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 project. Once copied, click the red RUN button to launch the project in a virtual machine. Please be patient while the environment loads.

PS: You will need to log in 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 post assumes that you are working on The Construct; please adapt them to your local PC and ros2 installation.

Step 2: Find and explore the Gazebo model file containing the differential drive plugin

Now, we will find a Gazebo model file with the differential drive plugin. For this post, we’ll use the turtlebot3_ws/src/turtlebot3_simulations/turtlebot3_gazebo/models/turtlebot3_burger/model.sdf file. Sometimes it’s also defined as a .xacro file.

Head over to the Code Editor to explore this file as well as other files.

Open the Code Editor
Gazebo model file

Let’s examine the differential drive section of this plugin file and compare it with the one on the ROS Answers post.

turtlebot3_ws/src/turtlebot3_simulations/turtlebot3_gazebo/models/turtlebot3_burger/model.sdf

<plugin name="turtlebot3_diff_drive" filename="libgazebo_ros_diff_drive.so">

      <ros>
        <!-- <namespace>/tb3</namespace> -->
      </ros>

      <update_rate>30</update_rate>

      <!-- wheels -->
      <left_joint>wheel_left_joint</left_joint>
      <right_joint>wheel_right_joint</right_joint>

      <!-- kinematics -->
      <wheel_separation>0.160</wheel_separation>
      <wheel_diameter>0.066</wheel_diameter>

      <!-- limits -->
      <max_wheel_torque>20</max_wheel_torque>
      <max_wheel_acceleration>1.0</max_wheel_acceleration>

      <command_topic>cmd_vel</command_topic>

      <!-- output -->
      <publish_odom>true</publish_odom>
      <publish_odom_tf>true</publish_odom_tf>
      <publish_wheel_tf>false</publish_wheel_tf>

      <odometry_topic>odom</odometry_topic>
      <odometry_frame>odom</odometry_frame>
      <robot_base_frame>base_footprint</robot_base_frame>

    </plugin>

On lines 385 and 392, we see entries for the /cmd_vel (<command_topic>cmd_vel</command_topic>) and /odom (<odometry_topic>odom</odometry_topic>) topics respectively. However, these entries are missing in the file on ROS Answers.

Gazebo plugin file: mising command and odometry entries

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro"  name="robot">

   <gazebo>
        <plugin name="diff_drive" filename="libgazebo_ros_diff_drive.so">
            <!-- Wheel info-->
            <left_joint>left_wheel_joint</left_joint>
            <right_joint>right_wheel_joint</right_joint>
            <wheel_separation>0.35</wheel_separation>
            <wheel_diameter>0.1</wheel_diameter>

            <!-- Limits-->
            <max_wheel_torque>200</max_wheel_torque>
            <max_wheel_acceleration>10.0</max_wheel_acceleration>

            <!-- Output-->
            <odometry_frame>odom</odometry_frame>
            <robot_base_frame>base_link</robot_base_frame>

            <publish_odom>true</publish_odom> 
            <publish_odom_tf>true</publish_odom_tf> 
            <publish_wheel_tf>true</publish_wheel_tf> 

        </plugin>
     </gazebo>

</robot>

Are these lines really necessary for moving the robot and getting its odometry? Let’s find out!

Step 3: Investigate the impacts of the of the command and odometry topic tags

Let’s comment out those tags in the file, and see if we can find the /cmd_vel and /odom topics, and if they work.

turtlebot3_ws/src/turtlebot3_simulations/turtlebot3_gazebo/models/turtlebot3_burger/model.sdf (modified)

<plugin name="turtlebot3_diff_drive" filename="libgazebo_ros_diff_drive.so">

      <ros>
        <!-- <namespace>/tb3</namespace> -->
      </ros>

      <update_rate>30</update_rate>

      <!-- wheels -->
      <left_joint>wheel_left_joint</left_joint>
      <right_joint>wheel_right_joint</right_joint>

      <!-- kinematics -->
      <wheel_separation>0.160</wheel_separation>
      <wheel_diameter>0.066</wheel_diameter>

      <!-- limits -->
      <max_wheel_torque>20</max_wheel_torque>
      <max_wheel_acceleration>1.0</max_wheel_acceleration>

      <!-- <command_topic>cmd_vel</command_topic> -->

      <!-- output -->
      <publish_odom>true</publish_odom>
      <publish_odom_tf>true</publish_odom_tf>
      <publish_wheel_tf>false</publish_wheel_tf>

      <!-- <odometry_topic>odom</odometry_topic> -->
      <odometry_frame>odom</odometry_frame>
      <robot_base_frame>base_footprint</robot_base_frame>

    </plugin>

Now let’s run the package…in Terminal 1

cd
source turtlebot3_ws/install/setup.bash
export TURTLEBOT3_MODEL=burger
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py

…check the topics in Terminal 2

ros2 topic list

# output:
/clock
/cmd_vel
/imu
/joint_states
/odom
/parameter_events
/performance_metrics
/robot_description
/rosout
/scan
/tf
/tf_static

Well, the topics are there, but are they working? Let’s publish to the /cmd_vel topic and see if the robot moves. We also echo the /odom in another terminal. Run the following in Terminal 2:

# Try to move the robot with teleop
ros2 run teleop_twist_keyboard teleop_twist_keyboard

Then in Terminal 3:

ros2 topic echo /odom

The robot didn’t move and nothing was echoing from /odom!

Now we need to confirm that it’s not working because of those tags. Let’s modify the launch command in Terminal 1. Press Ctrl + C to stop the simulation and run the following commands instead:

# note that we changed the Turtlebot3 model
export TURTLEBOT3_MODEL=waffle
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py

Now try to move the robot again and check the /odom eching…Poof, both working! Why? We modified the model file for “burger”; the one for “waffle” was intact!

Final confirmation: uncomment the lines in turtlebot3_ws/src/turtlebot3_simulations/turtlebot3_gazebo/models/turtlebot3_burger/model.sdf , stop the simulation in Terminal 1 and run the following commands.

# note that we changed the Turtlebot3 model back to burger
export TURTLEBOT3_MODEL=burger
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py

Done! Now everything should be working!

So we have confirmed that the command and odometry tags are necessary.

Step 4: Check your learning

Do you understand how to use the Gazebo differential drive plugin in ROS 2? 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 use the Gazebo differential drive plugin in ROS 2

Here you go:

Feedback

Did you like this post? Do you have any questions about how to use the Gazebo differential drive plugin in ROS 2? 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.

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 integrate OpenCV with a ROS2 C++ node

How to integrate OpenCV with a ROS2 C++ node

In this post, you will learn how to integrate the OpenCV library with a ROS2 C++ node. The example shown builds into a “hello-world” binary for ROS2 integration with OpenCV that publishes an image to the ROS network.

After going through this post, you would be able to use OpenCV to do things related to image processing and computer vision and make the results available to other ROS2 nodes. The example uses ROS2 Humble.

Step 1: Fire up a system with ROS2 installation

“Hey, do I have to install ros2 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 ros2 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: Verify that OpenCV is installed

All ROS installs include OpenCV, so verify whether OpenCV has been installed.

Open Code Editor
Open a web shell

Open a web shell and run the following command:

pkg-config --modversion opencv4

You should get a version number similar to this:

4.5.4

If the above output is similar to what you see, you are set and ready, everything should work. Otherwise, please install OpenCV using the following command:

sudo apt install libopencv-dev python3-opencv

Step 3: Create a ROS2 C++ node integrating the OpenCV library

First, we need to create a package. We need the following dependencies for the package:

  • rclcpp – this is the ros2 C++ API we’ll use to create the ros2 node
  • std_msgs – needed for sending message header while sending the image
  • sensor_msgs – needed for sending the image itself
  • cv_bridge – converts from the OpenCV image format to the ros2 image format
  • image_transport – compresses the image for transport within the ros2 network
  • OpenCV – generates the image we want to send

Run the following command in the terminal you used in Step 2, to create the package:

cd ~/ros2_ws/src
ros2 pkg create my_opencv_demo --dependencies rclcpp std_msgs sensor_msgs cv_bridge image_transport OpenCV

Now go to the src folder of the package you just created and create the C++ file that will define the node:

cd ~/ros2_ws/src/my_opencv_demo/src
touch minimal_opencv_ros2_node.cpp

Open the Code Editor, locate the C++ file you just created, and paste the code indicated below. Explanations are given as comments within the code.

Open the Code Editor
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include "std_msgs/msg/header.hpp"
#include <chrono&gt;
#include <cv_bridge/cv_bridge.h&gt; // cv_bridge converts between ROS 2 image messages and OpenCV image representations.
#include <image_transport/image_transport.hpp&gt; // Using image_transport allows us to publish and subscribe to compressed image streams in ROS2
#include <opencv2/opencv.hpp&gt; // We include everything about OpenCV as we don't care much about compilation time at the moment.

using namespace std::chrono_literals;

class MinimalImagePublisher : public rclcpp::Node {
public:
  MinimalImagePublisher() : Node("opencv_image_publisher"), count_(0) {
    publisher_ =
        this-&gt;create_publisher<sensor_msgs::msg::Image&gt;("random_image", 10);
    timer_ = this-&gt;create_wall_timer(
        500ms, std::bind(&amp;MinimalImagePublisher::timer_callback, this));
  }

private:
  void timer_callback() {
    // Create a new 640x480 image
    cv::Mat my_image(cv::Size(640, 480), CV_8UC3);

    // Generate an image where each pixel is a random color
    cv::randu(my_image, cv::Scalar(0, 0, 0), cv::Scalar(255, 255, 255));

    // Write message to be sent. Member function toImageMsg() converts a CvImage
    // into a ROS image message
    msg_ = cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", my_image)
               .toImageMsg();

    // Publish the image to the topic defined in the publisher
    publisher_-&gt;publish(*msg_.get());
    RCLCPP_INFO(this-&gt;get_logger(), "Image %ld published", count_);
    count_++;
  }
  rclcpp::TimerBase::SharedPtr timer_;
  sensor_msgs::msg::Image::SharedPtr msg_;
  rclcpp::Publisher<sensor_msgs::msg::Image&gt;::SharedPtr publisher_;
  size_t count_;
};

int main(int argc, char *argv[]) {
  rclcpp::init(argc, argv);
  // create a ros2 node
  auto node = std::make_shared<MinimalImagePublisher&gt;();

  // process ros2 callbacks until receiving a SIGINT (ctrl-c)
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

Finally, edit the package’s CMakeLists.txt (~/ros2_ws/src/my_opencv_demo/CMakeLists.txt) file to recognize the node. Copy the lines of code shown below and paste them before the invocation of ament_package():

add_executable(minimal_opencv_ros2_node src/minimal_opencv_ros2_node.cpp)
ament_target_dependencies(minimal_opencv_ros2_node rclcpp std_msgs sensor_msgs cv_bridge image_transport OpenCV)
​
install(TARGETS
   minimal_opencv_ros2_node
   DESTINATION lib/${PROJECT_NAME}
 )

The CMakeLists.txt file should now look like this:

cmake_minimum_required(VERSION 3.8)
project(my_opencv_demo)

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

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(cv_bridge REQUIRED)
find_package(image_transport REQUIRED)
find_package(OpenCV REQUIRED)

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

add_executable(minimal_opencv_ros2_node src/minimal_opencv_ros2_node.cpp)
ament_target_dependencies(minimal_opencv_ros2_node rclcpp std_msgs sensor_msgs cv_bridge image_transport OpenCV)

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

ament_package()

Congratulations! You have created a ros2 C++ node integrating OpenCV. Now, we need to see if it works!

Step 4: Compile and test ROS2 C++ node integrating the OpenCV library

Generate the ros2 executable by compiling and sourcing the package:

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

If you got to this point, great! Now we will run the node:

ros2 run my_opencv_demo minimal_opencv_ros2_node

You should see some output similar to this:

[INFO] [1677071986.446315963] [opencv_image_publisher]: Image 0 published
[INFO] [1677071986.941745471] [opencv_image_publisher]: Image 1 published
[INFO] [1677071987.442009334] [opencv_image_publisher]: Image 2 published
[INFO] [1677071987.941677164] [opencv_image_publisher]: Image 3 published
[INFO] [1677071988.441115565] [opencv_image_publisher]: Image 4 published
[INFO] [1677071988.940492910] [opencv_image_publisher]: Image 5 published
[INFO] [1677071989.441007118] [opencv_image_publisher]: Image 6 published

Now let’s run ros2 topic list to confirm the existence of the image topic. Leave the node running in the current terminal and run the following command in a new terminal:

source ~/ros2_ws/install/setup.bash
ros2 topic list

The output should inclide the /random_image topic:

/parameter_events
/random_image
/rosout

Finally lets us see what the image produced by OpenCV looks like. Run the following in the same terminal where you ran ros2 topic list. If you get the error, (image_view:8839): Gdk-ERROR **: 13:30:34.498: The program 'image_view' received an X Window System error, just run the command again.

ros2 run image_view image_view --ros-args --remap image:=/random_image

You should now see something like this pop up on your screen. Yahoo!

Random image output from the OpenCV node

Great job! You have successfully created a ros2 C++ node integrating OpenCV!

Step 5: Extra: add text to the Image

Copy the lines of code shown below and paste them inside the timer callback function just before writing the message to be sent:

    // Declare the text position
    cv::Point text_position(15, 40);
    
    // Declare the size and color of the font
    int font_size = 1;
    cv::Scalar font_color(255, 255, 255);

    // Declare the font weight
    int font_weight = 2;

    // Put the text in the image
    cv::putText(my_image, "ROS2 + OpenCV", text_position, cv::FONT_HERSHEY_COMPLEX, font_size, font_color, font_weight);

Stop the currently running node, recompile and source the package, and re-run the node. You should now see something like this:

Random image with text from the OpenCV node

Step 6: Check your learning

Do you understand how to create a ros2 C++ node integrating OpenCV? If you don’t know it yet, please go over the post again, more carefully this time.

(Extra) Step 7: Watch the video to understand how to integrate the OpenCV library with a ROS2 C++ node

Here you go:

If you want to learn more about ROS 2 and dive deeper into robotics, check out the Robotics Developer Masterclass, where you’ll master robotics development from scratch and get 100% job-ready to work at robotics companies.




Feedback

Did you like this post? Do you have any questions about how to integrate the OpenCV library with a ROS2 C++ node? 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 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.

Pin It on Pinterest