If you are writing a ROS controller, that means you are working with a real robot. Congratulations!

It involves a lot of boilerplate code, so this post describes the process step-by-step. We will focus on the general process rather that the actual code of the controller. See franka_ros/franka_example_controllers/CartesianImpedanceExampleController for a concrete example.

We will assume that you want to write MyController in the eg_controller ROS package, which is in the ws ROS workspace. Directory structure follows the convention.

  • Header file include/eg_controller/mycontroller.h and source code src/mycontroller.cpp implement the eg_controller::MyController class that inherits from controller_interface::ControllerBase or a derivative thereof

  • Build the shared library libeg_controller containing the eg_controller::MyController class using CMakeLists.txt

  • You will probably have to <depend> on controller_interface and hardware_interface in package.xml to be able to compile

  • Put the following macro at the end of src/mycontroller.cpp. It allows the ROS control infrastructure (specifically, controller_manager/spawner) to dynamically load the eg_controller::MyController controller class using ROS pluginlib:

PLUGINLIB_EXPORT_CLASS(eg_controller::MyController, controller_interface::ControllerBase)
  • Also for ROS pluginlib: put the following XML in ws/src/eg_controller/eg_controller_plugin.xml. Note how it references the shared library libeg_controller we compiled above.
<library path="lib/libeg_controller">
  <class name="eg_controller/MyController" type="eg_controller::MyController" base_class_type="controller_interface::ControllerBase">
    <description>
      Short description of MyController.
    </description>
  </class>
</library>
  • Last step for ROS pluginlib: put the following XML at the end of ws/src/eg_controller/package.xml
<export>
  <controller_interface plugin="${prefix}/eg_controller_plugin.xml"/>
</export>
  • Controller packages provide a YAML file describing the parameters their controllers use. Because controllers are dynamically loaded classes only, and not their complete ROS nodes, it is not possible to pass command line arguments to them. The only way to configure them is to load their parmeters to the ROS parameter server in the proper namespace. Then the controller code can load them from that namespace. For example ws/src/eg_controller/config/eg_controller.yaml:
mycontroller:
  type: eg_controller/MyController
  param1: 108
  param2:
    - tat
    - tvam
    - asi

This YAML file when loaded to the ROS parameter server will allow controller_manager/spawner to load the mycontroller controller. type will allow it to look up the matching pluginlib entry, and the controller class can use the rest of the parameters - param1 and param2 here

  • For example, you can load this controller in a launch file like so:
<?xml version="1.0" ?>
<launch>
  <!-- load MyController's parameters -->
  <rosparam command="load" file="$(find eg_controller)/config/eg_controller.yaml" />
  <!-- load MyController -->
  <node name="controller_spawner" pkg="controller_manager" type="spawner" respawn="false" output="screen" args="myconroller"/>
</launch>

Notes

(page source)