Modbus カスタムコマンド

この例はカスタム Modbus ファンクション・コードの処理方法を示しています。

この例は Modbus クライアントとサーバとして動作します。両者間の接続は Modbus TCP 経由で確立されます。カスタムModbusリクエストの送受信に使用され、リクエストとレスポンスに基づいて内部状態を調整します。

この例の主な目的は、カスタムModbusファンクション・コードを扱うModbusクライアントまたはModbusサーバを実装する方法のデモ・コードを提供することです。

ユーザ定義Modbusファンクション・コード

Modbus プロトコルは1 - 127 (0x01 - 0x7F HEX) のファンクション・コードをサポートしています。ほとんどのファンクション・コードはよく定義され、公開されています。しかし、ユーザー定義ファンクションに使用できる 2 つの範囲があります。それらは65 - 72 (0x41 - 48 HEX)100 - 110 (0x64 - 0x6E HEX) です。ユーザはこれらの範囲からファンクション・コードを選択し、カスタムで処理することができます。

このアプリケーションでは、CustomRead コマンドを実装するためにファンクションコード65 (0x41 HEX) を使用し、CustomWrite コマンドを実装するためにファンクションコード66 (0x42 HEX) を使用しています。この例では、カスタム・コマンドは単にHolding Registers を読み書きするために使用されています。

カスタム Modbus コマンドの送信

カスタム Modbus コマンドはQModbusClient::sendRawRequest() メソッドを使用して送信されます。このメソッドはQByteArray にエンコードされるファンクション・コードと引数のリストでQModbusRequest オブジェクトを生成する必要があります:

    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()]);constquint8バイトカウント=quint8(unit.valueCount()* 2);    QModbusRequestwriteRequest        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 からサンプルを選択します。詳細については、Building and Running an Exampleを参照してください。

この例は他のアプリケーションと組み合わせて使用することはできません。一旦サンプルが開始されると、アプリケーション自体の中でカスタム 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.