Skip to main content

Creating Nodes - Messages

Chapter description

In this chapter, you'll make your first ROS program. You'll apply what you've learned in the previous chapter about starting the camera. You'll start by creating a workspace, then make your first node, and discover how to build and run it. Your initial code will teach you how to send information between nodes using messages, which involves creating something called a publisher and a subscriber.

You can run this tutorial on:

Repository containing the final effect after doing all the ROS Tutorials you can find here.

Workspace setup

To begin developing your own nodes in ROS, you need to do some workspace configuration first. A workspace is a place where all your source files, libraries, and compiled nodes will be stored.

First, you need to create a folder where your workspace will be located. It is recommended to add _ws to the end of the workspace name. All of the packages with source files should be stored inside the workspace in the src folder. Let's create our workspace named ros_ws and src inside it.

husarion@husarion:~$
mkdir -p ~/ros_ws/src

Creating a new package

In ROS, nodes are organized into packages, so in order to create a node, you need to create a package. Packages are created using the catkin_create_pkg command and it must be executed in the src folder of your workspace. The syntax of catkin_create_pkg is:

catkin_create_pkg package_name <required packages>

where package_name is desired package name and argument required packages is optional and contain names of packages that are used by newly created packages. For our tutorial, we will create a package named tutorial_pkg that depends on the package roscpp. Package roscpp is the ROS library for C++.

husarion@husarion:~$
cd ~/ros_ws/src
catkin_create_pkg tutorial_pkg roscpp

After running this command, you will create a folder named tutorial_pkg and some files in it. Your workspace file structure should now look like this:

ros_ws/
└── src/
└── tutorial_pkg/
├── CMakeLists.txt
├── include/
│ └── tutorial_pkg
├── package.xml
└── src/

The basic package contains:

  • CMakeLists.txt: This is the build instructions for your nodes. You need to edit this file if you want to compile any node. We will do it later.
  • include: This is folder where you put header files. Header files contain declarations of functions, classes, or other code elements that are used in your source code but are defined in other source files. They act as a way for your code to communicate with other parts of your program.
  • package.xml: This file contains package metadata such as author, description, version, or required packages. The package can be built without changing this file, but you should adjust it if you want to distribute your package to others.
  • src: This is folder where you put the source code files for your project. It typically contains the actual code files you write for your software, such as C++ or Python source code files.

Write your first node

Now you have a workspace created and your own package inside that workspace. You are ready to create your first ROS node.

Code of your first node

Let’s create a C++ file for your node and name it my_first_node.cpp. Place it in the src folder inside tutorial_pkg:

husarion@husarion:~$
touch ~/ros_ws/src/tutorial_pkg/src/my_first_node.cpp

Open the file in your favorite text editor and paste the following code:

~/ros_ws/src/tutorial_pkg/src/my_first_node.cpp
#include <ros/ros.h>

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

ROS_INFO("Node started!");
ros::spin();
}
The code explained
  1. Add the header file for the basic ROS library.

    #include <ros/ros.h>
  2. Beginning of node main function.

    int main(int argc, char **argv) {
  3. Initialization of ROS node, this function contacts with ROS master and registers node in the system.

    ros::init(argc, argv, "my_node");
  4. Get the handle for node, this handle is required for interactions with system e.g. subscribing to topic.

    ros::NodeHandle n("~");
  5. Use ROS_INFO to print message. ROS_INFO and similar logging functions are replacement for printf/cout function. It's part of rosconsole, which supports hierarchical loggers and verbosity levels.

    ROS_INFO("Node started!");
  6. Enters a loop where it "spins" or checks for incoming messages and events.

    ros::spin();

Remember to save the C++ file.

Building configuration

Before you build the node, you need to edit the CMakeLists.txt file in the tutorial_pkg directory. Open the file in your favorite text editor and paste the following content:

~/ros_ws/src/tutorial_pkg/CMakeLists.txt
cmake_minimum_required(VERSION 2.8.3)
project(tutorial_pkg)

add_compile_options(-std=c++11)

find_package(catkin REQUIRED COMPONENTS
roscpp
)

catkin_package(
CATKIN_DEPENDS
)

include_directories(${catkin_INCLUDE_DIRS})

add_executable(my_first_node src/my_first_node.cpp)

target_link_libraries(my_first_node
${catkin_LIBRARIES}
)
The code explained
  1. Specify the minimum version of CMake required to build this package.
cmake_minimum_required(VERSION 2.8.3)
  1. Define the name of the CMake project.
project(tutorial_pkg)
  1. Set the C++ standard to C++11.
add_compile_options(-std=c++11)
  1. Tell CMake to find the ROS package named roscpp and make it available for this package. It's required for building ROS C++ code.
find_package(catkin REQUIRED COMPONENTS roscpp)
  1. Specify the dependencies of the current package on other ROS packages.
catkin_package(CATKIN_DEPENDS)
  1. Include the directories specified by catkin_INCLUDE_DIRS in the package's build process. These directories contain header files that may be needed by the code in this package.
include_directories(${catkin_INCLUDE_DIRS})
  1. Instruct CMake to build an executable named my_first_node from the source file my_first_node.cpp located in the src directory of this package.
add_executable(my_first_node src/my_first_node.cpp)
  1. Link the my_first_node executable with the libraries specified in ${catkin_LIBRARIES}, which typically includes ROS libraries required for building ROS nodes.
target_link_libraries(my_first_node ${catkin_LIBRARIES})

Save the changes and close the editor. Now, you are ready to proceed with the ROS build process for your workspace.

Build code

When the building configuration is finished, you can navigate to your workspace main directory and build your project using the catkin_make command:

husarion@husarion:~$
cd ~/ros_ws
catkin_make

After executing this command, you should see the build output similar to the one shown below and two new folders will be created inside your ros_ws:

  • build for storing intermediate build files,
  • install folder for storing the output files.

image

After this operation you should have two new folders in your workspace:

  • build for storing files that are used during compilation,
  • develfor storing output binary files.
note

Remember to build your package every time you make changes to your code.

Run node

Sourcing files

Before running your node, you need to source the appropriate environment setup files. Open a terminal and navigate to your workspace main directory, then run the following command to source the setup files:

husarion@husarion:~$
source ~/ros_ws/devel/setup.bash

These environment variables enable you to run your node from any directory. To avoid manually sourcing the setup files every time you open a new terminal, you can add the following line to your .bashrc file to automatically source your ROS workspace:

husarion@husarion:~$
echo 'source ~/ros_ws/devel/setup.bash' >> ~/.bashrc

Task 1 - Running the node

Run your node using the rosrun command. Then use the rosnode list command to examine the system and check if your node is visible.

Hint

In the previous chapter you learned the rosrun and roslaunch commands to run code. In this case it is necessary to execute the rosrun command because we do not have a launch file created. As a reminder, running the rosrun command looks like this.

rosrun package_name executable_name [options]

After a successful startup, you should see the text Node started! and you should be able to see the new node named my_node using the rosnode list command.

Creating a subscriber

As you can see, your first node is pretty boring. It does nothing but output one line of text to the terminal. Let's modify the previous code. We will use our camera to calculate the average brightness of the received image. To subscribe image from /image topic we will use the following code.

Open again the previous my_first_node.cpp

~/ros_ws/src/tutorial_pkg/src/my_first_node.cpp
#include <ros/ros.h>
#include <sensor_msgs/Image.h>

void imageCallback(const sensor_msgs::ImageConstPtr &image)
{
long long sum = 0;
for (int value : image->data)
{
sum += value;
}
int avg = sum / image->data.size();
ROS_INFO("Brightness: %d", avg);
}

int main(int argc, char **argv)
{
ros::init(argc, argv, "my_first_node");
ros::NodeHandle n("~");
ros::Subscriber sub = n.subscribe("/camera/color/image_raw", 10, imageCallback);

ROS_INFO("Node started!");
ros::spin();
}
The code explained
  1. To process messages received from the camera, you need to include the header file for the Image message type. The message fields can be found here:
#include <sensor_msgs/Image.h>
  1. Then you need a function for processing received message (argument is pointer to incoming message).
void imageCallback(const sensor_msgs::ImageConstPtr &image)
{
...
}
  1. Variable for storing sum of all pixel values.
long long sum = 0;
  1. Iteration through every pixel and colour and add current pixel value to sum.
for (int value : image->data)
{
sum += value;
}
  1. Creating variable and iterating through each pixel value in the data field of the image message and adding it to the sum.
long long sum = 0;
for (uint8_t value : image->data)
{
sum += value;
}
  1. Calculating the average value by dividing the sum by the total number of pixels and printing the brightness value.
int avg = sum / image->data.size();
RCLCPP_INFO(get_logger(), "Brightness: %d", avg);
  1. Last thing is defining subscriber.
ros::Subscriber sub = n.subscribe("/camera/color/image_raw", 10, imageCallback);

Here we use method subscribe of NodeHandle object. Arguments of method are:

  • /camera/color/image_raw - name of topic to subscribe.
  • 10 - message queue size. Messages are processed in order they come in. In the case that node receives, in short time, more messages than this value, excessive messages will be dropped.
  • imageCallback - function to process incoming messages.

Building configuration

Before running the code, we need to find and link the new sensor_msgs dependency to the CMakeList.txt. Please add highlighted changes:

~/ros_ws/src/tutorial_pkg/CMakeLists.txt
cmake_minimum_required(VERSION 2.8.3)
project(tutorial_pkg)

add_compile_options(-std=c++11)

find_package(catkin REQUIRED COMPONENTS
roscpp
sensor_msgs
)

catkin_package(
CATKIN_DEPENDS
)

include_directories(${catkin_INCLUDE_DIRS})

add_executable(my_first_node src/my_first_node.cpp)

target_link_libraries(my_first_node
${catkin_LIBRARIES}
)

Build & run your code

Now you can build and run this node along with camera. To run the camera, run the RObot or Gazebo simulator as shown in the previous chapter.

Terminal 1
roslaunch astra_camera astra.launch
Terminal 2
rosrun tutorial_pkg my_first_node

After running this launcher in the terminal, you should see the brightness information.

Creating a publisher

Now let's modify the node to publish brightness value to a new topic instead of logging info into the terminal. For this purpose, we will use message type std_msgs/UInt8.

~/ros_ws/src/tutorial_pkg/src/my_first_node.cpp
#include <ros/ros.h>
#include <sensor_msgs/Image.h>
#include <std_msgs/UInt8.h>

ros::Publisher brightness_pub;

void imageCallback(const sensor_msgs::ImageConstPtr &image)
{
long long sum = 0;
for (int value : image->data)
{
sum += value;
}
int avg = sum / image->data.size();

std_msgs::UInt8 brightness_value;
brightness_value.data = avg;
brightness_pub.publish(brightness_value);
}

int main(int argc, char **argv)
{
ros::init(argc, argv, "my_first_node");
ros::NodeHandle n("~");
ros::Subscriber sub = n.subscribe("/camera/color/image_raw", 10, imageCallback);
brightness_pub = n.advertise<std_msgs::UInt8>("brightness", 1);

ROS_INFO("Node started!");
ros::spin();
}
The code explained
  1. Add header file to obtain new msg type std_msgs/UInt8.
#include <std_msgs/UInt8.h>

Message std_msgs/UInt8 is object with only one field uint8_t data (contain value from 0 to 255).

  1. Next define publisher object with global scope.
ros::Publisher brightness_pub;
  1. Configuration and linking the publisher to the selected topic.
brightness_pub = n.advertise<std_msgs::UInt8>("brightness", 1);

Here we use method advertise of NodeHandle object. Arguments of method are:

  • brightness - topic name.
  • 1 - message queue size.
  1. Declare the type of message which will be published (brightness_msg), put some data into the message, and send it whenever we calculate a mean brightness.
std_msgs::msg::UInt8 brightness_msg;
brightness_msg.data = avg;
publisher_->publish(brightness_msg);

Building configuration

As before we should add the new std_msgs dependency to the CMakeList.txt. However, this is not necessary because std_msgs messages are automatically linked with the messages sensor_msgs. For didactic purposes, we add a new dependency.

~/ros_ws/src/tutorial_pkg/CMakeLists.txt
cmake_minimum_required(VERSION 2.8.3)
project(tutorial_pkg)

add_compile_options(-std=c++11)

find_package(catkin REQUIRED COMPONENTS
roscpp
sensor_msgs
std_msgs
)

catkin_package(
CATKIN_DEPENDS
)

include_directories(${catkin_INCLUDE_DIRS})

add_executable(my_first_node src/my_first_node.cpp)

target_link_libraries(my_first_node
${catkin_LIBRARIES}

Build & run your code

To build and run your code. You can use the same command as before. Make sure your camera or simulator is working.

husarion@husarion:~$
rosrun tutorial_pkg my_first_node

Task 2 - Examining topics

Use rostopic echo to read the brightness of the image from the /my_node/brightness topic.

Hint

As a reminder, you should type:

rostopic echo [topic_name]

Message types

ROS packages, apart from the functionality itself, provide various types of messages. Some of the most common packages used are:

  • std_msgs - messages with basic data types like integer or float
  • geometry_msgs - for handling information regarding object position, orientation, velocity or acceleration, also for defining points or polygons
  • nav_msgs - for handling maps or robot localization data
  • sensor_msgs - for handling data from sensors

Each of these packages contains many types of messages, which have their uses in different applications. When creating nodes for mobile robots, you'll likely encounter some of these messages:

  • geometry_msgs/Twist - message for defining linear and angular object velocity. Could be also used for setting desired velocities.
  • nav_msgs/OccupancyGrid - message for handling flat occupancy grid based maps.
  • nav_msgs/Odometry - message for defining robot position and orientation based on odometry measurements.
  • nav_msgs/Path - message for defining robot path as a set of positions
  • sensor_msgs/JointState - it has fields for determining position, velocity and effort for a set of joints.
  • sensor_msgs/LaserScan - message for handling scans of planar laser scanners like RpLidar or Hokuyo.
  • sensor_msgs/Range - message for one dimensional distance measurement

It is also possible to implement user-defined messages if existing ones are not sufficient.

Summary

After completing this tutorial you should know how to:

  • create a workspace,
  • create your custom nodes, that can send and receive messages,
  • build code and configure building process,
  • run and examine the system.

In the next tutorial, we will continue this topic. You will learn about another method of communication which are services. See you there!


by Łukasz Mitka & Rafał Górecki, Husarion

Do you need any support with completing this tutorial or have any difficulties with software or hardware? Feel free to describe your thoughts on our community forum: https://community.husarion.com or to contact our support: support@husarion.com.