在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的内存泄漏问题。