Components vs. Plugins in ROS 2

Ubuntu + ROS 2

After our series of post about ROS 2 CLI tools (1, 2), we continue exploring the ROS 2 realm taking a look at ROS 2 components and more specifically, how they compare to plugins.

spoiler alert:

Long story short, components are plugins.

Short story long? Is that a thing?

Well plugins and components are indeed essentially the same thing. Down the road, both are built into respective shared libraries, neither have a main function, they both are loaded at runtime and used by a third party.

We’ll note here that while plugins come straight outta ROS 1, components on the other hand are the ROS 2 evolution of ROS 1 nodelets (what’s that?) after being exposed to a Fire Stone on a full moon. Same idea, different beasts.

Plugins vs. Components

So what are the actual differences? To put it simply, a component is a plugin which derives from a ROS 2 node.

This assertion is backed by the fact the both rely on the class_loader package, a ROS-independent library for dynamic class introspection and loading from runtime libraries. Their respective internal plugin-related plumbing (factory, registration, library path finding, loading etc.) is managed underneath by class_loader. Both offer a macro-based helper for registration, and both macros resolve to class_loader‘s CLASS_LOADER_REGISTER_CLASS macro. Yeah they immediately resolve to it, I mean, they don’t even try to hide it. On the other hand, why would they?

For a traditional plugin, the base class can be anything, user defined or not. The only constraint is that the derived class has to be default constructible (therefore, by the law of C++, the base class too). In the case of components, the plugin class commonly derives from the rclcpp::Node class, but it is not required. Indeed, the requirements for a class to be exported as a component are,

  • Have a constructor that takes a single argument that is a rclcpp::NodeOptions instance.
  • Have a method of the signature:
    • rclcpp::node_interfaces::NodeBaseInterface::SharedPtr get_node_base_interface(void)

which includes rclcpp::Node of course, but also e.g. rclcpp_lifecycle::LifecycleNode. It means that a component can inherit from any class meeting those requirements, but it can also implement them itself and skip inheritance altogether.

That last point is an important difference between plugins and components.

Components are special plugins

Indeed, traditionally plugins must declare their base classes when registering to class_loader factory scheme. This allows the factory to instantiate the derived object to a smart-pointer of base type which in turn can be delivered to the user, the developer.

// Registering a plugin
PLUGINLIB_EXPORT_CLASS(BaseClass, DerivedClass)
//
...
// Instantiating a plugin
// We have access to all of BaseClass API
std::shared_ptr<BaseClass> base_ptr = plugin_loader.createSharedInstance("DerivedClass");

Components’ base class registration on the other hand was developed relying on void (smart) pointer-based type-erasure. Thus allowing for a simple wrapper class as a common base class to all components. This design implies that the developer is not expected to query the factory for a new instance by himself. What would you do with a void pointer anyway? Instead the factory serves intended agents such as the rclcpp_components::ComponentManager and rclcpp_components::ComponentContainer.

// Registering a component
RCLCPP_COMPONENTS_REGISTER_NODE(DerivedNode)
//
...
// NodeInstanceWrapper is a wrapper around both a
// std::shared_ptr<void> and a
// rclcpp::node_interfaces::NodeBaseInterface::SharedPtr
// Not much to do with those!
rclcpp_components::NodeInstanceWrapper component_wrapper = component_loader->create_node_instance(options);

In most cases, components inherit from rclcpp::Node as it is the easiest way to fulfill the above requirements. Therefore, we’ll assume that a component is a ROS 2 node, with everything it involves, possibly parameters, listeners/publishers, services, action, et tutti quanti.

We can therefore make an important distinction here: components are plugins with a ROS 2 interface. This draws an important line as when and where to use plugins or components.

Plugins or components, that is the question

The answer to this question obviously depends on your project. But first, let me assume that you do have a need for plugins and spare you asking

‘Do you really need to use plugins?’

(see what I did there).

As a general rule, I’d recommend for you to use components over plugins
whenever possible. The reason is simple, you, as a components developer, do not have to deal with the plugin aspect of things beside the registration macro. No plugin name as a ROS parameter, no plugin loader, no exception handling etc. Then from a user perspective it is clearer I believe to launch a specific node, say my_plugin_b_node, rather than launching an interface node given a plugin’s name as a ROS parameter my_node plugin_name:='PluginB'. It also spares the frustration of seeing the node crash or being stall because the plugin is not found or misspelled.

Note that this recommendation does not prevent you from enforcing some common API the same manner it would arise from using plugins. Your components can inherit from a base class of your making which enforce this API. The base class may also implement the ROS interface, abstracting it away, leaving to the derived class a simple virtual doMath(Arg) -like function to define.

But my stuff is so modular that I’m using several plugins

Can you possibly rethink the design? Can you break it down in several small nodes that communicate through ROS interfaces? I’d bet that some of those intermediate data could be useful somewhere else in your system too.

If not, or if you find yourself with a different use case which does require plugin, you may still declare your node class as a component! Components and plugins are not mutually exclusive. Moreover,

In ROS 2 [components is] the recommended way of writing your code.

Show me some code

While doing the legwork for this post, I wrote a small ROS 2 package which has no other purpose than being a goto example on how to write a component or a plugin in ROS 2. You may find it on github: demo_plugin_component. This example showcase the use of plugins in conjunction with components, look for one and get both. After compiling, one may run it in either way,

Like a plain node,

$ ros2 run demo_plugin_component talker_node __params:=demo_plugin_component/cfg/params.yaml

As a component in a component container,

ros2 run rclcpp_components component_container
ros2 component load /ComponentManager demo_plugin_component ros2_playground::TalkerNode -p writter_name:='ros2_playground::MessageWritterDerived'

As a component in a component container, from a launch file,

ros2 launch demo_plugin_component talker.launch.py

Do not forget to run a topic echo to monitor the node output, it is a talker after all,

ros2 topic echo /chatter

Talk to us today

Interested in running Ubuntu Desktop in your organisation?

Newsletter signup

Select topics you’re
interested in

In submitting this form, I confirm that I have read and agree to Canonical’s Privacy Notice and Privacy Policy.

Are you building a robot on top of Ubuntu and looking for a partner? Talk to us!

Contact Us

Related posts

ROSCon 2019 – Canonical

What an exhausting, yet intriguing few days. Huge thanks to Open Robotics et. al. for hosting and setting it up. The fantastic community came in full force...

The State of Robotics – October 2019

October came, and October went. Happy November everybody. This month, since last month was quite Ubuntu robotics heavy, the focus is more on you. For you....

PSA for ROS users: Some things to know as Python 2 approaches EOL

We recently got an interesting question from a customer, and I think the answer might be helpful to a wider audience. Python 2 will reach end of life in two...