第一个订阅者程序

1 写在开头的话

      首先确保自己已经有工作区间,且已经将该工作区间的setup.bash加入当前用户的环境变量中,若是则直接看第二章节,否则继续往下看。

     若没有工作区间则查看顺序为:1.1——>1.2——>2——>1.3——》3

     若有工作区间但是没有将setup.bash加入当前用户的环境变量中,则查看顺序为:1.2——》1.3——》2——》3

    若是已经有工作区间且已经将该工作区间的setup.bash加入当前用户的环境变量中,则查看顺序1.2—》2—》3

1.1 新建工作区间

  在当前用户home目录下调用mkdir命令新建文件夹test,cd进入该文件夹,再次调用mkdir新建文件夹src,如下

1.2创建功能包

cd进入工作区间的src目录,注意:图中test就是本人的工作区间名字

在src目录下调用catkin_create_pkg,创建功能包subpose,如下

  调用ls查看是否创建成功,成功会在src下出现subpose文件夹,如下

cd进入文件夹subpose,通过ls命令可以看到如下2个文件

 第一个配置文件,叫做package.xml

 第二个文件,叫做CMakeLists.txt,是一个Cmake的脚本文件,Cmake是一个符合工业标准的跨平台编译系统。

1.3  添加环境变量

       执行此部分需要两个条件(满足其一即可),条件为:其一,在查看本文前已经有工作区间但是没有将该工作区间的setuo.bash配置到当前用户的环境变量中;其二,已经执行了本文的第二部分

      添加环境变量到当前目录步骤如下:

      a.在当前用户目录的工作区目录下,即test目录下【此时的test为本人的工作区名称,若查阅本文者查看此文前已经有自己的工作区间,则此步骤的test为查阅者的工作区间名称】,调用ls查看此目录下内容,如下:

   b.调用cd进入devel目录,调用ls查看此目录下内容:

c. 注意:a、b步骤只是为了确保该工作区间中存在该工作区间环境变量的配置文件setup.bash,没有做任何实质性的工作

d。再次进入当前用户的home目录,调用ls -a查看该目录下包含隐藏文件的全部文件,如下

可以看到,上图中有隐藏文件.bashrc

e。调用vim编辑此文件,如下

在最后加入一行:source  ~/test/devel/setup.bash,注意此处的test是工作区间的名字,根据需要修改,如下

按esc退出编辑,继而输入:wq保存退出


2  创建编译执行订阅者程序

2.1 创建订阅者程序‘

2.1.1 添加cpp文件

    在subpose文件夹下通过vim创建订阅者程序的cpp文件,并添加如下代码段

   

   程序subpose.cpp订阅turtlesim机器人发布的位姿数据

2.1.2 对subpose.cpp文件进行解释

2.1.2.1 头文件分析

    A.头文件ros/ros.h包含了标准ROS类的声明,你将会在每一个你写的ROS程序中包含它。

    B. 本例程中,回调函数poseMessageReceived接收类型为turtlesim::Pose的消息,所以我们需要的头文件是turtlesim/Pose.h。

    C.iomanip.h是I/O流控制头文件,就像C里面的格式化输出一样.,该头文件中包含的函数大概有如下几种:                                         setfill(c) 设填充字符为c因为代码中                                                                                                                                                     setprecision(n) 设显示小数精度为n位
  setw(n) 设域宽为n个字符
  setiosflags(ios::fixed) 固定的浮点显示
  setiosflags(ios::scientific) 指数表示
  setiosflags(ios::left) 左对齐
  setiosflags(ios::right) 右对齐
  setiosflags(ios::skipws 忽略前导空白
  setiosflags(ios::uppercase) 16进制数大写输出
  setiosflags(ios::lowercase) 16进制小写输出
  setiosflags(ios::showpoint) 强制显示小数点
  setiosflags(ios::showpos) 强制显示符号

2.1.2.2  回调函数poseMessageReceived说明

        发布和订阅消息的一个重要的区别是订阅者节点无法知道消息什么时候到达。为了应对这一事实,我们必须把响应收到消息事件的代码放到回调函数里,ROS每接收到一个新的消息将调用一次这个函数。订阅者的回调函数类似于:
void function_name(const package_name::type_name &msg){}

       其中参数package_name 和type_name和发布消息时的相同,它们指明了我们想订阅的话题的消息类

       回调函数的主体有权限访问接收到消息的所有域,并以它认为合适的方式存储、使用或丢弃接收到的数据

       此例中这个回调函数仅仅是通过ROS_INFO_STREAM在终端打印消息数据,包括x、y和theta数据成员

       注意订阅者的回调函数的返回值类型为void。其实这样安排是合理的,因为调用此函数是ROS的工作,返回值也要交给ROS,所以我们的程序无法获得返回值

2.1.2.3  main函数内代码段说明

 A.ros::init函数初始化ROS客户端库。请在你程序的起始处调用一次该函数3。函数最后的参数是一个包含节点默认名的字符串。

 B.ros::NodeHandle(节点句柄)对象是你的程序用于和ROS系统交互的主要机制4。创建此对象会将你的程序注册为ROS节点管    理器的节点。最简单的方法就是在整个程序中只创建一个NodeHandle对象。

C.创建订阅者对象

       为了订阅一个话题,我们需要创建一个ros::Subscriber对象
                      ros::Subscriber sub = node_handle.subscribe (topic_name,queue_size, pointer_to_callback_function);

        node_handle是ros::NodeHandle类的一个对象,是你在程序的开始处创建的,即ng。我们将调用这个对象的subscribe方法;                                                                                                                                                                                                           topic_name是我们想要订阅的话题的名称,以字符串的形式表示。   

       queue_size是本订阅者接收消息的队列大小,是一个整数。通常,你可以使用一个较大的整数,例如1000,而不用太多关心队列处理过程。当新的消息到达时,它们会被保存在一个队列中,直到ROS有机会去执行相应的回调函数。此参数表示ROS在队列中同一时刻可以存储的消息的最大值。如果新消息到达时队列已满,最早到达的还没有被处理的消息将会被丢弃以便腾出空间来.ROS清空一个发布序列的速率取决于实际上给订阅者传输消息所占用的时间,而这个时间在很大程度上是不受控制的。ROS清空订阅序列的速度取决于我们处理回调函数有多快。因此,我们可以通过如下两个方法减少订阅者队列溢出的可能性:(1)通过调用ros::spin或者ros:spinOnce确保允许回调发生;(2)减少每个回调函数的计算时间

        pointer_to_callback_function是指向回调函数的指针,当有消息到达时要通过这个指针找到回调函数。在C++中,你可以通过对函数名使用符号运算符(&,“取址”)来获得函数的指针

       创建ros::Subscriber对象时,我们没有在任何地方显式地提到消息类型。实际上,subscribe方法是模板化的,C++编译器会根据我们提供的函数指针中的数据类型判断出正确的消息类型。

D.给ROS控制权

         最后的复杂之处在于只有当我们明确给ROS许可时,它才会执行我们的回调函数13。实际上有两个略微不同的方式来做到这一点,如下所示:

          ros::spinOnce();这个代码要求ROS去执行所有挂起的回调函数,然后将控制权限返回给我们。

          ros::spin();这个方法要求ROS等待并且执行回调函数,直到这个节点关机。换句话说,ros::spin()大体等于这样一个循环:
                       while(ros::ok( )   {   ros::spinOnce()    }

         使用ros::spinOnce()还是使用ros::spin()的建议如下:你的程序除了响应回调函数,还有其他重复性工作要做吗?如果答案是“否”,那么使用ros::spin();否则,合理的选择是写一个循环,做其他需要做的事情,并且周期性地调用ros::spinOnce()来处理回调。本例中使用ros::spin(),因为程序唯一的工作就是接收和打印接收到的位姿消息。

          订阅者程序中常见的一个错误是不小心忽略了调用ros::spinOnce和ros::spin。在这种情况下,ROS永远没有机会去执行你的回调函数。忽略ros::spin会导致你的程序在开始运行后不久就退出。忽略ros::spinOnce使程序表现的好像没有接收到任何消息。

 2.2 编译subpose程序

 2.2.1 添加依赖库

   A.因为subpose使用了来自turtlesim包的消息类型和roscpp中的ros.h头文件,我们必须声明对这两个包的依赖关系

     进入工作区间目录下的src目录,cd再次进入subpose功能包,vim打开该目录下的CMakeLists.txt

    

     CMakeLists.txt文件的默认版本含有如下行:find_package(catkin REQUIRED)

    

       所依赖的其他catkin包可以添加到这一行的COMPONENTS关键字后面,如下所示:

                     find_package(catkin REQUIRED COMPONENTS package-names)

        对于subpose例程,我们需要添加名为roscpp和turtlesim的依赖库。因此,修改后的find_package行如下所示:

  

2.2.2 在package.xml文件中声明消息类型依赖库 

       我们同样需要在包的清单文件package.xml中列出依赖库,通过使用build_depend (编译依赖)和run_depend(运行依赖)两个关键字实 现【注意:格式有2种,具体见https://blog.csdn.net/guosuling/article/details/83214460中的3.2】::
                                              <build_export_depend>package-name</build_export_depend>
                                             <exec_depend>package-name</run_depend>

        打开package.xml文件后,找到文件偏下方的绿色字体<buildtool_depend>catkin</buildtoo;_depend>,在其后添加我们的依赖库,添加后效果如下。按esc,然后:wq退出保存

 

        在清单文件中声明的依赖库并没有在编译过程中用到;如果你在此处忽略它们,你可能不会看到任何错误消息,直到发布你的包给其他人,他们可能在没有安装所需包的情况下编译你发布的包而导致报错。

2.3  声明可执行文件

       在功能包的CMakeLists.txt中添加两行,来声明我们需要创建的可执行文件。其一般形式是:
                                         add_executable(executable-name source-files)
                                          target_link_libraries(executable-name ${catkin_LIBRARIES})

       第一行声明了我们想要的可执行文件的文件名,以及生成此可执行文件所需的源文件列表。如果你有多个源文件,把它们列在此处,并用空格将其区分开

        第二行告诉Cmake当链接此可执行文件时需要链接哪些库(在上面的find_package中定义)。如果你的包中包括多个可执行文件,为每一个可执行文件复制和修改上述两行代码。

       在我们的例程中,我们需要一个名为subpose的可执行文件,它通过名为subpose.cpp的源文件编译而来。所以我们需要添加如下几行代码到CMakeLists.txt中。注意:在CMakeLists.txt文件中有很多注释和例句,找到对应的例句,在其下方输入我们要求的命令,保存退出

2.4. 编译工作区

         使用下列命令在工作区目录下编译包中所有的可执行文件 :catkin_make

        

        执行结果如下表示成功

       

        若出现如下找不到头文件的错误,首先详细检查自己代码中相关头文件和函数是否书写出错,若已经确保直接书写正确则见另外一篇文章https://mp.csdn.net/postedit/83214460的2.4.3

       

      若出现大面积的错误信息:未定义的引用,如下所示

           从上图中绿色字体处信息得知:是程序的链接出现了问题,故应检查自己的CMakeLists.txt文件中链接库的声明target_link_libraries(executable-name ${catkin_LIBRARIES}),正确的声明如下图中黄色字体


3 执行subpose程序

        此例程订阅的是turtle1/pose话题,而turtlesim节点会发布消息到该话题,所以为了验证我们的程序,可以新建turtlesim节点,令其发布该消息到话题,从而使我们的程序可以从该话题获取该消息。两个步骤验证我们的程序,如下3.1和3.2

        注意:不论哪种验证方法都必须已经执行过本文的1.3部分,且重新打开一个shell用于运行roscore(节点管理器)

3.1 只运行turtlesim_node节点

       若只运行该节点,那么海龟的位姿并没有改变,所以运行我们的程序时输出一直不变。此步骤验证步骤如下:

  A.单独的shell中运行roscore

 

 B.新建一个shell,运行turtlesim_node节点,此时会出现一个海龟

      C. 新建一个shell,运行我们的subpose程序,可以看到右上角的shell中,输出位姿不变

3.2 运行turtlesim_node节点,同时再运行turtle_teleop_key节点

A .如已经进行步骤3.1,且roscore没有关闭,可以直接跳过此步;否则执行如下步骤:单独的shell中运行roscore,截图如3.1的A

B.如已经进行步骤3.2,且turtlesim_node节点每一个关闭,那么可以直接跳过此步;否则执行如下步骤:新建一个shell,运行turtlesim_node节点,此时会出现一个海龟,截图如3.1的B

C.新建一个shell,运行turtle_teleop_key节点。截图如下

D.新建一个shell,运行我们的subpose程序【若已经3.1且没有关闭我们之前运行的程序,那么跳过此步】,此时因为海龟的位姿依旧没有改变,故我们的输出还是没有改变

E.将鼠标焦点放到turtle_teleop_key,按键盘的上下左右箭头,可以观察到我们的海归根据我们的箭头运动,同时我们自己的节点subpose的输出也根据我们的箭头而改变,如下

猜你喜欢

转载自blog.csdn.net/guosuling/article/details/83385682