开发背景
在ROS进行结构体消息收发时,经常用书写proto文件,然后将proto字段与结构体字段一一对应,额外需要写一个adaptor转换程序,如果想要发送n个结构体,那么要写n个proto以及n个adaptor。这样很繁琐,能否有一个通用的方法?
下面我将对我在这个过程中碰到的问题,以及自己解决问题的思路做一个梳理,希望能帮到你。
(下面是临时写的,大致框架,具体后面在补充cmake,以及完整的例子)
简短版本
利用std_msgs::String发送结构体(最终版)
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
#include <iostream>
#include <string>
/*
发布方实现:
1.包含头文件;
ROS中文本类型 ----> std_msgs/String.h
2.初始化ROS节点;
3.创建节点句柄;
4.创建发布者对象;
5.编写发布逻辑并发布数据v
*/
enum struct GradeClassification: uint8_t{
PASS = 0,
GOOD,
EXCELLENT,
};
struct Junior {
char name[32];
int height;
GradeClassification grade_classification;
};
int main(int argc, char *argv[]) {
const int rate_value = 20;
setlocale(LC_ALL,"");
// 2.初始化ROS节点;
ros::init(argc, argv,"string_ros_pub");
// 3.创建节点句柄;
ros::NodeHandle nh;
// 4.创建发布者对象;
ros::Publisher pub = nh.advertise<std_msgs::String>("/test_string",10);
// 5.编写发布逻辑并发布数据;
// (5.1) 先创建ROS标准消息std_msgs::String
std_msgs::String msg;
// (5.2) 创建结构体变量
Junior junior;
// 以 20HZ 的频率发布数据
ros::Rate rate(rate_value);
// 设置编号
int seq = 0;
// 休眠以防没注册完已发布数据
ros::Duration(3).sleep();
// 编写循环,循环中发布数据
while(ros::ok()) {
// (5.3) 给结构体变量赋值
junior.height = 178;
junior.grade_classification=GradeClassification::EXCELLENT;
// (5.4) 创建 std::string
std::string s_temp;
// 这一步非常重要!!!
s_temp.assign((char*)&junior, sizeof(Junior));
// (5.5) 将std::string 赋值给ROS标准消息std_msgs::String的data成员变量
msg.data = s_temp;
// (5.6) 将载有结构体变量junior数据的消息std_msgs::String 发送
pub.publish(msg);
// ROS_INFO 打印发出的消息编号
ROS_INFO("[Pub data] talker sent a message ! No. %d", seq);
++seq;
rate.sleep();
ros::spinOnce();
}
return 0;
}
利用std_msgs::String接收结构体(最终版)
#include <iostream>
#include <string>
#include <sstream>
#include "ros/ros.h"
#include "std_msgs/String.h"
/*
订阅方实现:
1.包含头文件;
ROS中文本类型 ----> std_msgs/String.h
2.初始化ROS节点;
3.创建节点句柄;
4.创建订阅方对象;
5.处理订阅的数据。
6.spin()函数
*/
enum struct GradeClassification: uint8_t{
PASS = 0,
GOOD,
EXCELLENT,
};
struct Junior {
char name[32];
int height;
GradeClassification grade_classification;
};
// 5.处理订阅的数据。
void doMsg(const std_msgs::String::ConstPtr &msg) {
// (5.1) 创建结构体Junior的智能指针
static std::shared_ptr<Junior> junior_msg = std::make_shared<Junior>();
// (5.2) 将载有结构体变量junior数据的std_msgs::String消息内容拷贝到junior_msg中
memcpy((char*)junior_msg.get(), (char*)msg->data.c_str(), sizeof(Junior));
// (5.3) 打印订阅到的消息
ROS_INFO("[Sub data] junior_msg->height : %d", junior_msg->height);
ROS_INFO("[Sub data] junior_msg->grade_classification : %d", (uint8_t)junior_msg->grade_classification);
}
int main(int argc, char *argv[]) {
setlocale(LC_ALL,"");
// 2.初始化ROS节点;
ros::init(argc,argv,"string_ros_sub");
// 3.创建节点句柄;
ros::NodeHandle nh;
// 4.创建订阅方对象;
ros::Subscriber sub = nh.subscribe("/test_string",10,doMsg);
ros::spin();
return 0;
}
接下来是详细解决过程,碰到的问题,调试过程以及解决思路,不喜勿喷!!!!
解决过程
首先我想着ROS标准消息里有可以发送消息的例子,我找到了 std_msgs::String消息,下面是std_msgs::String 消息收发的Demo
std_msgs::String发送
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
/*
发布方实现:
1.包含头文件;
ROS中文本类型 ----> std_msgs/String.h
2.初始化ROS节点;
3.创建节点句柄;
4.创建发布者对象;
5.编写发布逻辑并发布数据v
*/
int main(int argc, char *argv[]) {
setlocale(LC_ALL,"");
// 2.初始化ROS节点;
ros::init(argc, argv,"string_ros_pub");
// 3.创建节点句柄;
ros::NodeHandle nh;
// 4.创建发布者对象;
ros::Publisher pub = nh.advertise<std_msgs::String>("/test_string",10);
// 5.编写发布逻辑并发布数据;
//要求以 10HZ 的频率发布数据,并且文本后添加编号
//先创建被发布的消息
std_msgs::String msg;
//发布频率
ros::Rate rate(10);
//设置编号
int seq = 0;
//休眠以防没注册完已发布数据
ros::Duration(3).sleep();
//编写循环,循环中发布数据
while(ros::ok()) {
count++;
//msg.data="hello,I'm erGouZi";
//实现字符串凭借数字
std::stringstream ss;
ss << "hello ...>" <<seq;
msg.data = ss.str();
pub.publish(msg);
//添加日志
ROS_INFO("发布的数据是:%s",ss.str().c_str());
rate.sleep();
ros::spinOnce();//官方建议,处理回调函数
}
return 0;
}
std_msgs::String接收
#include "ros/ros.h"
#include "std_msgs/String.h"
/*
订阅方实现:
1.包含头文件;
ROS中文本类型 ----> std_msgs/String.h
2.初始化ROS节点;
3.创建节点句柄;
4.创建订阅方对象;
5.处理订阅的数据。
6.spin()函数
*/
void doMsg(const std_msgs::String::ConstPtr &msg) {
//通过msg获取并操作订阅到的数据
ROS_INFO("订阅的数据: %s",msg->data.c_str());
}
int main(int argc, char *argv[]) {
setlocale(LC_ALL,"");
// 2.初始化ROS节点;
ros::init(argc,argv,"string_ros_sub");
// 3.创建节点句柄;
ros::NodeHandle nh;
// 4.创建订阅方对象;
ros::Subscriber sub = nh.subscribe("/test_string",10,doMsg);
// 5.处理订阅的数据。
ros::spin();
return 0;
}
从上面可以看到,std_msgs::String 有成员变量 data,在pub程序中
std::stringstream ss;
ss << "hello ...>" <<seq;
msg.data = ss.str();
变量 ss 数据类型是 std::stringstream,ss.str() 返回的数据类型是 string,那么,说明std_msgs::String 的成员变量 data是可以接收数据类型是string类型的数据并进行消息收发。
接下来就行改动,发送一个结构体
利用std_msgs::String发送结构体(测试一)
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
/*
发布方实现:
1.包含头文件;
ROS中文本类型 ----> std_msgs/String.h
2.初始化ROS节点;
3.创建节点句柄;
4.创建发布者对象;
5.编写发布逻辑并发布数据v
*/
enum struct GradeClassification: uint8_t{
PASS = 0,
GOOD,
EXCELLENT,
};
struct Junior {
char name[32];
int height;
GradeClassification grade_classification;
};
int main(int argc, char *argv[]) {
setlocale(LC_ALL,"");
// 2.初始化ROS节点;
ros::init(argc, argv,"string_ros_pub");
// 3.创建节点句柄;
ros::NodeHandle nh;
// 4.创建发布者对象;
ros::Publisher pub = nh.advertise<std_msgs::String>("/test_string",10);
// 5.编写发布逻辑并发布数据;
// 要求以 10HZ 的频率发布数据,并且文本后添加编号
// 先创建被发布的消息
std_msgs::String msg;
// 创建结构体变量
Junior junior;
// 给结构体变量赋值
junior.height = 178;
junior.grade_classification=GradeClassification::EXCELLENT;
// 创建 std::string
std::string s_temp;
s_temp(junior);
// 发布频率
ros::Rate rate(10);
// 设置编号
int seq = 0;
// 休眠以防没注册完已发布数据
ros::Duration(3).sleep();
// 编写循环,循环中发布数据
while(ros::ok()) {
count++;
//msg.data="hello,I'm erGouZi";
// 实现字符串凭借数字
std::stringstream ss;
ss << "hello ...>" <<seq;
msg.data = ss.str();
pub.publish(msg);
// 添加日志
ROS_INFO("发布的数据是:%s",ss.str().c_str());
rate.sleep();
ros::spinOnce();//官方建议,处理回调函数
}
return 0;
}
利用std_msgs::String接收结构体(测试一)
#include "ros/ros.h"
#include "std_msgs/String.h"
/*
订阅方实现:
1.包含头文件;
ROS中文本类型 ----> std_msgs/String.h
2.初始化ROS节点;
3.创建节点句柄;
4.创建订阅方对象;
5.处理订阅的数据。
6.spin()函数
*/
void doMsg(const std_msgs::String::ConstPtr &msg) {
//通过msg获取并操作订阅到的数据
ROS_INFO("订阅的数据: %s",msg->data.c_str());
}
int main(int argc, char *argv[]) {
setlocale(LC_ALL,"");
// 2.初始化ROS节点;
ros::init(argc,argv,"string_ros_sub");
// 3.创建节点句柄;
ros::NodeHandle nh;
// 4.创建订阅方对象;
ros::Subscriber sub = nh.subscribe("/test_string",10,doMsg);
// 5.处理订阅的数据。
ros::spin();
return 0;
}