Qt_modbus_Thread

在Qt中,使用modbus连接PLC,有断线重连部分,与PLC形成长链接。源源不断的读取PLC数据,没有内存泄漏,但是在写数据部分,由于自己写的程序只发送一次写命令,可能造成写失败。

 废话不多说,上代码:头文件:

#ifndef BASE_THREAD_H
#define BASE_THREAD_H
//Prevents duplicate header files
#include <QObject>
#include <QModbusTcpClient>
#include <QMutexLocker>
#include <QModbusReply>
#include <QThread>
#include <QHash>
#include <QFrame>
#include <vld.h>
#include <Device_Struct.h>
#include <utils.h>
class Widget;


class BaseThread : public QThread
{
    Q_OBJECT
public:
    BaseThread(QObject *parent = nullptr);
    ~BaseThread();

signals:
    void signalConnectDevice();
    void signalReadRegisterData(int, int, int); //placeholder parameters
    void signalWriteRegisterData(int, uint16_t, int);
    void signalKeepAlive();

    void signalForMainWindowShowZZJ1(bool isConnected);
    void signalForMainWindowShowZZJ2(bool isConnected);
    void signalForMainWindowShowXHD1(bool isConnected);
    void signalForMainWindowShowXHD2(bool isConnected);

public slots:
    void slotErrorOccurred(QModbusDevice::Error error);
    void slotStateChanged(QModbusDevice::State state);
    void slotConnectDevice();
    void slotReadyRead();
    //this virtual function have to override
    void slotReadRegisterData(int nStartAddress, int nNum, int type = QModbusDataUnit::HoldingRegisters);
    void slotWriteRegisterData(quint16 DeviceNO, quint16 addr, int value, int itype);
    void READ_HEARTBEAT();

public:
    quint16 SERVER_ID;//device ID for diff device used in read/write
    quint16 simulatorNum;
    bool connect(QString strIp, int nPort);
    bool isConnected();
    bool isClosed();
    void sleep(unsigned int msec);
    bool readRegister16(uint16_t uAddr, int &uValue);
    //QVector<Device_Info> GetDeviceInfos();
    QModbusTcpClient * m_pClient;
    //Read HoldingRegisters
    QHash<uint16_t, int> m_readValueHoldingRegistersHash;
    QHash<uint16_t, uint16_t> m_readAddrHoldingRegistersHash;  //read response
    QHash<uint16_t, uint16_t> m_writeAddrHoldingRegistersHash; //write response
    //prevent memory leak for ModbusReply
    QList<QModbusReply*> replyList;
    QList<QModbusReply*> respList;
    QVector<Device_Info> GetXHD_Infos();
    QVector<Device_Info> GetZZJ_Infos();

protected:
    void run() override;

private:
    //be care of memory leak
    bool m_bConnected;
    bool m_bAppClose;
    QVector<Device_Info> XHD_Infos;
    QVector<Device_Info> ZZJ_Infos;
    //avoid re-execution
    QVector<Device_Info> LastZZJ1Infos;
    QVector<Device_Info> LastXHD1Infos;
    QVector<Device_Info> LastZZJ2Infos;
    QVector<Device_Info> LastXHD2Infos;
    //QVector<Device_Info> Device_Infos;
};


#endif // BASE_THREAD_H

其中,utils代码如下:

#include "utils.h"

Utils::Utils()
{

};

Utils::~Utils()
{

};

QString Utils::EncodeData(QString data)
{
    QByteArray value  = data.toUtf8();
    QByteArray key1 = "123321";
    QByteArray key2 = "321123";
    for(quint16 i = 0; i < value.size(); ++i)
    {
        quint16 keyIndex1 = i % key1.size();
        quint16 keyIndex2 = i % key2.size();
        value[i] = value[i] ^ key1[keyIndex1];
        value[i] = value[i] ^ key2[keyIndex2];
    }
    return QString(value);
}

QByteArray Utils::HexToByte(QString str)
{
    QByteArray bytes = str.toUtf8();
    const char* arr = bytes.constData();
    return QByteArray::fromHex(arr);
}

QString Utils::ByteToHex(QByteArray bytes)
{
    QByteArray bytes1 = bytes.toHex();
    const char* arr = bytes1.constData();
    return QString::fromUtf8(arr).toUpper();
}
//clrbit/setbit/getbit external interface copy from web
bool Utils::getBit(int value, quint16 pos)
{
    bool bx;

    if(((value >> pos) & 1) == 1)
        bx = true;
    else
        bx = false;
    return bx;
}

quint16 Utils::clrBit(int value, quint16 pos)
{
    uint16_t nx;

    nx = 0x1 << pos;
    nx = ~nx;
    value = value & nx;
    return value;
}

bool Utils::isValidIP(const QString& ipAddress)
{
    if (ipAddress.isEmpty()) {
        return false;
    }

    // RegularExpression
    QRegularExpression ipRegex("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$");
    if (!ipRegex.match(ipAddress).hasMatch()) {
        return false;
    }

    // split it
    QStringList ipParts = ipAddress.split(".");
    if (ipParts.size() != 4) {
        return false;
    }

    // 0-255?
    bool ok;
    for (const QString& part : ipParts) {
        int ipInt = part.toInt(&ok);
        if (!ok || ipInt < 0 || ipInt > 255) {
            return false;
        }
    }

    // correct
    return true;
}


quint16 Utils::setBit(int value, quint16 pos)
{
    quint16 nx;

    nx = 0x1 << pos;
    return value = nx | value;
}




cpp:

#include "Base_Thread.h"
#include <QVariant>
#include <QModbusTcpClient>
#include <QModbusDevice>
#include <QDebug>
#include <QElapsedTimer>
#include <QTime>
#include <QVector>
#include <QCoreApplication>
#include <vld.h>

/*
refer to blogs:https://blog.csdn.net/Mingyueruya/article/details/121332323

According to the communication protocol, only 40001 holdingRegisters are used(XHD ZZJ); when the hash table m_readAddrHoldingRegistersHash is used, the stored values
keep {0:1}, means only read/write this single holdingRegister, address starts from 0.

Depending on the communication protocol, the XHD should be implemented as another class.

TODO::considering:Thread dll(later),config file(completed),others ZZJ(now just two),simulator QTServer(later);
we need read/write quickly!The same menu only set one Qmenu.

TODO:: When the program is initialized, if not online, turnout color should be gray.
program needs to wait for a while, needs debug. DO NOT USE PROXY!

TODO::disconnect and reconnect(test), online first then read. cache?, which will slow down the program.
the port is in use ! whats the matter?

connect the control machine, (preferably use modbusServer,mind leak)

vld no memory leak but modbusReply reference modbusTcpClient!LEAK!!!
*/

/* considers:
1, If you use the TCPSocket written by own, there is a data reading error, which is obviously 000c,
but ff0c is displayed.
2, multi-thread, do not use global variables and static variables.
3,maybe TCPSocket generate a thread error.
4,own TCPSocket memory high,CPU high.
*/

struct sModbusTimer
{
    QElapsedTimer time;
    uint32_t interval;

    void start(uint32_t t)
    {
        interval = t;
        time.start();
    };

    bool isTimeOut()
    {
        return time.elapsed() > interval;
    };
};
//default parameters in the function declaration, dont default parameters in the function definition
BaseThread::BaseThread(QObject *parent)
    : QThread(parent)
    ,m_pClient(new QModbusTcpClient(this)) // fathers father father QObiect
{
    m_bAppClose = false; //APP close flag
    m_bConnected = false;//connect flag
    simulatorNum = 0;
    m_readAddrHoldingRegistersHash[0] = 1;//ZZJ 40001 holdingRegisters
    m_writeAddrHoldingRegistersHash[0] = 42;
    QObject::connect(this, &BaseThread::signalConnectDevice, this, &BaseThread::slotConnectDevice);
    QObject::connect(this, &BaseThread::signalReadRegisterData, this, &BaseThread::slotReadRegisterData);
}

BaseThread::~BaseThread()
{
    //QThread::msleep(256);
    //quit gentlely
    requestInterruption();
    quit();
    wait();
    m_bAppClose = true;
    if (m_bConnected){
        m_pClient->disconnect();
    };
    if(m_pClient){
        delete m_pClient;
    }
}

//keep alive
void BaseThread::READ_HEARTBEAT()
{
    if(simulatorNum >= 10000){
        simulatorNum = 0;
    }
    if(simulatorNum++ % 512 == 0){
        QModbusDataUnit readUnit = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 1, 1);
        if(m_pClient){
            if (auto *resp = m_pClient->sendReadRequest(readUnit, SERVER_ID)){ //SERVER_ID->deviceID(slave ID)
                if (nullptr == resp){m_bConnected = false;}
                else{
                    m_pClient->disconnect(SIGNAL(timeoutChanged(int)),0,0);
                    if(!resp->isFinished()){
                        QObject::connect(resp, &QModbusReply::finished, this, [this,resp](){
                            if (resp->error() == QModbusDevice::NoError){
                                m_bConnected = true;}
                            else{m_bConnected = false;}
                            resp->deleteLater();
                        });
                    }
                    else
                    {resp->deleteLater();}
                }
            }
        }
    }
}

QVector<Device_Info> BaseThread::GetZZJ_Infos()
{
    return ZZJ_Infos;
}

QVector<Device_Info> BaseThread::GetXHD_Infos()
{
    return XHD_Infos;
}

bool BaseThread::connect(QString strIp, int nPort)
{
    if (m_bConnected)
        return true;
    //ip detected
    if (!Utils::isValidIP(strIp)){
        return false;
    }
    //m_pClient type : QModbusTcpClient *,QModbusTcpClientpublic:QModbusClient:public QModbusDevice
    //function prototype : void setConnectionParameter(int parameter, const QVariant &value);
    m_pClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, QVariant::fromValue(nPort));//Set the connection parameter port
    m_pClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter,QVariant::fromValue(strIp));//Set the connection parameter IP
    m_pClient->setTimeout(3000);//Set the timeout
    m_pClient->setNumberOfRetries(3);
    //m_pClient->connectDevice() just connected to a virtual (simulated) channel !
    //even if disconnect also return true!!!
    //connect to the device and return a flag  !!! no no no , this flags not connected flag!
    if(m_pClient->connectDevice()){
        //state changed
        QObject::connect(m_pClient, &QModbusTcpClient::stateChanged, this, &BaseThread::slotStateChanged);
        //error occured
        QObject::connect(m_pClient, &QModbusTcpClient::errorOccurred, this, &BaseThread::slotErrorOccurred);
        //keep alive
        QObject::connect(this, &BaseThread::signalKeepAlive, this, &BaseThread::READ_HEARTBEAT);
    }
    m_bConnected = (m_pClient->state() == QModbusDevice::ConnectedState) ? true:false;

    if (!this->isRunning()){ //thread not exec , start it
        this->start();}
    //useless code , this return value  not used.
    return m_bConnected;   //return BOOL
}
//function which used in main , provide external interface
bool BaseThread::isConnected()
{
    return m_bConnected;
}

bool BaseThread::isClosed()
{
    return m_bAppClose;
}
void BaseThread::slotErrorOccurred(QModbusDevice::Error error)
{
    //do nothing??
    qDebug() << "ModbusClient Error Num:" << error << "\n";
    qDebug() << "ModbusClient Error Info:" << m_pClient->errorString() << "\n";
    m_pClient->disconnectDevice();
}

void BaseThread::slotStateChanged(QModbusDevice::State state)
{
    switch (state)
    {
    case QModbusDevice::UnconnectedState:
    {
        m_bConnected = false;
        //logger_->info("UnconnectedState!"); dont use logger,will cause program crashes
    }break;
    case QModbusDevice::ConnectedState:
    {
        m_bConnected = true;
        //logger_->info("ConnectedState!");
    }break;
    case QModbusDevice::ClosingState:
    {
        m_bConnected = false;
        //logger_->info("ClosingState!");
    }break;
    case QModbusDevice::ConnectingState:
    {
        m_bConnected = false;
    }break;
    default:
        break;
    }
}

// reconnect/connect to device
void BaseThread::slotConnectDevice()
{
    if (!m_bConnected)
    {
        //abort (have to test)
        //follows sleep code not good!
        //sleep(500); // app lags
        //BaseThread::msleep(500); // CPU high
        m_pClient->connectDevice();
    }
}

void BaseThread::run()
{
    sModbusTimer timeout;//timer strcuct
    int nStep = 0;
    while (!isInterruptionRequested())
    {
        //we have to sleep with thread ,dont call sleep function (bottom) otherwise CPU high
        BaseThread::msleep(4); // set 128ms sleep
        // APP Closed
        if (m_bAppClose) //if app closed,thread out too
            return;
        //About 2 seconds to send a heartbeat
        //emit signalKeepAlive();
        switch (nStep) //0 : start
        {
        case 0:
        {
            if (!m_bConnected)//If connection successful, flag is false, wait 1500ms, and make nStep = 1
            {
                switch (SERVER_ID) {
                case ZZJConst::StartDC + 1: //LastZZJ1Infos.clear() avoid the judgment of not entering  slotreadregisters`s emit
                {emit signalForMainWindowShowZZJ1(m_bConnected);if(LastZZJ1Infos.size()!=0){LastZZJ1Infos.clear();}}break;
                case ZZJConst::StartDC + 2:
                {emit signalForMainWindowShowZZJ2(m_bConnected);if(LastZZJ2Infos.size()!=0){LastZZJ2Infos.clear();}}break;
                case XHDConst::StartLed + 1:
                {emit signalForMainWindowShowXHD1(m_bConnected);if(LastXHD1Infos.size()!=0){LastXHD1Infos.clear();}}break;
                case XHDConst::StartLed + 2:
                {emit signalForMainWindowShowXHD2(m_bConnected);if(LastXHD2Infos.size()!=0){LastXHD2Infos.clear();}}break;
                default:break;}
                timeout.start(1500);
                //put disconnected here , give app a rest
                //m_pClient->disconnectDevice();
                //BaseThread::msleep(128);
                nStep = 1;
            }
            else  //If the connection is successful, make nStep = 2
                nStep = 2;
        }break;
        case 1:
        {
            if (timeout.isTimeOut() && !m_bConnected) //exceeds 3s and is not connected yet
            {
                // sending signals to connected again, different threads need to be sent with the signal
                emit signalConnectDevice();
                nStep = 0;//(cycle between  two steps (0,1) until the connection is successful

            }
            else if (m_bConnected) //if connected,nstep = 0 -> 2
                nStep = 0;

        }break;

        case 2:
        {
            // if true keep flush
            if (true)
            {
                static int nIndex = 0; //just one
                auto keys = m_readAddrHoldingRegistersHash.keys();//assume  map[(20(key),3(value))]   keys {20}
                uint16_t uAddr = keys[nIndex]; //20
                // read holdingRegister for response
                emit signalReadRegisterData(uAddr, m_readAddrHoldingRegistersHash[uAddr],(int)QModbusDataUnit::RegisterType::HoldingRegisters);
            }
            nStep = 0;
        }break; //break to  prevent case penetration

        default:
            break;
        }
    }

}

// this function isnt used
void BaseThread::slotReadyRead()
{
    auto* reply = qobject_cast<QModbusReply*>(sender());//qobject_cast
    if (reply)//if reply not empty
    {
        replyList.append(reply);
        if (reply->error() == QModbusDevice::NoError)// reply no error
        {
            m_pClient->disconnect(SIGNAL(timeoutChanged(int)),0,0);
            const QModbusDataUnit unit = reply->result(); //define QModbusDataUnit for recv data
            uint16_t nStartAddr = unit.startAddress();
            int count =  unit.valueCount();
            if (unit.registerType() == QModbusDataUnit::RegisterType::HoldingRegisters){
                for(int i = 0;i < count; i+=1)
                {
                    int value = unit.value(i);
                    m_readValueHoldingRegistersHash[nStartAddr + i] = value;
                }
            }
            replyList.removeOne(reply);
            reply->deleteLater();
        }
        else
        {
            replyList.removeOne(reply);
            reply->deleteLater();
        }
    }
}

void BaseThread::slotReadRegisterData(int nStartAddress, int nNum, int type)
{
    //establsih QModbusDataUnit to read
    QModbusDataUnit readUnit((QModbusDataUnit::RegisterType)type, nStartAddress, nNum);
    //Send a read request, return reply . SERVER_ID represents slave ID
    auto* reply = m_pClient->sendReadRequest(readUnit, SERVER_ID);
    if (reply)   //if not empty
    {
        replyList.append(reply);
        if (!reply->isFinished())//finished slot here
        {
            QObject::connect(reply, &QModbusReply::finished, this, [this,reply]()
            {
                if (reply->error() == QModbusDevice::NoError)// no error
                {
                    //Adding this may prevent overflow
                    m_pClient->disconnect(SIGNAL(timeoutChanged(int)),0,0);
                    const QModbusDataUnit unit = reply->result(); //result contains a lot such follows:
                    uint16_t nStartAddr = unit.startAddress();
                    int count =  unit.valueCount();
                    //this qDebug for easy viewing
                    //qDebug() << " ===============>" + QString::number(count);
                    // Insert return value into hash table [addr1:value1;addr2:value2...]
                    if (unit.registerType() == QModbusDataUnit::RegisterType::HoldingRegisters){
                        for(int i = 0;i < count; i+=1)
                        {
                            int value = unit.value(i);
                            m_readValueHoldingRegistersHash[nStartAddr + i] = value;
                            //if(SERVER_ID == 3){
                            //qDebug() << QString::number(value) + "====thread interior=========";}
                        }
                        if (SERVER_ID > ZZJConst::StartDC && SERVER_ID <=(ZZJConst::StartDC + ZZJConst::NumDC)){
                            ZZJ_Infos = ZZJConst::Parse_ZZJ_HoldingRegisters(m_readValueHoldingRegistersHash,ZZJConst::ZZJ_deviceReadInformation);
                            switch (SERVER_ID) {
                            case ZZJConst::StartDC + 1:
                            {
                                if(!ZZJConst::ComparasionOfSWMCStruct(ZZJ_Infos,LastZZJ1Infos)){
                                    LastZZJ1Infos = ZZJ_Infos;
                                    emit signalForMainWindowShowZZJ1(m_bConnected);
                                }
                            } break;
                            case ZZJConst::StartDC + 2:
                            {
                                if(!ZZJConst::ComparasionOfSWMCStruct(ZZJ_Infos,LastZZJ2Infos)){
                                    LastZZJ2Infos = ZZJ_Infos;
                                    emit signalForMainWindowShowZZJ2(m_bConnected);
                                }
                            }break;
                            default: break;
                            }

                        }
                        if (SERVER_ID > XHDConst::StartLed && SERVER_ID <=(XHDConst::StartLed + XHDConst::NumLed)){
                            XHD_Infos = XHDConst::Parse_XHD_HoldingRegisters(m_readValueHoldingRegistersHash,XHDConst::XHD_deviceReadInformation);
                            switch (SERVER_ID) {
                            case XHDConst::StartLed + 1:
                            {
                                if(!XHDConst::ComparasionOfSILStruct(XHD_Infos,LastXHD1Infos)){
                                    LastXHD1Infos = XHD_Infos;
                                    emit signalForMainWindowShowXHD1(m_bConnected);
                                }
                            } break;
                            case XHDConst::StartLed + 2:
                            {
                                if(!XHDConst::ComparasionOfSILStruct(XHD_Infos,LastXHD2Infos)){
                                    LastXHD2Infos = XHD_Infos;
                                    emit signalForMainWindowShowXHD2(m_bConnected);
                                }
                            }break;
                            default: break;
                            }
                        }
                    }
                    //mind leak!
                    replyList.removeOne(reply);
                    reply->deleteLater();
                }
                else
                {
                    replyList.removeOne(reply);
                    reply->deleteLater();
                }
            });
        }
        else
        {
            replyList.removeOne(reply);
            reply->deleteLater();
        }
    }
}

//uValue is a reference , external interface
bool BaseThread::readRegister16(uint16_t uAddr, int &uValue)
{
    if (!m_bConnected)
        return false;
    //read length
    const int nReadLength = 1;

    // addr not found , inserted into the address hash list
    auto itFind = m_readValueHoldingRegistersHash.find(uAddr);//iterator
    if (itFind == m_readValueHoldingRegistersHash.end())
    {
        m_readAddrHoldingRegistersHash[uAddr] = nReadLength;
        return false;
    }
    else
    {
        // return value
        uValue = m_readValueHoldingRegistersHash[uAddr];
    }

    return true;
}

// wirte slot
void BaseThread::slotWriteRegisterData(quint16 DeviceNO,quint16 addr, int value,int  itype)
{
    if (!m_pClient){return;};
    if (SERVER_ID == DeviceNO) {

        const int nLength = 1;
        //Build write dataunit
        QModbusDataUnit writeUnit = QModbusDataUnit((QModbusDataUnit::RegisterType)itype, addr, nLength);
        writeUnit.setValue(addr,value);//0 refer to addr/index
        if (auto *resp = m_pClient->sendWriteRequest(writeUnit, SERVER_ID)) //SERVER_ID->deviceID(slave ID)
        {
            respList.append(resp);//prevent leak
            if (!resp->isFinished())
            {
                QObject::connect(resp, &QModbusReply::finished, this, [=]()
                {
                    if (resp->error() == QModbusDevice::NoError)
                    {
                        m_pClient->disconnect(SIGNAL(timeoutChanged(int)),0,0);
                        const QModbusDataUnit unit = resp->result(); //define QModbusDataUnit for recv data
                        uint16_t nStartAddr = unit.startAddress();
                        int count =  unit.valueCount();
                        if (unit.registerType() == QModbusDataUnit::RegisterType::HoldingRegisters){
                            for(int i = 0;i < count; i+=1)
                            {
                                int value = unit.value(i);
                                m_writeAddrHoldingRegistersHash[nStartAddr + i] = value;
                            }
                            replyList.removeOne(resp);
                            resp->deleteLater();
                            return;
                        }
                    }
                    else{
                        replyList.removeOne(resp);
                        resp->deleteLater();
                        slotWriteRegisterData(DeviceNO, addr, value, itype);
                    }
                });
            }
            else
            {// broadcast replies return immediately
                replyList.removeOne(resp);
                resp->deleteLater();
            }
        } else
        {
            qDebug() <<tr("Write error: ") + m_pClient->errorString();
        }

    }
}

void BaseThread::sleep(unsigned int msec)
{
    QTime dieTime = QTime::currentTime().addMSecs(msec);

    while (QTime::currentTime() < dieTime)
    {
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
    }

    return ;
}

/*

对遇到困难的一些办法:取消重试次数,因为重试次数只有在无响应的情况下才重试,如果有错误响应还是有问题。因此自己完善重试的步骤。

将写操作做互斥锁保护,保证主线程那里的信号灯emit按顺序执行。

或者在主线程那里直接调用本线程中的写操作函数,保证顺序执行。

适当加大重试次数。并用一个flag:如 success 标志写数据是否成功。



*/

//TODO
/*
 * 1,For the ZZJ, it is necessary to list a dictionary, corresponding to the state, Whether it is in place
 * etc. and display them on the system settings. have to establish a System settings table,
 * closed avoid memory leak, see Qt lessons for details.
 *
 * 2,The logical part of the XHD related to ZZJ,XHD need a table
 *
 * 3,now we redundant code XHD dont use inherit
 */


有时候会遇到内存泄漏,添加这行代码:

m_pClient->disconnect(SIGNAL(timeoutChanged(int)),0,0);

表示断开QtModbusTCPClient内部的定时器,能够解决Qt5.15.2的内存泄漏问题。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值