ROS之通信机制

话题通信

话题通信原理

话题通信有三种角色:

  • ROS Master (管理者)
  • Talker (发布者)
  • Listener (订阅者)
    Master负责链接发布者和订阅者,通过相同的话题进行发布和订阅链接,链接后发布者和订阅者之间不再需要Master的持续链接,就可以持续发布和订阅通信数据(Master通过roscore启动)
    在这里插入图片描述

0.Talker注册

Talker启动后,会通过RPC在 ROS Master 中注册自身信息,其中包含所发布消息的话题名称。ROS Master 会将节点的注册信息加入到注册表中。

1.Listener注册

Listener启动后,也会通过RPC在 ROS Master 中注册自身信息,包含需要订阅消息的话题名。ROS Master 会将节点的注册信息加入到注册表中。

2.ROS Master实现信息匹配

ROS Master 会根据注册表中的信息匹配Talker 和 Listener,并通过 RPC 向 Listener 发送 Talker 的 RPC 地址信息。

3.Listener向Talker发送请求

Listener 根据接收到的 RPC 地址,通过 RPC 向 Talker 发送连接请求,传输订阅的话题名称、消息类型以及通信协议(TCP/UDP)。

4.Talker确认请求

Talker 接收到 Listener 的请求后,也是通过 RPC 向 Listener 确认连接信息,并发送自身的 TCP 地址信息。

5.Listener与Talker件里连接

Listener 根据步骤4 返回的消息使用 TCP 与 Talker 建立网络连接。

6.Talker向Listener发送消息

连接建立后,Talker 开始向 Listener 发布消息。

注意点:

  • 启动listener和tailer前要先启动Master(roscore)

  • 上述实现流程中,前五步使用的 RPC协议,最后两步使用的是 TCP 协议

  • Talker 与 Listener 的启动无先后顺序要求

  • Talker 与 Listener 都可以有多个

  • Talker 与 Listener 连接建立后,不再需要 ROS Master。也即,即便关闭ROS Master,Talker 与 Listern 照常通信。

C++代码实现话题通信

发布者(文件名:Hello_pub)
#include "ros/ros.h"
#include "std_msgs/String.h" //系统内置文本类型的消息
#include <sstream>

int main(int argc, char  *argv[])
{   
    //设置编码,防止中文乱码
    setlocale(LC_ALL,"");

    //2.初始化 ROS 节点:命名(唯一)
    // 参数1和参数2 后期为节点传值会使用
    // 参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
    ros::init(argc,argv,"talker");
    //3.实例化 ROS 句柄
    ros::NodeHandle nh;//该类封装了 ROS 中的一些常用功能

    //4.实例化 发布者 对象
    //泛型: 发布的消息类型
    //参数1: 要发布到的话题
    //参数2: 队列中最大保存的消息数,超出此阀值时,先进的先销毁(时间早的先销毁)
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);

    //5.组织被发布的数据,并编写逻辑发布数据
    //数据(动态组织)
    std_msgs::String msg;
    // msg.data = "你好啊!!!";
    std::string msg_front = "Hello 你好!"; //消息前缀
    int count = 0; //消息计数器

    //逻辑(一秒1次)
    ros::Rate r(1);

    //不通过强制退出就一直为真
    while (ros::ok())
    {
        //使用 stringstream 拼接字符串与编号
        std::stringstream ss;
        //将数据存放进ss
        ss << msg_front << count;
        //将文本内容取出,为string类型数据赋值给msg变量,msg变量数据存放在data属性下
        msg.data = ss.str();
        //发布msg消息
        pub.publish(msg);
        
        //加入调试,打印发送的消息,%s为格式化输出char性数组数据,通过c_str()转化出成char类数据
        ROS_INFO("发送的消息:%s",msg.data.c_str());

        //根据前面制定的发送贫频率自动休眠 休眠时间 = 1/频率;
        r.sleep();
        count++;//循环结束前,让 count 自增
        //暂无应用
        ros::spinOnce();
    }
    return 0;
}
订阅者(文件名:Hello_sub)
#include "ros/ros.h"
#include "std_msgs/String.h"

void doMsg(const std_msgs::String::ConstPtr& msg_p){
    ROS_INFO("我听见:%s",msg_p->data.c_str());
}
int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    //2.初始化 ROS 节点:命名(唯一)
    ros::init(argc,argv,"listener");
    //3.实例化 ROS 句柄
    ros::NodeHandle nh;

    //4.实例化 订阅者 对象,运行后挂起继续向下运行,直到遇见命令主动回调才开始进入doMsg函数中
    //回调函数调用时会将接受的订阅数据作为参数传给回调函数,
    //"chatter"是要接受的话题名称,Master会通过该名称对于ros运行时的话题进行匹配链接。
    ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,doMsg);
    //5.处理订阅的消息(回调函数)

    ros::spin();//循环读取接收的数据,并调用回调函数处理

    return 0;
}
配置文件

功能包下的CMakeLists.txt文件,并不是工作空间下的。

# Hello_pub.cpp在配置文件中映射一个名称叫做Hello_pub,可以自定义,但一般选择不带后缀的文件名
add_executable(Hello_pub
  src/Hello_pub.cpp
)
add_executable(Hello_sub
  src/Hello_sub.cpp
)

# Hello_pub就是填入文件映射名称定位Hello_pub.cpp文件
target_link_libraries(Hello_pub
  ${catkin_LIBRARIES}
)
target_link_libraries(Hello_sub
  ${catkin_LIBRARIES}
)
运行

编译文件

catkin_make

启动Master

roscore

刷新环境变量

source ./devel/setup.bash 
# 运行Hello_pub文件
rosrun 功能包名称 Hello_pub

# 运行Hello_sub文件
rosrun 功能包名称 Hello_sub

自定义数据的传输

ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,就不能较好的传输。那么我们就要创建自定数据类进行数据打包传输。

在文件数据打包时支持的数据类型有
msgs只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:

  • int8, int16, int32, int64 (或者无符号类型: uint*)

  • float32, float64

  • string

  • time, duration

  • other msg files

  • variable-length array[] and fixed-length array[C]

创建msg文件用于存放自定义数据包
在这里插入图片描述
在新建的 msg 目录下添加文件 Person.msg

string name
uint16 age
float64 height

那么Person.msg就是一个自定义数据包,里面有三种属性,是由原生数据组成

自定义文件配置

package.xml中添加:

 <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

CMakeLists.txt文件添加:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

add_message_files(
  FILES
  Person.msg
)

generate_messages(
  DEPENDENCIES
  std_msgs
)

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES demo02_talker_listener
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

关于C++代码

发布者

导包:

#include "功能包名称/msg文件名.h"
例如:
#include "demo02_talker_listener/Person.h"

实例化传输包,赋值和传输:

//包类型为  包名::文件名
demo02_talker_listener::Person p;
p.name = "sunwukong";
p.age = 2000;
p.height = 1.45;

//实例化传输通道,确定数据数据传输类型和话题名称
ros::Publisher pub = nh.advertise<demo02_talker_listener::Person>("chattern",1000);
//发步消息
pub.publish(p);
订阅者

导包:

#include "功能包名称/msg文件名.h"
例如:
#include "demo02_talker_listener/Person.h"

接收数据

//定接受类型和接受话题名称
ros::Subscriber sub = nh.subscribe<demo02_talker_listener::Person>("chatter_person",10,doPerson);

回调函数

void doPerson(const demo02_talker_listener::Person::ConstPtr& p){
	char[] name=p->name.c_str();
	int age=p->age;
	double height=p->height;
}

服务通信

服务通信原理

,该模型中涉及到三个角色:

  • ROS master(管理者)
  • Server(服务端)
  • Client(客户端)

ROS Master 负责保管 Server 和 Client 注册的信息,并匹配话题相同的 Server 与 Client ,帮助 Server 与 Client 建立连接,连接建立后,Client 发送请求信息,Server 返回响应信息。
在这里插入图片描述0.Server注册

Server 启动后,会通过RPC在 ROS Master 中注册自身信息,其中包含提供的服务的名称。ROS Master 会将节点的注册信息加入到注册表中。

1.Client注册

Client 启动后,也会通过RPC在 ROS Master 中注册自身信息,包含需要请求的服务的名称。ROS Master 会将节点的注册信息加入到注册表中。

2.ROS Master实现信息匹配

ROS Master 会根据注册表中的信息匹配Server和 Client,并通过 RPC 向 Client 发送 Server 的 TCP 地址信息。

3.Client发送请求

Client 根据步骤2 响应的信息,使用 TCP 与 Server 建立网络连接,并发送请求数据。

4.Server发送响应

Server 接收、解析请求的数据,并产生响应结果返回给 Client。

C++实现服务通信

自定义传输数据

srv 文件内的可用数据类型与 msg 文件一致,且定义 srv 实现流程与自定义 msg 实现流程类似:

  • 按照固定格式创建srv文件
  • 编辑配置文件
  • 编译生成中间文件
    在这里插入图片描述

srv文件夹内创建test.srv文件

int32 num1
int32 num2
---
int32 sum

int32 num1和int32 num2客户端输入的数据
int32 sum是服务端给客户的返回数据
中间用- - -分割
配置
package.xml:

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

CMakeLists.txt:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

add_service_files(
  FILES
  test.srv # 自定义的文件
)

generate_messages(
  DEPENDENCIES
  std_msgs
)

这样 test.srv就可以被使用了

服务端代码编写(文件Add_Server.cpp)

#include "ros/ros.h"
#include "first_ros/test.h"//服务通信自定义数据包first_ros是功能包

// bool 返回值由于标志是否处理成功
bool doReq(first_ros::test::Request& req,//接受test文件---上的数据
          first_ros::test::Response& resp){//接受test文件---下的数据
    //取出客户端输入的数据
    int num1 = req.num1;
    int num2 = req.num2;
    ROS_INFO("服务器接收到的请求数据为:num1 = %d, num2 = %d",num1, num2);

    //如果没有异常,那么相加并将结果赋值给 resp下的sum变量
    resp.sum = num1 + num2;
    //返回值bool告诉服务端是否成功接受数据并提交发送给客户端,自动提交数据给客户端
    return true;
}

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

客户端代码编写(文件:Add_Client.cpp)

#include "ros/ros.h"
//1。导入包
#include "first_ros/test.h"

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

    // 2.初始化 ROS 节点
    ros::init(argc,argv,"Add_Client");
    // 3.创建 ROS 句柄
    ros::NodeHandle nh;
    // 4.创建 客户端 对象,接受话题为Add的消息
    ros::ServiceClient client = nh.serviceClient<first_ros::test>("Add");

    //等待服务启动成功,就可以在启动服务端前运行客户端
    //方式1
    ros::service::waitForService("Add");
    //方式2
    // client.waitForExistence();

    // 5.组织请求数据
    first_ros::test ai;
    std::cout<<"请输入两个整数"<<std::endl;
    std::cin>>ai.request.num1>>ai.request.num2;
    // 6.发送请求,返回 bool 值,标记是否成功,,就是服务端回调函数的返回值
    bool flag = client.call(ai);
    // 7.处理响应
    if (flag)
    {
        ROS_INFO("请求正常处理,响应结果:%d",ai.response.sum);
    }
    else
    {
        ROS_ERROR("请求处理失败....");
        return 1;
    }
    return 0;
}

配置

add_executable(Add_Server src/Add_Server.cpp)
add_executable(Add_Client src/Add_Client.cpp)


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


target_link_libraries(Add_Server
  ${catkin_LIBRARIES}
)
target_link_libraries(Add_Client
  ${catkin_LIBRARIES}
)

运行

运行

编译文件

catkin_make

启动Master

roscore

刷新环境变量(未找到运行文件就要刷新变量)

source ./devel/setup.bash 

运行

# 运行Add_Server.cpp文件
rosrun first_ros Add_Server

# 运行Add_Client,cpp文件
rosrun first_ros Add_Client

参数服务

原理

参数服务器实现是最为简单的,该模型如下图所示,该模型中涉及到三个角色:

  • ROS Master (管理者)
  • Talker (参数设置者)
  • Listener (参数调用者)

ROS Master 作为一个公共容器保存参数,Talker 可以向容器中设置参数,Listener 可以获取参数。

在这里插入图片描述

整个流程由以下步骤实现:
1.Talker 设置参数

Talker 通过 RPC 向参数服务器发送参数(包括参数名与参数值),ROS Master 将参数保存到参数列表中。

2.Listener 获取参数

Listener 通过 RPC 向参数服务器发送参数查找请求,请求中包含要查找的参数名。

3.ROS Master 向 Listener 发送参数值

ROS Master 根据步骤2请求提供的参数名查找参数值,并将查询结果通过 RPC 发送给 Listener。

C++代码实现参数设置

#include "ros/ros.h"
int main(int argc, char *argv[])
{
    ros::init(argc,argv,"set_update_param");
    //NodeHandle--------------------------------------------------------
    ros::NodeHandle nh;

	//设置也可以是修改
    nh.setParam("nh_int",10); //整型
    nh.setParam("nh_double",3.14); //浮点型
    nh.setParam("nh_bool",true); //bool
    nh.setParam("nh_string","hello NodeHandle"); //字符串
    nh.setParam("nh_vector",stus); // vector
    nh.setParam("nh_map",friends); // map


/*
	param(键,默认值) 
            存在,返回对应结果,否则返回默认值

    getParam(键,存储结果的变量)
            存在,返回 true,且将值赋值给参数2
            若果键不存在,那么返回值为 false,且不为参数2赋值

    getParamCached键,存储结果的变量)--提高变量获取效率
            存在,返回 true,且将值赋值给参数2
            若果键不存在,那么返回值为 false,且不为参数2赋值

    getParamNames(std::vector<std::string>)
            获取所有的键,并存储在参数 vector 中 

    hasParam(键)
            是否包含某个键,存在返回 true,否则返回 false

    searchParam(参数1,参数2)
            搜索键,参数1是被搜索的键,参数2存储搜索结果的变量


	deleteParam("键")
        根据键删除参数,删除成功,返回 true,否则(参数不存在),返回 false
*/
	return 0;
}

运行中的常用命令

rosnode

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

rostopic

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

rosmsg

rosmsg list
rosmsg package 包名 //列出某个包下的所有msg
rosmsg show 消息名称 //显示消息描述

rosservice

rosservice list    列出所有活动的服务
rosservice args 服务名 //打印服务参数
rosservice info 服务名  // 打印有关服务的信息
rosservice call 服务名。。。。tab键提取 //调用服务对象发布消息
rossrv info 消息数据名 // 详细的服务消息数据组成,话题是rosmsg info 话题数据名

rosparam

获取和设置ROS中master参数。

rosparam set    设置参数
rosparam get    获取参数
rosparam load    从外部文件加载参数
rosparam dump    将参数写出到外部文件
rosparam delete    删除参数
rosparam list    列出所有参数
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「 25' h 」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值