1. 1. ROS(Robot Operation System)新手村速查手册
    1. 1.1. 基本概念
      1. 1.1.1. Workspace
      2. 1.1.2. Packege
      3. 1.1.3. Node
      4. 1.1.4. Topic
        1. 1.1.4.1. 为什么呢
        2. 1.1.4.2. 图形化结点状态
      5. 1.1.5. Service
      6. 1.1.6. rosparam
      7. 1.1.7. rqt_console
      8. 1.1.8. ros_launch
    2. 1.2. 具体应用
      1. 1.2.1. msg and srv
        1. 1.2.1.1. msg
        2. 1.2.1.2. srv
      2. 1.2.2. Publisher and Subscriber
        1. 1.2.2.1. Publisher
        2. 1.2.2.2. Subscriber
        3. 1.2.2.3. 使用
      3. 1.2.3. Service and Client
        1. 1.2.3.1. Service
        2. 1.2.3.2. Client
        3. 1.2.3.3. 使用
    3. 1.3. 实用工具
      1. 1.3.1. rosed
      2. 1.3.2. .bag file
        1. 1.3.2.1. .bag数据回放与分析
          1. 1.3.2.1.1. 直接播放
          2. 1.3.2.1.2. 使用ros_readbagfile
          3. 1.3.2.1.3. 方法二的好处
      3. 1.3.3. roswtf

ROS(Robot Operation System)新手村速查手册

基本概念

Workspace

Workspace间彼此相互独立,包含Package
进入目录,创建src文件夹后(空的就可以),可以使用”catkin_make [targets] [-DCMAKE_VARIBLES…]”来构建项目,该命令可被视作于”mkdir build;cd build;cmake;make;make install”

Packege

包含设置、配置、结点信息等,至少应该包含package.xml文件和CMakeList.txt文件

1
2
# 创建新package
catkin_create_pkg beginner_tutorials std_msgs rospy roscpp

Node

是一个可执行文件,通过ROS与其他Node直接通信
rosout:相当于stdout/stderr
roscore:相当于主结点+rosout+parameter server
可以使用如下命令运行某包下某结点

1
2
rosrun turtlesim turtlesim_node
# 这个时候你的屏幕上会出现一个乌龟

Topic

运行刚才的乌龟,打开一个新窗口并输入

1
rosrun turtlesim turtle_teleop_key

这个时候就可以用键盘操作乌龟了

为什么呢

1
2
rosrun rqt_graph rqt_graph
# 这个node可以反应Node之间的交互关系

如果运行了乌龟结点和乌龟跑跑结点,可以在新的弹出窗口看到他们被列了出来,中间通过单箭头连接,箭头上的条件为形如”/xxx/xxx”的文字
“/xxx/xxx”就是Topic,两个结点之间正在通过这个Topic进行通信
箭头指向订阅者,在本例中即乌龟订阅了乌龟跑跑结点

1
2
3
4
5
6
7
8
9
10
rostopic echo "/xxx/xxx"
# 使用这个来偷听topic内容
rostopic type "/xxx/xxx"
# 使用这个看他们的密语格式 type
rostopic show "/xxx/xxx"
# 使用这个来细看他们的密语格式
rostopic pub "/xxx/xxx" type msg
# 使用这个发送密语 使用参数-1可以保证发送一次后自动退出
rostopic hz "/xxx/xxx"
# 查看topic的性能

图形化结点状态

1
rosrun rqt_plot rqt_plot

打开之后输入要打印的topic,即可以坐标轴的形式查看Topic中的信息

Service

为Node提供另一种数据交互方式,允许Node发送request接收response

1
2
3
4
5
6
7
8
9
10
rosservice list
# 查看所有service信息
rosservice call
# 向service发送请求(可附加参数)
rosservice type
# 打印type
rosservice find
# 通过type反查service
rosservice uri
# 查找service的uri

比如可以

1
2
3
rosservice call /spawn 2 2 0.2 ""
# 向/spawn发送 “在(2,2)创建一个角度0.2的turtle“ 的请求
# resp:新tuetle的名字

rosparam

可以理解为ROS提供的字典服务,Service将数据存放于此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rosparam set /turtlesim/background_r 150
rosservice call /clear
# 此时背景会变为紫色(rgb的r值变为150)
rosparam get /turtlesim/background_b
# 可以查询当前背景rbg的b值


rosparam dump [file_name] [namespace]
# 下载到文件
rosparam load [file_name] [namespace]
# 从文件加载
rosparam delete
# 删除某一参数
rosparam list
# 查看所有list的名称

rqt_console

rqt_console: 可以展示Node输出内容,在下方的
rqt_logger_level: 可以更改Logger的等级(Debug\Info\Warn\Error\Fatal)

ros_launch

在Node下创建文件夹launch,并在里面编写launch文件,即可使用roslaunch,使用后会自动根据launch脚本内容进行初始化

1
roslaunch beginner_tutorials turtlemimic.launch

具体应用

msg and srv

msg

基本使用步骤:

  1. 在Node目录(roscd)下建立xxx.msg文件,并写入消息格式

  2. 进入package.xml,修改以下内容

    1
    2
    <build_depend>message_generation</build_depend>
    <exec_depend>message_runtime</exec_depend>

    其中,generation在构建时起作用,runtime在运行时起作用

  3. 进入CMakeLists.txt

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    find_package(
    message_generation
    //如果有先前的东西不要删除
    )
    catkin_package(
    CATKIN_DEPENDS message_runtime
    )
    add_message_files(
    //注释里有
    FILES
    //你的消息文件
    )
    generate_messages(
    //这个应该会有,取消注释即可
    DEPENDENCIES
    std_msgs
    )
  4. 至此,消息创建完成,使用rosmsg show /查看是否创建成功,如果打印了刚才输入的消息体结构,则为成功

srv

  1. srv和msg使用一套构建和运行依赖,虽然他们叫messagexxx,但是同时对msg和src可用

  2. 进入CMakeList.txt

    1
    2
    3
    4
    5
    add_service_files(
    //注释里有
    FILES
    //你的消息文件
    )
  3. 使用rosmsg show /查看是否创建成功,如果打印了刚才输入和输出的消息体结构,则为成功

Publisher and Subscriber

  • 在ros中,stdin、stdout、stderr会被定向到运行rosrun的终端窗口,但建议还是使用ROS_INFO或其他ROS内建函数
  • CMakeListd.txt: include_directories(include ${catkin_INCLUDE_DIRS} ${PCL_INCLUDE_DIRS})

Publisher

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
#include "ros/ros.h"
#include "std_msgs/String.h"//ros的消息体
#include <sstream>
int main(int argc, char **argv){
ros::init(argc,argv,"name");
/**
* 传入argc和argv是为了检查传入参数是否有ros参数被传入(好拗口)
* 参数3是当前结点的名字,即rosrun <package> <nodeName>
*/
ros::NodeHandle n;
/**
* NodeHandle和ros系统进行交互,当第一个NodeHandle被创建时,Node被创建,当最后一个NodeHandler被销毁时,Node被销毁
*/
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
/**
* advertise函数返回一个可以publish<?>参数,Topic为chatter,最大size为1000的ros::Publisher对象,返回后可以持续调用chatter_pub.publish(parameter)函数进行消息发布
* 当advertise函数被调用时,masternode会通知每一个订阅了该topic的node,该结点会和当前结点建立连接,当所有的Publisher对象被销毁时,topic会自动变为不活跃态
*/
ros::Rate loop_rate(10);
/**
* 消息发送频率,单位为Hz
*/
int count=0;
/**
* 消息发送条数
*/
while(ros::ok()){
/**
* SIGINT、同名结点上线,ros关机,所有handles被摧毁时不ok
*/
std_msgs::String msg;
std::stringstream ss;
ss<<"hello world"<<count;
msg.data=ss.str();
//msg为消息体,形式不限,上文仅为范例
ROS_INFO("%s",msg.data.c_str());

chatter_pub.publish(msg);
ros::spinOnce();

loop_rate.sleep();
/**
* 必须写这一句,不然程序无法按照设想方式运行
*/
}

}

Subscriber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "ros/ros.h"
#include "std_msgs/String.h"//ros的消息体
#include <sstream>
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,"name");
ros::NodeHandle n;
//上文意义间Publisher,一样的
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
/**
* 建立一个subscribe对象,可以调用subscribe函数进行监听
* subscribe(topic,limit,function)
* 调用一次,监听topic,最多接受limit条,接受后消息被传入function
*/
ros::spin();
//类似上文的while(ros::ok()) 会一直subscribe,但不会陷入机械论陷阱
return 0;
}

使用

  1. 修改CMakeList.txt,加入

    1
    2
    3
    4
    include_directories(
    ${catkin_INCLUDE_DIRS}
    )
    # 不然#include "ros/ros.h"引不进来
  2. 编译

    1
    2
    roscd <packageName>
    catkin_make
  3. 启动ros环境

    1
    roscore
  4. rosrun启动节点(打开新终端后启动)

Service and Client

Service

Service=srv+Subscriber,也是作为一个Node在工作,拥有自己的uri,可以收到req,回复res
req和res的格式在srv文件里被定义,下面将演示如何使用

  1. 这是一个srv file,规定入参为两个int64的整数,出参为一个int64的整数

    1
    2
    3
    4
    5
    # srv file
    int64 a
    int64 b
    ---
    int64 sum
  2. 之后我们可以编写一个cpp文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #include "ros/ros.h"
    #include "test/AddTwoInts.h"
    //.h文件会根据srv文件自动生成,不用自己编写

    bool add(test::AddTwoInts::Request &req,
    test::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();
    //文件基本结构和listener一致
    return 0;
    }

Client

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 "test/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");
//指出这是对于哪一个Service的Client
beginner_tutorials::AddTwoInts srv;
srv.request.a = atoll(argv[1]);//atoll 将字符串转换为ll
srv.request.b = atoll(argv[2]);
if (client.call(srv))//向client发出请求
{
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
}
else
{
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}

return 0;
}

使用

修改CMakeList.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)

之后运行rosrun就可以了

实用工具

rosed

.bag file

录制一段时间内的topic:msg,生成.bag文件以在今后重复发送
开始录制:

1
2
3
4
rosbag record -a
# -a:记录所有topic
rosbag record -O subset <topic1> <topic2>.....
# 使用-O表示正在记录子集,subset后跟随需要记录的topic名

查看内容:

1
rosbag info <filename>

重复播放:

1
2
rosbag play <filename>
rosbag play -r n

局限性:
一些操作对时间间隔非常敏感,因此rosbag录制的脚本有可能难以完全复制操作

.bag数据回放与分析

直接播放
  1. 打开终端窗口,设定需要监听的topic,并将其输出到文件
    1
    2
    3
    rostopic echo /obs1/gps/fix | tee topic1.yaml
    # 将/obs1/gps/fix的内容输出到topic1.yaml文件里
    # tee命令 输出到stdout和文件
    要监听几个topic到几个文件,就打开几个终端
  2. 播放rosbag
    1
    2
    time rosbag play --immediate demo.bag --topics /topic1 /topic2
    # time:记录运行时间
    之后就可以在相应的文件里看到rosbag中对应topic的内容了
使用ros_readbagfile

点此查看配置教程
读取:

1
2
3
4
# 仅输出到stdout
time ros_readbagfile <mybagfile.bag> [topic1] [topic2] > tee topics.yaml
# 输出到文件和stdout
time ros_readbagfile <mybagfile.bag> [topic1] [topic2] | tee topics.yaml
方法二的好处
  • ros_readbagfile读取速度快
  • ros_readbagfile可以一次提取多个topic内容

roswtf

在遇到困难时使用roswtf,可以协助检查潜在的逻辑/设置问题