232 const Context context_;
233 const rclcpp::Logger logger_;
235 void halt() override final;
237 BT::NodeStatus tick() override final;
240 static std::mutex & getMutex();
243 static ClientsRegistry & getRegistry();
245 bool dynamic_client_instance_ = false;
246 std::shared_ptr<ActionClientInstance> client_instance_;
247 std::
string action_client_key_;
248 std::shared_future<typename GoalHandle::SharedPtr> future_goal_handle_;
249 typename GoalHandle::SharedPtr goal_handle_;
250 rclcpp::Time time_goal_sent_;
251 BT::NodeStatus on_feedback_state_change_;
252 bool goal_response_received_;
254 bool result_received_;
255 bool cancel_requested_;
256 WrappedResult result_;
263template <class ActionT>
264RosActionNode<ActionT>::ActionClientInstance::ActionClientInstance(
265 rclcpp::Node::SharedPtr node, rclcpp::CallbackGroup::SharedPtr group, const std::
string & action_name)
267 action_client = rclcpp_action::create_client<ActionT>(node, action_name, group);
271template <
class ActionT>
273: BT::ActionNodeBase(instance_name, config),
275 logger_(context.getChildLogger(
auto_apms_util::toSnakeCase(instance_name)))
278 this->modifyPortsRemapping(context_.copyAliasedPortValuesToOriginalPorts(
this));
280 if (
const BT::Expected<std::string> expected_name = context_.getTopicName(
this)) {
286 dynamic_client_instance_ =
true;
290template <
class ActionT>
295template <
class ActionT>
301template <
class ActionT>
304 std::string result_str;
305 switch (result.code) {
306 case rclcpp_action::ResultCode::ABORTED:
307 result_str =
"ABORTED";
309 case rclcpp_action::ResultCode::CANCELED:
310 result_str =
"CANCELED";
312 case rclcpp_action::ResultCode::SUCCEEDED:
313 result_str =
"SUCCEEDED";
315 case rclcpp_action::ResultCode::UNKNOWN:
316 result_str =
"UNKNOWN";
320 logger_,
"%s - Goal completed. Received result %s.", context_.getFullyQualifiedTreeNodeName(
this).c_str(),
322 if (result.code == rclcpp_action::ResultCode::SUCCEEDED)
return BT::NodeStatus::SUCCESS;
323 if (cancel_requested_ && result.code == rclcpp_action::ResultCode::CANCELED)
return BT::NodeStatus::SUCCESS;
324 return BT::NodeStatus::FAILURE;
327template <
class ActionT>
330 return BT::NodeStatus::RUNNING;
333template <
class ActionT>
336 const std::string msg = context_.getFullyQualifiedTreeNodeName(
this) +
" - Unexpected error " +
337 std::to_string(error) +
": " +
toStr(error) +
".";
338 RCLCPP_ERROR_STREAM(logger_, msg);
339 throw exceptions::RosNodeError(msg);
345 rclcpp::executors::SingleThreadedExecutor::SharedPtr executor_ptr = context_.executor_.lock();
347 throw exceptions::RosNodeError(
348 context_.getFullyQualifiedTreeNodeName(
this) +
" - Cannot cancel goal for action '" + client_instance_->name +
349 "' since the pointer to the associated ROS 2 executor expired.");
352 if (future_goal_handle_.valid()) {
354 logger_,
"%s - Awaiting goal response before trying to cancel goal...",
355 context_.getFullyQualifiedTreeNodeName(
this).c_str());
357 const rclcpp::FutureReturnCode ret =
358 executor_ptr->spin_until_future_complete(future_goal_handle_, context_.registration_options_.request_timeout);
359 if (ret != rclcpp::FutureReturnCode::SUCCESS) {
363 goal_handle_ = future_goal_handle_.get();
364 future_goal_handle_ = {};
365 goal_rejected_ = goal_handle_ ==
nullptr;
369 if (goal_rejected_) {
371 logger_,
"%s - Goal was rejected. Nothing to cancel.", context_.getFullyQualifiedTreeNodeName(
this).c_str());
379 logger_,
"%s - Goal has already reached a terminal state. Nothing to cancel.",
380 context_.getFullyQualifiedTreeNodeName(
this).c_str());
384 const std::string uuid_str = rclcpp_action::to_string(goal_handle_->get_goal_id());
386 logger_,
"%s - Canceling goal %s for action '%s'.", context_.getFullyQualifiedTreeNodeName(
this).c_str(),
387 uuid_str.c_str(), client_instance_->name.c_str());
390 std::shared_future<std::shared_ptr<typename ActionClient::CancelResponse>> future_cancel_response =
391 client_instance_->action_client->async_cancel_goal(goal_handle_);
392 if (
const auto code = executor_ptr->spin_until_future_complete(
393 future_cancel_response, context_.registration_options_.request_timeout);
394 code != rclcpp::FutureReturnCode::SUCCESS) {
396 logger_,
"%s - Failed to wait for response for cancellation request (Code: %s).",
397 context_.getFullyQualifiedTreeNodeName(
this).c_str(), rclcpp::to_string(code).c_str());
400 goal_handle_ =
nullptr;
405 if (!future_cancel_response.get()) {
406 throw std::logic_error(
"Shared pointer to cancel response is nullptr.");
408 typename ActionClient::CancelResponse cancel_response = *future_cancel_response.get();
409 std::string cancel_response_str;
410 switch (cancel_response.return_code) {
411 case action_msgs::srv::CancelGoal::Response::ERROR_REJECTED:
412 cancel_response_str =
"ERROR_REJECTED";
414 case action_msgs::srv::CancelGoal::Response::ERROR_UNKNOWN_GOAL_ID:
415 cancel_response_str =
"ERROR_UNKNOWN_GOAL_ID";
417 case action_msgs::srv::CancelGoal::Response::ERROR_GOAL_TERMINATED:
418 cancel_response_str =
"ERROR_GOAL_TERMINATED";
421 cancel_response_str =
"ERROR_NONE";
424 if (cancel_response.return_code == action_msgs::srv::CancelGoal::Response::ERROR_NONE) {
427 "%s - Cancellation request of goal %s for action '%s' was accepted (Response: %s). Awaiting completion...",
428 context_.getFullyQualifiedTreeNodeName(
this).c_str(),
429 rclcpp_action::to_string(goal_handle_->get_goal_id()).c_str(), client_instance_->name.c_str(),
430 cancel_response_str.c_str());
433 std::shared_future<WrappedResult> future_goal_result =
434 client_instance_->action_client->async_get_result(goal_handle_);
435 if (
const auto code =
436 executor_ptr->spin_until_future_complete(future_goal_result, context_.registration_options_.request_timeout);
437 code == rclcpp::FutureReturnCode::SUCCESS) {
439 logger_,
"%s - Goal %s for action '%s' was cancelled successfully.",
440 context_.getFullyQualifiedTreeNodeName(
this).c_str(), uuid_str.c_str(), client_instance_->name.c_str());
443 logger_,
"%s - Failed to wait until cancellation completed (Code: %s).",
444 context_.getFullyQualifiedTreeNodeName(
this).c_str(), rclcpp::to_string(code).c_str());
449 if (cancel_response.return_code == action_msgs::srv::CancelGoal::Response::ERROR_GOAL_TERMINATED) {
451 logger_,
"%s - Goal %s for action '%s' has already terminated (Response: %s). Nothing to cancel.",
452 context_.getFullyQualifiedTreeNodeName(
this).c_str(), uuid_str.c_str(), client_instance_->name.c_str(),
453 cancel_response_str.c_str());
456 logger_,
"%s - Cancellation request was rejected (Response: %s).",
457 context_.getFullyQualifiedTreeNodeName(
this).c_str(), cancel_response_str.c_str());
462 goal_handle_ =
nullptr;
466inline void RosActionNode<T>::halt()
468 if (status() == BT::NodeStatus::RUNNING) {
469 cancel_requested_ =
true;
477inline BT::NodeStatus RosActionNode<T>::tick()
481 throw exceptions::RosNodeError(
487 if (dynamic_client_instance_ && client_instance_) {
488 dynamic_client_instance_ =
false;
493 if (status() == BT::NodeStatus::IDLE && dynamic_client_instance_) {
494 const BT::Expected<std::string> expected_name = context_.getTopicName(
this);
496 createClient(expected_name.value());
498 throw exceptions::RosNodeError(
500 " - Cannot create the action client because the action name couldn't be resolved using "
501 "the expression specified by the node's registration parameters (" +
502 NodeRegistrationOptions::PARAM_NAME_ROS2TOPIC +
": " + context_.registration_options_.
topic +
503 "). Error message: " + expected_name.error());
507 if (!client_instance_) {
511 auto & action_client = client_instance_->action_client;
514 auto check_status = [
this](BT::NodeStatus status) {
515 if (!isStatusCompleted(status)) {
516 throw exceptions::RosNodeError(
523 if (status() == BT::NodeStatus::IDLE) {
524 setStatus(BT::NodeStatus::RUNNING);
526 goal_response_received_ =
false;
527 goal_rejected_ =
false;
528 result_received_ =
false;
529 cancel_requested_ =
false;
530 on_feedback_state_change_ = BT::NodeStatus::RUNNING;
534 if (!action_client->action_server_is_ready()) {
535 return onFailure(SERVER_UNREACHABLE);
539 if (!setGoal(goal)) {
540 return check_status(onFailure(INVALID_GOAL));
543 typename ActionClient::SendGoalOptions goal_options;
544 goal_options.goal_response_callback = [
this](
typename GoalHandle::SharedPtr goal_handle) {
546 this->goal_response_received_ =
true;
547 this->goal_rejected_ = goal_handle ==
nullptr;
548 this->goal_handle_ = goal_handle;
550 goal_options.feedback_callback =
551 [
this](
typename GoalHandle::SharedPtr ,
const std::shared_ptr<const Feedback> feedback) {
552 this->on_feedback_state_change_ = onFeedback(*feedback);
553 if (this->on_feedback_state_change_ == BT::NodeStatus::IDLE) {
554 throw std::logic_error(
557 this->emitWakeUpSignal();
559 goal_options.result_callback = [
this](
const WrappedResult & result) {
563 if (this->cancel_requested_) {
566 this->onResultReceived(result);
568 this->result_received_ =
true;
569 this->goal_handle_ =
nullptr;
570 this->result_ = result;
571 this->emitWakeUpSignal();
574 future_goal_handle_ = action_client->async_send_goal(goal, goal_options);
576 return BT::NodeStatus::RUNNING;
579 if (status() == BT::NodeStatus::RUNNING) {
580 std::unique_lock<std::mutex> lock(getMutex());
584 if (!goal_response_received_) {
587 return check_status(onFailure(SEND_GOAL_TIMEOUT));
589 return BT::NodeStatus::RUNNING;
590 }
else if (future_goal_handle_.valid()) {
592 future_goal_handle_ = {};
594 if (goal_rejected_)
return check_status(onFailure(GOAL_REJECTED_BY_SERVER));
596 logger_,
"%s - Goal %s accepted by server, waiting for result.",
598 rclcpp_action::to_string(goal_handle_->get_goal_id()).c_str());
602 if (on_feedback_state_change_ != BT::NodeStatus::RUNNING) {
603 cancel_requested_ =
true;
605 return on_feedback_state_change_;
609 if (result_received_) {
610 return check_status(onResultReceived(result_));
613 return BT::NodeStatus::RUNNING;
616template <
class ActionT>
619 if (action_name.empty()) {
620 throw exceptions::RosNodeError(
621 context_.getFullyQualifiedTreeNodeName(
this) +
622 " - Argument action_name is empty when trying to create the client.");
627 client_instance_ && action_name == client_instance_->name &&
628 client_instance_->action_client->action_server_is_ready()) {
632 std::unique_lock lk(getMutex());
634 rclcpp::Node::SharedPtr node = context_.nh_.lock();
636 throw exceptions::RosNodeError(
637 context_.getFullyQualifiedTreeNodeName(
this) +
638 " - The weak pointer to the ROS 2 node expired. The tree node doesn't "
639 "take ownership of it.");
641 rclcpp::CallbackGroup::SharedPtr group = context_.cb_group_.lock();
643 throw exceptions::RosNodeError(
644 context_.getFullyQualifiedTreeNodeName(
this) +
645 " - The weak pointer to the ROS 2 callback group expired. The tree node doesn't "
646 "take ownership of it.");
648 action_client_key_ = std::string(node->get_fully_qualified_name()) +
"/" + action_name;
650 auto & registry = getRegistry();
651 auto it = registry.find(action_client_key_);
652 if (it == registry.end() || it->second.expired()) {
653 client_instance_ = std::make_shared<ActionClientInstance>(node, group, action_name);
654 registry.insert({action_client_key_, client_instance_});
656 logger_,
"%s - Created client for action '%s'.", context_.getFullyQualifiedTreeNodeName(
this).c_str(),
657 action_name.c_str());
659 client_instance_ = it->second.lock();
662 bool found = client_instance_->action_client->wait_for_action_server(context_.registration_options_.wait_timeout);
664 std::string msg = context_.getFullyQualifiedTreeNodeName(
this) +
" - Action server with name '" +
665 client_instance_->name +
"' is not reachable.";
666 if (context_.registration_options_.allow_unreachable) {
667 RCLCPP_WARN_STREAM(logger_, msg);
669 RCLCPP_ERROR_STREAM(logger_, msg);
670 throw exceptions::RosNodeError(msg);
676template <
class ActionT>
679 if (client_instance_)
return client_instance_->name;