Modbus自定义命令

示例展示了如何处理自定义Modbus功能码。

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

示例的主要目的是提供一个关于如何实现处理自定义Modbus功能码的Modbus客户端或Modbus服务器的示例代码。

用户定义的Modbus功能码

Modbus协议支持范围在1 - 127(十六进制0x01 - 0x7F)的功能码。大多数功能码都有明确的定义并公开文档记录。然而,有两个范围可以用于用户自定义功能。那分别是65 - 72(十六进制0x41 - 0x48)和100 - 110(十六进制0x64 - 0x6E)。用户可以从这些范围中选择功能码,并以某种自定义方式处理它们。

该应用程序使用功能码65(十六进制0x41)来实现CustomRead命令,并使用功能码66(十六进制0x42)来实现CustomWrite命令。在这个例子中,自定义命令用于简单地读取和写入保持寄存器

发送自定义Modbus命令

通过使用QModbusClient::sendRawRequest()方法发送自定义Modbus命令。此方法需要生成一个带有所需功能码和参数列表的QModbusRequest对象,这些参数将被编码到一个QByteArray中。

    QModbusDataUnit unit {
        QModbusDataUnit::HoldingRegisters,
        ui->startAddress->value(),
        qMin(ui->numberOfRegisters->currentText().toUShort(),
            quint16(10 - ui->startAddress->value())) // do not go beyond 10 entries
    };

    for (qsizetype i = 0, total = unit.valueCount(); i < total; ++i)
        unit.setValue(i, m_model->m_registers[i + unit.startAddress()]);

    const quint8 byteCount = quint8(unit.valueCount() * 2);
    QModbusRequest writeRequest {
        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 运行示例,打开 欢迎 模式并从 示例 中选择示例。有关更多信息,请访问 构建和运行示例

此示例不能与其它应用程序一起使用。一旦开始示例,它只可以在应用程序内部交换自定义 Modbus 命令。客户端和服务器之间的所有交互都使用 Modbus TCP 协议。

示例项目 @ code.qt.io

© 2024 The Qt Company Ltd. 本文档中包含的贡献的文档版权属于其各自的所有者。本文档是根据自由软件基金会出版的 GNU 自由文档许可协议版本 1.3 的条款许可的。Qt 和相关标志是 The Qt Company Ltd 在芬兰以及/或其他国家和地区的 商标。所有其他商标属其各自所有者所有。