QT-ModbusTCP之QModbusTcpClient的使用


前言

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


一、效果演示

请添加图片描述

二、通讯类

1.头文件

#pragma once

#include <QObject>
#include <QModbusTcpClient>
#include <QModbusReply>
#include <QThread>
#include <QHash>

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 <QVariant>
#include <QDebug>
#include <QTime>
#include <QVector>

#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

  • 7
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
在 Linux 下使用 Qt 实现 Modbus TCP 通讯读写操作可以参考以下步骤: 1. 首先,在 Qt 项目中添加 Modbus 库,可以通过在 .pro 文件中添加以下代码实现: ``` QT += modbus ``` 2. 在代码中引入 Modbus 头文件: ``` #include <QModbusTcpClient> ``` 3. 创建 Modbus 客户端对象并连接到 Modbus 设备: ``` QModbusTcpClient *client = new QModbusTcpClient(this); client->setConnectionParameter(QModbusDevice::NetworkPortParameter, port); client->setConnectionParameter(QModbusDevice::NetworkAddressParameter, address); client->setTimeout(timeout); client->setNumberOfRetries(retries); if (!client->connectDevice()) { qDebug() << "连接 Modbus 设备失败"; return; } ``` 其中,port、address、timeout、retries 分别为 Modbus 设备的端口号、IP 地址、超时时间和重试次数。 4. 实现读取 Modbus 寄存器的操作: ``` QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, startAddress, count); if (auto *reply = client->sendReadRequest(readUnit, slaveAddress)) { if (!reply->isFinished()) { connect(reply, &QModbusReply::finished, this, [this, reply]() { if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); for (uint i = 0; i < unit.valueCount(); i++) { const QString text = tr("Address: %1, Value: %2") .arg(startAddress + i).arg(unit.value(i)); qDebug() << text; } } else if (reply->error() == QModbusDevice::ProtocolError) { qDebug() << tr("读取 Modbus 寄存器时发生协议错误: %1 (code: 0x%2)") .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16); } else { qDebug() << tr("读取 Modbus 寄存器时发生错误: %1 (code: %2)") .arg(reply->errorString()).arg(reply->error()); } reply->deleteLater(); }); } else { delete reply; qDebug() << tr("读取 Modbus 寄存器时发生错误: 超时"); } } else { qDebug() << tr("读取 Modbus 寄存器时发生错误: %1") .arg(client->errorString()); } ``` 其中,startAddress、count、slaveAddress 分别为寄存器的起始地址、寄存器数量和从机地址。 5. 实现写入 Modbus 寄存器的操作: ``` QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, startAddress, count); for (uint i = 0; i < count; i++) writeUnit.setValue(i, values[i]); if (auto *reply = client->sendWriteRequest(writeUnit, slaveAddress)) { if (!reply->isFinished()) { connect(reply, &QModbusReply::finished, this, [this, reply]() { if (reply->error() == QModbusDevice::NoError) { qDebug() << "写入 Modbus 寄存器成功"; } else if (reply->error() == QModbusDevice::ProtocolError) { qDebug() << tr("写入 Modbus 寄存器时发生协议错误: %1 (code: 0x%2)") .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16); } else { qDebug() << tr("写入 Modbus 寄存器时发生错误: %1 (code: %2)") .arg(reply->errorString()).arg(reply->error()); } reply->deleteLater(); }); } else { delete reply; qDebug() << tr("写入 Modbus 寄存器时发生错误: 超时"); } } else { qDebug() << tr("写入 Modbus 寄存器时发生错误: %1") .arg(client->errorString()); } ``` 其中,values 为要写入的寄存器值数组。 以上就是在 Linux 下使用 Qt 实现 Modbus TCP 通讯读写操作的基本步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的大海贼

联系博主,为您提供有价值的资源

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值