@@ -56,12 +56,20 @@ class BtActionNode : public BT::ActionNodeBase
5656 if (!config ().blackboard ->get (" node" , node_)) {
5757 RCLCPP_ERROR (node_->get_logger (), " Failed to get 'node' from the blackboard" );
5858 }
59+ callback_group_ = node_->create_callback_group (
60+ rclcpp::CallbackGroupType::MutuallyExclusive, false );
61+ callback_group_executor_.add_callback_group (callback_group_, node_->get_node_base_interface ());
5962
6063 // Get the required items from the blackboard
64+ auto bt_loop_duration =
65+ config ().blackboard ->template get <std::chrono::milliseconds>(" bt_loop_duration" );
6166 getInputOrBlackboard (" server_timeout" , server_timeout_);
6267 wait_for_service_timeout_ =
6368 config ().blackboard ->get <std::chrono::milliseconds>(" wait_for_service_timeout" );
6469
70+ // Timeout should be less than bt_loop_duration to be able to finish the current tick
71+ max_timeout_ = std::chrono::duration_cast<std::chrono::milliseconds>(bt_loop_duration * 0.5 );
72+
6573 // Initialize the input and output messages
6674 goal_ = typename ActionT::Goal ();
6775 result_ = typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult ();
@@ -88,7 +96,7 @@ class BtActionNode : public BT::ActionNodeBase
8896 bool createActionClient (const std::string & action_name)
8997 {
9098 // Now that we have the ROS node to use, create the action client for this BT action
91- action_client_ = rclcpp_action::create_client<ActionT>(node_, action_name);
99+ action_client_ = rclcpp_action::create_client<ActionT>(node_, action_name, callback_group_ );
92100
93101 // Make sure the server is actually there before continuing
94102 RCLCPP_INFO (node_->get_logger (), " Waiting for \" %s\" action server" , action_name.c_str ());
@@ -116,10 +124,10 @@ class BtActionNode : public BT::ActionNodeBase
116124 {
117125 BT::PortsList basic = {
118126 BT::InputPort<std::string>(" server_name" , " Action server name" ),
119- BT::InputPort<double >(
127+ BT::InputPort<std::chrono::milliseconds >(
120128 " server_timeout" ,
121- 5.0 ,
122- " The amount of time to wait for a response from the action server, in seconds " )
129+ 5000 ,
130+ " The amount of time to wait for a response from the action server, in milliseconds " )
123131 };
124132 // The user defined ports are added to the basic ports
125133 basic.insert (addition.begin (), addition.end ());
@@ -221,14 +229,15 @@ class BtActionNode : public BT::ActionNodeBase
221229 case GOAL_SENT:
222230 {
223231 RCLCPP_DEBUG (node_->get_logger (), " %s GOAL_SENT" , node_->get_name ());
224- if (future_goal_handle_. valid () ) {
225- goal_handle_ = future_goal_handle_. get ();
226-
227- if (!goal_handle_ ) {
232+ if (future_goal_handle_) {
233+ auto elapsed =
234+ (node_-> now () - goal_sent_ts_). template to_chrono <std::chrono::milliseconds>();
235+ if (!is_future_goal_handle_complete (elapsed) ) {
228236 RCLCPP_ERROR (
229237 node_->get_logger (),
230238 " Goal was rejected by action server %s" , action_name_.c_str ());
231239 state_ = GOAL_FAILURE;
240+ future_goal_handle_.reset ();
232241 return BT::NodeStatus::FAILURE;
233242 } else {
234243 state_ = GOAL_EXECUTING;
@@ -240,6 +249,7 @@ class BtActionNode : public BT::ActionNodeBase
240249 node_->get_logger (),
241250 " Failed to send goal to action server %s" , action_name_.c_str ());
242251 state_ = GOAL_FAILURE;
252+ future_goal_handle_.reset ();
243253 return BT::NodeStatus::FAILURE;
244254 } else {
245255 return BT::NodeStatus::RUNNING;
@@ -267,6 +277,8 @@ class BtActionNode : public BT::ActionNodeBase
267277 state_ = GOAL_SENT;
268278 }
269279
280+ callback_group_executor_.spin_some ();
281+
270282 if (goal_result_available_) {
271283 state_ = GOAL_FINISHING;
272284 }
@@ -308,6 +320,7 @@ class BtActionNode : public BT::ActionNodeBase
308320 node_->get_logger (),
309321 " Failed to cancel action server for %s" , action_name_.c_str ());
310322 state_ = GOAL_FAILURE;
323+ future_goal_handle_.reset ();
311324 return BT::NodeStatus::FAILURE;
312325 }
313326 }
@@ -316,13 +329,15 @@ class BtActionNode : public BT::ActionNodeBase
316329 case GOAL_FINISHED:
317330 {
318331 RCLCPP_DEBUG (node_->get_logger (), " %s GOAL_FINISHED" , node_->get_name ());
332+ state_ = IDLE;
319333 return BT::NodeStatus::SUCCESS;
320334 }
321335 break ;
322336
323337 case GOAL_FAILURE:
324338 {
325339 RCLCPP_DEBUG (node_->get_logger (), " %s GOAL_FAILURE" , node_->get_name ());
340+ state_ = IDLE;
326341 return BT::NodeStatus::FAILURE;
327342 }
328343 break ;
@@ -331,6 +346,7 @@ class BtActionNode : public BT::ActionNodeBase
331346 break ;
332347 }
333348
349+ goal_handle_.reset ();
334350 return BT::NodeStatus::RUNNING;
335351 }
336352
@@ -355,8 +371,15 @@ class BtActionNode : public BT::ActionNodeBase
355371 */
356372 void cancel_goal ()
357373 {
358- if (goal_handle_ == nullptr ) {
374+ if (! goal_handle_) {
359375 future_cancer_handle_ = action_client_->async_cancel_goal (goal_handle_);
376+ if (callback_group_executor_.spin_until_future_complete (future_cancer_handle_,
377+ server_timeout_) != rclcpp::FutureReturnCode::SUCCESS)
378+ {
379+ RCLCPP_ERROR (
380+ node_->get_logger (), " Failed to cancel action server for %s" , action_name_.c_str ());
381+ }
382+
360383 } else {
361384 if (!createActionClient (action_name_)) {
362385 RCLCPP_ERROR (node_->get_logger (), " Failed to create action client" );
@@ -380,6 +403,7 @@ class BtActionNode : public BT::ActionNodeBase
380403 return true ;
381404 }
382405
406+ callback_group_executor_.spin_some ();
383407 auto status = goal_handle_->get_status ();
384408
385409 // Check if the goal is still executing
@@ -411,14 +435,52 @@ class BtActionNode : public BT::ActionNodeBase
411435 };
412436
413437 RCLCPP_INFO (
414- node_->get_logger (),
415- " Sending goal to action server %s" ,
416- action_name_.c_str ());
438+ node_->get_logger (), " Sending goal to action server %s" , action_name_.c_str ());
417439
418- future_goal_handle_ = action_client_->async_send_goal (goal_, send_goal_options);
440+ future_goal_handle_ = std::make_shared<
441+ std::shared_future<typename rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr>>(
442+ action_client_->async_send_goal (goal_, send_goal_options));
419443 goal_sent_ts_ = node_->now ();
420444 }
421445
446+ /* *
447+ * @brief Function to check if the action server acknowledged a new goal
448+ * @param elapsed Duration since the last goal was sent and future goal handle has not completed.
449+ * After waiting for the future to complete, this value is incremented with the timeout value.
450+ * @return boolean True if future_goal_handle_ returns SUCCESS, False otherwise
451+ */
452+ bool is_future_goal_handle_complete (std::chrono::milliseconds & elapsed)
453+ {
454+ auto remaining = server_timeout_ - elapsed;
455+
456+ // server has already timed out, no need to sleep
457+ if (remaining <= std::chrono::milliseconds (0 )) {
458+ future_goal_handle_.reset ();
459+ return false ;
460+ }
461+
462+ auto timeout = remaining > max_timeout_ ? max_timeout_ : remaining;
463+ auto result =
464+ callback_group_executor_.spin_until_future_complete (*future_goal_handle_, timeout);
465+ elapsed += timeout;
466+
467+ if (result == rclcpp::FutureReturnCode::INTERRUPTED) {
468+ future_goal_handle_.reset ();
469+ throw std::runtime_error (" send_goal failed" );
470+ }
471+
472+ if (result == rclcpp::FutureReturnCode::SUCCESS) {
473+ goal_handle_ = future_goal_handle_->get ();
474+ future_goal_handle_.reset ();
475+ if (!goal_handle_) {
476+ throw std::runtime_error (" Goal was rejected by the action server" );
477+ }
478+ return true ;
479+ }
480+
481+ return false ;
482+ }
483+
422484 /* *
423485 * @brief Function to increment recovery count on blackboard if this node wraps a recovery
424486 */
@@ -437,7 +499,7 @@ class BtActionNode : public BT::ActionNodeBase
437499 typename ActionT::Goal goal_;
438500 bool goal_updated_{false };
439501 bool goal_result_available_{false };
440- std::shared_future<typename rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr>
502+ std::shared_ptr<std:: shared_future<typename rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr> >
441503 future_goal_handle_;
442504 std::shared_future<typename ActionT::Impl::CancelGoalService::Response::SharedPtr>
443505 future_cancer_handle_;
@@ -447,11 +509,16 @@ class BtActionNode : public BT::ActionNodeBase
447509
448510 // The node that will be used for any ROS operations
449511 rclcpp_lifecycle::LifecycleNode::SharedPtr node_;
512+ rclcpp::CallbackGroup::SharedPtr callback_group_;
513+ rclcpp::executors::SingleThreadedExecutor callback_group_executor_;
450514
451515 // The timeout value while waiting for response from a server when a
452516 // new action goal is sent or canceled
453517 std::chrono::milliseconds server_timeout_;
454518
519+ // The timeout value for BT loop execution
520+ std::chrono::milliseconds max_timeout_;
521+
455522 // The timeout value for waiting for a service to response
456523 std::chrono::milliseconds wait_for_service_timeout_;
457524
0 commit comments