229 void halt() override final;
231 BT::NodeStatus tick() override final;
234 bool dynamic_client_instance_ = false;
235 std::shared_ptr<ActionClientInstance> client_instance_;
236 std::shared_future<typename GoalHandle::SharedPtr> future_goal_handle_;
237 typename GoalHandle::SharedPtr goal_handle_;
238 rclcpp::Time time_goal_sent_;
239 BT::NodeStatus on_feedback_state_change_;
240 bool goal_response_received_;
242 bool result_received_;
243 bool cancel_requested_;
244 WrappedResult result_;
251template <class ActionT>
252RosActionNode<ActionT>::ActionClientInstance::ActionClientInstance(
253 rclcpp::Node::SharedPtr node, rclcpp::CallbackGroup::SharedPtr group, const std::
string & action_name)
255 action_client = rclcpp_action::create_client<ActionT>(node, action_name, group);
259template <
class ActionT>
261: RosActionNodeBase(instance_name, config, context)
265 if (
const BT::Expected<std::string> expected_name = context_.getTopicName(
this)) {
266 createClient(expected_name.value());
271 dynamic_client_instance_ = true;
275template <
class ActionT>
280template <
class ActionT>
286template <
class ActionT>
289 std::string result_str;
290 switch (result.code) {
291 case rclcpp_action::ResultCode::ABORTED:
292 result_str =
"ABORTED";
294 case rclcpp_action::ResultCode::CANCELED:
295 result_str =
"CANCELED";
297 case rclcpp_action::ResultCode::SUCCEEDED:
298 result_str =
"SUCCEEDED";
300 case rclcpp_action::ResultCode::UNKNOWN:
301 result_str =
"UNKNOWN";
305 logger_,
"%s - Goal completed. Received result %s.", context_.getFullyQualifiedTreeNodeName(
this).c_str(),
307 if (result.code == rclcpp_action::ResultCode::SUCCEEDED)
return BT::NodeStatus::SUCCESS;
308 if (cancel_requested_ && result.code == rclcpp_action::ResultCode::CANCELED)
return BT::NodeStatus::SUCCESS;
309 return BT::NodeStatus::FAILURE;
312template <
class ActionT>
315 return BT::NodeStatus::RUNNING;
318template <
class ActionT>
321 const std::string msg = context_.getFullyQualifiedTreeNodeName(
this) +
" - Unexpected error " +
322 std::to_string(error) +
": " +
toStr(error) +
".";
323 RCLCPP_ERROR_STREAM(logger_, msg);
324 throw exceptions::RosNodeError(msg);
330 rclcpp::executors::SingleThreadedExecutor::SharedPtr executor_ptr = context_.getWaitablesExecutor();
332 throw exceptions::RosNodeError(
333 context_.getFullyQualifiedTreeNodeName(
this) +
" - Cannot cancel goal for action '" + client_instance_->name +
334 "' since the pointer to the associated ROS 2 executor expired.");
337 if (future_goal_handle_.valid()) {
339 logger_,
"%s - Awaiting goal response before trying to cancel goal...",
340 context_.getFullyQualifiedTreeNodeName(
this).c_str());
342 const rclcpp::FutureReturnCode ret =
343 executor_ptr->spin_until_future_complete(future_goal_handle_, context_.getRegistrationOptions().request_timeout);
344 if (ret != rclcpp::FutureReturnCode::SUCCESS) {
348 goal_handle_ = future_goal_handle_.get();
349 future_goal_handle_ = {};
350 goal_rejected_ = goal_handle_ ==
nullptr;
354 if (goal_rejected_) {
356 logger_,
"%s - Goal was rejected. Nothing to cancel.", context_.getFullyQualifiedTreeNodeName(
this).c_str());
364 logger_,
"%s - Goal has already reached a terminal state. Nothing to cancel.",
365 context_.getFullyQualifiedTreeNodeName(
this).c_str());
369 const std::string uuid_str = rclcpp_action::to_string(goal_handle_->get_goal_id());
371 logger_,
"%s - Canceling goal %s for action '%s'.", context_.getFullyQualifiedTreeNodeName(
this).c_str(),
372 uuid_str.c_str(), client_instance_->name.c_str());
375 std::shared_future<std::shared_ptr<typename ActionClient::CancelResponse>> future_cancel_response =
376 client_instance_->action_client->async_cancel_goal(goal_handle_);
377 if (
const auto code = executor_ptr->spin_until_future_complete(
378 future_cancel_response, context_.getRegistrationOptions().request_timeout);
379 code != rclcpp::FutureReturnCode::SUCCESS) {
381 logger_,
"%s - Failed to wait for response for cancellation request (Code: %s).",
382 context_.getFullyQualifiedTreeNodeName(
this).c_str(), rclcpp::to_string(code).c_str());
385 goal_handle_ =
nullptr;
390 if (!future_cancel_response.get()) {
391 throw std::logic_error(
"Shared pointer to cancel response is nullptr.");
393 typename ActionClient::CancelResponse cancel_response = *future_cancel_response.get();
394 std::string cancel_response_str;
395 switch (cancel_response.return_code) {
396 case action_msgs::srv::CancelGoal::Response::ERROR_REJECTED:
397 cancel_response_str =
"ERROR_REJECTED";
399 case action_msgs::srv::CancelGoal::Response::ERROR_UNKNOWN_GOAL_ID:
400 cancel_response_str =
"ERROR_UNKNOWN_GOAL_ID";
402 case action_msgs::srv::CancelGoal::Response::ERROR_GOAL_TERMINATED:
403 cancel_response_str =
"ERROR_GOAL_TERMINATED";
406 cancel_response_str =
"ERROR_NONE";
409 if (cancel_response.return_code == action_msgs::srv::CancelGoal::Response::ERROR_NONE) {
412 "%s - Cancellation request of goal %s for action '%s' was accepted (Response: %s). Awaiting completion...",
413 context_.getFullyQualifiedTreeNodeName(
this).c_str(),
414 rclcpp_action::to_string(goal_handle_->get_goal_id()).c_str(), client_instance_->name.c_str(),
415 cancel_response_str.c_str());
418 std::shared_future<WrappedResult> future_goal_result =
419 client_instance_->action_client->async_get_result(goal_handle_);
420 if (
const auto code = executor_ptr->spin_until_future_complete(
421 future_goal_result, context_.getRegistrationOptions().request_timeout);
422 code == rclcpp::FutureReturnCode::SUCCESS) {
424 logger_,
"%s - Goal %s for action '%s' was cancelled successfully.",
425 context_.getFullyQualifiedTreeNodeName(
this).c_str(), uuid_str.c_str(), client_instance_->name.c_str());
428 logger_,
"%s - Failed to wait until cancellation completed (Code: %s).",
429 context_.getFullyQualifiedTreeNodeName(
this).c_str(), rclcpp::to_string(code).c_str());
434 if (cancel_response.return_code == action_msgs::srv::CancelGoal::Response::ERROR_GOAL_TERMINATED) {
436 logger_,
"%s - Goal %s for action '%s' has already terminated (Response: %s). Nothing to cancel.",
437 context_.getFullyQualifiedTreeNodeName(
this).c_str(), uuid_str.c_str(), client_instance_->name.c_str(),
438 cancel_response_str.c_str());
441 logger_,
"%s - Cancellation request was rejected (Response: %s).",
442 context_.getFullyQualifiedTreeNodeName(
this).c_str(), cancel_response_str.c_str());
447 goal_handle_ =
nullptr;
451inline void RosActionNode<T>::halt()
453 if (status() == BT::NodeStatus::RUNNING) {
454 cancel_requested_ =
true;
462inline BT::NodeStatus RosActionNode<T>::tick()
466 throw exceptions::RosNodeError(
467 context_.getFullyQualifiedTreeNodeName(
this) +
" - ROS 2 context has been shut down.");
472 if (dynamic_client_instance_ && client_instance_) {
473 dynamic_client_instance_ =
false;
478 if (status() == BT::NodeStatus::IDLE && dynamic_client_instance_) {
479 const BT::Expected<std::string> expected_name = context_.getTopicName(
this);
481 createClient(expected_name.value());
483 throw exceptions::RosNodeError(
484 context_.getFullyQualifiedTreeNodeName(
this) +
485 " - Cannot create the action client because the action name couldn't be resolved using "
486 "the expression specified by the node's registration parameters (" +
487 NodeRegistrationOptions::PARAM_NAME_ROS2TOPIC +
": " + context_.getRegistrationOptions().topic +
488 "). Error message: " + expected_name.error());
492 if (!client_instance_) {
493 throw exceptions::RosNodeError(context_.getFullyQualifiedTreeNodeName(
this) +
" - client_instance_ is nullptr.");
496 auto & action_client = client_instance_->action_client;
499 auto check_status = [
this](BT::NodeStatus status) {
500 if (!isStatusCompleted(status)) {
501 throw exceptions::RosNodeError(
502 context_.getFullyQualifiedTreeNodeName(
this) +
" - The callback must return either SUCCESS or FAILURE.");
508 if (status() == BT::NodeStatus::IDLE) {
509 setStatus(BT::NodeStatus::RUNNING);
511 goal_response_received_ =
false;
512 goal_rejected_ =
false;
513 result_received_ =
false;
514 cancel_requested_ =
false;
515 on_feedback_state_change_ = BT::NodeStatus::RUNNING;
519 if (!action_client->action_server_is_ready()) {
520 return onFailure(SERVER_UNREACHABLE);
524 if (!setGoal(goal)) {
525 return check_status(onFailure(INVALID_GOAL));
528 typename ActionClient::SendGoalOptions goal_options;
529 goal_options.goal_response_callback = [
this](
typename GoalHandle::SharedPtr goal_handle) {
531 this->goal_response_received_ =
true;
532 this->goal_rejected_ = goal_handle ==
nullptr;
533 this->goal_handle_ = goal_handle;
535 goal_options.feedback_callback =
536 [
this](
typename GoalHandle::SharedPtr ,
const std::shared_ptr<const Feedback> feedback) {
537 this->on_feedback_state_change_ = onFeedback(*feedback);
538 if (this->on_feedback_state_change_ == BT::NodeStatus::IDLE) {
539 throw std::logic_error(
540 this->context_.getFullyQualifiedTreeNodeName(
this) +
" - onFeedback() must not return IDLE.");
542 this->emitWakeUpSignal();
544 goal_options.result_callback = [
this](
const WrappedResult & result) {
548 if (this->cancel_requested_) {
551 this->onResultReceived(result);
553 this->result_received_ =
true;
554 this->goal_handle_ =
nullptr;
555 this->result_ = result;
556 this->emitWakeUpSignal();
559 future_goal_handle_ = action_client->async_send_goal(goal, goal_options);
560 time_goal_sent_ = context_.getCurrentTime();
561 return BT::NodeStatus::RUNNING;
564 if (status() == BT::NodeStatus::RUNNING) {
567 if (!goal_response_received_) {
569 if ((context_.getCurrentTime() - time_goal_sent_) > context_.getRegistrationOptions().request_timeout) {
570 return check_status(onFailure(SEND_GOAL_TIMEOUT));
572 return BT::NodeStatus::RUNNING;
573 }
else if (future_goal_handle_.valid()) {
575 future_goal_handle_ = {};
577 if (goal_rejected_)
return check_status(onFailure(GOAL_REJECTED_BY_SERVER));
579 logger_,
"%s - Goal %s accepted by server, waiting for result.",
580 context_.getFullyQualifiedTreeNodeName(
this).c_str(),
581 rclcpp_action::to_string(goal_handle_->get_goal_id()).c_str());
585 if (on_feedback_state_change_ != BT::NodeStatus::RUNNING) {
586 cancel_requested_ =
true;
588 return on_feedback_state_change_;
592 if (result_received_) {
593 return check_status(onResultReceived(result_));
596 return BT::NodeStatus::RUNNING;
599template <
class ActionT>
602 if (action_name.empty()) {
603 throw exceptions::RosNodeError(
604 context_.getFullyQualifiedTreeNodeName(
this) +
605 " - Argument action_name is empty when trying to create the client.");
610 client_instance_ && action_name == client_instance_->name &&
611 client_instance_->action_client->action_server_is_ready()) {
615 rclcpp::Node::SharedPtr node = context_.getRosNode();
616 rclcpp::CallbackGroup::SharedPtr group = context_.getWaitablesCallbackGroup();
618 throw exceptions::RosNodeError(
619 context_.getFullyQualifiedTreeNodeName(
this) +
620 " - The weak pointer to the ROS 2 callback group expired. The tree node doesn't "
621 "take ownership of it.");
626 action_name, [&] {
return std::make_shared<ActionClientInstance>(node, group, action_name); });
628 bool found = client_instance_->action_client->wait_for_action_server(context_.getRegistrationOptions().wait_timeout);
630 std::string msg = context_.getFullyQualifiedTreeNodeName(
this) +
" - Action server with name '" +
631 client_instance_->name +
"' is not reachable.";
632 if (context_.getRegistrationOptions().allow_unreachable) {
633 RCLCPP_WARN_STREAM(logger_, msg);
635 RCLCPP_ERROR_STREAM(logger_, msg);
636 throw exceptions::RosNodeError(msg);
642template <
class ActionT>
645 if (client_instance_)
return client_instance_->name;