当前位置:   article > 正文

BehaviorTree + Groot 在ros中的运用_ros移动机器人行为决策树

ros移动机器人行为决策树

 一、BehaviorTree + Groot 在ros中的运用的参考资料

1、BehaviorTree.cpp开源库

地址:github.com/BehiaviorTree/BehaviorTree.CPP

提供BehaviorTree框架,提供examples学习,但是相对比较零碎,需要对BehaviorTree有一个初步了解

2、BehaviorTree主要概念、API与教程

地址:BehaviorTree.CPP

对开源库有一些讲解,相对清晰

3、古月居小明工坊的 《ROS实验 | 行为树实现机器人智能》
网页链接:ROS实验 | 行为树实现机器人智能 - 古月居

二、BehaviorTree + Groot 在ros中的运用基础

1、运行过程

  1. 一个名为“tick”的信号被发送到树的根部,并在树中传播,直到到达叶节点。

  2. 接收tick信号的树节点执行其callback。此回调必须返回。

    SUCCESS,FAILURE or RUNNING

  3. 如果一个TreeNode有一个或多个子节点,它将根据其状态、外部参数或前一个同级节点的结果来顺序tick他们。

  4. LeafNode,即那些没有任何子节点的TreeNode,是实际的命令,即行为树与系统其余部分交互的地方。Actions节点是最常见的叶节点类型。

2、tick的运行原理

Sequence 处在运行中, tick 传到 DetectObject, 返回SUCCESS,tick传到GraspObject,返回SUCCESS,子节点全部完成,父节点Sequence状态变成SUCCESS。

其余详细资料请参考提供的参考资料

三、调试工具

1、groot

Groot是与BehaviorTree.CPP搭配使用的工具,分为Editor、Monitor、Log Replay 3种模式,具有行为树编辑、状态监控、历史log回放等功能。

指南:Groot - Interacting with Behavior Trees — Navigation 2 1.0.0 documentation

2、StdCoutLogger

作用:在终端打印行为树中的节点执行状态变化。

代码仅需在加载tree后添加StdCoutLogger类的1个实例(且只能有1个实例),运行效果如下:

StdCoutLogger logger_cout(tree);

四、实践

        理论的东西看起来容易,但离实践还有一定距离,接下来用一个实践小项目来对BehaviorTree + Groot + Ros进行演示,项目题目来源于古月居,实现一个巡逻的小乌龟的游戏

Groot下的行为树如下图

运行过程

1、attack子节点haveEnemy判断周围是否有敌人。

2、若没有敌人,Ation节点moveToEnemy将不被触发,守卫将执行patrol节点,对区域进行搜索。

3、若找到敌人,moveToEnemy节点将被触发,守卫向敌人前进。

1、小乌龟生成与tf发布

此部分不再详细描述,代码如下

  1. void poseCallback(const turtlesim::PoseConstPtr& msg)
  2. {
  3. // tf广播器
  4. static tf::TransformBroadcaster br;
  5. // 根据乌龟当前的位姿,设置相对于世界坐标系的坐标变换
  6. tf::Transform transform;
  7. transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) );
  8. tf::Quaternion q;
  9. q.setRPY(0, 0, msg->theta);
  10. transform.setRotation(q);
  11. // 发布坐标变换
  12. br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));
  13. }
  14. int main(int argc, char** argv)
  15. {
  16. // 初始化节点
  17. ros::init(argc, argv, "my_tf_broadcaster");
  18. if (argc != 2)
  19. {
  20. ROS_ERROR("need turtle name as argument");
  21. return -1;
  22. };
  23. turtle_name = argv[1];
  24. // 订阅乌龟的pose信息
  25. ros::NodeHandle node;
  26. ros::Subscriber sub = node.subscribe(turtle_name+"/pose", 10, &poseCallback);
  27. ros::spin();
  28. return 0;
  29. };
  1. void poseCallback(const turtlesim::PoseConstPtr& msg)
  2. {
  3. // tf广播器
  4. static tf::TransformBroadcaster br;
  5. // 根据乌龟当前的位姿,设置相对于世界坐标系的坐标变换
  6. tf::Transform transform;
  7. transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) );
  8. tf::Quaternion q;
  9. q.setRPY(0, 0, msg->theta);
  10. transform.setRotation(q);
  11. // 发布坐标变换
  12. br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));
  13. // 发布固定点坐标
  14. tf::Transform transform_a;
  15. transform_a.setOrigin(tf::Vector3(1.0, 6.0, 0.0));
  16. tf::Quaternion q_a;
  17. q_a.setRPY(0.0, 0.0, 0.0);
  18. transform_a.setRotation(q_a);
  19. br.sendTransform(tf::StampedTransform(transform_a, ros::Time::now(), "world", "point_a"));
  20. tf::Transform transform_b;
  21. transform_b.setOrigin(tf::Vector3(10.0, 6.0, 0.0));
  22. tf::Quaternion q_b;
  23. q_b.setRPY(0.0, 0.0, 0.0);
  24. transform_b.setRotation(q_b);
  25. br.sendTransform(tf::StampedTransform(transform_b, ros::Time::now(), "world", "point_b"));
  26. }
  27. int main(int argc, char** argv)
  28. {
  29. // 初始化节点
  30. ros::init(argc, argv, "my_tf_broadcaster");
  31. if (argc != 2)
  32. {
  33. ROS_ERROR("need turtle name as argument");
  34. return -1;
  35. };
  36. turtle_name = argv[1];
  37. // 订阅乌龟的pose信息
  38. ros::NodeHandle node;
  39. ros::Subscriber sub = node.subscribe(turtle_name+"/pose", 10, &poseCallback);
  40. // 通过服务调用,产生第二只乌龟turtle2
  41. ros::service::waitForService("spawn");
  42. ros::ServiceClient add_turtle =
  43. node.serviceClient<turtlesim::Spawn>("spawn");
  44. turtlesim::Spawn srv;
  45. add_turtle.call(srv);
  46. ros::spin();
  47. return 0;
  48. };

2、Action节点构建

        节点构建以静态库的方式进行构建,构建的类需要对behaviorTree的类进行继承,巡逻节点类构建如下

  1. class BTActionPatrol : public BT::AsyncActionNode
  2. {
  3. protected:
  4. ros::NodeHandle nh_;
  5. // 定义turtle2的速度控制发布器
  6. ros::Publisher turtle_vel =
  7. nh_.advertise<geometry_msgs::Twist>("turtle2/cmd_vel", 10);
  8. public:
  9. BTActionPatrol(const std::string& name, const BT::NodeConfiguration& config)
  10. :BT:: AsyncActionNode(name, config)
  11. {
  12. }
  13. ~BTActionPatrol(void)
  14. {}
  15. BT::NodeStatus tick() override;
  16. static BT::PortsList providedPorts()
  17. {
  18. return{ BT::InputPort<std::string>("message") };
  19. }
  20. virtual void halt() override;
  21. };

publisher与providedPorts定义本不应该放头文件里面。

在构建节点时需要注意重写 tick 与 halt函数,需要提供static BT::PortsList providedPorts接口函数

重写的tick函数如下,主要操作在这里实现。

  1. BT::NodeStatus BTActionPatrol::tick()
  2. {
  3. tf::TransformListener listener;
  4. tf::StampedTransform transform_a, transform_b, transform;
  5. // 查找坐标变换
  6. listener.waitForTransform("/turtle2", "/point_a", ros::Time(0), ros::Duration(3.0));
  7. listener.lookupTransform("/turtle2", "/point_a", ros::Time(0), transform_a);
  8. listener.waitForTransform("/turtle2", "/point_b", ros::Time(0), ros::Duration(3.0));
  9. listener.lookupTransform("/turtle2", "/point_b", ros::Time(0), transform_b);
  10. double distance_a, distance_b;
  11. distance_a = sqrt(pow(transform_a.getOrigin().x(), 2) + pow(transform_a.getOrigin().y(), 2));
  12. distance_b = sqrt(pow(transform_b.getOrigin().x(), 2) + pow(transform_b.getOrigin().y(), 2));
  13. if (nh_.hasParam("/goal_point")){
  14. if(distance_a < 0.5) {
  15. nh_.setParam("/goal_point", "b");
  16. ROS_INFO("Change direction to b");
  17. }
  18. else if(distance_b < 0.5){
  19. nh_.setParam("/goal_point", "a");
  20. ROS_INFO("Change direction to b");
  21. }
  22. }
  23. else{
  24. nh_.setParam("/goal_point", "a");
  25. }
  26. std::string direction;
  27. nh_.getParam("/goal_point", direction);
  28. if(direction == "a"){
  29. ROS_INFO("Nav to a");
  30. transform = transform_a;
  31. }
  32. else if(direction == "b"){
  33. ROS_INFO("Nav to b");
  34. transform = transform_b;
  35. }
  36. // 根据turtle1和turtle2之间的坐标变换,计算turtle2需要运动的线速度和角速度
  37. // 并发布速度控制指令,使turtle2向turtle1移动
  38. geometry_msgs::Twist vel_msg;
  39. vel_msg.angular.z = 1.0 * atan2(transform.getOrigin().y(),
  40. transform.getOrigin().x());
  41. // vel_msg.linear.x = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) +
  42. // pow(transform.getOrigin().y(), 2));
  43. vel_msg.linear.x = 0.8;
  44. turtle_vel.publish(vel_msg);
  45. ROS_INFO("Action nav_b is successful!");
  46. return BT::NodeStatus::SUCCESS;
  47. }

moveToEnemy节点与上一节点一样,继承Action类,判断是否有enemy的节点为条件节点,继承Condition类详细代码将不在此处详写

3、构建树

使用xml文件构建行为树,代码如下

  1. <root main_tree_to_execute = "DoorClosed">
  2. <BehaviorTree ID="DoorClosed">
  3. <Fallback name="playGame">
  4. <Sequence name="attack">
  5. <haveEnemy/>
  6. <moveToEnemy/>
  7. </Sequence>
  8. <patrol/>
  9. </Fallback>
  10. </BehaviorTree>
  11. </root>

载入节点

  1. BehaviorTreeFactory factory;
  2. factory.registerNodeType<BTActionNav>("moveToEnemy");
  3. factory.registerNodeType<BTActionPatrol>("patrol");
  4. factory.registerNodeType<BTActionHaveEnemy>("haveEnemy");
  5. auto tree = factory.createTreeFromText(xml_text);

添加Groot调试

PublisherZMQ publisher_zmq(tree);

4、修改CmakeLists

构建静态库

  1. add_library(sample_nodes STATIC
  2. src/treeNode/action_nav_enemy.cpp
  3. src/treeNode/action_patrol.cpp
  4. src/treeNode/condition_have_enemy.cpp
  5. )
  6. target_link_libraries(sample_nodes
  7. ${catkin_LIBRARIES}
  8. BT::behaviortree_cpp_v3)

 添加可执行文件

  1. add_executable(guard_robot_tree src/tree/guard_robot_tree.cpp)
  2. target_link_libraries(
  3. guard_robot_tree
  4. ${catkin_LIBRARIES}
  5. BT::behaviortree_cpp_v3
  6. sample_nodes
  7. # /opt/ros/melodic/lib/librosconsole.so
  8. # /opt/ros/melodic/lib/libroscpp_serialization.so
  9. )

5、运行效果

五、总结

对BehaviorTree进行了学习,还有很多功能有待开发,如节点间的通讯,由可以使用ros话题通讯代替,暂时不需要,后续有新内容还会进行更新,对于文章中的错误希望大家一起讨论。

六、参考

BT9:各种调试工具介绍_linxigjs的博客-CSDN博客

ROS实验 | 行为树实现机器人智能 - 古月居

Behavior-tree 在ROS中的应用(入门)_bug有点多的博客-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/400967?site
推荐阅读
相关标签
  

闽ICP备14008679号