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 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() メソッドを呼び出し、カスタム・ファンクション・コードでサーバー・レスポンスを処理します。

カスタム実装は、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;
}

例の実行

Qt Creator からサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳細については、Building and Running an Example を参照してください。

このサンプルは、他のアプリケーションと組み合わせて使用することはできません。このサンプルを起動すると、アプリケーション内でカスタム Modbus コマンドを交換するだけです。クライアントとサーバー間のすべてのやり取りは Modbus TCP プロトコルを使用します。

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

©2024 The Qt Company Ltd. ここに含まれるドキュメントの著作権はそれぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。