Overview of Codebase
Last updated
Last updated
In this article, we will present a high-level overview of the ros2 control codebase, which involves two repositories: ros2_control and ros2_controllers. Collectively, we refer to these as the ros2 control library for the ease of discussion.
The ros2 control library consists of following major components:
Control Node Executable: An executable responsible for launching the Controller Manager node
Controller Manager: This is the central entity that manages the life cycle of controllers. It loads, unloads, starts, and stops controllers, and also handles their configuration.
Resource Manager: This component manages resources like sensors and actuators, ensuring that they are properly allocated to controllers as needed.
Hardware Interface: This component provides an abstraction over different hardware devices. It allows controllers to communicate with hardware without needing to know the specifics of the hardware.
Controllers: Controllers are responsible for implementing specific control algorithms. Examples include joint trajectory controllers for arm movements, velocity controllers, position controllers, etc. They process input data and send command values to actuators.
In this article, we will guide you through each component and present the interaction betwteen them.
The first component is control node. Strictly speaking, this is just an executable called ros2_control_node defined in . The source file of this executable is . The main responsibility of this executable is to launch a ControllerManager
and start the main update loop. The snippets below shows the critical section of the script:
This loop continuously reads states from hardware, updates commands, and then writes these commands to the hardware. The loop's frequency is set by the update rate parameter.
From previous section, we established that the ControllerManager
is a critical piece in the ros2 control library. Given the extensive size of the source code, it's impractical to cover every detail here. Instead, this section will focus on the code snippets of the read
, update
, and write
functions.
We make the following observations based on the listed code above:
The read
and write
operations are delegated to the ResourceManager
ControllerManager
has access to a list of controllers.
We will see in the later section that the ResourceManager is responsible for dealing with the hardware whereas the controllers are used to apply control theory algirhtms.
The following parameters are invovled in ResourceManager
:
activate_components_on_start: Determines whether components are activated at startup.
configure_components_on_start: Specifies if components should be configured when starting.
robot_description: Contains the description of the robot configuration.
update_rate: Sets the frequency at which updates are made.
use_sim_time: Indicates whether to use simulated time.
The read
and write
functions delegate the operations to the components in ResourceStorage
as indicated by the code below:
sensor
, actuator
, and system
are hardware type, which is specified by the type
attribute in the ros2_control
element. As we can see, the sensor
type hardware is read-only whereas actuator
and system
type hardware can do both read and write.
We also want to highlight the classic technique dependency inversion employed in the implementation. The ResourceStorage
class depends on the System
class, which in turn depends on the SystemInterface
as it's used as one of the arguments in the constructor. The user-provided plugin class needs to implements the SystemInterface
so that it will be picked up by ros2 control automatically. It's interesting to notice the SystemInterface
itself extends the lifecycle node interface. This suggests the hardware control code is in the form of a lifecycle node.
Now, let's focus on the update operations performed by the ControllerManager
. We've see earlier that these operations are delegated to the Controller
class.
One of the key line in the update
method of ControllerManager
class is the one below:
Where the variable loaded_controller
is of type ControllerSpec
, which has the following definition:
As we can see, the update
function simply read the command value from rt_command_ptr
and then set the value in the command_interfaces_
. We are already familiar with what command interfaces do and the remaining questions are (1) what is rt_command_ptr
variable and (2) where it gets from the data. The rt_comamnd_ptr
can be considered a buffer and the value is provided by the subscription to the command topic:
In this article, we examined many key components of the ros2 control library. The term interface seems overloaded and its meaning varies in different contexts. Therefore, when we come across the term "interface" in the code or documentation, it is important to understand what exactly it represents. It could refer to a system interface, a command interface, a state interface, or merely the general concept of an interface in software engineering.
The root component of the ros2 control library is the ros2 control node executable, responsible for launching the ControllerManager
node. The ControllerManager
is the core of the library, it has access to the list of controllers and the resource manager. The resource manager is responsible for loading the URDF file and load and initialize the hardware information accordingly. These hardware information is stored in the resource storage, which also has access to the the user-implemented hardware plugin. The hardware plugin behaves like a driver from ROS2's perspective. It can read data from the hardware or send command to it.
A hardware can have many components and each of them is represented by a HardwareComponentInfo
class, which contains the command data and the hardware state. Command interface and state interface in the ComponentInfo
class are accessible by controllers. Controllers apply control theory algorithm to determine the optimal commands that needed to be sent to the hardware component. They are the key components for the update
step in the update loop. Their implementation involves subscribing to the command topic and updating the state (i.e. command interface and state interface) of the component.
The diagram below illustrates the overall architecture:
The first thing we want to highlight is that ResourceManager
is defined in , which means it's closed related to the actual hardware. Secondly, ResourceManager
class consumes the URDF information as indicated by the constructor taking an urdf
argument and the load_urdf
function. Third, it has a field of type ResoureStorage
.
This means the update
action is provided in the ControllerInterfaceBase
(.
This setup is similar to the system/actuator/sensor interface. The ControllerSpec
depends on the ControllerInterface
and the user can provide their own controller, which will implement the ControllerInterface
. The ros2_controllers packages provides implementations of many well-known controllers. For more details, please refer to the package.
Let's take a closer look at how the controller work. We will study the ForwardCommandController
, presumably the most simple one. According to the , this controller just forwards the command to the interface. Our previous focus was on the update
function in different classes, so let's take a look at the implementation of the update
function in ForwardCommandController
class. The complete code can be found at and here we only list the essential part of this function: