Benutzerdefinierter Modbus-Befehl

Das Beispiel zeigt, wie benutzerdefinierte Modbus-Funktionscodes behandelt werden können.

Das Beispiel fungiert sowohl als Modbus-Client als auch als Server in einer einzigen Anwendung. Die Verbindung zwischen ihnen wird über Modbus TCP hergestellt. Es wird verwendet, um benutzerdefinierte Modbus-Anfragen zu senden und zu empfangen und passt seine internen Zustände basierend auf der Anfrage und Antwort an.

Der Hauptzweck des Beispiels ist die Bereitstellung von Demo-Code für die Implementierung eines Modbus-Clients oder Modbus-Servers, der benutzerdefinierte Modbus-Funktionscodes verarbeitet.

Benutzerdefinierte Modbus-Funktionscodes

Das Modbus-Protokoll unterstützt Funktionscodes im Bereich 1 - 127 (0x01 - 0x7F HEX). Die meisten der Funktionscodes sind gut definiert und öffentlich dokumentiert. Es gibt jedoch zwei Bereiche, die für benutzerdefinierte Funktionen verwendet werden können. Diese sind 65 - 72 (0x41 - 48 HEX) und 100 - 110 (0x64 - 0x6E HEX). Der Benutzer kann Funktionscodes aus diesen Bereichen auswählen und sie auf eine benutzerdefinierte Weise behandeln.

Diese Anwendung verwendet den Funktionscode 65 (0x41 HEX) zur Implementierung des Befehls CustomRead und den Funktionscode 66 (0x42 HEX) zur Implementierung des Befehls CustomWrite. In diesem Beispiel werden die benutzerdefinierten Befehle zum einfachen Lesen und Schreiben von Holding Registers verwendet.

Senden von benutzerdefinierten Modbus-Befehlen

Die benutzerdefinierten Modbus-Befehle werden mit der Methode QModbusClient::sendRawRequest() gesendet. Diese Methode erfordert die Erzeugung eines QModbusRequest Objekts mit dem gewünschten Funktionscode und einer Liste von Argumenten, die in ein QByteArray kodiert werden:

    QModbusDataUnit unit { QModbusDataUnit::HoldingRegisters, ui->startAddress->value(),        qMin(ui->numberOfRegisters->currentText().toUShort(),
            quint16(10 -  ui->startAddress->value())) // nicht über 10 Einträge hinausgehen}; 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() };

Die Methode QModbusClient::sendRawRequest() gibt ein QModbusReply Objekt zurück, das wie üblich zur Fehlerprüfung verwendet werden kann:

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

Benutzerdefinierter Modbus-Server

Der benutzerdefinierte Server ist von der Klasse QModbusTcpServer abgeleitet. Er setzt die Methode QModbusServer::processPrivateRequest() außer Kraft.

class ModbusServer : public QModbusTcpServer
{
    Q_OBJECT
    Q_DISABLE_COPY_MOVE(ModbusServer)

public:
    ModbusServer(QObject *parent = nullptr);

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

Die Basisserverklasse ruft die Methode processPrivateRequest() auf, wenn ein Befehl mit einem benutzerdefinierten Funktionscode empfangen wird.

Die benutzerdefinierte Implementierung verarbeitet den Befehl CustomRead, indem sie eine QModbusResponse mit den Werten der angeforderten Register erzeugt:

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

Die Verarbeitung des Befehls CustomWrite umfasst das Extrahieren der neuen Werte aus dem empfangenen QModbusPdu, die eigentliche Aktualisierung der Werte und die Rückgabe eines QModbusResponse mit den tatsächlich aktualisierten Registern:

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

Benutzerdefinierter Modbus-Client

Der benutzerdefinierte Client ist von der Klasse QModbusTcpClient abgeleitet. Er setzt die Methode QModbusClient::processPrivateResponse() außer Kraft.

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

Die Basis-Client-Klasse ruft die Methode processPrivateResponse() auf, um die Serverantworten mit benutzerdefinierten Funktionscodes zu verarbeiten.

Die benutzerdefinierte Implementierung verarbeitet die Antworten mit den Funktionscodes CustomRead und 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);
}

Die Antwort CustomRead wird durch Dekodierung des bereitgestellten QModbusPdu und Extraktion der Werte für die angeforderten Register verarbeitet:

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

Die Antwort CustomWrite wird durch einfache Validierung der Antwortparameter bearbeitet:

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

Ausführen des Beispiels

Zum Ausführen des Beispiels von Qt Creatorauszuführen, öffnen Sie den Modus Welcome und wählen Sie das Beispiel unter Examples aus. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.

Dieses Beispiel kann nicht in Verbindung mit anderen Anwendungen verwendet werden. Sobald das Beispiel gestartet ist, kann es nur innerhalb der Anwendung selbst benutzerdefinierte Modbus-Befehle austauschen. Alle Interaktionen zwischen dem Client und dem Server verwenden das Modbus TCP-Protokoll.

Beispielprojekt @ 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.