不论是给海龟发布速度指令的Twist消息还是去订阅海龟位置的pose消息,二者都是已经在ros里定义好的可以直接使用的;而当ros定义好的消息没有办法满足我们使用要求,便需要自己来定义所使用的消息的类型,下面是关于消息的自定义并完成publisher和subscriber使用,这次完成消息的传输是传输person的个人信息,包括名字年龄性别,publisher来发布人的信息,subscriber来接受人的信息,通过的topic的名字为/person_info;会把person的个人信息在我们的learning_topic上作定义,所以这里的消息是learning_topic里的person这个消息,整个通讯仍需要在ROS Master去实现
需实现的模型
第一步 怎样自定义话题消息
1.定义msg文件(完成数据接口的定义)
针对person做定义描述,其包含一个名字的字符串,性别,年龄;接下来针对sex性别分为三种,来定义一些宏定义以便在sex里使用表达性别
我们需要把所有定义放到person.msg文件中,该文件与语言无关
这里的string,uint8都表示我们要在不同的程序里要扩展成对应该种程序的表示方法,比如c++,对应string,char型.在编译时去动态的去做拓展的,所以在编译的时候还需要去配置一系列的编译选项
创建person.msg文件
在工作空间里,主目录/catkin_ws/src/learning_topic,创建名为msg文件夹
(然后与消息相关的定义全部都放到msg文件夹里方便管理)
在msg里打开终端使用touch命令,touch Person.msg,创建一个Person.msg文件;随后打开文件输入这些定义
随后ros在编译时就会根据这些定义来变成不同的c++和python的程序
接下来根据数据接口定义设置一些编译的规则
2.在package.xml中添加功能包依赖
这里我们会添加一个动态生成程序的一个功能包的依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
点开learning_topic里的package.xml,复制上两行于尾部
编译依赖,依赖动态产生message这样一个功能包message_generation
执行依赖,依赖动态的message的runtime的运行时的依赖
3.在CMakeLists.txt里添加编译选项
find_package( … message_generation)
由于前面添加了一个依赖的功能包,故而也需要在CMakeLists.txt里find_package里添加message_generation功能包
add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)
添加一个把.msg文件编译成对应的不同的程序文件的一个配置项
找到对应位置,上面有对应的一些注释的内容,和我们要添加进来的内容类似
第一个add_message_files(FILES Person.msg)
会把我们刚定义的Person.msg作为我们定义的接口,这样我们在编译的时候就会发现这个定义的接口然后针对其去做编译
第二个generate_messages(DEPENDENCIES std_msgs)
generate_messages,就是我们在编译这个Person.msg文件的时候需要依赖哪些ros已有的库或者包,这里我们需要依赖std_msgs(刚刚的string,uint8均在其里面做定义)
上面的注释也有说明
catkin_package( … message_runtime)
创建一个message运行的依赖,对应于上面的<exec_depend>
完成基本配置后,可以返回工作空间目录下尝试编译,同样使用catkin_make编译
编译成功后,查看编译所生成的代码文件
devel/include/lenrning_topic/Person.h
即为根据刚刚的.msg文件去编译得到的c++的头文件
里面的内容会根据很贱的几个定义扩展出一系列针对c++的类的一些定义,包括属性等内容
那么,如何调用刚刚所创建的Person.msg生成的一些头文件,怎样通过程序使用
这里会涉及发布者和订阅者创建
第二步,publisher和subscriber代码实现(例c++)
将课件代码/learning_topic/src里的person_publisher.cpp和person_subscriber.cpp文件复制到工作空间里
catkin_ws/src/learning_topic/src里
person_publisher.cpp代码,发布者内容
/**
* 该例程将发布/person_info话题,自定义消息类型learning_topic::Person
*/
#include <ros/ros.h>
#include "learning_topic/Person.h"
//这里的include "learning_topic/Person.h"即为刚刚在devel下include所看到的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
//创建一个Publisher,发布自己创建的learning_topic::Person这样一个消息的接口的,该消息的接口需要通过上面的#include "learning_topic/Person.h"去调用;对应到程序它所发布的数据是往person_info这个话题里发的,队列长度是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类型的消息,名为Tom,年龄18,性别以learning_topic::Person::male这样宏定义的调用方式来调用在message里定义的宏
learning_topic::Person person_msg;
person_msg.name = "Tom";
person_msg.age = 18;
person_msg.sex = learning_topic::Person::male;
// 发布消息,通过publish方法把我们创建好的person的消息数据发布出去
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);
// 按照循环频率延时,大概延时100ms时间保证我们循环的频率,然后不断循环不断发布个人信息
loop_rate.sleep();
}
return 0;
}
person_subscriber.cpp代码,订阅者信息
/**
* 该例程将订阅/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;
}
这里我们看到的publisher和subscriber的实现和前面两讲完全一样,只不过这里的消息接口#include “learning_topic/Person.h”,是我们自己生成的头文件,publisher和subscriber所发布和订阅的话题的消息也是我们自己定义的person的消息。以上则为person消息的使用方法
接下来去编译这两个代码
第三步,配置代码编译规则
将下列复制到CMakeLists.txt里
add_executable(person_publisher src/person_publisher.cpp)
target_link_libraries(person_publisher ${catkin_LIBRARIES})
add_dependencies(person_publisher ${PROJECT_NAME}_generate_messages_cpp)add_executable(person_subscriber src/person_subscriber.cpp)
target_link_libraries(person_subscriber ${catkin_LIBRARIES})
add_dependencies(person_subscriber ${PROJECT_NAME}_generate_messages_cpp)
相比较上面多出 一行add_dependencies,因为有一些代码是动态生成的,所以需要让可执行文件和动态生成的一些程序也要产生依赖关系,故该句话是用来去动态和刚才生成的头文件做连接的,这是要和我们自定义消息链接的话必须要添加的一句话
第四步,编译并运行发布者和订阅者
cd ~/catkin_ws //回到工作空间根目录下
catkin_make //用该指令去做编译
source devel/setup.bash //设置环境变量,可通过下面方法减去该步骤,跳过
roscore
rosrun learning_topic person_subscriber
rosrun learning_topic person_publisher //运行learning_topic里的一个节点,节点名为person_publisher
可见不断发布信息,接收信息,通讯正常运行
小测验:终止roscore,通讯仍在继续
因ROS Master是帮助节点创建连接的,一旦连接创建成功后ROS Master作用则不再存在了,而当需要访问参数时,或者第三个节点需要插入与二者建立连接时则仍需借助
对于python代码
将课件代码/learning_topic/scripts里的person_publisher.py和person_subscriber.py文件复制到工作空间里
catkin_ws/src/learning_topic/scripts里
person_publisher.py代码,发布者实现
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 该例程将发布/person_info话题,自定义消息类型learning_topic::Person
import rospy
from learning_topic.msg import Person
//通过from learning_topic.msg import Person引入
def velocity_publisher():
# ROS节点初始化
rospy.init_node('person_publisher', anonymous=True)
# 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度10
person_info_pub = rospy.Publisher('/person_info', Person, queue_size=10)
#设置循环的频率
rate = rospy.Rate(10)
while not rospy.is_shutdown():
# 初始化learning_topic::Person类型的消息
person_msg = Person()
person_msg.name = "Tom";
person_msg.age = 18;
person_msg.sex = Person.male;
# 发布消息
person_info_pub.publish(person_msg)
rospy.loginfo("Publsh person message[%s, %d, %d]",
person_msg.name, person_msg.age, person_msg.sex)
# 按照循环频率延时
rate.sleep()
if __name__ == '__main__':
try:
velocity_publisher()
except rospy.ROSInterruptException:
pass
person_subscriber.py代码,订阅者实现
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person
import rospy
from learning_topic.msg import Person
//引入定义的接口
def personInfoCallback(msg): //具体做消息的处理和显示
rospy.loginfo("Subcribe Person Info: name:%s age:%d sex:%d",
msg.name, msg.age, msg.sex)
def person_subscriber():
# ROS节点初始化
rospy.init_node('person_subscriber', anonymous=True)
# 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
rospy.Subscriber("/person_info", Person, personInfoCallback)
# 循环等待回调函数
rospy.spin()
if __name__ == '__main__':
person_subscriber()