【Qt】modbus客户端笔记

发布于:2025-04-01 ⋅ 阅读:(28) ⋅ 点赞:(0)

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: 父对象指针,默认为 nullptr
    explicit 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();

    // 获取设备的 ID
    int deviceId() const;

    // 获取设备的 IP 地址
    QString deviceAddress() const;

    // 获取设备的名称
    QString deviceName() const;

    // 获取设备的端口号
    quint16 devicePort() const;

signals:
    // 当设备连接状态发生变化时发出的信号,携带设备 ID
    // deviceId: 设备的 ID
    void signal_DeviceConnectState(int deviceId);

    // 当所有寄存器读取完成时发出的信号,携带读取到的数据单元
    // unit: 包含读取结果的 QModbusDataUnit
    void 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 客户端已连接,设置设备状态为 Connected
        setStatus(DeviceStatus::Connected);
    } else if (state == QModbusDevice::ConnectingState) {
        // 如果 Modbus 客户端正在连接,设置设备状态为 Connecting
        setStatus(DeviceStatus::Connecting);
    } else if (state == QModbusDevice::UnconnectedState) {
        // 如果 Modbus 客户端未连接,设置设备状态为 Disconnected
        setStatus(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 客户端未连接,输出错误信息并返回 false
        qDebug() << "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 {
        // 如果发送写入请求失败,输出错误信息并返回 false
        qDebug() << "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;
        // 发出设备连接状态变化的信号,携带设备 ID
        emit 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 客户端未连接,设置设备状态为 Disconnected
        setStatus(DeviceStatus::Disconnected);
        break;
    case QModbusDevice::ConnectedState:
        // 如果 Modbus 客户端已连接,设置设备状态为 Connected
        setStatus(DeviceStatus::Connected);
        break;
    case QModbusDevice::ConnectingState:
        // 如果 Modbus 客户端正在连接,设置设备状态为 Connecting
        setStatus(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 客户端已连接,设置设备状态为 Connected
        setStatus(DeviceStatus::Connected);
    } else {
        // 如果 Modbus 客户端未连接,设置设备状态为 Disconnected
        setStatus(DeviceStatus::Disconnected);
    }
    // 返回连接结果
    return result;
}
// 断开设备连接的函数实现
void ModbusDevice::disconnectDevice()
{
    // 断开 Modbus 客户端与设备的连接
    m_modbusClient->disconnectDevice();
    // 设置设备状态为 Disconnected
    setStatus(DeviceStatus::Disconnected);
    // 停止心跳检测定时器
    m_heartbeatTimer->stop();
    // 停止重连定时器
    m_reconnectTimer->stop();
}
// 获取设备 ID 的函数实现
int ModbusDevice::deviceId() const
{
    // 从设备属性中获取设备 ID
    return 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 为 1
    properties["deviceID"] = 1;
    // 设置设备的 IP 地址为本地回环地址
    properties["ipAddress"] = "127.0.0.1";
    // 设置设备的端口号为 502
    properties["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();
}    

网站公告

今日签到

点亮在社区的每一天
去签到