Tutorial on ROS Pluginlib
12 May 2020
[ ROS , software , how-to ]


One of the most useful things that you can do when developing a robotics stack is to compartmentalize your code into plugins. This is very useful when you need to write various implementations of a similar thing, and want to be able to build/include/use only the plugins we are interested in and/or switch between implementations at run-time. This allows you to be able to develop different implementations in parallel and save you time during compilation depending on the use case.

However I’ve found that most of the tutorials illustrate only the base cases where the plugins are built inside a single package (and are quite confusing: example). However this is eliminating a key feature of plugins – which is that you are able to develop independent implementations without having to compile all of the plugins.

The base class of the plugin and the implementations all in one package. This makes it difficult to work on individual plugins without having to compile all of the other plugins and their dependencies.
The base class of the plugin and the implementations all in one package. This makes it difficult to work on individual plugins without having to compile all of the other plugins and their dependencies.
The base plugin in one package, and the plugins all in one package. This is effectively the same as the before, all of the plugins are still in the same package.
The base plugin in one package, and the plugins all in one package. This is effectively the same as the before, all of the plugins are still in the same package.

The more proper usage case is illustrated as follows, where relevant plugins are packaged separately, allowing the possibility of separate compilation. This is the most helpful use case, when you design your code architecture for loading various planners, controllers, etc. This is a quick tutorial to showcase how to set up this architecture.

The ideal use case described in this tutorial. Relevant plugins are packaged separately, allowing the possibility of separate compilation
The ideal use case described in this tutorial. Relevant plugins are packaged separately, allowing the possibility of separate compilation
A note on structure: This tutorial is built around assuming that you have your base class and implementation class are in different packages, and that you may have more than one plugin packages. You may find this structure unnecessary. Here are some suggestions:
  • If you want to have base class and plugin classes in the same package, follow the same steps here, except just put them all in one package (and add to the same CMakeLists.txt and package.xml as appropriate).
  • If you find it cumbersome to have multiple plugin packages, you can just put all of your plugins inside a single plugin package. In that case, ignore all the parts regarding nonlinear_controller.

This tutorial requires basic understanding of ROS, writing CMakeLists.txt, and basic proficiency of C++ and virtual abstract classes.

Problem statement

Suppose that you have a control framework which we will call ControlFramework that loads a controller and applies it, regardless of its underlying choice of implementation. Suppose you have 3 implementations: LinearController1, and LinearController2, and NonlinearController, you want to be able to choose between them at runtime. Also, suppose that NonlinearController has its own dependencies noninear_dependency that takes a long time to build… and on the days you only want to use one of the LinearControllers, you’d like to just ignore NonlinearController altogether.

Code layout

We will have have 4 pieces of code in this:

  • A wrapper class, ControlFramework, which loads the plugin at runtime depending on a parameter.
  • A virtual abstract base class, Controller, which will contain virtual abstract functions that ControlFramework will call at runtime.
  • The plugins LinearController1, LinearController2, and NonlinearController, which will derive from base class Controller.

These will be placed into three packages:

  • controller, which contains the wrapper class ControlFramework and the virtual abstract base class Controller.
  • linear_controller, which contains plugins LinearController1 and LinearController2.
  • nonlinear_controller, which contains the plugin NonlinearController.

Base class layout

To do this, we create a new virtual base class Controller.h in the package controller where we will call the plugins from, like so:

controller
  ├ config
  ├ include
  │  └ controller
  │     └ controlFramework.h
  │     └ Controller.h
  ├ src
  │  └ ControlFramework.cpp
  ├ CMakeLists.txt
  └ package.xml

Plugin classes layout

These are pretty standard, but notice the addition of a plugins.xml. Their names doesn’t really matter, whatever you like as long as you are consistent:

linear_controller
  ├ config
  ├ include
  │  └ linear_controller
  │     └ LinearController1.h
  │     └ LinearController2.h
  ├ src
  │  └ LinearController1.cpp
  │  └ LinearController2.cpp
  ├ CMakeLists.txt
  ├ package.xml
  └ linear_controller_plugins.xml
nonlinear_controller
  ├ config
  ├ include
  │  └ nonlinear_controller
  │     └ NonlinearController.h
  ├ src
  │  └ NonlinearController.cpp
  ├ CMakeLists.txt
  ├ package.xml
  └ nonlinear_controller_plugins.xml

Creating the plugins

Base class

First, we will write the base class Controller and the plugins. Here, think about what you want your plugin to do. For example, we want all of the plugins to take in a current state, current input, and output the new input.

controller/include/controller/Controller.h

#pragma once

#include <ros/ros.h>

namespace control {
class Controller {
public:
  Controller() {};
  virtual ~Controller() {};

  virtual bool initialize(const ros::NodeHandle& n,
                          const ros::NodeHandle& n_private) = 0;
  virtual double computeFeedback(double x, double u) = 0;
};
} //namespace control

We’ve included an initialization function, as well as our computeFeedback function.

plugin packages

Now we write the derived classes. A very basic implementation is as follows:

linear_controller/include/linear_controller/LinearController1.h

#pragma once

#include <ros/ros.h>
#include <controller/Controller.h>                      // Inheriting the base class
namespace control {
class LinearController1 : public Controller {
public:
  LinearController1() {};                               // Constructor
                                                        // No destructor!
  bool initialize(const ros::NodeHandle& n,               
                  const ros::NodeHandle& n_private);
  double computeFeedback(double x, double u);

private:
  // More stuff...
};
}

linear_controller/src/LinearController1.cpp

#include <linear_controller/LinearController1.h>
#include <pluginlib/class_list_macros.h>               // Note, you must include this line

namespace control {

bool LinearController1::initialize(const ros::NodeHandle& n,               
                                   const ros::NodeHandle& n_private)
{
  // do stuff here!
  return true;
}

double LinearController1::computeFeedback(double x, double u)
{
  // do stuff here!
  return 0.0;
}
}
PLUGINLIB_EXPORT_CLASS(control::LinearController1, control::Controller)

Note that in the cpp file, we added #include <pluginlib/class_list_macros.h>. We also need to add the last line to the file: PLUGINLIB_EXPORT_CLASS(namespace::PluginClassName, namespace::DerivedClassName), which registers classes as plugins.

Now do the same for LinearController2 and NonlinearController!

Building and exporting the plugins

Okay, now we have to write the CMakeLists.txt, package.xml, and the plugin.xml’s for these packages. Because these files are different packages, we’re going to compile them as separate libraries and add them to the pluginlib manifest.

Building the plugins

We first write the CMakeLists.txt for the plugin packages. For package linear_controller, we add both LinearController1 and LinearController2 to the library that we compile, which is called linear_controller:

linear_controller/CMakeLists.txt

...
include_directories(include
      ${catkin_INCLUDE_DIRS}
)

add_library(linear_controller                         # <-- the library name!
      src/LinearController1.cpp
      src/LinearController2.cpp
)

add_dependencies(linear_controller ${catkin_EXPORTED_TARGETS})
target_link_libraries(linear_controller ${catkin_LIBRARIES} )
install(TARGETS linear_controller
      LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
)

And similarly for nonlinear_controller. Remember, the whole reason that we wanted separate packages for plugins is because we want to be able to compile only the plugins we are interested in. Here, nonlinear_controller has the dependency nonlinear_dependency. If we’re only working with linear_controller, we want to not have to compile nonlinear_controller and it’s dependencies if we wanted to.

nonlinear_controller/CMakeLists.txt

...
find_package(catkin REQUIRED COMPONENTS
      nonlinear_dependency                           # dependencies only to nonlinear
)

catkin_package(
      INCLUDE_DIRS include
      LIBRARIES ${PROJECT_NAME}
      CATKIN_DEPENDS
        nonlinear_dependency                        # dependencies only to nonlinear
      DEPENDS
)
...
include_directories(include
      ${catkin_INCLUDE_DIRS}
)

add_library(nonlinear_controller                    # <-- the library name!
      src/NonlinearController.cpp
)

add_dependencies(nonlinear_controller ${catkin_EXPORTED_TARGETS})
target_link_libraries(nonlinear_controller ${catkin_LIBRARIES} )
install(TARGETS nonlinear_controller
      LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
)

Write the Plugin XML file

For each package, we have to write a plugin xml. The plugin xml should declare all of the plugins contained in that package. For linear_controller, this should look like:

linear_controller/linear_controller_plugins.xml

<library path="lib/liblinear_controller">
  <class type="control::LinearController1" base_class_type="control::Controller">
    <description>LinearController1 for control</description>
  </class>
  <class type="control::LinearController2" base_class_type="control::Controller">
    <description>LinearController2 for control</description>
  </class>
</library>

here, we have 2 plugin classes declared inside this library. Note that the library path, lib/liblibrary_name should match the library name that you’ve specified in the CMakeLists.txt.

If you’ve compiled a separate library inside this package (So you have two libraries that you’ve specified in CMakeLists.txt), you may append to this xml like such:

linear_controller/linear_controller_plugins.xml

<library path="lib/liblinear_controller">
  <class type="control::LinearController1" base_class_type="control::Controller">
    <description>LinearController1 for control</description>
  </class>
  <class type="control::LinearController2" base_class_type="control::Controller">
    <description>LinearController2 for control</description>
  </class>
</library>
<!-- plugins in a separate library that you've compiled in CMakeLists.txt -->
<library path="lib/libanother_linear_controller">
  <class type="control::AnotherLinearController" base_class_type="control::Controller">
    <description>AnotherLinearController for control</description>
  </class>
</library>

similarly:

nonlinear_controller/nonlinear_controller_plugins.xml

<library path="lib/libnonlinear_controller">
  <class type="control::NonlinearController" base_class_type="control::Controller">
    <description>NonlinearController for control</description>
  </class>
</library>

Export the plugins to ROS

Now, with the plugins.xml manifests set, we venture to package.xml to make the plugins visible to the ROS toolchain with the export tag

linear_controller/package.xml

<package>
  ...
  <export>
    <controller plugin="${prefix}/linear_controller_plugins.xml" />
  </export>
</package>
Note: The export tag has the following format:
base_class_package_name plugin="${prefix}/{plugins_filename}.xml"
where base_class_package_name is the package where the base class of the plugin is located! In our case, this will be package controller which contains the Controller.h virtual abstract class that we have derived the plugins from.

Similarly for nonlinear_controller:

nonlinear_controller/package.xml

<package>
  ...
  <export>
    <controller plugin="${prefix}/nonlinear_controller_plugins.xml" />
  </export>
</package>

Check build/export

After you’ve built and exported your code, you can already check if you were successful! Open a terminal, and use

rospack plugins --attrib=plugin controller

You should see something like..

controller /path/to/linear_controller_plugins.xml
controller /path/to/nonlinear_controller_plugins.xml

Using the plugins

Now comes the fun part! we will use the plugins in ControlFramework.

We first add the appropriate headers to ControlFramework.h:

controller/include/controller/ControlFramework.h

#pragma once

#include <ros/ros.h>
#include <pluginlib/class_loader.h>                  // Allows us to load plugins
#include <controller/Controller.h>                   // The header file for the base class

namespace control {

class ControlFramework {
public:
  ControlFramework();
  ~ControlFramework();

  bool initialize(const ros::NodeHandle& n,
                  const ros::NodeHandle& n_private);
  // More stuff...
private:
  // We'll create a flag whether it is linear or nonlinear:
  enum ControlMethod { LINEAR1, LINEAR2, NONLINEAR };

  // Here, we have to maintain a plugin loader for the entire duration of the program
  std::unique_ptr<pluginlib::ClassLoader<controller::Controller>> controller_plugin_loader_ptr_;
  // And this is the pointer to the controller
  boost::shared_ptr<Controller> controller_;

  // More stuff...
};
} // namespace control

and lastly, to use:

controller/src/ControlFramework.cpp

#include <controller/ControlFramework.h>                 

namespace control {
bool ControlFramework::initialize(const ros::NodeHandle& n,
                                  const ros::NodeHandle& n_private)
{
  // stuff ...

  // Load the plugin loader: The syntax here is:
  // std::make_unique<pluginlib::ClassLoader<namespace::base_class_name>>
  //                        ("base_class_package_name", "namespace::base_class_name");
  controller_plugin_loader_ptr_ = std::make_unique<pluginlib::ClassLoader<controller::Controller>>
                                                      ("control", "control::Controller");

  // Here we're going to switch which controller depending on the method chosen
  // by a parameter, 'method'.
  if (method == ControlMethod.LINEAR1)
  {
    controller_ = controller_plugin_loader_ptr_->createInstance("control::LinearController1");
  } else if (method == ControlMethod.LINEAR2)
  {
    controller_ = controller_plugin_loader_ptr_->createInstance("control::LinearController2");
  } else if (method == ControlMethod.NONLINEAR)
  {
    controller_ = controller_plugin_loader_ptr_->createInstance("control::NonlinearController");
  }

  // And as we've added an initialization function to allow initialization of
  // specific methods for each specific controller
  controller_->initialize(n, n_private);
}
  // More stuff...
private:
  // Here, we have to maintain a plugin loader for the entire duration of the program
  std::unique_ptr<pluginlib::ClassLoader<controller::Controller>> controller_plugin_loader_ptr_;
  // And this is the pointer to the controller
  boost::shared_ptr<Controller> controller_;

  // More stuff...
};
} // namespace control

And voila! You can now use controller_ as a normal pointer to your controller and use it as normal.