|---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文件
ROS程序运行之后,不同的节点之间是错综复杂的,ROS 中提供了一个实用的工具: rqt_graph
rqt_graph能够创建一个显示当前系统运行情况的动态图形。ROS 分布式系统中不同进程需要进行数据交互,计算图可以以点对点的网络形式表现数据交互过程。rqt_graph是rqt程序包中的一部分。
下面在demo02_ws中以小乌龟程序为例展示rqt_graph工具:
ROS通信机制主要分为三种:
话题通信使用频率最高,是基于发布订阅模式的,即一个节点发布消息,另一个节点订阅消息
像雷达、摄像头、GPS… 等等一些传感器数据的采集,都是使用了话题通信,换言之,话题通信适用于不断更新的数据传输相关的应用场景。
如下图所示:
需求: 编写发布订阅实现,要求发布方每秒10次发布消息,订阅方订阅消息并将消息打印输出.
由于大部分内容已经被封装好了,我们要实现通信模型实现的关注点为:
流程:
发布方C++实现代码:
#include "ros/ros.h"
#include "std_msgs/String.h" /*数据为普通文本类型时要包含此头文件*/
#include
/*
发布方实现:
1.包含头文件
ROS中的文本类型 std_msgs/String.h
2.初始化节点
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.h
2.初始化节点
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由于描述性较差而显得力不从心,这种场景下可以使用自定义的消息类型
需求:自定义msg,包含人的姓名,年龄,身高等
流程:
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属性:
#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今天就到这里了,明天见
❤华❤