move_base代码解析(一)MoveBase::executeCb

news2024/11/17 21:47:17

move_base是ROS中的经典路径规划算法,move_base 功能包提供了基于动作(action)的路径规划实现,move_base 可以根据给定的目标点,控制机器人底盘运动至目标位置,并且在运动过程中会连续反馈机器人自身的姿态与目标点的状态信息。

1、goalCB

move_base的主函数定义在move_base_node.cpp中,具体的对外publish以及subscriber实现在move_base.cpp中。对于move_base而言,它的输入一般只需要一个目标点就可以了:

goal_sub_ = simple_nh.subscribe<geometry_msgs::PoseStamped>("goal", 1, boost::bind(&MoveBase::goalCB, this, _1));

这是一个订阅函数,传入的是goal,主要作用是为rviz等提供调用借口,将geometry_msgs::PoseStamped形式的goal转换成move_base_msgs::MoveBaseActionGoal,再发布到对应类型的goal话题中。这个函数很简单,只是入口,并不是实际执行的核心函数。它的做的事情是将传入的目标点坐标封装成actionlib的goal。真正的执行函数是在构造函数里,actionlib server注册的executeCb()这个函数。

2、executeCB

executeCB函数定义在这里:

    //actionserver实例化一个对象,一直执行回调函数executeCb。
    //Action servrer接收外部的目标请求,驱动整个的路径规划和移动过程。
    //actionlib会启动一个线程,当外部请求到来时,调用MoveBase::executeCb回调函数处理
    as_ = new MoveBaseActionServer(ros::NodeHandle(), "move_base", boost::bind(&MoveBase::executeCb, this, _1), false);

接下来实例化函数主要分为以下部分:

2.1、判断四元数的合法性

executeCB函数首先会判断传入位姿的四元数的合法性,如果四元数不对就退出。

    //位姿的四元数合法性检查,如果四元数不对就返回
    if(!isQuaternionValid(move_base_goal->target_pose.pose.orientation)){
      as_->setAborted(move_base_msgs::MoveBaseResult(), "Aborting on goal because it was sent with an invalid quaternion");
      return;
    }

四元数的判断函数如下:

bool MoveBase::isQuaternionValid(const geometry_msgs::Quaternion& q){
    //first we need to check if the quaternion has nan's or infs
    //1、首先检查四元数是否元素完整
    //isfinite()用于检查并返回给定数是否为有限数的函数,有限数是既不是无限也不是NaN(不是数字)的任何浮点数。 
    if(!std::isfinite(q.x) || !std::isfinite(q.y) || !std::isfinite(q.z) || !std::isfinite(q.w)){
      ROS_ERROR("Quaternion has nans or infs... discarding as a navigation goal");
      return false;
    }
    //2、检查四元数是否趋近于0
    tf2::Quaternion tf_q(q.x, q.y, q.z, q.w);

    //next, we need to check if the length of the quaternion is close to zero
    if(tf_q.length2() < 1e-6){//四元数的长度不能为0
      ROS_ERROR("Quaternion has length close to zero... discarding as navigation goal");
      return false;
    }

    //next, we will normalize the quaternion and check that it transforms the vertical vector correctly
    //3、对四元数规范化,转化为vector
    tf_q.normalize();

    tf2::Vector3 up(0, 0, 1);

    double dot = up.dot(up.rotate(tf_q.getAxis(), tf_q.getAngle()));
    //四元数的Z轴必须是垂直的
    if(fabs(dot - 1) > 1e-3){
      ROS_ERROR("Quaternion is invalid... for navigation the z-axis of the quaternion must be close to vertical.");
      return false;
    }

    return true;
  }

它的判断主要分为了三个部分:
1、首先检查四元数是否元素完整
2、检查四元数是否趋近于0
3、对四元数规范化,转化为vector,判断四元数的Z轴必须是垂直的

2.2、将目标的坐标系统一转换为全局坐标系

确认四元数没有问题以后,需要将当前坐标转换到世界坐标系下:

geometry_msgs::PoseStamped goal = goalToGlobalFrame(move_base_goal->target_pose);

主要思路是调用tf变换,根据当前的pose的frame_id以及世界坐标系的frame_id将当前坐标系下的坐标转换成世界坐标系下的坐标。

2.3、将速度置零

这里简单的通过调用一个publishZeroVelocity函数将机器人输出的速度设置为0

2.4、设置目标点并唤醒路径规划线程

前面123步是准备,第4步开始就是正式的处理路径规划事宜了:

    boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_);//给该线程上锁
    planner_goal_ = goal;//传入目标并开启线程
    runPlanner_ = true;
    planner_cond_.notify_one();
    lock.unlock();//打开互斥锁

2.5、然后发布goal,设置控制频率

    //发布当前的目标坐标
    current_goal_pub_.publish(goal);
    //设置了一个频率,后面会用到
    ros::Rate r(controller_frequency_);

2.6、开启costmap更新

    if(shutdown_costmaps_){//如果代价地图被关闭,似乎在move_base中如果机器人长时间不运动的话代价地图会被关闭以节省资源
      ROS_DEBUG_NAMED("move_base","Starting up costmaps that were shut down previously");
      planner_costmap_ros_->start();
      controller_costmap_ros_->start();
    }

在move_base中,为了节省资源,代价地图似乎不是一直开着的。如果被关闭的话,每次路径规划前需要重新判断以下是否为开启状态,没有开启的话需要开启一下。

2.7、重置时间标志位

    //记录几个时间,最新的控制时间,上一次有效的规划,上一次最后的碰撞重置
    last_valid_control_ = ros::Time::now();
    last_valid_plan_ = ros::Time::now();
    last_oscillation_reset_ = ros::Time::now();
    planning_retries_ = 0;

这几个时间可能后面会用到

在完成以上几个步骤之后,程序会进入一个while循环,在这个循环中执行了以下步骤:

2.8、修改循环频率

      //如果频率被改变的话
      if(c_freq_change_)
      {
        ROS_INFO("Setting controller frequency to %.2f", controller_frequency_);
        r = ros::Rate(controller_frequency_);//修改频率
        c_freq_change_ = false;
      }

2.9、判断是否有抢占式目标以及目标点是否可用

if(as_->isPreemptRequested())
  if(as_->isNewGoalAvailable()){//是否有新的目标点
    //如果有新的目标,会接受的,但不会关闭其他进程
    move_base_msgs::MoveBaseGoal new_goal = *as_->acceptNewGoal();//接受新的目标点,这个同样是simple_action_server.h的函数
          

isPreemptRequested由actionlib/server/simple_action_server.h实现。ROS中对于action的抢占是否允许这个是怎么判定的?这个函数的返回可能是true也可能是false

2.10、判断四元数的合法性

同2.1,但是由于有新的goal所以需要重新再判断一次

            if(!isQuaternionValid(new_goal.target_pose.pose.orientation)){
            //如果四元数不合法,中断目标并返回
            as_->setAborted(move_base_msgs::MoveBaseResult(), "Aborting on goal because it was sent with an invalid quaternion");
            return;
          }

2.11、将目标的坐标系统一转换为全局坐标系

同2.2,但是由于有新的goal所以需要重新再判断一次

          //将目标位置转换到global坐标系下(geometry_msgs形式)
          goal = goalToGlobalFrame(new_goal.target_pose);

2.12、设置状态为PLANNING

move_base的状态机有三种,PLANNING、CONTROLLING以及CLEARING。规划时的状态为PLANNING

          //enum MoveBaseState {
          //  PLANNING,
          //  CONTROLLING,
          //  CLEARING
          //};move_base的状态,状态改变为PLANNING全局路径规划状态
          state_ = PLANNING;

2.13、设置目标点并唤醒路径规划线程

同2.4,但是由于有新的goal所以需要重新再执行一次

2.14、把goal发布给可视化工具

同2.5,但是由于有新的goal所以需要重新再执行一次

          //publish the goal point to the visualizer
          ROS_DEBUG_NAMED("move_base","move_base has received a goal of x: %.2f, y: %.2f", goal.pose.position.x, goal.pose.position.y);
          current_goal_pub_.publish(goal);//发布新的目标点的坐标

2.15、重置规划时间

同2.7,但是由于有新的goal所以需要重新再执行一次

          //make sure to reset our timeouts and counters
          //重新记录几个时间
          last_valid_control_ = ros::Time::now();
          last_valid_plan_ = ros::Time::now();
          last_oscillation_reset_ = ros::Time::now();
          planning_retries_ = 0;

2.16、重置状态,设置为抢占式任务

这里的判断是对应2.9中的第二个if判断:if(as_->isNewGoalAvailable())。如果当前没有新的目标点可用时,需要重置当前的状态机,以及允许任务抢占:

        else {//没有发现新的目标点
          //if we've been preempted explicitly we need to shut things down
          resetState();//重置状态

          //notify the ActionServer that we've successfully preempted
          ROS_DEBUG_NAMED("move_base","Move base preempting the current goal");
          as_->setPreempted();//允许新的目标抢占

          //we'll actually return from execute after preempting
          return;
        }

2.17、如果目标点的坐标系和全局地图的坐标系不相同

虽然前面进行过一次tf变换,但是在进行路径规划之前还是需要重新判断一下goal是否已经转换成功了,如果之前的tf变换没有成功的话需要重新处理,这里重新处理的流程前面都有同样的函数就不展开讲了。

      //再判断一次目标点的frame_id,需要确认目标点是在世界坐标系下的。如果目标点不在世界坐标系下
      if(goal.header.frame_id != planner_costmap_ros_->getGlobalFrameID()){
        goal = goalToGlobalFrame(goal);//需要降目标点转到世界坐标系下

        //we want to go back to the planning state for the next execution cycle
        recovery_index_ = 0;//recovery重试次数设置为0
        state_ = PLANNING;//修改机器人的状态

        //we have a new goal so make sure the planner is awake
        lock.lock();
        planner_goal_ = goal;//传入目标并开启线程
        runPlanner_ = true;
        planner_cond_.notify_one();
        lock.unlock();

        //publish the goal point to the visualizer
        ROS_DEBUG_NAMED("move_base","The global frame for move_base has changed, new frame: %s, new goal position x: %.2f, y: %.2f", goal.header.frame_id.c_str(), goal.pose.position.x, goal.pose.position.y);
        current_goal_pub_.publish(goal);//发布新的目标

        //make sure to reset our timeouts and counters
        //更新几个时间
        last_valid_control_ = ros::Time::now();
        last_valid_plan_ = ros::Time::now();
        last_oscillation_reset_ = ros::Time::now();
        planning_retries_ = 0;
      }

2.18、调用和计算全局路径,并进行局部规划计算

这里才是真正路径规划执行的地方,executeCB是路径规划的启动函数,而executeCycle是路径规划的执行函数,后面这个单独展开讲。它的输入是目标点,输出是bool变量代表是否到达目标点。

      bool done = executeCycle(goal);
      //if we're done, then we'll return from execute
      //如果完成任务则返回
      if(done)
        return;

2.19、控制时间以及控制频率

这里会循环输出当前的控制周期,如果控制频率过高的话会输出一个警告信息,之前自己做仿真的时候就遇到过这个问题,当时控制频率高于实际运算频率的时候终端会一直报警告:
在这里插入图片描述

      ros::WallDuration t_diff = ros::WallTime::now() - start;
      ROS_DEBUG_NAMED("move_base","Full control cycle time: %.9f\n", t_diff.toSec());

      r.sleep();
      //make sure to sleep for the remainder of our cycle time
      //一个关于控制周期的警告
      if(r.cycleTime() > ros::Duration(1 / controller_frequency_) && state_ == CONTROLLING)
        ROS_WARN("Control loop missed its desired rate of %.4fHz... the loop actually took %.4f seconds", controller_frequency_, r.cycleTime().toSec());

2.20、如果while循环被中断

在executeCB中,新的goal都在while循环中处理,但是如果ros发生故障,算法跳出while(n.ok())语句的时候,需要对当前的状态进行一定的处理:

    //唤醒规划线程并退出
    lock.lock();
    runPlanner_ = true;
    planner_cond_.notify_one();
    lock.unlock();

    //if the node is killed then we'll abort and return
    //如果节点被中断
    //将move_base的结果状态status置为Aborted,并且设置text内容为Aborting on goal because it was sent with an invalid quaternion,反馈Aborted的原因;
    as_->setAborted(move_base_msgs::MoveBaseResult(), "Aborting on the goal because the node has been killed");
    return;

2.21、executeCb小结executeCb

executeCb函数是move_base中路径规划的起始函数。在这里传入一个goal目标点,该函数首先需要判断传入目标点的四元数是否合理,然后将该坐标转换到世界坐标系下同时通过publisher发布该坐标用于可视化显示。随后开启线程以及代价地图costmap,记录当前的开始时间以及规划的状态机state,最后完成这些后函数开始调用executeCycle(goal)函数执行正式的路径规划过程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/22485.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTTP协议中的HTTP报文

HTTP中的HTTP报文 1、HTTP报文信息 1.1定义 用于HTTP协议交互的信息叫做HTTP 报文。 HTTP 报文大致可分为报文首部和报文主体两块。两者由最初出现的空行&#xff08;CRLF&#xff09;来划分&#xff08;通常并不一定要有报文主体&#xff09;。 1.2请求报文和响应报文 请…

CSS13_由html{height: 100%} 引发的CSS百分比宽高度的思考

一、html, body { height:100% } CSS有一个常见设置&#xff1a;html,body{ height:100% }&#xff0c;可能大家已经熟视无睹了&#xff0c;但细细思索&#xff0c;可能会有些奇怪&#xff0c;为什么html还需要设置height:100%呢&#xff1f;html不就代表整个页面了吗&#xf…

[附源码]java毕业设计网上书店管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

SpringBoot项目使用JSP

SpringBoot不推荐使用JSP,而是使用模板技术代替JSP视图 先创建个SpringBoot项目 使用JSP需要如下配置 加入一个处理JSP的依赖(使用该JSP依赖原因是SpringBoot的jar包是内嵌了一个Tomcat因此要用这个Jar包,如果你的SpringBoot需要打成war包,不使用内嵌Tomcat,可用通用的JSP依赖…

深度学习基础--神经网络(3)神经网络的学习,损失函数初识

文章目录神经网络的学习从数据中学习数据区分损失函数均方误差交叉熵误差mini-batch学习损失函数更新的思路本文为学习笔记整理参考书籍&#xff1a;《深度学习入门 : 基于Python的理论与实现 》/ (日) 斋藤康毅著 ; 陆宇杰译. – 北京 : 人民邮电出版社, 2018.7&#xff08;20…

Android插件式换肤以及资源加载流程分析

前言 APP更换皮肤的方式有很多&#xff0c;如系统自带的黑夜模式、插件换肤、通过下发配置文件加载不同主题等等&#xff0c;我们这里就浅谈下插件换肤方式。想实现插件换肤功能&#xff0c;我们就需要先弄清楚 :APP是如何完成资源加载的。 资源加载流程 这里我们以ImageVie…

植物大战僵尸变态辅助开发系列教程(E语言实现和VC6实现)(中)

植物大战僵尸变态辅助开发系列教程&#xff08;E语言实现和VC6实现&#xff09;&#xff08;中&#xff09;26、第一种方法实现变态加速功能27、第二种方法找出变态攻击加速的方法28、加快阳光、金币生产速度29、全屏僵尸29、全屏减速第一课30、全屏减速第二课31、全屏奶油的找…

(Transferrin)TF-PEG-DBCO/TCO/tetrazine 转铁蛋白-聚乙二醇-二苯基环辛炔/反式环辛烯/四嗪

产品名称&#xff1a;转铁蛋白-聚乙二醇-二苯基环辛炔 英文名称&#xff1a;(Transferrin)TF-PEG-DBCO 质量控制&#xff1a;95% 原料分散系数PDI&#xff1a;≤1.05 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 用 途&#xff1a;仅供科研实验使用&#xff0c…

Android App开发动画特效中插值器和估值器的讲解以及利用估值器实现弹幕动画实战(附源码和演示视频 可直接使用)

需要图片集和源码请点赞关注收藏后评论区留言~~~ 一、插值器和估值器 插值器用来控制属性值的变化速率&#xff0c;也可以理解为动画播放的速度&#xff0c;默认是先加速再减速。若要给动画播放指定某种速率形式&#xff0c;调用setInterpolator方法设置对应的插值器实现类即可…

Spring Boot 分离配置文件的 N 种方式

今天聊一个小伙伴在星球上的提问&#xff1a; 问题不难&#xff0c;解决方案也有很多&#xff0c;因此我决定撸一篇文章和大家仔细说说这个问题。 1. 配置文件位置 首先小伙伴们要明白&#xff0c;Spring Boot 默认加载的配置文件是 application.properties 或者 application…

【云计算大数据_牛客_Hbase】选择/判断——Hbase

1.Hive 1.下面关于Hive metastore的三种模式的描述错误的是() Derby方式是内嵌的方式,也是默认的启动方式,一般用于单元测试local模式中,使用MySQL 本地部署实现metastoreremote模式为远程MySQLDerby方式在同一时间只能有多个进程连接使用数据库 2. 百度文库 2、代码sel…

Android App开发中集合动画和属性动画的讲解及实战演示(附源码 简单易懂 可直接使用)

需要图片集和源码请点赞关注收藏后评论区留言~~~ 一、集合动画 有时一个动画效果会加入多种动画&#xff0c;比如一个旋转一边缩放&#xff0c;这时便会用到集合动画AnimationSet把几个补间动画组装起来&#xff0c;实现让某视图同时呈现多种动画的效果 因为集合动画和补间动…

Jetson Orin使用Yolo5开源数据集训练模型检测口罩

软硬件环境&#xff1a; 乌班图 20.04 64位蟒蛇与 3.8.10英伟AGX Orin库达11.4PyTorch1.12YOLOv5-6.1感谢开源数据集下载地址 正常都是自己收集完了训练&#xff0c;今天就省略这个步骤了。 如果想自己制作看下面的流程。 软硬件环境搭建教程链接 刷机的话使用官方教程或者…

DNS协议

DNS服务器 人类更喜欢记忆主机名&#xff0c;而路由器更喜欢定长的、有结构层次的IP地址&#xff0c;DNS应运而生&#xff1a;DNS能进行主机名到IP地址转换的目录服务。 DSN是&#xff1a; &#xff08;1&#xff09;一个由分层的DNS服务器&#xff08;DNS server&#xff09;实…

元数据管理-解决方案调研二:元数据管理解决方案——Saas/内部解决方案(2)

Saas/内部解决方案 2.5、Azure Purview 地址&#xff1a;Azure Purview - Unified Data Governance Solution | Microsoft Azure 特点&#xff1a; 1、创建跨整个数据资产的统一数据地图&#xff0c;为有效的数据治理和使用奠定基础 1.1、自动化和管理混合源的元数据&#xf…

重打包实现frida持久化 笔记

修改Dex Using Frida on Android without root 修改so [翻译]在未root的设备上使用frida 2个方法本质都是通过重打包让app自己加载frida-gadget.so &#xff08;但感觉没有谁家app会让人轻易重打包吧。。。&#xff09; apktool d org.telegram.messenger_4.8.4-12207.apk -r…

Tomcat配置SSL证书别名tomcat无法识别密钥项

Tomcat配置SSL证书一直启动失败&#xff0c;主要问题如下&#xff1a; java.io.IOException: Alias name tomcat does not identify a key entry at org.apache.tomcat.util.net.jsse.JSSEUtil.getKeyManagers(JSSEUtil.java:280) 别名tomcat无法识别密钥项&#xff0c;是因…

STM32单片机远程控制大棚种植系统

想要更多项目私wo!!! 一、电路设计 ​​​​​系统示意图硬件系统 系统由五个单片机系统组成的&#xff0c;其中51系列的单片机四个&#xff0c;STM32F407单片机一个&#xff0c;各个子系统之间通过NRF24L01无线模块进行通信。 系统硬件组成框图​​​​主控制板主要由STM32…

【VuePress2.0】快速开始(不用)

文章目录VuePress2.x1.1 安装安装VuePress2.x&#xff08;手动安装&#xff09;1.2 VuePress2.x 基本操作VuePress2.x主题2.1 安装VuePress2.x主题&#xff08;yarn&#xff09;2.2 报错解决&#xff08;没效果&#xff0c;不用看&#xff09;2.3 VuePress2.x主题 基本操作VueP…

Java基础深化和提高-------IO流

目录 IO流技术介绍 什么是IO 什么是数据源 流的概念 第一个简单的IO流程序 IO流的经典写法 IO流新语法经典写法 Java中流的概念细分 按流的方向分类&#xff1a; 按处理的数据单元分类&#xff1a; 按处理对象不同分类&#xff1a; Java中IO流类的体系 Java中IO的四大抽象…