Skip to content

Commit 894a4f9

Browse files
authored
One by one activation for controller groups (#11)
* One by one activation for controller groups * reformatting * Fixed Tests * Reformatting * Removed unnecessary debug output * updated pre-commits
1 parent 0b47304 commit 894a4f9

File tree

7 files changed

+104
-18
lines changed

7 files changed

+104
-18
lines changed

.pre-commit-config.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
repos:
1616
# Standard hooks
1717
- repo: https://github.com/pre-commit/pre-commit-hooks
18-
rev: v5.0.0
18+
rev: v6.0.0
1919
hooks:
2020
- id: check-added-large-files
2121
- id: check-docstring-first
@@ -31,7 +31,7 @@ repos:
3131

3232
# CPP Checks
3333
- repo: https://github.com/pre-commit/mirrors-clang-format
34-
rev: v20.1.4
34+
rev: v21.1.6
3535
hooks:
3636
- id: clang-format
3737
name: Clang Format
@@ -49,7 +49,7 @@ repos:
4949

5050
# Python hooks
5151
- repo: https://github.com/astral-sh/ruff-pre-commit
52-
rev: v0.12.7
52+
rev: v0.14.5
5353
hooks:
5454
- id: ruff-check
5555
args: [ --fix ]
@@ -64,19 +64,19 @@ repos:
6464

6565
# Package XML
6666
- repo: https://github.com/Joschi3/package_xml_validation.git
67-
rev: v1.2.0
67+
rev: v1.2.8
6868
hooks:
6969
- id: format-package-xml
7070
name: Validate and Format package.xml
7171

7272
# Spellcheck
7373
- repo: https://github.com/crate-ci/typos
74-
rev: v1.34.0
74+
rev: v1
7575
hooks:
7676
- id: typos
7777

7878
- repo: https://github.com/python-jsonschema/check-jsonschema
79-
rev: 0.33.2
79+
rev: 0.35.0
8080
hooks:
8181
- id: check-github-workflows
8282
args: ["--verbose"]

hector_controller_spawner/include/hector_controller_spawner/hector_controller_spawner.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ class MultiSpawner final : public rclcpp::Node
7272
std::unordered_map<std::string, std::string> &current_state );
7373
bool ensureControllerState( bool desired_state,
7474
const std::unordered_map<std::string, std::string> &current_state );
75+
bool loadControllerGroup( const std::vector<std::string> &to_activate,
76+
const std::vector<std::string> &to_deactivate );
7577
bool switchControllersRequest( const std::vector<std::string> &to_activate,
7678
const std::vector<std::string> &to_deactivate );
7779

@@ -80,9 +82,13 @@ class MultiSpawner final : public rclcpp::Node
8082
std::vector<std::string> controllers_;
8183
std::unordered_map<std::string, ControllerCfg> controller_cfg_;
8284
std::vector<ControllerGroup> controller_groups_;
85+
std::map<std::string, std::vector<std::string>>
86+
chained_connections_; // controller name → controllers that depend on it
8387
double retry_delay_{ 5.0 };
88+
double start_delay_{ 0.0 };
8489
std::string estop_topic_;
8590
bool restart_after_estop_deactivation_{ false };
91+
bool load_groups_one_by_one_{ true };
8692

8793
std::atomic<bool> in_progress_{ false };
8894
std::atomic<bool> done_{ false };

hector_controller_spawner/src/hector_controller_spawner.cpp

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ void MultiSpawner::initialize()
1515
controllers_ =
1616
this->declare_parameter<std::vector<std::string>>( "controllers", std::vector<std::string>() );
1717
retry_delay_ = this->declare_parameter<double>( "retry_delay", 5.0 );
18+
start_delay_ = this->declare_parameter<double>( "start_delay", 0.0 );
1819
estop_topic_ = this->declare_parameter<std::string>( "estop_topic", "" );
1920
restart_after_estop_deactivation_ =
2021
this->declare_parameter<bool>( "restart_after_estop_deactivation", true );
22+
load_groups_one_by_one_ = this->declare_parameter<bool>( "load_groups_one_by_one", true );
2123

2224
for ( const auto &ctrl : controllers_ ) {
2325
ControllerCfg cfg;
@@ -39,6 +41,13 @@ void MultiSpawner::initialize()
3941
ss << " estop_topic: '" << estop_topic_ << "'\n";
4042
RCLCPP_DEBUG( get_logger(), "%s", ss.str().c_str() );
4143

44+
if ( start_delay_ > 0.0 ) {
45+
RCLCPP_INFO( get_logger(), "Delaying start sequence by %.1f seconds...", start_delay_ );
46+
const auto delay = std::chrono::duration_cast<std::chrono::nanoseconds>(
47+
std::chrono::duration<double>( start_delay_ ) );
48+
rclcpp::sleep_for( delay );
49+
}
50+
4251
// 2) Create service clients
4352
set_hw_state_client_ = this->create_client<controller_manager_msgs::srv::SetHardwareComponentState>(
4453
"controller_manager/set_hardware_component_state" );
@@ -273,8 +282,8 @@ bool MultiSpawner::ensureControllerState(
273282
}
274283

275284
/* Issue one switch_controller call for this group. */
276-
if ( ( group_requested_active && !switchControllersRequest( group, /*deactivate*/ {} ) ) ||
277-
( !group_requested_active && !switchControllersRequest( {}, group ) ) ) {
285+
if ( ( group_requested_active && !loadControllerGroup( group, /*deactivate*/ {} ) ) ||
286+
( !group_requested_active && !loadControllerGroup( {}, group ) ) ) {
278287
RCLCPP_ERROR( get_logger(), "Failed to %s controller group containing '%s'",
279288
group_requested_active ? "activate" : "deactivate",
280289
vecToString( group ).c_str() );
@@ -289,14 +298,40 @@ bool MultiSpawner::ensureControllerState(
289298
return success;
290299
}
291300

301+
bool MultiSpawner::loadControllerGroup( const std::vector<std::string> &to_activate,
302+
const std::vector<std::string> &to_deactivate )
303+
{
304+
if ( load_groups_one_by_one_ ) {
305+
// deactivate in reverse order
306+
for ( auto it = to_deactivate.rbegin(); it != to_deactivate.rend(); ++it ) {
307+
const auto &ctrl = *it;
308+
if ( !switchControllersRequest( {}, { ctrl } ) ) {
309+
RCLCPP_ERROR( get_logger(), "Deactivated controller: %s", ctrl.c_str() );
310+
return false;
311+
}
312+
RCLCPP_DEBUG( get_logger(), "Deactivated controller: %s", ctrl.c_str() );
313+
}
314+
// activate in order (in respect to normal dependencies)
315+
for ( const auto &ctrl : to_activate ) {
316+
if ( !switchControllersRequest( { ctrl }, {} ) ) {
317+
RCLCPP_ERROR( get_logger(), "Failed to activate controller '%s'", ctrl.c_str() );
318+
return false;
319+
}
320+
RCLCPP_DEBUG( get_logger(), "Activated controller: %s", ctrl.c_str() );
321+
}
322+
return true;
323+
}
324+
return switchControllersRequest( to_activate, to_deactivate );
325+
}
326+
292327
bool MultiSpawner::switchControllersRequest( const std::vector<std::string> &to_activate,
293328
const std::vector<std::string> &to_deactivate )
294329
{
295330
if ( !switch_ctrl_client_->wait_for_service( 2s ) ) {
296331
RCLCPP_ERROR( get_logger(), "switch_controller service unavailable" );
297332
return false;
298333
}
299-
auto req = std::make_shared<controller_manager_msgs::srv::SwitchController::Request>();
334+
const auto req = std::make_shared<controller_manager_msgs::srv::SwitchController::Request>();
300335
req->activate_controllers = to_activate;
301336
req->deactivate_controllers = to_deactivate;
302337
req->strictness = controller_manager_msgs::srv::SwitchController::Request::BEST_EFFORT;
@@ -317,7 +352,9 @@ void MultiSpawner::parseControllerInfo(
317352

318353
// —— auto-detect chained controllers groups ——
319354
for ( const auto &c : resp.controller ) {
355+
chained_connections_.erase( c.name );
320356
for ( const auto &conn : c.chain_connections ) {
357+
chained_connections_[c.name].push_back( conn.name );
321358
// check if there is a ControllerGroup that includes name or conn.name
322359
bool found = false;
323360
for ( auto &group : controller_groups_ ) {
@@ -365,6 +402,30 @@ void MultiSpawner::parseControllerInfo(
365402
c.name.c_str(), c.state.c_str() );
366403
}
367404
}
405+
// add recursive chained connections
406+
for ( auto &group : controller_groups_ ) {
407+
bool changed = true;
408+
while ( changed ) {
409+
changed = false;
410+
for ( const auto &ctrl : group ) {
411+
for ( const auto &dependent_ctrl : chained_connections_[ctrl] ) {
412+
for ( const auto &dep_dep_ctr : chained_connections_[dependent_ctrl] ) {
413+
if ( std::find( chained_connections_[ctrl].begin(), chained_connections_[ctrl].end(),
414+
dep_dep_ctr ) == chained_connections_[ctrl].end() ) {
415+
chained_connections_[ctrl].push_back( dep_dep_ctr );
416+
changed = true;
417+
}
418+
}
419+
}
420+
}
421+
}
422+
// sort members in group by number of dependent controllers (descending)
423+
std::sort( group.begin(), group.end(), [this]( const std::string &a, const std::string &b ) {
424+
const size_t size_a = chained_connections_.count( a ) ? chained_connections_[a].size() : 0;
425+
const size_t size_b = chained_connections_.count( b ) ? chained_connections_[b].size() : 0;
426+
return size_a < size_b;
427+
} );
428+
}
368429
}
369430

370431
bool MultiSpawner::loadAndActivateHardware( const std::string &name )

hector_controller_spawner/test/test_spawner_estop.test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ def test_complete_estop_workflow(self):
116116
self.assertEqual(
117117
len(active_hardware),
118118
0,
119-
"No hardware interfaces should be active while e-stop is active",
119+
"No hardware interfaces should be active while e-stop is active. Active hardware: "
120+
+ ", ".join(active_hardware),
120121
)
121122

122123
# Verify no controllers are active

hector_ros_controllers/package.xml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@
33
<name>hector_ros_controllers</name>
44
<version>1.0.0</version>
55
<description>Various custom ros2 controllers</description>
6-
76
<maintainer email="[email protected]">Marek Daniv</maintainer>
8-
97
<license>Apache License 2.0</license>
10-
118
<url type="website">https://control.ros.org</url>
12-
<url type="bugtracker">https://github.com/ros-controls/ros2_controllers/issues</url>
139
<url type="repository">https://github.com/ros-controls/ros2_controllers/</url>
10+
<url type="bugtracker">https://github.com/ros-controls/ros2_controllers/issues</url>
1411

1512
<buildtool_depend>ament_cmake</buildtool_depend>
1613

hector_ros_controllers/src/collision_checker.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,6 @@ bool CollisionChecker::checkCollisionQ( const Eigen::VectorXd &q )
194194
for ( std::size_t k = 0; k < geom_model_.collisionPairs.size(); ++k ) {
195195
auto &dreq = geom_data_.distanceRequests[k];
196196
dreq.enable_nearest_points = true;
197-
dreq.gjk_initial_guess = hpp::fcl::GJKInitialGuess::CachedGuess;
198197
}
199198

200199
// Distance pass (fills distanceResults + caches)
@@ -275,6 +274,11 @@ void CollisionChecker::publishMarkers() const
275274
if ( dres.min_distance <= 0.0 ) {
276275
add_unique( cp.first );
277276
add_unique( cp.second );
277+
RCLCPP_WARN(
278+
node_->get_logger(),
279+
"Collision detected between objects %zu and %zu (distance=%.6f), names '%s' - '%s'",
280+
cp.first, cp.second, dres.min_distance, geom_model_.geometryObjects[cp.first].name.c_str(),
281+
geom_model_.geometryObjects[cp.second].name.c_str() );
278282
}
279283
}
280284

@@ -368,6 +372,18 @@ void CollisionChecker::publishMarkers() const
368372
for ( std::size_t k = 0; k < geom_model_.collisionPairs.size(); ++k ) {
369373
const auto &dres = geom_data_.distanceResults[k];
370374

375+
// check if nearest points are valid (no nans or infs)
376+
if ( dres.nearest_points[0].hasNaN() || dres.nearest_points[1].hasNaN() ||
377+
!dres.nearest_points[0].allFinite() || !dres.nearest_points[1].allFinite() ) {
378+
RCLCPP_WARN_STREAM(
379+
node_->get_logger(),
380+
"Skipping invalid nearest points for pair "
381+
<< k << " (distance=" << dres.min_distance << "names "
382+
<< geom_model_.geometryObjects[geom_model_.collisionPairs[k].first].name << " - "
383+
<< geom_model_.geometryObjects[geom_model_.collisionPairs[k].second].name << ")" );
384+
continue;
385+
}
386+
371387
// dres.nearest_points[0] and [1] should be in the base_link frame (after placements)
372388
geometry_msgs::msg::Point pA, pB;
373389
pA.x = dres.nearest_points[0][0];

hector_ros_controllers/src/safety_position_controller.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,14 @@ SafetyPositionController::update_and_write_commands( const rclcpp::Time &, const
241241
// write commands if no collision detected
242242
write_position_commands( cmd_positions_ );
243243
} else {
244-
// write current positions to hold position in case of collision
245-
RCLCPP_WARN_THROTTLE( get_node()->get_logger(), *get_node()->get_clock(),
246-
throttle_logging_msg, "Collision detected! Holding current positions." );
244+
if ( !success_cc_setup )
245+
RCLCPP_WARN_THROTTLE( get_node()->get_logger(), *get_node()->get_clock(),
246+
throttle_logging_msg, "Failed to setup collision checking." );
247+
else
248+
// write current positions to hold position in case of collision
249+
RCLCPP_WARN_THROTTLE( get_node()->get_logger(), *get_node()->get_clock(),
250+
throttle_logging_msg,
251+
"Collision detected! Holding current positions." );
247252
write_position_commands( current_positions_ );
248253
// success = false; // make sure parent controllers are unloaded
249254
}

0 commit comments

Comments
 (0)