Qt Modbus 2 通信实现

上一文章主要了解下Modbus协议和事务处理流程,本章则直接贴放代码实现qt 上位机与温控器通讯。项目主要实现定时读取温度、设置温度、开始加热和停止加热四个功能。
采用的是 Modbus Rtu 通信

1 Qt modbus 模块依赖

QT += serialbus serialport

源代码

.h文件

#ifndef MODBUSDEVICE_H
#define MODBUSDEVICE_H#include <QObject>
#include <QModbusDevice>
#include <QSerialPort>
#include <QModbusPdu>
#include <QModbusRtuSerialMaster>
#include <QSerialPortInfo>
#include <QTimer>
#define ADDR_READ_PV  "0100"
#define ADDR_READ_SV  "0300"
#define ADDR_SET_SV   "0300"
#define ADDR_RUN  "0190"
class ModbusDevice : public QObject
{Q_OBJECT
public:explicit ModbusDevice(QObject *parent = nullptr);~ModbusDevice();void setAddresss(const QVector<quint8> addrs);Q_INVOKABLE void setCom(const QString &com);Q_INVOKABLE QString getCom();void open(const QString &portname);void closeAndDelete();Q_INVOKABLE void close();Q_INVOKABLE void connect(const QString &com);Q_INVOKABLE void disconnect();Q_INVOKABLE bool state();void sendReadPV();void sendSetSV(const float v);void sendRun();void setStopSync(const quint8 addr);void sendStop();void sendSetSV(const quint8 addr,const float v);void sendReadPV(const quint8 addr);void sendReadSV(const quint8 addr);void sendRun(const quint8 addr);void sendStop(const quint8 addr);void stopTimer();signals:void sigError(const QString &msg);void sigDisconnected();void sigConnected();void sigModbusSendError(const QString &msg);void sigInfo(const QString &msg);void sigGetPV(const int index,const float v);void sigGetSV(const int index,const float v);
private slots:void onReplyTimeout();
private:void creatDevice();void reconnect();void creatTimer();void startTimer();void handleSerialPortDisconnected();void readValue(const quint8 address,const QString addr);void setRun(const quint8 address,const bool isRun);void startReplyTimer();void stopReplyTimer();QSerialPort::BaudRate   baudRate=QSerialPort::Baud9600;QSerialPort::DataBits   dataBits=QSerialPort::Data8;QSerialPort::Parity     parity=QSerialPort::EvenParity;QSerialPort::StopBits   stopBits=QSerialPort::TwoStop;QTimer m_pvTimer;QTimer m_replyTimer;QString m_strCom;QModbusClient *m_device = nullptr;QVector<quint8> m_deviceAddrs;int m_errCount = 0;int m_timeoutContinousCount = 0;
};#endif // MODBUSDEVICE_H

cpp文件

#include "modbusdevice.h"
#include <QDebug>
#include <QModbusReply>
#include <QCoreApplication>
#include <QThread>
ModbusDevice::ModbusDevice(QObject *parent) : QObject(parent)
{m_deviceAddrs << 1 <<2;creatTimer();creatDevice();
}ModbusDevice::~ModbusDevice()
{stopTimer();closeAndDelete();
}void ModbusDevice::setAddresss(const QVector<quint8> addrs)
{m_deviceAddrs = addrs;
}void ModbusDevice::open(const QString &portname)
{if (!m_device){sigError(QString("串口连接失败,请排查线路"));return;}m_device->setConnectionParameter(QModbusDevice::SerialPortNameParameter,portname);m_device->connectDevice();
}void ModbusDevice::closeAndDelete()
{if (!m_device)return;m_device->disconnectDevice();m_device->disconnect();delete m_device;m_device = nullptr;
}void ModbusDevice::close()
{if (!m_device)return;stopTimer();m_device->disconnectDevice();
}void ModbusDevice::connect(const QString &com)
{setCom(com);open(com);
}void ModbusDevice::disconnect()
{if (!m_device)return ;close();
}bool ModbusDevice::state()
{if (!m_device)return false;return m_device->state() == QModbusDevice::ConnectedState;
}void ModbusDevice::sendReadPV()
{for(auto v:m_deviceAddrs){sendReadPV(v);}
}void ModbusDevice::sendSetSV(const float v)
{for(auto addr:m_deviceAddrs){sendSetSV(addr,v);}
}void ModbusDevice::sendRun()
{for(auto addr:m_deviceAddrs){sendRun(addr);}
}void ModbusDevice::setStopSync(const quint8 addr)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;const QByteArray pduData = QByteArray::fromHex("01900000");quint16 fc = 0x06;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),addr);while (!reply->isFinished()) {QCoreApplication::processEvents();if (QCoreApplication::instance()->closingDown()) {break;}QThread::msleep(10);}if (reply->error() == QModbusDevice::NoError) {qDebug() << "Command sent successfully.";} else {qWarning() << "Failed to send command:" << reply->errorString();}reply->deleteLater();
}void ModbusDevice::sendStop()
{for(auto addr:m_deviceAddrs){sendStop(addr);}
}void ModbusDevice::sendSetSV(const quint8 addr,const float v)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;short tmp = (v*10);QString msg =  QString("0300%1").arg(tmp, 4, 16, QLatin1Char('0')).toUpper();const QByteArray pduData = QByteArray::fromHex(msg.toLatin1());quint16 fc = 0x06;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),addr);startReplyTimer();if (!reply->isFinished()) {QObject::connect(reply, &QModbusReply::finished, this, [=]() {stopReplyTimer();if (reply->error() == QModbusDevice::NoError) {sigInfo(QString("设置指令发送成功"));qDebug() << "setD1Value reveive:" << reply->rawResult();} else if (reply->error() == QModbusDevice::ProtocolError) {QString msg = QString("setValue response error: %1 Mobus exception: 0x%2").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16);qDebug() << msg;emit sigModbusSendError(msg);} else {QString msg = (tr("setValue response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), -1, 16));qDebug() << msg;emit sigModbusSendError(msg);}reply->deleteLater();});} else {stopReplyTimer();// broadcast replies return immediatelyqDebug() << "Receive: Synchronous response pdu: " << reply->rawResult() << Qt::endl;reply->deleteLater();}
}void ModbusDevice::sendReadPV(const quint8 addr)
{readValue(addr,ADDR_READ_PV);
}void ModbusDevice::sendReadSV(const quint8 addr)
{readValue(addr,ADDR_READ_SV);
}void ModbusDevice::sendRun(const quint8 addr)
{setRun(addr,true);
}void ModbusDevice::sendStop(const quint8 addr)
{setRun(addr,false);
}void ModbusDevice::setCom(const QString &com)
{if(com != m_strCom){m_strCom = com;}
}QString ModbusDevice::getCom()
{return m_strCom;
}void ModbusDevice::reconnect()
{if (!m_device)return ;if (m_device->state() != QModbusDevice::ConnectedState) {qInfo() << "Attempting to reconnect...";m_device->connectDevice();}
}void ModbusDevice::creatDevice()
{m_device = new QModbusRtuSerialMaster;//m_device->setConnectionParameter(QModbusDevice::SerialPortNameParameter,portname);m_device->setConnectionParameter(QModbusDevice::SerialParityParameter, parity);m_device->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,dataBits);m_device->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,stopBits);m_device->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,baudRate);m_device->setTimeout(1000);m_device->setNumberOfRetries(1);QObject::connect(m_device, &QModbusDevice::errorOccurred, this, [=](QModbusDevice::Error) {QString msg = m_device->errorString();qDebug().noquote() << QStringLiteral("Error: %1").arg(msg);emit sigError(msg);}, Qt::QueuedConnection);QObject::connect(m_device, &QModbusDevice::stateChanged, [=](QModbusDevice::State state) {switch (state) {case QModbusDevice::UnconnectedState:{qDebug().noquote() << QStringLiteral("State: Entered unconnected state.");stopTimer();emit sigDisconnected();}break;case QModbusDevice::ConnectingState:qDebug().noquote() << QStringLiteral("State: Entered connecting state.");break;case QModbusDevice::ConnectedState:{qDebug().noquote() << QStringLiteral("State: Entered connected state.");sendReadPV();startTimer();emit sigConnected();}break;case QModbusDevice::ClosingState:qDebug().noquote() << QStringLiteral("State: Entered closing state.");break;}});
}void ModbusDevice::handleSerialPortDisconnected(){qWarning() << "Serial port disconnected.";close();}void ModbusDevice::creatTimer()
{m_pvTimer.setInterval(4000);QObject::connect(&m_pvTimer,&QTimer::timeout,this,[=]{sendReadPV();});m_replyTimer.setInterval(3000);m_replyTimer.setSingleShot(true);QObject::connect(&m_replyTimer,SIGNAL(timeout()),this,SLOT(onReplyTimeout()));
}void ModbusDevice::startTimer()
{m_pvTimer.start();
}void ModbusDevice::stopTimer()
{m_pvTimer.stop();
}void ModbusDevice::onReplyTimeout()
{m_timeoutContinousCount++;qDebug() << __func__;sigModbusSendError(QString("发包响应超时,请排查线路"));if(m_timeoutContinousCount>3){sigError(QString("通信超时,请排查线路"));m_timeoutContinousCount=0;close();}
}void ModbusDevice::readValue(const quint8 address,const QString addr)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;QString msg = addr+"0001";const QByteArray pduData = QByteArray::fromHex(msg.toLatin1());//qDebug() << "Send: Sending PDU with predefined function code.";quint16 fc = 0x03;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),address);startReplyTimer();if (!reply->isFinished()) {QObject::connect(reply, &QModbusReply::finished, this, [=](){stopReplyTimer();if (reply->error() == QModbusDevice::NoError) {const QByteArray rawData = reply->rawResult().data();//ex:0x030200dcQByteArray lastTwoBytes = rawData.mid(rawData.size() - 2, 2);// 使用 QDataStream 进行字节序转换QDataStream stream(lastTwoBytes);quint16 shortValue;stream >> shortValue;if(addr == ADDR_READ_PV){emit sigGetPV(address,(float)shortValue/10);}else if(addr == ADDR_READ_SV){emit sigGetSV(address,(float)shortValue/10);}} else if (reply->error() == QModbusDevice::ProtocolError) {QString msg =  QString("readValue response error: %1 Mobus exception: 0x%2").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16);qDebug() << msg;emit sigModbusSendError(msg);} else {QString msg =  (tr("readValue response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), -1, 16));qDebug() << msg;m_errCount ++;if(m_errCount > 3){emit sigError(QString("温控%1 连接已断开").arg(address));m_errCount = 0;reconnect();}emit sigModbusSendError(msg);}reply->deleteLater();});} else {stopReplyTimer();// broadcast replies return immediatelyqDebug() << "Receive: Synchronous response pdu: " << reply->rawResult() << Qt::endl;reply->deleteLater();}
}void ModbusDevice::setRun(const quint8 address,const bool isRun)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;const QByteArray pduData = isRun? QByteArray::fromHex("01900001"):QByteArray::fromHex("01900000");quint16 fc = 0x06;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),address);startReplyTimer();if (!reply->isFinished()) {QObject::connect(reply, &QModbusReply::finished, this, [=]() {stopReplyTimer();if (reply->error() == QModbusDevice::NoError) {if(isRun)sigInfo(QString("开始指令发送成功"));else {sigInfo(QString("停止指令发送成功"));}qDebug() << "setRun reveive:" << reply->rawResult();} else if (reply->error() == QModbusDevice::ProtocolError) {QString msg =  QString("setRun response error: %1 Mobus exception: 0x%2").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16);qDebug() << msg;emit sigModbusSendError(msg);} else {QString msg = (tr("setRun response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), -1, 16));qDebug() << msg;emit sigModbusSendError(msg);}reply->deleteLater();});} else {// broadcast replies return immediatelystopReplyTimer();qDebug() << "setRun Receive: Synchronous response pdu: " << reply->rawResult() << Qt::endl;reply->deleteLater();}
}void ModbusDevice::startReplyTimer()
{m_replyTimer.start();
}void ModbusDevice::stopReplyTimer()
{m_replyTimer.stop();
}

异常处理

1. 通信断开异常检测

测试过程中发现,拔掉串口线,QModbusClient 并未检测到任何异常,这时候发包也不会有任何异常提示,所以在发包之后自己启动一个timer,来通过超时判断是否串口连接出现问题。有更好的方法可以分享下。

void ModbusDevice::sendSetSV(const quint8 addr,const float v)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;short tmp = (v*10);QString msg =  QString("0300%1").arg(tmp, 4, 16, QLatin1Char('0')).toUpper();const QByteArray pduData = QByteArray::fromHex(msg.toLatin1());quint16 fc = 0x06;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),addr);startReplyTimer();if (!reply->isFinished()) {QObject::connect(reply, &QModbusReply::finished, this, [=]() {stopReplyTimer();if (reply->error() == QModbusDevice::NoError) {sigInfo(QString("设置指令发送成功"));qDebug() << "setD1Value reveive:" << reply->rawResult();} else if (reply->error() == QModbusDevice::ProtocolError) {QString msg = QString("setValue response error: %1 Mobus exception: 0x%2").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16);qDebug() << msg;emit sigModbusSendError(msg);} else {QString msg = (tr("setValue response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), -1, 16));qDebug() << msg;emit sigModbusSendError(msg);}reply->deleteLater();});} else {stopReplyTimer();// broadcast replies return immediatelyqDebug() << "Receive: Synchronous response pdu: " << reply->rawResult() << Qt::endl;reply->deleteLater();}
}

其中 startReplyTimer() 和 stopReplyTimer() 来实现链路检测。

2. 程序关闭时,需要发送一条指令,等发送和响应结束后才能关闭App

因为是加热设备,为了安全起见,程序关闭时,会主动停止加热。此时就需要实现一个同步发送的指令。等待返回后才能关闭App

void ModbusDevice::setStopSync(const quint8 addr)
{if (!m_device)return ;if(m_device->state() != QModbusDevice::ConnectedState)return;QModbusReply *reply = nullptr;const QByteArray pduData = QByteArray::fromHex("01900000");quint16 fc = 0x06;reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(fc), pduData),addr);while (!reply->isFinished()) {QCoreApplication::processEvents();if (QCoreApplication::instance()->closingDown()) {break;}QThread::msleep(10);}if (reply->error() == QModbusDevice::NoError) {qDebug() << "Command sent successfully.";} else {qWarning() << "Failed to send command:" << reply->errorString();}reply->deleteLater();
}

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

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

相关文章

033_Structure_Static_In_Matlab求解结构静力学问题两套方法

结构静力学问题 静力学问现在是已经很简单的问题&#xff0c;在材料各向同性的情况下&#xff0c;对于弹性固体材料&#xff0c;很容易通过有限元求解。特别是线弹性问题&#xff0c;方程的矩阵形式可以很容易的写出&#xff08;准确得说是很容易通过有限元表达&#xff09;&a…

rnn/lstm 项目实战

tip:本项目用到的数据和代码在https://pan.baidu.com/s/1Cw6OSSWJevSv7T1ouk4B6Q?pwdz6w2 1. RNN : 预测股价 任务&#xff1a;基于zgpa_train.csv数据,建立RNN模型,预测股价 1.完成数据预处理&#xff0c;将序列数据转化为可用于RNN输入的数据 2.对新数据zgpa_test.csv进…

jenkins 构建报错 mvn: command not found

首先安装过 maven&#xff0c;并且配置过环境变量 win r ,输入 cmd 键入 mvn -v 出现上图输出&#xff0c;则证明安装成功。 原因 jenkins 没有 maven 配置全局属性, 导致无法找到 mvn 命令。 解决方案 找到全局属性&#xff0c;点击新增&#xff0c;配置 MAVEN_HOME 路…

轮廓图【HTML+CSS+JavaScript】

给大家分享一个很好看的轮播图&#xff0c;这个也是之前看到别人写的效果感觉很好看&#xff0c;所以后面也自己实现了一下&#xff0c;在这里分享给大家&#xff0c;希望大家也可以有所收获 轮播图效果&#xff1a; 视频效果有点浑浊&#xff0c;大家凑合着看&#xff0c;大家…

ChatGPT变AI搜索引擎!以后还需要谷歌吗?

前言 在北京时间11月1日凌晨&#xff0c;正值ChatGPT两岁生日之际&#xff0c;OpenAI宣布推出最新的人工智能搜索体验&#xff01;具备实时网络功能&#xff01;与 Google 展开直接竞争。 ChatGPT搜索的推出标志着ChatGPT成功消除了即时信息这一最后的短板。 这项新功能可供 …

Netty 组件介绍 - ByteBuf

直接内存&堆内存 ByteBuf buffer ByteBufAllocator.DEFAULT.heapBuffer(10);ByteBuf byteBuf ByteBufAllocator.DEFAULT.directBuffer(10); 组成 ByteBuf维护了两个不同的索引&#xff0c;一个用于读取&#xff0c;一个用于写入。 写入 内存回收 堆内存使用的是JVM内…

都快2025年了,来看看哪个编程语言才是时下热门吧

早上好啊&#xff0c;大佬们&#xff0c;今天咱们不讲知识&#xff0c;今天我们来看看时下热门的编程语言都是哪些&#xff0c;大佬们又都是在学哪些语言呢。 最近一些朋友和我在讨论哪个编程语言是现在 最好用 最厉害 的编程语言。 有人说&#xff0c;Python简单好用&#xf…

【雷达信号数据集】雷达脉冲活动分段的多级学习算法【附下载链接】

摘要 无线电信号识别是电子战中的一项重要功能。电子战系统需要精确识别和定位雷达脉冲活动&#xff0c;以产生有效的对抗措施。尽管这些任务很重要&#xff0c;但基于深度学习的雷达脉冲活动识别方法在很大程度上仍未得到充分探索。虽然之前已经探索了用于雷达调制识别的深度…

vscode php Launch built-in server and debug, PHP内置服务xdebug调试,自定义启动参数配置使用示例

在vscode中&#xff0c;当我们安装了插件 PHP Debug&#xff08;xdebug.php-debug&#xff09;或者 xdebug.php-pack 后 我们通过内置默认的 php xdebug配置启动php项目后&#xff0c;默认情况下我们在vscode中设置断点是不会生效的&#xff0c;因为我们的内置php服务默认启动时…

(二 上)VB 2010 设计初步

目录 一、常用类应用 1.Console类控制台 2.窗体基本控件 二、面向对象程序设计 1.类和对象 2.对象的属性、方法、事件属 1.属性 2.方法 3.事件、事件过程 1.事件 2.事件过程 3.对象浏览器 三、.NET类库与命名空间 1.命名空间 常用命名空间 1.System命名空间 2.…

[CARLA系列--01]CARLA 0.9.15 在Windows下的安装教程(一)

Carla是一款开源的自动驾驶仿真器&#xff0c;它基本可以用来帮助训练自动驾驶的所有模块&#xff0c;包括感知系统&#xff0c;Localization, 规划系统等等.Carla这个产品目前已经更新到了最新的0.9.15版本,目前遇到好多人在windows系统上如何安装可编辑版的Carla遇到了好多问…

禾川HCQ1控制器程序编译报错如何解决

1、第一次打开用户程序 2、提示库未安装 3、安装库文件 4、脉冲轴库未安装 5、没有错误 去禾川自动化官网,把可以安装的包和库都安装下,程序编译就没有错误了。 6、下载相关包文件

ubuntu20安装opencv3.2记录

系统环境 ubuntu20安装了ros-noetic&#xff0c;所以系统默认装了opencv4.2.0&#xff0c;但是跑fastlivo推荐的是opencv3.2.0&#xff0c;而且海康相机别人写的ros驱动&#xff08;海康相机ros驱动&#xff09;也是需要opencv3.2.0&#xff0c;最终还是选择安装多版本的openc…

基于NVIDIA NIM平台实现盲人过马路的demo(一)

前言:利用NVIDIA NIM平台提供的大模型进行编辑,通过llama-3.2-90b-vision-instruct模型进行初步的图片检测 step1: 部署大模型到本地,引用所需要的库 import os import requests import base64 import cv2 import time from datetime import datetimestep2: 观看官方使用文…

MATLAB下的四个模型的IMM例程(CV、CT左转、CT右转、CA四个模型),附源代码可复制

文章目录 基于IMM算法的目标跟踪概述源代码运行结果代码结构与功能1. 初始化2. 仿真参数设置3. 模型参数设置4. 生成量测数据5. IMM算法初始化6. IMM迭代7. 绘图8. 辅助函数总结基于IMM算法的目标跟踪 概述 该MATLAB代码实现了基于交互式多模型(IMM)算法的目标跟踪,旨在估…

Netty 组件介绍 - Future Promise

在异步处理时&#xff0c;经常用到这两个接口 netty 中的 Future 继承 jdk 中的 FutuFuture&#xff0c;而Promise 又对 netty Future 进行了扩展。 idk Future 只能同步等待任务结束&#xff08;或成功或失败)才能得到结果netty Future 可以同步等待任务结束得到结也可以异…

ai数字人分身123口播克隆数字人小程序源码_博纳软云

功能配置 一、用户 用户管理小黑屋用户反馈登录设置短信参数 二、作品 视频作品背景音乐库背景音乐分类 三、形象分身 上传记录视频要求参数配置 四、声音克隆 克隆记录参数配置声音要求文案示例 五、AI文案 生成记录创作模型模型分类Al配置 六、充值 充值订单积分套…

Elasticsearch Interval 查询:为什么它们是真正的位置查询,以及如何从 Span 转换

作者&#xff1a;来自 Elastic Mayya Sharipova 解释 span 查询如何成为真正的位置查询以及如何从 span 查询过渡到它们。 长期以来&#xff0c;Span 查询一直是有序和邻近搜索的工具。这些查询对于特定领域&#xff08;例如法律或专利搜索&#xff09;尤其有用。但相对较新的 …

软件测试模型

软件测试模型是在软件开发过程中&#xff0c;用于指导软件测试活动的一系列方法和框架。这些模型帮助测试团队确定何时进行测试、测试什么以及如何测试&#xff0c;从而确保软件的质量和稳定性。 一 V模型 V模型是一种经典的软件测试模型,它由瀑布研发模型演变而来的测试模型…

Tiling与流水线技术小结

文章目录 Tiling技术Loop TilingAI推理中的Tiling 参考 流水线技术指令周期 参考 Tiling技术 Tiling&#xff08;平铺&#xff09;是一种将大的问题或数据集分解为较小的子问题或子数据集的技术&#xff0c;目的是提高数据局部性和缓存利用率&#xff0c;从而提升程序性能。 在…