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
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:
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).
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
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:
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:
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
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
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
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
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.
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:
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:
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:
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).
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:
An existing ROS2 Package, publicly available for everyone to use
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
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:
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:
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
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 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:
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.
Replaces the heading Forthcoming with version (date) (eg. 0.0.1 (2022-01-08)) in CHANGELOG.rst
Commits those changes
Creates a Git tag (eg. 0.0.1) to mark this point in the Git history
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:
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
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.
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
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:
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.
Let’s examine the differential drive section of this plugin file and compare it with the one on the ROS Answers post.
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.
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.
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.
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 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:
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.
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include "std_msgs/msg/header.hpp"
#include <chrono>
#include <cv_bridge/cv_bridge.h> // cv_bridge converts between ROS 2 image messages and OpenCV image representations.
#include <image_transport/image_transport.hpp> // Using image_transport allows us to publish and subscribe to compressed image streams in ROS2
#include <opencv2/opencv.hpp> // 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->create_publisher<sensor_msgs::msg::Image>("random_image", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&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_->publish(*msg_.get());
RCLCPP_INFO(this->get_logger(), "Image %ld published", count_);
count_++;
}
rclcpp::TimerBase::SharedPtr timer_;
sensor_msgs::msg::Image::SharedPtr msg_;
rclcpp::Publisher<sensor_msgs::msg::Image>::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>();
// 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():
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!
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:
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.
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:
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:
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 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
Once inside the terminal, let’s run the commands below:
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
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):
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
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:
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:
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.
“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.
First, we look at the turtlebot3_teleop package.
Let’s look for the executable file for this package. We can find that in the setup.py file.
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.
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.
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.