两个概念:包Package
和Manifest
清单文件。
1、Package
包括库文件、可执行文件、脚本及一些其他文件;
2、Manifest
是对package
的相关信息的一个描述,提供了package之间的依赖性,以及一个包的元信息,比如版本,维护者和许可证信息等;
在这个工作空间中,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
是一个命令行工具,使用于工作空间时,工作空间目录就会多出bulid
、devel
目录。最后一行指令为了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。
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:=[新名称]
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参数服务内容。
常用命令:
命令 | 说明 |
---|---|
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
服务可对节点的请求做出相应。
常用命令:
命令 | 说明 |
---|---|
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的新龟。
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
属于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
可以用来启动定义在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
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可以检查出ROS系统安装、节点与话题间的错误。执行:
roscd
roswtf
查看结果即可。
使多台装有ROS的主机能够共享话题传递消息,在多台主机上使用如下命令:
export ROS_MASTER_URI=http://[需要监听主机的IP地址]:11311
注意:
1、只有执行了该命令的终端才有共享话题的能力,可以将该指令加入.bashrc
文件中使其永久生效。
2、需要监听主机的ip必须一致,且不可互相监听,被监听的主机该环境变量可以不设置,或设置为http://localhost:11311
即可。
若主机上含有多个ROS工作空间,出现source
了一个工作空间,其他工作空间里的包就会无法在终端环境中进行rosrun
、roscd
、rosed
等操作时,说明ROS工作空间环境变量串接出了问题。此时先在.bashrc
文件中设置任意一个工作空间的setup.bash
的source
,例如:
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
在含有.rosinstall
的src
目录下初始化wstool
:
wstool init
在含有.rosinstall
的src
目录下更新源码使其与Git上同步:
wstool update
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
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
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>
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