ROS的文件系统
catkin_workspace
workspace_folder/ -- WORKSPACE
src/ -- SOURCE SPACE
CMakeLists.txt -- The 'toplevel' CMake file
package_1/
CMakeLists.txt
package.xml
...
package_n/
CATKIN_IGNORE -- Optional empty file to exclude package_n from being processed
CMakeLists.txt
package.xml
...
build/ -- BUILD SPACE
CATKIN_IGNORE -- Keeps catkin from walking this directory
devel/ -- DEVELOPMENT SPACE (set by CATKIN_DEVEL_PREFIX)
bin/
etc/
include/
lib/
share/
.catkin
env.bash
setup.bash
setup.sh
...
install/ -- INSTALL SPACE (set by CMAKE_INSTALL_PREFIX)
bin/
etc/
include/
lib/
share/
.catkin
env.bash
setup.bash
setup.sh
...
catkin_workspace最多有四个space(文件夹),每个space有不同的作用。
-
Source Space:该空间下每个文件夹里有一个或多个catkin packages。该空间根目录下必须有一个CMakeLists.txt文件,可以在该空间根目录使用
catkin_init_workspace
命令生成该文件。 -
Build Space:该空间是调用cmake和make来编译source space中的catkin packages的路径。cmake和catkin把缓存信息(cache information)和其他中间件(intermediate files)保存在这里。
-
Development (Devel) Space :编译后的目标的存放路径。
-
Install Space:make install命令存放的路径
参考wiki.ros.org中关于catkin工作空间的描述1
ROS下载包
把PACKAGE替换成想要下载的包名称
ROS中重要的概念
Node
一个节点就是在ros package里的一个可执行文件,ros节点通过ROS client library和其他节点互相通信。节点的功能:
-
订阅或发布topic主题
-
提供或使用service服务
编写的node节点在cmake后会生成对应的可执行文件,放在
devel/lib/<package>
对应的包名称的路径下。
Topic
可以通过rostopic list -v
或者图形化工具rosrun rqt_graph rqt_graph
查看节点和话题之间的关系
显示被发布和被订阅的主题,并分别显示了发布者和订阅者的数量、消息类型message type。
箭头上是主题,箭头从发布者指向订阅者。
使用图形化工具rosrun rqt_plot rqt_plot
可以显示话题里发布的数据。
ROS Services
服务是节点和其他节点互相交流的另一个方式,服务允许节点发送一个服务请求request和接收一个应答response。
使用rosservice type [service]
可以查看服务的类型,如果类型是std_srvs/Empty
,说明当请求该服务时不需要任何参数(请求服务时不会发送数据,接收应答时不会接收数据)。
Parameter Server
参数服务器可以存储integers, floats, boolean, dictionaries, and lists。
rqt_console和rqt_logger_level
rqt_console
链接到ros的日志机制(logging framework)来显示节点运行时的输出。
rqt_logger_level
可以改变显示的日志等级:(Fatal,Error,Warn,Info,Debug)。
roslaunch
roslaunch
从一个启动文件(launch file)里启动节点。
<launch>
# 使用group可以建立两个相同名称的节点,不会起冲突,因为在两个group里
<group ns="turtlesim1">
# 包名称 自定义节点名称 节点名称
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
</group>
<group ns="turtlesim2">
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
</group>
# 第3个节点
<node pkg="turtlesim" name="mimic" type="mimic">
<remap from="input" to="turtlesim1/turtle1"/>
<remap from="output" to="turtlesim2/turtle1"/>
</node>
</launch>
msg
msg存储在包的msg/
目录下,msg只是简单的文本文件,每行都有字段类型和字段名称。
字段类型:
ros里有一种特殊的字段类型:Header。包含时间戳(timestamp)和坐标帧信息(coordinate frame information),经常看到在msg文件的第一行有
Header header
声明。
srv
msg存储在包的srv/
目录下,srv和msg的区别是:srv有request和response两个部分,由虚线分隔开。虚线上面是request,下面是response。
int64 A
int64 B
---
int64 Sum
编写的.msg和.srv文件在cmake的时候都会生成对应的头文件,放在
devel/include/
路径下
新建messages,services或actions
以下是使用catkin_create_pkg
生成的CMakeLists.txt
里的注释:
要在这个包里声明和编译messages,services或actions,跟随以下步骤:
MSG_DEP_SET是你在message/services/actions中使用的消息类型(message types)的包的集合(使用MSG_DEP_SET这个名字只是为了下面的注释方便说明,没有其他目的,不是ros里特殊的宏定义)。
在这个包的package.xml
里:
- 在
<build_depend>
里添加message_generation
- 给
MSG_DEP_SET
里的每个包添加<build_depend>
和<exec_depend>
- 如果 MSG_DEP_SET 不为空,则以下依赖项已被引入,但仍然可以确定地声明:
在<exec_depend>
里添加message_runtime
在这个包的CMakeLists.txt
里:
- 在
find_package(catkin REQUIRED COMPONENTS ...)
添加message_generation
和MSG_DEP_SET
里的每个包 - 在
catkin_package(CATKIN_DEPENDS ...)
里添加message_runtime
和MSG_DEP_SET
里的每个包 - 取消对应的
add_*_files
注释,并添加.msg/.srv/.action
文件 - 取消
generate_messages
的注释,并在generate_messages(DEPENDENCIES ...)
添加MSG_DEP_SET
里的每个包
任何在msg文件夹下的.msg文件都会生成ros所支持的语言的代码,下面是以/home/lyq/catkin_ws/src/beginner_tutorials
为例,请将的catkin_ws
换成自己的catkin_workspace
根路径,beginner_tutorials
换成自己的包的根路径。
参考wiki创建msg和srv教程2
写一个Publisher Node:src/talker.cpp
//'ros/ros.h'是一个方便的头文件,它包含了使用ros系统中最常见的公共部分所必需的所有头文件。
#include "ros/ros.h"
//由于使用到`std_msgs/String`的消息类型,需要包含对应的头文件`std_msgs/String.h`
#include "std_msgs/String.h"
#include <sstream>
/**
* This tutorial demonstrates simple sending of messages over the ROS system.
*/
int main(int argc, char **argv)
{
ros::init(argc, argv, "talker"); //初始化ros并定义节点名称。
/**
* NodeHandle 是和 ROS system 通信的主要接口。
* NodeHandle 是完全初始化节点的地方。
* 对应的解构的 NodeHandle 会释放node使用的所有资源.
*/
ros::NodeHandle n;
/**
* 使用 advertise() 函数告诉ros你要发布的主题名称和发布的方式。
* 这会调用 ROS master node,登记(registry)哪个节点在发布/订阅主题。
* 在 advertise() 函数调用完成后,ROS master node会注意哪些节点尝试订阅这个主题,
* 这些节点会分别和发布这个主题的节点建立一个点对点的连接。
* advertise() 函数返回一个 Publisher 对象,通过它可以调用 publish() 函数来发布消息。
* 当这个 Publisher 对象的所有副本都被 destroyed 时,这个主题会被自动取消广播。
*
* advertise() 函数的第二个参数是用来发布消息的消息队列 message queue 的长度
* 如果消息的发布速度比我们发送它们的速度快,这里的数字指定在丢弃一些消息之前要缓冲多少消息。
*/
// 发布一个名为chatter的主题,消息类型是std_msgs::String
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
//定义循环频率,从最后一次调用Rate::sleep()开始计算时间。这里定义为10Hz
ros::Rate loop_rate(10);
/**
* A count of how many messages we have sent. This is used to create
* a unique string for each message.
*/
int count = 0;
while (ros::ok())
{
/**
* This is a message object. You stuff it with data, and then publish it.
*/
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
ROS_INFO("%s", msg.data.c_str()); //ROS_INFO
// publish() 的参数类型必须和前面调用的 advertise<>() 相同
chatter_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
在主函数开始必须用ros::init()
先初始化ros系统,ros::init()
有两种方式:
- ros::init(argc, argv):通过命令行参数映射节点名称
- ros::init(argc, argv,"<node_name>"):直接通过代码自己定义节点名称
关于消息队列message queue,wiki教程中有说道:
- If messages are published more quickly than we can send them, the number here specifies how many messages to buffer up before throwing some away.
- The second argument is the size of our publishing queue.In this case if we are publishing too quickly it will buffer up a maximum of 1000 messages before beginning to throw away old ones.
关于ros::ok():
关于ros::spinOnce():
在这个程序里不重要,因为没有回调函数callbacks。如果要订阅主题,需要调用这个函数,否则不会调用回调函数。
写一个Subscriber Node:src/listener.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
/**
* This tutorial demonstrates simple receipt of messages over the ROS system.
*/
// 当一个新的消息到达chatter主题时,会调用这个回调函数,消息通过一个boost shared_ptr指针传递。
// https://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener");
ros::NodeHandle n;
/**
* subscribe() 函数告诉 ROS 你想要订阅一个特定的主题。
* 这会调用 ROS master node,登记(registry)哪个节点在发布/订阅主题。
* 消息被传递到一个回调函数,在这里是 chatterCallback
* subscribe() 函数返回一个 Subscriber 对象,直到你取消订阅前要保证它的存在。
* 当这个对象的所有副本被销毁时,回调函数会自动取消订阅
*
* subscribe() 函数的第二个参数是消息队列,第三个参数是回调函数名称
* 如果消息到达的速度大于我们处理的速度,达到最大mq长度时会丢弃旧的消息。
*/
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
/**
* ros::spin() 会进入一个循环, 产生 callbacks.
* 在ros1,所有的回调都会在这个线程(the main one)里调用。
* 当按下Ctrl-C或节点被master关闭,ros::spin()会退出。
*/
ros::spin();
return 0;
}
ros:spin()的作用:
ros:spin()不会返回,一旦接受到数据就会执行回调函数。注意回调函数是ros:spin()调用的。
编译节点
在这个包的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)
-
这会产生2个可执行文件 talker 和 listener,默认会放在你的
devel space
的对应包目录下路径在~/catkin_ws/devel/lib/<package name>
. -
add_dependencies()用法:
第一个参数是要编译生成的target(这里是可执行文件),其他是这个target的依赖。告诉编译器先编译其他依赖,再编译target。
写一个Service Node:src/add_two_ints_server.cpp
#include "ros/ros.h"
//`beginner_tutorials/AddTwoInts.h`是从`.srv`文件生成的头文件
#include "beginner_tutorials/AddTwoInts.h"
// 注意这里的参数类型: Request 和 Response,并且是用指针传递
bool add(beginner_tutorials::AddTwoInts::Request &req,
beginner_tutorials::AddTwoInts::Response &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节点
ros::NodeHandle n; // 节点句柄
// 发布服务,第一个参数是服务的名称,第二个参数是调用的函数
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
在这里ros::spin()的作用和subcriber中的作用类似,一旦调用不会返回,当接收到数据时自动调用add函数。
写一个Client Node:src/add_two_ints_client.cpp
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
#include <cstdlib>
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<beginner_tutorials::AddTwoInts>("add_two_ints");
beginner_tutorials::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
// call()函数调用了服务,调用成功会返回true并且改变srv.response。
// 如果失败,srv.response不会改变。
if (client.call(srv))
{
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
}
else
{
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}
return 0;
}
编译节点
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)
- 生成2个可执行文件,默认放在
devel space
下对应的包目录里,默认是~/catkin_ws/devel/lib/<package name>
。