ROS - A Quick Overview

Subscribe Send me a message home page tags


#ros  #turtlebot3 

Table of Content

Introduction

This post is a quick overview of ROS. Most of the content is based on A Gentle Introduction to ROS.

ROS is a framework that makes the development of robotic applications easier. It provides the following building blocks

The message passing interface is crucial because it hides all the details of actual communications between different devices. The computation unit of ROS is a node, which is a running process. Note that there is no mention of location in the definition of the node because where the process is running does not matter too much. All we care about is that nodes will communicate with each other using the message passing interface provided by ROS. It's interesting to notice that the node concept has nothing to do with robots, which means the implementation details such as controlling the robot, and making high-level decisions are encapsulated completely and this is the reason why ROS is super powerful.

An immediate consequence of using node as the abstraction level is that the system does not (need to) know if it's interacting with a real robot or a simulator. This makes debugging and experimenting much easier.

Typical Setting

The figure below shows a typical setup. There are three types of nodes

Local nodes are running on the host together with the ROS core. Ros core behaves like a server and is the brain of the ROS. Local nodes are used to control sensors and robotic parts connected to the host. Remote nodes are nodes that run remotely and there is no direct wired connection between this node and the ROS. The remote nodes need to know where to find the ROS core and we can provide this information in the environment variable ROS_MASTER_URI. Serial nodes are nodes that are connected to ROS via serial communication. For example, we can connect Arduino to a Raspberry Pi host using a USB cable and use Arduino to control the motors. With this setting, Arduino becomes a node, similar to a local node that controls a sensor. The only difference is that the communication is done through a serial channel.

Again, from the ROS' point of view, all of them are just nodes.

typical_ros_hardware_setup.png

ROS Project Structure

Typically, a ROS project is implemented in a workspace, which contains almost everything about the project. The code is stored in workspace/src and is organized using packages. A ROS package is a library or a logical grouping of nodes. It's defined by a package manifest file package.xml. A node is in turn represented by an executable file which is used to initiate the node it represents. The logical grouping also contains the information about the communication between the nodes therefore packages contain messages used by the nodes. A ROS package also contains other supporting files.

But how does ROS know which workspace to use? After we build the code, we need to execute source devel/setup.sh in the workspace. This step will inform ROS that we want to use a specific workspace.

The diagram below shows a hello-world example of a ROS workspace.

my_ros_workspace
    ├── devel
    │   ├── cmake.lock
    │   ├── env.sh
    │   ├── lib
    │   │   └── pkgconfig
    │   │       └── agitr.pc
    │   ├── local_setup.bash
    │   ├── local_setup.sh
    │   ├── local_setup.zsh
    │   ├── setup.bash
    │   ├── setup.sh
    │   ├── _setup_util.py
    │   ├── setup.zsh
    │   └── share
    │       └── agitr
    │           └── cmake
    │               ├── agitrConfig.cmake
    │               └── agitrConfig-version.cmake
    └── src
        ├── agitr
        │   ├── CMakeLists.txt
        │   ├── hello_world.py
        │   ├── package.xml
        │   ├── publisher_example.py
        │   └── subscriber_example.py
        └── CMakeLists.txt -> /opt/ros/noetic/share/catkin/cmake/toplevel.cmake

Launch File

A better way to launch nodes is to use a launch file. The idea is similar to AWS cloudformation template: we specify the resources in a file. The ROS launch file has the following structure

<launch>
    <node
        pkg = package-name
        type = this-is-the-executable-file
        name = node-name
        required = boolean
        respawn = boolean
        launch-prefix = ""
    />
</launch>

pkg and name are straightforward. The type argument is the executable file. Recall that when we use the rosrun command, we need to provide both package name and executable file and the executable file is the type argument.

If required is true, it means if this required node is terminated then ROS will terminate all other active nodes. If respawn is true, it means if the node is terminated then ROS will try to re-initiate the node.

launch-prefix is an advanced argument. It adds a prefix to the command generated from the execution of the launch file. One of the use cases is to create a new terminal window when using the launch file. In order to do so, we can set launch-prefix="xterm -e".

To use the launch file, we can execute the following command:

roslaunch package-name launch-file-name

Other options of the roslaunch command:

Logging

We can find logging in three places

We are all familiar with the console. Regarding the topic, ROS publishes the logging messages to the rosout topic and we can view the loggings just like view other ROS messages. The log file is located at ~/.ros/log/run_id/rosout.log and to get the run id, use the following command

rosparam get /run_id

Parameters

There are four ways to access parameters:

For example, we can set parameters in a launch file by setting:

<group ns="duck_color">
    <param name="huey" value="red"/>
    <param name="dewey" value="blue"/>
</group>

Or read parameters from a file by using

<rosparam command="load" file="path-to-param-file"/>

Services

ROS provides two communication mechanisms:

Service is less used compared to the publication and subscription pattern. A service is defined by

Service name is a string naming the service we want to call. This should be a relative name but can also be a global name. Service type is the name of the service object defined in the header file. Here is an example from ROS website:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"

bool add(beginner_tutorials::AddTwoInts::Request  &req,
         beginner_tutorials::AddTwoInts::Response &res)
{
  res.sum = req.a + req.b;
  ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
  ROS_INFO("sending back response: [%ld]", (long int)res.sum);
  return true;
}

int main(int argc, char **argv)
{
  ros::init(argc, argv, "add_two_ints_server");
  ros::NodeHandle n;

  ros::ServiceServer service = n.advertiseService("add_two_ints", add);
  ROS_INFO("Ready to add two ints.");
  ros::spin();

  return 0;
}

add_two_ints is the service name and the service type is beginner_tutorials/AddTwoInts. The type is used when implementing the callback function, which is the service requester handler.

Note that a service can only handle/process one service type but a service type can be used in different services.

Graph Resource Names

All entities are (graph) resources in ROS, including

Every resource is identified by a resource name. There are three types of resource names:

Strictly speaking, there is only global name. It's an equivalenet of the absolute file path in an operating system. Relative name and private name will be translated into a global name and it's this translation process that brings more flexibility.

When translating a relative name to a global name, ROS attaches a default name to it. The default namespace is tracked individually for each node and if we don't specify anything, the global namespace (/) is used.

There are three ways to set the default namespace:

When translating a private name to a global name, ROS attaches the node's global name to it. For example, if we have a private name ~my-privaname and the node is /robot/camera-node, then the global name after translation is /robot/camera-node/my-private-name. Therefore, private name is really a private attribute of a node.

Note that one of the differences between a relative name and a private name is that different relative names may share the same default namespace while private names are truly independent.

There is another type of name called anonymous name. It's used to make sure the node name is unique in ROS.

Note: To some extent, package is compile-time grouping of nodes while default namespace is runtime grouping of nodes. The namesapce can be set in the launch file but the launch file is executed at package level. In this sense, default namespace is a more granular control.

Commands

CommandDescription
rospack listList packages
rospack find package-nameFind the directory of a single package
rosls package-nameView the files in a package directory
roscd package-nameGo to the package directory
rosrun package-name executable-name Start a node. To overwrite the node name, use __name:=node-name.
roslaunch package-name launch-file-nameUse launch file to launch nodes.
rosnode listListing running nodes
rosnode info node-nameInspect a node
rosnode kill node-nameKill a node.
rosnode cleanup

Purge the registration of any node that cannot be contacted immediately. Prints list of unreachable nodes which has to be confirmed.

IMPORTANT: rosnode cleanup was meant as a temporary solution and its use was not encouraged in normal operation. Its benefit is aesthetic and it has the downside of potentially unregistering functioning nodes.

rqt_graphDisplay the message graph between nodes.
rostopic listList active topics.
rostopic echo topic-nameEcho messages that are being published.
rostopic hz topic-nameMeasure the speed of message publication.
rostopic bw topic-nameMeasure the bandwidth of message publication.
rostopic info topic-nameInspect a topic.
rosmsg show message-type-nameInspect a message type.
roswtf This command is used for sanity checks.
catkin_create_pkg package-name Create a package.
rosparam get /run_id Query the run id.
rosparam list List parameters
rosparam get parameter-name Get the paramter values.
rosparam get namespace Get parameters in a namespace.
rosparam set parameter-name paramter-value Set parameter values.
rosparam dump filename namespace Dump parameters to a file.
rosparam load filename namespace Load parameters from a file.
rosservice list List all services
rosnode info node-name List services by node.
rosservice node service-name Find the node offering a service.
rosservice info service-name Find the data type of a service.
rossrv show service-data-type-name Inspect service datae types.

----- END -----

Welcome to join reddit self-learning community.
Send me a message Subscribe to blog updates

Want some fun stuff?

/static/shopping_demo.png