Create a ROS Node Outside a Catkin Workspace

In this tutorial I’ll show you how to create a ROS node outside a catkin workspace – both for Python and Cpp.

Most of the post is dedicated to linking ROS libraries to a Cpp file, because as you’ll see it’s quite simple for Python.

Why would you want to create a node outside of a catkin workspace

First, why? Why not simply write all your code inside a catkin workspace and don’t talk about it anymore? Well, sometimes it could be the solution, but there are some cases where things aren’t as simple as that.

  • Maybe you already have a big legacy application that you can’t just move into a catkin workspace. You want to clearly separate your non-ROS code from your ROS code.
  • Also, maybe the ROS part of your application is very small. For example, you just need to use ROS for computing some motion planning, while the rest of your application has nothing to do with ROS.
  • Or for any other reason you just want to separate your application into 2 distinct parts: the ROS code and the higher level application code. And you don’t want to mix them, you want to be able to run them separately, while keeping the possibility to make a link between them.

What you could do here, is to use a ROS-independent way of communication between the 2 parts of your application – ex: your own TCP client/server mechanism, which is not a good idea since you’ll waste a lot of time implementing things that already exist and work well.

Finally, one of the solution that seems to solve your problem is to create a ROS node outside your catkin workspace. This node will be linked to your non-ROS application, while being able to communicate with your ROS application using topics, services, and actions.

Let’s see how to do that.

Python node outside catkin workspace

Well, this part is quite easy. You can just create a Python file anywhere you want, and execute it.

$ touch python_node.py
$ … edit your file and use ROS functionalities…
$ chmod +x python_node.py
$ python python_node.py # Make sure you have a roscore running before that

Just make sure you have setup your ROS environment with source /opt/ros/melodic/setup.bash before you run your Python script.

Your PYTHONPATH environment variable should contain “/opt/ros/melodic/lib/python2.7/dist-packages” (normally setup when you source your ROS environment).

That’s pretty much it! As you could guess, Cpp requires a little bit more work than Python, that’s why the rest of this post is dedicated to Cpp.

Cpp node outside catkin workspace

Cpp files need to be compiled in order to be executed. Let’s see how to compile a Cpp file with the right flags so your node will work.

A simple file

First, create a Cpp file and write a simple node.

// test.cpp

#include <ros/ros.h>

int main(int argc, char **argv)
{
    ros::init(argc, argv, "test_node_outside_catkin_ws");
    ros::NodeHandle nh;
    ROS_INFO("It worked!");
}

If you just try to compile the file without linking any library you’ll get an error

$ g++ test.cpp -o your_node
test.cpp:3:10: fatal error: ros/ros.h: No such file or directory
#include <ros/ros.h>
^~~~~~~~~~~
compilation terminated.

To compile a Cpp node with the g++ command line tool, you’ll have to include some paths and add some libraries.

$ g++ test.cpp -o your_node -I/opt/ros/melodic/include -L/opt/ros/melodic/lib -lroscpp -lrostime -lrosconsole
$ ./your_node # Make sure you have a roscore running before that
[ INFO] [1562566986.312784106]: It worked!

Basically you have to include the path of the ROS installation folder on your computer, especially the headers and the libraries files. If you have a different ROS version, simply replace “melodic” by your current version (ex: “kinetic”).

In this example I have included the roscpp, rostime and rosconsole libraries.

Let’s run a publisher outside your catkin workspace

// test.cpp

#include <ros/ros.h>
#include <std_msgs/Int64.h>

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

    ros::Publisher pub = nh.advertise<std_msgs::Int64>("/number", 1000);
    int counter = 0;
    ros::Rate rate(10);
    ROS_INFO("Node and publisher created, now publishing data");

    while (ros::ok())
    {
        std_msgs::Int64 msg;
        msg.data = counter++;
        pub.publish(msg);
        rate.sleep();
    }
}

Here we simply publish a counter value at 10Hz, using a ROS Rate. Note the new include for the Int64 message from the std_msgs package.

Let’s try to compile with the same flags as before.

$ g++ test.cpp -o your_node -I/opt/ros/melodic/include -L/opt/ros/melodic/lib -lroscpp -lrostime -lrosconsole
/usr/bin/ld: /tmp/ccWmIKXz.o: undefined reference to symbol '_ZN3ros13serialization18throwStreamOverrunEv'
/opt/ros/melodic/lib/libroscpp_serialization.so: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status

Well, that didn’t work. It seems we didn’t provide the flag for the libroscpp_serialization.so. Let’s add it and compile again:

$ g++ test.cpp -o your_node -I/opt/ros/melodic/include -L/opt/ros/melodic/lib -lroscpp -lrostime -lrosconsole -lroscpp_serialization
$ ./your_node # Make sure you have a roscore running before that
[ INFO] [1562568217.238139066]: Node and publisher created, now publishing data

It works! In another terminal you can also check the published value on the /number topic (with rostopic list and rostopic echo)

How to add any ROS library

By linking the 4 previous libraries you can already do many things: create a node, use the log functionalities, publish data on a topic, subscribe to a topic, create a service server, etc. That’s most of the core ROS functionalities.

Now, if you want to add another ROS library, you’ll have to know which flag to use for the compilation.

Once you understand how to do, this will become quite easy.

First, go to the lib folder of your ROS installation.

$ cd /opt/ros/melodic/lib/
$ ls
… List of files ...

The libraries you can use are all following the same name rules: “lib” + name of the library + “.so” extention.

To link a library with g++, what you have to do is to take the name of the library (without “lib” and “.so”), and add the “-l” prefix to it. For example, if you want to link the libcv_bridge.so library you’ll have to add “-lcv_bridge” to your compilation flags.

Also, note that in order to link a library, you have to install the corresponding packages before – with sudo apt install.

Sometimes, when compiling you’ll have missing dependencies that are not ROS libraries. Let’s say you want to use the TF functionality. You’ve added #include <tf/transform_listener.h> in your Cpp code so you can listen to your robot TF. But when compiling you get this error:

/usr/bin/ld: /tmp/ccyNSRqt.o: undefined reference to symbol '_ZN5boost6system15system_categoryEv'
//usr/lib/x86_64-linux-gnu/libboost_system.so.1.65.1: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status

Well, this just means that you have to add “-lboost_system” to your compilation flags! And if you don’t have the boost_system library installed, you will first need to install it – in this case, make a simple Google search if you don’t know the name of the package to install.

Node outside catkin workspace with CMakeLists.txt

If you use CMake to compile your files, then in your CMakeLists.txt, you’ll have to write this:

include_directories(/opt/ros/melodic/include /opt/ros/melodic/lib)

add_executable(your_node test.cpp)

target_link_libraries(your_node
-I/opt/ros/melodic/include -L/opt/ros/melodic/lib
-lroscpp -lrostime -lrosconsole -lroscpp_serialization)

You just have to add the path to the libraries in include_directories(), and add the library flags in the target_link_library().

Conclusion

As you can see, creating a ROS node outside a catkin workspace is totally doable, though it requires a little bit of understanding about how to link libraries to a Cpp file, and how to find ROS libraries to add.

With this post you should be able to get started and do some interesting stuff!

Leave a Comment