[ROS2 Q&A] 222 – How to use hidden /cmd_vel topic with ros1_bridge

[ROS2 Q&A] 222 – How to use hidden /cmd_vel topic with ros1_bridge

What we are going to learn

  1. How to launch ros1_bridge
  2. How to use available topics that don’t show up
  3. How to connect to The Construct’s Real Robot Lab RoBoX

List of resources used in this post

  1. The Construct: https://app.theconstructsim.com/
  2. RoBoX Real Robot Lab: https://app.theconstructsim.com/#/RealRobot
  3. Repositories:
    1. https://github.com/ROBOTIS-GIT/turtlebot3
    2. https://github.com/ROBOTIS-GIT/turtlebot3_msgs
    3. https://github.com/ROBOTIS-GIT/turtlebot3_simulations
  4. This post answers the following question asked on ROS Answers: https://answers.ros.org/question/359969/ros2-foxy-ros1_bridge-topics-missing/

Creating a rosject

In order to learn how to use hidden topics with ros1_bridge, let’s start by creating a rosject (ROS project). We are going to use The Construct (https://www.theconstruct.ai/) for this tutorial, but if you have ROS2 installed on your own computer, you should be able to do ~everything on your own computer, except this creating a rosject part.

Let’s start by opening The Construct (https://www.theconstruct.ai/) and logging in. You can easily create a free account if you still don’t have one.

Once inside, let’s create My Rosjects and then, Create a new rosject:

My Rosjects

My Rosjects

 

Create a new rosject

Create a new rosject

For the rosject, let’s select ROS2 Foxy for the ROS Distro, let’s name the rosject as Turtlebot3 hidden topics. You can leave the rosject public.

If you mouse over the recently created rosject, you should see a Run button. Just click that button to launch the rosject.

Turtlebot3 hidden topics in ROS2

Turtlebot3 hidden topics in ROS2

Cloning the required repositories with the simulations

After we have created our rosject and opened it, let’s now open a web shell as we can see in the image below:

Open a new Web Shell/Terminal

Open a new Web Shell/Terminal

After that, let’s enter into the ~/simulation_ws/src directory and clone three repositories there with the following commands:

cd ~/simulation_ws/src/
git clone https://github.com/ROBOTIS-GIT/turtlebot3

git clone https://github.com/ROBOTIS-GIT/turtlebot3_msgs

git clone https://github.com/ROBOTIS-GIT/turtlebot3_simulations

Starting the simulation

Once we have correctly cloned the repositories that contain the simulation and ROS messages needed to simulate Turtlebot with ROS, we now need to start the simulation.

The rosject that we created comes by default with ROS2 Noetic and ROS2 Foxy installed.

In order to test the simulation in ROS2, let’s first launch it with ROS1 to make sure the simulation is working properly at least in ROS1. We will then use ROS1 Bridge to be able to see the topics in ROS2.

Let’s start by sourcing ros1 noetic with, and sourcing our simulation_ws/devel folder as follows:

cd ~/simulation_ws

source /opt/ros/noetic/setup.bash

source devel/setup.bash

After having sourced the workspace that contains the simulation, we should now be able to launch it with the following commands:

export TURTLEBOT3_MODEL=burger

roslaunch turtlebot3_gazebo turtlebot3_empty_world.launch

 

Now that the simulation has been launched, we can open gazebo in order to see it. For that, hover your mouse over the button that has the icon of gazebo and click Open Gazebo. After that, you should be able to see the turthebot3 simulation in ROS1, which means the simulation is well defined, and the meshes are correct.

Open Gazebo by clicking Open Gazebo

Open Gazebo by clicking Open Gazebo

Launching ROS1 Bridge

Let’s start by sourcing the ros workspaces in order. Let’s start by sourcing ROS1, then ROS2, and finally, run the bridge. For that, open a second web shell and type the following:

source /opt/ros/noetic/setup.bash

source /opt/ros/foxy/setup.bash

ros2 run ros1_bridge dynamic_bridge --bridge-all-topics

If everything went well, you should have seen some logs like the following:

created 1to2 bridge for topic '/clock' with ROS 1 type 'rosgraph_msgs/Clock' and ROS 2 type 'rosgraph_msgs/msg/Clock'
created 1to2 bridge for topic '/gazebo/link_states' with ROS 1 type 'gazebo_msgs/LinkStates' and ROS 2 type 'gazebo_msgs/msg/LinkStates'
created 1to2 bridge for topic '/gazebo/model_states' with ROS 1 type 'gazebo_msgs/ModelStates' and ROS 2 type 'gazebo_msgs/msg/ModelStates'
created 1to2 bridge for topic '/imu' with ROS 1 type 'sensor_msgs/Imu' and ROS 2 type 'sensor_msgs/msg/Imu'
created 1to2 bridge for topic '/joint_states' with ROS 1 type 'sensor_msgs/JointState' and ROS 2 type 'sensor_msgs/msg/JointState'
created 1to2 bridge for topic '/odom' with ROS 1 type 'nav_msgs/Odometry' and ROS 2 type 'nav_msgs/msg/Odometry'
created 1to2 bridge for topic '/rosout' with ROS 1 type 'rosgraph_msgs/Log' and ROS 2 type 'rcl_interfaces/msg/Log'
created 1to2 bridge for topic '/rosout_agg' with ROS 1 type 'rosgraph_msgs/Log' and ROS 2 type 'rcl_interfaces/msg/Log'
created 1to2 bridge for topic '/scan' with ROS 1 type 'sensor_msgs/LaserScan' and ROS 2 type 'sensor_msgs/msg/LaserScan'
created 1to2 bridge for topic '/tf' with ROS 1 type 'tf2_msgs/TFMessage' and ROS 2 type 'tf2_msgs/msg/TFMessage'
Created 2 to 1 bridge for service /gazebo/clear_body_wrenches
Created 2 to 1 bridge for service /gazebo/clear_joint_forces
Created 2 to 1 bridge for service /gazebo/delete_light
Created 2 to 1 bridge for service /gazebo/delete_model
Created 2 to 1 bridge for service /gazebo/get_joint_properties
Created 2 to 1 bridge for service /gazebo/get_light_properties
Created 2 to 1 bridge for service /gazebo/get_link_properties
Created 2 to 1 bridge for service /gazebo/get_link_state
Created 2 to 1 bridge for service /gazebo/get_model_properties
Created 2 to 1 bridge for service /gazebo/get_model_state
Created 2 to 1 bridge for service /gazebo/get_physics_properties
Created 2 to 1 bridge for service /gazebo/get_world_properties
Created 2 to 1 bridge for service /gazebo/pause_physics
Created 2 to 1 bridge for service /gazebo/reset_simulation
Created 2 to 1 bridge for service /gazebo/reset_world
Created 2 to 1 bridge for service /gazebo/set_joint_properties
Created 2 to 1 bridge for service /gazebo/set_link_properties
Created 2 to 1 bridge for service /gazebo/set_link_state
Created 2 to 1 bridge for service /gazebo/set_model_configuration
Created 2 to 1 bridge for service /gazebo/set_model_state
Created 2 to 1 bridge for service /gazebo/set_physics_properties
Created 2 to 1 bridge for service /gazebo/spawn_sdf_model
Created 2 to 1 bridge for service /gazebo/spawn_urdf_model
Created 2 to 1 bridge for service /gazebo/unpause_physics
Created 2 to 1 bridge for service /imu_service
[INFO] [1641212256.136511189] [ros_bridge]: Passing message from ROS 1 rosgraph_msgs/Clock to ROS 2 rosgraph_msgs/msg/Clock (showing msg only once per type)
[INFO] [1641212256.138899978] [ros_bridge]: Passing message from ROS 1 gazebo_msgs/LinkStates to ROS 2 gazebo_msgs/msg/LinkStates (showing msg only once per type)
created 2to1 bridge for topic '/gazebo/link_states' with ROS 2 type 'gazebo_msgs/msg/LinkStates' and ROS 1 type 'gazebo_msgs/LinkStates'
[INFO] [1641212256.140508214] [ros_bridge]: Passing message from ROS 1 gazebo_msgs/ModelStates to ROS 2 gazebo_msgs/msg/ModelStates (showing msg only once per type)
created 2to1 bridge for topic '/rosout' with ROS 2 type 'rcl_interfaces/msg/Log' and ROS 1 type'rosgraph_msgs/Log'
[INFO] [1641212256.141466301] [ros_bridge]: Passing message from ROS 1 sensor_msgs/Imu to ROS 2sensor_msgs/msg/Imu (showing msg only once per type)
[INFO] [1641212256.142755982] [ros_bridge]: Passing message from ROS 1 sensor_msgs/JointState to ROS 2 sensor_msgs/msg/JointState (showing msg only once per type)
removed 2to1 bridge for topic '/gazebo/link_states'
[INFO] [1641212256.144608098] [ros_bridge]: Passing message from ROS 1 nav_msgs/Odometry to ROS2 nav_msgs/msg/Odometry (showing msg only once per type)
[INFO] [1641212256.146602236] [ros_bridge]: Passing message from ROS 1 tf2_msgs/TFMessage to ROS 2 tf2_msgs/msg/TFMessage (showing msg only once per type)
[INFO] [1641212256.160265245] [ros_bridge]: Passing message from ROS 2 rcl_interfaces/msg/Log to ROS 1 rosgraph_msgs/Log (showing msg only once per type)
[INFO] [1641212256.466793392] [ros_bridge]: Passing message from ROS 1 sensor_msgs/LaserScan toROS 2 sensor_msgs/msg/LaserScan (showing msg only once per type)
[INFO] [1641212256.469177152] [ros_bridge]: Passing message from ROS 1 rosgraph_msgs/Log to ROS2 rcl_interfaces/msg/Log (showing msg only once per type)

 

Checking the /cmd_vel topic

Now that ros1_bridge is up and running, let’s open a third terminal and list the ros2 topics with:

ros2 topic list

You should have seen at least the following topics:

/clock
/gazebo/link_states
/gazebo/model_states
/imu
/joint_states
/odom
/parameter_events
/rosout
/rosout_agg
/scan
/tf

If we look carefully at the list of topics above, we cannot see any /cmd_vel topic. But the topic exists in ROS1. To ensure the topic exists in ROS1, let’s open a fourth terminal and list the topics with:

source /opt/ros/noetic/setup.bash

rostopic list

The list should contain something like the output below, showing that indeed we have a topic called /cmd_vel (the second one in the list below).

/clock
/cmd_vel
/gazebo/link_states
/gazebo/model_states
/gazebo/parameter_descriptions
/gazebo/parameter_updates
/gazebo/performance_metrics
/gazebo/set_link_state
/gazebo/set_model_state
/imu
/joint_states
/odom
/rosout
/rosout_agg
/scan
/tf

And this is what confuses some users. How is that /cmd_vel is available in ros1 but it is not available on ros2? Well, we still do not have a definite answer on why this happens, but we know that if in ROS2 we start publishing in that topic, it will be received by ROS1 and the robot will move accordingly, even though we are not able to see the topic with ros2 topic list. Let’s try that.

First, if we were to publish messages in the /cmd_vel topic using ROS1, the command would be as follows (you do not need to run the command below):

rostopic pub /cmd_vel geometry_msgs/Twist "linear:
  x: 0.0
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 0.0"

For ros2, the message instead of geometry_msgs/Twist would be geometry_msgs/msg/Twist. Since ros2 does not auto-complete because it does not know the /cmd_vel topic, we can copy the auto-completed values for geometry_msgs/Twist in ROS1 and pass it to ros2.

Let’s go back to the third terminal where ROS2 Foxy is sourced by default and run the following command:

ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "linear:
  x: 0.0
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 0.3"

You should have seen something like what we have below:

publisher: beginning loop
publishing #1: geometry_msgs.msg.Twist(linear=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.0), angular=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.3))
publishing #2: geometry_msgs.msg.Twist(linear=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.0), angular=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.3))
publishing #3: geometry_msgs.msg.Twist(linear=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.0), angular=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.3))
publishing #4: geometry_msgs.msg.Twist(linear=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.0), angular=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.3))
publishing #5: geometry_msgs.msg.Twist(linear=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.0), angular=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.3))

And the robot should now be rotating.

As you can see, with ROS2 we are able to move the robot that is running with ROS1, even if we cannot list the topic with ros2.

By looking at the logs of the second web shell, where ros1_bridge was launched, we can also confirm that the message is being correctly interchanged from the ROS2 to the ROS1 /cmd_vel topic:

created 2to1 bridge for topic '/cmd_vel' with ROS 2 type 'geometry_msgs/msg/Twist' and ROS 1 type ''
[INFO] [1641214498.129565691] [ros_bridge]: Passing message from ROS 2 geometry_msgs/msg/Twistto ROS 1 geometry_msgs/Twist (showing msg only once per type)
removed 2to1 bridge for topic '/cmd_vel'
created 2to1 bridge for topic '/cmd_vel' with ROS 2 type 'geometry_msgs/msg/Twist' and ROS 1 type ''

So, that is it. We just learned that even when some topics are not visible in ROS2 through ros1_bridge, we can still publish messages on them using ROS2.

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.

[ROS2 Q&A] 226 – Work with ROS2 turtlesim

[ROS2 Q&A] 226 – Work with ROS2 turtlesim

In this post it will be shown how to work with Turtlesim with ROS 2, one of the best ways of testing ROS2 features with a very simple simulation

Configuring environment

In order to do that in a way anyone can reproduce, let’s use the App of TheconstructSim. Start by creating a new rosject here

After creating it, just hit the Run button and wait for the desktop environment to get ready.

Launch turtlesim simulation

Start by opening a web shell and executing the following command:

ros2 run turtlesim turtlesim_node

You must have some logs in the shell and the simulation ready in the graphical tools, like the image below:

Control using the keyboard

In order to control the robot using the teleop keyboard, we must check if the topics match. In that case, turtlesim has a namespace and the teleop keyboard must be re-mapped.

Check the topics of the simulator:

ros2 topic list

In that case, we need to map the keyboard to the topic /turtle1/cmd_vel

This is quite simple by using the cli, just run the node adding an argument, like below:

ros2 run teleop_twist_keyboard teleop_twist_keyboard cmd_vel:=/turtle1/cmd_vel

This is a quick way to start working with mobile robots. You can try many different algorithms quickly without having to configure a more complex simulation.

Related courses & material:

[ROS Q&A] 225 – Setup your ROS2 Workspace with Unlimited Node Files

[ROS Q&A] 225 – Setup your ROS2 Workspace with Unlimited Node Files

In this post it will be shown how to organize your ROS2 package in such manner that allows you to have as many python scripts as you need. It is a quite simple way of doing it, but can be confusing for those who are coming from ROS 1. Let’s check it out!

Configuring environment

In order to do that in a way anyone can reproduce, let’s use the App of TheconstructSim. Start by creating a new rosject here

After creating it, just hit the Run button and wait for the desktop environment to get ready.

 

Create the ROS package

With the environment ready, create a new ROS 2 package inside the given workspace. Use the commands below:

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python pkg1 --dependencies rclpycies rclpy 

You must have the package created like in the image below:

 

Create the script files

Create the first script files you want to have in your package:

cd ~/ros2_ws/src

touch pkg1/pkg1/hello_world.py
chmod +x pkg1/pkg1/hello_world.py

touch pkg1/pkg1/goodbye_world.py
chmod +x pkg1/pkg1/goodbye_world.py

You must be able to edit the files from the IDE at this point:

Go to the root workspace:

  • Compile the package
  • Source the workspace
  • Check that your package is added to ROS environment
cd ~/ros2_ws
colcon build

source install/setup.bash

ros2 pkg list | grep pkg1

 

Add some code to the scripts to have some nodes

Add the code below to the hello_world.py script:

import rclpy
from rclpy.node import Node

class MyNode(Node):
    def __init__(self):
        # call super() in the constructor in order to initialize the Node object
        # the parameter we pass is the node name
        super().__init__('hello_world')
        # create a timer sending two parameters:
        # - the duration between 2 callbacks (0.2 seeconds)
        # - the timer function (timer_callback)
        self.create_timer(0.2, self.timer_callback)
        
    def timer_callback(self):
        # print a ROS2 log on the terminal with a great message!
        self.get_logger().info("Hello World!")

def main(args=None):
    # initialize the ROS communication
    rclpy.init(args=args)
    # declare the node constructor
    node = MyNode()
    # pause the program execution, waits for a request to kill the node (ctrl+c)
    rclpy.spin(node)
    # shutdown the ROS communication
    rclpy.shutdown()

if __name__ == '__main__':
    main()

And the same for the goodbye_world.py, just changing the info text

import rclpy
from rclpy.node import Node

class MyNode(Node):
    def __init__(self):
        # call super() in the constructor in order to initialize the Node object
        # the parameter we pass is the node name
        super().__init__('hello_world')
        # create a timer sending two parameters:
        # - the duration between 2 callbacks (0.2 seeconds)
        # - the timer function (timer_callback)
        self.create_timer(0.2, self.timer_callback)
        
    def timer_callback(self):
        # print a ROS2 log on the terminal with a great message!
        self.get_logger().info("Goodbye World!")

def main(args=None):
    # initialize the ROS communication
    rclpy.init(args=args)
    # declare the node constructor
    node = MyNode()
    # pause the program execution, waits for a request to kill the node (ctrl+c)
    rclpy.spin(node)
    # shutdown the ROS communication
    rclpy.shutdown()

if __name__ == '__main__':
    main()

 

Create a launch file

In order to run both scripts at the same time, let’s create a launch file:

cd ~/ros2_ws/src
touch pkg1/launch/hello_goodbye.launch
chmod +x pkg1/launch/hello_goodbye.launch

Add the following content:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='pkg1',
            executable='hello_world',
            output='screen'),
        Node(
            package='pkg1',
            executable='goodbye_world',
            output='screen'),
    ])

 

Configure the setup.py of the package

In order to add the launch file to the setup, it must contain some libraries like os and glob. The content of the file ~/ros2_ws/src/pkg1/setup.py will look like below:

from setuptools import setup
import os
from glob import glob

package_name = 'pkg1'

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

 

Compile and run

Compile the package once more and run the launch file

cd ~/ros2_ws
colcon build

source install/setup.bash

ros2 launch pkg1 hello_goodbye.launch.py

The expected result is in the image below:

 

Related courses & material:

[ROS2 Q&A] 224 – How to source ROS1 & ROS2 side by side

[ROS2 Q&A] 224 – How to source ROS1 & ROS2 side by side

What we are going to learn

  1. How to only source one ROS distro at a time
  2. How to create new ROS2 workspaces

List of resources used in this post

  1. The Construct: https://app.theconstructsim.com/
  2. ROS Development Studio (ROSDS) —▸ http://rosds.online
  3. Robot Ignite Academy –▸ https://www.robotigniteacademy.com
  4. ROS2 Tutorials –▸
    1. https://app.theconstructsim.com/#/Course/61
    2. https://app.theconstructsim.com/#/Course/50
  5. This video answers the following question asked on ROS Answers: https://answers.ros.org/question/389464/why-does-ros2-installsetupbash-source-ros1-and-ros2/

Creating a rosject

In order to learn how to create conditional publishers, let’s start by creating a publisher and a subscriber in Python. We are going to use The Construct (https://www.theconstruct.ai/) for this tutorial, but if you have ROS2 installed on your own computer, you should be able to do ~everything on your own computer, except this creating a rosject part.

Let’s start by opening The Construct (https://www.theconstruct.ai/) and logging in. You can easily create a free account if you still don’t have one.

Once inside, let’s create My Rosjects and then, Create a new rosject:

My Rosjects

My Rosjects

 

Create a new rosject

Create a new rosject

For the rosject, let’s select ROS2 Foxy for the ROS Distro, let’s name the rosject as ROS Q&A 224. You can leave the rosject public.

If you mouse over the recently created rosject, you should see a Run button. Just click that button to launch the rosject.

Checking enviroment configuration

Open a webshell and go to the installation folder of ROS, like shown below:

You can see there are installed two ROS distros, ROS 1 Noetic and ROS 2 Foxy

That means both systems are installed, like the user shown his workspace. Now, open the code editor and check the workspaces:

There are two main workspaces, catkin_ws (for ROS noetic) and ros2_ws (for ROS 2 foxy)

Configuring an environment with both setups

The quickest way to have a ROS environment with both ROS and ROS2 is by creating a new workspace. In the webshell, execute the following:

Open in the IDE the file ~/new_ros2_ws/install/setup.bash. This is a fresh new ROS2 workspace, it does not have ROS noetic attached to it. It is shown in the image below, no ROS commands (e.g: roscore) are available:

You can have as many ROS 2 workspaces as you want and the fresh new workspace built does not have the setup to attache ROS 1 to it, unless you configure to do so.

 

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.

[ROS2 Q&A] 219 – How to Detect Obstacles in ROS2 Foxy with TurtleBot3

[ROS2 Q&A] 219 – How to Detect Obstacles in ROS2 Foxy with TurtleBot3

What we are going to learn

  1. How to clone ROS2 Foxy’s ROBOTIS repositories
  2. How to set up ROS2 environment
  3. How to run ROS2 nodes downloaded from GitHub

List of resources used in this post

  1. ROS Development Studio (ROSDS) —▸ http://rosds.online
  2. Robot Ignite Academy –▸ https://www.robotigniteacademy.com
  3. The question that this post answers –▸ https://answers.ros.org/question/369896/file-turtlebot3-obstacle-detection-was-not-found-in-the-share-directory-of-package-turtlebot3-example/
  4. ROS2 Tutorials –▸
    1. https://app.theconstructsim.com/#/Course/61
    2. https://app.theconstructsim.com/#/Course/50
  5. TurtleBot3 Manual –▸ https://emanual.robotis.com/docs/en/platform/turtlebot3/overview/
  6. Repositories –▸
    1. https://github.com/ROBOTIS-GIT/turtlebot3_simulations
    2. https://github.com/ROBOTIS-GIT/turtlebot3
    3. https://github.com/ROBOTIS-GIT/turtlebot3_msgs
  7. Use the rosject: https://app.theconstructsim.com/#/l/3e2282a3/

Creating a rosject

In order to detect obstacles in ROS2 with TurtleBot3, let’s start by creating a rosject. We are going to use The Construct (https://www.theconstruct.ai/) for this tutorial, but if you have ROS2 installed on your own computer, you should be able to do ~everything on your own computer, except this creating a rosject part.

Let’s start by opening The Construct (https://www.theconstruct.ai/) and logging in. You can easily create a free account if you still don’t have one.

Once inside, let’s create My Rosjects and then, Create a new rosject:

My Rosjects

My Rosjects

 

Create a new rosject

Create a new rosject

For the rosject, let’s select ROS2 Foxy for the ROS Distro, let’s name the rosject as Turtlebot3 Obstacle Detection. You can leave the rosject public.

Turtlebot3 Obstacle Detection rosject for ros2

Turtlebot3 Obstacle Detection rosject for ros2

If you mouse over the recently created rosject, you should see a Run button. Just click that button to launch the rosject.

Cloning the Turtlebot3 repositories

Once the rosject is open, let’s start by cloning the necessary repositories.

Let’s open a new terminal by clicking on the Open a new shell window button:

Open a new shell

Open a new shell

After having a web shell open, let’s enter our ros2_ws/src folder:

cd ~/ros2_ws/src

Let’s now source our ROS Foxy environment with:

source /opt/ros/foxy/setup.bash

It is worth mentioning that this source command has to be executed in all web shells that you open. If you want to automatically run that command for every shells, just add it to the .bashrc with:

echo 'source /opt/ros/foxy/setup.bash' >> ~/.bashrc

Now, when new shells are opened, that command will be executed automatically.

Now let’s clone the turtlebot3 repositories. Let’s specify the foxy-devel branch:

git clone -b foxy-devel https://github.com/ROBOTIS-GIT/turtlebot3

git clone -b foxy-devel https://github.com/ROBOTIS-GIT/turtlebot3_msgs

git clone -b foxy-devel https://github.com/ROBOTIS-GIT/turtlebot3_simulations

Before compiling our ROS2 workspace, let’s first install a package that is required:

sudo apt-get update 

sudo apt-get install -y ros-foxy-dynamixel-sdk

We should now be able to compile our workspace with:

cd ~/ros2_ws/

colcon build

If everything went ok, the output you should have got should be similar to the following:

user:~/ros2_ws$ colcon build
Starting >>> turtlebot3_msgs
Starting >>> turtlebot3_description
Starting >>> turtlebot3_cartographer
Starting >>> turtlebot3_navigation2
Starting >>> turtlebot3_teleop
Finished <<< turtlebot3_teleop [1.66s]
Finished <<< turtlebot3_cartographer [12.0s]
Finished <<< turtlebot3_navigation2 [12.0s]
Finished <<< turtlebot3_description [12.3s]
[Processing: turtlebot3_msgs]
Finished <<< turtlebot3_msgs [1min 3s]
Starting >>> turtlebot3_node
Starting >>> turtlebot3_example
Starting >>> turtlebot3_fake_node
Finished <<< turtlebot3_example [1.56s]
Finished <<< turtlebot3_fake_node [14.2s]
[Processing: turtlebot3_node]
Finished <<< turtlebot3_node [52.1s]
Starting >>> turtlebot3_bringup
Finished <<< turtlebot3_bringup [2.69s]
Starting >>> turtlebot3
Finished <<< turtlebot3 [2.77s]
Starting >>> turtlebot3_gazebo
[Processing: turtlebot3_gazebo]
Finished <<< turtlebot3_gazebo [57.1s]
Starting >>> turtlebot3_simulations
Finished <<< turtlebot3_simulations [2.74s]

Summary: 12 packages finished [3min 1s]

Launching a Turtlebot3 simulation

Now that our workspace is compiled, let’s source it with:

source ~/ros2_ws/install/setup.bash

and start a simulation. For that, let’s use the following commands:

export TURTLEBOT3_MODEL=burger

ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py

Now that the simulation was launched, we could open Gazebo by clicking the Gazebo button as we can see in the image below:

Turtlebot3 Obstacle Detection rosject - Open Gazebo

Turtlebot3 Obstacle Detection rosject – Open Gazebo

Launching the teleop keyboard

Now that the simulation is up and running, let’s run the teleop keyboard in oder to easily move the robot around.

For that, let’s open another web shell. After that, let’s source our environment:

cd ~/ros2_ws

source install/setup.bash

export TURTLEBOT3_MODEL=burger
ros2 run turtlebot3_teleop teleop_keyboard

You should now be able to move the robot around by pressing the keys as instructed in your web shell, using the keys w, a, s, d, and x.

Moving around:
  w
a s d
  x

Launching the Object Detector

Awesome. Now we have our simulation running, the keyboard teleoperator running, it is now time to run our Object Detector.

For that, let’s open a new terminal (the third one). After having it open, let’s source our workspace again:

cd ~/ros2_ws

source install/setup.bash

export TURTLEBOT3_MODEL=burger

ros2 run turtlebot3_example turtlebot3_obstacle_detection

The obstacle detection should now be working properly. It should output something similar to the following:

[INFO] [1638920304.914586963] [turtlebot3_obstacle_detection]: Turtlebot3 obstacle detection node has been initialised.

Since it may be subscribed to the /cmd_vel_raw topic, let’s kill the keyboard teleop launched in the second terminal, and run it again as follows (using the /cmd_vel_raw topic):

export TURTLEBOT3_MODEL=burger
ros2 run turtlebot3_teleop teleop_keyboard /cmd_vel:=/cmd_vel_raw

If you now move next to the wall, the terminal where the Obstacle Detector node was launched, you should see some messages saying that an obstacle was detected. Something similar to:

[INFO] [1638920462.116441990] [turtlebot3_obstacle_detection]: Obstacles are detected nearby. Robot stopped.
[INFO] [1638920462.126177608] [turtlebot3_obstacle_detection]: Obstacles are detected nearby. Robot stopped.
[INFO] [1638920462.136174240] [turtlebot3_obstacle_detection]: Obstacles are detected nearby. Robot stopped.
[INFO] [1638920462.146231079] [turtlebot3_obstacle_detection]: Obstacles are detected nearby. Robot stopped.
[INFO] [1638920462.160709358] [turtlebot3_obstacle_detection]: Obstacles are detected nearby. Robot stopped.

Congratulations. You now know how to detect obstacles with Turtlebot3 using ROS2.

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube (important: the video uses the ROS Dashing version, but it may help you anyway). If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

 

[ROS2 Q&A] 218 – How to Create Conditional Publisher in ROS2

[ROS2 Q&A] 218 – How to Create Conditional Publisher in ROS2

What we are going to learn

  1. How to create a simple publisher and subscriber
  2. How to work with incoming messages
  3. How to add condition to topic publication

List of resources used in this post

  1. ROS Development Studio (ROSDS) —▸ http://rosds.online
  2. Robot Ignite Academy –▸ https://www.robotigniteacademy.com
  3. ROS2 Cookbook –▸ https://github.com/mikeferguson/ros2_cookbook/blob/main/pages/launch.md
  4. Question asked on ROS Answers –▸ https://answers.ros.org/question/373178/conditional-statement-for-publisher-and-subscriber/

Creating a rosject

In order to learn how to create conditional publishers, let’s start by creating a publisher and a subscriber in Python. We are going to use The Construct (https://www.theconstruct.ai/) for this tutorial, but if you have ROS2 installed on your own computer, you should be able to do ~everything on your own computer, except this creating a rosject part.

Let’s start by opening The Construct (https://www.theconstruct.ai/) and logging in. You can easily create a free account if you still don’t have one.

Once inside, let’s create My Rosjects and then, Create a new rosject:

My Rosjects

My Rosjects

 

Create a new rosject

Create a new rosject

For the rosject, let’s select ROS2 Foxy for the ROS Distro, let’s name the rosject as Battery Level Node. You can leave the rosject public.

Battery Level Node - Conditional Publisher in ROS2

Battery Level Node – Conditional Publisher in ROS2

If you mouse over the recently created rosject, you should see a Run button. Just click that button to launch the rosject.

Writing a simple publisher and subscriber (Python)

Once the rosject is open, we can now create our publisher and subscriber. For that, we are going to use ROS Docs as a reference.

Let’s open a new terminal by clicking on the Open a new shell window button:

Open a new shell

Open a new shell

Once the terminal is open, we can list the files with the ls command:

user:~$ ls
ai_ws  catkin_ws  notebook_ws  ros2_ws  simulation_ws  webpage_ws

We can see a workspace named ros2_ws. Let’s enter that workspace using cd ros2_ws/:

user:~$ cd ros2_ws/
user:~/ros2_ws$

Let’s now source our workspace with:

source ~/ros2_ws/install/setup.bash

Let’s now enter our src folder:

 cd ~/ros2_ws/src/

And create a package named py_pubsub (python publisher and subscriber):

ros2 pkg create --build-type ament_python py_pubsub

The output should be similar to:

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

Be aware that in order to create this package, we basically used ROS Docs for reference.

If you now list your src folder using ls, you should be able to see our package:

user:~/ros2_ws/src$ ls
py_pubsub

We can now enter into this py_pubsub package using cd py_pubsub/. We will find a new folder named py_pubsub inside it. Let’s just enter into it. The full command would be as easy as cd ~/ros2_ws/src/py_pubsub/py_pubsub/ . But if you want to go step by step:

user:~/ros2_ws/src$ cd py_pubsub/
user:~/ros2_ws/src/py_pubsub$ ls
package.xml  py_pubsub  resource  setup.cfg  setup.py  test
user:~/ros2_ws/src/py_pubsub$ cd py_pubsub/
user:~/ros2_ws/src/py_pubsub/py_pubsub$ ls
__init__.py
user:~/ros2_ws/src/py_pubsub/py_pubsub$

Now inside that last py_pubsub folder, let’ download a publisher and a subscriber named publisher_member_function.py and subscriber_member_function.py respectively. Bear in mind that up to this point we are basically following the docs aforementioned:

cd ~/ros2_ws/src/py_pubsub/py_pubsub

wget https://raw.githubusercontent.com/ros2/examples/foxy/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function.py

wget https://raw.githubusercontent.com/ros2/examples/foxy/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_member_function.py

We should now have three files inside our py_pubsub folder/package:

cd ~/ros2_ws/src/py_pubsub/py_pubsub; ls

__init__.py publisher_member_function.py subscriber_member_function.py

Let’s now open the publisher_member_function.py using the Code Editor:

ROS2 Publisher

ROS2 Publisher

Let’s modify the timer_callback method of the publisher_member_funcion.py, so that instead of increasing the initial value, let’s decrease it. Let’s also modify our constructor (__init__ method) to make the initial value of the i variable be 100.

What this means is that the i variable will initially have the value 100, then it is going to decrease it. The final content of the   publisher_member_funcion.py file should be similar to the following:

# Copyright 2016 Open Source Robotics Foundation, Inc.
#
# 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 rclpy
from rclpy.node import Node
from std_msgs.msg import String

class MinimalPublisher(Node):

    def __init__(self):
        super().__init__('minimal_publisher')
        self.publisher_ = self.create_publisher(String, 'topic', 10)
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.i = 100

    def timer_callback(self):
        msg = String()
        msg.data = 'Battery Level: %d' % self.i
        self.publisher_.publish(msg)
        self.get_logger().info('Publishing: "%s"' % msg.data)
        self.i -= 1

def main(args=None):
    rclpy.init(args=args)
    minimal_publisher = MinimalPublisher()
    rclpy.spin(minimal_publisher)
    # Destroy the node explicitly
    # (optional - otherwise it will be done automatically
    # when the garbage collector destroys the node object)
    minimal_publisher.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()
Now let’s open also our subscriber_member_function.py file and modify the listener_callback method so that it will have the following content:
def listener_callback(self, msg):
        if msg.data in ['Battery Level: 70', 'Battery Level: 69', 'Battery Level: 68', 'Battery Level: 67', 'Battery Level: 66']:
            self.get_logger().info('WARNING!: "%s"' % msg.data)
            self.publisher_.publish(msg)

What the changes are going to do is: if the battery level is between 70 and 66 %, then a warning message will be printed, and that warning will be published into a topic named /battery.

We also have to create the publisher into the constructor (__init__ method) with:

 self.publisher_ = self.create_publisher(String, 'battery', 10)

So in the end, the final subscriber will look like this:

# Copyright 2016 Open Source Robotics Foundation, Inc.
#
# 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 rclpy
from rclpy.node import Node
from std_msgs.msg import String

class MinimalSubscriber(Node):

    def __init__(self):
        super().__init__('minimal_subscriber')
        self.subscription = self.create_subscription(
            String,
            'topic',
            self.listener_callback,
            10)
        self.subscription  # prevent unused variable warning
        self.publisher_ = self.create_publisher(String, 'battery', 10)

    def listener_callback(self, msg):

        if msg.data in ['Battery Level: 70', 'Battery Level: 69', 'Battery Level: 68', 'Battery Level: 67', 'Battery Level: 66']:
            self.get_logger().info('WARNING!: "%s"' % msg.data)
            self.publisher_.publish(msg)

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

    minimal_subscriber = MinimalSubscriber()
    rclpy.spin(minimal_subscriber)

    # Destroy the node explicitly
    # (optional - otherwise it will be done automatically
    # when the garbage collector destroys the node object)
    minimal_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Now that we have our publisher and subscriber, ready where the subscriber contains a conditional publisher,  now open our package.xml file and add rclpy and std_msgs as dependencies, by adding the two lines below after ament_python:

<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>

The final result should be similar to the image below:

exec_depend rcl_py and std_msgs

exec_depend rcl_py and std_msgs

Let’s now open the setup.py file and add our publisher and subscriber (let’s call them talker and listener respectively) to the entry_points. Our final entry_points should look like:

entry_points={
        'console_scripts': [
                'talker = py_pubsub.publisher_member_function:main',
                'listener = py_pubsub.subscriber_member_function:main',
        ],
},

If you are wondering whether your final setup.py file is correct, yours should look like this:

from setuptools import setup

package_name = 'py_pubsub'

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

Building our workspace (publisher and subscriber)

Now that our publisher and subscriber are ready, let’s now build our workspace.

To make sure all dependencies are correct, let’s fist run rosdep install:

cd ~/ros2_ws/

rosdep install -i --from-path src --rosdistro foxy -y

If everything went ok, you should see something like this:

#All required rosdeps installed successfully

Now let’s build it with:

cd ~/ros2_ws/

colcon build

The output should be similar to the following:

Starting >>> py_pubsub [2.2s] 
[0/1 complete] [py_pubsub - 0.9s] 
Finished <<< py_pubsub [1.56s] 

Summary: 1 package finished [2.83s]

Running the publisher and subscriber

Now that our ros2_ws (ROS2 Workspace) is built, let’s run our publisher and subscriber to make sure they are working. Let’s start by running our publisher, that we named talker.

source ~/ros2_ws/install/setup.bash

ros2 run py_pubsub talker

If everything went ok, you should see something similar to this:

[INFO] [1636416737.394168909] [minimal_publisher]: Publishing: "Battery Level: 100"
[INFO] [1636416737.876938844] [minimal_publisher]: Publishing: "Battery Level: 99"
[INFO] [1636416738.377307008] [minimal_publisher]: Publishing: "Battery Level: 98"
[INFO] [1636416738.877402234] [minimal_publisher]: Publishing: "Battery Level: 97"
[INFO] [1636416739.377231791] [minimal_publisher]: Publishing: "Battery Level: 96"
[INFO] [1636416739.877032283] [minimal_publisher]: Publishing: "Battery Level: 95"
[INFO] [1636416740.377206630] [minimal_publisher]: Publishing: "Battery Level: 94"
[INFO] [1636416740.877335527] [minimal_publisher]: Publishing: "Battery Level: 93"
[INFO] [1636416741.377765577] [minimal_publisher]: Publishing: "Battery Level: 92"

But, if you got an error message like this one “No executable found“, then it means you need to make your publisher and subscriber executables. You can make them executable with:

chmod +x ~/ros2_ws/src/py_pubsub/py_pubsub/*

You can now open a different web shell and run the subscriber named listener with:

source ~/ros2_ws/install/setup.bash

ros2 run py_pubsub listener

If the battery level is already below 66%, you should see no messages being published by the listener. If that is the case, please kill and start the talker again in the first terminal. You can kill it with CTRL+C , and can start it again just as you did before:

CTRL+C

ros2 run py_pubsub talker

After having restarted the talker in the first terminal, you can now keep an eye on the terminal where the listener was executed. When the battery level goes between 70 and 66%, the following message should have been printed.

[INFO] [1636416915.455386963] [minimal_subscriber]: WARNING!: "Battery Level: 70"
[INFO] [1636416915.934977703] [minimal_subscriber]: WARNING!: "Battery Level: 69"
[INFO] [1636416916.435285872] [minimal_subscriber]: WARNING!: "Battery Level: 68"
[INFO] [1636416916.935092663] [minimal_subscriber]: WARNING!: "Battery Level: 67"
[INFO] [1636416917.434961817] [minimal_subscriber]: WARNING!: "Battery Level: 66"

And to make sure the message is being published, you can now open a third terminal and check the output of the /battery topic with:

ros2 topic echo /battery

The message printed should be similar to the one below:

$ ros2 topic echo /battery 

data: 'Battery Level: 70'
---
data: 'Battery Level: 69'
---
data: 'Battery Level: 68'
---
data: 'Battery Level: 67'
---
data: 'Battery Level: 66'

Be aware that in order to see the message being published in the third terminal, where the /battery topic is being echoed, you have to restart the talker again, since the battery level at this point is well below the 65%.

Again, you can kill the talker in the first terminal with CTRL+C , and can start it again just as you did before:

CTRL+C

ros2 run py_pubsub talker

Now you should be able to see the output in the third terminal (/battery topic)

Congratulations. You made it. You now know not only how to create conditional publishers, but you have also learned how to create packages,  and modify publishers and subscribers in ROS2 Python.

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.

Pin It on Pinterest