ROS2 Challenge#

This is my solution to the ROS2 Challenge to show my understanding of ROS2.

I have used Ubuntu:Focal docker image and source installed Ros2 Foxy for these tasks.

docker pull ubuntu:focal

‘Hello! ROS2 is fun’#

This is introduction to creating a simple publisher and subscriber in ROS2. Basically there is one node that listens on a topic on which the other node talks.

talker-listener

Creating ROS2 package:#

cd <path/to/ros2_ws>/src
ros2 pkg create ros2_is_fun --build-type ament_cmake --dependencies std_msgs rclcpp

This will create ros2_is_fun package with following files and directory inside it.

ros2_is_fun/
    CMakeLists.txt
    package.xml
    include/ros2_is_fun
    src/

For more info on the function of these files see What makes up a ROS 2 package?

The source code publisher.cpp and subscriber.cpp will be added in the src directory. The CMakeLists.txt will be edited to add the executable to the package and install them.

add_executable(publisher src/publisher.cpp)
ament_target_dependencies(publisher rclcpp std_msgs)

add_executable(subscriber src/subscriber.cpp)
ament_target_dependencies(subscriber rclcpp std_msgs)

install(TARGETS 
  publisher
  subscriber
  DESTINATION lib/${PROJECT_NAME})

Building the package#

colcon build --packages-select ros2_is_fun

Using the package#

To be able to run the executables, first source the setup files from install directory.

source install/local_setup.bash
source install/setup.bash

Run the executables:

ros2 run ros2_is_fun publisher
ros2 run ros2_is_fun subscriber
Pub-Sub

Source Code#

Source Code
#include <chrono>       // for std::chrono (time utilities)
#include <functional>   // for std::bind

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"


class PublisherNode : public rclcpp::Node
{
    public:
        PublisherNode() : Node("publisher_node")
        {
            count_ = 0;
            publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
            timer_ = this->create_wall_timer(
                std::chrono::milliseconds(1000),
                std::bind(&PublisherNode::timer_callback, this)
            );
        }
    private:
        void timer_callback()
        {
            auto message = std_msgs::msg::String();
            message.data = "(" + std::to_string(count_++) + ") Hello! ROS2 is fun";
            RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
            publisher_->publish(message);
        }
        rclcpp::TimerBase::SharedPtr timer_;
        rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
        size_t count_;
};

int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);
  std::shared_ptr<PublisherNode> node = std::make_shared<PublisherNode>();
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}
#include <functional>   // for std::bind

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using std::placeholders::_1;

class SubscriberNode : public rclcpp::Node
{
    public:
        SubscriberNode() : Node("subscriber_node")
        {
            subscription_ = this->create_subscription<std_msgs::msg::String>(
                "topic", 10, std::bind(&SubscriberNode::topic_callback, this, _1)
            );
        }
    private:
        void topic_callback(const std_msgs::msg::String::SharedPtr message)
        {
            RCLCPP_INFO(this->get_logger(), "Msg Rcvd: '%s'", message->data.c_str());
        }
        rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);
  std::shared_ptr<SubscriberNode> node = std::make_shared<SubscriberNode>();
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

Visualizing Laser Scan Data#

Jack Jack Laser

This part involves using laser sensors to scan the environment and visualize the data in rviz2.

Pre-requisites#

I have used turtlebot3_burger as the robot model, and dynamic_world as a model for the world.

Environment Setup#

My ~/.bashrc includes these lines:

source /opt/ros/foxy/setup.bash
source /usr/share/colcon_cd/function/colcon_cd.sh
export _colcon_cd_root=/opt/ros/foxy/
source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.bash
export TURTLEBOT3_MODEL=burger
export GAZEBO_MODEL_PATH=/usr/share/gazebo-11/models:/opt/ros/foxy/share/turtlebot3_gazebo/models/:$GAZEBO_MODEL_PATH

Also, the package models if any need to be added to the path as well.

export GAZEBO_MODEL_PATH=/home/ros2_ws/src/jde_ros2_asgn/models:$GAZEBO_MODEL_PATH

Nodes#

To view the Laser Scans we need atleast 4 nodes:

  1. Gazebo - The simulation environment.

gazebo $(ros2 pkg prefix --share jde_ros2_asgn)/worlds/dynamic_world.world
  1. Robot State Publisher - To broadcast the state of the robot to the tf2 transform library.

ros2 run robot_state_publisher robot_state_publisher $(ros2 pkg prefix --share turtlebot3_description)/urdf/turtlebot3_burger.urdf
  1. Teleop Twist Keyboard - The title is self-explanatory.

ros2 run teleop_twist_keyboard teleop_twist_keyboard
  1. RViz2 - ROS vizualtion tool

rviz2

Launching the nodes#

I have created a launch file named laser_scan.launch in launch directory to launch all the nodes from one command. (Except for the keyboard node, you know why.)

ros2 launch jde_ros2_asgn laser_scan.launch.py

and

ros2 run teleop_twist_keyboard teleop_twist_keyboard

Source Code#

laser_scan.launch.py
# -*- coding: utf-8 -*-

"""
Description: TODO
Author: Muhammed Abdullah Shaikh
Date Created: Mar 14, 2024
Last Modified: Mar 14, 2024
Python Version: 3.8.11
License: BSD-3-Clause License
"""

import launch
from launch.substitutions import Command, LaunchConfiguration, PathJoinSubstitution
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.substitutions import FindPackageShare
from launch_ros.actions import Node

def generate_launch_description():

    pkg_jde_ros2_asgn = FindPackageShare('jde_ros2_asgn')
    pkg_gazebo_ros  = FindPackageShare('gazebo_ros')
    pkg_turtlebot3_descrition  = FindPackageShare('turtlebot3_description')

    urdf_path = PathJoinSubstitution([pkg_turtlebot3_descrition, 'urdf', 'turtlebot3_burger.urdf'])
    world_path = PathJoinSubstitution([pkg_jde_ros2_asgn, 'worlds', 'dynamic_world.world']) 
    rviz_config_path =  PathJoinSubstitution([pkg_jde_ros2_asgn, 'rviz', 'laser_scan.rviz'])  

    return launch.LaunchDescription([
        
        DeclareLaunchArgument(
            name='world', 
            default_value=world_path,
            description='Absolute path to world file'
        ),

        DeclareLaunchArgument(
            name='rvizconfig',
            default_value=rviz_config_path,
            description='Absolute path to rviz config file'
        ),

        DeclareLaunchArgument(
            name='use_sim_time',
            default_value='false',
            description='Use simulation (Gazebo) clock if true'
        ),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                PathJoinSubstitution([pkg_gazebo_ros, 'launch', 'gzserver.launch.py'])
            ),
            launch_arguments={'world': LaunchConfiguration('world')}.items(),
        ),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                 PathJoinSubstitution([pkg_gazebo_ros, 'launch','gzclient.launch.py'])
            ),
        ),

        Node(
            package='robot_state_publisher',
            executable='robot_state_publisher',
            parameters=[{'use_sim_time': LaunchConfiguration('use_sim_time', default='false')}],
            arguments=[urdf_path]
        ),
        
        Node(
            package='rviz2',
            executable='rviz2',
            name='rviz2',
            output='screen',
            arguments=['-d', LaunchConfiguration('rvizconfig')],
        ),

    ])

ROS2 Navigation2#

In simple terms we want our robot to move from point A to point B. But first, we need to know where the robot is (i.e. localization) and where are points A and B (i.e. mapping).

You Go To Robot City

Background#

For mapping, I have used slam_toolbox package, to help estimate the robot position and create the map of the world.

Once a map is available we need to localize, which is done by creating a transform chain map -> odom -> base_link (Oh the errors here are painful!!!). We will use tf2 transform library to do that.

Now, what left is navigation.

Creating the Map#

We will launch the slam tool box, create a gazebo simulation world, visualize the map in rviz2 and teleop the robot.

ros2 launch slam_toolbox online_async_launch.py
ros2 launch jde_ros2_asgn navingation_tb3.launch.py
rviz2 -d $(ros2 pkg prefix --share jde_ros2_asgn)/rviz/mapping.rviz
ros2 run teleop_twist_keyboard teleop_twist_keyboard

Once, the mapping is done, we will save it using nav2_map_server.

ros2 run nav2_map_server map_saver_cli -f src/jde_ros2_asgn/maps/my_cafe --ros-args -p save_map_timeout:=10000

Waypoint Navigation#

I have created a launch file to start all the required nodes, however I have been facing issue when nav2_bringup is lanuched using the same launch file. Hence, we will use additional terminal to launch it.

ros2 launch jde_ros2_asgn navigation_tb3.launch.py
ros2 launch nav2_bringup bringup_launch.py use_sim_time:=True autostart:=True map:=/home/ros2_ws/src/jde_ros2_asgn/maps/my_cafe.yaml
ros2 run rviz2 rviz2 -d $(ros2 pkg prefix nav2_bringup)/share/nav2_bringup/rviz/nav2_default_view.rviz

Rest is shown in the video.

If I only had more time.#

I used up ~1.5 weeks of time for the following to work. Why? Because the challenge said “(or any other robot if you wish to).

The issues mentioned were not the limitation, but the time was. Hence I then decided to move forward with the TurtleBot3.

Racecar and Tunnel World

This is racecar model and tunnel world taken from mit-racecar which I guess is used for labs in Robotics: Science and Systems (MIT Course).

The issues I faced:

  • Spawning the robot URDF in Gazebo. (I beleive version issues or my skill isssues.)

  • The package was written using ROS kinetic.

  • The racecar is Ackermann-steering Robot, and this was my first time working with ackermann steering.

The other candidate robot model I had in mind was Sahayak-v3 developed at IvLabs.

Use of AI#

Why add this section? Because I read a discussion where @jmplaza mentioned how selection process is more rigorous in these ChatGPT times.
I have not used any assitance of AI tools like ChatGPT, co-pilot, etc for the programming part of this challenge.
Also, AI is still not very good at debugging ROS errors.
I have included the reveleant resources I used during this challenge.

References#