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

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 immediately
        qDebug() << "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:0x030200dc
                QByteArray 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 immediately
        qDebug() << "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 immediately
        stopReplyTimer();
        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 immediately
        qDebug() << "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.kler.cn/a/380284.html

相关文章:

  • 缓存、注解、分页
  • Centos8安装软件失败更换镜像源
  • ArcGIS005:ArcMap常用操作101-150例动图演示
  • IT运维的365天--018 如何在内网布置一个和外网同域名的网站,并开启SSL(https访问),即外网证书如何在内网使用
  • 第02章 MySQL环境搭建
  • 常见的开源软件许可证及其应用案例
  • Ubuntu桌面环境下没有网络配置图标,只有显示VPN设置
  • 网络:IP分片和组装
  • 考取无人机“飞手”执照,进入部队、电力、铁路、石油企业抢占优势
  • 前端Nginx的安装与应用
  • 电脑如何不断网切换IP:实用方法与注意事项‌
  • Android -- [SelfView] 自定义圆盘指针时钟
  • qt QStatusBar详解
  • k8s 查看cpu使用率最高的pod
  • Hive自定义函数—剔除周日周六(小时级别)
  • 爬虫学习4
  • Vue中ref、reactive、toRef、toRefs的区别
  • IoTDB时序数据库使用
  • R 环境安装
  • 103 - Lecture 2 Table and Data Part 1
  • 初识JDBC
  • 深度学习基础知识-全连接层
  • Spring Cloud OpenFeign:基于Ribbon和Hystrix的声明式服务调用
  • python之正则表达式总结
  • 一键AI换衣-可图AI试衣
  • qt QSplitter详解