-
Notifications
You must be signed in to change notification settings - Fork 71
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
Make RosTopicSubNode a StatefulActionNode and handle different reliability QoS configurations #95
base: humble
Are you sure you want to change the base?
Make RosTopicSubNode a StatefulActionNode and handle different reliability QoS configurations #95
Conversation
…essage between ticks
return NodeStatus::RUNNING; | ||
} | ||
|
||
// TODO(schornakj): handle timeout here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it useful to allow timing out while waiting for a message to be received? We could get it from RosNodeParams::server_timeout
like we do in the action and service client nodes. Alternatively, it's possible to use behavior tree logic to sleep for some duration in parallel and trigger a failure if the sleep finishes before a message is received, so even without a timeout there's a way to break out of deadlock if needed.
// If no message was received, return RUNNING | ||
if(last_msg_ == nullptr) | ||
{ | ||
return NodeStatus::RUNNING; | ||
} | ||
|
||
auto status = checkStatus(onTick(last_msg_)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be simpler if the user's implementation of onTick
received the message by const reference instead of as a pointer. It would make it pretty unambiguous that if onTick
is called then you've received a message, which is a bit friendlier than the current code where you need to check if the pointer actually points to anything before potentially handling the message. I didn't add this here yet because it's a breaking change that would impact all existing code derived from RosTopicSubNode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would prevent the use case where you just want to fail if there is no message, as const ref requires the message to be there.
// GIVEN we create publisher with Reliable reliability QoS settings after creating the BT node and after the node starts ticking | ||
createPublisher(rclcpp::QoS(kHistoryDepth).reliable()); | ||
|
||
// TODO(Joe): Why does the node need to be ticked in between the publisher appearing and sending a message for the message to be received? Seems highly suspicious! | ||
ASSERT_THAT(bt_node.executeTick(), testing::Eq(NodeStatus::RUNNING)); | ||
|
||
// GIVEN the publisher has published a message | ||
publisher_->publish(std_msgs::build<Empty>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect to be able to create a publisher and then immediately publish through it without needing to tick the BT node in between. This is an edge case but I think there are situations where it could result in missing messages, such as when manually publishing messages from the command line using ros2 topic pub
. Does anyone know what could be going on here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the ros executor (which actually processes the receive message) is only spun during the tick action, and when publishing from the command line, as soon as the command is finished the publisher is broken off and no longer available. As ROS2 is peer to peer, if the publisher is gone, it can't send the message through anymore. You can work around this with commandline publishing with the keep alive flag where you can say how long you want the publisher to stay active after the message is published.
Handling different QoS configurations is something we've also hit up against. However I don't really understand the need to wait for a message. Personally I feel like the behaviour tree pattern for handling this is to add a decorator before the node |
I've found that this is a very common situation when handling robot state messages or sensor data in a behavior tree. In those cases it's important to make sure that the message was created only after entering a specific part of a tree, and it's very convenient to know that if a specific node succeeds then the rest of the tree has access to current sensor data. To provide a specific example that I've run into recently: the driver node for the Zivid 3D camera advertises a
I think one of the guiding themes of the BehaviorTree project is to provide C++ libraries that enable creating concise and readable behavior trees -- the Switch nodes are a great example of this. While it's certainly possible to handle this sort of thing using controls, decorators, and scripts, that approach makes the end user responsible for a lot more behavior tree complexity and duplicated C++ code than if the BT node itself provided a solution. |
Yes it makes the tree a bit more verbose, but it keeps the usage pattern more consistent. There are always different opinions and usage patterns for things. Personally I am a big fan of RISC architectures, IE keep basic building blocks simple, reusable, and consistent even at the cost of verbosity further down. This can always be hidden and reused in a sub tree for these types of use cases. |
@@ -51,7 +53,9 @@ class RosTopicSubNode : public BT::ConditionNode | |||
protected: | |||
struct SubscriberInstance | |||
{ | |||
SubscriberInstance(std::shared_ptr<rclcpp::Node> node, const std::string& topic_name); | |||
SubscriberInstance(std::shared_ptr<rclcpp::Node> node, const std::string& topic_name, | |||
const std::size_t history_depth, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just pass a complete QoS profile?
There were two things I wanted to improve about the RosTopicSubNode:
onTick
must strictly either returnSUCCESS
orFAILURE
doesn't match up well with the process of waiting for a message to be received. Practically there are actually three states: the message has not yet been received and we should continueRUNNING
while we wait for it, or the message has been received and the user's code determines if the contents of the message meanSUCCESS
orFAILURE
.Here's what I did to achieve these goals:
onTick
can now returnRUNNING
, which allows explicitly waiting for a new message to be received. This allows the behavior tree to be simpler if the goal is to get a new message before continuing.RosNodeParams
and requiring the user to set the QoS themselves. This is a partial fix for Allow override of QoS for RosTopicSubNode and RosTopicPubNode #14.I've kept this as a draft for now because I have a few design questions (see comments below).