Skip to content

Commit 1977792

Browse files
authored
Merge branch 'master' into fix/exclusive_hw_interface_switching
2 parents 3141a50 + 7317e4f commit 1977792

File tree

28 files changed

+1284
-153
lines changed

28 files changed

+1284
-153
lines changed

.pre-commit-config.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ repos:
6363

6464
# CPP hooks
6565
- repo: https://github.com/pre-commit/mirrors-clang-format
66-
rev: v20.1.0
66+
rev: v20.1.3
6767
hooks:
6868
- id: clang-format
6969
args: ['-fallback-style=none', '-i']
@@ -133,7 +133,7 @@ repos:
133133
exclude: CHANGELOG\.rst|\.(svg|pyc|drawio)$
134134

135135
- repo: https://github.com/python-jsonschema/check-jsonschema
136-
rev: 0.32.1
136+
rev: 0.33.0
137137
hooks:
138138
- id: check-github-workflows
139139
args: ["--verbose"]

controller_interface/include/controller_interface/helpers.hpp

+8-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
#include <string>
2020
#include <vector>
2121

22+
// Add hardware interface helpers here, so all inherited controllers can use them
23+
#include "hardware_interface/helpers.hpp"
24+
2225
namespace controller_interface
2326
{
2427
/// Reorder interfaces with references according to joint names or full interface names.
@@ -79,13 +82,12 @@ inline bool interface_list_contains_interface_type(
7982
}
8083

8184
template <typename T>
82-
void add_element_to_list(std::vector<T> & list, const T & element)
85+
[[deprecated(
86+
"Use ros2_control::add_item method instead. This method will be removed by the ROS 2 Kilted "
87+
"Kaiju release.")]] void
88+
add_element_to_list(std::vector<T> & list, const T & element)
8389
{
84-
if (std::find(list.begin(), list.end(), element) == list.end())
85-
{
86-
// Only add to the list if it doesn't exist
87-
list.push_back(element);
88-
}
90+
ros2_control::add_item(list, element);
8991
}
9092

9193
} // namespace controller_interface

controller_interface/src/chainable_controller_interface.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ ChainableControllerInterface::export_state_interfaces()
9090
state_interfaces_ptrs_vec.clear();
9191
throw std::runtime_error(error_msg);
9292
}
93-
ordered_exported_state_interfaces_.push_back(state_interface);
94-
add_element_to_list(exported_state_interface_names_, interface_name);
93+
ros2_control::add_item(ordered_exported_state_interfaces_, state_interface);
94+
ros2_control::add_item(exported_state_interface_names_, interface_name);
9595
state_interfaces_ptrs_vec.push_back(
9696
std::const_pointer_cast<const hardware_interface::StateInterface>(state_interface));
9797
}
@@ -176,8 +176,8 @@ ChainableControllerInterface::export_reference_interfaces()
176176
reference_interfaces_ptrs_vec.clear();
177177
throw std::runtime_error(error_msg);
178178
}
179-
ordered_exported_reference_interfaces_.push_back(reference_interface);
180-
add_element_to_list(exported_reference_interface_names_, interface_name);
179+
ros2_control::add_item(ordered_exported_reference_interfaces_, reference_interface);
180+
ros2_control::add_item(exported_reference_interface_names_, interface_name);
181181
reference_interfaces_ptrs_vec.push_back(reference_interface);
182182
}
183183

controller_manager/controller_manager/spawner.py

+51-22
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
)
3333
from controller_manager.controller_manager_services import ServiceNotFoundError
3434

35+
from filelock import Timeout, FileLock
3536
import rclpy
3637
from rclpy.node import Node
3738
from rclpy.signals import SignalHandlerOptions
@@ -169,34 +170,61 @@ def main(args=None):
169170
if not os.path.isfile(param_file):
170171
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), param_file)
171172

172-
node = Node("spawner_" + controller_names[0])
173+
try:
174+
spawner_node_name = "spawner_" + controller_names[0]
175+
lock = FileLock("/tmp/ros2-control-controller-spawner.lock")
176+
max_retries = 5
177+
retry_delay = 3 # seconds
178+
for attempt in range(max_retries):
179+
tmp_logger = rclpy.logging.get_logger(spawner_node_name)
180+
try:
181+
tmp_logger.debug(
182+
bcolors.OKGREEN + "Waiting for the spawner lock to be acquired!" + bcolors.ENDC
183+
)
184+
# timeout after 20 seconds and try again
185+
lock.acquire(timeout=20)
186+
tmp_logger.debug(bcolors.OKGREEN + "Spawner lock acquired!" + bcolors.ENDC)
187+
break
188+
except Timeout:
189+
tmp_logger.warn(
190+
bcolors.WARNING
191+
+ f"Attempt {attempt+1} failed. Retrying in {retry_delay} seconds..."
192+
+ bcolors.ENDC
193+
)
194+
time.sleep(retry_delay)
195+
else:
196+
tmp_logger.error(
197+
bcolors.ERROR + "Failed to acquire lock after multiple attempts." + bcolors.ENDC
198+
)
199+
return 1
173200

174-
if node.get_namespace() != "/" and args.namespace:
175-
raise RuntimeError(
176-
f"Setting namespace through both '--namespace {args.namespace}' arg and the ROS 2 standard way "
177-
f"'--ros-args -r __ns:={node.get_namespace()}' is not allowed!"
178-
)
201+
node = Node(spawner_node_name)
179202

180-
if args.namespace:
181-
warnings.filterwarnings("always")
182-
warnings.warn(
183-
"The '--namespace' argument is deprecated and will be removed in future releases."
184-
" Use the ROS 2 standard way of setting the node namespacing using --ros-args -r __ns:=<namespace>",
185-
DeprecationWarning,
186-
)
203+
if node.get_namespace() != "/" and args.namespace:
204+
raise RuntimeError(
205+
f"Setting namespace through both '--namespace {args.namespace}' arg and the ROS 2 standard way "
206+
f"'--ros-args -r __ns:={node.get_namespace()}' is not allowed!"
207+
)
187208

188-
spawner_namespace = args.namespace if args.namespace else node.get_namespace()
209+
if args.namespace:
210+
warnings.filterwarnings("always")
211+
warnings.warn(
212+
"The '--namespace' argument is deprecated and will be removed in future releases."
213+
" Use the ROS 2 standard way of setting the node namespacing using --ros-args -r __ns:=<namespace>",
214+
DeprecationWarning,
215+
)
189216

190-
if not spawner_namespace.startswith("/"):
191-
spawner_namespace = f"/{spawner_namespace}"
217+
spawner_namespace = args.namespace if args.namespace else node.get_namespace()
192218

193-
if not controller_manager_name.startswith("/"):
194-
if spawner_namespace and spawner_namespace != "/":
195-
controller_manager_name = f"{spawner_namespace}/{controller_manager_name}"
196-
else:
197-
controller_manager_name = f"/{controller_manager_name}"
219+
if not spawner_namespace.startswith("/"):
220+
spawner_namespace = f"/{spawner_namespace}"
221+
222+
if not controller_manager_name.startswith("/"):
223+
if spawner_namespace and spawner_namespace != "/":
224+
controller_manager_name = f"{spawner_namespace}/{controller_manager_name}"
225+
else:
226+
controller_manager_name = f"/{controller_manager_name}"
198227

199-
try:
200228
for controller_name in controller_names:
201229

202230
if is_controller_loaded(
@@ -375,6 +403,7 @@ def main(args=None):
375403
return 1
376404
finally:
377405
node.destroy_node()
406+
lock.release()
378407
rclpy.shutdown()
379408

380409

controller_manager/doc/parameters_context.yaml

+9-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ hardware_components_initial_state: |
1515
- "base3"
1616
1717
diagnostics.threshold.controllers.periodicity: |
18-
The ``periodicity`` diagnostics will be published only for the asynchronous controllers, because any affect to the synchronous controllers will be reflected directly in the controller manager's periodicity.
18+
The ``periodicity`` diagnostics will be published for the asynchronous controllers, because any affect to the synchronous controllers will be reflected directly in the controller manager's periodicity. It is also published for the synchronous controllers that have a different update rate than the controller manager update rate.
1919
20-
diagnostics.threshold.controllers.execution_time: |
21-
The ``execution_time`` diagnostics will be published for all controllers. The ``mean_error`` for a synchronous controller will be computed against zero, as it should be as low as possible. However, the ``mean_error`` for an asynchronous controller will be computed against the controller's desired update period, as the controller can take a maximum of the desired period cycle to execute it's update cycle.
20+
diagnostics.threshold.controllers.execution_time.mean_error: |
21+
The ``execution_time`` diagnostics will be published for all controllers. The ``mean_error`` for a synchronous controller will be computed against zero, as it should be as low as possible. However, the ``mean_error`` for an asynchronous controller will be computed against the controller's desired update period, as the controller can take a maximum of the desired period to execute its update cycle.
22+
23+
diagnostics.threshold.hardware_components.periodicity: |
24+
The ``periodicity`` diagnostics will be published for the asynchronous hardware components, because any affect to the synchronous hardware components will be reflected directly in the controller manager's periodicity. It is also published for the synchronous hardware components that have a different read/write rate than the controller manager update rate.
25+
26+
diagnostics.threshold.hardware_components.execution_time.mean_error: |
27+
The ``execution_time`` diagnostics will be published for all hardware components. The ``mean_error`` for a synchronous hardware component will be computed against zero, as it should be as low as possible. However, the ``mean_error`` for an asynchronous hardware component will be computed against its desired read/write period, as the hardware component can take a maximum of the desired period to execute the read/write cycle.

controller_manager/include/controller_manager/controller_manager.hpp

+17-1
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,22 @@ class ControllerManager : public rclcpp::Node
375375
*/
376376
void clear_requests();
377377

378+
/**
379+
* Perform hardware command mode change for the given list of controllers to activate and
380+
* deactivate.
381+
* \param[in] rt_controller_list list of controllers in the real-time list.
382+
* \param[in] activate_controllers_list list of controllers to activate.
383+
* \param[in] deactivate_controllers_list list of controllers to deactivate.
384+
* \param[in] rt_cycle_name name of the real-time cycle.
385+
* \note This method is meant to be used only in the real-time control loops (`read`, `update` and
386+
* `write`).
387+
*/
388+
void perform_hardware_command_mode_change(
389+
const std::vector<ControllerSpec> & rt_controller_list,
390+
const std::vector<std::string> & activate_controllers_list,
391+
const std::vector<std::string> & deactivate_controllers_list,
392+
const std::string & rt_cycle_name);
393+
378394
/**
379395
* If a controller is deactivated all following controllers (if any exist) should be switched
380396
* 'from' the chained mode.
@@ -433,7 +449,7 @@ class ControllerManager : public rclcpp::Node
433449
* \returns return_type::OK if all preceding controllers pass the checks, otherwise
434450
* return_type::ERROR.
435451
*/
436-
controller_interface::return_type check_preceeding_controllers_for_deactivate(
452+
controller_interface::return_type check_preceding_controllers_for_deactivate(
437453
const std::vector<ControllerSpec> & controllers, int strictness,
438454
const ControllersListIterator controller_it, std::string & message);
439455

controller_manager/package.xml

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<depend>std_msgs</depend>
3333
<depend>libstatistics_collector</depend>
3434
<depend>generate_parameter_library</depend>
35+
<exec_depend>python3-filelock</exec_depend>
3536

3637
<test_depend>ament_cmake_gmock</test_depend>
3738
<test_depend>ament_cmake_pytest</test_depend>

0 commit comments

Comments
 (0)