How to Create a ROS2 node outside the ROS workspace

In this short post, I show how to create a ROS2 node outside of a ROS workspace. It can be required for different cases. For example: sometimes there is the necessity to use ROS as a part of an already developed project and it is not possible to move it inside the ROS workspace or maybe there is the requirement to leave well separated the ROS code from the non-ROS code.

This post will try to provide an updated shorter version of the article with the guide for creating a ROS1 node outside the catkin workspace. I suggest reading the ROS1 article, some explanations are still valid for ROS2.

Available solutions

To reach the objective of communicating with ROS2 by using code outside a ROS workspace there are multiple solutions:

  1. Network approach: A TCP client/server mechanism can be implemented to communicate with a ROS node with whatever other type of software. It is a very general solution since it is not dependent on ROS and so it is not needed to be updated with changes in ROS. The negative part is that you have to implement it from scratch and it can be unnecessary for the need.
  2. DDS: Communicate using the DDS, the middleware used by ROS2 for communication. It is a protocol on top of TCP/UDP working with the concept of publisher/subscriber. The negative part of this solution is that ROS2 is still young and there are possibilities that the DDS layer will change in time. It can create maintenance issues for your project.
  3. Create a ROS2 node in the project outside the ROS workspace.

Creation of the ROS2 node

NOTE: ROS2 is needed, if it is not in your system, follow the Installation guide.

First of all, let’s create a simple ROS2 node as it is done inside the ROS2 workspace.

So at the top of the file, I included some standard libraries needed for the example, the rclcpp library and the topic type used:

#include <chrono>
#include <functional>
#include <memory>
#include <string>

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

Then the node is created:

class MyNode : public rclcpp::Node
{
public:
    MyNode() : Node("my_publisher_node")
    {
        timer_ = this->create_wall_timer(
            std::chrono::milliseconds(500),
            std::bind(&MyNode::timerCallback, this));
        publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    }
private:
    void timerCallback()
    {
        auto message = std_msgs::msg::String();
        message.data = "Hello, world! " + std::to_string(count_++);
        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_;
};

If you are familiar with ROS2 you should not see anything strange. The node contains a simple publisher that publishes the message "Hello world!" followed by an incremental number that indicates the number of the message.

Finally, there is the main function that initializes the rclcpp library, creates the node and calls the spin.

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

Setup the compilation

In my example, I used CMake to compile the files. Here is the CMakeList.txt:

include_directories(/opt/ros/foxy/include /opt/ros/foxy/lib)
link_directories(/opt/ros/foxy/lib)

add_executable(node_outside_workspace main.cpp)

target_link_libraries(node_outside_workspace
    -lrclcpp
    -lrcutils
    -lrcl
    -ltracetools
    -lstd_msgs__rosidl_typesupport_cpp
)

NOTE: The paths in the include_directories and link_directories could change if you installed ROS on a different path or if a different version of ROS2 is used.

Every time a new library is used you need to link it in the compilation. You can find all the ROS2 libraries at opt/ros/foxy/lib. For example, if you want to use geometry_msgs library you need libgeometry_msgs__rosidl_typesupport_cpp.so(found using find /opt/ros/foxy/lib -name "*geometry_msgs*") and so you need to add in the target_link_libraries the value :-lgeometry_msgs__rosidl_typesupport_cpp.

That’s all, now it is possible to compile the node and run it.

Conclusion

This approach allows any C++ project to communicate with ROS2. It is fast to implement and enables to use of all the functionalities of ROS2 without compiling the project inside ROS.


All the code of this article comes from the following github repository. More information and how to test the approach is in the README.md of the repository.

For any clarification and questions feel free to contact me using the contact form, open a new issue in the repository or comment under this post.