当前位置: 首页 > java >正文

ROS2 QT 多线程功能包设计

一个完整的 ROS2 QT 多线程功能包实现,包含可选 GUI 界面、发布者线程、接收者线程和参数服务器交互功能。

功能包结构

plaintext

ros2_qt_multi_thread/
├── CMakeLists.txt
├── package.xml
├── src/
│   ├── main.cpp
│   ├── main_window.cpp
│   ├── main_window.ui
│   ├── ros_node.cpp
│   ├── publisher_thread.cpp
│   ├── subscriber_thread.cpp
│   └── param_manager.cpp
└── include/└── ros2_qt_multi_thread/├── main_window.hpp├── ros_node.hpp├── publisher_thread.hpp├── subscriber_thread.hpp└── param_manager.hpp

核心文件实现

1. package.xml

xml

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3"><name>ros2_qt_multi_thread</name><version>0.0.0</version><description>ROS2 QT Multi-threaded Package with GUI, Publisher, Subscriber and Param Server</description><maintainer email="your@email.com">Your Name</maintainer><license>Apache-2.0</license><buildtool_depend>ament_cmake</buildtool_depend><buildtool_depend>ament_cmake_qt</buildtool_depend><depend>rclcpp</depend><depend>std_msgs</depend><depend>Qt5Widgets</depend><depend>Qt5Core</depend><build_depend>ament_cmake</build_depend><exec_depend>rclcpp</exec_depend><exec_depend>std_msgs</exec_depend><exec_depend>Qt5Widgets</exec_depend><build_type>ament_cmake</build_type><export><build_type>ament_cmake</build_type></export>
</package>

2. CMakeLists.txt

cmake

cmake_minimum_required(VERSION 3.5)
project(ros2_qt_multi_thread)if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")add_compile_options(-Wall -Wextra -Wpedantic)
endif()# Qt5 配置
find_package(Qt5 COMPONENTS Widgets Core REQUIRED)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(Qt5_LIBRARIES Qt5::Widgets Qt5::Core)# ROS2 配置
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)# 包含目录
include_directories(include${Qt5Widgets_INCLUDE_DIRS}
)# 源文件
set(SOURCESsrc/main.cppsrc/main_window.cppsrc/ros_node.cppsrc/publisher_thread.cppsrc/subscriber_thread.cppsrc/param_manager.cpp
)# UI文件
set(UISsrc/main_window.ui
)# 生成可执行文件
add_executable(${PROJECT_NAME}${SOURCES}${UIS}
)# 链接库
target_link_libraries(${PROJECT_NAME}${Qt5_LIBRARIES}rclcpp::rclcpp${std_msgs_TARGETS}
)# 安装
install(TARGETS${PROJECT_NAME}DESTINATION lib/${PROJECT_NAME}
)ament_package()

3. 头文件

include/ros2_qt_multi_thread/main_window.hpp

cpp

运行

#ifndef ROS2_QT_MULTI_THREAD_MAIN_WINDOW_HPP_
#define ROS2_QT_MULTI_THREAD_MAIN_WINDOW_HPP_#include <QMainWindow>
#include <QThread>
#include <QTimer>
#include <memory>
#include "ui_main_window.h"
#include "ros_node.hpp"
#include "param_manager.hpp"namespace ros2_qt_multi_thread
{class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(bool use_gui = true, QWidget *parent = nullptr);~MainWindow();void showStatusMessage(const QString &message);void updatePublisherRate(int rate);void updateSubscriberCount(int count);void addSubscriberMessage(const QString &message);public slots:void onStartPublishClicked();void onStopPublishClicked();void onUpdateParamsClicked();void onRosSpinOnce();private:Ui::MainWindow ui_;bool use_gui_;std::shared_ptr<RosNode> ros_node_;std::unique_ptr<ParamManager> param_manager_;QTimer *ros_timer_;
};}  // namespace ros2_qt_multi_thread#endif  // ROS2_QT_MULTI_THREAD_MAIN_WINDOW_HPP_
include/ros2_qt_multi_thread/ros_node.hpp

cpp

运行

#ifndef ROS2_QT_MULTI_THREAD_ROS_NODE_HPP_
#define ROS2_QT_MULTI_THREAD_ROS_NODE_HPP_#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
#include <QObject>
#include <memory>
#include "publisher_thread.hpp"
#include "subscriber_thread.hpp"
#include "param_manager.hpp"namespace ros2_qt_multi_thread
{class RosNode : public rclcpp::Node
{Q_OBJECTpublic:RosNode(const std::string &node_name = "qt_ros2_node");~RosNode() override;void startPublishing(int rate);void stopPublishing();void startSubscribing();void setParameters(const std::map<std::string, std::string> &params);std::map<std::string, std::string> getParameters();std::shared_ptr<PublisherThread> getPublisherThread() { return publisher_thread_; }std::shared_ptr<SubscriberThread> getSubscriberThread() { return subscriber_thread_; }std::shared_ptr<ParamManager> getParamManager() { return param_manager_; }signals:void statusUpdated(const QString &status);void messageReceived(const QString &message);void subscriberCountUpdated(int count);private:std::shared_ptr<PublisherThread> publisher_thread_;std::shared_ptr<SubscriberThread> subscriber_thread_;std::shared_ptr<ParamManager> param_manager_;
};}  // namespace ros2_qt_multi_thread#endif  // ROS2_QT_MULTI_THREAD_ROS_NODE_HPP_
include/ros2_qt_multi_thread/publisher_thread.hpp

cpp

运行

#ifndef ROS2_QT_MULTI_THREAD_PUBLISHER_THREAD_HPP_
#define ROS2_QT_MULTI_THREAD_PUBLISHER_THREAD_HPP_#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
#include <QThread>
#include <QMutex>
#include <atomic>namespace ros2_qt_multi_thread
{class PublisherThread : public QThread
{Q_OBJECTpublic:PublisherThread(rclcpp::Node::SharedPtr node);~PublisherThread() override;void setRate(int rate);void startPublishing();void stopPublishing();bool isPublishing() const { return is_publishing_; }signals:void statusUpdated(const QString &status);void messagePublished(const QString &message);protected:void run() override;private:rclcpp::Node::SharedPtr node_;rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;mutable QMutex mutex_;std::atomic<bool> is_publishing_;int publish_rate_;int message_count_;
};}  // namespace ros2_qt_multi_thread#endif  // ROS2_QT_MULTI_THREAD_PUBLISHER_THREAD_HPP_
include/ros2_qt_multi_thread/subscriber_thread.hpp

cpp

运行

#ifndef ROS2_QT_MULTI_THREAD_SUBSCRIBER_THREAD_HPP_
#define ROS2_QT_MULTI_THREAD_SUBSCRIBER_THREAD_HPP_#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
#include <QThread>
#include <QMutex>
#include <atomic>namespace ros2_qt_multi_thread
{class SubscriberThread : public QThread
{Q_OBJECTpublic:SubscriberThread(rclcpp::Node::SharedPtr node);~SubscriberThread() override;void startSubscribing();void stopSubscribing();int getMessageCount() const { return message_count_; }signals:void messageReceived(const QString &message);void countUpdated(int count);private:void topicCallback(const std_msgs::msg::String::SharedPtr msg);rclcpp::Node::SharedPtr node_;rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscriber_;mutable QMutex mutex_;std::atomic<bool> is_subscribing_;std::atomic<int> message_count_;
};}  // namespace ros2_qt_multi_thread#endif  // ROS2_QT_MULTI_THREAD_SUBSCRIBER_THREAD_HPP_
include/ros2_qt_multi_thread/param_manager.hpp

cpp

运行

#ifndef ROS2_QT_MULTI_THREAD_PARAM_MANAGER_HPP_
#define ROS2_QT_MULTI_THREAD_PARAM_MANAGER_HPP_#include <rclcpp/rclcpp.hpp>
#include <QObject>
#include <map>
#include <string>namespace ros2_qt_multi_thread
{class ParamManager : public QObject
{Q_OBJECTpublic:ParamManager(rclcpp::Node::SharedPtr node);~ParamManager() override = default;void setParameters(const std::map<std::string, std::string> &params);std::map<std::string, std::string> getParameters() const;void declareDefaultParameters();signals:void parametersUpdated(const std::map<std::string, std::string> &params);void statusUpdated(const QString &status);private:rclcpp::Node::SharedPtr node_;mutable std::recursive_mutex param_mutex_;std::map<std::string, std::string> current_params_;
};}  // namespace ros2_qt_multi_thread#endif  // ROS2_QT_MULTI_THREAD_PARAM_MANAGER_HPP_

4. 源文件实现

src/main.cpp

cpp

运行

#include <QApplication>
#include <rclcpp/rclcpp.hpp>
#include <iostream>
#include "ros2_qt_multi_thread/main_window.hpp"int main(int argc, char *argv[])
{// 初始化ROS2rclcpp::init(argc, argv);// 检查是否需要启动GUIbool use_gui = true;for (int i = 0; i < argc; ++i) {if (std::string(argv[i]) == "--no-gui") {use_gui = false;break;}}// 初始化QT应用QApplication app(argc, argv);// 创建主窗口ros2_qt_multi_thread::MainWindow main_window(use_gui);// 如果使用GUI,则显示窗口if (use_gui) {main_window.show();} else {main_window.showStatusMessage("Running in headless mode");}// 运行QT事件循环int result = app.exec();// 关闭ROS2rclcpp::shutdown();return result;
}
src/main_window.cpp

cpp

运行

#include "ros2_qt_multi_thread/main_window.hpp"
#include <QMessageBox>
#include <QTimer>namespace ros2_qt_multi_thread
{MainWindow::MainWindow(bool use_gui, QWidget *parent): QMainWindow(parent), use_gui_(use_gui)
{// 设置UIif (use_gui_) {ui_.setupUi(this);setWindowTitle("ROS2 QT Multi-Thread");// 连接信号槽connect(ui_.start_publish_btn, &QPushButton::clicked, this, &MainWindow::onStartPublishClicked);connect(ui_.stop_publish_btn, &QPushButton::clicked, this, &MainWindow::onStopPublishClicked);connect(ui_.update_params_btn, &QPushButton::clicked, this, &MainWindow::onUpdateParamsClicked);}// 创建ROS节点ros_node_ = std::make_shared<RosNode>();// 设置参数管理器param_manager_ = std::make_unique<ParamManager>(ros_node_);param_manager_->declareDefaultParameters();// 启动订阅者ros_node_->startSubscribing();// 连接ROS信号connect(ros_node_->getSubscriberThread().get(), &SubscriberThread::messageReceived,this, &MainWindow::addSubscriberMessage);connect(ros_node_->getSubscriberThread().get(), &SubscriberThread::countUpdated,this, &MainWindow::updateSubscriberCount);connect(ros_node_.get(), &RosNode::statusUpdated,this, &MainWindow::showStatusMessage);// 创建ROS定时器,定期处理事件ros_timer_ = new QTimer(this);connect(ros_timer_, &QTimer::timeout, this, &MainWindow::onRosSpinOnce);ros_timer_->start(10);  // 每10ms处理一次
}MainWindow::~MainWindow()
{ros_node_->stopPublishing();delete ros_timer_;
}void MainWindow::showStatusMessage(const QString &message)
{if (use_gui_) {ui_.status_bar->showMessage(message, 3000);ui_.log_textEdit->append(message);} else {std::cout << "[STATUS] " << message.toStdString() << std::endl;}
}void MainWindow::updatePublisherRate(int rate)
{if (use_gui_) {ui_.publish_rate_label->setText(QString("Publish Rate: %1 Hz").arg(rate));}
}void MainWindow::updateSubscriberCount(int count)
{if (use_gui_) {ui_.sub_count_label->setText(QString("Received Messages: %1").arg(count));} else {std::cout << "Total received messages: " << count << std::endl;}
}void MainWindow::addSubscriberMessage(const QString &message)
{if (use_gui_) {ui_.sub_messages_textEdit->append(message);// 限制显示行数while (ui_.sub_messages_textEdit->document()->blockCount() > 100) {QTextCursor cursor(ui_.sub_messages_textEdit->document()->firstBlock());cursor.select(QTextCursor::BlockUnderCursor);cursor.removeSelectedText();cursor.deleteChar();}} else {std::cout << "[RECEIVED] " << message.toStdString() << std::endl;}
}void MainWindow::onStartPublishClicked()
{if (use_gui_) {int rate = ui_.publish_rate_spinBox->value();ros_node_->startPublishing(rate);showStatusMessage(QString("Starting publishing at %1 Hz").arg(rate));}
}void MainWindow::onStopPublishClicked()
{ros_node_->stopPublishing();showStatusMessage("Stopped publishing");
}void MainWindow::onUpdateParamsClicked()
{if (use_gui_) {std::map<std::string, std::string> params;params["publish_topic"] = ui_.pub_topic_lineEdit->text().toStdString();params["subscribe_topic"] = ui_.sub_topic_lineEdit->text().toStdString();params["message_prefix"] = ui_.msg_prefix_lineEdit->text().toStdString();ros_node_->setParameters(params);showStatusMessage("Parameters updated");}
}void MainWindow::onRosSpinOnce()
{// 处理ROS事件rclcpp::spin_some(ros_node_);
}}  // namespace ros2_qt_multi_thread
src/ros_node.cpp

cpp

运行

#include "ros2_qt_multi_thread/ros_node.hpp"namespace ros2_qt_multi_thread
{RosNode::RosNode(const std::string &node_name): Node(node_name)
{// 创建发布者线程publisher_thread_ = std::make_shared<PublisherThread>(shared_from_this());// 创建订阅者线程subscriber_thread_ = std::make_shared<SubscriberThread>(shared_from_this());// 创建参数管理器param_manager_ = std::make_shared<ParamManager>(shared_from_this());// 连接信号connect(publisher_thread_.get(), &PublisherThread::statusUpdated,this, &RosNode::statusUpdated);connect(subscriber_thread_.get(), &SubscriberThread::messageReceived,this, &RosNode::messageReceived);connect(subscriber_thread_.get(), &SubscriberThread::countUpdated,this, &RosNode::subscriberCountUpdated);
}RosNode::~RosNode()
{stopPublishing();
}void RosNode::startPublishing(int rate)
{publisher_thread_->setRate(rate);publisher_thread_->startPublishing();
}void RosNode::stopPublishing()
{if (publisher_thread_ && publisher_thread_->isPublishing()) {publisher_thread_->stopPublishing();}
}void RosNode::startSubscribing()
{subscriber_thread_->startSubscribing();
}void RosNode::setParameters(const std::map<std::string, std::string> &params)
{param_manager_->setParameters(params);
}std::map<std::string, std::string> RosNode::getParameters()
{return param_manager_->getParameters();
}}  // namespace ros2_qt_multi_thread
src/publisher_thread.cpp

cpp

运行

#include "ros2_qt_multi_thread/publisher_thread.hpp"
#include <chrono>
#include <thread>namespace ros2_qt_multi_thread
{PublisherThread::PublisherThread(rclcpp::Node::SharedPtr node): node_(node), is_publishing_(false), publish_rate_(10), message_count_(0)
{// 创建发布者std::string topic_name = "qt_publisher_topic";if (node_->has_parameter("publish_topic")) {node_->get_parameter("publish_topic", topic_name);} else {node_->declare_parameter("publish_topic", topic_name);}publisher_ = node_->create_publisher<std_msgs::msg::String>(topic_name, 10);
}PublisherThread::~PublisherThread()
{stopPublishing();wait();
}void PublisherThread::setRate(int rate)
{QMutexLocker locker(&mutex_);publish_rate_ = rate;
}void PublisherThread::startPublishing()
{QMutexLocker locker(&mutex_);if (!is_publishing_) {is_publishing_ = true;if (!isRunning()) {start();}}
}void PublisherThread::stopPublishing()
{QMutexLocker locker(&mutex_);is_publishing_ = false;
}void PublisherThread::run()
{while (is_publishing_) {// 获取当前参数std::string topic_name = "qt_publisher_topic";std::string message_prefix = "Message from QT: ";node_->get_parameter("publish_topic", topic_name);node_->get_parameter("message_prefix", message_prefix);// 如果话题名称已更改,重新创建发布者if (publisher_->get_topic_name() != topic_name) {publisher_ = node_->create_publisher<std_msgs::msg::String>(topic_name, 10);emit statusUpdated(QString("Publisher topic changed to: %1").arg(QString::fromStdString(topic_name)));}// 创建并发布消息auto msg = std_msgs::msg::String();msg.data = message_prefix + std::to_string(message_count_++);publisher_->publish(msg);emit messagePublished(QString::fromStdString(msg.data));emit statusUpdated(QString("Published: %1").arg(QString::fromStdString(msg.data)));// 控制发布频率int rate;{QMutexLocker locker(&mutex_);rate = publish_rate_;}if (rate > 0) {std::this_thread::sleep_for(std::chrono::milliseconds(1000 / rate));} else {std::this_thread::sleep_for(std::chrono::milliseconds(100));}}
}}  // namespace ros2_qt_multi_thread
src/subscriber_thread.cpp

cpp

运行

#include "ros2_qt_multi_thread/subscriber_thread.hpp"namespace ros2_qt_multi_thread
{SubscriberThread::SubscriberThread(rclcpp::Node::SharedPtr node): node_(node), is_subscribing_(false), message_count_(0)
{// 初始化订阅者std::string topic_name = "qt_subscriber_topic";if (node_->has_parameter("subscribe_topic")) {node_->get_parameter("subscribe_topic", topic_name);} else {node_->declare_parameter("subscribe_topic", topic_name);}createSubscriber(topic_name);
}SubscriberThread::~SubscriberThread()
{stopSubscribing();wait();
}void SubscriberThread::startSubscribing()
{QMutexLocker locker(&mutex_);if (!is_subscribing_) {is_subscribing_ = true;if (!isRunning()) {start();}}
}void SubscriberThread::stopSubscribing()
{QMutexLocker locker(&mutex_);is_subscribing_ = false;
}void SubscriberThread::createSubscriber(const std::string &topic_name)
{subscriber_ = node_->create_subscription<std_msgs::msg::String>(topic_name,10,std::bind(&SubscriberThread::topicCallback, this, std::placeholders::_1));
}void SubscriberThread::topicCallback(const std_msgs::msg::String::SharedPtr msg)
{message_count_++;emit messageReceived(QString::fromStdString(msg->data));emit countUpdated(message_count_);
}void SubscriberThread::run()
{std::string current_topic;while (is_subscribing_ && rclcpp::ok()) {// 检查话题是否更改std::string topic_name;node_->get_parameter("subscribe_topic", topic_name);if (topic_name != current_topic) {current_topic = topic_name;createSubscriber(topic_name);}// 处理回调rclcpp::spin_some(node_);// 短暂休眠,降低CPU占用std::this_thread::sleep_for(std::chrono::milliseconds(10));}
}}  // namespace ros2_qt_multi_thread
src/param_manager.cpp

cpp

运行

#include "ros2_qt_multi_thread/param_manager.hpp"namespace ros2_qt_multi_thread
{ParamManager::ParamManager(rclcpp::Node::SharedPtr node): node_(node)
{declareDefaultParameters();
}void ParamManager::declareDefaultParameters()
{std::lock_guard<std::recursive_mutex> lock(param_mutex_);// 声明默认参数node_->declare_parameter("publish_topic", "qt_publisher_topic");node_->declare_parameter("subscribe_topic", "qt_subscriber_topic");node_->declare_parameter("message_prefix", "Message from QT: ");node_->declare_parameter("log_level", "info");// 初始化当前参数映射current_params_["publish_topic"] = "qt_publisher_topic";current_params_["subscribe_topic"] = "qt_subscriber_topic";current_params_["message_prefix"] = "Message from QT: ";current_params_["log_level"] = "info";
}void ParamManager::setParameters(const std::map<std::string, std::string> &params)
{std::lock_guard<std::recursive_mutex> lock(param_mutex_);for (const auto &[key, value] : params) {// 设置ROS参数node_->set_parameter(rclcpp::Parameter(key, value));current_params_[key] = value;emit statusUpdated(QString("Set parameter: %1 = %2").arg(QString::fromStdString(key), QString::fromStdString(value)));}emit parametersUpdated(current_params_);
}std::map<std::string, std::string> ParamManager::getParameters() const
{std::lock_guard<std::recursive_mutex> lock(param_mutex_);return current_params_;
}}  // namespace ros2_qt_multi_thread

使用说明

1. 编译功能包

bash

colcon build --packages-select ros2_qt_multi_thread
source install/setup.bash

2. 运行程序

带 GUI 模式

bash

ros2 run ros2_qt_multi_thread ros2_qt_multi_thread
无头模式(无 GUI)

bash

ros2 run ros2_qt_multi_thread ros2_qt_multi_thread --no-gui

3. 功能说明

  • 发布者线程:可配置发布频率和消息前缀,通过 ROS 参数动态更改发布话题
  • 接收者线程:订阅指定话题,统计接收消息数量,支持动态更改订阅话题
  • 参数服务器:管理所有可配置参数,包括话题名称、消息前缀等
  • GUI 界面:提供直观的控制界面,显示发布 / 订阅状态和消息内容

4. 关键特性

  • 多线程分离:发布者和订阅者在独立线程中运行,避免相互阻塞
  • 参数动态更新:支持运行时更改参数,无需重启程序
  • 可选 GUI:根据需求选择是否启动图形界面
  • 信号槽机制:QT 信号槽与 ROS 回调无缝集成,确保线程安全

这个功能包可以作为 ROS2 与 QT 集成的基础框架,你可以根据实际需求扩展消息类型和功能。

http://www.xdnf.cn/news/17553.html

相关文章:

  • 基于PyTorch一文讲清楚损失函数与激活函数并配上详细的图文讲解
  • redis集群-docker环境
  • 咪咕MGV3200-KLH_GK6323V100C_板号E503744_安卓9_短接强刷包-可救砖
  • 大数据架构演变之路
  • java的三种组件漏洞靶场攻略
  • LeetCode 组合总数
  • 人工智能系列(8)如何实现无监督学习聚类(使用竞争学习)?
  • 1. 电阻选型
  • 计算机网络:如何理解目的网络不再是一个完整的分类网络
  • mpv core_thread pipeline
  • jmeter常规压测【读取csv文件】
  • 北京JAVA基础面试30天打卡06
  • Vulhub靶场组件漏洞(XStream,fastjson,Jackson)
  • 北京天津廊坊唐山打捞失物日记
  • 双非二本如何找工作?
  • jxWebUI--按钮
  • 黑马SpringBoot+Elasticsearch作业2实战:商品搜索与竞价排名功能实现
  • 【RocketMQ 生产者和消费者】- ConsumeMessageConcurrentlyService 并发消费消息
  • socket编程中系统调用send()详细讲解
  • MySQL自增ID与UUID的区别及其在索引分裂中的表现与优化
  • 七、CV_模型微调
  • 通过sealos工具在ubuntu 24.02上安装k8s集群
  • DevOps:从GitLab .gitlab-ci.yml 配置文件到CI/CD
  • 第十五讲:set和map
  • WebAssembly技术详解:从浏览器到云原生的高性能革命
  • 本地WSL部署接入 whisper + ollama qwen3:14b 总结字幕增加利用 Whisper 分段信息,全新 Prompt功能
  • 国内外主流大模型深度体验与横向评测:技术、场景与未来展望
  • 生产工具革命:定制开发开源AI智能名片S2B2C商城小程序重构商业生态的范式研究
  • 密码学的数学基础2-Paillier为什么产生密钥对比RSA慢
  • 基于django的宠物用品购物商城的设计与实现