Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add payloads to Action #66

Merged
merged 3 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,13 @@ class TreeExecutionServer
rclcpp::Node::SharedPtr node();

/// @brief Name of the tree being executed
const std::string& currentTreeName() const;
const std::string& treeName() const;

/// @brief Tree being executed, nullptr if it doesn't exist, yet.
BT::Tree* currentTree();
/// @brief The payload received in the last goal
const std::string& goalPayload() const;

/// @brief Tree being executed.
const BT::Tree& tree() const;

/// @brief Pointer to the global blackboard
BT::Blackboard::Ptr globalBlackboard();
Expand Down Expand Up @@ -110,9 +113,14 @@ class TreeExecutionServer
*
* @param status The status of the tree after the last tick
* @param was_cancelled True if the action was cancelled by the Action Client
*
* @return if not std::nullopt, the string will be sent as [return_message] to the Action Client.
*/
virtual void onTreeExecutionCompleted(BT::NodeStatus status, bool was_cancelled)
{}
virtual std::optional<std::string> onTreeExecutionCompleted(BT::NodeStatus status,
bool was_cancelled)
{
return std::nullopt;
}

/**
* @brief onLoopFeedback is a callback invoked at each loop, after tree.tickOnce().
Expand Down
81 changes: 48 additions & 33 deletions behaviortree_ros2/src/tree_execution_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,16 @@ struct TreeExecutionServer::Pimpl
{
rclcpp_action::Server<ExecuteTree>::SharedPtr action_server;
std::thread action_thread;
// thread safe bool to keep track of cancel requests
std::atomic<bool> cancel_requested{ false };

std::shared_ptr<bt_server::ParamListener> param_listener;
bt_server::Params params;

BT::BehaviorTreeFactory factory;
std::shared_ptr<BT::Groot2Publisher> groot_publisher;

std::string current_tree_name;
std::shared_ptr<BT::Tree> tree;
std::string tree_name;
std::string payload;
BT::Tree tree;
BT::Blackboard::Ptr global_blackboard;
bool factory_initialized_ = false;

Expand Down Expand Up @@ -122,7 +121,6 @@ TreeExecutionServer::handle_goal(const rclcpp_action::GoalUUID& /* uuid */,
{
RCLCPP_INFO(kLogger, "Received goal request to execute Behavior Tree: %s",
goal->target_tree.c_str());
p_->cancel_requested = false;
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}

Expand All @@ -136,7 +134,6 @@ rclcpp_action::CancelResponse TreeExecutionServer::handle_cancel(
"processing one.");
return rclcpp_action::CancelResponse::REJECT;
}
p_->cancel_requested = true;
return rclcpp_action::CancelResponse::ACCEPT;
}

Expand Down Expand Up @@ -171,43 +168,50 @@ void TreeExecutionServer::execute(
// This blackboard will be owned by "MainTree". It parent is p_->global_blackboard
auto root_blackboard = BT::Blackboard::create(p_->global_blackboard);

p_->tree = std::make_shared<BT::Tree>();
*(p_->tree) = p_->factory.createTree(goal->target_tree, root_blackboard);
p_->current_tree_name = goal->target_tree;
p_->tree = p_->factory.createTree(goal->target_tree, root_blackboard);
p_->tree_name = goal->target_tree;
p_->payload = goal->payload;

// call user defined function after the tree has been created
onTreeCreated(*p_->tree);
onTreeCreated(p_->tree);
p_->groot_publisher.reset();
p_->groot_publisher =
std::make_shared<BT::Groot2Publisher>(*(p_->tree), p_->params.groot2_port);
std::make_shared<BT::Groot2Publisher>(p_->tree, p_->params.groot2_port);

// Loop until the tree is done or a cancel is requested
const auto period =
std::chrono::milliseconds(static_cast<int>(1000.0 / p_->params.tick_frequency));
auto loop_deadline = std::chrono::steady_clock::now() + period;

// operations to be done if the tree execution is aborted, either by
// cancel_requested_ or by onLoopAfterTick()
// cancel requested or by onLoopAfterTick()
auto stop_action = [this, &action_result](BT::NodeStatus status,
const std::string& message) {
p_->tree.haltTree();
action_result->node_status = ConvertNodeStatus(status);
action_result->error_message = message;
RCLCPP_WARN(kLogger, action_result->error_message.c_str());
p_->tree->haltTree();
onTreeExecutionCompleted(status, true);
// override the message value if the user defined function returns it
if(auto msg = onTreeExecutionCompleted(status, true))
{
action_result->return_message = msg.value();
}
else
{
action_result->return_message = message;
}
RCLCPP_WARN(kLogger, action_result->return_message.c_str());
};

while(rclcpp::ok() && status == BT::NodeStatus::RUNNING)
{
if(p_->cancel_requested)
if(goal_handle->is_canceling())
{
stop_action(status, "Action Server canceling and halting Behavior Tree");
goal_handle->canceled(action_result);
return;
}

// tick the tree once and publish the action feedback
status = p_->tree->tickExactlyOnce();
status = p_->tree.tickExactlyOnce();

if(const auto res = onLoopAfterTick(status); res.has_value())
{
Expand All @@ -219,56 +223,67 @@ void TreeExecutionServer::execute(
if(const auto res = onLoopFeedback(); res.has_value())
{
auto feedback = std::make_shared<ExecuteTree::Feedback>();
feedback->msg = res.value();
feedback->message = res.value();
goal_handle->publish_feedback(feedback);
}

const auto now = std::chrono::steady_clock::now();
if(now < loop_deadline)
{
p_->tree->sleep(loop_deadline - now);
p_->tree.sleep(loop_deadline - now);
}
loop_deadline += period;
}
}
catch(const std::exception& ex)
{
action_result->error_message = std::string("Behavior Tree exception:") + ex.what();
RCLCPP_ERROR(kLogger, action_result->error_message.c_str());
action_result->return_message = std::string("Behavior Tree exception:") + ex.what();
RCLCPP_ERROR(kLogger, action_result->return_message.c_str());
goal_handle->abort(action_result);
return;
}

// call user defined execution complete function
onTreeExecutionCompleted(status, false);
// Call user defined onTreeExecutionCompleted function.
// Override the message value if the user defined function returns it
if(auto msg = onTreeExecutionCompleted(status, false))
{
action_result->return_message = msg.value();
}
else
{
action_result->return_message =
std::string("Tree finished with status: ") + BT::toStr(status);
}

// set the node_status result to the action
action_result->node_status = ConvertNodeStatus(status);

// return success or aborted for the action result
if(status == BT::NodeStatus::SUCCESS)
{
RCLCPP_INFO(kLogger, "BT finished with status: %s", BT::toStr(status).c_str());
RCLCPP_INFO(kLogger, action_result->return_message.c_str());
goal_handle->succeed(action_result);
}
else
{
action_result->error_message = std::string("Behavior Tree failed during execution "
"with status: ") +
BT::toStr(status);
RCLCPP_ERROR(kLogger, action_result->error_message.c_str());
RCLCPP_ERROR(kLogger, action_result->return_message.c_str());
goal_handle->abort(action_result);
}
}

const std::string& TreeExecutionServer::currentTreeName() const
const std::string& TreeExecutionServer::treeName() const
{
return p_->tree_name;
}

const std::string& TreeExecutionServer::goalPayload() const
{
return p_->current_tree_name;
return p_->payload;
}

BT::Tree* TreeExecutionServer::currentTree()
const BT::Tree& TreeExecutionServer::tree() const
{
return p_->tree ? p_->tree.get() : nullptr;
return p_->tree;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any chance of the user accessing the tree and it being outdated or empty? It doesn't seem like there is.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically an empty Tree can be recognized.
But in general I don't believe this can happen, this is the reason why I made the change

}

BT::Blackboard::Ptr TreeExecutionServer::globalBlackboard()
Expand Down
19 changes: 14 additions & 5 deletions btcpp_ros2_interfaces/action/ExecuteTree.action
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
# Request
#### Request ####

# Name of the tree to execute
string target_tree
# Optional, implementation-dependent, payload.
string payload
---
# Result
string error_message
#### Result ####

# Status of the tree
NodeStatus node_status
# result payload or error message
string return_message
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we override the return message or add a payload for this additional information in the result? What if the tree errors/fails and there is useful information in the result_message but we also want a payload? It looks like the message is not overwritten when we encounter an exception and that is the only place I can think of where we need the additional info so maybe 1 string is enough.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have many hints about the fact that an error occurred:

  • the Action client returned a gola aborted or cancelled
  • NodeStatus will not be SUCCESS

Also, the payload may contain both human-readable and machine-readable data (not my problem 😝 )

---
# Feedback. This can be customized by the user
string msg
#### Feedback ####

#This can be customized by the user
string message
4 changes: 3 additions & 1 deletion btcpp_ros2_samples/src/sample_bt_executor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ class MyActionServer : public BT::TreeExecutionServer
logger_cout_ = std::make_shared<BT::StdCoutLogger>(tree);
}

void onTreeExecutionCompleted(BT::NodeStatus status, bool was_cancelled) override
std::optional<std::string> onTreeExecutionCompleted(BT::NodeStatus status,
MarqRazz marked this conversation as resolved.
Show resolved Hide resolved
bool was_cancelled) override
{
// NOT really needed, even if logger_cout_ may contain a dangling pointer of the tree
// at this point
logger_cout_.reset();
return std::nullopt;
}

private:
Expand Down
Loading