【一学就会的ROS基础入门教程 】03-1 ROS基础编程:ROS工作空间的创建、话题topic的发布与接收、以及话题消息的自定义使用
文前白话
本篇系列博客是依据哔站视频 【古月居】ROS 21讲 入门教程,学习整理,用于记录实践过程,加强学习,在系列整理的章节引导篇博客中,附带课程的课件和代码,需要的可以自行翻阅下载,共同学习。水平有限,若有整理错误之处,还请给予留言反馈,不胜感激。
系列整理的章节引导篇博客链接: 一学就会的ROS基础入门教程系列.
- 本篇从ROS的相关应用编程继续学习: ROS工作空间的创建、话题topic的发布与接收、以及话题消息的自定义使用。
1、创建工作空间与功能包
关于工作空间的介绍
- 具体的文件夹理解:
- src : 存放代码的空间(功能包的代码、配置文件、lunach文件)
- build: 编译空间(存放编译过程中产生的中间文件)
- devel: 开发空间(编译生成的可执行文件、库、脚本)
- Install: 安装空间 (install 指令生成的结果存放空间)
创建开发工作空间主要的终端操作命令步骤
- 具体的步骤解析:
- 1、从创建工作空间&初始化(创建文件夹src)
2、在工作空间下进行编译:
- 编译结果:
- 再执行:
catkin_make install
创建功能包
- 主要的命令流程:
- 实际操作:在创建的 src 文件夹下:
- 执行创建命令:
- 生成功能包的两个标配文件:
- CMakeLists.txt 和 package.xml 的作用:
功能包必须有的文件,package.xml 是介绍功能包的信息:名称、版本、功能、开发者信息、开源许可证、依赖信息等等。
CMakeLists.txt :描述功能包编译的规则,编译时候需要依赖的库
-
其中生成的文件夹:src 放置功能包的代码文件(如cpp文件)、Include 放置 头文件(如c++的.h头文件)
-
为了使功能包生效,进行编译:
- 编译完成后,要运行功能包的某一个程序文件,先要设置一下工作空间的环境变量:
执行:
source devel/setup.bash
- 设置以后,才能让系统找得到工作空间,并且找到工作空间对应的功能包
-
echo $ROS_PACKAGE_PATH 是 ros 自身的环境变量,它会通过环境变量查找所有ros功能包的路径, 只有经过 设置功能包的 source devel/setup.bash 后, ROS_PACKAGE_PATH (ros 自身的环境变量)才会包含创建的工作空间的路径,才能找到对应的功能包,运行程序文件。
-
为了避免每次创建功能包,忘记执行环境变量的配置,可以将 source devel/setup.bash 命令复制添加到 .bashrc 的配置文件里面:
** .bashrc在根目录下: (CTRL+h 可以显示隐藏文件)**
-
这样,就不用每次都在终端进行这个工作空间的环境变量的配置。(重启终端生效)
2、简单的话题发布与接收
发布者Publisher的编程实现(发布 Topic)
原理图示:
- 具体的操作:
1、创建功能包:
2、创建话题发布者的代码
-
发布话题的逻辑步骤:
-
话题发布代码
/**
* 该例程将发布turtle1/cmd_vel话题,消息类型geometry_msgs::Twist
*/
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
int main(int argc, char **argv)
{
//1、 (初始化)ROS节点初始化
ros::init(argc, argv, "velocity_publisher"); // "velocity_publisher"节点名称; argc, argv 初始化的参数输入
//2、(注册) 创建节点句柄,管理节点的资源
ros::NodeHandle n;
// 3、(创建消息)创建一个Publisher,发布名为/turtle1/cmd_vel的topic,消息类型为geometry_msgs::Twist,队列长度10
// 队列理解:发布的频率较高,不能及时接收,就先存到队列中去
//(如果队列满了,就会把时间较久的数据抛出掉,留下10个最新的数据)。
ros::Publisher turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);
// 4、(发布消息)设置循环的频率
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok())
{ //while 循环主要就是封装数据、并发布数据、延时满足频率进入到下次循环
// 初始化geometry_msgs::Twist类型的消息
geometry_msgs::Twist vel_msg; // 类
vel_msg.linear.x = 0.5; // 运动的线速度 m/s
vel_msg.angular.z = 0.2; // 角速度 弧度/m
// 发布消息
turtle_vel_pub.publish(vel_msg);
//调用方法发布数据,数据内容会通过ROS底层系列的通讯机制,将数据压入队列,队列通过底层的以太网
//的ros—topic 的 tcp 通讯机制不断的发出去,这里通过下面的ROS_INFO,输出信息,告知客户端,信息发送的状态
ROS_INFO("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]",
vel_msg.linear.x, vel_msg.angular.z);
// 按照循环频率延时
loop_rate.sleep();
}
return 0;
}
3、 配置话题发布者代码的编译规则:
-
其实就是,规定编译可执行文件生成的文件名、链接相关的依赖库;
-
将上述两句指令,拷贝到 CMakeLists.txt 文件中
-
对于指令的解释:
-
add_executable(velocity_publisher src/velocity_publisher. cpp)
-
作用:描述将哪一个程序文件(velocity_publisher. cpp),编译成哪一个可执行文件(velocity_publisher src)
-
target_link_libraries(velocity_publisher S(catkin_LIBRARIES])
-
作用: 帮助将生成的可执行文件与ROS 的一些库建立连接(c++的库)、
-
4、进行功能包的编译
5、设置环境变量:
-
在工作空间的根目录:
执行: source devel/setup.bash -
注:如果已经将 source devel/setup.bash 命令复制添加到 .bashrc 的配置文件里面,可以 忽略此步骤。
6、终端下运行功能包:
订阅者Suberscriber的编程实现(接收 Topic)
逻辑原理图:
具体操作:
1、在功能包的 src 中创建 话题订阅者的代码:
/**
* 该例程将订阅/turtle1/pose话题,消息类型turtlesim::Pose
*/
#include <ros/ros.h>
#include "turtlesim/Pose.h"
// 接收到订阅的消息后,会进入消息回调函数
void poseCallback(const turtlesim::Pose::ConstPtr& msg) // turtlesim::Pose是消息的 结构,与turtle1/pose是一致的, 通过常指针调用
{
// 将接收到的消息打印出来
ROS_INFO("Turtle pose: x:%0.6f, y:%0.6f", msg->x, msg->y);
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "pose_subscriber");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为/turtle1/pose的topic,注册回调函数poseCallback
ros::Subscriber pose_sub = n.subscribe("/turtle1/pose", 10, poseCallback);
//10是队列长度,存储从发布者传输的数据(缓冲区)
// 因为不知道什么时候会有消息传输进来,一旦发现,就
// 循环等待回调函数(死循环,一旦发现 队列中 有数据进来就 立刻调用personInfoCallback来处理)
ros::spin();
return 0;
}
//注意:回调函数非常重要,要完成消息的处理,要高效(不嵌套) ;不能卡住,影响下一个数据的进入
2、配置话题订阅者代码编译规则:
- 即是:
将:
添加到:
3、 进行编译
- 在工作空间下执行:
catkin_make
4、配置环境变量:
-
在工作空间的根目录:
执行: source devel/setup.bash -
注:如果已经将 source devel/setup.bash 命令复制添加到 .bashrc 的配置文件里面,可以 忽略此步骤。
5、终端下运行订阅话题:
-
终端下分别执行:
-
当你不知道某个文件夹的具体某一个文件的名称时候,可以进入该文件夹下 用 tab 键 补全 显示来查看:
-
如果TAB键不能补全,说明可能环境变量不对、或者程序没有编译成功。
-
再打开海龟的键盘操纵节点,移动小海龟 :
- 海龟对应的位置信息就会实时显示出来。
注:实现语言C++ 和 Python
- 功能包的实现既可以是c++也可以是 python 语言,区别在于,python脚本语言不用进行编译。
-
创建单独的文件夹来存放python代码文件:仅仅是为了区分c++,方便管理
-
同时,由于语言的不同,python 不需要编译,可以直接执行
3、话题消息的自定义与使用
-
现有的 ros 话题消息无法满足需求,可以自己来定义/发布话题
逻辑原理:
具体操作步骤:
1、自定义话题消息
- 其中,注意理解宏定义的用法:
- 在功能包的文件夹下创建自定义消息存放的文件夹:msg(方便查找管理)
- 然后,创建自定义消息内容的文件:
- 内容可以自行发布:
- 注:.msg 文件格式既不是c++也不是python文件,它是 ros 在编译过程中,结合具体的定义来变成对应的 c++ 或者 python 程序。
2、 设置编译规则:
- 即是:添加功能包依赖(编译依赖+执行依赖)
- 在添加编译选项
3、在工作空间下进行编译:
4、通过程序调用生成的头文件:
- 创建发布者与订阅者的个自代码:
/**
* 该例程将发布/person_info话题,自定义消息类型learning_topic::Person
*/
#include <ros/ros.h>
#include "learning_topic/Person.h"
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "person_publisher");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10
ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info", 10);
// 设置循环的频率
ros::Rate loop_rate(1);
int count = 0;
while (ros::ok())
{
// 初始化learning_topic::Person类型的消息
learning_topic::Person person_msg;
person_msg.name = "Tom";
person_msg.age = 18;
person_msg.sex = learning_topic::Person::male; // 通过宏定义调用显示 性别 信息的值male =1
// 发布消息
person_info_pub.publish(person_msg);
ROS_INFO("Publish Person Info: name:%s age:%d sex:%d",
person_msg.name.c_str(), person_msg.age, person_msg.sex);
// 按照循环频率延时
loop_rate.sleep();
}
return 0;
}
/**
* 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person
*/
#include <ros/ros.h>
#include "learning_topic/Person.h"
// 接收到订阅的消息后,会进入消息回调函数
void personInfoCallback(const learning_topic::Person::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("Subcribe Person Info: name:%s age:%d sex:%d",
msg->name.c_str(), msg->age, msg->sex);
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "person_subscriber");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
5、配置topic发布/接收代码编译规则:
- 即是:在功能包文件夹下的 CMakeList.txt 中,添加:
- 其中,add_dependencies…
(有一些代码是动态生成的,需要把生成的可执行文件与动态生成的程序产生依赖关系;用来动态的跟我们生成的头文件产生连接(与自定义的消息产生连接))
6、编译并在终端运行发布者和订阅者:
- 编译:
- 运行 roscore, 打开节点管理器:
-
实现通讯。
-
此时,如果关闭 roscore (ROS Master) 发布者 与订阅者仍然可以继续通讯,但是不能 与其他的节点进行 通讯。
-
(这里已经提前设置了环境变量)
-
python版本:
- 将代码放在对应的python文件夹中:
- 除去不用编译,其他步骤同c++版本步骤一致。