Skip to main content
danger

This has not been proof-read or modified for simpler reading yet.

12/23/24

Packages

Packages are how ROS2 organizes programs. It contains all of the source code written in C++/Python, configuration, compilation, launch, and parameter files. It allows easier as well sharing as everything is packaged. In ROS2, you can make CMake packages and Python packages.

All CMake packages must have the following folders and files:

  • launch folder: Contains all of the launch files
  • src folder: Contains all of the source code (C++/Python)
  • CMakeLists.txt: Contains the rules for compiling the C++ source code
  • package.xml: Contains all of the package metadata and dependencies needed by the package at runtime and compiletime

Creating a Package

We can create packages by running the command below:

tip

Always create packages inside of the src folder within your ROS2 workspace. Generally, that path is ~/ros2_ws/src.

ros2 pkg create <package_name> --build-type ament_cmake --dependencies rclcpp <package_dependencies>
# rclcpp is the ros core library for cpp

You can then compile it to ensure there are no dependency conflicts using the command:

colcon build # Compiles all packages within your workspace
colcon build --packages-select <package_name> # Compiling one package

Source the workspace script setup.bash within ~/ros2_ws/install to update environment variables. This ensures ROS2 has the newly created package. Anytime you make a change within the package, you must recompile the package and re-source the script.

You can run the command below to see your new package:

ros2 pkg list
ros2 pkg list | grep <package_name> # Filters the list using <package_name>

Launch Files

On a basic level, launch files allow you to start up multiple nodes without having start up multiple terminals. The launch file follows the general structure below and must be stored inside of <my_package>/launch:

## File name conventionally follows the format of *.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node

## Function name must be generate_launch_description
## Everything else within body is up to user
def generate_launch_description():
return LaunchDescription([
Node(
package='teleop_twist_keyboard', # Specifies which package the binary is in
executable='teleop_twist_keyboard', # Specifies the binary
output='screen', # Specifies what channel to output the results of the binary
emulate_tty=True), # Outputs colored logging messages
])

We then have to modify our setup.py file so that the ROS2 environment can find and access the .launch.py files. All we do is add and modify these 3 lines of code.

import os
from glob import glob

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'))
],

You can then run the launch files by first giving it execution permissions and then launching it:

chmod +x <launch_file>.launch.py
ros2 launch <package_name> <launch_file>.launch.py

Source Files

We can create source files within <my_package>/src. The way you structure your source code is dependent on you and can follow object oriented programming, functional, procedural, or whatever coding paradigm you want to use. Generally though, the best practice is to modularize one "node" per source file.

#include "rclcpp/rclcpp.hpp"

int main(int argc, char \* argv[])
{
// Initialize the ROS2 communication
rclcpp::init(argc, argv);

// Create a ROS2 node
auto theNode = rclcpp::Node::make_shared("beginner");

// Print a message to the terminal
RCLCPP_INFO(theNode->get_logger(), "I'm writing to the terminal!");

// Shutdown the ROS2 communication
rclcpp::shutdown();
return 0;
}

CMakeLists and setup file

Since we created a source file, we need to tell the build system (colcon) how to compile our files. We can do so by modifying our CMakeLists.txt and setup.py found within the root of our package folder.

add_executable(simple_node src/simple.cpp) # Generate a binary executable with name simple_node from src/simple.cpp
ament_target_dependencies(simple_node rclcpp) # Specify which dependencies the binary requires for compiling and usage

# Store the binaries within the path lib/${PROJECT_NAME}

install(TARGETS
simple_node
DESTINATION lib/${PROJECT_NAME}
)

# Store the launch files within the path share/${PROJECT_NAME}

install(DIRECTORY
launch
DESTINATION share/${PROJECT_NAME}/
)

Once we compile our package, we can see our new node using ros2 node list For more information about a node, we can use ros2 node info /<package_name>.

Topics

Topics are communication channels that nodes use to exchange messages. To see available topics, we can use ros2 topic list. For more information about a topic, we can use ros2 topic info <topic_name>.

Publishers

Publishers are nodes that continuously send out messages to a topic channel at a specific frequency. We can create a very trivial publisher below that counts at rate of 10 Hz.

// Import all the necessary ROS libraries and import the Int32 message from the std_msgs package
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/int32.hpp"

int main(int argc, char \* argv[])
{
rclcpp::init(argc, argv);

auto theNode = rclcpp::Node::make_shared("counter_publisher");

// We tell the node to create a publisher at the topic /counter using the type Int32
// Specify the queue (buffer) to only be of size 10.
auto thePublisher = node->create_publisher<std_msgs::msg::Int32>("counter", 10);
// We create the message we want to send to the channel.
auto theMessage = std::make_shared<std_msgs::msg::Int32>();
theMessage->data = 0;
// Publish at 10 Hz
rclcpp::WallRate loop_rate(std::chrono::milliseconds(100));

// Continuously execute until program stops or exits via user command
while (rclcpp::ok()) {
thePublisher->publish(\*message);
theMessage->data++;
// Execute the node and process the messages in the queue until there's nothing left in original queue.
rclcpp::spin_some(node);
// We ensure the publish rate stays at 10 Hz by sleeping here.
loop_rate.sleep();
}

rclcpp::shutdown();
return 0;
}

We can launch and compile the package and run ros2 topic echo /counter to see that it is indeed counting.

Object Oriented Programming

We've made all of our source code using basic logic and control flow. A better way, however, is to use OOP principles to ensure cleaner and modular code. We can take the publisher code and create an object below. I won't be going into OOP here since this is primarily a ROS2 note.

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/int32.hpp"
#include <chrono>

class SimplePublisher : public rclcpp::Node
{
public:
SimplePublisher() : Node("simple_publisher"), count(0){
thePublisher = this->create_publisher<std_msgs::msg::Int32>("counter", 10);
theTimer = this->create_wall_timer(std::chrono_milliseconds(500), std::bind(&SimplePublisher::timer_callback, this));
}

protected:
void timer_callback(){
auto theMessage = std_msgs::msg::Int32();
theMessage.data = count;
count++;
thePublisher->publish(theMessage);
}

private:
rclcpp::TimerBase::SharedPtr theTimer;
rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr thePublisher;
size_t count;
};

int main(int argc, char \* argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<SimplePublisher>());
rclcpp::shutdown();
return 0;
}