Write a ROS controller
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 codesrc/mycontroller.cpp
implement theeg_controller::MyController
class that inherits fromcontroller_interface::ControllerBase
or a derivative thereof -
Build the shared library
libeg_controller
containing theeg_controller::MyController
class usingCMakeLists.txt
-
You will probably have to
<depend>
oncontroller_interface
andhardware_interface
inpackage.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 theeg_controller::MyController
controller class usingROS 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 librarylibeg_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
-
Threading model:
controller_manager/spawner
, the actual node that loads controllers, usesrospy.spin()
.rospy.spin()
has a complex threading model whose details are not well documented. Matthew Elwin has investigated them in detail. The relevant part is that callbacks in the controller code can run in separate threads and are not thread-safe. For example, see howfranka_ros/franka_example_controllers/CartesianImpedanceExampleController
uses a mutex.