ROS机器人操作系统个人笔记,用于Ctrl+F查询

ROS初级入门

ROS文件系统(参考)

两个概念:包PackageManifest清单文件。

1、Package包括库文件、可执行文件、脚本及一些其他文件;

2、Manifest是对package的相关信息的一个描述,提供了package之间的依赖性,以及一个包的元信息,比如版本,维护者和许可证信息等;


创建catkin工作空间(参考)

在这个工作空间中,catkin的包可以被编译。

1、导入ROS环境变量:

source /opt/ros/kinetic/setup.bash

2、创建工作空间:

mkdir -p ~/catkin_ws/src/
cd ~/catkin_ws/src/
catkin_init_workspace

3、编译工作空间,并设置ROS终端预处理环境:

cd ~/catkin_ws/
catkin_make
source devel/setup.bash
echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc

catkin_make是一个命令行工具,使用于工作空间时,工作空间目录就会多出buliddevel目录。最后一行指令为了devel目录中的setup.bash在用户每次打开终端时都被执行。
build目录是build space的默认所在位置,同时cmake和make也是在这里被调用来配置并编译你的程序包。devel目录是devel space的默认所在位置, 同时也是在你安装程序包之前存放可执行文件和库文件的地方。

4、创建一个ROS包:

cd ~/catkin_ws/src
catkin_create_pkg beginner_tutorials std_msgs rospy roscpp

5、ROS常用命令:

命令 例子 说明
rospack depends rospack depends beginner_tutorials 递归检测一个包所有依赖项
rospack find rospack find beginner_tutorials 查看一个包的位置信息
roscd roscd beginner_tutorials 进入到一个包的目录
rosls rosls beginner_tutorials 列举包中所有文件和文件夹
rosed rosed std_msgs Header.msg 它可以直接通过package名来获取到待编辑的文件,并使用vi编辑器打开

定义自己的程序包(参考)

即在开发工程中,需要修改当前包的相关编译、依赖、安装设置,在生成新包时,并不会将这些配置自动写入配置文件。

1、自定义package.xml


<package format="2">
  
  <name>beginner_tutorialsname>
  
  <version>0.0.0version>
  
  <description>The beginner_tutorials packagedescription>

  
  <maintainer email="[email protected]">ubuntumaintainer>

  
  <license>TODOlicense>

  
  <buildtool_depend>catkinbuildtool_depend>
  
  <build_depend>roscppbuild_depend>
  <build_depend>rospybuild_depend>
  <build_depend>std_msgsbuild_depend>
  <build_export_depend>roscppbuild_export_depend>
  <build_export_depend>rospybuild_export_depend>
  <build_export_depend>std_msgsbuild_export_depend>
  <exec_depend>roscppexec_depend>
  <exec_depend>rospyexec_depend>
  <exec_depend>std_msgsexec_depend>
package>

2、自定义CMakeLists.txt

#CMake最小需求版本
cmake_minimum_required(VERSION 2.8.3#程序包的名字
project(packagename)

#查找相关的CMake包,一个包是通过find_package发现CMake的,它导致创造一些CMake的环境变量,例如:可以在括号内增加对message_generation的依赖,此依赖对msg和srv都起作用
find_package(catkin REQUIRED COMPONENTS
    roscpp
    rospy
    std_msgs
)

#若程序包中存在消息目录msg和消息文件.msg,使以下函数生效,将FILES后面的文件名改正确就好
# add_message_files(
#   FILES
#   Message1.msg
#   Message2.msg
# )

#若程序包中存在服务目录srv和服务文件.srv,使以下函数生效,将FILES后面的文件名改正确就好
# add_service_files(
#   FILES
#   Service1.srv
#   Service2.srv
# )

#若程序包中存在动作目录action和动作文件.action,使以下函数生效,将FILES后面的文件名改正确就好
# add_action_files(
#   FILES
#   Action1.action
#   Action2.action
# )

#例如添加.msg和.srv文件后,我们要确保CMake知道在什么时候重新配置我们的项目,使以下函数生效
# generate_messages(
#   DEPENDENCIES
#   std_msgs
# )
#所有在msg路径下的.msg文件都将转换为ROS所支持语言的源代码。生成的C++头文件将会放置在~/catkin_ws/devel/include/beginner_tutorials/。 Python脚本语言会在 ~/catkin_ws/devel/lib/python2.7/dist-packages/beginner_tutorials/msg 目录下创建。 lisp文件会出现在 ~/catkin_ws/devel/share/common-lisp/ros/beginner_tutorials/msg/ 路径下.

#是一个catkin提供的CMake的宏,指定catkin的具体信息,又被用来生成包的配置和CMake文件。例如:添加完.msg和.srv文件后,需在CATKIN_DEPENDS后添加message_runtime来设置运行依赖。还有此函数必须声明在add_library()和add_executable之前。此函数的5个参数依次:导出包的路径即CFLAGS;导入到项目的库;其他catkin项目的项目依赖;非catkin CMake的项目所在依赖;额外的配置选项。
catkin_package(
#  INCLUDE_DIRS include  #一般都会在/include//.h下
#  LIBRARIES ${PROJECT_NAME}  # 导入到项目的库,就是添加include目录下的所有.h文件,不论是哪个main()、哪个cpp的头文件
#  CATKIN_DEPENDS roscpp rospy std_msgs  #添加上文中find_package里的消息类依赖
#  DEPENDS system_lib
)

#用于指定库的路径,例如:在此添加了一个Boost库${Boost_INCLUDE_DIRS}
#只添加一个include的路径,如:
#include_directories( include include/fsm_robot
#  ${catkin_INCLUDE_DIRS}
#)
include_directories(include
  ${catkin_INCLUDE_DIRS}
#${Boost_INCLUDE_DIRS}
)

#功能是用来建立共享的库,在src/程序包中
#此处添加所有不含main()的cpp文件
# add_library(beginner_tutorials
#   src/${PROJECT_NAME}/beginner_tutorials.cpp
# )

#指定一个编译后的可执行目标即(可执行文件名  源文件名)
#此处添加所有含main()的cpp文件
# add_executable(beginner_tutorials_node src/beginner_tutorials_node.cpp)

#为可执行文件添加对生成的消息文件的依赖(可执行文件名  依赖项),依赖项为:${PROJECT_NAME}_generate_messages_cpp;还用过${PROJECT_NAME}_gencpp。目前还不了解其他依赖项有哪些和其使用方法。
# add_dependencies(beginner_tutorials_node beginner_tutorials_generate_messages_cpp)

#指定库的可执行文件的目标链接(可执行文件名  catkin库),一般为${catkin_LIBRARIES}
#在 target_link_libraries的括号内,写出含main()节点名和与之相关的(即main()的cpp中用到的所有cpp)文件名,有几个main就写几个target_link_libraries
# target_link_libraries(beginner_tutorials_node
#   ${catkin_LIBRARIES}
# )

#以下为ROS软件包开发完成后,需要生成安装包使用,即使用catkin_make instll命令时会调用到下面

#用于安装Python可执行脚本,指定路径
# install(PROGRAMS
#   scripts/my_python_script.py
#   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )

#添加可执行文件或者库文件,指定安装的目标(包名  节点名)四行分别是:具体目标;归档目标 -静态库和DLL(Windows)中的.lib存根;库目标 -非DLL共享库和模块;运行时间目标 -可执行的目标和DLL(Windows)中的风格共享库。
# install(TARGETS beginner_tutorials beginner_tutorials_node
#   ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )

#安装头文件,此处后两句意义:安装.h文件,过滤.svn文件
# install(DIRECTORY include/${PROJECT_NAME}/
#   DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
#   FILES_MATCHING PATTERN "*.h"
#   PATTERN ".svn" EXCLUDE
# )

#添加资源文件的目录,例如文件夹:urdf mesh rviz,其下的所有子目录的文件也会安装到相应的目录下
#install(DIRECTORY model
#            DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
#    )
# install(DIRECTORY urdf
#            DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
#    )
# install(DIRECTORY mesh
#            DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
#    )
# install(DIRECTORY rviz
#            DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
#    )

#添加资源文件,如launch文件等:launch/bringup.launch
# install(FILES
#   # myfile1
#   # myfile2
#   DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# )

#测试工作区
# catkin_add_gtest(${PROJECT_NAME}-test test/test_beginner_tutorials.cpp)
# if(TARGET ${PROJECT_NAME}-test)
#   target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
# endif()

#自我记录CMake的宏,生成文档
# catkin_add_nosetests(test)

更多参考资料CMakeLists.txt,Extracted CMake API reference。


理解ROS节点(参考)

Nodes:节点,一个节点即为一个可执行文件,它可以通过ROS与其它节点进行通信。

Messages:消息,消息是一种ROS数据类型,用于订阅或发布到一个话题。

Topics:话题,节点可以发布消息到话题,也可以订阅话题以接收消息。

Master:节点管理器,ROS名称服务 (比如帮助节点找到彼此)。

rosout: ROS中相当于stdout/stderr。

roscore: 主机 + rosout + 参数服务器。

1、运行ROS程序前先执行roscore,若无法执行可能时系统时间与现实时间差距太远,或者是本机的环境变ROS_MASTER_URI设置不正确导致,或是~/.ros文件夹归属用户非登录用户

2、显示当前运行的ROS节点信息:rosnode list

4、返回运行中的节点信息:rosnode info /rosout

5、测试节点延迟:rosnode ping [节点名]

6、运行一个新的节点:rosrun [包名] [节点名]

7、使用Remapping Argument改变节点名称:rosrun [包名] [节点名] __name:=[新名称]


理解ROS话题(参考)

1、首先启动roscore

2、演示一个通过键盘控制turtle的例子,分别打开两个终端执行如下两条指令:

rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key

3、使用rqt_graph显示当前运行的节点和话题:

rosrun rqt_graph rqt_graph

4、查看话题中的消息:

rostopic echo /turtle1/cmd_vel

5、列出所有当前订阅和发布的话题:

rostopic list -v

6、查看话题的消息类型:

rostopic type /turtle1/cmd_vel

7、查看消息的详情:

rosmsg show geometry_msgs/Twist

8、把数据消息发布到某个话题上:

rostopic pub -1 /turtle1/cmd_vel geometry_msgs/Twist  -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, 1.8]'

以1Hz的频率将消息发布到话题上:

rostopic pub /turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, 1.8]'

9、查看话题中消息的发布频率:

rostopic hz /turtle1/cmd_vel

10、查看发布到话题上的数据变化图形:

rosrun rqt_plot rqt_plot

在弹出的界面中topic文本框中输入想要查看的话题,即可打印出该话题中所有变量变化曲线(变量不能是字符串型或消息类型,否则无法打印)


理解ROS参数(参考)

能够存储并操作正在运行的ROS参数服务内容。

常用命令:

命令 说明
rosparam set 设置参数
rosparam get 获取参数
rosparam load 从文件读参数
rosparam dump 向文件中写入参数
rosparam delete 删除参数
rosparam list 列出参数名

1、获取turtlesim_node例子中的参数,例如:

rosparam list
rosparam get /

2、设置background_b参数值,并调用clear服务使生效,例如:

rosparam set /background_b 150
rosservice call /clear

3、将当前参数列表存储于配置文件:

rosparam dump ~/params.yaml

4、将参数配置文件载入ROS参数服务,并调用clear服务使生效,例如:

rosparam load ~/params.yaml  #后面可加要载入的名称空间
rosservice call /clear

理解ROS服务(参考)

服务可对节点的请求做出相应。

常用命令:

命令 说明
rosservice list 输出可用服务的信息
rosservice call 调用带参数的服务
rosservice type 输出服务类型
rosservice find 依据类型寻找服务
rosservice uri 输出服务的ROSPRC uri

1、调用turtlesim_node例子中的clear服务可清除移动轨迹,例如:

rosservice call /clear

2、了解turtlesim_node例子中的spawn服务,例如:

rosservice type /spawn

3、终端打印:turtlesim/Spawn服务类型,查看服务参数:

rossrv show turtlesim/Spawn

4、终端打印:float32 x float32 y float32 theta string name string name,通过类型参数调用该带参数的spawn服务,例如:

rosservice call /spawn 2 2 0.2 “hello”

可在turtlesim中获得一只名为hello的新龟。


创建ROS服务、服务节点、客户节点(参考一)(参考二)

srv含两个部分:请求和响应。

1、在beginner_tutorials包中创建一个服务文件夹:

roscd beginner_tutorials && mkdir srv

2、在srv文件夹中制作一个服务文件AddTwoInts.srv,文件内容为:

int64 a
int64 b
*****
int64 sum

注意:*****短线上方的请求部分,下方的为相应部分。

3、在CMakeLists.txt文件中增加对message_generation的依赖,否则服务文件无法被转换成C++、Python的源代码:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation)

4、取消掉CMakeLists.txt文件中add_service_files()方法的注释,并添加自己的服务文件进去,确保自己的服务文件会被编译:

add_service_files(
  FILES
  AddTwoInts.srv
)

5、取消掉CMakeLists.txt文件中generate_messages()方法的注释,若不存在该方法,添加的服务文件不会被编译。然后根据服务文件中所包含的类型所基于的包,添加对该包的依赖,由于本例子只有int64类型的项,不需要有任何包的依赖(例如服务文件中若所包含std_msgs/Int64类型的项,就添加std_msgs包的依赖),所以以下添加的std_msgs依赖只是用来防止你的其他服务、消息文件编译报错:

generate_messages(
  DEPENDENCIES
  std_msgs
)

执行catkin_make,即可在~/catkin_ws/devel/include/beginner_tutorials
路径下找到编译生成的C++头文件。

6、编写一个调用该服务的服务节点~/catkin_ws/devel/include/beginner_tutorials/src/add_two_ints_server.cpp文件中:

#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"  //由编译系统自动根据我们先前创建的srv文件生成的对应该srv文件的头文件

bool add(beginner_tutorials::AddTwoInts::Request  &req,
         beginner_tutorials::AddTwoInts::Response &res)  //两个形参分别表示请求和反馈,它们是srv文件中“*****”短线上方和下发的两组值。该函数返回true时,该res的值会被发送到客户端
{
  res.sum = req.a + req.b;
  ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
  ROS_INFO("sending back response: [%ld]", (long int)res.sum);
  return true;
}

int main(int argc, char **argv)
{
  ros::init(argc, argv, "add_two_ints_server");
  ros::NodeHandle n;

  ros::ServiceServer service = n.advertiseService("add_two_ints", add);  //建立一个名为add_two_ints的ROS服务,并为其绑定了一个用于处理客户请求的函数add
  ROS_INFO("Ready to add two ints.");
  ros::spin();

  return 0;
}

7、编写一个调用该服务的客户节点~/catkin_ws/devel/include/beginner_tutorials/src/add_two_ints_client.cpp文件中:

#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
#include 

int main(int argc, char **argv)
{
  ros::init(argc, argv, "add_two_ints_client");
  if (argc != 3)
  {
    ROS_INFO("usage: add_two_ints_client X Y");
    return 1;
  }

  ros::NodeHandle n;
  ros::ServiceClient client = n.serviceClient("add_two_ints");  //创建一个客户端,用来调用名为add_two_ints的服务
  beginner_tutorials::AddTwoInts srv;  //定义一个服务对象,并写好请求
  srv.request.a = atoll(argv[1]);
  srv.request.b = atoll(argv[2]);
  if (client.call(srv))  //将服务对象发送给服务节点,若对方服务返回false,则srv.response.sum的值为0,若对方的服务函数返回true,则srv.response的值时是合法的,获得反馈结果
  {
    ROS_INFO("Sum: %ld", (long int)srv.response.sum);
  }
  else
  {
    ROS_ERROR("Failed to call service add_two_ints");
    return 1;
  }

  return 0;
}

8、编译节点,修改CMakeLists.txt文件,在文件末尾添加:

add_executable(add_two_ints_server src/add_two_ints_server.cpp)
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_dependencies(add_two_ints_server beginner_tutorials_gencpp)

add_executable(add_two_ints_client src/add_two_ints_client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
add_dependencies(add_two_ints_client beginner_tutorials_gencpp)

执行catkin_make

9、开启roscore命令,分别在两个终端下运行服务端和客户端,观察打印结果:

rosrun beginner_tutorials add_two_ints_server
rosrun beginner_tutorials add_two_ints_client 1 2

使用rqt_console和rqt_logger_level进行调试(参考)

rqt_console属于ROS日志框架(logging framework)的一部分,用来显示节点的输出信息。rqt_logger_level允许我们修改节点运行时输出信息的日志等级(logger levels)(包括 DEBUG、WARN、INFO和ERROR)。首先在两个终端中打开这两个界面:

rosrun rqt_console rqt_console
rosrun rqt_logger_level rqt_logger_level

这时候,在使用turtlesim_node例子的时候,就可以在rqt_console界面中看到打印的相关信息,在rqt_logger_level界面调整turtlesim的日志输出等级,可以改变rqt_console界面中展示的内容。


使用roslaunch(参考)

roslaunch可以用来启动定义在launch文件中的多个节点,启动roslaunch会默认启动roscore,就不需要再使用roscore了。用法:

roslaunch [包名称] [launch文件]

1、先建立一个launch文件于~/catkin_ws/devel/include/beginner_tutorials/launch/turtlemimic.launch文件中:

<launch>
  
  <group ns="turtlesim1">
    <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
  group>

  <group ns="turtlesim2">
    <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
  group>

  
  <node pkg="turtlesim" name="mimic" type="mimic">
    <remap from="input" to="turtlesim1/turtle1"/>
    <remap from="output" to="turtlesim2/turtle1"/>
  node>

launch>

2、启动这个launch文件:

roslaunch beginner_tutorials turtlemimic.launch

3、发送一个速度消息给turtlesim1这个节点组:

rostopic pub /turtlesim1/turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'

会看到,turtlesim2这个节点组的小乌龟也会跟着动起来。

4、可通过rqt_graph工具来查看话题订阅情况:

rosrun rqt_graph rqt_graph

创建ROS消息、消息发布器、消息订阅器(参考一)(参考二)

msg文件就是一个描述ROS中所使用消息类型的简单文本,被用作给话题中发布。

在ROS中有一个特殊的数据类型:Header,它含有时间戳和坐标系信息。在msg文件的第一行经常可以看到Header header的声明。

1、在beginner_tutorials包中创建一个消息文件夹:

roscd beginner_tutorials && mkdir msg

2、在msg文件夹中制作一个消息文件Num.msg,文件内容为:

int64 num

3、在package.xml文件中添加对message_generation的构建依赖和message_runtime的运行依赖(在创建srv服务时,并不需要这一步):

<build_depend>message_generationbuild_depend>
<build_export_depend>message_generationbuild_export_depend>
<exec_depend>message_runtimeexec_depend>

注意:若是形式的package.xml文件,则需要如下格式添加:

<build_depend>message_generationbuild_depend>
<run_depend>message_runtimerun_depend>

4、在CMakeLists.txt文件中增加对message_generation的依赖,否则服务文件无法被转换成C++、Python的源代码:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

5、取消掉catkin_package()方法的注释,并添加message_runtime运行依赖(在创建srv服务时,并不需要这一步):

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES beginner_tutorials
   CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

6、取消掉CMakeLists.txt文件中add_message_files()方法的注释,并添加自己的消息文件进去,确保自己的消息文件会被编译:

add_message_files(
  FILES
  Num.msg
)

7、取消掉CMakeLists.txt文件中generate_messages()方法的注释,若不存在该方法,添加的消息文件不会被编译。然后根据消息文件中所包含的类型所基于的包,添加对该包的依赖,由于本例子只有int64类型的项,不需要有任何包的依赖(例如消息文件中若所包含std_msgs/Int64类型的项,就添加std_msgs包的依赖),所以以下添加的std_msgs依赖只是用来防止你的其他服务、消息文件编译报错:

generate_messages(
  DEPENDENCIES
  std_msgs
)

执行catkin_make,即可在~/catkin_ws/devel/include/beginner_tutorials路径下找到编译生成的C++头文件。

8、编写一个调用该消息的发布器~/catkin_ws/devel/include/beginner_tutorials/src/talker.cpp文件中:

#include 
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "beginner_tutorials/Num.h"  //由编译系统自动根据我们先前创建的msg文件生成的对应该msg文件的头文件

int main(int argc, char **argv)
{
  ros::init(argc, argv, "talker");
  ros::NodeHandle n;
  ros::Publisher chatter_pub = n.advertise("chatter", 1000);  //利用句柄对象建立一个名为chatter的话题,该话题使用的消息内容为beginner_tutorials/Num
  ros::Rate loop_rate(10);  //定义该消息的发布频率为10Hz
  int count = 0;

  while (ros::ok())  //检测Ctrl+C
  {
    beginner_tutorials::Num msg;
    std::stringstream ss;
    ss << "hello world " << count;
    msg.num = count;  //将要发布的内容写入消息中

    ROS_INFO("%zu", msg.num);

    chatter_pub.publish(msg);  //执行发布这个消息到话题的发布函数

    ros::spinOnce();  //相当于ros::spin()的单次调用,这里其实不需要这一句,最好在这里加上ros::spinOnce()这一语句,否则你的回调函数就永远也不会被调用了,或是在while循环外部直接添加ros::spin()都可以
    loop_rate.sleep();  //根据上面定义的10Hz,这里做适当休息,保证发布频率
    ++count;
  }
  return 0;
}

7、编写一个调用该消息的订阅器~/catkin_ws/devel/include/beginner_tutorials/src/listener.cpp文件中:

#include "ros/ros.h"
#include "std_msgs/String.h"
#include "beginner_tutorials/Num.h"  //由编译系统自动根据我们先前创建的msg文件生成的对应该msg文件的头文件

void chatterCallback(const beginner_tutorials::Num::ConstPtr& msg)  //一个处理该话题消息的回调函数,调用形式类似单片机的中断函数
{
  ROS_INFO("I heard: [%zu]", msg->num);
}

int main(int argc, char **argv)
{
  ros::init(argc, argv, "listener");
  ros::NodeHandle n;
  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);  //利用句柄对象订阅chatter话题内容,指定了一个处理该话题消息的回调函数chatterCallback,还定义了一个接受1000个消息的缓存,防止chatterCallback函数处理消息的速度不够快,但当缓存达到1000条消息后,再有新的消息到来就将开始丢弃先前接收的消息。当缓存容量定义为0时,可以理解为处理实时消息内容,将时序脱节的消息不再处理

  ros::spin();  //进入自循环,可以尽可能快的调用消息回调函数。如果没有消息到达,它不会占用很多 CPU,所以不用担心
  return 0;
}

8、编译节点,修改CMakeLists.txt文件,在文件末尾添加:

add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker beginner_tutorials_generate_messages_cpp)

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener beginner_tutorials_generate_messages_cpp)

执行catkin_make。

9、开启roscore命令,分别在两个终端下运行服务端和客户端,观察打印结果:

rosrun beginner_tutorials talker
rosrun beginner_tutorials listener

录制与回放数据(参考)

记录ROS系统运行时的话题数据,记录的话题数据将会累积保存到bag文件中。

将当前发布的所有话题数据都录制保存到一个bag文件中,例如:

rosbag record -a

录制指定的话题到bag文件中,例如:

rosbag record -O subset [话题1] [话题2]

查看bag文件信息,例如:

rosbag info [bag文件]

回放bag文件内容,例如:

rosbag play [bag文件]

回放bag文件时,指定文件中消息的发布频率:

rosbag play -r 2 [bag文件]

roswtf(参考)

roswtf可以检查出ROS系统安装、节点与话题间的错误。执行:

roscd
roswtf

查看结果即可。


ROS的分布式连接和运行(参考)

使多台装有ROS的主机能够共享话题传递消息,在多台主机上使用如下命令:

export ROS_MASTER_URI=http://[需要监听主机的IP地址]:11311

注意:

1、只有执行了该命令的终端才有共享话题的能力,可以将该指令加入.bashrc文件中使其永久生效。

2、需要监听主机的ip必须一致,且不可互相监听,被监听的主机该环境变量可以不设置,或设置为http://localhost:11311即可。


ROS工作空间串接

若主机上含有多个ROS工作空间,出现source了一个工作空间,其他工作空间里的包就会无法在终端环境中进行rosrunroscdrosed等操作时,说明ROS工作空间环境变量串接出了问题。此时先在.bashrc文件中设置任意一个工作空间的setup.bashsource,例如:

source ~/catkin_ws/devel/setup.bash

然后在该工作空间中找到devel/_setup_util.py文件,并在其中找到CMAKE_PREFIX_PATH变量的定义处(约265行左右),增加其他工作空间、ROS安装目录的setup.bash所在路径,如:

CMAKE_PREFIX_PATH = '/home/ubuntu/catkin_ws/devel;/opt/ros/kinetic'.split(';')

使用;隔开每个工作空间的setup.bash所在路径,最后添加ROS安装目录的setup.bash所在路径。

重启终端,ROS工作空间环境变量串接问题即可解决。


管理系统依赖项(参考)

可以使用rosdep命令来安装一个ROS包,例如:

rosdep install [ros包]
rosdep install --from-paths [路径/包]

初始化rosdep

sudo rosdep init

更新rosdep

rosdep update

wstool命令来源码安装ROS包,-j5表示指定安装进程数为5,在建立的src文件夹中会包含一个.rosinstall文件,里面含有Git源码的网址,例如:

wstool init -j5 src [官方给定的安装URL路径]

.rosinstall文件中添加一个新网址,ros_tutorials是包描述,例如:

wstool set ros_tutorials --git://github.com/ros/ros_tutorials.git

在含有.rosinstallsrc目录下初始化wstool

wstool init

在含有.rosinstallsrc目录下更新源码使其与Git上同步:

wstool update

actionlib教程(参考)

ROS的行为库相对于发布器、订阅器而言是一种更为专业的ROS功能编写方式,它可以使节点的调用者更为方便轻松的对节点进行观测和控制,例如actionlib的运行状态是否正常、发布的指令的完成情况跟踪、控制节点功能停止运行、控制节点功能继续运行等。

练习环境准备,在src下建立:

catkin_create_pkg actionlib_tutorials actionlib message_generation roscpp rospy std_msgs actionlib_msgs

1、在../actionlib_tutorials/action目录下创建一个动作消息Fibonacci.action,固定格式、固定语句,利用*****短线区分目标goal成功状态result实时反馈状态feedback

int32 order
*****
int32[] sequence
*****
int32[] sequence

2、确保CMakeLists.txt中的以下方法中的内容:

find_package(catkin REQUIRED COMPONENTS
  actionlib
  actionlib_msgs
  message_generation
  roscpp
  rospy
  std_msgs
)
add_action_files(
  FILES
  Fibonacci.action
)
generate_messages(
  DEPENDENCIES
  actionlib_msgs std_msgs
)
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES actionlib_tutorials
   CATKIN_DEPENDS actionlib actionlib_msgs message_generation roscpp rospy std_msgs
#  DEPENDS system_lib
)

确保package.xml中存在message_generation运行依赖:

<run_depend>message_runtimerun_depend>

注意:若是形式的package.xml文件,则需要按如下格式添加:

<exec_depend>message_generationexec_depend>

3、在../actionlib_tutorials/src目录下写一个简单的服务器fibonacci_server.cpp

#include 
#include 
#include 

class FibonacciAction
{
protected:
  ros::NodeHandle nh_;  //句柄必须定义在as_之前,否则会报错
  actionlib::SimpleActionServer as_;  //创建动作服务节点
  std::string action_name_;
  actionlib_tutorials::FibonacciFeedback feedback_;  //创建用于发布反馈的消息
  actionlib_tutorials::FibonacciResult result_;  //创建用于设置成功标志为的对象

public:
  //初始化ActionServer对象,为其指定名称、执行函数指针
  FibonacciAction(std::string name) : as_(nh_, name, boost::bind(&FibonacciAction::executeCB, this, _1), false), action_name_(name)
  {
    as_.start();
  }

  ~FibonacciAction(void)
  {
  }

  void executeCB(const actionlib_tutorials::FibonacciGoalConstPtr &goal)  //会每次有新请求时被执行
  {
    ros::Rate r(1);  //以1Hz的频率执行下面的for循环
    bool success = true;

    feedback_.sequence.clear();
    feedback_.sequence.push_back(0);
    feedback_.sequence.push_back(1);
    //打印运行状态,goal->order是从client传过来的
    ROS_INFO("%s: Executing, creating fibonacci sequence of order %i with seeds %i, %i", action_name_.c_str(), goal->order, feedback_.sequence[0], feedback_.sequence[1]);

    for(int i=1; i<=goal->order; i++)
    {
      if (as_.isPreemptRequested() || !ros::ok())  //当取消执行或服务被抢占,则返回失败
      {
        ROS_INFO("%s: Preempted", action_name_.c_str());
        as_.setPreempted();  //设置ActionServer的抢占状态
        success = false;
        break;
      }
      feedback_.sequence.push_back(feedback_.sequence[i] + feedback_.sequence[i-1]);
      as_.publishFeedback(feedback_);  //发布反馈变量的所得值,在话题/fibonacci/feedback上
      r.sleep();
    }

    if(success)
    {
      result_.sequence = feedback_.sequence;
      ROS_INFO("%s: Succeeded", action_name_.c_str());
      as_.setSucceeded(result_);  //最终执行完成设置成功标志
    }
  }
};

int main(int argc, char** argv)
{
  ros::init(argc, argv, "fibonacci");
  FibonacciAction fibonacci(ros::this_node::getName());  //调用类的构造函数即可开始ActionServer的执行,因为构造函数中执行了as_.start();
  ros::spin();
  return 0;
}

4、在../actionlib_tutorials/src目录下写一个简单的客户机fibonacci_client.cpp

#include 
#include 
#include 
#include 

int main (int argc, char **argv)
{
  ros::init(argc, argv, "test_fibonacci");
  actionlib::SimpleActionClient ac("fibonacci", true);  //创建行为客户端,成功会开启客户端创建自己的线程

  ROS_INFO("Waiting for action server to start.");
  ac.waitForServer();  //等待行为服务器开启

  ROS_INFO("Action server started, sending goal.");
  actionlib_tutorials::FibonacciGoal goal;  //消息内只有一个int32类型的order成员
  goal.order = 20;
  ac.sendGoal(goal);  //发送目标到行为服务器

  bool finished_before_timeout = ac.waitForResult(ros::Duration(30.0));  //等待30秒来让服务完成任务,30秒过后函数将停止,若30秒内未收到来自服务的成功标志位,即as_.setSucceeded(result_);则ac.waitForResult返回0
  if (finished_before_timeout)
  {
    actionlib::SimpleClientGoalState state = ac.getState();  //获得当前服务状态
    ROS_INFO("Action finished: %s",state.toString().c_str());
  }
  else
    ROS_INFO("Action did not finish before the time out.");
  return 0;
}

5、在CMakeLists.txt文件后追加如下内容,并编译测试,查看打印结果:

add_executable(fibonacci_server src/fibonacci_server.cpp)
target_link_libraries(fibonacci_server ${catkin_LIBRARIES})
add_dependencies(fibonacci_server ${actionlib_tutorials_EXPORTED_TARGETS})

add_executable(fibonacci_client src/fibonacci_client.cpp)
target_link_libraries(fibonacci_client ${catkin_LIBRARIES})
add_dependencies(fibonacci_client ${actionlib_tutorials_EXPORTED_TARGETS})

6、在../actionlib_tutorials/src目录下写一个基于回调的服务器averaging_server.cpp

首先在action目录下再创建一个动作消息Averaging.action,内容如下:

#goal definition
int32 samples
*****
#result definition
float32 mean
float32 std_dev
*****
#feedback
int32 sample
float32 data
float32 mean
float32 std_dev

修改CMakeLists.txt中的add_action_files()方法:

add_action_files(
  FILES
  Fibonacci.action
  Averaging.action
)

代码内容:

#include 
#include "beginner_tutorials/Num.h"
#include 
#include 

class AveragingAction
{
public:
  AveragingAction(std::string name) : as_(nh_, name, false), action_name_(name)
  {
    as_.registerGoalCallback(boost::bind(&AveragingAction::goalCB, this));  //goalCB会每次有新请求时被执行
    as_.registerPreemptCallback(boost::bind(&AveragingAction::preemptCB, this));  //preemptCB只会在抢占时被执行
    sub_ = nh_.subscribe("/chatter", 1, &AveragingAction::analysisCB, this);  //订阅一个其他话题数据
    as_.start();
  }

  ~AveragingAction(void)
  {
  }

  void goalCB()  //只有当client发一个goal时才会执行该函数,并获得新消息内容
  {
    data_count_ = 0;
    sum_ = 0;
    sum_sq_ = 0;
    goal_ = as_.acceptNewGoal()->samples;
  }

  void preemptCB()  //preemptCB只会在抢占时被执行
  {
    ROS_INFO("%s: Preempted", action_name_.c_str());
    as_.setPreempted();  //设置行为状态为抢占(preempted)
  }

  void analysisCB(const beginner_tutorials::Num::ConstPtr& msg)  //订阅一个其他话题数据
  {
    if (!as_.isActive())  //确保行为还没有被client取消,无客户端消息时返回0,有客户端消息时返回1
      return;

    data_count_++;
    feedback_.sample = data_count_;
    feedback_.data = msg->num;
    sum_ += msg->num;
    feedback_.mean = sum_ / data_count_;
    sum_sq_ += pow(msg->num, 2);
    feedback_.std_dev = sqrt(fabs((sum_sq_/data_count_) - pow(feedback_.mean, 2)));
    as_.publishFeedback(feedback_);  //将结果发布在反馈话题上

    if(data_count_ > goal_)
    {
      result_.mean = feedback_.mean;
      result_.std_dev = feedback_.std_dev;

      if(result_.mean < 5.0)
      {
        ROS_INFO("%s: Aborted", action_name_.c_str());
        as_.setAborted(result_);  //设置行为状态为崩溃(aborted)
      }
      else
      {
        ROS_INFO("%s: Succeeded", action_name_.c_str());
        as_.setSucceeded(result_);  //设置行为状态为成功(succeeded)
      }
    }
  }

protected:
  ros::NodeHandle nh_;
  actionlib::SimpleActionServer as_;
  std::string action_name_;
  int data_count_, goal_;
  float sum_, sum_sq_;
  actionlib_tutorials::AveragingFeedback feedback_;
  actionlib_tutorials::AveragingResult result_;
  ros::Subscriber sub_;
};

int main(int argc, char** argv)
{
  ros::init(argc, argv, "averaging");
  AveragingAction averaging(ros::this_node::getName());
  ros::spin();
  return 0;
}

7、在../actionlib_tutorials/src目录下写一个基于回调的客户机averaging_client.cpp

#include 
#include 
#include 

using namespace actionlib_tutorials;
typedef actionlib::SimpleActionClient Client;

class MyNode
{
public:
  MyNode() : ac("averaging", true)
  {
    ROS_INFO("Waiting for action server to start.");
    ac.waitForServer();
    ROS_INFO("Action server started, sending goal.");
  }

  void doStuff(int order)
  {
    AveragingGoal goal;
    goal.samples = order;

    ac.sendGoal(goal,
                boost::bind(&MyNode::doneCb, this, _1, _2),
                Client::SimpleActiveCallback(),
                Client::SimpleFeedbackCallback());  //基于回调客户端的重点在于充分利用ac客户节点,不光给服务端发送指令,还要指定三个函数用来处理完成、活跃、反馈状态的的触发事件
  }

  void doneCb(const actionlib::SimpleClientGoalState& state,
              const AveragingResultConstPtr& result)
  {
    ROS_INFO("Finished in state [%s]", state.toString().c_str());
    ROS_INFO("Answer mean: %f, std_dev: %f", result->mean, result->std_dev);
    ros::shutdown();
  }

private:
  Client ac;
};

int main (int argc, char **argv)
{
  ros::init(argc, argv, "averaging_client");
  MyNode my_node;
  my_node.doStuff(10);
  ros::spin();
  return 0;
}

8、在CMakeLists.txt文件后追加如下内容,并编译:

add_executable(averaging_client src/averaging_client.cpp)
target_link_libraries(averaging_client ${catkin_LIBRARIES})
add_dependencies(averaging_client ${actionlib_tutorials_EXPORTED_TARGETS})

add_executable(averaging_server src/averaging_server.cpp)
target_link_libraries(averaging_server ${catkin_LIBRARIES})
add_dependencies(averaging_server ${actionlib_tutorials_EXPORTED_TARGETS})

分别在三个终端下执行如下指令,观察打印结果:

rosrun actionlib_tutorials averaging_server
rosrun actionlib_tutorials averaging_client
rosrun beginner_tutorials talker

tf命令

1、tf_echo打印源坐标系和目标坐标系传送信息的工具,打印地图和里程计传输消息,例如:

rosrun tf tf_echo [原坐标系] [目标坐标系]
rosrun tf tf_echo /map /odom

2、view_frames一个可视化完整坐标树生成工具,会在home目录生成pdf文件,用法:

rosrun tf view_frames
evince frames.pdf

3、在rviz中检查TF帧,以turtle_tf为例:

roslaunch turtle_tf turtle_tf_demo.launch
rosrun rviz rviz -d `rospack find turtle_tf`/rviz/turtle_rviz.rviz

rosserial_arduino(参考)

1、获取ROS的Arduino库,在自己的src下并编译:

git clone https://github.com/ros-drivers/rosserial.git

2、在ArduinoIDE的库路径下,如~/sketchbook/libraries,获取当前ROS系统的所有消息库头文件ros_lib,执行:

rosrun rosserial_arduino make_libraries.py .

3、写一个用于烧写于Arduino的ROS**消息发布节点**:

#include 
#include 

ros::NodeHandle nh;
std_msgs::String str_msg;
ros::Publisher chatter("chatter", &str_msg);
char hello[13] = "hello world!";

void setup()
{
  nh.initNode();  //该操作使串口波特率变为57600
  nh.advertise(chatter);
}

void loop()
{
  str_msg.data = hello;
  chatter.publish( &str_msg );
  nh.spinOnce();
  delay(1000);
}

4、写一个用于烧写于Arduino的ROS**消息订阅节点**:

#include 
#include 

ros::NodeHandle nh;

void messageCb( const std_msgs::Empty& toggle_msg)
{
  digitalWrite(13, HIGH-digitalRead(13));
}

ros::Subscriber sub("toggle_led", &messageCb );

void setup()
{
  pinMode(13, OUTPUT);
  nh.initNode();  //该操作使串口波特率变为57600
  nh.subscribe(sub);
}

void loop()
{
  nh.spinOnce();
  delay(1);
}

5、ROS连接Arduino与其通信,建议使用launch进行启动通信节点,方便指定硬件虚拟文件:

<node pkg="rosserial_server" type="serial_node" name="rosserial_server_1" respawn="true">
  <param name="port" value="/dev/ttyACM0"/>
node>

Ubuntu系统使ROS功能开机自启动

1、先在rc.local中添加:

su [用户名] -c "/home/[用户名]/bin/launch_ros.sh"

2、再在第一步所指定的launch_ros.sh写:

#!/bin/bash
sleep 15
echo "running ros"
screen -dmS ros_run /home/[用户名]/bin/ros_run.sh

3、再在第二步所指定的ros_run.sh写:

#!/bin/bash
source /home/[用户名]/catkin_ws/devel/setup.bash
roslaunch [包名] [launch文件名]

注意:这些脚本文件都必须是777权限。


kobuki备忘录:

1、kobuki启动:

roslaunch kobuki_node minimal.launch

2、kobuki屏蔽障碍物:

roslaunch kobuki_node minimal.launch
roslaunch kobuki_random_walker safe_random_walker_app.launch

3、kobuki键盘控制:

roslaunch kobuki_node minimal.launch
roslaunch kobuki_keyop safe_keyop.launch

4、kobuki罗技手柄控制:

roslaunch turtlebot_bringup minimal.launch
roslaunch turtlebot_teleop logitech.launch

5、kobuki仪表盘:

roslaunch turtlebot_bringup minimal.launch
rqt -s kobuki_dash

6、kobuki自动充电:

roslaunch kobuki_auto_docking compact.launch
roslaunch kobuki_auto_docking activate.launch

7、kobuki陀螺仪漂移后临时校准使用,扬汤止沸而已,以后还会再漂移:

roslaunch turtlebot_bringup minimal.launch
roslaunch turtlebot_calibration calibrate.launch

8、kobuki重要话题:

话题名称 描述
/mobile_base/commands/velocity kobuki控制速度话题的一个根
/cmd_vel_mux/active 一个速度话题
/cmd_vel_mux/input/navi 导航系统功能使用的发布速度话题
/cmd_vel_mux/input/safety_controller 重要,该话题发布的速度指令执行级别最高,一般测试中用于开发者急停、控制使用
/cmd_vel_mux/input/teleop 遥控手柄一般使用的速度发布话题
/mobile_base/sensors/core 电量、PWM调制、bump传感器等话题

安装ROS网络时间协议,安装完后记得手动使用ntpdate命令同步一下网络时间:

sudo apt-get install chrony

本文档不定期更新,若有不当之处,还望指正。

你可能感兴趣的:(ROS)