@TOC
初学ROS做的笔记,权当备忘录
旨在梳理beginner level的常用操作,以便后续参考。
1.安装并配置ROS环境
1.1安装
- 设置source.list
首先Ctrl+Alt+T打开终端,输入以下指令:
1
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
- 设置keys
1
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
也可以使用另外的仓库==hkp://pgp.mit.edu:80 or hkp://keyserver.ubuntu.com:80==代替
- 安装 首先update源:
1
sudo apt-get update
然后安装全功能包:
1
sudo apt-get install ros-kinetic-desktop-full
- 设置环境变量
1
2
echo "source /opt/ros/kinetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
- 安装依赖
1
sudo apt install python-rosdep python-rosinstall python-rosinstall-generator python-wstool build-essential
- 安装ros-dep
1
sudo apt install python-rosdep
- 初始化ros-dep
1
2
sudo rosdep init
rosdep update
1.2 管理环境
确认ROS的环境变量是否设置好了,ROS_PACKAGE_PATH.
1
printenv | grep ROS
如果没有设置,需要source(根据实际安装的版本来)
1
source /opt/ros/kinetic/setup.bash
1.3 创建ROS空间
创建一个catkin workspace空间
1
2
3
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/
catkin_make #编译指令
2.ROS文件系统介绍
3.创建并编译ROS程序包
在当前目录创建一个package的命令如下,也可以同时指明这个package所依赖的其他package:
1
roscreate-pkg [package_name] [depend1] [depend2] [depend3]
3.1 创建新的ROS package
切换到~/catkin_ws 并创建package
1
2
cd ~/ros_workspace
roscreate-pkg beginner_tutorials std_msgs rospy roscpp
创建package成功以后可以使用roscd切换到package目录
1
roscd beginner_tutorials
如果不能使用roscd命令,则需要再次source
1
source ./devel/setup.sh
3.2编译package
在catkin_ws目录下
1
catkin_make
build 目录是build space的默认所在位置,同时cmake 和 make也是在这里被调用来配置并编译你的程序包。devel 目录是devel space的默认所在位置, 同时也是在你安装程序包之前存放可执行文件和库文件的地方。
4.理解ROS节点和话题
安装一个轻量级的模拟器:
1
sudo apt-get install ros-kinetic-ros-tutorials
4.1图的概念
-
Nodes:节点,一个节点即为一个可执行文件,它可以通过ROS与其它节点进行通信。
-
Messages:消息,消息是一种ROS数据类型,用于订阅或发布到一个话题。
-
Topics:话题,节点可以发布消息到话题,也可以订阅话题以接收消息。
-
Master:节点管理器,ROS名称服务 (比如帮助节点找到彼此)。
-
rosout: ROS中相当于stdout/stderr。
-
roscore: 主机+ rosout + 参数服务器 (参数服务器会在后面介绍)。
4.2节点
一个节点其实只不过是ROS程序包中的一个可执行文件。ROS节点可以使用ROS客户库与其他节点通信。节点可以发布或接收一个话题。节点也可以提供或使用某种服务。
(节点是ros中非常重要的一个概念,为了帮助初学者理解这个概念,这里举一个通俗的例子:
例如,咱们有一个机器人,和一个遥控器,那么这个机器人和遥控器开始工作后,就是两个节点。遥控器起到了下达指 令的作用;机器人负责监听遥控器下达的指令,完成相应动作。从这里我们可以看出,节点是一个能执行特定工作任 务的工作单元,并且能够相互通信,从而实现一个机器人系统整体的功能。在这里我们把遥控器和机器人简单定义为两个节点,实际上在机器人中根据控制器、传感器、执行机构等不同组成模块,还可以将其进一步细分为更多的节点,这个是根据用户编写的程序来定义的。)
4.3客户端库
ROS客户端库允许使用不同编程语言编写的节点之间互相通信:
- rospy = python 客户端库
- roscpp = c++ 客户端库
4.4 roscore
roscore 是你在运行所有ROS程序前首先要运行的命令。
1
roscore
4.5使用rosnode
rosnode 显示当前运行的ROS节点信息。rosnode list 指令列出活跃的节点:
1
rosnode list
rosnode info 命令返回的是关于一个特定节点的信息。
1
$ rosnode info /rosout
4.6使用rosrun
rosrun 允许你使用包名直接运行一个包内的节点(而不需要知道这个包的路径)。用法:
1
rosrun [package_name] [node_name]
用一下命令运行一个turtlesim_node
1
rosrun turtlesim turtlesim_node
5.理解ROS服务和参数
5.1 ROS Services
服务(services)是节点之间通讯的另一种方式。服务允许节点发送请求(request) 并获得一个响应(response) rosservice可以很轻松的使用 ROS 客户端/服务器框架提供的服务。rosservice提供了很多可以在topic上使用的命令,如下所示: 使用方法:
1
2
3
4
5
rosservice list #输出可用服务的信息
rosservice call #调用带参数的服务
rosservice type #输出服务类型
rosservice find #依据类型寻找服务find services by service type
rosservice uri #输出服务的ROSRPC uri
1
rosservice call [service] [args]
1
rosservice type [service]
5.2 Using rosparam
rosparam使得我们能够存储并操作ROS 参数服务器(Parameter Server)上的数据。参数服务器能够存储整型、浮点、布尔、字符串、字典和列表等数据类型。rosparam使用YAML标记语言的语法。一般而言,YAML的表述很自然:1 是整型, 1.0 是浮点型, one是字符串, true是布尔, [1, 2, 3]是整型列表, {a: b, c: d}是字典. rosparam有很多指令可以用来操作参数,如下所示: 使用方法:
1
2
3
4
5
6
rosparam set #设置参数
rosparam get #获取参数
rosparam load #从文件读取参数
rosparam dump #向文件中写入参数
rosparam delete #删除参数
rosparam list #列出参数名
1
2
rosparam set [param_name]
rosparam get [param_name]
1
2
rosparam dump [file_name]
rosparam load [file_name] [namespace]
6. 使用rqt_console和roslaunch
安装:
1
sudo apt-get install ros-<distro>-rqt ros-<distro>-rqt-common-plugins ros-<distro>-turtlesim
6.1使用rqt_console和rqt_logger_level
开两个终端分别运行:
1
rosrun rqt_console rqt_console
1
rosrun rqt_logger_level rqt_logger_level
再开一个新的终端运行:
1
rosrun turtlesim turtlesim_node
6.2使用roslaunch
roslaunch可以用来启动定义在launch文件中的多个节点。用法:
1
roslaunch [package] [filename.launch]
先切换到beginner_tutorials程序包目录下:
1
roscd beginner_tutorials
如果roscd执行失败了,记得设置你当前终端下的ROS_PACKAGE_PATH环境变量,设置方法如下
1
export ROS_PACKAGE_PATH=~/catkin_ws/:$ROS_PACKAGE_PATH
创建一个launch文件夹
1
2
mkdir launch
cd launch
创建一个名为turtlemimic.launch的launch文件,件里面
1
touch turtlemimic.launch
并复制粘贴以下内容到该文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<launch> <!--以launch标签开头以表明这是一个launch文件 -->
<!--
在这里我们创建了两个节点分组并以'命名空间(namespace)'标签来区分,其中一个名为turtulesim1,另一个名为turtlesim2,两个组里面都使用相同的turtlesim节点并命名为'sim'。这样可以让我们同时启动两个turtlesim模拟器而不会产生命名冲突。
-->
<group ns="turtlesim1">
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
</group>
<group ns="turtlesim2">
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
</group>
<!--
在这里我们启动模仿节点,并将所有话题的输入和输出分别重命名为turtlesim1和turtlesim2,这样就会使turtlesim2模仿turtlesim1。
-->
<node pkg="turtlesim" name="mimic" type="mimic">
<remap from="input" to="turtlesim1/turtle1"/>
<remap from="output" to="turtlesim2/turtle1"/>
</node>
</launch>
现在让我们通过roslaunch命令来启动launch文件:
1
roslaunch beginner_tutorials turtlemimic.launch
在将会有两个turtlesims被启动,然后我们在一个新终端中使用rostopic命令发送速度设定消息
1
rostopic pub /turtlesim1/turtle1/command_velocity turtlesim/Velocity -r 1 -- 2.0 -1.8
7.创建ROS消息和ROS服务
-
消息(msg): msg文件就是一个描述ROS中所使用消息类型的简单文本。它们会被用来生成不同语言的源代码。
-
服务(srv): 一个srv文件描述一项服务。它包含两个部分:请求和响应。
msg文件存放在package的msg目录下,srv文件则存放在srv目录下。
msg文件实际上就是每行声明一个数据类型和变量名。可以使用的数据类型如下:
- int8, int16, int32, int64 (plus uint*)
- float32, float64
- string
- time, duration
- other msg files
- variable-length array[] and fixed-length array[C]
在ROS中有一个特殊的数据类型:Header,它含有时间戳和坐标系信息。在msg文件的第一行经常可以看到Header header的声明.
7.1 使用msg
在之前创建的package里定义新的消息
1
2
3
cd ~/catkin_ws/src/beginner_tutorials
mkdir msg
echo "int64 num" > msg/Num.msg
接下来,还有==关键==的一步:我们要确保msg文件被转换成为C++,Python和其他语言的源代码: 查看package.xml, 确保它包含一下两条语句:
1
2
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
在 CMakeLists.txt文件中,利用find_packag函数,增加对message_generation的依赖,这样就可以生成消息了。 你可以直接在COMPONENTS的列表里增加message_generation,就像这样:
1
2
3
4
5
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
message_generation
)
还有运行依赖
1
2
3
4
5
6
7
8
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES beginner_tutorials
CATKIN_DEPENDS roscpp std_msgs
# DEPENDS system_lib
message_runtime
)
1
2
3
4
add_message_files(
FILES
Num.msg
)
手动添加.msg文件后,我们要确保CMake知道在什么时候重新配置我们的project。 确保添加了如下代码:
1
generate_messages()
具体文件见GitHub:CMakeList.txt
使用
1
rosmsg show [message type]
具体方法
1
rosmsg show beginner_tutorials/Num #rosmsg show Num
7.2 使用msv
在刚刚那个package中创建一个服务:
1
2
roscd beginner_tutorials
mkdir srv
这次我们不再手动创建服务,而是从其他的package中复制一个服务。 roscp是一个很实用的命令行工具,它实现了将文件从一个package复制到另外一个package的功能。 使用方法:
1
roscp [package_name] [file_to_copy_path] [copy_path]
从rospy_tutorials package中复制一个服务文件了:
1
roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv
还有很关键的一步:我们要确保srv文件被转换成C++,Python和其他语言的源代码。 还是再CMakeLists.txt中增加:
1
2
3
4
add_service_files(
FILES
AddTwoInts.srv
)
使用
1
rossrv show <service type>
1
rossrv show beginner_tutorials/AddTwoInts # rossrv show AddTwoInts
修改MakeLists.txt
1
2
3
4
generate_messages(
DEPENDENCIES
std_msgs
)
编译
1
2
cd ~/catkin_ws/
catkin_make
8.编写简单的消息发布器和订阅器并测试(C++)
8.1 编写消息发布器
『节点』(Node) 是指 ROS 网络中可执行文件。接下来,我们将会创建一个发布器节点(“talker”),它将不断的在 ROS 网络中广播消息 切换到之前创建的 beginner_tutorials package 路径下:
1
cd ~/catkin_ws/src/beginner_tutorials
创建一个src文件
1
mkdir -p ~/catkin_ws/src/beginner_tutorials/src
在src中新建一个talker.cpp文件,复制talker.cpp的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//ros/ros.h 是一个实用的头文件,它引用了 ROS 系统中大部分常用的头文件。
#include "ros/ros.h"
//这引用了 std_msgs/String 消息, 它存放在 std_msgs package 里,是由 String.msg 文件自动生成的头文件。需要关于消息的定义,可以参考 msg 页面。
#include "std_msgs/String.h"
#include <sstream>
int main(int argc,char **argv)
{
/*
初始化 ROS 。它允许 ROS 通过命令行进行名称重映射——然而这并不是现在讨论的重点。在这里,我们也可以指定节点的名称——运行过程中,节点的名称必须唯一。
这里的名称必须是一个 base name ,也就是说,名称内不能包含 / 等符号
*/
ros::init(argc,argv,"talker");
/*
为这个进程的节点创建一个句柄。第一个创建的 NodeHandle 会为节点进行初始化,最后一个销毁的 NodeHandle 则会释放该节点所占用的所有资源。
*/
ros::NodeHandle n;
/*
chatter 话题的节点,将要有数据发布。第二个参数是发布序列的大小。如果我们发布的消息的频率太高,缓冲区中的消息在大于 1000 个的时候就会开始丢弃先前发布的消息。
NodeHandle::advertise() 返回一个 ros::Publisher 对象,它有两个作用: 1) 它有一个 publish() 成员函数可以让你在topic上发布消息; 2) 如果消息类型不对,它会拒绝发布。
*/
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter",1000);
/*
ros::Rate 对象可以允许你指定自循环的频率。它会追踪记录自上一次调用 Rate::sleep() 后时间的流逝,并休眠直到一个频率周期的时间。
在这个例子中,我们让它以 10Hz 的频率运行。
*/
ros::Rate loop_rate(10);
/*
roscpp 会默认生成一个 SIGINT 句柄,它负责处理 Ctrl-C 键盘操作——使得 ros::ok() 返回 false。
如果下列条件之一发生,ros::ok() 返回false:
SIGINT 被触发 (Ctrl-C)
被另一同名节点踢出 ROS 网络
ros::shutdown() 被程序的另一部分调用
节点中的所有 ros::NodeHandles 都已经被销毁
一旦 ros::ok() 返回 false, 所有的 ROS 调用都会失效。
*/
int count=0;
while(ros::ok())
{
/*
我们使用一个由 msg file 文件产生的『消息自适应』类在 ROS 网络中广播消息。现在我们使用标准的String消息,它只有一个数据成员 "data"。当然,你也可以发布更复杂的消息类型。
*/
std_msgs::String msg;
std::stringstream ss;
ss<<"hello world"<<count;
msg.data=ss.str();
/*
ROS_INFO 和其他类似的函数可以用来代替 printf/cout 等函数
*/
ROS_INFO("%s",msg.data.c_str());
chatter_pub.publish(msg);//这里,我们向所有订阅 chatter 话题的节点发送消息。
/*
在这个例子中并不是一定要调用 ros::spinOnce(),因为我们不接受回调。然而,如果你的程序里包含其他回调函数,最好在这里加上 ros::spinOnce()这一语句,否则你的回调函数就永远也不会被调用了。
*/
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
8.2 编写订阅器
在src下新建listener.cpp文件,复制listener.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "ros/ros.h"
#include "std_msgs/String.h"
/*
这是一个回调函数,当接收到 chatter 话题的时候就会被调用。消息是以 boost shared_ptr 指针的形式传输,这就意味着你可以存储它而又不需要复制数据。
*/
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;
/*
告诉 master 我们要订阅 chatter 话题上的消息。当有消息发布到这个话题时,ROS 就会调用 chatterCallback() 函数。第二个参数是队列大小,以防我们处理消息的速度不够快,当缓存达到 1000 条消息后,再有新的消息到来就将开始丢弃先前接收的消息。
NodeHandle::subscribe() 返回 ros::Subscriber 对象,你必须让它处于活动状态直到你不再想订阅该消息。当这个对象销毁时,它将自动退订 chatter 话题的消息。
有各种不同的 NodeHandle::subscribe() 函数,允许你指定类的成员函数,甚至是 Boost.Function 对象可以调用的任何数据类型。roscpp overview 提供了更为详尽的信息。
*/
ros::Subscriber sub = n.subscribe("chatter",1000,chatterCallback);
/*
ros::spin() 进入自循环,可以尽可能快的调用消息回调函数。如果没有消息到达,它不会占用很多 CPU,所以不用担心。一旦 ros::ok() 返回 false,ros::spin() 就会立刻跳出自循环。这有可能是 ros::shutdown() 被调用,或者是用户按下了 Ctrl-C,使得 master 告诉节点要终止运行。也有可能是节点被人为关闭的。
*/
ros::spin();
return 0;
}
编译: 在CMakeLists.txt中增加以下内容
1
2
3
4
5
6
7
include_directories(include ${catkin_INCLUDE_DIRS})
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
切换到catkin_ws目录编译
1
2
cd ~/catkin_ws
catkin_make
8.3 测试
确保roscore可用,并运行:
1
roscore
==catkin specific== 如果使用catkin,确保你在调用catkin_make后,在运行你自己的程序前,已经source了catkin工作空间下的setup.sh文件:
1
2
cd ~/catkin_ws
source ./devel/setup.bash
运行发布器
1
rosrun beginner_tutorials talker
运行订阅器
1
rosrun beginner_tutorials listener
9.编写简单的服务器和客户端并测试(C++)
9.1 服务器
进入先前你在catkin workspace教程中所创建的beginner_tutorials包所在的目录:
1
cd ~/catkin_ws/src/beginner_tutorials
创建src/add_two_ints_server.cpp文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "ros/ros.h"
//beginner_tutorials/AddTwoInts.h是由编译系统自动根据我们先前创建的srv文件生成的对应该srv文件的头文件。
#include "beginner_tutorials/AddTwoInts.h"
/*
这个函数提供两个int值求和的服务,int值从request里面获取,而返回数据装入response内,这些数据类型都定义在srv文件内部,函数返回一个boolean值。
*/
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::NodeHandle n;
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
9.2 客户端
创建src/add_two_ints_client.cpp文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#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]);
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;
}
编辑一下beginner_tutorials里面的CMakeLists.txt 添加下面代码
1
2
3
4
5
6
7
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)
编译
1
2
cd ~/catkin_ws
catkin_make
9.3 测试
- 运行service
1
rosrun beginner_tutorials add_two_ints_server
- 运行client
1
rosrun beginner_tutorials add_two_ints_client 1 3