一.ROS架构

1.1ROS的文件系统

|---WorkSpace --- 自定义的工作空间|--- build:编译空间,用于存放CMake和catkin的缓存信息、配置信息和其他中间文件。|--- devel:开发空间,用于存放编译后生成的目标文件,包括头文件、动态&静态链接库、可执行文件等。|--- src: 源码|-- package:功能包(ROS基本单元)包含多个节点、库与配置文件,包名所有字母小写,只能由字母、数字与下划线组成|-- CMakeLists.txt 配置编译规则,比如源文件、依赖项、目标文件|-- package.xml 包信息,比如:包名、版本、作者、依赖项...(以前版本是 manifest.xml)|-- scripts 存储python文件|-- src 存储C++源文件|-- include 头文件|-- msg 消息通信格式文件|-- srv 服务通信格式文件|-- action 动作格式文件|-- launch 可一次性运行多个节点 |-- config 配置信息|-- CMakeLists.txt: 编译的基本配置

目前我用到的只有工作空间,在工作空间下的src目录中创建pkg功能包,在功能包src目录中新建.cpp文件以及在功能包目录下新建launch文件夹,在功能包下修改的Cmakelists.txt文件

1.2ROS计算图

ROS程序运行之后,不同的节点之间是错综复杂的,ROS 中提供了一个实用的工具: rqt_graph

rqt_graph能够创建一个显示当前系统运行情况的动态图形。ROS 分布式系统中不同进程需要进行数据交互,计算图可以以点对点的网络形式表现数据交互过程。rqt_graph是rqt程序包中的一部分。

下面在demo02_ws中以小乌龟程序为例展示rqt_graph工具:![请添加图片描述](https://img-blog.csdnimg.cn/direct/09c0a7aec41f4a44bba57b5cf0c36645.png

启动乌龟程序launch文件

打开一个终端运行rqt工具:

rqt_graph#或者 rosrun rqt_graph rqt_graph


这样就通过图形化显示工具理清节点之间的关系了

二.ROS通信机制

(其实通信就是进行数据交换)

ROS通信机制主要分为三种:

  1. 话题通信(发布订阅模式)
  2. 服务通信(请求响应)
  3. 参数服务器(参数共享模式)

2.1话题通信

话题通信使用频率最高,是基于发布订阅模式的,即一个节点发布消息,另一个节点订阅消息

像雷达、摄像头、GPS… 等等一些传感器数据的采集,都是使用了话题通信,换言之,话题通信适用于不断更新的数据传输相关的应用场景。

2.1.1话题通信理论模型

如下图所示:

2.1.2话题通信基本操作

需求: 编写发布订阅实现,要求发布方每秒10次发布消息,订阅方订阅消息并将消息打印输出.

由于大部分内容已经被封装好了,我们要实现通信模型实现的关注点为:

  1. 发布方实现
  2. 订阅放实现
  3. 数据(此处为普通文本)

流程:

  1. 编写发布方实现
  2. 编写订阅方实现
  3. 编辑配置文件
  4. 编译运行

2.1.3普通文本案例

发布方C++实现代码:

#include "ros/ros.h"#include "std_msgs/String.h" /*数据为普通文本类型时要包含此头文件*/#include /*发布方实现:1.包含头文件ROS中的文本类型 std_msgs/String.h2.初始化节点3.创建节点句柄4.创建发布者对象5.编写发布逻辑并发布数据*/int main(int argc, char *argv[]){//2.初始化 ROS 节点 ros::init(argc,argv,"publisher");//3.创建节点句柄ros::NodeHandle nh;//4.创建发布者对象ros::Publisher pub = nh.advertise<std_msgs::String>("topic_name",10); //"topic_name"为话题//5.编写发布逻辑并发布数据//要求发布频率为10Hz并且文本后要添加编号//首先创建一个消息std_msgs::String msg;//实现发布频率ros::Rate rate(10);//实现编号int counter = 0;//编写循环发布消息while (ros::ok)//此处条件意思是:如果这个节点还存活就执行这个循环{// msg.data = "hello";counter ++;//实现字符串拼接数字std::stringstream ss;ss << "hello ----" << counter;msg.data = ss.str();pub.publish(msg); //发布消息rate.sleep(); // 相当于延时函数}return 0;}

订阅方C++实现代码:

#include "ros/ros.h"#include "std_msgs/String.h"/*订阅方实现:1.包含头文件ROS中的文本类型 std_msgs/String.h2.初始化节点3.创建节点句柄4.创建订阅者对象5.处理订阅到的数据*/void deal_msg(const std_msgs::String::ConstPtr& msg){ROS_INFO("订阅到的数据:%s",msg->data.c_str());}int main(int argc, char *argv[]){setlocale(LC_ALL, "");ros::init(argc,argv,"listener");ros::NodeHandle nh;ros::Subscriber sub = nh.subscribe<std_msgs::String>("topic_name",10,deal_msg);// 第三次参数为回调函数,用来处理接受到的数据//回调函数相当于地雷,只有接收到引爆信号(接收到消息)才会执行ros::spin(); // 如果没有此语句,程序只会运行一遍,要一直订阅就要使用该语句return 0;}

上述案例实现了普通文本的话题通信

在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是,这些数据一般只包含一个data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,比如: 激光雷达的信息… std_msgs由于描述性较差而显得力不从心,这种场景下可以使用自定义的消息类型

2.1.4自定义msg案例

需求:自定义msg,包含人的姓名,年龄,身高等

流程:

  1. 按照固定格式创建msg文件
  2. 编辑配置文件
  3. 编译生成可以被C++调用的中间文件

1.在功能包下新建msg文件夹
2.在msg文件夹中新建 .msg 文件
3.在该文件中根据需求自定义消息类型

4.修改配置文件①packa.xml②Cmakelist.txt

在package.xml文件中增加这两行

在Cmakelists.txt中修改:




修改完成后记得保存
5.编译生成中间文件:

发现生成 .h 头文件 那么我们以后就可以通过调用头文件来使用我们自定义的msg消息了

6.配置vscode

为了方便代码提示以及避免误抛异常,需要先配置 vscode,将前面生成的 head 文件路径配置进
c_cpp_properties.json 的 includepath属性:


下面编写发布方C++实现代码:

#include "ros/ros.h"#include "plumbing_pub_sub/person.h"int main(int argc, char *argv[]){setlocale(LC_ALL,"");ros::init(argc,argv,"publisher");ros::NodeHandle nh;ros::Publisher pub = nh.advertise<plumbing_pub_sub::person>("topic_name", 10);//定义要被发布的数据以及发布频率plumbing_pub_sub::person person;person.name = "qmh";person.age = 19;person.height = 180;//设置发布频率变ros::Rate rate(1);//循环发布while (ros::ok){pub.publish(person);rate.sleep();//目前没什么用但是官网建议加上此语句ros::spinOnce();}return 0;}

在Cmakelists.txt中除了要增加target,executable,还要修改:

此处目的是为了防止出现msg文件未编译与cpp文件同时编译出现顺序上的错我,修改过后再编译时就会先编译msg避免了上述错误

订阅方C++代码实现:

#include "ros/ros.h"#include "plumbing_pub_sub/person.h"void deal_msg(const plumbing_pub_sub::person::ConstPtr &msg){ROS_INFO("接收到:我叫:%s,我今年%d岁,身高%fcm",msg->name.c_str(), msg->age, msg->height);}int main(int argc, char *argv[]){setlocale(LC_ALL,"");ros::init(argc, argv, "listener");ros::NodeHandle nh;ros::Subscriber sub = nh.subscribe("topic_name", 10, deal_msg);ros::spin();return 0;}

ok今天就到这里了,明天见
❤华❤