用 Qt Creator 创建项目来转换 ros 消息(涉及 ros 消息的订阅、接收、解析 以及 ros 消息的发布)

13 篇文章 3 订阅

1 说明

本文以 ros 消息的转换为例介绍如何使用 c++ 来:

  1. 接收 ros 消息并解析
  2. 发布 ros 消息

本案例运行的环境:

  • Ubuntu 16.04LTS
  • Qt Creator 4.11.1 (Based on Qt 5.14.1 (GCC 5.3.1 20160406 (Red Hat 5.3.1-6), 64 bit))
  • ROS Kinetic
  • c++ 11

2 创建 QT 项目

File - New File or Project ...(或Ctrl+N);
选择Qt Console Application,然后点击Choose...
在这里插入图片描述

设置项目名和路径后点击Next >,设置编译工具为qmake后一直点击Next >

在这里插入图片描述
然后就自动生成了项目文件(show是我的项目名称,之后的演示是基于重新生成的tran_msg项目):
在这里插入图片描述
自动生成的文件如下:
show.pro:

QT -= gui

CONFIG += c++11 console
CONFIG -= app_bundle

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
        main.cpp

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

main.cpp:

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    return a.exec();
}

2 将消息头文件复制到 QT 项目工作目录

消息的头文件是根据*.msg生成的 c++ 头文件,一般在 catkin 工作空间下,具体路径为<path to workspace>/devel/include/<package name>/,如果要创建自己的消息可以参考这篇文章:使用 catkin 的方式创建自定义的 ros 消息
对于我来说,将以下消息的头文件复制进了工作目录,为了方便理解消息的字段结构,我也将消息文件本体*.msg复制到了工作目录。
在这里插入图片描述
需要注意的是,如果消息中包涵自定义消息类型的字段,需要将这个自定义消息的头文件也拷贝到工作目录中。
例如,我们有两个*.msg文件:

  • A.msg
  • B.msg

A 消息的内容为:

int 32 id
...
B b_object
...

此时 A 就嵌套了 B 消息,如果我们的项目中需要用到 A 消息,除了需要将 A 消息的头文件拷贝到项目的工作目录,同时也需要把 B 消息的头文件拷贝到项目的工作目录。

3 修改 .pro 文件

.pro文件中包涵了编译的一些信息,需要进行修改,修改之后的文件为

QT += core
QT -= gui

CONFIG += c++11 console
CONFIG -= app_bundle

TEMPLATE = app
INCLUDEPATH += /opt/ros/indigo/include \
               /opt/ros/kinetic/include \
               ./planner \
               /usr/include/eigen3

DEPENDPATH += /opt/ros/indigo/include \
              /opt/ros/kinetic/include

LIBS += -L/opt/ros/indigo/lib -L/opt/ros/kinetic/lib -L/usr/local/lib \
    -lroscpp -lrospack -lpthread -lrosconsole \
    -lrosconsole_log4cxx -lrosconsole_backend_interface \
    -lxmlrpcpp -lroscpp_serialization -lrostime \
    -lcpp_common  -lroslib -lpthread

SOURCES += main.cpp

HEADERS += \
    BodyIMU.h \
    CarlaEgoVehicleControl.h \
    CarlaEgoVehicleInfo.h \
    CarlaImu.h \
    ChasisStatus.h \
    GPSPoint.h \
    insMsg.h

具体来说,添加了复制进来的消息头文件:

HEADERS += \
    BodyIMU.h \
    CarlaEgoVehicleControl.h \
    CarlaEgoVehicleInfo.h \
    CarlaImu.h \
    ChasisStatus.h \
    GPSPoint.h \
    insMsg.h

添加了一些依赖项/链接库的路径:

INCLUDEPATH += /opt/ros/indigo/include \
               /opt/ros/kinetic/include \
               ./planner \
               /usr/include/eigen3

DEPENDPATH += /opt/ros/indigo/include \
              /opt/ros/kinetic/include

LIBS += -L/opt/ros/indigo/lib -L/opt/ros/kinetic/lib -L/usr/local/lib \
    -lroscpp -lrospack -lpthread -lrosconsole \
    -lrosconsole_log4cxx -lrosconsole_backend_interface \
    -lxmlrpcpp -lroscpp_serialization -lrostime \
    -lcpp_common  -lroslib -lpthread

4 main.cpp 的准备工作

4.1 添加#include项:

  1. ros 依赖
#include "ros/ros.h"
#include "ros/init.h"
  1. 消息头文件
#include "BodyIMU.h"
#include "CarlaImu.h"

(下面的示例就用到这两个消息)

4.2 ros 初始化

参考ros 初始化和关闭 (ros::init(); ros::shutdown())
在 main 函数的开头进行 ros 的初始化,其命令格式为:

ros::init(argc,argv,"my_node_name");

ros::init(argc,argv,"my_node_name",ros::init_options::AnonymousName);

初始化选项有

  • NoSigintHandler: 不安装 SIGINT 句柄。这种情况下你需要自己安装SIGINT句柄来保证节点在退出时候会正确的关闭 。 SIGINT的默认操作是终结一个进行,所以如果你想自己处理 SIGTERM 你需要使用这个选项。
ros::init_options::NoSigintHandler 
  • AnonymousName: 匿名节点名称,增加一个随机数在节点名的后面,使得节点名唯一。
ros::init_options::AnonymousName 
  • NoRosout:不向/rosout 话题广播 rosconsole
ros::init_options::NoRosout 

我这里直接使用第一种方式:

ros::init(argc,argv,"TransTool");

5 我的程序总览

下面是我的main.cpp函数的内容,接下来的两个部分我将结合这个函数讲解 ros 消息的接收和发送。

#include "ros/ros.h"
#include "ros/init.h"

#include "BodyIMU.h"
#include "CarlaImu.h"

// 因为要在`main`和消息处理函数`CarlaImuInfoCallback`都使用这个变量,所以将其定义为全局变量
static ros::Publisher car_imu_info;
// 处理接收到的消息的函数
void CarlaImuInfoCallback(const trans_msg::CarlaImu &msg)
{
    trans_msg::BodyIMU imu;

    imu.acc_x = msg.linear_acceleration.x;
    imu.acc_y = msg.linear_acceleration.y;
    imu.acc_z = msg.linear_acceleration.z;

    imu.rot_x = msg.angular_velocity.x;
    imu.rot_y = msg.angular_velocity.y;
    imu.rot_z = msg.angular_velocity.z;

    imu.frame_num = msg.header.seq;
    imu.time_stamp = msg.header.stamp.nsec;
    // 发布消息
    car_imu_info.publish(imu);
//    printf("get imu\n");
}

int main(int argc, char *argv[])
{
    ros::init(argc,argv,"TransTool");

    ros::Subscriber carla_imu_info;
//    ros::Publisher car_imu_info;
    ros::NodeHandle NodeHandle;

    car_imu_info = NodeHandle.advertise<trans_msg::BodyIMU>("carImu",10);
    carla_imu_info = NodeHandle.subscribe("/carla/ego_vehicle/imu/imu1",10,CarlaImuInfoCallback);
	// 定时接收消息
    while(1)
    {
        ros::spinOnce();
        usleep(100000);
    }
}

6 订阅 ros 消息并解析

参考ROS自定义消息
定义订阅实例:

ros::Subscriber carla_imu_info;

实例化NodeHandle

NodeHandle 是roscpp用于创建订阅者、发布者的接口。详情参见http://docs.ros.org/melodic/api/roscpp/html/classros_1_1NodeHandle.html

ros::NodeHandle NodeHandle;

然后就是订阅节点了,一般的用法如下:

void callback(const std_msgs::Empty::ConstPtr& message)
{
}
ros::Subscriber sub = handle.subscribe("my_topic", 1, callback);

对应于我的代码就是:

carla_imu_info = NodeHandle.subscribe("/carla/ego_vehicle/imu/imu1",10,CarlaImuInfoCallback);
// 处理接收到的消息
void CarlaImuInfoCallback(const trans_msg::CarlaImu &msg)
{
    // 处理函数
}

说明:下面代码中的trans_msg是我自定消息所在package的名称,不太清楚可以打开消息头文件查看
在这里插入图片描述

接下来需要写一个 ros 消息回调处理函数,这个函数有两个选项:

  • ros::spin()
  • ros::spinOnce()

ros::spin()自身就是一个循环体,也就是说代码执行到这个语句就进入ros::spin()的内循环了,不断的触发,如果接收到订阅的节点的消息,就回调声明的消息处理函数;
ros::spinOnce()的特性如其名,只监听一次消息,只触发一次回调函数,执行完它之后程序还会继续执行。如果要用ros::spinOnce()持续监听消息,则需要将其放置在循环体中。

在我的代码里就是将其放置在循环体中来实现周期性监听消息的目的的:

 while(1)
 {
     ros::spinOnce();
     usleep(100000);
 }

7 发布 ros 消息

先实例化一个Publisher

ros::Publisher car_imu_info;

然后设置publisher(下面这一句的含义是:发布名为carImu,消息类型为trans_msg::BodyIMU,队列长度10)

car_imu_info = NodeHandle.advertise<trans_msg::BodyIMU>("carImu",10);

下面是初始化一个 trans_msg::BodyIMU 类型的消息

trans_msg::BodyIMU imu;

imu.acc_x = msg.linear_acceleration.x;
imu.acc_y = msg.linear_acceleration.y;
imu.acc_z = msg.linear_acceleration.z;

imu.rot_x = msg.angular_velocity.x;
imu.rot_y = msg.angular_velocity.y;
imu.rot_z = msg.angular_velocity.z;

imu.frame_num = msg.header.seq;
imu.time_stamp = msg.header.stamp.nsec;

最后发布这个消息:

car_imu_info.publish(imu);

如果不是像我这样有一个消息来触发创建当前消息,可以用一个循环来持续发布消息,如下:

// 设置循环的频率 
ros::Rate loop_rate(1); 
while (ros::ok()) { 
	// 初始化std_msgs::String类型的消息 
	my_topic::topic_msg msg;
	// 发布消息 
	pub_topic.publish(msg); 
	// 按照循环频率延时 
	loop_rate.sleep(); 
}

8 代码运行

使用QT运行 ros 项目的问题参见https://blog.csdn.net/amnesiagreen/article/details/108356826

我的程序的功能是将CARLA 仿真环境中车的IMU消息订阅并解析,然后转换为另一种格式的IMU消息。
我先运行CARLA的仿真环境,然后运行我的消息转换工程:
在这里插入图片描述

  • 2
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ROS中,回调函数是一种常见的处理机制,用于处理传入的消息或事件。当一个消息或事件到达时,回调函数会被自动调用。 要在一个回调函数中使用另一个回调函数中的数据,可以采取以下几种方法: 1. 全局变量:将数据定义为全局变量,使得所有的回调函数都可以访问和修改该变量。这种方法简单直接,但可能会引入并发访问的问题,需要注意线程安全性。 2. 类成员变量:如果你在ROS中使用了类,可以将数据定义为类的成员变量。这样,不同的回调函数可以通过类的实例来访问和修改数据。这种方法更加面向对象,可以更好地封装数据和逻辑。 3. 传递参数:在ROS中,回调函数通常有一个参数,用于接收传入的消息或事件。你可以在回调函数中将需要传递的数据作为参数传递给另一个回调函数。这样,你可以在一个回调函数中获取到数据,并将其传递给另一个回调函数进行处理。 4. 使用消息队列:如果你需要在多个回调函数之间共享数据,并且需要保证数据的同步和顺序,可以使用ROS提供的消息队列机制。你可以将数据发布到一个消息队列中,然后在另一个回调函数中订阅消息队列来获取数据。 需要注意的是,在使用回调函数时,要确保数据的正确性和一致性。避免在一个回调函数中修改数据,而在另一个回调函数中读取该数据时出现竞态条件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值