behaviortree / behaviortree.ros2 Goto Github PK
View Code? Open in Web Editor NEWBehaviorTree.CPP utilities to work with ROS2
License: Apache License 2.0
BehaviorTree.CPP utilities to work with ROS2
License: Apache License 2.0
Currently, the bt_action_node throws an exception when the action server rejects the goal, see here.
I encountered some use cases where a variable is generated from a node, written through an output to the blackboard, passed as an input to an action node and then used in the goal request. This runtime generation of the goal request can lead to situations where the action server rejects it. A goal rejection could however be resolved in the behavior tree through a fallback etc. if the bt_action_node would not throw but return NodeStatus::Failure instead.
Let me know if you agree so i can file a PR. thanks
Run sleep_client
node from the btcpp_ros2_samples/ package. Also, sleep_server
node must be running in parallel.
We identified three scenarios of BT diagrams for our tests, which we document below. In these scenarios, we must differentiate between
sleep_server
node.and
Tree.xml:
<root BTCPP_format="4">
<BehaviorTree>
<Sequence>
<PrintValue message="start sleepAction"/>
<SleepAction name="sleepA" msec="5000"/>
<PrintValue message="sleepAction completed"/>
<PrintValue message="start sleepNormal"/>
<Sleep msec="6000"/>
<PrintValue message="sleepNormal completed"/>
</Sequence>
</BehaviorTree>
</root>
Expected Behavior: While the tree executes the sleepAction node, the CPU should spike a little, as communication between nodes (action client and server nodes) will be happening, but afterwards when the sleepAction node succeeds, the sleepNormal node should execute and the CPU usage should be back to low values, as there is no ROS communication happening.
Actual Behavior: When the tree is executing the sleepAction node, the CPU consumption rises up to 44%. When the sleepNormal node is being executed, the CPU consumption reaches 101%, and remains high until the sleepNormal succeeds causing the tree to succeed and the client.cpp
script to exit.
Tree.xml:
<root BTCPP_format="4">
<BehaviorTree>
<Sequence>
<PrintValue message="start sleepAction"/>
<SleepAction name="sleepA" msec="5000"/>
<PrintValue message="sleepAction completed"/>
</Sequence>
</BehaviorTree>
</root>
Expected Behavior: While the tree executes the sleepAction node, the CPU should spike a little, as communication between nodes (action client and server nodes) will be happening, but afterwards when the sleepAction node succeeds, the sleepNormal node should execute and the CPU usage should be back to low values.
Actual Behavior: When the tree is executing the sleepAction node, the CPU consumption rises up to 39% until the node succeeds causing the tree to succeed and the client.cpp
script to exit.
Tree.xml:
<root BTCPP_format="4">
<BehaviorTree>
<Sequence>
<PrintValue message="start sleepNormal"/>
<Sleep msec="6000"/>
<PrintValue message="sleepNormal completed"/>
</Sequence>
</BehaviorTree>
</root>
Expected Behavior: While the tree executes the sleepNormal node, the CPU should remain small, as there is no communication through ROS.
Actual Behavior: When the sleepNormal node is being executed, the CPU consumption reaches 5%.
Scenario 1 reveals that there is an issue with BT Nodes acting as ROS Action Clients, especially when we note that we tested the same tree, but replaced the sleepAction with an addTwoIntsService BT Node acting as a ROS Service Client (not featured here or in the original source code of BT.ROS2) , and the CPU usage was very low during the execution of the subsequent sleepNormal node.
Scenario 3 compared to Scenario 1 shows what the baseline CPU usage should be while a sleepNormal BT node is executed, namely it should be around 5% as in Scenario 3, but instead in Scenario 1, following (not before) the execution of a sleepAction node (or any ROS Action based BT Node for that matter), the CPU hits 100%, and stays there, until another ROS Action BT Node is executed, at which point CPU consumption drops to around 40%, before shooting back up to 100% when not executing a ROS Action BT Node.
Below I have attached my cpp file for which I wish to write a unit test, specifically for the onTick method. This node is supposed to run at the top of the tree inside a reactive fallback to control the current state of the tree eg, localized, e stop, hw failure, sw failure etc. Since it is an essential node in the tree, I wanted to write a unit test for it. The code is from the sample subscriber wrapper provided in the package
`
#include "my_behavior_tree/ok_status.hpp"
using namespace BT;
OKStatus::OKStatus(const std::string& name,
const NodeConfig& conf,
const RosNodeParams& params)
: RosTopicSubNode<std_msgs::msg::String>(name, conf, params)
{
}
BT::PortsList OKStatus::providedPorts()
{
return{InputPort<std::string>("topic_name")};
}
// Check if status is okay or not, if not return failure. Used to control state of the tree
NodeStatus OKStatus::onTick(const std::shared_ptr<std_msgs::msg::String>& last_msg)
{ // TODO - Add multiple state checks
if (last_msg)
{
status = last_msg->data;
if(status =="OK")
{
// RCLCPP_INFO(logger(), "Not in error");
c = 0;
return NodeStatus::SUCCESS;
}
else
{
c =1;
// RCLCPP_INFO(logger(), "In error");
return NodeStatus::FAILURE;
}
}
else
{
if(c ==0)
{
return NodeStatus::SUCCESS;
}
else
{
return NodeStatus::FAILURE;
}
}
}
`
This is the corresponding header file
`
#include "behaviortree_ros2/bt_topic_sub_node.hpp"
#include <std_msgs/msg/string.hpp>
using namespace BT;
class OKStatus: public RosTopicSubNode<std_msgs::msg::String>
{
public:
OKStatus(const std::string& name,
const NodeConfig& conf,
const RosNodeParams& params);
static BT::PortsList providedPorts();
NodeStatus onTick(const std::shared_ptr<std_msgs::msg::String>& last_msg) override;
std::string status;
int c;
};
`
This is the Test method I have written
`
#include <gtest/gtest.h>
#include <ament_index_cpp/get_package_share_directory.hpp>
#include <ament_index_cpp/get_package_prefix.hpp>
#include <rclcpp/rclcpp.hpp>
#include <vector>
#include <string>
#include <fstream>
#include <memory>
#include <utility>
#include <boost/filesystem.hpp>
#include <my_behavior_tree/ok_status.hpp>
using namespace BT;
std_msgs::msg::String createString(const std::string& name)
{
std_msgs::msg::String sample;
sample.data = name;
return sample;
}
class BehaviorTreeTestFixture : public ::testing::Test
{
protected:
std::shared_ptr<BehaviorTreeHandler> bt_handler;
std_msgs::msg::String a;
public:
void SetUp() override
{
bt_handler = std::make_shared<BehaviorTreeHandler>();
a = createString("OK");
}
void TearDown() override
{
bt_handler.reset();
}
};
TEST_F(BehaviorTreeTestFixture, TestSuccess)
{
rclcpp::init(0, nullptr);
auto nh = std::make_shared<rclcpp::Node>("tree_test_node");
RosNodeParams params;
params.nh = nh;
params.default_port_value = "test";
factory.registerNodeType<OKStatus>("OKStatus", params);
OKStatus ok_status("ok_status",NodeConfig(),RosNodeParams());
auto message_ptr = std::make_shared<std_msgs::msg::String>(a);
auto status = ok_status.onTick(message_ptr);
EXPECT_EQ(status, BT::NodeStatus::SUCCESS);
rclcpp::shutdown();
}
`
I am getting the following error when I am running this code
unknown file: Failure C++ exception with description "Both [topic_name] in the InputPort and the RosNodeParams are empty." thrown in the test body.
My idea is to write a unit test specifically for the onTick method only without involving any of the ROS components, but since the OKStatus class is inheriting from the RosTopicSubNode class, I believe that will not be possible without completely re writing the class and separating out the ROS dependency and onTick method.
So I wanted to check if someone has faced a similar problem or has some better suggestions to write a unit test for this method?
I have also tried to load the xml file and also add a tickOnce/tickWhileRunning method but even then I get the same error, however with those cases, the subscriber is actually created but the code eventually crashes at line 181 https://github.com/BehaviorTree/BehaviorTree.ROS2/blob/humble/behaviortree_ros2/include/behaviortree_ros2/bt_topic_sub_node.hpp
Thank you
From what I see here, the service client is non-blocking:
I think it would be nice to have a blocking version as well, since people often send a service request then wait for the response. Or does this not fit well with the BT philosophy?
Hi,
First of all, thank you for providing such a wrapper and examples on how to use it.
I was testing the sleep_client
and sleep_server
example and I am getting the following error
Error for the sleep_client
>>> [rcutils|error_handling.c:108] rcutils_set_error_state()
This error state is being overwritten:
'Service type support not from this implementation. Got:
Handle's typesupport identifier (rosidl_typesupport_cpp) is not supported by this library, at ./src/type_support_dispatch.hpp:111
Could not load library libbehaviortree_ros2__rosidl_typesupport_introspection_cpp.so: dlopen error: libbehaviortree_ros2__rosidl_typesupport_introspection_cpp.so: cannot open shared object file: No such file or directory, at ./src/shared_library.c:99, at ./src/type_support_dispatch.hpp:76
while fetching it, at ./src/rmw_node.cpp:4503'
with this new error message:
'type_support is null, at ./src/rmw_node.cpp:4573'
rcutils_reset_error() should be called after error handling to avoid this.
<<<
terminate called after throwing an instance of 'rclcpp::exceptions::RCLError'
what(): could not initialize rcl action client: type_support is null, at ./src/rmw_node.cpp:4573, at ./src/rcl/client.c:113
[1] 149016 IOT instruction (core dumped) ./install/behaviortree_ros2/bin/sleep_client
Error for the sleep_server
>>> [rcutils|error_handling.c:108] rcutils_set_error_state()
This error state is being overwritten:
'Service type support not from this implementation. Got:
Handle's typesupport identifier (rosidl_typesupport_cpp) is not supported by this library, at ./src/type_support_dispatch.hpp:111
Could not load library libbehaviortree_ros2__rosidl_typesupport_introspection_cpp.so: dlopen error: libbehaviortree_ros2__rosidl_typesupport_introspection_cpp.so: cannot open shared object file: No such file or directory, at ./src/shared_library.c:99, at ./src/type_support_dispatch.hpp:76
while fetching it, at ./src/rmw_node.cpp:4503'
with this new error message:
'type_support is null, at ./src/rmw_node.cpp:4573'
rcutils_reset_error() should be called after error handling to avoid this.
<<<
terminate called after throwing an instance of 'rclcpp::exceptions::RCLError'
what(): type_support is null, at ./src/rmw_node.cpp:4573, at ./src/rcl/service.c:124
[1] 149048 IOT instruction (core dumped) ./install/behaviortree_ros2/bin/sleep_server
I tested by building the package using ROS Humble and tested the executables by calling them directly from the build directory as ./build/behaviortree_ros2/sleep_server
Am I missing something to correctly build/use this package?
I have been trying to create some basic examples of each of the node types, all work fine apart from the subscriber.
You say to register the node using RegisterRosAction<DerivedClass>(factory, params);
However nowhere in either the Behavortree.cpp or the Behavortree.ros2 does RegisterRosAction exist. I'm assuming this comment was taken from your Ros 1 implementation.
I have been using the below in order to register my plugins which has worked for actions, services and publishers however does not work for subscribers.
RegisterRosNode(factory, "install/bt_ros_tests/lib/liblistening_plugin.so", params);
It builds, however returns a runtime error terminate called after throwing an instance of 'BT::RuntimeError' what(): Could not load library: install/bt_ros_tests/lib/liblistening_plugin.so: undefined symbol: _ZN11ListenerSub6onTickERKSt10shared_ptrIN8std_msgs3msg7String_ISaIvEEEE
Any help as to why would be much appreciated, all my code is base very closely to your examples and am happy to share part if needed
Thank you for adding the new and exciting feature, BT::TreeExecutionServer
.
I attempted to create my own class by inheriting from BT::TreeExecutionServer
, but it seems that the registerNodesIntoFactory
I inherited is not functioning properly. When attempting to load a behavior tree containing the Node added within registerNodesIntoFactory, the following error occurs:
[ERROR] [1715003079.255998664] [bt_action_server]: Failed to load BehaviorTree: tree.xml
Error at line 7: -> Node not recognized: MyNode
This issue seems to be caused by two reasons:
BehaviorTree.ROS2/behaviortree_ros2/src/tree_execution_server.cpp
Lines 73 to 95 in 8790909
BehaviorTree.ROS2/behaviortree_ros2/src/bt_utils.cpp
Lines 137 to 167 in 8790909
When I implemented the override of void registerNodesIntoFactory(BT::BehaviorTreeFactory& factory)
, I wrote it like this:
BT::RosNodeParams params;
params.nh = std::dynamic_pointer_cast<rclcpp::Node>(nodeBaseInterface());
This caused the following exceptions during tree construction:
[ERROR] [1715013001.175801122] [bt_action_server]: Behavior Tree exception:bad_weak_ptr
[ERROR] [1715013002.693655469] [bt_action_server]: Behavior Tree exception:The ROS node went out of scope. RosNodeParams doesn't take the ownership of the node.
Instead, when I changed the API to directly assign rclcpp::Node::SharedPtr to nh, it worked perfectly.
Why doesn't TreeExecutionServer
directly expose rclcpp::Node::SharedPtr
?
If there are no issues, I would like to add an API to directly exposerclcpp::Node::SharedPtr
.
As I commented after the fix for the original issue #57 , cancelGoal()
still doesn't work correctly for pre-response action cancellation. The issue now is, that goal_handle_
is not reset after a previous goal has terminated, which causes this condition to always become true after the first goal response. Therefore the function misleadingly assumes for any subsequent goals that the corresponding response has already arrived. As a result, an rclcpp_action::UnkownGoalError
is thrown in action_client->async_get_result()
if cancelGoal()
is called when the current goal has not arrived yet, since goal_handle_
refers to the previous, already terminated goal.
We either have to reset goal_handle_
appropriately, or refer to the currently relevant goal uuid if we want to verify that the response has arrived.
I also suggested an entirely different implementation of RosActionNode
here, which would prevent this issue from ocurring in the first place (Behavior A: Wait for goal response during initial tick).
Hi all,
I have a docker image with ros2 and Im trying to build behaviortree and groot together to start making some cool stuff. I was wondering if you guys have any idea why my build is failing:
The Dockerfile that does the behaviortree part of the build (using ubuntu 20.04 and ros2 foxy):
RUN mkdir -p $HOME/$EBOTS_ARMS_WORKSPACE/behaviortree_cpp && \
git clone \
https://github.com/BehaviorTree/BehaviorTree.CPP.git \
$HOME/$EBOTS_ARMS_WORKSPACE/behaviortree_cpp && \
cd $HOME/$EBOTS_ARMS_WORKSPACE/behaviortree_cpp && \
mkdir build && \
cd build && \
cmake .. && \
make && \
make install
RUN mkdir -p $HOME/$EBOTS_ARMS_WORKSPACE/behaviortree_ros && \
git clone \
https://github.com/BehaviorTree/BehaviorTree.ROS2.git \
$HOME/$EBOTS_ARMS_WORKSPACE/behaviortree_ros
# I don't think the submodules are necessary if we do the rosdep install? *unsure*
RUN mkdir -p $HOME/$EBOTS_ARMS_WORKSPACE/groot && \
git clone \
https://github.com/BehaviorTree/Groot \
$HOME/$EBOTS_ARMS_WORKSPACE/groot && \
cd $HOME/$EBOTS_ARMS_WORKSPACE/groot && \
git submodule update --init --recursive && \
mkdir build && \
cd build && \
cmake .. && \
make
# Install dependencies of all packages
RUN cd $HOME/$EBOTS_ARMS_WORKSPACE && \
rosdep install -r \
--from-paths . -y
The error:
--- stderr: behaviortree_ros2
In file included from /home/ebots/workspace/behaviortree_ros/test/test_client.cpp:1:
/home/ebots/workspace/behaviortree_ros/include/behaviortree_ros2/bt_action_node.hpp: In lambda function:
/home/ebots/workspace/behaviortree_ros/include/behaviortree_ros2/bt_action_node.hpp:217:7: error: there are no arguments to ‘emitStateChanged’ that depend on a template parameter, so a declaration of ‘emitStateChanged’ must be available [-fpermissive]
217 | emitStateChanged();
| ^~~~~~~~~~~~~~~~
/home/ebots/workspace/behaviortree_ros/include/behaviortree_ros2/bt_action_node.hpp:217:7: note: (if you use ‘-fpermissive’, G++ will accept your code, but allowing the use of an undeclared name is deprecated)
/home/ebots/workspace/behaviortree_ros/include/behaviortree_ros2/bt_action_node.hpp: In lambda function:
/home/ebots/workspace/behaviortree_ros/include/behaviortree_ros2/bt_action_node.hpp:225:7: error: there are no arguments to ‘emitStateChanged’ that depend on a template parameter, so a declaration of ‘emitStateChanged’ must be available [-fpermissive]
225 | emitStateChanged();
| ^~~~~~~~~~~~~~~~
/home/ebots/workspace/behaviortree_ros/test/test_client.cpp: In function ‘int main(int, char**)’:
/home/ebots/workspace/behaviortree_ros/test/test_client.cpp:112:10: error: ‘class BT::Tree’ has no member named ‘sleep’
112 | tree.sleep(std::chrono::milliseconds(100));
| ^~~~~
In file included from /home/ebots/workspace/behaviortree_ros/test/test_client.cpp:1:
/home/ebots/workspace/behaviortree_ros/include/behaviortree_ros2/bt_action_node.hpp: In instantiation of ‘BT::NodeStatus BT::RosActionNode<ActionT>::tick() [with ActionT = behaviortree_ros2::action::Sleep]’:
/home/ebots/workspace/behaviortree_ros/include/behaviortree_ros2/bt_action_node.hpp:177:14: required from here
/home/ebots/workspace/behaviortree_ros/include/behaviortree_ros2/bt_action_node.hpp:217:23: error: ‘emitStateChanged’ was not declared in this scope
217 | emitStateChanged();
| ~~~~~~~~~~~~~~~~^~
/home/ebots/workspace/behaviortree_ros/include/behaviortree_ros2/bt_action_node.hpp:225:23: error: ‘emitStateChanged’ was not declared in this scope
225 | emitStateChanged();
| ~~~~~~~~~~~~~~~~^~
In file included from /usr/include/c++/9/future:48,
from /opt/ros/foxy/include/rclcpp/executors.hpp:18,
from /home/ebots/workspace/behaviortree_ros/include/behaviortree_ros2/bt_action_node.hpp:6,
from /home/ebots/workspace/behaviortree_ros/test/test_client.cpp:1:
/usr/include/c++/9/bits/std_function.h: At global scope:
/usr/include/c++/9/bits/std_function.h:523:2: error: ‘std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_Callable<typename std::decay<_Functor>::type>, std::function<_Res(_ArgTypes ...)>&> std::function<_Res(_ArgTypes ...)>::operator=(_Functor&&) [with _Functor = BT::RosActionNode<ActionT>::tick() [with ActionT = behaviortree_ros2::action::Sleep]::<lambda(rclcpp_action::ClientGoalHandle<behaviortree_ros2::action::Sleep>::SharedPtr, std::shared_ptr<const behaviortree_ros2::action::Sleep_Feedback_<std::allocator<void> > >)>; _Res = void; _ArgTypes = {std::shared_ptr<rclcpp_action::ClientGoalHandle<behaviortree_ros2::action::Sleep> >, std::shared_ptr<const behaviortree_ros2::action::Sleep_Feedback_<std::allocator<void> > >}; std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_Callable<typename std::decay<_Functor>::type>, std::function<_Res(_ArgTypes ...)>&> = std::function<void(std::shared_ptr<rclcpp_action::ClientGoalHandle<behaviortree_ros2::action::Sleep> >, std::shared_ptr<const behaviortree_ros2::action::Sleep_Feedback_<std::allocator<void> > >)>&]’, declared using local type ‘BT::RosActionNode<ActionT>::tick() [with ActionT = behaviortree_ros2::action::Sleep]::<lambda(rclcpp_action::ClientGoalHandle<behaviortree_ros2::action::Sleep>::SharedPtr, std::shared_ptr<const behaviortree_ros2::action::Sleep_Feedback_<std::allocator<void> > >)>’, is used but never defined [-fpermissive]
523 | operator=(_Functor&& __f)
| ^~~~~~~~
/usr/include/c++/9/bits/std_function.h:523:2: error: ‘std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_Callable<typename std::decay<_Functor>::type>, std::function<_Res(_ArgTypes ...)>&> std::function<_Res(_ArgTypes ...)>::operator=(_Functor&&) [with _Functor = BT::RosActionNode<ActionT>::tick() [with ActionT = behaviortree_ros2::action::Sleep]::<lambda(const WrappedResult&)>; _Res = void; _ArgTypes = {const rclcpp_action::ClientGoalHandle<behaviortree_ros2::action::Sleep>::WrappedResult&}; std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_Callable<typename std::decay<_Functor>::type>, std::function<_Res(_ArgTypes ...)>&> = std::function<void(const rclcpp_action::ClientGoalHandle<behaviortree_ros2::action::Sleep>::WrappedResult&)>&]’, declared using local type ‘BT::RosActionNode<ActionT>::tick() [with ActionT = behaviortree_ros2::action::Sleep]::<lambda(const WrappedResult&)>’, is used but never defined [-fpermissive]
/usr/include/c++/9/bits/std_function.h:523:2: error: ‘std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_Callable<typename std::decay<_Functor>::type>, std::function<_Res(_ArgTypes ...)>&> std::function<_Res(_ArgTypes ...)>::operator=(_Functor&&) [with _Functor = BT::RosActionNode<ActionT>::tick() [with ActionT = behaviortree_ros2::action::Sleep]::<lambda(std::shared_future<std::shared_ptr<rclcpp_action::ClientGoalHandle<behaviortree_ros2::action::Sleep> > >)>; _Res = void; _ArgTypes = {std::shared_future<std::shared_ptr<rclcpp_action::ClientGoalHandle<behaviortree_ros2::action::Sleep> > >}; std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_Callable<typename std::decay<_Functor>::type>, std::function<_Res(_ArgTypes ...)>&> = std::function<void(std::shared_future<std::shared_ptr<rclcpp_action::ClientGoalHandle<behaviortree_ros2::action::Sleep> > >)>&]’, declared using local type ‘BT::RosActionNode<ActionT>::tick() [with ActionT = behaviortree_ros2::action::Sleep]::<lambda(std::shared_future<std::shared_ptr<rclcpp_action::ClientGoalHandle<behaviortree_ros2::action::Sleep> > >)>’, is used but never defined [-fpermissive]
make[2]: *** [CMakeFiles/test_client.dir/build.make:63: CMakeFiles/test_client.dir/test/test_client.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:126: CMakeFiles/test_client.dir/all] Error 2
make: *** [Makefile:141: all] Error 2
---
Failed <<< behaviortree_ros2 [31.0s, exited with code 2]
Aborted <<< behaviortree_cpp [1min 50s]
Summary: 5 packages finished [1min 51s]
1 package failed: behaviortree_ros2
6 packages aborted: behaviortree_cpp control_msgs controller_manager_msgs groot industrial_msgs rviz_visual_tools
2 packages had stderr output: behaviortree_cpp behaviortree_ros2
10 packages not processed
The command '/bin/sh -c cd $HOME/$EBOTS_ARMS_WORKSPACE && . /opt/ros/$ROS_DISTRO/setup.sh && colcon build --allow-overriding control_msgs controller_interface hardware_interface joint_state_publisher joint_state_publisher_gui controller_manager ros2_control_test_assets --cmake-args -DCMAKE_BUILD_TYPE=Release' returned a non-zero code: 2
When running sleep_server
from examples (on windows), the node crashes immediately with
$ sleep_server
[rcutils|error_handling.c:66] an error string (message, file name, or formatted message) will
be truncated
Hi all,
I'm facing an issue using my own created ROS2 Action BT Plugin.
I'm basically using the same header and cpp file as the sample sleep_action in the btcpp_ros2_samples
package. I register the Action Server using the macro CreateRosNodePlugin(ros_behavior_tree::DummySleepAction, "DummySleepAction");
from behaviortree_ros2/plugins.hpp
in the cpp file of the plugin.
Opposing to BehaviorTree/BehaviorTree.CPP#300 , I'm also adding the compile definitions BT_EXPORT_PLUGIN
in my CMakeLists.txt:
add_library(dummy_sleep_action_bt_node SHARED plugins/action/DummySleepAction.cpp)
list(APPEND plugin_libs dummy_sleep_action_bt_node)
foreach(bt_plugin ${plugin_libs})
ament_target_dependencies(${bt_plugin} ${dependencies})
target_compile_definitions(${bt_plugin} PRIVATE BT_PLUGIN_EXPORT)
endforeach()
The plugin is being loaded using the BT::SharedLibrary loader
with factory_.registerFromPlugin(loader.getOSName(p));
(p
being "dummy_sleep_action_bt_node"
).
Does anyone have an idea what the issue might be? Also please let me know if I can provide any more information.
Best regards,
Tom
EDIT: In the meantime I found out, that naively "mixing" the provided functions by this package and functionalities that were part of the navigation2 behavior tree package could be the cause of the problem as I'm not registering the action plugins as described in the "sleep_client" sample of this package. Will update, but still happy for some feedback.
The changes introduced by BehaviorTree/BehaviorTree.CPP@77c0571 broke this package.
While compiling I get the following error
Starting >>> behaviortree_ros2
--- stderr: behaviortree_ros2
/usr/bin/ld: CMakeFiles/sleep_client.dir/test/sleep_client.cpp.o: in function `std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, BT::PortInfo> BT::InputPort<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::basic_string_view<char, std::char_traits<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string_view<char, std::char_traits<char> >)':
sleep_client.cpp:(.text._ZN2BT9InputPortINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEESt4pairIS6_NS_8PortInfoEESt17basic_string_viewIcS4_ERKT_SB_[_ZN2BT9InputPortINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEESt4pairIS6_NS_8PortInfoEESt17basic_string_viewIcS4_ERKT_SB_]+0x7d): undefined reference to `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > BT::toStr<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
gmake[2]: *** [CMakeFiles/sleep_client.dir/build.make:172: sleep_client] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:711: CMakeFiles/sleep_client.dir/all] Error 2
gmake: *** [Makefile:146: all] Error 2
---
Failed <<< behaviortree_ros2 [2.97s, exited with code 2]
From what I can see, it seems that the way the providedBasicPorts
was generated is not valid anymore.
If I change the InputPort
definition like this:
- InputPort<std::string>("action_name", "__default__placeholder__", "Action server name")
+ InputPort<std::string>("action_name", "Action server name")
the issue is fixed and no side effects are present.
I'm a fan of headless robots.
Would you be interested in developing a webui similar to Groot but accessible via a browser?
Perhaps building on https://flows.nodered.org/node/node-red-contrib-uibuilder#22-simple-flow
It would be useful to launch a tree with the TreeExecutionServer from it's launch.
This would bypass the secondary node that needs to be created to send a goal to the TreeExecutionServer.
Recently was exploring the use of plugin for behaviortree_ros2, after colcon build, I ran the command "ros2 run ros2 run btcpp_ros2_samples sleep_server" on one terminal and another command "ros2 run btcpp_ros2_samples sleep_client_dyn
" in another terminal. But the client terminal was unable to run and return the following error:
terminate called after throwing an instance of 'BT::RuntimeError' what(): Could not load library: ../lib/libsleep_action_plugin.so: cannot open shared object file: No such file or directory [ros2run]: Aborted
So i tried to copy the relative path of the .so file in the install directory and paste it into line 80 of the code. Below is the before change and after change.
BEFORE:
#ifdef USE_SLEEP_PLUGIN
RegisterRosNode(factory, "../lib/libsleep_action_plugin.so", params);
#else
factory.registerNodeType("SleepAction", params);
#endif
AFTER:
#ifdef USE_SLEEP_PLUGIN
RegisterRosNode(factory, "install/btcpp_ros2_samples/lib/btcpp_ros2_samples/libsleep_plugin.so", params);
#else
factory.registerNodeType("SleepAction", params);
#endif
I then colcon build and tried to run the code again, but it said that the ID "Sleep" is already registered.
terminate called after throwing an instance of 'BT::BehaviorTreeException' what(): ID [Sleep] already registered [ros2run]: Aborted
Not sure if this is an issue or I am doing something wrong. Please do provide guidance. Thank you.
UPDATE:
Was able to resolve the issue by redoing the git clone for this package and installing all the dependencies and changing the path of the .so file to the correct path.
This directory appears not to exist:
With BehaviorTree.CPP installed viasudo apt install ros-humble-behaviortree-cpp
on Ubuntu 22.04.
Running the launchfile results in the following output, and I couldn't find any reference to bt_plugins
in any BehaviorTree.CPP CMakeLists.txt:
$ ros2 launch btcpp_ros2_samples sample_bt_executor.launch.xml
[INFO] [launch]: All log files can be found below /home/ros/.ros/log/2024-05-15-11-04-28-365715-ros-desktop-72895
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [sample_bt_executor-1]: process started with pid [72896]
[INFO] [sleep_server-2]: process started with pid [72898]
[sample_bt_executor-1] [INFO] [1715796268.500210508] [bt_action_server]: Starting Action Server: behavior_server
[sample_bt_executor-1] terminate called after throwing an instance of 'std::filesystem::__cxx11::filesystem_error'
[sample_bt_executor-1] what(): filesystem error: directory iterator cannot open directory: No such file or directory [/opt/ros/humble/share/behaviortree_cpp/bt_plugins]
[ERROR] [sample_bt_executor-1]: process has died [pid 72896, exit code -6, cmd '/home/ros/prebuilt_ws/install/btcpp_ros2_samples/lib/btcpp_ros2_samples/sample_bt_executor --ros-args --params-file /home/ros/prebuilt_ws/install/btcpp_ros2_samples/share/btcpp_ros2_samples/config/sample_bt_executor.yaml'].
^C[WARNING] [launch]: user interrupted with ctrl-c (SIGINT)
[sleep_server-2] [INFO] [1715796270.557774044] [rclcpp]: signal_handler(signum=2)
[INFO] [sleep_server-2]: process has finished cleanly [pid 72898]
Hi
Thank you for providing such great library.
I am currently implemented to behaviors in ros2 and now i designed a behavior tree using back-chaining method. My problem is that some actions in my behavior tree should run for an infinite time until a certain condition changed. For example suppose I have a behavior tree like below :
All "Scripts" in above behavior tree are implemented as action servers and all "ScriptConditions" are implemented as SubTopics in ROS2.
The challenge I am currently facing is that as long as no one is visible for the robot, it should search for a person in the room. It means that it should run a long running or infinite action of searching until "Is Any Person Visible" condition is met and it will automatically "halt" the search action and start another infinite action of approaching that person until another condition of "Can High Five To Person" is met. After that it will "High Five and Shut Down".
I can easily implement the behaviors such that they never return success and keep running forever until they fail for some reason (for example missing the person in case of approaching that person action). The problem is that the behavior-tree send "Goal Timeout" after some time and change the action node state to failure resulting in running that action again and again.
One could argue that the action should return "success" whenever the condition is met but isn't the success condition of behavior or action already defined in behavior tree ? for example for the action "Search For A Person In The Room" to be successful, "Is Any Person Visible?" should return success.
I would be very thankful if anyone could help me with this.
Hi everyone.
I'm having some issues with my first behavior tree implementation.
I created a simple action client, that inherits from the RosActionNode class, in order to send target poses to a 6DOF manipulator but as soon as this client is ticked, I get a segmentation fault.
The main node, i.e. the one that "builds" and ticks the behavior tree, is the following:
#include "segmentation_bt_client.hpp"
#include "move_bt_action.hpp"
#include "behaviortree_cpp/bt_factory.h"
#include "behaviortree_cpp/loggers/bt_cout_logger.h"
#include "behaviortree_ros2/plugins.hpp"
using namespace BT;
int main(int argc, char** argv)
{
const std::string bt_xml_path = "/home/diruscio/ros2_proj/src/parcel_pkg/config/bt_xml.xml";
rclcpp::init(argc,argv);
auto node = std::make_shared<rclcpp::Node>("bt_main");
node->declare_parameter("bt_xml", rclcpp::ParameterValue(bt_xml_path));
std::string bt_xml;
node->get_parameter("bt_xml", bt_xml);
RosNodeParams params_action;
params_action.nh = node;
params_action.default_port_value="move_action_server";
RosNodeParams params_service;
params_service.nh = node;
params_service.default_port_value = "cloud_segmentation_server";
BehaviorTreeFactory factory;
factory.registerNodeType<MoveToTargetAction>("MoveToTarget", params_action);
factory.registerNodeType<SegmentationClient>("SegmentationClient", params_service);
auto tree = factory.createTreeFromFile(bt_xml);
tree.tickWhileRunning();
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
return 0;
}
And the XML file is:
<root BTCPP_format="4" >
<BehaviorTree ID="MainTree">
<Sequence name="root_sequence">
<MoveToTarget move_type="home" target_frame="{target_frame}" />
<SegmentationClient target_frame="{target_frame}"/>
<Repeat num_cycles="3">
<Sequence>
<MoveToTarget move_type="pick" target_frame="{target_frame}"/>
<MoveToTarget move_type="away" target_frame="{target_frame}"/>
<MoveToTarget move_type="release" target_frame="{target_frame}"/>
<SegmentationClient target_frame="{target_frame}"/>
</Sequence>
</Repeat>
</Sequence>
</BehaviorTree>
</root>
When running the main node, I get the following output:
[INFO] [1693418921.313392475] [move_client_logger]: Reading fixed poses from file: /home/diruscio/ros2_proj/src/parcel_pkg/config/fixed_positions.yaml
[INFO] [1693418921.313670412] [move_client_logger]: Fixed poses read correctly
[INFO] [1693418921.848795157] [segmentation_client_logger]: Segmentation client initialized
[INFO] [1693418921.851096244] [move_client_logger]: Reading fixed poses from file: /home/diruscio/ros2_proj/src/parcel_pkg/config/fixed_positions.yaml
[INFO] [1693418921.851268127] [move_client_logger]: Fixed poses read correctly
[INFO] [1693418921.872170030] [move_client_logger]: Reading fixed poses from file: /home/diruscio/ros2_proj/src/parcel_pkg/config/fixed_positions.yaml
[INFO] [1693418921.872526930] [move_client_logger]: Fixed poses read correctly
[INFO] [1693418921.883179841] [move_client_logger]: Reading fixed poses from file: /home/diruscio/ros2_proj/src/parcel_pkg/config/fixed_positions.yaml
[INFO] [1693418921.884537858] [move_client_logger]: Fixed poses read correctly
[INFO] [1693418921.888328717] [segmentation_client_logger]: Segmentation client initialized
[INFO] [1693418921.888423013] [move_client_logger]: Move to home position.
[INFO] [1693418921.888430763] [move_client_logger]: Move type: PTP
[INFO] [1693418921.888433997] [move_client_logger]: Speed: 1.000
[INFO] [1693418921.888438358] [move_client_logger]: Target x: -0.027
[INFO] [1693418921.888441878] [move_client_logger]: Target y: 0.744
[INFO] [1693418921.888444680] [move_client_logger]: Target z: 0.659
[INFO] [1693418921.888447384] [move_client_logger]: Target qx: -0.707
[INFO] [1693418921.888450183] [move_client_logger]: Target qy: -0.674
[INFO] [1693418921.888452922] [move_client_logger]: Target qz: 0.177
[INFO] [1693418921.888455738] [move_client_logger]: Target qw: -0.120
[ros2run]: Segmentation fault
The target pose is printed before the "setGoal" function returns "true".
The action server does not receive any request.
I sure I'm facing a trivial bug, but I have not been able to find it.
Additional information:
OS: Ubuntu 22.04
ROS Version: ROS 2 Humble
BehaviorTree.ROS2 version: 0.2.0
I'm running in a weird behavior when trying to halting an action node.
Setup:
BehaviorTree.ROS2 branch humble bb0d510
BehaviorTree.CPP branch master acbc707
Structure:
ws
|- BehaviorTree.ROS2
|- BehaviorTree.CPP
|- test
|- CMakeLists.txt
|- package.xml
|- src
|- bt_act_test.cpp
CMakeLists.txt:
cmake_minimum_required(VERSION 3.8)
project(bt_act_test)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED )
find_package(rclcpp_action REQUIRED )
find_package(behaviortree_cpp REQUIRED )
find_package(behaviortree_ros2 REQUIRED )
find_package(example_interfaces REQUIRED)
add_executable(bt_act_test src/bt_act_test.cpp)
target_include_directories(bt_act_test PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
ament_target_dependencies(bt_act_test
rclcpp
rclcpp_action
behaviortree_cpp
behaviortree_ros2
example_interfaces
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
install(TARGETS
bt_act_test
DESTINATION lib/${PROJECT_NAME})
ament_package()
package.xml:
...
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>rclcpp_action</depend>
<depend>behaviortree_ros2</depend>
<depend>behaviortree_cpp</depend>
<depend>example_interfaces</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
...
bt_act_test.cpp:
#include <stdio.h>
#include <string>
#include <chrono>
#include <thread>
#include <memory>
#include <filesystem>
#include <vector>
#include <example_interfaces/action/fibonacci.hpp>
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <behaviortree_ros2/bt_action_node.hpp>
using vec_int = std::vector<int32_t>;
using Fibonacci = example_interfaces::action::Fibonacci;
using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle<Fibonacci>;
void sleep(int ms=10){
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
class FibonacciActionServer : public rclcpp::Node {
public:
explicit FibonacciActionServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions())
: Node("server_node", options)
{
using namespace std::placeholders;
this->action_server_ = rclcpp_action::create_server<Fibonacci>(
this,
"fibonacci",
std::bind(&FibonacciActionServer::handle_goal, this, _1, _2),
std::bind(&FibonacciActionServer::handle_cancel, this, _1),
std::bind(&FibonacciActionServer::handle_accepted, this, _1)
);
}
private:
rclcpp_action::Server<Fibonacci>::SharedPtr action_server_;
rclcpp_action::GoalResponse handle_goal(
const rclcpp_action::GoalUUID & uuid,
std::shared_ptr<const Fibonacci::Goal> goal)
{
RCLCPP_INFO(this->get_logger(), "Received goal request with order %d", goal->order);
(void)uuid;
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
rclcpp_action::CancelResponse handle_cancel(
const std::shared_ptr<GoalHandleFibonacci> goal_handle)
{
RCLCPP_INFO(this->get_logger(), "Received request to cancel goal");
(void)goal_handle;
return rclcpp_action::CancelResponse::ACCEPT;
}
void handle_accepted(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
{
using namespace std::placeholders;
// this needs to return quickly to avoid blocking the executor, so spin up a new thread
std::thread{std::bind(&FibonacciActionServer::execute, this, _1), goal_handle}.detach();
}
void execute(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
{
RCLCPP_INFO(this->get_logger(), "Executing goal");
rclcpp::Rate loop_rate(1);
const auto goal = goal_handle->get_goal();
auto feedback = std::make_shared<Fibonacci::Feedback>();
auto & sequence = feedback->sequence;
sequence.push_back(0);
sequence.push_back(1);
auto result = std::make_shared<Fibonacci::Result>();
for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) {
// Check if there is a cancel request
if (goal_handle->is_canceling()) {
result->sequence = sequence;
goal_handle->canceled(result);
RCLCPP_INFO(this->get_logger(), "Goal canceled");
return;
}
// Update sequence
sequence.push_back(sequence[i] + sequence[i - 1]);
// Publish feedback
goal_handle->publish_feedback(feedback);
RCLCPP_INFO(this->get_logger(), "Publish feedback");
loop_rate.sleep();
}
// Check if goal is done
if (rclcpp::ok()) {
result->sequence = sequence;
goal_handle->succeed(result);
RCLCPP_INFO(this->get_logger(), "Goal succeeded");
}
}
};
using namespace BT;
class FibonacciAction: public RosActionNode<Fibonacci>
{
public:
FibonacciAction(const std::string& name,
const NodeConfig& conf,
const RosNodeParams& params)
: RosActionNode<Fibonacci>(name, conf, params)
{}
// The specific ports of this Derived class
// should be merged with the ports of the base class,
// using RosActionNode::providedBasicPorts()
static PortsList providedPorts()
{
return providedBasicPorts({InputPort<unsigned>("order"), OutputPort<vec_int>("result")});
}
// This is called when the TreeNode is ticked and it should
// send the request to the action server
bool setGoal(RosActionNode::Goal& goal) override
{
// get "order" from the Input port
getInput("order", goal.order);
// return true, if we were able to set the goal correctly.
return true;
}
// Callback executed when the reply is received.
// Based on the reply you may decide to return SUCCESS or FAILURE.
NodeStatus onResultReceived(const WrappedResult& wr) override
{
std::stringstream ss;
ss << "Result received: ";
for (auto number : wr.result->sequence) {
ss << number << " ";
}
RCLCPP_INFO(node_->get_logger(), ss.str().c_str());
this->setOutput<vec_int>("result", wr.result->sequence);
return NodeStatus::SUCCESS;
}
// Callback invoked when there was an error at the level
// of the communication between client and server.
// This will set the status of the TreeNode to either SUCCESS or FAILURE,
// based on the return value.
// If not overridden, it will return FAILURE by default.
virtual NodeStatus onFailure(ActionNodeErrorCode error) override
{
RCLCPP_ERROR(node_->get_logger(), "Error: %d", error);
return NodeStatus::FAILURE;
}
// we also support a callback for the feedback, as in
// the original tutorial.
// Usually, this callback should return RUNNING, but you
// might decide, based on the value of the feedback, to abort
// the action, and consider the TreeNode completed.
// In that case, return SUCCESS or FAILURE.
// The Cancel request will be send automatically to the server.
NodeStatus onFeedback(const std::shared_ptr<const Feedback> feedback)
{
std::stringstream ss;
ss << "Next number in sequence received: ";
for (auto number : feedback->sequence) {
ss << number << " ";
}
RCLCPP_INFO(node_->get_logger(), ss.str().c_str());
this->setOutput<vec_int>("result", feedback->sequence);
return NodeStatus::RUNNING;
}
};
template<typename T>
class GetBBvalueNode : public BT::SyncActionNode{
public:
GetBBvalueNode (
const std::string& name, const BT::NodeConfig& config,
std::function<BT::NodeStatus(T)> callback,
std::function<BT::NodeStatus(BT::Expected<T>)> no_value_callback = [](BT::Expected<T> input){
throw BT::RuntimeError("missing required input [input]: ", input.error() );
return BT::NodeStatus::FAILURE; //needed to pass contructor checks
}
) :
BT::SyncActionNode(name, config),
_callback(callback),
_no_value_callback(no_value_callback) {}
BT::NodeStatus tick() override{
auto input = getInput<T>("input"); //get value from port "input"
// Check if expected is valid. If not, throw its error
if (!input) return this->_no_value_callback(input);
else return this->_callback(input.value());
}
/**
* @brief set port input as <T> input
*/
static BT::PortsList providedPorts(){
return{ BT::InputPort<T>("input", "read value")};
}
private:
std::function<BT::NodeStatus(T)> _callback;
std::function<BT::NodeStatus(BT::Expected<T>)> _no_value_callback;
};
int main(int argc, char *argv[]){
rclcpp::init(argc, argv);
static const char* xml_text = R"(
<root BTCPP_format="4" >
<BehaviorTree ID="MainTree">
<ReactiveSequence>
<GetVec input="{res}"/>
<Fibonacci order="5"
result="{res}"/>
</ReactiveSequence>
</BehaviorTree>
</root>
)";
std::vector<vec_int> res_vecs;
auto get_vec = [&res_vecs](vec_int input){
if ( std::find(res_vecs.begin(), res_vecs.end(), input) == res_vecs.end() ){
res_vecs.push_back(input);
for(auto el : res_vecs.back()) std::cout << el << "; ";
std::cout << std::endl;
}
if (res_vecs.size() == 2) return BT::NodeStatus::FAILURE;
else return BT::NodeStatus::SUCCESS;
};
auto on_err = [](BT::Expected<vec_int> input){
(void)input;
return BT::NodeStatus::SUCCESS;
};
BehaviorTreeFactory factory;
// provide the ROS node and the name of the action service
RosNodeParams params;
auto node = std::make_shared<rclcpp::Node>("fibonacci_action_client");
params.nh = node;
params.default_port_value = "fibonacci";
factory.registerNodeType<FibonacciAction>("Fibonacci", params);
factory.registerNodeType<GetBBvalueNode<vec_int>>("GetVec", get_vec, on_err);
auto server_node = std::make_shared<FibonacciActionServer>();
auto spin_callback = [](rclcpp::Node::SharedPtr node){rclcpp::spin(node);};
std::thread{spin_callback, server_node}.detach();
sleep();
// tick the tree
auto tree = factory.createTreeFromText(xml_text);
tree.tickWhileRunning();
rclcpp::shutdown();
return 0;
}
The code is essentially copied-pasted from the tutorials BT ros2 integration and ROS2 action server.
When I try to run the executable I get the following:
root@f1e379732b0f:~/projects# ros2 run bt_act_test bt_act_test
[INFO] [1687792324.400457200] [server_node]: Received goal request with order 5
[INFO] [1687792324.401037500] [server_node]: Executing goal
[INFO] [1687792324.401250200] [server_node]: Publish feedback
[INFO] [1687792324.421203700] [fibonacci_action_client]: Goal accepted by server, waiting for result
[INFO] [1687792325.401689100] [server_node]: Publish feedback
[INFO] [1687792325.407532400] [fibonacci_action_client]: Next number in sequence received: 0 1 1 2
0; 1; 1; 2;
[INFO] [1687792326.401572200] [server_node]: Publish feedback
[INFO] [1687792326.405681700] [fibonacci_action_client]: Next number in sequence received: 0 1 1 2 3
0; 1; 1; 2; 3;
[INFO] [1687792326.406163200] [server_node]: Received request to cancel goal
terminate called after throwing an instance of 'std::runtime_error'
what(): Asked to publish result for goal that does not exist
[ros2run]: Aborted
Apologies for the long message, but I hope it will make the error reproduction easier.
Thanks in advance for the support
Users should have a mechanism to override QoS when subscribing / publishing for topics.
@facontidavide would you prefer this to be done over the blackboard, at compilation time in the derived class, or allowing both (like for the topic name ?)
I have a local version at compile time, but since the subscriber / publisher are created in the constructor, I had to move the initialization part in a public function that take an rclcpp::QoS
parameter. This add a bit of boilerplate, as the end user has to call this function in the constructor of it's derived class. If this seems sufficient to you, I can create a PR.
Thanks for this amazing library,
Cheers
Running ROS 2 Humble on Ubuntu 22.04
I'm using a behavior tree that has a BT action node to send a goal to the Nav2 navigate_to_pose
action server. My problem is that if the robot navigates for more than 10 seconds (which is the default server_timeout of a BT::RosActionNode
), the node's onFailure
method is triggered and returns the error code SEND_GOAL_TIMEOUT
. From what I've read of the bt_action_node.hpp
file, this means that the client did not receive a response regarding whether the goal was accepted or not from the server within 10 seconds.
The thing is that even if I haven't received a goal_response, the Nav2 action server runs normally and the robot navigates (since async_send_goal
is used I'm guessing). I think the problem lies in the way that bt_action_node.hpp
(line 459 and after) checks the future
to receive the goal_handle
. It seems to never return rclcpp::FutureReturnCode::SUCCESS
in the spin_until_future_complete
with no delay.
What's weird is that if the navigation time is under 10 seconds, then the BT action node does not fail, but I can see in the prints that the goal_response_callback
only gets called after the robot reaches its navigation goal. This means that the response about whether the action goal is accepted or not is only received after the robot is done navigating. I also don't receive any feedback from the action server during the navigation, probably since I have no goal_handle.
I also have another BT action node for docking in my behavior tree (where the action server was custom-made), and I don't seem to have a problem with it since I can see in the debug prints that the goal was accepted and I receive feedback.
Because of this, I'm not sure if it's a Nav2 problem with the way that the navigate_to_pose
action server was written or an edge case that BehaviorTree.ROS2 did not consider.
I found a way to make it work for the navigate_to_pose
action by setting the nodelay in bt_action_node.hpp
(line 452) to 10ms instead of 0ms:
auto nodelay = std::chrono::milliseconds(0);
-> auto nodelay = std::chrono::milliseconds(10);
I don't have a clear understanding of the reason why the spin_until_future_complete
has a timeout of 0ms. I guess it's to have a non-blocking behavior.
I've recreated the same issue with a simple action client node that calls the navigate_to_pose
action server and handles the goal accepted response with the same logic that bt_action_node.hpp
has. I'll leave the .hpp
and .cpp
code if you want to test it.
#ifndef NAVIGATE_TO_POSE_CLIENT_HPP
#define NAVIGATE_TO_POSE_CLIENT_HPP
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <geometry_msgs/msg/pose_stamped.hpp>
#include <nav2_msgs/action/navigate_to_pose.hpp>
class NavigateToPoseClient : public rclcpp::Node
{
public:
using NavigateToPose = nav2_msgs::action::NavigateToPose;
using GoalHandleNavigateToPose = rclcpp_action::ClientGoalHandle<NavigateToPose>;
explicit NavigateToPoseClient(const rclcpp::NodeOptions & options = rclcpp::NodeOptions());
void send_goal(const geometry_msgs::msg::PoseStamped & goal_pose);
bool spin_until_goal_received();
private:
void goal_response_callback(const rclcpp_action::ClientGoalHandle<NavigateToPose>::SharedPtr & goal);
void feedback_callback(GoalHandleNavigateToPose::SharedPtr, const std::shared_ptr<const NavigateToPose::Feedback> feedback);
void result_callback(const GoalHandleNavigateToPose::WrappedResult & result);
rclcpp_action::Client<NavigateToPose>::SharedPtr action_client_;
std::shared_future<GoalHandleNavigateToPose::SharedPtr> future_goal_handle_;
GoalHandleNavigateToPose::SharedPtr goal_handle_;
rclcpp::Time time_goal_sent_;
bool goal_received_ = false;
};
#endif // NAVIGATE_TO_POSE_CLIENT_HPP
#include "osc_nav/nodes/navigate_to_pose_client.hpp"
NavigateToPoseClient::NavigateToPoseClient(const rclcpp::NodeOptions & options)
: Node("navigate_to_pose_client", options)
{
action_client_ = rclcpp_action::create_client<NavigateToPose>(this, "navigate_to_pose");
// Wait for the action server to be available
while (!action_client_->wait_for_action_server(std::chrono::seconds(10))) {
RCLCPP_INFO(this->get_logger(), "Waiting for action server to be available...");
}
}
void NavigateToPoseClient::send_goal(const geometry_msgs::msg::PoseStamped & goal_pose)
{
if (!action_client_) {
RCLCPP_ERROR(this->get_logger(), "Action client not initialized");
return;
}
auto goal_msg = NavigateToPose::Goal();
goal_msg.pose = goal_pose;
RCLCPP_INFO(this->get_logger(), "Sending goal");
auto send_goal_options = rclcpp_action::Client<NavigateToPose>::SendGoalOptions();
send_goal_options.goal_response_callback = std::bind(&NavigateToPoseClient::goal_response_callback, this, std::placeholders::_1);
send_goal_options.feedback_callback = std::bind(&NavigateToPoseClient::feedback_callback, this, std::placeholders::_1, std::placeholders::_2);
send_goal_options.result_callback = std::bind(&NavigateToPoseClient::result_callback, this, std::placeholders::_1);
future_goal_handle_ = action_client_->async_send_goal(goal_msg, send_goal_options);
time_goal_sent_ = this->now();
}
bool NavigateToPoseClient::spin_until_goal_received()
{
auto nodelay = std::chrono::milliseconds(0); // 0ms creates the bug where the goal accpeted is received only at the end. 10ms solves the issue
auto timeout = rclcpp::Duration::from_seconds(double(10));
RCLCPP_WARN(this->get_logger(), "Timeout: %f", timeout.seconds());
auto ret = rclcpp::spin_until_future_complete(this->get_node_base_interface(), future_goal_handle_, nodelay);
if(ret != rclcpp::FutureReturnCode::SUCCESS)
{
if((this->now() - time_goal_sent_) > timeout)
{
RCLCPP_ERROR(this->get_logger(), "Goal timed out");
return false;
}
else
{
return false;
}
}
else
{
goal_received_ = true;
goal_handle_ = future_goal_handle_.get();
future_goal_handle_ = {};
if(!goal_handle_)
{
RCLCPP_ERROR(this->get_logger(), "Goal rejected by server");
return false;
}
return true;
}
}
void NavigateToPoseClient::goal_response_callback(const rclcpp_action::ClientGoalHandle<NavigateToPose>::SharedPtr & goal)
{
auto goal_handle = goal.get();
if (!goal_handle) {
RCLCPP_ERROR(this->get_logger(), "Goal was rejected by the action server");
} else {
RCLCPP_INFO(this->get_logger(), "Goal accepted by the action server, waiting for result");
}
}
void NavigateToPoseClient::feedback_callback(GoalHandleNavigateToPose::SharedPtr, const std::shared_ptr<const NavigateToPose::Feedback> feedback)
{
RCLCPP_INFO(this->get_logger(), "Received feedback: distance remaining = %f", feedback->distance_remaining);
}
void NavigateToPoseClient::result_callback(const GoalHandleNavigateToPose::WrappedResult & result)
{
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(this->get_logger(), "Goal succeeded");
break;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
break;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
break;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
break;
}
}
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<NavigateToPoseClient>();
// Example goal pose, replace with your desired goal
geometry_msgs::msg::PoseStamped goal_pose;
goal_pose.header.frame_id = "map";
goal_pose.header.stamp = node->now();
goal_pose.pose.position.x = 0.0;
goal_pose.pose.position.y = -1.0;
goal_pose.pose.orientation.w = 1.0;
node->send_goal(goal_pose);
while (rclcpp::ok())
{
if (node->spin_until_goal_received())
{
break;
}
rclcpp::sleep_for(std::chrono::milliseconds(100)); // Small sleep to prevent busy-waiting
}
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
in README.md it says this is supported after galactic. I assume there is no planned support for foxy, I am just curious what is the main reason for this
I'm currently refactoring our behavior trees to use these base classes in our merge to BTV4. Currently I've reached an issue where we have this ReactiveSequence
<Sequence>
<SubTree ID="DisableStopOnObstacle" _autoremap="true"/>
<ReactiveSequence>
<Sequence>
<SubTree ID="CheckEStop" _autoremap="true"/>
<SubTree ID="CheckContinueMissionDocking" _autoremap="true"/>
</Sequence>
<DockingAction
action_name="/docking_action_server"
distance="-0.1"
linear_speed="0.03"
stop_at_charger="false"
stop_at_mag_marker="false"
target_id="{docking_id}"
allowed_time="30"
failure_if_no_success = "true"
level="{level}"
message="{message}"
/>
</ReactiveSequence>
</Sequence>
Both the Subtrees CheckEStop
and CheckContinueMissionDocking
are service node calls.
In our BT3.8 implementation this sequence would work because the service node calls would wait until the service had responded and then only return SUCCESS or FAILURE. However with this Service Node implementation a service node can now return RUNNING when waiting for a response. This is meaning that we cannot have a Service node and Action node within the same Reactive Sequence.
Is this expected behavior? If so, is there a way to have a service node and action node within the same Reactive Sequence?
This issue relates to issue #41.
Assume a tree consisting of a single Sequence
of StatefulActionNode
nodes, all belonging to class
class TickRemoteTree : public BT::StatefulActionNode
:
<root BTCPP_format="4">
<BehaviorTree ID="tree">
<Sequence>
<TickRemoteTree robot_name="A" var="1"/>
<TickRemoteTree robot_name="B" var="2"/>
<TickRemoteTree robot_name="A" var="10"/>
<TickRemoteTree robot_name="B" var="20"/>
<TickRemoteTree robot_name="A" var="100"/>
<TickRemoteTree robot_name="B" var="200"/>
</Sequence>
</BehaviorTree>
</root>
What I am trying to achieve is execute each TickRemoteTree
action sequentially, that is, make robot A sleep for 1 sec, then wake up, then make robot B sleep for 2 sec, then wake up, then make robot A sleep again for 10 sec, and so on. (Essentially the same thing as BehaviorTree/BehaviorTree.CPP#242 (comment))
The way this must be achieved is by sending variable var
to robots A and B via a call to a service that they provide. Assume that var
specifies the amount of time each robot should sleep. Since TickRemoteTree
is a StatefulActionNode
, each TickRemoteTree
node returns RUNNING
after being ticked, and execution should not continue further than each node, since all TickRemoteTree
are wrapped inside a Sequence
. After waking up, each robot calls a service called tockFromRemoteTree
that TickRemoteTree
provides, making the node able to escape from a RUNNING state and into a SUCCESS or FAILURE state.
However, it is clear I am misunderstanding some things because what happens in practice is that
tockFromRemoteTree
service with a SUCCESS state, the callbacks of all six tockFromRemoteTree
services of all six nodes are called, one after another, andIt is as though all six nodes are pointers to a single changing object, but I would like to have six different objects. What is perhaps needed is some mix between RosServiceNode
and StatefulActionNode
, which seem to be complementary because the latter cannot deal with services on its own (at least to my understanding and programming level). Someone would say this is why the RosActionNode
exists. But what if one needed to avoid actions altogether? (actions are unbridgeable between ROS 1 and ROS2).
The BT::StatusChangeLogger is a handy class that automatically logs changes in the active behavior node's state as the tree is run. BehaviorTree.CPP comes with loggers for stdout, a file, a database, etc.
I could not find an equivalent such logger for ROS2. Is there any reason the ROS2 repo shouldn't have one?
I wrote one.
With the newest changes to the parent classes bt_action_node and bt_service_node, access is lost to the encapsulated ROS2 node. Access to its clock is necessary for throttled logging. I have proposed a simple fix in PR #77 which adds a method for accessing the clock.
It's common practice to place all logic inside a ROS node, with the main method simply creating and spinning the node. However, registering BT nodes from within an rclcpp::Node child class can lead to a circular dependency:
RosNodeParams params;
params.nh = this->shared_from_this();
params.default_port_value = "btcpp_string";
factory_->registerNodeType<ReceiveString>("ReceiveString", params);
This is due to passing this->shared_from_this()
as a rclcpp node pointer, creating a situation where the main ROS node holds a reference to a BT node and vice versa, leading to a circular dependency. One potential solution is to use weak pointers for BT nodes. It worked for me in a simple example similar to subscriber_test
. Not sure if there are any obvious drawbacks of this approach. What are your thoughts on this?
Also, what's your opinion on creating and ticking BT tree inside ROS node e.g. using ROS timer? Currently, I use ROS parameters to load BT nodes via plugins, and moving this outside the node might get complicated.
When using the RosTopicSubNode
object and providing the topic_name
port using a blackboard pointer, the subscriber is created on the first tick. When using topics with a transient local setting I would expect the last message on the topic to be provided as the last_msg
argument on that same first tick. Instead we keep getting nullptr's.
I've been playing around with multiple settings on either subscription as publisher side, but haven't been able to resolve my problem.
The problem only occurs on the very first tick or ticks until the next message is published on the topics.
Is there a way to fix this?
I heard from my colleagues this behavior also occurs for action/service nodes using blackboard pointers. for action_name
and service_name
. We haven't been testing publisher nodes, but perhaps the issue is present there as well.
Reproducability
I can reproduce my issue as follows:
ros2 topic pub --qos-durability=transient_local --qos-history=keep_last --qos-depth=1 test_topic std_msgs/msg/Bool "{data: False}"
Used behaviorTree
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="test_tree">
<GetDigitalSignal topic_name="{test_topic_name}" value="{output}"/>
</BehaviorTree>
<BehaviorTree ID="MainTree">
<Sequence>
<Script code="my_topic:='test_topic'"/>
<Delay delay_msec="10000"> <!-- Added Delay to allow some time connecting to Groot-->
<KeepRunningUntilFailure>
<ForceSuccess>
<Delay delay_msec="3000"> <!-- Delay added to see in Groot what happens easier-->
<ParallelAll>
<GetDigitalSignal topic_name="test_topic" value="{my_output_static}"/>
<GetDigitalSignal topic_name="{my_topic}" value="{my_output_scripted}"/>
<SubTree ID="test_tree" test_topic_name="{my_topic}" output="{my_ouput_subtree}"/>
</ParallelAll>
</Delay>
</ForceSuccess>
</KeepRunningUntilFailure>
</Delay>
</Sequence>
</BehaviorTree>
</root>
GetDigitalSignal Implementation
static BT::PortsList providedPorts()
{
return providedBasicPorts({
BT::OutputPort<std_msgs::msg::Bool::_data_type>("value", "Current value of the digital signal"),
});
}
BT::NodeStatus onTick(const std_msgs::msg::Bool::SharedPtr &msg)
{
if (msg != nullptr)
{
setOutput("value", msg->data);
return BT::NodeStatus::SUCCESS;
}
return BT::NodeStatus::FAILURE;
}
If we call ros2 interface show btcpp_ros2_interfaces/msg/NodeStatus
, we get the following issue:
Error processing '// generated from rosidl_adapter/resource/msg.idl.em' of 'btcpp_ros2_interfaces/NodeStatus': '//'
Traceback (most recent call last):
File "/opt/ros/humble/bin/ros2", line 33, in <module>
sys.exit(load_entry_point('ros2cli==0.18.10', 'console_scripts', 'ros2')())
File "/opt/ros/humble/lib/python3.10/site-packages/ros2cli/cli.py", line 91, in main
rc = extension.main(parser=parser, args=args)
File "/opt/ros/humble/lib/python3.10/site-packages/ros2interface/command/interface.py", line 35, in main
return extension.main(args=args)
File "/opt/ros/humble/lib/python3.10/site-packages/ros2interface/verb/show.py", line 201, in main
_show_interface(
File "/opt/ros/humble/lib/python3.10/site-packages/ros2interface/verb/show.py", line 148, in _show_interface
for line in _get_interface_lines(interface_identifier):
File "/opt/ros/humble/lib/python3.10/site-packages/ros2interface/verb/show.py", line 116, in _get_interface_lines
yield InterfaceTextLine(
File "/opt/ros/humble/lib/python3.10/site-packages/ros2interface/verb/show.py", line 43, in __init__
msg_spec = parse_message_string(
File "/opt/ros/humble/local/lib/python3.10/dist-packages/rosidl_adapter/parser.py", line 520, in parse_message_string
Type(type_string, context_package_name=pkg_name),
File "/opt/ros/humble/local/lib/python3.10/dist-packages/rosidl_adapter/parser.py", line 277, in __init__
super(Type, self).__init__(
File "/opt/ros/humble/local/lib/python3.10/dist-packages/rosidl_adapter/parser.py", line 190, in __init__
raise InvalidResourceName(type_string)
rosidl_adapter.parser.InvalidResourceName: //
The root cause is that the NodeStatus.msg
is located and installed into msgs
, but msg
is expected, see
https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Custom-ROS2-Interfaces.html#cmakelists-txt
I am facing a situation of the following form: There exist one central pc, one robot of type A, and one robot of type B. The pc is designed to be running a BT. That BT needs, on occasion, among other things, to make robot A and robot B perform certain actions, e.g. check self diagnostics, initialise navigation goals, make them navigate somewhere, etc.
At this point I have only bundled up all actions that pertain to each robot type to separate BTs, i.e. there are 2 BTs: BT_A
and BT_B
. My question is: how would it be best in your opinion to implement BT_pc
so that it calls functionalities (tree nodes) of BT_A
and BT_B
? Is something like this even feasible? Additionally, is it feasible given that all trees run on different machines?
I understand that the answer to this question is part ROS-2-implementation-specific and part Behavior-tree-related. If you believe that it is best suited for the main BehaviorTree repository, or elsewhere, please point it out to me. Thank you.
I stumbled over a use-case where a service call is taking longer than 1 second. I do not want to globally increase the service timeout over the BT::RosNodeParams parameter as this would affect all plugins I am using.
Is there any concern about removing the const declaration of service_timeout_ variable for the ROS2 service timeout here? This way one could overwrite the value in the setRequest() function (and get the timeout value eventually over an input port).
Let me know and i will file a PR.
Normally a ROS2 BT node would be set this way:
// in main()
BehaviorTreeFactory factory;
auto node = std::make_shared<rclcpp::Node>("fibonacci_action_client");
// provide the ROS node and the name of the action service
RosNodeParams params;
params.nh = node;
params.default_port_value = "fibonacci";
factory.registerNodeType<FibonacciAction>("Fibonacci", params);
Using the bt_factory.h (BT.CPP) for creating a non-ROS plugin is straightforward, but when adding ROS2 necessities the ROS node goes out of scope.
Inversly, when using the plugins.hpp (BT.ROS2) macro, I can't find how to set the params:
So how should a ROS2 plugin be set with its params?
Thank you in advance!
i import BehaviorTree.ROS2 as submodule.
directory structure is
xxxx_ws
dependencies
BehaviorTree.CPP
BehaviorTree.ROS2
src
package1
package2
colcon.pkg
pixi.toml
colcon.pkg
{
"build": {
"base-paths": ["src", "dependencies/BehaviorTree.CPP", "dependencies/BehaviorTree.ROS2"],
"packages-skip": ["btcpp_ros2_samples"]
}
}
use flow command will got error.
pixi shell
colcon build --symlink-install --cmake-args -DPython3_FIND_VIRTUALENV=ONLY
Starting >>> behaviortree_ros2
-- Found ament_cmake: 1.3.7 (/Users/pipinet/workspace/automation/.pixi/envs/default/share/ament_cmake/cmake)
-- Override CMake install command with custom implementation using symlinks instead of copying resources
-- Found rclcpp: 16.0.8 (/Users/pipinet/workspace/automation/.pixi/envs/default/share/rclcpp/cmake)
-- Found rosidl_generator_c: 3.1.5 (/Users/pipinet/workspace/automation/.pixi/envs/default/share/rosidl_generator_c/cmake)
-- Found rosidl_adapter: 3.1.5 (/Users/pipinet/workspace/automation/.pixi/envs/default/share/rosidl_adapter/cmake)
-- Found rosidl_generator_cpp: 3.1.5 (/Users/pipinet/workspace/automation/.pixi/envs/default/share/rosidl_generator_cpp/cmake)
-- Using all available rosidl_typesupport_c: rosidl_typesupport_fastrtps_c;rosidl_typesupport_introspection_c
-- Using all available rosidl_typesupport_cpp: rosidl_typesupport_fastrtps_cpp;rosidl_typesupport_introspection_cpp
-- Found rmw_implementation_cmake: 6.1.1 (/Users/pipinet/workspace/automation/.pixi/envs/default/share/rmw_implementation_cmake/cmake)
-- Found rmw_cyclonedds_cpp: 1.3.4 (/Users/pipinet/workspace/automation/.pixi/envs/default/share/rmw_cyclonedds_cpp/cmake)
-- Using RMW implementation 'rmw_cyclonedds_cpp' as default
-- Found rclcpp_action: 16.0.8 (/Users/pipinet/workspace/automation/.pixi/envs/default/share/rclcpp_action/cmake)
-- Found behaviortree_cpp: 4.6.0 (/Users/pipinet/workspace/automation/install/behaviortree_cpp/share/behaviortree_cpp/cmake)
-- Found btcpp_ros2_interfaces: 0.2.0 (/Users/pipinet/workspace/automation/install/btcpp_ros2_interfaces/share/btcpp_ros2_interfaces/cmake)
-- Found generate_parameter_library: 0.3.7 (/Users/pipinet/workspace/automation/.pixi/envs/default/share/generate_parameter_library/cmake)
-- Found parameter_traits: 0.3.7 (/Users/pipinet/workspace/automation/.pixi/envs/default/share/parameter_traits/cmake)
-- Found rclcpp_lifecycle: 16.0.8 (/Users/pipinet/workspace/automation/.pixi/envs/default/share/rclcpp_lifecycle/cmake)
-- Configuring done (2.2s)
-- Generating done (0.1s)
-- Build files have been written to: /Users/pipinet/workspace/automation/build/behaviortree_ros2
[ 20%] Running `/Users/pipinet/workspace/automation/.pixi/envs/default/bin/generate_parameter_library_cpp /Users/pipinet/workspace/automation/build/behaviortree_ros2/bt_executor_parameters/include//bt_executor_parameters.hpp /Users/pipinet/workspace/automation/dependencies/BehaviorTree.ROS2/behaviortree_ros2/src/bt_executor_parameters.yaml `
[ 20%] Built target bt_executor_parameters
[ 40%] Building CXX object CMakeFiles/behaviortree_ros2.dir/src/bt_ros2.cpp.o
[ 60%] Building CXX object CMakeFiles/behaviortree_ros2.dir/src/bt_utils.cpp.o
[ 80%] Building CXX object CMakeFiles/behaviortree_ros2.dir/src/tree_execution_server.cpp.o
/Users/pipinet/workspace/automation/dependencies/BehaviorTree.ROS2/behaviortree_ros2/src/tree_execution_server.cpp:206:28: warning: format string is not a string literal (potentially insecure) [-Wformat-security]
RCLCPP_WARN(kLogger, action_result->return_message.c_str());
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rclcpp/rclcpp/logging.hpp:972:7: note: expanded from macro 'RCLCPP_WARN'
__VA_ARGS__); \
^~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:750:5: note: expanded from macro 'RCUTILS_LOG_WARN_NAMED'
__VA_ARGS__)
^~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:72:64: note: expanded from macro 'RCUTILS_LOG_COND_NAMED'
rcutils_log(&__rcutils_logging_location, severity, name, __VA_ARGS__); \
^~~~~~~~~~~
/Users/pipinet/workspace/automation/dependencies/BehaviorTree.ROS2/behaviortree_ros2/src/tree_execution_server.cpp:206:28: note: treat the string as an argument to avoid this
RCLCPP_WARN(kLogger, action_result->return_message.c_str());
^
"%s",
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rclcpp/rclcpp/logging.hpp:972:7: note: expanded from macro 'RCLCPP_WARN'
__VA_ARGS__); \
^
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:750:5: note: expanded from macro 'RCUTILS_LOG_WARN_NAMED'
__VA_ARGS__)
^
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:72:64: note: expanded from macro 'RCUTILS_LOG_COND_NAMED'
rcutils_log(&__rcutils_logging_location, severity, name, __VA_ARGS__); \
^
/Users/pipinet/workspace/automation/dependencies/BehaviorTree.ROS2/behaviortree_ros2/src/tree_execution_server.cpp:238:24: error: no viable conversion from 'duration<[...], ratio<[...], __static_lcm<ratio<1, 1000000000>::den, ratio<1, 1000000000>::den>::value aka 1000000000>>' to 'duration<[...], ratio<[...], 1000000>>'
p_->tree.sleep(loop_deadline - now);
^~~~~~~~~~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/bin/../include/c++/v1/__chrono/duration.h:204:28: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'typename common_type<duration<long long, ratio<1, 1000000000>>, duration<long long, ratio<1, 1000000000>>>::type' (aka 'duration<long long, ratio<__static_gcd<ratio<1, 1000000000>::num, ratio<1, 1000000000>::num>::value, __static_lcm<ratio<1, 1000000000>::den, ratio<1, 1000000000>::den>::value>>') to 'const duration<long long, ratio<1, 1000000>> &' for 1st argument
class _LIBCPP_TEMPLATE_VIS duration
^
/Users/pipinet/workspace/automation/.pixi/envs/default/bin/../include/c++/v1/__chrono/duration.h:204:28: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'typename common_type<duration<long long, ratio<1, 1000000000>>, duration<long long, ratio<1, 1000000000>>>::type' (aka 'duration<long long, ratio<__static_gcd<ratio<1, 1000000000>::num, ratio<1, 1000000000>::num>::value, __static_lcm<ratio<1, 1000000000>::den, ratio<1, 1000000000>::den>::value>>') to 'duration<long long, ratio<1, 1000000>> &&' for 1st argument
/Users/pipinet/workspace/automation/.pixi/envs/default/bin/../include/c++/v1/__chrono/duration.h:267:9: note: candidate template ignored: requirement 'treat_as_floating_point<long long>::value || (__no_overflow<ratio<1, 1000000000>, ratio<1, 1000000>>::ratio<1, 1000>::den == 1 && !treat_as_floating_point<long long>::value)' was not satisfied [with _Rep2 = long long, _Period2 = std::ratio<1, 1000000000>]
duration(const duration<_Rep2, _Period2>& __d,
^
/Users/pipinet/workspace/automation/.pixi/envs/default/bin/../include/c++/v1/__chrono/duration.h:255:18: note: explicit constructor is not a candidate
explicit duration(const _Rep2& __r,
^
/Users/pipinet/workspace/automation/install/behaviortree_cpp/include/behaviortree_cpp/bt_factory.h:124:50: note: passing argument to parameter 'timeout' here
void sleep(std::chrono::system_clock::duration timeout);
^
/Users/pipinet/workspace/automation/dependencies/BehaviorTree.ROS2/behaviortree_ros2/src/tree_execution_server.cpp:246:27: warning: format string is not a string literal (potentially insecure) [-Wformat-security]
RCLCPP_ERROR(kLogger, action_result->return_message.c_str());
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rclcpp/rclcpp/logging.hpp:1407:7: note: expanded from macro 'RCLCPP_ERROR'
__VA_ARGS__); \
^~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:997:5: note: expanded from macro 'RCUTILS_LOG_ERROR_NAMED'
__VA_ARGS__)
^~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:72:64: note: expanded from macro 'RCUTILS_LOG_COND_NAMED'
rcutils_log(&__rcutils_logging_location, severity, name, __VA_ARGS__); \
^~~~~~~~~~~
/Users/pipinet/workspace/automation/dependencies/BehaviorTree.ROS2/behaviortree_ros2/src/tree_execution_server.cpp:246:27: note: treat the string as an argument to avoid this
RCLCPP_ERROR(kLogger, action_result->return_message.c_str());
^
"%s",
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rclcpp/rclcpp/logging.hpp:1407:7: note: expanded from macro 'RCLCPP_ERROR'
__VA_ARGS__); \
^
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:997:5: note: expanded from macro 'RCUTILS_LOG_ERROR_NAMED'
__VA_ARGS__)
^
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:72:64: note: expanded from macro 'RCUTILS_LOG_COND_NAMED'
rcutils_log(&__rcutils_logging_location, severity, name, __VA_ARGS__); \
^
/Users/pipinet/workspace/automation/dependencies/BehaviorTree.ROS2/behaviortree_ros2/src/tree_execution_server.cpp:269:26: warning: format string is not a string literal (potentially insecure) [-Wformat-security]
RCLCPP_INFO(kLogger, action_result->return_message.c_str());
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rclcpp/rclcpp/logging.hpp:537:7: note: expanded from macro 'RCLCPP_INFO'
__VA_ARGS__); \
^~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:503:5: note: expanded from macro 'RCUTILS_LOG_INFO_NAMED'
__VA_ARGS__)
^~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:72:64: note: expanded from macro 'RCUTILS_LOG_COND_NAMED'
rcutils_log(&__rcutils_logging_location, severity, name, __VA_ARGS__); \
^~~~~~~~~~~
/Users/pipinet/workspace/automation/dependencies/BehaviorTree.ROS2/behaviortree_ros2/src/tree_execution_server.cpp:269:26: note: treat the string as an argument to avoid this
RCLCPP_INFO(kLogger, action_result->return_message.c_str());
^
"%s",
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rclcpp/rclcpp/logging.hpp:537:7: note: expanded from macro 'RCLCPP_INFO'
__VA_ARGS__); \
^
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:503:5: note: expanded from macro 'RCUTILS_LOG_INFO_NAMED'
__VA_ARGS__)
^
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:72:64: note: expanded from macro 'RCUTILS_LOG_COND_NAMED'
rcutils_log(&__rcutils_logging_location, severity, name, __VA_ARGS__); \
^
/Users/pipinet/workspace/automation/dependencies/BehaviorTree.ROS2/behaviortree_ros2/src/tree_execution_server.cpp:274:27: warning: format string is not a string literal (potentially insecure) [-Wformat-security]
RCLCPP_ERROR(kLogger, action_result->return_message.c_str());
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rclcpp/rclcpp/logging.hpp:1407:7: note: expanded from macro 'RCLCPP_ERROR'
__VA_ARGS__); \
^~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:997:5: note: expanded from macro 'RCUTILS_LOG_ERROR_NAMED'
__VA_ARGS__)
^~~~~~~~~~~
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:72:64: note: expanded from macro 'RCUTILS_LOG_COND_NAMED'
rcutils_log(&__rcutils_logging_location, severity, name, __VA_ARGS__); \
^~~~~~~~~~~
/Users/pipinet/workspace/automation/dependencies/BehaviorTree.ROS2/behaviortree_ros2/src/tree_execution_server.cpp:274:27: note: treat the string as an argument to avoid this
RCLCPP_ERROR(kLogger, action_result->return_message.c_str());
^
"%s",
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rclcpp/rclcpp/logging.hpp:1407:7: note: expanded from macro 'RCLCPP_ERROR'
__VA_ARGS__); \
^
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:997:5: note: expanded from macro 'RCUTILS_LOG_ERROR_NAMED'
__VA_ARGS__)
^
/Users/pipinet/workspace/automation/.pixi/envs/default/include/rcutils/rcutils/logging_macros.h:72:64: note: expanded from macro 'RCUTILS_LOG_COND_NAMED'
rcutils_log(&__rcutils_logging_location, severity, name, __VA_ARGS__); \
^
4 warnings and 1 error generated.
make[2]: *** [CMakeFiles/behaviortree_ros2.dir/src/tree_execution_server.cpp.o] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [CMakeFiles/behaviortree_ros2.dir/all] Error 2
make: *** [all] Error 2
Failed <<< behaviortree_ros2 [13.7s, exited with code 2]
is it possible to applly remappings to nodes in bt.ROS2?
currently i am launching bt codes using ExecuteProcess froM roslaunnch file
Hello!
I am struggeling to integrate behaviortree_cpp version 4.0.1 into my ROS2 package. I cannot even pass the first step, which would be including the dependencies in CMakeLists.txt and package.xml.
With "rosdep install -i --from-path src --rosdistro humble -y" I am always getting the error:
ERROR: the following packages/stacks could not have their rosdep keys resolved to system dependencies: behavior_tree_example: Cannot locate rosdep definition for [behaviortree_cpp]
If I add the behaviortree_cpp_v3 dependencies, there is no problem at all.
Is it somehow not integrated, i.e. do I have to integrate it manually?
Thanks in advance,
best,
hans
Hi,
I am trying to dockerized my behavior tree calling several ros2 actions server. The action servers are running inside another container.
Everything works well if I run the behavior tree outside the docker.
When running inside the docker, it keeps telling that the action server is unreachable and kills itself. When I tried to enter the docker container terminal, I could list all the action servers using the ros2 action list
command.
Could it be caused by it needs some time to recognize / discover the action server?
Thank you in advance
At the time of 70aa7c9
, bt_service_node.hpp
fails to build for galactic due to line 267,
future_response_ = service_client_->async_send_request(request).share();
In galactic, async_send_request()
returns a shared_future
, so further calling .share()
invokes an error (reference).
Removing the call to .share()
resolved the error.
Hi, I wanted to note that it can be desirable to throw rclcpp_action::exceptions::UnknownGoalHandleError
or at least any kind of exception on cancelGoal()
if the goal response has not arrived yet. In fact, I am relying on such a case in my application, because I want to make sure that the goal is canceled either way, so I wait until it has arrived and proceed with the cancellation. After merging #53, there is no way for me to detect if haltTree()
ran into a problem or not. Before I did like below:
try {
tree.haltTree();
} catch (const rclcpp_action::exceptions::UnknownGoalHandleError& e) {
// If tree is halted directly after an action started, the goal response might not have been
// received yet. In this case we assume the goal is about to arrive and asynchronously retry to HALT
}
I have a seperate ROS2 node that tries to halt the tree and doesn't know about when a RosActionNode published goal requests, so it is mandatory for this case to be detectable during halting the tree.
I am falling back to a previous commit, until this issue has been resolved.
Originally posted by @robin-mueller in #53 (comment)
Hey guys, anyone know if there could be a similar onFeedback function as in ROS2 OR I need to abandon BehaviorTree.ROS and implement Action(goal result feedback ,all by myself) by using BehaviorTree.CPP .
Thanks for any answer.
Hello @facontidavide ,
Firstly, i want to thank for your great work. I have found a bug in cancelGoal
function in bt_action_node.hpp
. When it's canceled goal, it's not waited that the goal gets result after finishing. This bug gives rise to exist the possibility of sending new goal without handling result_callback of corresponding goal. If you debug by putting following lines to according places, you understand the bug.
RCLCPP_INFO(node_->get_logger(), "RESULT_GOAL ID : %s", rclcpp_action::to_string(result.goal_id).c_str());
to line 332
RCLCPP_INFO(node_->get_logger(), "CANCEL_GOAL ID : %s", rclcpp_action::to_string(goal_handle_->get_goal_id()).c_str());
to line 431
I tried with your btcpp_ros2_samples. I run ros2 run btcpp_ros2_samples sleep_client
for my fixed version and your current version of this repo.
Your output:
PrintValue: start
[INFO] [1695293776.715275411] [sleep_client]: RESULT_GOAL ID : ef75e3cbdd7c226bb1c6e2cd36a0c236
[INFO] [1695293776.715445058] [sleep_client]: sleepA: onResultReceived. Done = true
PrintValue: sleep completed
[INFO] [1695293778.268966187] [sleep_client]: CANCEL_GOAL ID : dd28f1f6ab96854d84f85a2fdde2a2
[INFO] [1695293778.269641275] [sleep_client]: sleepB: onHalt
PrintValue: sleep aborted
PrintValue: start
[INFO] [1695293780.270740751] [sleep_client]: RESULT_GOAL ID : 1dcb1f40d8421bd4f824346bfb878b
[INFO] [1695293780.270829274] [sleep_client]: sleepA: onResultReceived. Done = true
PrintValue: sleep completed
[INFO] [1695293780.271580174] [sleep_client]: RESULT_GOAL ID : dd28f1f6ab96854d84f85a2fdde2a2
[INFO] [1695293781.773432366] [sleep_client]: CANCEL_GOAL ID : 2160659ffbfa6596de53aabe02fa9e5
[INFO] [1695293781.774070320] [sleep_client]: sleepB: onHalt
PrintValue: sleep aborted
PrintValue: start
[INFO] [1695293783.775155203] [sleep_client]: RESULT_GOAL ID : 956327c886842cca4da9a83ba9a375df
[INFO] [1695293783.775239360] [sleep_client]: sleepA: onResultReceived. Done = true
PrintValue: sleep completed
[INFO] [1695293783.775899932] [sleep_client]: RESULT_GOAL ID : 2160659ffbfa6596de53aabe02fa9e5
[INFO] [1695293785.275925308] [sleep_client]: CANCEL_GOAL ID : 45126ab2219b8e3861537d843c991
[INFO] [1695293785.276538092] [sleep_client]: sleepB: onHalt
PrintValue: sleep aborted
PrintValue: start
[INFO] [1695293787.277526354] [sleep_client]: RESULT_GOAL ID : dd451ef6592bf2b73c6935dc4cf70d6
[INFO] [1695293787.277612585] [sleep_client]: sleepA: onResultReceived. Done = true
PrintValue: sleep completed
[INFO] [1695293787.278468184] [sleep_client]: RESULT_GOAL ID : 45126ab2219b8e3861537d843c991
[INFO] [1695293788.783090360] [sleep_client]: CANCEL_GOAL ID : acbe325acc152b348c40c6a96178fd8c
[INFO] [1695293788.783685525] [sleep_client]: sleepB: onHalt
PrintValue: sleep aborted
PrintValue: start
[INFO] [1695293790.784665696] [sleep_client]: RESULT_GOAL ID : 3a38ae54fb365a81490d7737b49a8
[INFO] [1695293790.784733127] [sleep_client]: sleepA: onResultReceived. Done = true
PrintValue: sleep completed
[INFO] [1695293790.785492102] [sleep_client]: RESULT_GOAL ID : acbe325acc152b348c40c6a96178fd8c
[INFO] [1695293792.286093402] [sleep_client]: CANCEL_GOAL ID : 8688086cee0346310fbdc595213e
[INFO] [1695293792.286707982] [sleep_client]: sleepB: onHalt
PrintValue: sleep aborted
My Output:
PrintValue: start
[INFO] [1695295344.142244774] [sleep_client]: RESULT_GOAL ID : f9f6d789aa5114b51857d44126bc662
[INFO] [1695295344.142411571] [sleep_client]: sleepA: onResultReceived. Done = true
PrintValue: sleep completed
[INFO] [1695295345.643276985] [sleep_client]: CANCEL_GOAL ID : e5261931333755dc253252dfb2e861d9
[INFO] [1695295345.743568867] [sleep_client]: RESULT_GOAL ID : e5261931333755dc253252dfb2e861d9
[INFO] [1695295345.743714297] [sleep_client]: sleepB: onHalt
PrintValue: sleep aborted
PrintValue: start
[INFO] [1695295347.744981916] [sleep_client]: RESULT_GOAL ID : c2baf96cb5b79a7674a94352a087383f
[INFO] [1695295347.745074847] [sleep_client]: sleepA: onResultReceived. Done = true
PrintValue: sleep completed
[INFO] [1695295349.264379159] [sleep_client]: CANCEL_GOAL ID : 5454cc3c188455c138a998b5d9280
[INFO] [1695295349.346670567] [sleep_client]: RESULT_GOAL ID : 5454cc3c188455c138a998b5d9280
[INFO] [1695295349.346815698] [sleep_client]: sleepB: onHalt
PrintValue: sleep aborted
PrintValue: start
[INFO] [1695295351.348284469] [sleep_client]: RESULT_GOAL ID : 1ab7cf57e54120eaf0fceeddd65562c4
[INFO] [1695295351.348366768] [sleep_client]: sleepA: onResultReceived. Done = true
PrintValue: sleep completed
[INFO] [1695295352.868317555] [sleep_client]: CANCEL_GOAL ID : f276a469da4f1365db2cd3bc79961469
[INFO] [1695295352.949544723] [sleep_client]: RESULT_GOAL ID : f276a469da4f1365db2cd3bc79961469
[INFO] [1695295352.949629849] [sleep_client]: sleepB: onHalt
PrintValue: sleep aborted
PrintValue: start
[INFO] [1695295354.950950643] [sleep_client]: RESULT_GOAL ID : e99b9b5b9c75a854f22234197362ff9
[INFO] [1695295354.951044802] [sleep_client]: sleepA: onResultReceived. Done = true
PrintValue: sleep completed
[INFO] [1695295356.471232107] [sleep_client]: CANCEL_GOAL ID : 69b57badefdb26a65bafeaca7b2a4537
[INFO] [1695295356.552313938] [sleep_client]: RESULT_GOAL ID : 69b57badefdb26a65bafeaca7b2a4537
[INFO] [1695295356.552409254] [sleep_client]: sleepB: onHalt
PrintValue: sleep aborted
PrintValue: start
[INFO] [1695295358.553869406] [sleep_client]: RESULT_GOAL ID : eee66aede6adfc71a2326b49d98fd677
[INFO] [1695295358.553942439] [sleep_client]: sleepA: onResultReceived. Done = true
PrintValue: sleep completed
[INFO] [1695295360.058143973] [sleep_client]: CANCEL_GOAL ID : 6cafc4d991f0ff796b338252e123618
[INFO] [1695295360.155102960] [sleep_client]: RESULT_GOAL ID : 6cafc4d991f0ff796b338252e123618
[INFO] [1695295360.155213032] [sleep_client]: sleepB: onHalt
PrintValue: sleep aborted
Shortly, Without waiting result, it cancels goal with new id, but runs result_callback with previous id.
In addition, i have several questions for you:
emitWakeUpSignal
in result_callback and feedback? I think you use for preventing race condition etc. But i would love to hear from you as well.goal_handle_.goal_id == result.goal_id
after this fix. I haven't been sure. Could you inform me about it? Because it looks like navigation2 stack is little bit sufferer by this issue.If all of it is problem or bug, i will pull request to ros-planning/navigation2 stack too.
Thank you in advance.
Currently to use topics in BT I wirte up a simple sub node and expose needed members as ports. This works, however after a while there are a lot of similiar code.
Is it possible to make this generic?
Also it would be nice to have generic port types for topic types. To this, to_yaml
functions could be used. For example
multi_uav_bt_nodes::Pose msg;
msg.position.x = 10;
msg.position.y = 10;
msg.position.z = 10;
EXPECT_EQ( geometry_msgs::msg::to_yaml(msg,true), "{position: {x: 10.0000, y: 10.0000, z: 10.0000}, orientation: {x: 0.00000, y: 0.00000, z: 0.00000, w: 1.00000}}" );
couldnt find how to parse it but probobly some function rosidl will work.
Also for reference my sub nodes looks like this:
using Pose = geometry_msgs::msg::Pose;
class ListenPoseAction : public BT::RosTopicSubNode<Pose>
{
public:
ListenPoseAction(
const std::string & name,
const BT::NodeConfig & conf,
const BT::RosNodeParams & params)
: BT::RosTopicSubNode<Pose>(name, conf, params) {}
static BT::PortsList providedPorts();
BT::NodeStatus onTick(const typename Pose::SharedPtr & last_msg) override;
virtual ~ListenPoseAction();
};
BT::PortsList ListenPoseAction::providedPorts()
{
return providedBasicPorts(
{
BT::OutputPort<double>("X"),
BT::OutputPort<double>("Y"),
BT::OutputPort<double>("Z")});
}
BT::NodeStatus ListenPoseAction::onTick(const typename Pose::SharedPtr & last_msg)
{
if (!last_msg) {
return BT::NodeStatus::FAILURE;
}
setOutput("X", last_msg->position.x);
setOutput("Y", last_msg->position.y);
setOutput("Z", last_msg->position.z);
return BT::NodeStatus::SUCCESS;
}
ListenPoseAction::~ListenPoseAction()
{
}
note that ony the first line of the hpp (which could be a template variable) and code related to ports (which would be the generic port) change for different ros topics
When creating a custom RosActionNode and overriding the onFeedback method, the method is never called. Here is an example:
The .hpp file
#pragma once
#include <behaviortree_ros2/bt_action_node.hpp>
#include <iostream>
#include "behaviortree_cpp/action_node.h"
#include "dictator_behavior_tree/pose_to_string.hpp"
#include "geometry_msgs/msg/pose.hpp"
#include "nav2_msgs/action/navigate_to_pose.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
using Nav2 = nav2_msgs::action::NavigateToPose;
namespace BT
{
class NavigateToPose : public RosActionNode<Nav2>
{
public:
NavigateToPose(const std::string &name, const NodeConfig &conf, const RosNodeParams ¶ms)
: RosActionNode<Nav2>(name, conf, params)
{
}
static PortsList providedPorts()
{
return providedBasicPorts({InputPort<Position2D>("goal_pose")});
}
bool setGoal(RosActionNode::Goal &goal) override;
NodeStatus onResultReceived(const WrappedResult &wr) override;
NodeStatus onFailure(ActionNodeErrorCode error) override;
NodeStatus onFeedback(const std::shared_ptr<const Feedback> feedback) override;
};
} // namespace BT
the implemented method:
NodeStatus NavigateToPose::onFeedback(const std::shared_ptr<const Feedback> feedback)
{
std::cout << "FEEDBACK" << std::endl;
std::stringstream ss;
ss << "Estimated remaining time: " << feedback->estimated_time_remaining.sec << " ";
std::cout << ss.str() << std::endl;
return NodeStatus::RUNNING;
}
With this example, I never get an output to the terminal for the received feedback. I also validated that the action does provide a feedback by listening to the action feedback topic ros2 topic echo /navigate_to_pose/_action/feedback
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.