@@ -169,17 +169,46 @@ class RosActionNode : public BT::ActionNodeBase
169
169
NodeStatus tick () override final ;
170
170
171
171
protected:
172
+ struct ActionClientInstance
173
+ {
174
+ ActionClientInstance (std::shared_ptr<rclcpp::Node> node,
175
+ const std::string& action_name);
176
+
177
+ ActionClientPtr action_client;
178
+ rclcpp::CallbackGroup::SharedPtr callback_group;
179
+ rclcpp::executors::SingleThreadedExecutor callback_executor;
180
+ typename ActionClient::SendGoalOptions goal_options;
181
+ };
182
+
183
+ static std::mutex& getMutex ()
184
+ {
185
+ static std::mutex action_client_mutex;
186
+ return action_client_mutex;
187
+ }
188
+
189
+ rclcpp::Logger logger ()
190
+ {
191
+ return node_->get_logger ();
192
+ }
193
+
194
+ using ClientsRegistry =
195
+ std::unordered_map<std::string, std::weak_ptr<ActionClientInstance>>;
196
+ // contains the fully-qualified name of the node and the name of the client
197
+ static ClientsRegistry& getRegistry ()
198
+ {
199
+ static ClientsRegistry action_clients_registry;
200
+ return action_clients_registry;
201
+ }
202
+
172
203
std::shared_ptr<rclcpp::Node> node_;
173
- std::string prev_action_name_;
204
+ std::shared_ptr<ActionClientInstance> client_instance_;
205
+ std::string action_name_;
174
206
bool action_name_may_change_ = false ;
175
207
const std::chrono::milliseconds server_timeout_;
176
208
const std::chrono::milliseconds wait_for_server_timeout_;
209
+ std::string action_client_key_;
177
210
178
211
private:
179
- ActionClientPtr action_client_;
180
- rclcpp::CallbackGroup::SharedPtr callback_group_;
181
- rclcpp::executors::SingleThreadedExecutor callback_group_executor_;
182
-
183
212
std::shared_future<typename GoalHandle::SharedPtr> future_goal_handle_;
184
213
typename GoalHandle::SharedPtr goal_handle_;
185
214
@@ -195,6 +224,16 @@ class RosActionNode : public BT::ActionNodeBase
195
224
// ---------------------- DEFINITIONS -----------------------------
196
225
// ----------------------------------------------------------------
197
226
227
+ template <class T >
228
+ RosActionNode<T>::ActionClientInstance::ActionClientInstance(
229
+ std::shared_ptr<rclcpp::Node> node, const std::string& action_name)
230
+ {
231
+ callback_group =
232
+ node->create_callback_group (rclcpp::CallbackGroupType::MutuallyExclusive);
233
+ callback_executor.add_callback_group (callback_group, node->get_node_base_interface ());
234
+ action_client = rclcpp_action::create_client<T>(node, action_name, callback_group);
235
+ }
236
+
198
237
template <class T >
199
238
inline RosActionNode<T>::RosActionNode(const std::string& instance_name,
200
239
const NodeConfig& conf,
@@ -262,46 +301,62 @@ inline bool RosActionNode<T>::createClient(const std::string& action_name)
262
301
throw RuntimeError (" action_name is empty" );
263
302
}
264
303
265
- callback_group_ =
266
- node_->create_callback_group (rclcpp::CallbackGroupType::MutuallyExclusive);
267
- callback_group_executor_.add_callback_group (callback_group_,
268
- node_->get_node_base_interface ());
269
- action_client_ = rclcpp_action::create_client<T>(node_, action_name, callback_group_);
304
+ std::unique_lock lk (getMutex ());
305
+ action_client_key_ = std::string (node_->get_fully_qualified_name ()) + " /" + action_name;
270
306
271
- prev_action_name_ = action_name;
307
+ auto & registry = getRegistry ();
308
+ auto it = registry.find (action_client_key_);
309
+ if (it == registry.end ())
310
+ {
311
+ client_instance_ = std::make_shared<ActionClientInstance>(node_, action_name);
312
+ registry.insert ({ action_client_key_, client_instance_ });
313
+ }
314
+ else
315
+ {
316
+ client_instance_ = it->second .lock ();
317
+ }
272
318
273
- bool found = action_client_->wait_for_action_server (wait_for_server_timeout_);
319
+ action_name_ = action_name;
320
+
321
+ bool found =
322
+ client_instance_->action_client ->wait_for_action_server (wait_for_server_timeout_);
274
323
if (!found)
275
324
{
276
- RCLCPP_ERROR (node_->get_logger (),
277
- " %s: Action server with name '%s' is not reachable." , name ().c_str (),
278
- prev_action_name_.c_str ());
325
+ RCLCPP_ERROR (logger (), " %s: Action server with name '%s' is not reachable." ,
326
+ name ().c_str (), action_name_.c_str ());
279
327
}
280
328
return found;
281
329
}
282
330
283
331
template <class T >
284
332
inline NodeStatus RosActionNode<T>::tick()
285
333
{
334
+ if (!rclcpp::ok ())
335
+ {
336
+ halt ();
337
+ return NodeStatus::FAILURE;
338
+ }
339
+
286
340
// First, check if the action_client_ is valid and that the name of the
287
341
// action_name in the port didn't change.
288
342
// otherwise, create a new client
289
- if (!action_client_ || (status () == NodeStatus::IDLE && action_name_may_change_))
343
+ if (!client_instance_ || (status () == NodeStatus::IDLE && action_name_may_change_))
290
344
{
291
345
std::string action_name;
292
346
getInput (" action_name" , action_name);
293
- if (prev_action_name_ != action_name)
347
+ if (action_name_ != action_name)
294
348
{
295
349
createClient (action_name);
296
350
}
297
351
}
352
+ auto & action_client = client_instance_->action_client ;
298
353
299
354
// ------------------------------------------
300
355
auto CheckStatus = [](NodeStatus status) {
301
356
if (!isStatusCompleted (status))
302
357
{
303
- throw std::logic_error (" RosActionNode: the callback must return either SUCCESS of "
304
- " FAILURE" );
358
+ throw LogicError (" RosActionNode: the callback must return either SUCCESS nor "
359
+ " FAILURE" );
305
360
}
306
361
return status;
307
362
};
@@ -340,7 +395,7 @@ inline NodeStatus RosActionNode<T>::tick()
340
395
goal_options.result_callback = [this ](const WrappedResult& result) {
341
396
if (goal_handle_->get_goal_id () == result.goal_id )
342
397
{
343
- RCLCPP_DEBUG (node_-> get_logger (), " result_callback" );
398
+ RCLCPP_DEBUG (logger (), " result_callback" );
344
399
result_ = result;
345
400
emitWakeUpSignal ();
346
401
}
@@ -351,29 +406,30 @@ inline NodeStatus RosActionNode<T>::tick()
351
406
auto goal_handle_ = future_handle.get ();
352
407
if (!goal_handle_)
353
408
{
354
- RCLCPP_ERROR (node_-> get_logger (), " Goal was rejected by server" );
409
+ RCLCPP_ERROR (logger (), " Goal was rejected by server" );
355
410
}
356
411
else
357
412
{
358
- RCLCPP_DEBUG (node_->get_logger (), " Goal accepted by server, waiting for "
359
- " result" );
413
+ RCLCPP_DEBUG (logger (), " Goal accepted by server, waiting for result" );
360
414
}
361
415
};
362
416
// --------------------
363
-
364
417
// Check if server is ready
365
- if (!action_client_->action_server_is_ready ())
418
+ if (!action_client->action_server_is_ready ())
419
+ {
366
420
return onFailure (SERVER_UNREACHABLE);
421
+ }
367
422
368
- future_goal_handle_ = action_client_ ->async_send_goal (goal, goal_options);
423
+ future_goal_handle_ = action_client ->async_send_goal (goal, goal_options);
369
424
time_goal_sent_ = node_->now ();
370
425
371
426
return NodeStatus::RUNNING;
372
427
}
373
428
374
429
if (status () == NodeStatus::RUNNING)
375
430
{
376
- callback_group_executor_.spin_some ();
431
+ std::unique_lock<std::mutex> lock (getMutex ());
432
+ client_instance_->callback_executor .spin_some ();
377
433
378
434
// FIRST case: check if the goal request has a timeout
379
435
if (!goal_received_)
@@ -382,8 +438,8 @@ inline NodeStatus RosActionNode<T>::tick()
382
438
auto timeout =
383
439
rclcpp::Duration::from_seconds (double (server_timeout_.count ()) / 1000 );
384
440
385
- auto ret = callback_group_executor_ .spin_until_future_complete (future_goal_handle_,
386
- nodelay);
441
+ auto ret = client_instance_-> callback_executor .spin_until_future_complete (
442
+ future_goal_handle_, nodelay);
387
443
if (ret != rclcpp::FutureReturnCode::SUCCESS)
388
444
{
389
445
if ((node_->now () - time_goal_sent_) > timeout)
@@ -449,25 +505,28 @@ inline void RosActionNode<T>::cancelGoal()
449
505
{
450
506
if (!goal_handle_)
451
507
{
452
- RCLCPP_WARN (node_-> get_logger (), " cancelGoal called on an empty goal_handle" );
508
+ RCLCPP_WARN (logger (), " cancelGoal called on an empty goal_handle" );
453
509
return ;
454
510
}
455
511
456
- auto future_result = action_client_->async_get_result (goal_handle_);
457
- auto future_cancel = action_client_->async_cancel_goal (goal_handle_);
512
+ auto & executor = client_instance_->callback_executor ;
513
+ auto & action_client = client_instance_->action_client ;
514
+
515
+ auto future_result = action_client->async_get_result (goal_handle_);
516
+ auto future_cancel = action_client->async_cancel_goal (goal_handle_);
517
+
518
+ constexpr auto SUCCESS = rclcpp::FutureReturnCode::SUCCESS;
458
519
459
- if (callback_group_executor_.spin_until_future_complete (
460
- future_cancel, server_timeout_) != rclcpp::FutureReturnCode::SUCCESS)
520
+ if (executor.spin_until_future_complete (future_cancel, server_timeout_) != SUCCESS)
461
521
{
462
- RCLCPP_ERROR (node_-> get_logger (), " Failed to cancel action server for [%s]" ,
463
- prev_action_name_ .c_str ());
522
+ RCLCPP_ERROR (logger (), " Failed to cancel action server for [%s]" ,
523
+ action_name_ .c_str ());
464
524
}
465
525
466
- if (callback_group_executor_.spin_until_future_complete (
467
- future_result, server_timeout_) != rclcpp::FutureReturnCode::SUCCESS)
526
+ if (executor.spin_until_future_complete (future_result, server_timeout_) != SUCCESS)
468
527
{
469
- RCLCPP_ERROR (node_-> get_logger (), " Failed to get result call failed :( for [%s]" ,
470
- prev_action_name_ .c_str ());
528
+ RCLCPP_ERROR (logger (), " Failed to get result call failed :( for [%s]" ,
529
+ action_name_ .c_str ());
471
530
}
472
531
}
473
532
0 commit comments