BehaviorTree.CPP
BehaviorTree.CPP copied to clipboard
header files and cpp files
Hello,
I'm a beginner when it comes to behavior trees and I'm having issues when splitting the declaration of BT nodes between hpp files and cpp files.
1st case:
- Only with one hpp file and a bt_factory_node
movebase_node.hpp:
#ifndef __MOVEBASE_BT_NODE_HPP_
#define __MOVEBASE_BT_NODE_HPP_
#include "behaviortree_cpp_v3/action_node.h"
// Custom type
struct Pose2D
{
double x, y, theta;
};
inline void SleepMS(int ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
namespace BT
{
// This template specialization is needed only if you want
// to AUTOMATICALLY convert a NodeParameter into a Pose2D
// In other words, implement it if you want to be able to do:
//
// TreeNode::getInput<Pose2D>(key, ...)
//
template <> inline Pose2D convertFromString(StringView key)
{
// three real numbers separated by semicolons
auto parts = BT::splitString(key, ';');
if (parts.size() != 3)
{
throw BT::RuntimeError("invalid input)");
}
else
{
Pose2D output;
output.x = convertFromString<double>(parts[0]);
output.y = convertFromString<double>(parts[1]);
output.theta = convertFromString<double>(parts[2]);
return output;
}
}
} // end namespace BT
// This is an asynchronous operation that will run in a separate thread.
// It requires the input port "goal".
class MoveBaseAction : public BT::AsyncActionNode
{
public:
// Any TreeNode with ports must have a constructor with this signature
MoveBaseAction(const std::string &name, const BT::NodeConfiguration &config)
: AsyncActionNode(name, config)
{
}
// It is mandatory to define this static method.
static BT::PortsList providedPorts()
{
return {BT::InputPort<Pose2D>("goal")};
}
BT::NodeStatus tick() override
{
Pose2D goal;
if (!getInput<Pose2D>("goal", goal))
{
throw BT::RuntimeError("missing required input [goal]");
}
printf("[ MoveBase: STARTED ]. goal: x=%.f y=%.1f theta=%.2f\n", goal.x, goal.y, goal.theta);
_halt_requested.store(false);
int count = 0;
// Pretend that "computing" takes 250 milliseconds.
// It is up to you to check periodicall _halt_requested and interrupt
// this tick() if it is true.
while (!_halt_requested && count++ < 25)
{
SleepMS(10);
}
std::cout << "[ MoveBase: FINISHED ]" << std::endl;
return _halt_requested ? BT::NodeStatus::FAILURE : BT::NodeStatus::SUCCESS;
}
virtual void halt() override
{
_halt_requested.store(true);
}
private:
std::atomic_bool _halt_requested;
};
#endif // MOVEBASE_BT_NODE_H
bt_factory_node.cpp:
#include "behaviortree_cpp_v3/bt_factory.h"
#include "battery_interface.hpp"
#include "say_something.hpp"
#include "movebase_node.hpp"
int main()
{
BT::BehaviorTreeFactory factory;
factory.registerSimpleCondition("BatteryOK", std::bind(CheckBattery));
factory.registerNodeType<MoveBaseAction>("MoveBase");
factory.registerNodeType<SaySomething>("SaySomething");
auto tree = factory.createTreeFromFile("/home/rythm/thesis_ws/src/BT_example/xml/simple_sequence.xml");
BT::NodeStatus status;
std::cout << "\n--- 1st executeTick() ---" << std::endl;
status = tree.tickRoot();
SleepMS(150);
std::cout << "\n--- 2nd executeTick() ---" << std::endl;
status = tree.tickRoot();
SleepMS(150);
std::cout << "\n--- 3rd executeTick() ---" << std::endl;
status = tree.tickRoot();
std::cout << std::endl;
return 0;
}
Result: It runs perfectly as it should
############################################################### 2nd case: Slip between hpp and cpp files.
movebase_node.hpp:
#ifndef __MOVEBASE_BT_NODE_HPP_
#define __MOVEBASE_BT_NODE_HPP_
#include "behaviortree_cpp_v3/action_node.h"
// Custom type
struct Pose2D
{
double x, y, theta;
};
inline void SleepMS(int ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
namespace BT
{
// This template specialization is needed only if you want
// to AUTOMATICALLY convert a NodeParameter into a Pose2D
// In other words, implement it if you want to be able to do:
//
// TreeNode::getInput<Pose2D>(key, ...)
//
template <> inline Pose2D convertFromString(StringView key)
{
// three real numbers separated by semicolons
auto parts = BT::splitString(key, ';');
if (parts.size() != 3)
{
throw BT::RuntimeError("invalid input)");
}
else
{
Pose2D output;
output.x = convertFromString<double>(parts[0]);
output.y = convertFromString<double>(parts[1]);
output.theta = convertFromString<double>(parts[2]);
return output;
}
}
} // end namespace BT
// This is an asynchronous operation that will run in a separate thread.
// It requires the input port "goal".
class MoveBaseAction : public BT::AsyncActionNode
{
public:
// Any TreeNode with ports must have a constructor with this signature
MoveBaseAction(const std::string &name, const BT::NodeConfiguration &config)
: AsyncActionNode(name, config)
{
}
// It is mandatory to define this static method.
static BT::PortsList providedPorts()
{
return {BT::InputPort<Pose2D>("goal")};
}
BT::NodeStatus tick() override;
virtual void halt() override;
private:
std::atomic_bool _halt_requested;
};
#endif // MOVEBASE_BT_NODE_H
movebase_node.cpp:
#include "movebase_node.hpp"
#include "behaviortree_cpp_v3/bt_factory.h"
// This function must be implemented in the .cpp file to create
// a plugin that can be loaded at run-time
BT_REGISTER_NODES(factory)
{
factory.registerNodeType<MoveBaseAction>("MoveBase");
}
BT::NodeStatus MoveBaseAction::tick()
{
Pose2D goal;
if (!getInput<Pose2D>("goal", goal))
{
throw BT::RuntimeError("missing required input [goal]");
}
printf("[ MoveBase: STARTED ]. goal: x=%.f y=%.1f theta=%.2f\n", goal.x, goal.y, goal.theta);
_halt_requested.store(false);
int count = 0;
// Pretend that "computing" takes 250 milliseconds.
// It is up to you to check periodicall _halt_requested and interrupt
// this tick() if it is true.
while (!_halt_requested && count++ < 25)
{
SleepMS(10);
}
std::cout << "[ MoveBase: FINISHED ]" << std::endl;
return _halt_requested ? BT::NodeStatus::FAILURE : BT::NodeStatus::SUCCESS;
}
void MoveBaseAction::halt()
{
_halt_requested.store(true);
}
The bt_factory_node.cpp is the same.
Result: When I compile it throws the following error:
/usr/bin/ld: CMakeFiles/bt_mission_node.dir/src/bt_mission_node.cpp.o: in function MoveBaseAction::MoveBaseAction(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, BT::NodeConfiguration const&)': bt_mission_node.cpp:(.text._ZN14MoveBaseActionC2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKN2BT17NodeConfigurationE[_ZN14MoveBaseActionC5ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKN2BT17NodeConfigurationE]+0x32): undefined reference to 'vtable for MoveBaseAction' collect2: error: ld returned 1 exit status make[2]: *** [BT_example/CMakeFiles/bt_mission_node.dir/build.make:107: /home/rythm/thesis_ws/devel/lib/BT_example/bt_mission_node] Error 1 make[1]: *** [CMakeFiles/Makefile2:808: BT_example/CMakeFiles/bt_mission_node.dir/all] Error 2 make: *** [Makefile:141: all] Error 2 Invoking "make -j16 -l16" failed
Can anyone explain to me what I am doing wrong, please?
Thank you for your help!
I need the entire project or a subset that reproduce the error to be sure. Can you share it with me?
Please provide a project I can compile and run, or I will close this issue
Closing this for inactivity. Since your error was undefined reference , it is most probably an issue in the cmake configuration