欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 旅游 > 【Qt】modbus客户端笔记

【Qt】modbus客户端笔记

2025/4/3 6:52:11 来源:https://blog.csdn.net/weixin_47564581/article/details/146566071  浏览:    关键词:【Qt】modbus客户端笔记

Qt 中基于 Modbus 协议的通用客户端学习笔记

一、概述

本客户端利用 Qt 的 QModbusTcpClient 实现与 Modbus 服务器的通信,具备连接、读写寄存器、心跳检测、自动重连等功能,旨在提供一个可靠且易用的 Modbus 客户端框架,方便在不同项目中集成使用。

二、核心功能实现

(一)初始化

  1. 在构造函数中:
    • 首先初始化基类 QObject,确保对象层次结构正确构建。
    • 实例化 m_modbusClient,将自身作为其父对象,以便管理内存。
    • 创建 m_heartbeatTimerm_reconnectTimer,并设置心跳间隔(如 2 秒)和自动重连间隔(如 2 秒)。
    • 连接相关信号与槽:
      • m_heartbeatTimer 超时,触发 checkHeartbeat 槽函数,用于检测心跳。
      • m_reconnectTimer 超时,触发 attemptReconnect 槽函数,尝试重新连接服务器。
      • m_modbusClient 的状态改变时,连接到 handleStateChanged 槽函数,以便及时更新设备连接状态。

(二)连接管理

  1. 获取连接状态
    • getStatus 函数通过查询 m_modbusClient 的当前状态(QModbusDevice::State),将其转换为自定义的 DeviceStatus(如 ConnectedConnectingDisconnected)枚举类型,并返回。这有助于上层代码了解设备的实时连接情况。
  2. 连接设备
    • connectDevice 函数首先从 m_properties 中获取存储的 IP 地址和端口信息,分别设置到 m_modbusClient 的连接参数中(使用 QModbusDevice::NetworkAddressParameterQModbusDevice::NetworkPortParameter),然后调用 m_modbusClientconnectDevice 方法尝试建立连接。根据连接结果,通过 setStatus 函数更新设备状态。
  3. 断开连接
    • disconnectDevice 函数调用 m_modbusClientdisconnectDevice 方法断开与服务器的连接,同时停止心跳定时器和自动重连定时器,并更新设备状态为 Disconnected

(三)读写寄存器

  1. 写寄存器
    • writeRegisters 函数首先检查设备是否已连接(通过 m_modbusClient 的状态判断),若未连接则打印错误信息并返回 false。接着,构建 QModbusDataUnit 对象,设置要写入的寄存器类型、起始地址以及数据值。然后,通过 m_modbusClient 发送写请求,并连接到回复信号,在回复完成后根据错误码判断写入操作是否成功。
  2. 读寄存器
    • readRegisters 函数同样先检查设备连接状态,若未连接则打印错误信息并返回。构建 QModbusDataUnit 对象指定要读取的寄存器类型、起始地址和数量,然后通过 m_modbusClient 发送读请求,并连接到 onAllRegistersReadReady 槽函数,等待读取结果。

(四)心跳检测与自动重连

  1. 心跳检测
    • checkHeartbeat 函数在心跳定时器超时时触发。它首先检查设备是否仍处于连接状态,若不是则直接处理连接断开情况(调用 handleStateChanged 函数将状态设为 Unconnected)。若连接正常,则向服务器发送一个简单的读寄存器请求(通常读取保持寄存器的特定位置,如 0 地址,长度为 1),通过回复的错误码判断连接是否依然存活,若有错误则同样处理连接断开。
  2. 自动重连
    • attemptReconnect 函数在自动重连定时器超时时执行。它检查设备是否未连接,若是,则尝试重新连接(调用 connectDevice 函数),并记录重连次数。若重连次数达到预设上限(如 MAX_RECONNECT_ATTEMPTS),则停止自动重连定时器,防止无意义的重复尝试。

三、信号与槽

  1. m_modbusClient 的状态改变时,handleStateChanged 槽函数被触发,根据不同的状态(UnconnectedStateConnectedStateConnectingState)更新设备的自定义连接状态,并触发相应操作,如启动或停止定时器。
  2. 在读寄存器操作完成后,onAllRegistersReadReady 槽函数被调用,它从 QModbusReply 中获取读取结果数据单元 QModbusDataUnit,并通过信号 allRegistersReadCompleted 将结果发射出去,供外部代码进一步处理。

欢迎大家提供优化建议。

#ifndef MODBUSDEVICE_H
#define MODBUSDEVICE_H#include <QObject>
#include <QModbusTcpClient>
#include <QTimer>
#include <QMap>
#include <QVariant>// 定义设备状态的枚举类型,用于表示 Modbus 设备当前的连接状态
enum class DeviceStatus {Disconnected,  // 设备处于断开连接状态Connecting,    // 设备正在尝试连接Connected      // 设备已成功连接
};// ModbusDevice 类继承自 QObject,用于管理 Modbus TCP 客户端的连接和通信
class ModbusDevice : public QObject
{Q_OBJECT
public:// 构造函数,接受一个包含设备属性的 QMap 和可选的父对象指针// properties: 包含设备属性的 QMap,如设备 ID、IP 地址、端口等// parent: 父对象指针,默认为 nullptrexplicit ModbusDevice(const QMap<QString, QVariant>& properties, QObject *parent = nullptr);// 析构函数,负责释放动态分配的资源~ModbusDevice();// 获取设备当前的连接状态DeviceStatus getStatus();// 获取设备的重连尝试次数int deviceReconnectAttempts() const;// 设置设备的属性值// key: 属性的键,如 "deviceID", "ipAddress" 等// value: 属性的值void setProperty(const QString& key, const QVariant& value);// 获取设备的属性值// key: 属性的键// 返回值: 属性的值,如果键不存在则返回默认值QVariant getProperty(const QString& key) const;// 向 Modbus 设备写入寄存器数据// registerType: 寄存器类型,如 QModbusDataUnit::HoldingRegisters// registerAddress: 寄存器的起始地址// values: 要写入的寄存器值的向量// 返回值: 写入操作是否成功bool writeRegisters(QModbusDataUnit::RegisterType registerType, quint16 registerAddress, const QVector<quint16>& values);// 从 Modbus 设备读取寄存器数据// registerType: 寄存器类型// registerAddress: 寄存器的起始地址// count: 要读取的寄存器数量void readRegisters(QModbusDataUnit::RegisterType registerType, quint16 registerAddress, quint16 count);// 尝试连接到 Modbus 设备// 返回值: 连接是否成功bool connectDevice();// 断开与 Modbus 设备的连接void disconnectDevice();// 获取设备的 IDint deviceId() const;// 获取设备的 IP 地址QString deviceAddress() const;// 获取设备的名称QString deviceName() const;// 获取设备的端口号quint16 devicePort() const;signals:// 当设备连接状态发生变化时发出的信号,携带设备 ID// deviceId: 设备的 IDvoid signal_DeviceConnectState(int deviceId);// 当所有寄存器读取完成时发出的信号,携带读取到的数据单元// unit: 包含读取结果的 QModbusDataUnitvoid allRegistersReadCompleted(const QModbusDataUnit& unit);private slots:// 心跳检测槽函数,定期检查设备的连接状态void checkHeartbeat();// 尝试重新连接设备的槽函数,在连接断开时调用void attemptReconnect();// 处理 Modbus 客户端状态变化的槽函数// state: 新的 Modbus 设备状态void handleStateChanged(QModbusDevice::State state);// 处理寄存器读取完成的槽函数void onAllRegistersReadReady();// 设置设备的连接状态,并执行相应的操作// newStatus: 新的设备连接状态void setStatus(DeviceStatus newStatus);private:// 存储设备属性的 QMap,如设备 ID、IP 地址、端口等QMap<QString, QVariant> m_properties;// Modbus TCP 客户端指针,用于与 Modbus 设备进行通信QModbusTcpClient* m_modbusClient;// 心跳检测定时器,定期触发心跳检测QTimer* m_heartbeatTimer;// 重连定时器,在连接断开时定期尝试重新连接QTimer* m_reconnectTimer;// 设备当前的连接状态DeviceStatus m_status;// 记录设备的重连尝试次数int m_reconnectAttempts = 0;// 最大重连尝试次数,超过该次数将停止重连static const int MAX_RECONNECT_ATTEMPTS = 5;
};#endif // MODBUSDEVICE_H    
#include "modbusdevice.h"
#include <QDebug>// 构造函数实现
ModbusDevice::ModbusDevice(const QMap<QString, QVariant>& properties, QObject *parent): QObject(parent),m_properties(properties),m_modbusClient(new QModbusTcpClient(this)),m_status(DeviceStatus::Disconnected)
{// 创建心跳检测定时器,并设置定时器的父对象为当前对象m_heartbeatTimer = new QTimer(this);// 设置心跳检测定时器的间隔为 2000 毫秒(即 2 秒)m_heartbeatTimer->setInterval(2000);// 连接心跳检测定时器的超时信号到 checkHeartbeat 槽函数connect(m_heartbeatTimer, &QTimer::timeout, this, &ModbusDevice::checkHeartbeat);// 创建重连定时器,并设置定时器的父对象为当前对象m_reconnectTimer = new QTimer(this);// 设置重连定时器的间隔为 2000 毫秒(即 2 秒)m_reconnectTimer->setInterval(2000);// 连接重连定时器的超时信号到 attemptReconnect 槽函数connect(m_reconnectTimer, &QTimer::timeout, this, &ModbusDevice::attemptReconnect);// 连接 Modbus 客户端的状态变化信号到 handleStateChanged 槽函数connect(m_modbusClient, &QModbusTcpClient::stateChanged, this, &ModbusDevice::handleStateChanged);
}// 析构函数实现
ModbusDevice::~ModbusDevice()
{// 释放 Modbus 客户端对象的内存delete m_modbusClient;
}// 获取设备状态的函数实现
DeviceStatus ModbusDevice::getStatus()
{// 获取 Modbus 客户端的当前状态QModbusDevice::State state = m_modbusClient->state();if (state == QModbusDevice::ConnectedState) {// 如果 Modbus 客户端已连接,设置设备状态为 ConnectedsetStatus(DeviceStatus::Connected);} else if (state == QModbusDevice::ConnectingState) {// 如果 Modbus 客户端正在连接,设置设备状态为 ConnectingsetStatus(DeviceStatus::Connecting);} else if (state == QModbusDevice::UnconnectedState) {// 如果 Modbus 客户端未连接,设置设备状态为 DisconnectedsetStatus(DeviceStatus::Disconnected);}// 返回设备的当前状态return m_status;
}// 获取设备重连尝试次数的函数实现
int ModbusDevice::deviceReconnectAttempts() const
{// 返回设备的重连尝试次数return m_reconnectAttempts;
}// 设置设备属性的函数实现
void ModbusDevice::setProperty(const QString& key, const QVariant& value)
{// 将属性键值对添加到 m_properties 中m_properties[key] = value;
}// 获取设备属性的函数实现
QVariant ModbusDevice::getProperty(const QString& key) const
{// 从 m_properties 中获取指定键的属性值return m_properties.value(key);
}// 写入寄存器的函数实现
bool ModbusDevice::writeRegisters(QModbusDataUnit::RegisterType registerType, quint16 registerAddress, const QVector<quint16>& values)
{if (m_modbusClient->state() != QModbusDevice::ConnectedState) {// 如果 Modbus 客户端未连接,输出错误信息并返回 falseqDebug() << "Device is not connected. Cannot write registers.";return false;}// 创建一个 Modbus 数据单元,用于写入寄存器QModbusDataUnit writeUnit(registerType, registerAddress, values.size());for (int i = 0; i < values.size(); ++i) {// 将值写入 Modbus 数据单元writeUnit.setValue(i, values[i]);}// 发送写入请求,并获取响应对象QModbusReply* reply = m_modbusClient->sendWriteRequest(writeUnit, m_properties["deviceID"].toInt());if (reply) {// 连接响应对象的完成信号到一个 lambda 函数connect(reply, &QModbusReply::finished, [reply]() {if (reply->error() != QModbusDevice::NoError) {// 如果写入过程中出现错误,输出错误信息qDebug() << "Modbus write registers error:" << reply->errorString();} else {// 如果写入成功,输出成功信息qDebug() << "Modbus write registers success:";}// 释放响应对象的内存reply->deleteLater();});// 返回写入操作成功return true;} else {// 如果发送写入请求失败,输出错误信息并返回 falseqDebug() << "Failed to send write registers request";return false;}
}// 读取寄存器的函数实现
void ModbusDevice::readRegisters(QModbusDataUnit::RegisterType registerType, quint16 registerAddress, quint16 count)
{if (m_modbusClient->state() != QModbusDevice::ConnectedState) {// 如果 Modbus 客户端未连接,输出错误信息并返回qDebug() << "Device is not connected. Cannot read registers.";return;}// 创建一个 Modbus 数据单元,用于读取寄存器QModbusDataUnit readUnit(registerType, registerAddress, count);// 发送读取请求,并获取响应对象QModbusReply* reply = m_modbusClient->sendReadRequest(readUnit, m_properties["deviceID"].toInt());if (reply) {// 连接响应对象的完成信号到 onAllRegistersReadReady 槽函数connect(reply, &QModbusReply::finished, this, &ModbusDevice::onAllRegistersReadReady);} else {// 如果发送读取请求失败,输出错误信息qDebug() << "Failed to send read registers request";}
}// 设置设备状态的函数实现
void ModbusDevice::setStatus(DeviceStatus newStatus)
{if (m_status != newStatus) {// 如果新状态与当前状态不同m_status = newStatus;// 发出设备连接状态变化的信号,携带设备 IDemit signal_DeviceConnectState(m_properties["deviceID"].toInt());if (m_status == DeviceStatus::Connected) {// 如果设备已连接,启动心跳检测定时器m_heartbeatTimer->start();// 重置重连尝试次数m_reconnectAttempts = 0;} else if (m_status == DeviceStatus::Disconnected) {// 如果设备断开连接,停止心跳检测定时器m_heartbeatTimer->stop();if (m_reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {// 如果重连尝试次数未达到最大次数,启动重连定时器m_reconnectTimer->start();}}}
}// 处理 Modbus 客户端状态变化的槽函数实现
void ModbusDevice::handleStateChanged(QModbusDevice::State state)
{switch (state) {case QModbusDevice::UnconnectedState:// 如果 Modbus 客户端未连接,设置设备状态为 DisconnectedsetStatus(DeviceStatus::Disconnected);break;case QModbusDevice::ConnectedState:// 如果 Modbus 客户端已连接,设置设备状态为 ConnectedsetStatus(DeviceStatus::Connected);break;case QModbusDevice::ConnectingState:// 如果 Modbus 客户端正在连接,设置设备状态为 ConnectingsetStatus(DeviceStatus::Connecting);break;default:break;}
}// 心跳检测槽函数实现
void ModbusDevice::checkHeartbeat()
{if (m_modbusClient->state() != QModbusDevice::ConnectedState) {// 如果 Modbus 客户端未连接,处理设备断开连接的情况handleStateChanged(QModbusDevice::UnconnectedState);return;}// 创建一个 Modbus 数据单元,用于读取心跳寄存器QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, 0, 1);// 发送读取请求,并获取响应对象QModbusReply* reply = m_modbusClient->sendReadRequest(readUnit, m_properties["deviceID"].toInt());if (reply) {// 连接响应对象的完成信号到一个 lambda 函数connect(reply, &QModbusReply::finished, this, [this, reply]() {if (reply->error() != QModbusDevice::NoError) {// 如果读取过程中出现错误,处理设备断开连接的情况handleStateChanged(QModbusDevice::UnconnectedState);}// 释放响应对象的内存reply->deleteLater();});} else {// 如果发送读取请求失败,处理设备断开连接的情况handleStateChanged(QModbusDevice::UnconnectedState);}
}// 尝试重新连接设备的槽函数实现
void ModbusDevice::attemptReconnect()
{if (m_modbusClient->state() != QModbusDevice::ConnectedState) {// 如果 Modbus 客户端未连接,输出重连尝试信息qDebug() << "尝试重连设备 ID:" << m_properties["deviceID"].toInt();if (m_reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {// 如果重连尝试次数未达到最大次数,尝试重新连接设备connectDevice();// 增加重连尝试次数m_reconnectAttempts++;} else {// 如果重连尝试次数达到最大次数,输出重连失败信息并停止重连定时器qDebug() << "设备 ID:" << m_properties["deviceID"].toInt() << " 重连次数达到上限,停止重连";m_reconnectTimer->stop();}}
}// 处理寄存器读取完成的槽函数实现
void ModbusDevice::onAllRegistersReadReady()
{// 获取发送信号的对象,并将其转换为 QModbusReply 指针QModbusReply* reply = qobject_cast<QModbusReply*>(sender());if (!reply) {// 如果转换失败,输出错误信息并返回qDebug() << "Invalid reply object";return;}if (reply->error() != QModbusDevice::NoError) {// 如果读取过程中出现错误,输出错误信息qDebug() << "Modbus read all registers error:" << reply->errorString();} else {// 如果读取成功,获取读取结果并发出所有寄存器读取完成的信号QModbusDataUnit unit = reply->result();emit allRegistersReadCompleted(unit);}// 释放响应对象的内存reply->deleteLater();
}// 连接设备的函数实现
bool ModbusDevice::connectDevice()
{// 从设备属性中获取 IP 地址QString ipAddress = m_properties["ipAddress"].toString();// 从设备属性中获取端口号quint16 port = m_properties["port"].toUInt();// 设置 Modbus 客户端的网络地址参数m_modbusClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, ipAddress);// 设置 Modbus 客户端的网络端口参数m_modbusClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, port);// 尝试连接 Modbus 设备,并获取连接结果bool result = m_modbusClient->connectDevice();if (m_modbusClient->state() == QModbusDevice::ConnectedState) {// 如果 Modbus 客户端已连接,设置设备状态为 ConnectedsetStatus(DeviceStatus::Connected);} else {// 如果 Modbus 客户端未连接,设置设备状态为 DisconnectedsetStatus(DeviceStatus::Disconnected);}// 返回连接结果return result;
}
// 断开设备连接的函数实现
void ModbusDevice::disconnectDevice()
{// 断开 Modbus 客户端与设备的连接m_modbusClient->disconnectDevice();// 设置设备状态为 DisconnectedsetStatus(DeviceStatus::Disconnected);// 停止心跳检测定时器m_heartbeatTimer->stop();// 停止重连定时器m_reconnectTimer->stop();
}
// 获取设备 ID 的函数实现
int ModbusDevice::deviceId() const
{// 从设备属性中获取设备 IDreturn m_properties["deviceID"].toInt();
}
// 获取设备 IP 地址的函数实现
QString ModbusDevice::deviceAddress() const
{// 从设备属性中获取设备 IP 地址return m_properties["ipAddress"].toString();
}
// 获取设备名称的函数实现
QString ModbusDevice::deviceName() const
{// 从设备属性中获取设备名称return m_properties["deviceName"].toString();
}
// 获取设备端口号的函数实现
quint16 ModbusDevice::devicePort() const
{// 从设备属性中获取设备端口号return m_properties["port"].toInt();
}    
#include "modbusdevice.h"
#include <QCoreApplication>
#include <QDebug>int main(int argc, char *argv[])
{// 创建一个 QCoreApplication 对象,用于管理应用程序的生命周期QCoreApplication a(argc, argv);// 创建一个 QMap 对象,用于存储 Modbus 设备的属性QMap<QString, QVariant> properties;// 设置设备的 ID 为 1properties["deviceID"] = 1;// 设置设备的 IP 地址为本地回环地址properties["ipAddress"] = "127.0.0.1";// 设置设备的端口号为 502properties["port"] = 502;// 设置设备的名称为 "TestDevice"properties["deviceName"] = "TestDevice";// 创建一个 ModbusDevice 对象,传入设备属性ModbusDevice device(properties);// 尝试连接 Modbus 设备if (device.connectDevice()) {// 如果连接成功,输出连接成功信息qDebug() << "设备连接成功";// 创建一个 QVector 对象,存储要写入寄存器的值QVector<quint16> values = {1, 2, 3};// 调用 writeRegisters 函数,向设备的保持寄存器写入数据device.writeRegisters(QModbusDataUnit::HoldingRegisters, 0, values);// 调用 readRegisters 函数,从设备的保持寄存器读取数据device.readRegisters(QModbusDataUnit::HoldingRegisters, 0, 3);// 连接 allRegistersReadCompleted 信号到一个 lambda 函数// 当寄存器读取完成时,输出读取到的值QObject::connect(&device, &ModbusDevice::allRegistersReadCompleted, [](const QModbusDataUnit& unit) {qDebug() << "读取寄存器完成,值为:" << unit.values();});} else {// 如果连接失败,输出连接失败信息qDebug() << "设备连接失败";}// 进入应用程序的事件循环,等待事件发生return a.exec();
}    

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词