ROS Node as Adapter of Core Logic

Look at some of famous projects including ROS interface: octomap (opens in a new tab), voxblox (opens in a new tab), cartographer (opens in a new tab), and zed (opens in a new tab). Can you guess what is shared between them? They tried to separate their core logic from ROS either by separating the repository or directories.
ROS as adapter, not entangler
Before discussing separation, I want to introduce a perspective that ROS can be seen as an adapter. Understanding this concept will help you clearcut ROS from your core logic.
Recap on adapter pattern
Adapter (opens in a new tab) is a very famous design pattern, which can be understood with the following words: wrapper, interface, bridge,or interpreter. Let us see the following three code snippets.
#include "my_core_logic.hpp"
#include "GameAsset.hpp"
class AdapterForGameEditor: public GameAsset { // GameAsset = interfacing with Game.cpp
public:
void run() override;
...
private:
CoreLogic core_logic_;
...
}
#include "AdapterForGameEditor.hpp"
void AdapterForGameEditor::run() {
core_logic_.run();
}
#include "AdapterForGameEditor.hpp"
GameAsset* asset_with_core_logic = new AdapterForGameEditor();
asset_with_core_logic->run();
Here, Game.cpp
can be seen as a client code, which knows only GameAsset
class. To perform some my_core_logic
encapsulated inside GameAsset
, we can
adopt the above adapter pattern for GameAsset
by defining
AdapterForGameEditor
which understands the interface GameAsset
while
including core logic.
ROS node can be also an adapter
In a very similar manner, we can easily find (opens in a new tab) the below adapter-pattern code.
#include <rclcpp>
#include <sl/Camera.hpp> // core
class ZedCamera : public rclcpp::Node{ // Node = base class having ROS interfacing
...
//
image_transport::CameraPublisher mPubRgb; // some publishers
clickedPtSub mClickedPtSub; // some subscriber
...
sl::Camera mZed; // core class from
}
As you can see, some core header is included as a member of ROS node, while the
ros node has interface derived from Node
. More generally, this kind of code
can be expressed with the below pseudo code (although I expressed in ROS2 code,
):
#include <rclcpp>
#include <your_core_logic.hpp>
class YourRosNode : public rclcpp::Node{
...
// set of publishers
rclcpp::Publisher<T>::SharedPtr publisher_;
// set of subscribers
rclcpp::Subscriber<T>::SharedPtr subscriber_;
// core logic class
YourCodeLogicClass core_class_;
}
As you can see from the above, your ROS node YourRosNode
can be made from 1)
inheriting interface Node
class and including core logic YourCoreClass
.

Organization
ROS and core logic on same file? Only if you are 100% sure that ROS is the only outlet!
Let us assume that we have a core logic and it should be shipped to various platforms. This is the case for ZED (opens in a new tab) SDK which should be delivered to Unity (opens in a new tab), Unreal (opens in a new tab), H-hub (opens in a new tab), and ROS 2 (opens in a new tab),... If you are in a similar situation, it is insane if you entangle ROS code and pure logic which should be reused across different outlets. Of course, ZED did not go that way. See this picture:

So, what is the direction?
Simple: YOUR CORE LOGIC SHOULD RUN AND TESTED WITHOUT ROS. Your aim is to
make your logics reusable across many outlets. Calling a core package as core
and a ros wrapping package as ros-wrapper
, this can be achieved by:
- Your
core
should be a pure CMake package for easy reusability, not catkin (opens in a new tab) or ament (opens in a new tab) package. You can create pure cmake package byros2 pkg create --build-type core
in ROS2. - You have two different directories or repositories for storing 1)
core
and 2)ros-wrapper
. ros wrapper
only finds and usescore
inCMakeLists.txt
. Also,ros wrapper
should not contain complex logics. It just sets incoming data forcore
and gets the computation result fromcore
.- For your development convenience, it would be good if
core
andros-wrapper
are built with a single build command (colcon build
orcatkin_make
) on a workspace (e.g.,ros2_ws
orcatkin_ws
). - Testing
core
should be possible withoutros-wrapper
.
If you want to know how we can write CMakeLists.txt
and design file
structure, read the next article π.