消息查询
rostopic list
/*
/rosout
/rosout_agg
/turtle1/cmd_vel //速度控制话题
/turtle1/color_sensor //改变背景颜色话题
/turtle1/pose //姿态位置话题
*/
rostopic info /turtle1/cmd_vel
/*topic
Type: geometry_msgs/Twist
Publishers:
* /teleop_turtle (http://localhost:34999/)
Subscribers:
* /turtlesim (http://localhost:36611/)
*/
//很明显,该话题是键盘控制节点发布,小乌龟去订阅
rostopic info /turtle1/pose
/*
Type: turtlesim/Pose
Publishers:
* /turtlesim (http://localhost:36611/)
Subscribers: None
*/
//该话题由小乌龟节点发布,目前还没有订阅者
话题 发布/订阅
(强烈推荐安装vscode等,会有提示,写代码非常方便)
发布案例
该例发布turtle1/cmd_vel
话题,消息类型geometry_msgs::Twist
来控制小乌龟的移动,为什么话题名一定要是这样,因为小乌龟本身一直在订阅该话题来实现移动,故我们发布该话题,小乌龟就会自动订阅,实现移动,对后期来说,话题名我们自己定,发布写好后,在写订阅者,只要两者的话题相同即可。但对于特定的,比如小乌龟,turtlebot,无人机等一系列仿真,他们的控制都已经封装好了,我们只需要发布即可,订阅是自动的,这种时候就一定要查询话题,才能动手写,查询就如文章一开始那样,先运行你的功能包,在查阅话题即可。
//前提:启动roscore
,和rosrun turtlesim turtlesim_node
cpp代码:
//rostopic list //查找所需话题
//rostopic info turtle1/cmd_vel //查找所需话题的消息类型
//rosmsg info geometry_msgs::Twist //查找具体消息类型格式
#include <ros/ros.h>
#include <iostream>
#include <geometry_msgs/Twist.h> //都需要添加消息类型对应的头问件
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "cmd_vel_publisher");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布话题名为/turtle1/cmd_vel,该话题消息类型为geometry_msgs::Twist,队列长度1 ----都是通过前面查询到的;
ros::Publisher pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 1);
//注意格式即可; <>里是消息类型,advertise的第一个参数是话题名,第二个是队列长度,具体可以看前面对advertise函数介绍
// 设置循环的频率
ros::Rate rate(10); //周期T = 1/10
while (ros::ok())
{
// 初始化geometry_msgs::Twist类型的消息
geometry_msgs::Twist msg;//rosmsg查询出消息的具体格式
msg.linear.x = 0.1;
msg.angular.z = 0.1;
// 发布消息
pub.publish(msg);
ROS_INFO("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]", msg.linear.x, msg.angular.z);
std::cout<<"v_x: "<<msg.linear.x<< " w_z: " << msg.angular.z<<endl;//等价上面
// 按照循环频率延时
rate.sleep();
}
return 0;
}
订阅案例
该案例订阅小乌龟的位置信息turtle1/pose,消息类型turtlesim/Pose,情景,在键盘控制下,小乌龟一直移动,则小乌龟的位置消息会一直更新,我们通过订阅该消息,在终端显示,接收到的位置消息
//前提:启动roscore
,和rosrun turtlesim turtlesim_node
,以及 rosrun turtlesim turtle_teleop_key
cpp代码:
//rostopic list
//rostopic info /turtle1/pose
//rosmsg info turtlesim/Pose
#include <ros/ros.h>
#include <iostream>
#include "turtlesim/Pose.h"
// 消息回调函数
void poseCallback(const turtlesim::Pose::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("Turtle pose: x:%0.6f, y:%0.6f", msg->x, msg->y);
std::cout<<"Turtle pose: x : "<<msg->x<<" ,y : " << msg->y<<std::endl;//上述两条等价,
}
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);
// 循环等待回调函数
ros::spin();//在循环等待中,若订阅到了对应话题,就会进入相应的回调处理函数
return 0;
}
在话题发布订阅中使用自定义类型的消息类型
自定义消息
案例
:设计一个表示学生的消息类型,包括姓名,年龄,性别,分数。
然后通过一个节点将该消息发布出去,在通过另一个节点订阅显示。
//首先建立消息
//在功能包下创建一个文件夹msg用来放自定义消息
//ros中的自定义消息比较简单,创建一个msg文件,按一定格式编写即可
//在msg文件夹下,创建文件student.msg打开写入
string name
uint8 age
uint8 sex
//其次配置功能包下相关文件
//package.xml
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
//CMakeLists.txt
find_package(catkin REQUIRED COMPONENTS
geometry_msgs//常见类型消息
roscpp//c++文件格式
rospy//python文件格式
std_msgs//一些常用的标准消息
turtlesim//小乌龟相关消息
message_generation//自定义msg文件
)//添加依赖
add_message_files(
FILES
student.msg//这里是你的msg文件名加后缀
)
generate_messages(
DEPENDENCIES
std_msgs
)//因为我们的msg文件里使用到string uint8等类型都是std_msgs里面的,所有需要依赖它来解析我们的msg文件
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES learning_topic
CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs turtlesim message_runtime
# DEPENDS system_lib
)//添加了message_runtime
//编译运行,就会在工作空间下devel/include/功能包名/找到对应的头文件
//至此我们的消息创建成功。
//编译一下,就会出现对于的头文件,该消息的类型名是: 功能包名:msg文件名
自定义消息发布
发布
cpp代码
//其实在这里话题名可以自己随便起 ,比如 stu_msg 消息类型自然是我们写的,不过要注意的是,头文件路径只包含到include,故我们包含头文件 #include "learning_topic/student.h" 消息类型learning_topic::student
#include <ros/ros.h>
#include <iostream>
#include "learning_topic/student.h"
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "stu_publisher");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布名为/stu_msg的topic,消息类型为learning_topic::student,队列长度1
ros::Publisher pub = n.advertise<learning_topic::studnet>("/stu_msg", 1);
// 循环频率
ros::Rate rate(1);
while (ros::ok())
{
// 初始化learning_topic::studnet类型的消息
learning_topic::studnet msg;
msg.name = "xiangwang";
msg.age = 22;
msg.sex = 1;
// 发布消息
pub.publish(msg);
std::cout<<"name = " << msg.name << std::endl;
rate.sleep();
}//按照循环频率发布消息,也可以参考advertise函数讲解,利用可选参数,发布一次也可以。
return 0;
}//编译完成后,启动roscore,启动该节点,利用rostopic list就可以查看到该话题
`
自定义消息订阅
`
/**
* 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person
*/
#include <ros/ros.h>
#include <iostream>
#include "learning_topic/Person.h"
// 接收到订阅的消息后,会进入消息回调函数
void stu_back(const learning_topic::student& msg)
{
// 消息打印
std::cout<<"name = :" << msg.name << " , age = :" << msg.age<<std::endl;
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "stu_subscriber");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为/stu_msg的topic,注册回调函数personInfoCallback
ros::Subscriber person_info_sub = n.subscribe("/stu_msg", 10, stu_back);
// 循环等待回调函数
ros::spin();
return 0;
}
总结
到此为止,话题的发布订阅简单使用就结束了,一般在项目中,通常会出现下列情况:
1:同一个节点需要订阅多个节点;
2:话题发布经常在回调函数中;
3:经常将发布订阅写在类中实现;
针对1:会出现需要话题同步–需要融合多个消息,或者多线程问题—各自订阅不受干扰。—后面在讲消息同步和多线程问题
针对2:一般会在回调函数中对数据进行处理,然后将处理结果或者一种控制信息发布出去,就需要在回调函数中发布消息,后期讲如何处理;
3:大型项目基本都是按类封装,我们可以借鉴这种写法;