QT-ModbusTCP之QModbusTcpClient的使用

QT-ModbusTCP之QModbusTcpClient的使用

  • 前言
  • 一、效果演示
  • 二、通讯类
    • 1.头文件
    • 2.源文件
  • 程序下载


前言

使用QModbusTcpClient来做ModbusTCP来通讯,原滋原味。使用方便并且稳定,虽然网上有很多开源的,当时有时候应用起来,出了问题,真的头大。这里介绍QModbusTcpClient的使用,已经完全满足了应用程序的基本开发,并且在原有的基础上了,做了后台线程的处理,保证了读取寄存器相对同步获取,以及服务端掉线能自动重新连接等机制。


一、效果演示

QT-ModbusTCP之QModbusTcpClient的使用_第1张图片

二、通讯类

1.头文件

#pragma once

#include 
#include 
#include 
#include 
#include 

class CModbusClient : public QThread
{
	Q_OBJECT

public:
	CModbusClient(QObject *parent = 0);
	~CModbusClient();

signals:
	void signalConnectDevice();
	void signalReadRegisterData(int, int, int);
	void signalWriteRegisterData(int, uint16_t, int);

private slots:
	void slotErrorOccurred(QModbusDevice::Error error);
	void slotStateChanged(QModbusDevice::State state);
	void slotConnectDevice();
	void slotReadyRead(); 
	void slotReadRegisterData(int nStartAddress, int nNum, int type = QModbusDataUnit::HoldingRegisters);
	void slotWriteRegisterData(int nStartAddress, uint16_t uValue, int type = QModbusDataUnit::HoldingRegisters);

public:
	bool connect(QString strIp, int nPort);
	bool isConnected();
	void release(); 

	bool getBit(uint16_t uAddr, uint16_t uBit);
	bool readRegister16(uint16_t uAddr, uint16_t &uValue);
	bool readRegister32(uint32_t uAddr, uint32_t &uValue);
	bool readCoil(uint16_t uAddr, uint16_t &uValue);

	bool setBit(uint16_t uAddr, uint16_t uBit, bool bState);
	bool writeRegister16(uint16_t uAddr, uint16_t uValue);
	bool writeRegister32(uint32_t uAddr, uint32_t uValue);
	bool writeCoil(uint16_t uAddr, uint16_t uValue);

protected:
	void run() override;

private:
	QModbusTcpClient *m_pClient;
	// 连接状态
	bool m_bConnected;
	
	// 程序退出,用来退出线程
	bool m_bAppClose;

	// 寄存器
	QHash<uint16_t, uint16_t> m_readValueHoldingRegistersHash;
	QHash<uint16_t, uint16_t> m_readAddrHoldingRegistersHash;

	// 线圈
	QHash<uint16_t, uint16_t> m_readValueCoilsHash;
	QHash<uint16_t, uint16_t> m_readAddrCoilsHash;

};


2.源文件

#include "CModbusClient.h"
#include 
#include 
#include 
#include 

#define  SERVER_ID         100

// 自定义的定时器
struct sModbusTimer
{
	QTime time;
	uint32_t interval;

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

	bool isTimeOut()
	{
		return time.elapsed() > interval;
	};
};


CModbusClient::CModbusClient(QObject *parent)
	: QThread(parent)
	,m_bConnected(false)
	,m_bAppClose(false)
	,m_pClient(new QModbusTcpClient())
{
	// 信号槽关联
	QObject::connect(this, &CModbusClient::signalConnectDevice, this, &CModbusClient::slotConnectDevice);
	QObject::connect(this, &CModbusClient::signalReadRegisterData, this, &CModbusClient::slotReadRegisterData);
	QObject::connect(this, &CModbusClient::signalWriteRegisterData, this, &CModbusClient::slotWriteRegisterData);
}

CModbusClient::~CModbusClient()
{
// 退出程序
	m_bAppClose = true;

	QThread::msleep(100);

	if (m_bConnected)
		m_pClient->disconnect();

}

// 连接
bool CModbusClient::connect(QString strIp, int nPort)
{
	if (m_bConnected)
		return true;

	m_pClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, QVariant::fromValue(nPort));
	m_pClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter,QVariant::fromValue(strIp));
	m_pClient->setTimeout(1000);
	m_pClient->setNumberOfRetries(3);

	m_bConnected = m_pClient->connectDevice();
	if (m_bConnected)
	{
		QObject::connect(m_pClient, &QModbusTcpClient::stateChanged, this, &CModbusClient::slotStateChanged);
		QObject::connect(m_pClient, &QModbusTcpClient::errorOccurred, this, &CModbusClient::slotErrorOccurred);
	}
	else
		qDebug() << m_pClient->errorString() << "\n";

	if (!this->isRunning())
		this->start();

	return m_bConnected;
}

// 是否连接
bool CModbusClient::isConnected()
{
	return m_bConnected;
}

// 资源释放
void  CModbusClient::release()
{
	m_bAppClose = true;
	// 延时确保后台线程全部退出
	QThread::msleep(200);
	m_pClient->disconnect();
}

// 错误触发
void CModbusClient::slotErrorOccurred(QModbusDevice::Error error)
{
	qDebug() << "ModbusClient Error Num:" << error << "\n";
	qDebug() << "ModbusClient Error Info:" << m_pClient->errorString() << "\n";
}

// 连接状态变化
void CModbusClient::slotStateChanged(QModbusDevice::State state)
{
	switch (state)
	{
	case QModbusDevice::UnconnectedState:
	{
		m_bConnected = false;
	}break;
	case QModbusDevice::ConnectedState:
	{
		m_bConnected = true;
	}break;
	case QModbusDevice::ClosingState:
	{
		m_bConnected = false;
	}break;
	default:
		break;
	}

	qDebug() << "ModbusClient StateChanged:" << state << "\n";
}

// 连接设备
void CModbusClient::slotConnectDevice()
{
	if (!m_bConnected)
		m_pClient->connectDevice();
}

// 返回读取的值
void CModbusClient::slotReadyRead()
{
	auto reply = qobject_cast<QModbusReply*>(sender());
	if (reply)
	{
		if (reply->error() == QModbusDevice::NoError)
		{
			const QModbusDataUnit unit = reply->result();
			uint16_t nStartAddr = unit.startAddress();

			auto valueList = unit.values();

			// 将读取的返回值直接插入到值哈希表里面
			if (unit.registerType() == QModbusDataUnit::RegisterType::HoldingRegisters)
			{
				for (int i = 0; i < valueList.size(); i++)
				{
					m_readValueHoldingRegistersHash[nStartAddr + i] = valueList[i];
					qDebug() << QString("Read HoldingRegisters Value[%1]:%2").arg(i + nStartAddr).arg(valueList[i]) << "\n";
				}
			}
			else if (unit.registerType() == QModbusDataUnit::RegisterType::Coils)
			{
				for (int i = 0; i < valueList.size(); i++)
				{
					m_readValueHoldingRegistersHash[nStartAddr + i] = valueList[i];
					qDebug() << QString("Read Coils Value[%1]:%2").arg(i + nStartAddr).arg(valueList[i]) << "\n";
				}
			}

		}
		else
		{
			qDebug() << "ReadyRead Error:" << reply->errorString() << "\n";
		}

		reply->deleteLater();
	}

}

// 后台线程
void CModbusClient::run()
{
	sModbusTimer timeout;
	int nStep = 0;
	while (true)
	{
		QThread::msleep(30);

		// 线程退出
		if (m_bAppClose)
			return;


		switch (nStep)
		{
		case 0:
		{
			if (!m_bConnected)
			{
				timeout.start(3 * 1000);
				nStep = 1;
			}
			else
				nStep = 2;

		}break;

		case 1:
		{
			if (timeout.isTimeOut() && !m_bConnected)
			{
				// 通过发送信号,再次连接设备,不同线程需要用信号发送
				emit signalConnectDevice();
				nStep = 0;
			}
			else if (m_bConnected)
				nStep = 0;

		}break;

		case 2:
		{
			// 后台不断刷新读取
			if (true)
			{
				static int nIndex = 0;
				auto keys = m_readAddrHoldingRegistersHash.keys();

				if (nIndex < keys.size())
				{
					uint16_t uAddr = keys[nIndex];

					// 发送信号,读寄存器请求,不同线程需要用信号发送
					emit signalReadRegisterData(uAddr, m_readAddrHoldingRegistersHash[uAddr],(int)QModbusDataUnit::RegisterType::HoldingRegisters);

					nIndex++;
				}
				else
					nIndex = 0;
			}

			if (true)
			{
				static int nIndex = 0;
				auto keys = m_readAddrCoilsHash.keys();

				if (nIndex < keys.size())
				{
					uint16_t uAddr = keys[nIndex];

					// 发送信号,读寄存器请求,不同线程需要用信号发送
					emit signalReadRegisterData(uAddr, m_readAddrCoilsHash[uAddr], (int)QModbusDataUnit::RegisterType::Coils);

					nIndex++;
				}
				else
					nIndex = 0;
			}


			nStep = 0;
		}break;

		default:
			break;
		}
	}

}

// 发送寄存器读取请求
void CModbusClient::slotReadRegisterData(int nStartAddress, int nNum, int  type)
{
	QModbusDataUnit readUnit((QModbusDataUnit::RegisterType)type, nStartAddress, nNum);
	if (auto* reply = m_pClient->sendReadRequest(readUnit, SERVER_ID))
	{
		if (!reply->isFinished())
			QObject::connect(reply, &QModbusReply::finished, this, &CModbusClient::slotReadyRead);
		else
			delete reply;
	}
	else
		qDebug() << m_pClient->errorString();
}

// 发送寄存器写入请求
void CModbusClient::slotWriteRegisterData(int nStartAddress, uint16_t uValue, int  type)
{
	if (!m_bConnected)
		return;

	const int nLength = 1;
	QModbusDataUnit writeUnit((QModbusDataUnit::RegisterType)type, nStartAddress, nLength);
	writeUnit.setValue(0, uValue);

	auto *reply = m_pClient->sendWriteRequest(writeUnit, SERVER_ID);
	if (reply != nullptr)
	{
		if (!reply->isFinished())
		{
			QObject::connect(reply, &QModbusReply::finished, this, [this, reply]()
			{
				if (reply->error() != QModbusDevice::NoError)
					qDebug() << "write response error:" << reply->errorString();
			});
		}

		reply->deleteLater();
	
	}
	else
		qDebug() << "write request error:" << m_pClient->errorString();

}

// 写入16位值
bool CModbusClient::readRegister16(uint16_t uAddr, uint16_t &uValue)
{
	if (!m_bConnected)
		return false;
	
	// 一次设置读取长度为100
	const int nReadLength = 100;

	// 如果值读取哈希链表没有找到,需要插入到地址哈希链表,让它在后台读取
	auto itFind = m_readValueHoldingRegistersHash.find(uAddr);
	if (itFind == m_readValueHoldingRegistersHash.end())
	{
		m_readAddrHoldingRegistersHash[uAddr] = nReadLength;
		return false;
	}
	else
	{
		// 找到值,直接返回
		uValue = m_readValueHoldingRegistersHash[uAddr];
	}

	return true;
}

// 读取32位值
bool CModbusClient::readRegister32(uint32_t uAddr, uint32_t &uValue)
{
	if (!m_bConnected)
		return false;

	bool bRet = false;
	uint16_t uData16[2] = { 0 };
	bRet = readRegister16(uAddr, uData16[0]);
	bRet = readRegister16(uAddr + 1, uData16[1]);

	uValue = uData16[0]  | (uData16[1] << 16);

	return bRet;
}

// 获取位值
bool CModbusClient::getBit(uint16_t uAddr, uint16_t uBit)
{
	if (!m_bConnected)
		return false;

	uint16_t uValue = 0;
	readRegister16(uAddr, uValue);

	return uValue & (1 << (uBit % 16));
}

// 读取线圈值
bool CModbusClient::readCoil(uint16_t uAddr, uint16_t &uValue)
{
	const int nReadLength = 100;
	auto itFind = m_readValueCoilsHash.find(uAddr);
	if (itFind == m_readValueCoilsHash.end())
	{
		m_readAddrCoilsHash[uAddr] = nReadLength;
		return false;
	}
	else
		uValue = m_readValueCoilsHash[uAddr];

	return true;
}

// 设置位值
bool CModbusClient::setBit(uint16_t uAddr, uint16_t uBit, bool bState)
{
	bool bRet = false;

	uint16_t uValue = 0;
	bRet = readRegister16(uAddr, uValue);

	if (bRet)
	{
		if (bState)
			uValue |= (1 << (uBit % 16));	 // 把某位置1
		else
			uValue &= ~(1 << (uBit % 16));	 // 把某位置0

		writeRegister16(uAddr, uValue);
	}

	return bRet;
}

// 写入16位值
bool CModbusClient::writeRegister16(uint16_t uAddr, uint16_t uValue)
{
	bool bRet = true;
	signalWriteRegisterData(uAddr, uValue, (int)QModbusDataUnit::RegisterType::HoldingRegisters);
	return bRet;
}

// 写入32位值
bool CModbusClient::writeRegister32(uint32_t uAddr, uint32_t uValue)
{
	bool bRet = false;

	uint16_t uData16[2] = {0};
	uData16[0] = uValue & 0Xffff;
	uData16[1] = (uValue >> 16) & 0Xffff;

	bRet = writeRegister16(uAddr, uData16[0]);
	bRet = writeRegister16(uAddr+1, uData16[1]);

	return bRet;
}

// 写入线圈值
bool CModbusClient::writeCoil(uint16_t uAddr, uint16_t uValue)
{
	bool bRet = true;
	signalWriteRegisterData(uAddr, uValue, (int)QModbusDataUnit::RegisterType::Coils);
	return bRet;
}


程序下载

https://download.csdn.net/download/u013083044/86784777

你可能感兴趣的:(Qt,qt,开发语言,c++)