Modbus 自定义命令

该示例显示了如何处理自定义 Modbus 功能代码。

该示例在一个应用程序中同时充当 Modbus 客户端和服务器。它们之间的连接通过 Modbus TCP 建立。它用于发送和接收自定义 Modbus 请求,并根据请求和响应调整其内部状态。

该示例的主要目的是提供一些演示代码,说明如何实现 Modbus 客户端或 Modbus 服务器,处理自定义 Modbus 功能代码。

用户定义的 Modbus 功能代码

Modbus 协议支持的功能代码范围为1 - 127 (0x01 - 0x7F HEX) 。大多数功能代码都有明确定义和公开文档。不过,有两个范围可用于用户自定义功能。它们是65 - 72 (0x41 - 48 HEX)100 - 110 (0x64 - 0x6E HEX) 。用户可以从这些范围中选择功能代码,并以某种自定义方式处理它们。

本应用程序使用函数代码65 (0x41 HEX) 执行CustomRead 命令,使用函数代码66 (0x42 HEX) 执行CustomWrite 命令。在本例中,自定义命令用于简单读写Holding Registers

发送自定义 Modbus 命令

自定义 Modbus 命令使用QModbusClient::sendRawRequest() 方法发送。该方法要求生成一个QModbusRequest 对象,其中包含所需的功能代码和参数列表,这些参数将被编码为QByteArray

    QModbusDataUnit单元      QModbusDataUnit::HoldingRegisters, ui->startAddress->value()        qMin(ui->numberOfRegisters->currentText().toUShort(),
            quint16(10 -  ui->startAddress->value()))// 不要超过 10 个条目};for(qsizetype i= 0,total=unit.valueCount(); i<total;++i) unit.setValue(i,  m_model->m_registers[i+unit.startAddress()]);constquint8byteCount=quint8(unit.valueCount()* 2);    QModbusRequest写入请求        QModbusPdu::FunctionCode(ModbusClient::CustomWrite)quint16(unit.startAddress())quint16(unit.valueCount()),byteCount,unit.values() };

QModbusClient::sendRawRequest() 方法返回一个QModbusReply 对象,可用于像往常一样检查错误:

    if (auto *reply = m_client.sendRawRequest(writeRequest, ui->serverAddress->value())) {
        if (!reply->isFinished()) {
            connect(reply, &QModbusReply::finished, this, [this, reply]() {
                if (reply->error() == QModbusDevice::ProtocolError) {
                    statusBar()->showMessage(tr("Write response error: %1 (Modbus exception: 0x%2)")
                        .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16),
                        5000);
                } else if (reply->error() != QModbusDevice::NoError) {
                    statusBar()->showMessage(tr("Write response error: %1 (code: 0x%2)").
                        arg(reply->errorString()).arg(reply->error(), -1, 16), 5000);
                }

                reply->deleteLater();
                }
            );
        }
    } else {
        statusBar()->showMessage(tr("Write error: ") + m_client.errorString(), 5000);
    }

自定义 Modbus 服务器

自定义服务器源于QModbusTcpServer 类。它覆盖了QModbusServer::processPrivateRequest() 方法。

class ModbusServer : public QModbusTcpServer
{
    Q_OBJECT
    Q_DISABLE_COPY_MOVE(ModbusServer)

public:
    ModbusServer(QObject *parent = nullptr);

private:
    QModbusResponse processPrivateRequest(const QModbusPdu &request) override;
};

当收到带有自定义功能代码的命令时,基本服务器类会调用processPrivateRequest() 方法。

自定义实现通过生成包含所请求寄存器值的QModbusResponse 来处理CustomRead 命令:

    if (ModbusClient::CustomRead == request.functionCode()) {
        quint16 startAddress, count;
        request.decodeData(&startAddress, &count);

        QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, startAddress, count);
        if (!data(&unit)) {
            return QModbusExceptionResponse(request.functionCode(),
                QModbusExceptionResponse::IllegalDataAddress);
        }
        return QModbusResponse(request.functionCode(), startAddress, quint8(count * 2), unit.values());
    }

处理CustomWrite 命令包括从接收到的QModbusPdu 中提取新值,进行实际值更新,并返回包含实际更新的寄存器的QModbusResponse

    if (ModbusClient::CustomWrite == request.functionCode()) {
        quint8 byteCount;
        quint16 startAddress, numberOfRegisters;
        request.decodeData(&startAddress, &numberOfRegisters, &byteCount);

        if (byteCount % 2 != 0) {
            return QModbusExceptionResponse(request.functionCode(),
                QModbusExceptionResponse::IllegalDataValue);
        }

        const QByteArray pduData = request.data().remove(0, WriteHeaderSize);
        QDataStream stream(pduData);

        QList<quint16> values;
        for (int i = 0; i < numberOfRegisters; i++) {
            quint16 tmp;
            stream >> tmp;
            values.append(tmp);
        }

        if (!writeData({QModbusDataUnit::HoldingRegisters, startAddress, values})) {
            return QModbusExceptionResponse(request.functionCode(),
                QModbusExceptionResponse::ServerDeviceFailure);
        }

        return QModbusResponse(request.functionCode(), startAddress, numberOfRegisters);
    }

自定义 Modbus 客户端

自定义客户端源自QModbusTcpClient 类。它覆盖了QModbusClient::processPrivateResponse() 方法。

class ModbusClient : public QModbusTcpClient
{
    Q_OBJECT
    Q_DISABLE_COPY_MOVE(ModbusClient)

public:
    ModbusClient(QObject *parent = nullptr);

    static constexpr QModbusPdu::FunctionCode CustomRead {QModbusPdu::FunctionCode(0x41)};
    static constexpr QModbusPdu::FunctionCode CustomWrite {QModbusPdu::FunctionCode(0x42)};

private:
    bool processPrivateResponse(const QModbusResponse &response, QModbusDataUnit *data) override;
};

基本客户端类调用processPrivateResponse() 方法,用自定义功能代码处理服务器响应。

自定义实现使用CustomReadCustomWrite 功能代码处理响应:

bool ModbusClient::processPrivateResponse(const QModbusResponse &response, QModbusDataUnit *data)
{
    if (!response.isValid())
        return QModbusClient::processPrivateResponse(response, data);

    if (CustomRead == response.functionCode())
        return collateBytes(response, data);

    if (CustomWrite == response.functionCode())
        return collateMultipleValues(response, data);
    return QModbusClient::processPrivateResponse(response, data);
}

CustomRead 响应是通过解码所提供的QModbusPdu 并提取所请求寄存器的值来处理的:

static bool collateBytes(const QModbusPdu &response, QModbusDataUnit *data)
{
    if (response.dataSize() < MinimumReadResponseSize)
        return false;

    quint16 address; quint8 byteCount;
    response.decodeData(&address, &byteCount);

    if (byteCount % 2 != 0)
        return false;

    if (data) {
        QDataStream stream(response.data().remove(0, 3));

        QList<quint16> values;
        const quint8 itemCount = byteCount / 2;
        for (int i = 0; i < itemCount; i++) {
            quint16 tmp;
            stream >> tmp;
            values.append(tmp);
        }
        *data = {QModbusDataUnit::HoldingRegisters, address, values};
    }
    return true;
}

CustomWrite 的响应只需验证响应参数即可:

static bool collateMultipleValues(const QModbusPdu &response, QModbusDataUnit *data)
{
    if (response.dataSize() != WriteResponseSize)
        return false;

    quint16 address, count;
    response.decodeData(&address, &count);

    if (count < 1 || count > 10)
        return false;

    if (data)
        *data = {QModbusDataUnit::HoldingRegisters, address, count};
    return true;
}

运行示例

要运行来自 Qt Creator,打开Welcome 模式,并从Examples 中选择示例。更多信息,请参阅Qt Creator: 教程:构建并运行

该示例不能与其他应用程序结合使用。示例启动后,只能在应用程序内部交换自定义 Modbus 命令。客户端与服务器之间的所有交互均使用 Modbus TCP 协议。

示例项目 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.