このページでは

Modbus カスタムコマンド

この例では、Modbusのカスタム機能コードの処理方法について説明しています。

このサンプルでは、単一のアプリケーション内でModbusクライアントとサーバーの両方の役割を果たします。両者の間の接続はModbus TCPを介して確立されます。このサンプルは、カスタムModbusリクエストの送受信を行い、リクエストとレスポンスに基づいて内部状態を調整します。

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

サーバーおよびクライアントのレジスタテーブル、カスタム機能コード 0x41(読み取り)および 0x42(書き込み)を含む Modbus カスタムコマンドウィンドウ

ユーザー定義のModbus機能コード

Modbusプロトコルは、1 - 127 (0x01 - 0x7F HEX) の範囲の機能コードをサポートしています。機能コードのほとんどは明確に定義されており、公開文書にも記載されています。しかし、ユーザー定義の機能に使用できる2つの範囲があります。それらは65 - 72 (0x41 - 48 HEX) と100 - 110 (0x64 - 0x6E HEX) です。ユーザーはこれらの範囲から機能コードを選択し、独自の方法で処理することができます。

このアプリケーションでは、関数コード65 (0x41 HEX) を使用してCustomRead コマンドを実装し、関数コード66 (0x42 HEX) を使用してCustomWrite コマンドを実装しています。この例では、カスタムコマンドを使用して、単にHolding Registers の読み書きを行っています。

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

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

    QModbusDataUnit unit {
        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 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() メソッドを呼び出し、カスタム関数コードを使用してサーバーの応答を処理します。

このカスタム実装では、CustomRead およびCustomWrite 関数コードを含むレスポンスを処理します。

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;
}

例の動作確認

以下の場所からサンプルを実行できます:

このサンプルは、他のアプリケーションと併用することはできません。サンプルが起動されると、アプリケーション内部でのみカスタム Modbus コマンドのやり取りが可能になります。クライアントとサーバー間のすべての通信には、Modbus TCP プロトコルが使用されます。

code.qt.io上のサンプルプロジェクト

© 2026 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.