首页 Ros 通信机制
文章
取消

Ros 通信机制

1. 话题通信

1.1 通信原理

话题在ROS中使用最为频繁

流程图

  1. talker 通过1234端口使用RPC(romote produce call 远程过程调用)向Master注册发布者信息,Master会将节点的注册信息加入到注册列表中
  2. listener 向 Master 注册订阅者信息
  3. Master 信息匹配,根据listener的订阅主题在注册列表中进行查找
    1. 无发布者,等待加入
    2. 有发布者,通过RPC向Listener发送Talker的RPC地址信息
  4. Listener 通过RPC地址信息 发送链接请求
  5. Talker继续通过RPC发送确认链接请求,并包括自身TCP地址信息
  6. Listener使用TCP尝试连接
  7. Talker使用TCP发送话题消息数据

乌龟示例中的话题通信

启动乌龟示例

1
2
3
$ roscore 
$ rosrun turtlesim turtlesim_node 
$ rosrun turtlesim turtle_teleop_key

可以查看当前运行的节点和话题

1
$ rosrun rqt_graph rqt_graph

image-20221027140957767

1.2 话题常用命令 rostopic

  1. rostopic list 所有活动状态的话题名称

    1
    2
    3
    4
    5
    6
    
    $ rostopic list
    /rosout
    /rosout_agg
    /turtle1/cmd_vel
    /turtle1/color_sensor
    /turtle1/pose
    
  2. rostopic type [topic] 类型

    1
    2
    
    $ rostopic type /turtle1/cmd_vel 
    geometry_msgs/Twist
    
  3. rostopic echo [topic] 输出该话题上发布的数据

    image-20221027142057859

  4. rostopic pub /话题名称 消息类型 消息内容

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

    以10hz的频率发送运动信息

1.3 话题通信基本操作 c++ 实现(自定义消息)

定义消息文件

src/下创建catkin_package 这里命名为 p2_topic,创建文件夹msg,该文件夹专门放自定义消息,创建文件 Person.msg

1
2
3
string name
uint16 age
float64 height

编辑配置文件

package.xml 中添加编译依赖和执行依赖

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

CMakeLists.txt 编辑msg相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
  message_generation
)
## 配置msg源文件
add_message_files(
  FILES
  Person.msg
)
## 生成消息时依赖于std_msgs
generate_messages(
  DEPENDENCIES
  std_msgs
)
## 执行时依赖
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES p2_topic
 CATKIN_DEPENDS roscpp std_msgs message_runtime
#  DEPENDS system_lib
)

编译

c++需要调用的中间文件生成在 devel/include/包名/xxx.h

image-20221031163135645

发布者针对指定话题发布特定数据类型的消息

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
/*
    需求: 循环发布人的信息

*/

#include "ros/ros.h"
#include "p2_topic/Person.h"

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");

    //1.初始化 ROS 节点
    ros::init(argc,argv,"talker_person");

    //2.创建 ROS 句柄
    ros::NodeHandle nh;

    //3.创建发布者对象
    ros::Publisher pub = nh.advertise<p2_topic::Person>("chatter_person",1000);

    //4.组织被发布的消息,编写发布逻辑并发布消息
    p2_topic::Person p;
    p.name = "sunwukong";
    p.age = 2000;
    p.height = 1.45;

    ros::Rate r(1);
    while (ros::ok())
    {
        pub.publish(p);
        p.age += 1;
        ROS_INFO("我叫:%s,今年%d岁,高%.2f米", p.name.c_str(), p.age, p.height);

        r.sleep();
        ros::spinOnce();
    }

    return 0;
}

订阅者

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
/*
    需求: 订阅人的信息

*/

#include "ros/ros.h"
#include "demo02_talker_listener/Person.h"

void doPerson(const demo02_talker_listener::Person::ConstPtr& person_p){
    ROS_INFO("订阅的人信息:%s, %d, %.2f", person_p->name.c_str(), person_p->age, person_p->height);
}

int main(int argc, char *argv[])
{   
    setlocale(LC_ALL,"");

    //1.初始化 ROS 节点
    ros::init(argc,argv,"listener_person");
    //2.创建 ROS 句柄
    ros::NodeHandle nh;
    //3.创建订阅对象
    ros::Subscriber sub = nh.subscribe<demo02_talker_listener::Person>("chatter_person",10,doPerson);

    //4.回调函数中处理 person

    //5.ros::spin();
    ros::spin();    
    return 0;
}

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
add_executable(pub src/pub.cpp)
add_executable(sub src/sub.cpp)



add_dependencies(pub ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(sub ${PROJECT_NAME}_generate_messages_cpp)


target_link_libraries(pub
  ${catkin_LIBRARIES}
)
target_link_libraries(sub
  ${catkin_LIBRARIES}
)

运行roscore、sub、pub

image-20221031164910065

2. 服务通信

2.1 通信原理

img

与话题通信类似,

  • server通过rpc 向master注册
  • client 通过rpc 向 master注册
  • master进行匹配,通过rpc 向client发送server的tcp地址
  • client通过tcp地址与server进行连接,发送请求数据
  • server进行解析,返回响应结果给client

自定义服务

自定义srv文件

1
2
3
4
int32 num1
int32 num2
---
int32 sum

请求和响应用 --- 进行分割

编辑配置文件 修改内容和前文(1.3 话题通信基本操作 c++ 实现(自定义消息)一致 不再赘述

编译后 同样会生成中间文件

image-20221031173333766

C++ 实现服务通信简单示例

服务端:

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
/*
    需求: 
        编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
        客户端再解析

    服务器实现:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 ROS 句柄
        4.创建 服务 对象
        5.回调函数处理请求并产生响应
        6.由于请求有多个,需要调用 ros::spin()

*/
#include "ros/ros.h"
#include "demo03_server_client/AddInts.h"

// bool 返回值由于标志是否处理成功
bool doReq(demo03_server_client::AddInts::Request& req,
          demo03_server_client::AddInts::Response& resp){
    int num1 = req.num1;
    int num2 = req.num2;

    ROS_INFO("服务器接收到的请求数据为:num1 = %d, num2 = %d",num1, num2);

    //逻辑处理
    if (num1 < 0 || num2 < 0)
    {
        ROS_ERROR("提交的数据异常:数据不可以为负数");
        return false;
    }

    //如果没有异常,那么相加并将结果赋值给 resp
    resp.sum = num1 + num2;
    return true;


}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ROS 节点
    ros::init(argc,argv,"AddInts_Server");
    // 3.创建 ROS 句柄
    ros::NodeHandle nh;
    // 4.创建 服务 对象
    ros::ServiceServer server = nh.advertiseService("AddInts",doReq);
    ROS_INFO("服务已经启动....");
    //     5.回调函数处理请求并产生响应
    //     6.由于请求有多个,需要调用 ros::spin()
    ros::spin();
    return 0;
}

客户端:

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
/*
    需求: 
        编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
        客户端再解析

    服务器实现:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 ROS 句柄
        4.创建 客户端 对象
        5.请求服务,接收响应

*/
// 1.包含头文件
#include "ros/ros.h"
#include "demo03_server_client/AddInts.h"

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");

    // 调用时动态传值,如果通过 launch 的 args 传参,需要传递的参数个数 +3
    if (argc != 3)
    // if (argc != 5)//launch 传参(0-文件路径 1传入的参数 2传入的参数 3节点名称 4日志路径)
    {
        ROS_ERROR("请提交两个整数");
        return 1;
    }


    // 2.初始化 ROS 节点
    ros::init(argc,argv,"AddInts_Client");
    // 3.创建 ROS 句柄
    ros::NodeHandle nh;
    // 4.创建 客户端 对象
    ros::ServiceClient client = nh.serviceClient<demo03_server_client::AddInts>("AddInts");
    //等待服务启动成功
    //方式1
    ros::service::waitForService("AddInts");
    //方式2
    // client.waitForExistence();
    // 5.组织请求数据
    demo03_server_client::AddInts ai;
    ai.request.num1 = atoi(argv[1]);
    ai.request.num2 = atoi(argv[2]);
    // 6.发送请求,返回 bool 值,标记是否成功
    bool flag = client.call(ai);
    // 7.处理响应
    if (flag)
    {
        ROS_INFO("请求正常处理,响应结果:%d",ai.response.sum);
    }
    else
    {
        ROS_ERROR("请求处理失败....");
        return 1;
    }

    return 0;
}

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
add_executable(AddInts_Server src/AddInts_Server.cpp)
add_executable(AddInts_Client src/AddInts_Client.cpp)


add_dependencies(AddInts_Server ${PROJECT_NAME}_gencpp)
add_dependencies(AddInts_Client ${PROJECT_NAME}_gencpp)


target_link_libraries(AddInts_Server
  ${catkin_LIBRARIES}
)
target_link_libraries(AddInts_Client
  ${catkin_LIBRARIES}
)

image-20221031174000271

3. 参数服务器

三个角色 管理者 master 设置者talker 调用者listener

  • talker rpc发送 键值
  • listener rpc 键查询
  • master 返回值

不是为高性能而设计的,最好用于存储静态的非二进制简单数据

4. 常用命令

4.1 rosnode

1
2
3
4
5
6
rosnode ping    测试到节点的连接状态
rosnode list    列出活动节点
rosnode info    打印节点信息
rosnode machine    列出指定设备上节点
rosnode kill    杀死某个节点
rosnode cleanup    清除不可连接的节点

4.2 rostopic

1
2
3
4
5
6
7
8
9
rostopic bw     显示主题使用的带宽
rostopic delay  显示带有 header 的主题延迟
rostopic echo   打印消息到屏幕
rostopic find   根据类型查找主题
rostopic hz     显示主题的发布频率
rostopic info   显示主题相关信息
rostopic list   显示所有活动状态下的主题
rostopic pub    将数据发布到主题
rostopic type   打印主题类型

4.3 rosmsg

1
2
3
4
5
6
rosmsg show    显示消息描述
rosmsg info    显示消息信息
rosmsg list    列出所有消息
rosmsg md5    显示 md5 加密后的消息
rosmsg package    显示某个功能包下的所有消息
rosmsg packages    列出包含消息的功能包

4.4 rosservice

列出和查询相关服务

1
2
3
4
5
6
7
rosservice args 打印服务参数
rosservice call    使用提供的参数调用服务
rosservice find    按照服务类型查找服务
rosservice info    打印有关服务的信息
rosservice list    列出所有活动的服务
rosservice type    打印服务类型
rosservice uri    打印服务的 ROSRPC uri

4.5 rossrv

显示ros服务类型消息的相关命令

1
2
3
4
5
6
rossrv show    显示服务消息详情
rossrv info    显示服务消息相关信息
rossrv list    列出所有服务信息
rossrv md5    显示 md5 加密后的服务消息
rossrv package    显示某个包下所有服务消息
rossrv packages    显示包含服务消息的所有包

4.6 rosparam

1
2
3
4
5
6
rosparam set    设置参数
rosparam get    获取参数
rosparam load    从外部文件加载参数
rosparam dump    将参数写出到外部文件
rosparam delete    删除参数
rosparam list    列出所有参数

外部文件 (yaml文件)

5. 通信机制实操

5.1 话题发布

实现乌龟运动控制:两个节点,显示节点和控制节点,这里主要实现控制节点,显示节点直接调用;实现控制节点,需要知道两个节点之间通信的话题和消息。

5.1.1 话题和消息获取

获取之前,需要先启动乌龟案例:

1
2
3
$ roscore 
$ rosrun turtlesim turtlesim_node 
$ rosrun turtlesim turtle_teleop_key

image-20221110111618505

再通过rqt_graph计算图查看话题

image-20221110111726517

或者 rostopic list 直接列出话题

通过rostopic type 话题名来查看消息类型

1
2
$ rostopic type /turtle1/cmd_vel
geometry_msgs/Twist

通过 rosmsg info 消息类型来获取消息格式

1
2
3
4
5
6
7
8
9
10
$ rosmsg info geometry_msgs/Twist
geometry_msgs/Vector3 linear
  float64 x
  float64 y
  float64 z
geometry_msgs/Vector3 angular
  float64 x
  float64 y
  float64 z

可见一个是线性速度 一个角速度

5.1.2 实现发布节点

创建功能包需要依赖的功能包: roscpp rospy std_msgs geometry_msgs

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 "geometry_msgs/Twist.h"

int main(int argc, char *argv[]){
    setlocale(LC_ALL,"");
    // 节点初始化 设置名字
    ros::init(argc,argv,"control");
    // 节点句柄
    ros::NodeHandle nh;
    // advertise创建发布者对象 确定 消息类型;主题;队列长度
    ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel",1000);

    geometry_msgs::Twist msg;
    msg.linear.x = 1.0;
    msg.linear.y = 0.0;
    msg.linear.z = 0.0;
    msg.angular.x = 0.0;
    msg.angular.y = 0.0;
    msg.angular.z = 2.0;
	// 定义发送频率 一般通过 r.sleep(); 放在循环中使用
    // 如果程序执行太快(不到定义的1/10 0.1秒),会睡到0.1秒 再进行循环
    ros::Rate r(10);
	// ctrl-c 会是ok 返回false;ros::shutdown被程序的另一部分调用;nh被销毁
    while(ros::ok()){
        pub.publish(msg);
        // 处理回调函数(这里没啥用,没有回调函数嘛)
        ros::spinOnce();
        r.sleep();
    }
    return 0;

}

节点句柄的两个作用:

  • roscpp程序中,节点句柄可以启动或关闭一个ros内部节点(一个roscpp程序只能有一个节点,但一个节点能有多个节点句柄)
  • 节点句柄可以增加一层命名空间,方便编写组件

5.2 话题订阅

需求:已知显示节点,要求控制乌龟并显示当前乌龟位姿

相当于是在之前控制乌龟的前提下,要编写订阅节点来获取乌龟位姿话题发布的消息

5.2.1 话题和消息获取

话题:

1
2
3
4
5
6
7
8
9
10
11
12
zs@ubuntu:~$ rostopic list -v

Published topics:
 * /rosout_agg [rosgraph_msgs/Log] 1 publisher
 * /rosout [rosgraph_msgs/Log] 2 publishers
 * /turtle1/cmd_vel [geometry_msgs/Twist] 1 publisher
 * /turtle1/pose [turtlesim/Pose] 1 publisher
 * /turtle1/color_sensor [turtlesim/Color] 1 publisher

Subscribed topics:
 * /rosout [rosgraph_msgs/Log] 1 subscriber
 * /turtle1/cmd_vel [geometry_msgs/Twist] 1 subscriber

对应的位姿话题是 /turtle1/pose 消息类型是 turtlesim/Pose,对应的格式:

1
2
3
4
5
6
zs@ubuntu:~$ rosmsg info turtlesim/Pose 
float32 x
float32 y
float32 theta
float32 linear_velocity
float32 angular_velocity

5.2.2 实现订阅节点

创建功能包需要依赖的功能包: roscpp rospy std_msgs turtlesim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "ros/ros.h"
#include "turtlesim/Pose.h"

void doPose(const turtlesim::Pose::ConstPtr& p){
    ROS_INFO("乌龟位姿信息:x=%.2f,y=%.2f,theta=%.2f,lv=%.2f,av=%.2f",
        p->x,p->y,p->theta,p->linear_velocity,p->angular_velocity
    );
}


int main(int argc, char *argv[]){
    ros::init(argc,argv,"pose_sub");
    ros::NodeHandle nh;
    // 需要指明回调函数 函数指针(函数名)
    ros::Subscriber sub =nh.subscribe<turtlesim::Pose>("/turtle1/pose",1000,doPose);
    // 进入循环,如果队列中有数据就处理,不然等待
    ros::spin();
    return 0;
}

5.3 服务调用

需求:向turtlesim 发送请求,在指定位置生成一只乌龟

同样,需要先启动乌龟显示节点

5.3.1 获取服务名称和服务消息

获取话题 /spawn rosservice list

获取类型:

1
2
$ rosservice type /spawn
turtlesim/Spawn

获取消息格式:

1
2
3
4
5
6
7
$ rossrv info turtlesim/Spawn
float32 x
float32 y
float32 theta
string name
---
string name

5.3.2 实现

创建功能包需要依赖的功能包: roscpp rospy std_msgs turtlesim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "ros/ros.h"
#include "turtlesim/Spawn.h"

int main(int argc, char* argv[]){
    ros::init(argc,argv,"turtle_spawn");
    ros::NodeHandle nh;
    ros::ServiceClient client = nh.serviceClient<turtlesim::Spawn>("/spawn");
    ros::service::waitForService("/spawn");
    turtlesim::Spawn spawn;
    spawn.request.x = 1.0;
    spawn.request.y = 1.0;
    spawn.request.theta = 1.57;
    spawn.request.name = "my_turtle";
    bool flag = client.call(spawn);
    if (flag)
    {
        ROS_INFO("新的乌龟生成,名字:%s",spawn.response.name.c_str());
    } else {
        ROS_INFO("乌龟生成失败!!!");
    }
    return 0;
}

5.4 参数设置

修改乌龟显示背景,该背景色通过参数服务器的方式以rgb方式设置

启动乌龟显示节点获取参数名:

rosparam list

1
2
3
4
$ rosparam list
/turtlesim/background_b
/turtlesim/background_g
/turtlesim/background_r
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"


int main(int argc, char *argv[])
{
    ros::init(argc,argv,"haha");

    ros::NodeHandle nh("turtlesim");
    //ros::NodeHandle nh;

    // ros::param::set("/turtlesim/background_r",0);
    // ros::param::set("/turtlesim/background_g",0);
    // ros::param::set("/turtlesim/background_b",0);

    nh.setParam("background_r",0);
    nh.setParam("background_g",0);
    nh.setParam("background_b",0);


    return 0;
}

nh直接定义了 命名空间 后面的设置 就可以不用输入 turtlesim

本文由作者按照 CC BY 4.0 进行授权

Slam Vslam十四讲笔记 2~8讲

Django Django2 By Example 02 Social Network Project