Skip to content

Commit cbedc83

Browse files
committed
ros2_control article
1 parent 0cdef06 commit cbedc83

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
---
2+
title: 'Learn ros2_control: from Definition to Implementation'
3+
publishDate: '2025-05-31'
4+
updatedDate: '2025-05-31'
5+
description: 'ros2_control can be understood by everyone, even if you are new to ROS2.'
6+
tags:
7+
- ros2
8+
- ros2_control
9+
- control
10+
- robotics
11+
- robot
12+
- c++
13+
- cpp
14+
- hardware
15+
language: 'English'
16+
draft: false
17+
---
18+
19+
# ros2_control for everyone
20+
21+
![alt text](./ros2_control_architecture.webp)
22+
23+
**ros2_control** is a framework system developed in ROS 2 ecosystem for elevating production speed and abstraction to any hardware system. The key concept is **abstraction**. From a single framework, you can empowering any robot project to life.
24+
25+
## 1. The design of ros2_control
26+
27+
1. **Controller Manager (CM)**
28+
Controller Manager, or in short CM, is the main component in ros2_control and a layer for connecting hardwares to the controllers. It manages the lifecyle of controllers, start and stop of controllers, and initialize the hardware. CM have access to both controllers and Resource Manager. We are configuring CM with yaml file config and manage or interfacing it via ROS service.
29+
30+
2. **Resource Manager (RM)**
31+
Resource Manager, RM in short, is an abstraction for hardware and the driver. We call driver or program that drive the real hardware as hardware component in ros2_control. We are not coding this RM as it is part of ros2_control system. RM loads hardware component and manages its lifecyle: `on_init()`, `on_configure()`, `on_activate()`, `read()`, `write()`. Node lifecyle is a new concept in ROS2, not available in ROS1, in short it is a node with steroids. Don’t worry to much on lifecyle, we will see it section 3.
32+
33+
3. **Controller Componenents (controller_interface)**
34+
Controllers is the program that we make to manipulate the hardware_interface. We can make many controllers for different control purpose like, for example, walking, jumping, or kick will use different controller. We seperate each control for easier management.
35+
We can refer controllers as the same in control theory. Given input values from sensor, it will output the calculated result in the actuator. This controller_interface also use ROS2 lifecyle and a loop function method: `update()`, that access latest hardware state, write to hardware component, and execute at configured frequency in CM yaml file.
36+
37+
4. **Hardware Components (hardware_interface)**
38+
The hardware component is the driver program that we make to drive, communicate with real, pyhsical hardware, on our robot. hardware_interface represent abstraction in ros2_control. It purpose is to access the real hardware, abstract it, so that we can access the hardware state and hardware command from controller_interface. hardware_interface export hardware state to controller and receive hardware command from controller. There are three types of hardware component:
39+
40+
- `Sensor`: can only read hardware state, e.g.: temperature sensor (read temperature)
41+
- `Actuator`: can only write command to hardware, e.g.: motor (write pwm signal)
42+
- `System`: capable of both sensors and actuator, e.g.: servo(read and write position)
43+
44+
---
45+
46+
## 2. ROS2 Lifecycle
47+
48+
Node lifecyle, introduced in ROS2, is designed as a system for managing the states of ROS2 node in ordered and predictable way. It is usefull in robotics application where we need full control for the node initialization, startup, shutdown, and the program loop. Used in a system where we need to make sure we need to run the initialization process first and shutdown the program properly so that the robot have the expected behavior.
49+
50+
The lifecycle includes these states:
51+
- **Unconfigured**: Initial state
52+
- **Inactive**: Configured but not operational
53+
- **Active**: Fully operational
54+
- **Finalized**: Shutting down
55+
56+
Transitions between states are triggered by these functions:
57+
- `configure()`: Initialize parameters, set up publishers/subscribers
58+
- `activate()`: Start program execution (e.g., timers)
59+
- `deactivate()`: Stop execution while retaining configuration
60+
- `cleanup()`: Release resources and clear data
61+
- `shutdown()`: Gracefully terminate the program
62+
63+
![Node Lifecyle](./lifecyle_state.png "Node Lifecyle")
64+
65+
---
66+
67+
## 3. Adaptation of ROS2 Lifecyle in hardware_interface and controller_interface
68+
69+
ros2_control use the same principle as the lifecycle. Both controller_interface and hardware_interface adapted the design of node lifecyle with more fine control of the program.
70+
71+
- `on_init()` \
72+
Called once when the controller is first instantiated.
73+
Used for basic initialization tasks like member variable setup.
74+
**Should not access ROS interfaces** (publishers, subscribers, parameters).
75+
Failures here prevent the controller from being used.
76+
77+
- `on_configure()` \
78+
Transitions the controller from "Unconfigured" to "Inactive" state.
79+
**Used to load parameters, initialize ROS interfaces, and allocate resources.**
80+
Sets up communication channels without actively using them.
81+
Common tasks: creating publishers/subscribers, loading configuration
82+
83+
- `on_activate()` \
84+
Transitions the controller from "Inactive" to "Active" state.
85+
Enables the controller to **start operating and controlling hardware**.
86+
Typically starts publishing control commands and processing sensor data
87+
88+
- `on_deactivate()`\
89+
Transitions the controller from "Active" to "Inactive" state.
90+
S**tops active control while maintaining configuration.**
91+
Ensures the controller stops sending commands to hardware.
92+
Allows for quick reactivation later without reconfiguring.
93+
94+
- `on_error()`\
95+
**Called when the controller encounters an error during operation**.
96+
Provides opportunity for error handling and graceful degradation.
97+
May attempt recovery or prepare for cleanup/shutdown.
98+
99+
- `on_cleanup()`\
100+
Transitions from "Inactive" to "Unconfigured" state.
101+
**Releases resources acquired during configuration.**
102+
Undoes what was done in on_configure().
103+
Returns the controller to a state where it can be reconfigured.
104+
105+
- `on_shutdown()`\
106+
Called when the controller is being terminated.
107+
Performs final cleanup before destruction.
108+
Releases any remaining resources.
109+
110+
### 3.1 Main Loop
111+
112+
Both **controller_interface** and **hardware_interface** have main loop that run at the specified frequency. This main loop can only be executed if the state is active, which is after `on_activate()` successfully executed without error.
113+
114+
- `read()`, `write()` \
115+
The main loop for **hardware_interface** with type **sensor** only capable of `read()`, **actuator** can only `write()`, and **system** capable of both.
116+
117+
- `update()` \
118+
The main loop for **controller_interface**.
119+
120+
### 3.2 Hardware Abstraction Management by **Export and Claim**
121+
122+
Every hardware state or command that we accessed from `hardware_interface` needs to be exported or advertised so that the controller_interface can access it. Based on the type of hardware_interface (Sensor, Actuator, or System), we use `export_state_interface` and `export_command_interface`. For *sensor type* hardware, we can only `export_state_interface`. For *actuator type* hardware, we can only `export_command_interface`. *System type* hardware capable of both.
123+
124+
The Resource Manager handles these exported interfaces and manages which controller can access which hardware resource. Controllers must explicitly claim the interfaces they need with `claim_interfaces()` during activation. This claiming mechanism prevents conflicts where multiple controllers might try to command the same hardware simultaneously.
125+
126+
The flow works as follows:
127+
1. Hardware components export their interfaces during initialization
128+
2. Controllers declare which interfaces they want to claim
129+
3. Resource Manager validates and manages these claims
130+
4. When activated, controllers gain exclusive access to their claimed command interfaces
131+
5. Multiple controllers can read state interfaces, but only one controller can write to a command interface
132+
133+
This export-and-claim pattern is central to how ros2_control safely manages hardware resources across different controllers.
134+
135+
#### 3.2.1. **Implementation of state export in `hardware_interface`**
136+
137+
```c++
138+
std::vector<hardware_interface::StateInterface> FooHardware::export_state_interfaces()
139+
{
140+
std::vector<hardware_interface::StateInterface> state_interfaces;
141+
for (unsigned int i = 0; i < info_.joints.size(); i++) {
142+
state_interfaces.emplace_back(hardware_interface::StateInterface(
143+
info_.joints[i].name,
144+
hardware_interface::HW_IF_POSITION,
145+
&joints_[i].state.position));
146+
state_interfaces.emplace_back(hardware_interface::StateInterface(
147+
info_.joints[i].name,
148+
hardware_interface::HW_IF_VELOCITY,
149+
&joints_[i].state.velocity));
150+
}
151+
152+
return state_interfaces;
153+
}
154+
```
155+
156+
From the code above, we are exporting the state interface by `export_state_interface()`. The example is from servo which is capable of position and velocity state. Here we have setup servo/ joint name in `info_.joints_` array. Then we push every state interface to `state_interfaces` vector. `hardware_interface::StateInterface(...)` takes prefix name (hardware name), state type (position and velocity), and variable pointer to handle the value we get from `read()` process.
157+
158+
#### 3.2.2. **Implementation of hardware command export in `hardware_interface`**
159+
160+
```cpp
161+
std::vector<hardware_interface::StateInterface> FooHardware::export_state_interfaces()
162+
{
163+
std::vector<hardware_interface::CommandInterface> command_interfaces;
164+
for (uint i = 0; i < info_.joints.size(); i++) {
165+
command_interfaces.emplace_back(
166+
hardware_interface::CommandInterface(
167+
info_.joints[i].name,
168+
hardware_interface::HW_IF_POSITION,
169+
&joints_[i].command.position));
170+
}
171+
172+
return command_interfaces;
173+
}
174+
```
175+
176+
Similiar to the previous state export, we export the command interface so that the hardware can be commanded from `controller_interface`. The command interfaces takes prefix name (hardware name), command type (position), and variable pointer to handle the value we pass trough to `write()` process.
177+
178+
#### 3.2.3. **Implementation of hardware claim-ing in `controller_interface`**
179+
180+
```cpp
181+
controller_interface::InterfaceConfiguration
182+
FooController::command_interface_configuration() const
183+
{
184+
controller_interface::InterfaceConfiguration config;
185+
config.type = controller_interface::interface_configuration_type::INDIVIDUAL;
186+
for (const auto& joint_name : joint_names_)
187+
{
188+
config.names.push_back(joint_name + "/" +
189+
hardware_interface::HW_IF_POSITION);
190+
}
191+
return config;
192+
}
193+
194+
controller_interface::InterfaceConfiguration
195+
FooController::state_interface_configuration() const
196+
{
197+
controller_interface::InterfaceConfiguration config;
198+
config.type = controller_interface::interface_configuration_type::ALL;
199+
return config;
200+
}
201+
```
202+
203+
Take a look at `command_interface_configuration()` first. We set the InterfaceConfiguration to INDIVIDUAL. It means we only claim the hardware individually, choosing the hardware what we want to command. The format to claim the hardware is a string of "hardware_name/command_type", e.g.: "head_pan/position" then we push it to the array.
204+
205+
Claiming the hardware state in `state_interface_configuration()` is the same as in command_interface_configuration(). In the example, we set the type to claim to ALL, we claim all available state exported from `hardware_interface`.
206+
207+
> [!IMPORTANT]
208+
> **Hardware command** can **only** be claimed **once** in any controller_interface. Claiming the hardware command in multiple controller_interface can result in hardware conflict and raise error from the Resource Manager.
25 KB
Loading
24.8 KB
Loading

0 commit comments

Comments
 (0)