| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
…essage between ticks
| return NodeStatus::RUNNING; | ||
| } | ||
|
|
||
| // TODO(schornakj): handle timeout here |
There was a problem hiding this comment.
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.
Sorry, something went wrong.
| // 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.
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.
Sorry, something went wrong.
There was a problem hiding this comment.
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.
Sorry, something went wrong.
| // 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.
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?
Sorry, something went wrong.
There was a problem hiding this comment.
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.
Sorry, something went wrong.
|
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 retryUntilSuccessful if you need to wait for the result. If you need to distinguish between a bad result and no read, you can always add an extra port in your node that describes failure reason. |
Sorry, something went wrong.
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 Trigger service server that initiates a scan and publishes the captured point cloud on a separate topic instead of returning it in the service response message. If I'm able to make the subscriber BT node block until it's received the message, I can write a sequence that triggers a scan and then grabs the published point cloud with just two BT nodes. If I instead must loop over a BT node until it succeeds while also disambiguating its reason for failure each tick then I need a much more complicated tree.
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. |
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
| { | ||
| 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.
Why not just pass a complete QoS profile?
Sorry, something went wrong.
|
I wonder if this should be a different node - a variation on topic nodes. This would be similar to how there are several variations of Sequence and Fallback with subtle but important differences in behavior. As @tony-p said, different API users may want to handle communication failure differently, with different tradeoffs. E.g. if a user naively negates such a RosTopicSubNode to negate the condition, then communication failure turns into a success. E.g. a condition throwing exceptions could bring down the tree, when the user wants to run communication failure fallbacks instead. And message staleness is a whole other can of worms. So, I will leave my user story here. In the stack I'm working on, the general philosophy is for the whole system to be up before a behavior tree is run. Each child class custom behavior:
|
Sorry, something went wrong.
|
@schornakj Did you end up finishing this up to your satisfaction or finding a different approach? This seems like something it'd be good to have. |
Sorry, something went wrong.
Sorry, something went wrong.
|
@tony-p Oh, thank you so much for the tip! In my case I'm specifically interested in having it be a StatefulActionNode so that I can guarantee a non-stale message, not so much in the QoS profile piece. |
Sorry, something went wrong.
I made this PR and then didn't get an opportunity to test it to my satisfaction on my actual system for several months. Fortunately, I'm finally in a place where I can return to this. There are a handful of edge cases I need to correct -- in particular the original implementation doesn't correctly handle publishers with TransientLocal durability.
I'm also leaning towards revising this into a variation on a node instead of an in-place modification of the existing node type. That would let this avoid breaking backwards compatibility with existing users of the topic subscriber BT node. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
There were two things I wanted to improve about the RosTopicSubNode:
Here's what I did to achieve these goals:
I've kept this as a draft for now because I have a few design questions (see comments below).