Qt串口异步通信案例(从机线程)

news2024/11/18 7:41:29

文章目录

  • 串口线程类
    • 初始化串口类
    • 打开串口并发送数据
    • 析构函数
  • 窗口设置
  • 窗口函数实现

串口线程类

SlaveThread(从机线程)

目的:等待并响应来自主机的请求,然后发送预设的响应数据。
关键行为:
    线程启动后,通过串口监听请求数据。
    当检测到有数据可读时,读取请求数据。
    读取完毕后,向串口写入预设的响应数据。
    如果写入成功,通过信号 request(QString) 发送接收到的请求信息给外部监听者。
    如果在读取请求或写入响应时发生超时,会触发 timeout(QString) 信号。
同步机制:使用 QMutex 保证线程安全,检查和更新串口号、等待超时时间以及响应内容,但没有使用 QWaitCondition,因为它是一个被动监听请求的线程,不需要主动唤醒机制。

SlaveThread (从机线程) 示例

假设在一个自动化仓库管理系统中,有多个库存检查站,每个站点装备了一个读取条形码的扫描枪作为从设备(Slave)。这些从设备并不主动发起操作,而是等待仓库管理系统(主设备)的指令来执行任务。
场景描述:

任务:当一个物品进入或离开仓库时,仓库管理系统需要记录该物品的条形码信息。
流程:主设备(如中央服务器)通过网络向各个站点的从机发送请求,要求读取条形码。从机(SlaveThread实例)不断监听串口,一旦接收到请求,立刻激活扫描枪读取条形码,然后通过串口将条形码数据作为响应发回给主设备。
特点:在这个场景中,SlaveThread体现为被动响应设备的职责,等待主设备的指令,执行读取任务并反馈结果。

SlaveThread 更适用于那些需要持续监听并响应外部指令的场景,如传感器数据采集、远程控制设备的响应等。(只采集数据,并不需要主动发送请求,而是始终监听并响应数据的到来)

class SlaveThread : public QThread
{
    Q_OBJECT

public:
    explicit SlaveThread(QObject *parent = nullptr);//
    ~SlaveThread();

    void startSlave(const QString &portName, int waitTimeout, const QString &response);//初始化串口及启动线程

signals:
    void request(const QString &s);//请求消息
    void error(const QString &s);//串口错误消息
    void timeout(const QString &s);//串口连接超时消息

private:
    void run() override;//串口通信

    QString m_portName;
    QString m_response;
    int m_waitTimeout = 0;
    QMutex m_mutex;
    bool m_quit = false;
};

初始化串口类

void SlaveThread::startSlave(const QString &portName, int waitTimeout, const QString &response)
{
    const QMutexLocker locker(&m_mutex);//初始化线程锁
    m_portName = portName;//设置串口号
    m_waitTimeout = waitTimeout;//设置串口的等待时间
    m_response = response;//设置串口的相应参数
    if (!isRunning())//判断线程是否运行
        start();//启动线程
}

打开串口并发送数据

void SlaveThread::run()
{
    bool currentPortNameChanged = false;

    // 加锁以安全地访问和更新线程间共享的数据
    m_mutex.lock();
    
    // 获取并记录当前配置与上次是否有变
    QString currentPortName;
    if (currentPortName != m_portName) { // 检查端口名是否变更
        currentPortName = m_portName;
        currentPortNameChanged = true; // 标记端口变更
    }

    int currentWaitTimeout = m_waitTimeout; // 获取超时时间
    QString currentResponse = m_response; // 获取响应信息
    m_mutex.unlock(); // 解锁,释放互斥锁

    QSerialPort serial; // 创建QSerialPort对象用于串口通信

    // 主循环,持续运行直到收到退出信号
    while (!m_quit) {
        // 如果端口设置发生变更
        if (currentPortNameChanged) {
            serial.close(); // 关闭旧端口
            serial.setPortName(currentPortName); // 设置新端口名

            // 尝试打开新的串口,失败则发送错误信号并退出
            if (!serial.open(QIODevice::ReadWrite)) {
                emit error(tr("无法打开端口 %1, 错误码 %2")
                           .arg(currentPortName).arg(serial.error()));
                return;
            }
        }

        // 等待读取请求数据
        if (serial.waitForReadyRead(currentWaitTimeout)) {
            // 读取所有可用的请求数据
            QByteArray requestData = serial.readAll();
            // 处理可能的分包情况,继续读取直到没有更多数据到来
            while (serial.waitForReadyRead(10))
                requestData += serial.readAll();

            // 准备响应数据并发送
            QByteArray responseData = currentResponse.toUtf8();
            serial.write(responseData);

            // 等待响应数据写入完成,成功则发送请求信号,否则发送写入超时信号
            if (serial.waitForBytesWritten(m_waitTimeout)) {
                QString request = QString::fromUtf8(requestData);
                emit request(request);
            } else {
                emit timeout(tr("等待写入响应超时 %1").arg(QTime::currentTime().toString()));
            }
        } else {
            // 如果等待读取请求超时,发送读取超时信号
            emit timeout(tr("等待读取请求超时 %1").arg(QTime::currentTime().toString()));
        }

        // 再次加锁,检查配置是否更新
        m_mutex.lock();
        if (currentPortName != m_portName) {
            currentPortName = m_portName;
            currentPortNameChanged = true;
        } else {
            currentPortNameChanged = false;
        }
        currentWaitTimeout = m_waitTimeout; // 更新超时时间
        currentResponse = m_response; // 更新响应信息
        m_mutex.unlock(); // 解锁
    }
}

析构函数

SlaveThread::~SlaveThread()
{
    m_mutex.lock();
    m_quit = true;
    m_mutex.unlock();
    wait();
}

窗口设置

#ifndef DIALOG_H
#define DIALOG_H

#include "slavethread.h"

#include <QDialog>

QT_BEGIN_NAMESPACE

class QLabel;
class QLineEdit;
class QComboBox;
class QSpinBox;
class QPushButton;

QT_END_NAMESPACE

class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = nullptr);

private slots:
    void startSlave();
    void showRequest(const QString &s);
    void processError(const QString &s);
    void processTimeout(const QString &s);
    void activateRunButton();

private:
    int m_transactionCount = 0;
    QLabel *m_serialPortLabel = nullptr;
    QComboBox *m_serialPortComboBox = nullptr;
    QLabel *m_waitRequestLabel = nullptr;
    QSpinBox *m_waitRequestSpinBox = nullptr;
    QLabel *m_responseLabel = nullptr;
    QLineEdit *m_responseLineEdit = nullptr;
    QLabel *m_trafficLabel = nullptr;
    QLabel *m_statusLabel = nullptr;
    QPushButton *m_runButton = nullptr;

    SlaveThread m_thread;
};

#endif // DIALOG_H

窗口函数实现

#include "dialog.h"

// 引入所需Qt模块的头文件
#include <QComboBox>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSerialPortInfo>
#include <QSpinBox>

// 定义Dialog类,继承自QWidget
Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    // 初始化UI组件
    m_serialPortLabel(new QLabel(tr("Serial port:"))), // 串口标签
    m_serialPortComboBox(new QComboBox),              // 串口选择组合框
    m_waitRequestLabel(new QLabel(tr("Wait request, msec:"))), // 等待请求时间标签
    m_waitRequestSpinBox(new QSpinBox),             // 等待请求时间微调框
    m_responseLabel(new QLabel(tr("Response:"))),     // 响应标签
    m_responseLineEdit(new QLineEdit(tr("Hello, I'm Slave."))), // 响应文本框,默认值
    m_trafficLabel(new QLabel(tr("No traffic."))),    // 流量信息标签
    m_statusLabel(new QLabel(tr("Status: Not running."))), // 状态标签
    m_runButton(new QPushButton(tr("Start")))      // 开始按钮
{
    // 设置等待请求时间的范围和默认值
    m_waitRequestSpinBox->setRange(0, 10000);
    m_waitRequestSpinBox->setValue(10000);

    // 获取并添加所有可用的串口名到组合框中
    const auto infos = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &info : infos)
        m_serialPortComboBox->addItem(info.portName());

    // 创建主网格布局,并将UI组件添加到布局中
    auto mainLayout = new QGridLayout;
    mainLayout->addWidget(m_serialPortLabel, 0, 0);
    mainLayout->addWidget(m_serialPortComboBox, 0, 1);
    mainLayout->addWidget(m_waitRequestLabel, 1, 0);
    mainLayout->addWidget(m_waitRequestSpinBox, 1, 1);
    mainLayout->addWidget(m_runButton, 0, 2, 2, 1); // 横跨两行
    mainLayout->addWidget(m_responseLabel, 2, 0);
    mainLayout->addWidget(m_responseLineEdit, 2, 1, 1, 3); // 横跨三列
    mainLayout->addWidget(m_trafficLabel, 3, 0, 1, 4); // 横跨四列
    mainLayout->addWidget(m_statusLabel, 4, 0, 1, 5); // 横跨五列
    setLayout(mainLayout); // 设置对话框的主布局

    // 设置窗口标题,并让串口选择框获得焦点
    setWindowTitle(tr("Blocking Slave"));
    m_serialPortComboBox->setFocus();

    // 连接信号与槽函数
    // 当按下开始按钮时,启动从机线程
    connect(m_runButton, &QPushButton::clicked, this, &Dialog::startSlave);
    // 当线程发出请求信号时,显示该请求
    connect(&m_thread, &SlaveThread::request, this,&Dialog::showRequest);
    // 处理错误信号
    connect(&m_thread, &SlaveThread::error, this, &Dialog::processError);
    // 处理超时信号
    connect(&m_thread, &SlaveThread::timeout, this, &Dialog::processTimeout);
    
    // 当串口选择、等待时间或响应文本改变时,激活或禁用开始按钮
    connect(m_serialPortComboBox, QOverload<const QString &>::of(&QComboBox::currentIndexChanged),
            this, &Dialog::activateRunButton);
    connect(m_waitRequestSpinBox, &QSpinBox::textChanged, this, &Dialog::activateRunButton);
    connect(m_responseLineEdit, &QLineEdit::textChanged, this, &Dialog::activateRunButton);
}

// 启动从机线程的槽函数
void Dialog::startSlave()
{
    m_runButton->setEnabled(false); // 禁用开始按钮
    // 更新状态标签,显示当前连接的端口信息
    m_statusLabel->setText(tr("Status: Running, connected to port %1.")
                           .arg(m_serialPortComboBox->currentText()));
    // 启动SlaveThread线程,传递配置参数
    m_thread.startSlave(m_serialPortComboBox->currentText(),
                        m_waitRequestSpinBox->value(),
                        m_responseLineEdit->text());
}

// 显示请求的槽函数
void Dialog::showRequest(const QString &s)
{
    // 更新流量信息,显示当前交易的请求和响应
    m_trafficLabel->setText(tr("Traffic, transaction #%1:"
                               "\n\r-request: %2"
                               "\n\r-response: %3")
                            .arg(++m_transactionCount) // 交易计数加一
                            .arg(s)                  // 请求内容
                            .arg(m_responseLineEdit->text())); // 预设响应
}

// 处理错误的槽函数
void Dialog::processError(const QString &s)
{
    // 重新激活开始按钮,更新状态标签显示错误信息
    activateRunButton();
    m_statusLabel->setText(tr("Status: Not running, %1.").arg(s));
    m_trafficLabel->setText(tr("No traffic."));
}

// 处理超时的槽函数
void Dialog::processTimeout(const QString &s)
{
    // 更新状态标签,显示超时信息
    m_statusLabel->setText(tr("Status: Running, %1.").arg(s));
    m_trafficLabel->setText(tr("No traffic."));
}

// 激活或禁用开始按钮的槽函数
void Dialog::activateRunButton()
{
    m_runButton->setEnabled(true); // 根据输入状态,决定是否启用开始按钮
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1710688.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

打造爆款活动:确定目标受众与吸引策略的实战指南

身为一名文案策划经理&#xff0c;我深知在活动策划的海洋中&#xff0c;确定目标受众并设计出能触动他们心弦的策略是何等重要。 通过以下步骤&#xff0c;你可以更准确地确定目标受众&#xff0c;并制定出有效的吸引策略&#xff0c;确保活动的成功&#xff1a; 明确活动目…

C++线程任务队列模型

功能描述 实现一个任务队列&#xff0c;用于任务的执行 任务队列 任务队列可以添加、删除任务&#xff0c;实现对任务的管理添加任务后&#xff0c;任务队列可以开始执行任务队列执行任务方式为串行执行 任务 任务执行需要持续一段10s内随机的时间&#xff0c;执行过程通过…

npm install node-sass 安装失败的解决方案:利用国内镜像加速安装

在开发前端项目时&#xff0c;使用Sass作为CSS预处理器是很多开发者的选择。然而&#xff0c;在通过npm安装其Node.js绑定库node-sass时&#xff0c;一些开发者可能会遇到安装失败的问题&#xff0c;尤其是网络原因导致的下载缓慢或中断。本文将指导你如何通过更换为国内镜像源…

联邦和反射器实验

拓扑图 一.实验要求 1.AS1存在两个环回&#xff0c;一个地址为192.168.1.0/24&#xff0c;该地址不能在任何协议中宣告 AS3存在两个环回&#xff0c;一个地址为192.168.2.0/24&#xff0c;该地址不能在任何协议中宣告 AS1还有一个环回地址为10.1.1.0/24&#xff…

【kubernetes】关于k8s集群的污点、容忍、驱逐以及k8s集群故障排查思路

目录 一、污点(Taint) 1.1污点介绍 1.2污点的组成格式 1.3当前 taint effect 支持如下三个选项&#xff1a; 1.4污点的增删改查 1.4.1验证污点的作用——NoExecute 1.4.2验证污点的作用——NoSchedule 1.4.3 验证污点的作用——PreferNoSchedule 1.5污点的配置与管理…

Python散点图矩阵代码模版

本文分享Python seaborn实现散点图矩阵代码模版&#xff0c;节选自&#x1f449;嫌Matplotlib繁琐&#xff1f;试试Seaborn&#xff01; 散点图矩阵&#xff08;scatterplot matrix&#xff09;展示原始数据中所有变量两两之间关系&#xff0c;可以规避单一统计指标的偏差&…

[Algorithm][动态规划][子数组/子串问题][最大子数组和][环形子数组的最大和][乘积最大子数组][乘积为正数的最长子数组长度]详细讲解

目录 1.最大子数组和1.题目链接2.算法原理详解3.代码实现 2.环形子数组的最大和1.题目链接2.算法原理详解3.代码实现 3.乘积最大子数组1.题目链接2.算法原理详解3.代码实现 4.乘积为正数的最长子数组长度1.题目链接2.算法原理详解3.代码实现 1.最大子数组和 1.题目链接 最大子…

ClickHouse数据管理与同步的关键技术

2024年 5 月 18 日&#xff0c;ClickHouse官方首届杭州 Meetup 活动成功举行。本次活动由 ClickHouse 和阿里云主办&#xff0c;NineData 和云数据库技术社区协办。围绕ClickHouse的核心技术、应用案例、最佳实践、数据管理、以及迁移同步等方面&#xff0c;和行业专家展开交流…

UE5 读取本地图片并转换为base64字符串

调试网址&#xff1a;在线图像转Base64 - 码工具 (matools.com) 注意要加&#xff08;data:image/png;base64,&#xff09; FString UBasicFuncLib::LoadImageToBase64(const FString& ImagePath) {TArray<uint8> ImageData;// Step 1: 读取图片文件到字节数组if (!…

嵌入式linux系统中NFS文件系统挂载详细实现

大家好,今天主要给大家分享一下,如何利用linux系统实现NFS文件系统挂载的方式与实现。 第一:linux-NFS挂载的目的 1、掌握 Ubuntu 系统 NFS 文件共享服务的安装及配置 2. 掌握嵌入式 Linux 系统通过 NFS 共享服务和 X86 宿主机进行数据共享,文件共享的方法。 …

【Unity脚本】Unity中如何按类型查找游戏对象(GameObject)

【知识链】Unity -> 脚本系统 -> 访问游戏对象 -> 按类型访问游戏对象摘要&#xff1a;本文介绍了Unity中按类型查找游戏对象&#xff08;GameObject&#xff09;的五种方法&#xff0c;并提出了使用这些方法的最佳实践。 本文目录 一、访问游戏对象的方法二、如何按…

【oracle】Oracle RAC中的GNS到底是什么?

本文为云贝教育 刘峰 原创&#xff0c;请尊重知识产权&#xff0c;转发请注明出处&#xff0c;不接受任何抄袭、演绎和未经注明出处的转载 一、概述 Oracle Grid Naming Service (GNS) 是Oracle Grid Infrastructure的一个重要组件&#xff0c;它提供了一种集中式的命名服务&…

css样式,点击 箭头方向上下转换

实现效果&#xff1a; 点击切换箭头方向 实现代码 <divclass"modelPart"click"showClick"><div class"modelPart_left"><img:srcaidefalutIconclass"sNodeIcon"><div>{{ selectModel }}</div><div …

C# 配置文件设置详解

文章目录 1. 配置文件在 C# 项目中的作用和重要性2. 不同类型的配置文件app.configconfig.exejson 3. 创建和修改配置文件文件位置添加内容修改内容保存和加载 4. 读取和写入配置文件app.config 文件读取config.exe 文件写入JSON 文件读写 5. 示例代码演示6. 配置文件在安全性方…

利用AI技术实现Medium文章的高效中文翻译

在深入学习大模型的过程中&#xff0c;我们常常需要查阅Medium上的技术文章。Medium作为一个流行的内容发布平台&#xff0c;汇集了大量高质量的技术和科学文章&#xff0c;对于希望紧跟技术前沿的学习者来说&#xff0c;是一个宝贵的知识库。然而&#xff0c;这些文章大多为英…

eclipse启动时间过长的问题

项目场景&#xff1a; 由于我用eclipse比较习惯&#xff0c;虽然IDEA很好&#xff0c;但是因为收费&#xff0c;所以在个人开发学习过程中一直还是使用eclipse&#xff0c;本文不讨论eclipse与IDEA孰优孰劣问题。 开发环境&#xff1a; 操作系统&#xff1a;Windows 11 22631…

CANDela studio之CDDT与CDD

CDDT有更高的权限&#xff0c;作为模板规范CDD文件。 CDD可修改的内容比CDDT少。 CDDT根据诊断协议提供诊断格式&#xff0c;主要就是分类服务和定义服务&#xff0c;一般是OEM释放&#xff0c;然后由供应商细化成自己零部件的CDD文件。 在这里举个例子&#xff0c;OEM在CDDT…

心链4---搜索页面前后端业务实现以及分布式session的共享实现

心链 — 伙伴匹配系统 搜索详情页面 之前前端的代码写到了搜索页面可以挑选搜索标签&#xff0c;并没有去根据具体标签搜索用户。这里就开始实现。 新建SearchResultPage.vue&#xff0c;并添加相关路由。 在搜索页添加搜索按钮&#xff0c;和触发点击。搜索页选择标签&#x…

ubuntu server 24.04 (Linux) 源码编译安装 OpenResty 1.25.3.1 Released

1 下载: OpenResty - 开源官方站 2 通过xftp等方式上传到ubuntu服务器 3 安装 #解压 tar zxvf openresty-1.25.3.1.tar.gz #创建运行用户 sudo groupadd www sudo useradd -g www www -s /bin/false #安装依赖软件 sudo apt update sudo apt-get install libpcre3-dev l…

数据分析之统计学基础

数据分析是现代企业和科研中不可或缺的一部分&#xff0c;而统计学是数据分析的基石。在本篇博客中&#xff0c;我们将介绍统计学的基础知识&#xff0c;涵盖数据类型、描述性统计&#xff08;集中趋势、离散程度和偏差程度&#xff09;&#xff0c;并通过代码实例加以说明。 …