모드버스 사용자 지정 명령

이 예는 사용자 지정 모드버스 기능 코드를 처리하는 방법을 보여줍니다.

이 예제는 단일 애플리케이션에서 모드버스 클라이언트와 서버 역할을 모두 수행합니다. 이들 간의 연결은 Modbus TCP를 통해 설정됩니다. 사용자 지정 Modbus 요청을 보내고 받는 데 사용되며 요청 및 응답에 따라 내부 상태를 조정합니다.

이 예제의 주요 목적은 사용자 지정 모드버스 기능 코드를 처리하는 모드버스 클라이언트 또는 모드버스 서버를 구현하는 방법에 대한 몇 가지 데모 코드를 제공하는 것입니다.

사용자 정의 모드버스 기능 코드

모드버스 프로토콜은 1 - 127 (0x01 - 0x7F HEX) 범위의 기능 코드를 지원합니다. 대부분의 기능 코드는 잘 정의되어 있고 공개적으로 문서화되어 있습니다. 그러나 사용자 정의 기능에 사용할 수 있는 두 가지 범위가 있습니다. 65 - 72 (0x41 - 48 HEX)100 - 110 (0x64 - 0x6E HEX) 입니다. 사용자는 이 범위에서 기능 코드를 선택하여 사용자 정의 방식으로 처리할 수 있습니다.

이 애플리케이션은 함수 코드 65 (0x41 HEX) 를 사용하여 CustomRead 명령을 구현하고 함수 코드 66 (0x42 HEX) 를 사용하여 CustomWrite 명령을 구현합니다. 이 예제에서 사용자 정의 명령은 단순히 Holding Registers 을 읽고 쓰는 데 사용됩니다.

사용자 지정 모드버스 명령 보내기

사용자 지정 모드버스 명령은 QModbusClient::sendRawRequest() 메서드를 사용하여 전송합니다. 이 메서드를 사용하려면 원하는 함수 코드와 인자 목록이 포함된 QModbusRequest 객체를 생성해야 하며, 이 객체는 QByteArray 로 인코딩됩니다:

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

커스텀 모드버스 서버

사용자 정의 서버는 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);
    }

커스텀 모드버스 클라이언트

사용자 지정 클라이언트는 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 에서 예제를 선택합니다. 자세한 내용은 예제 빌드 및 실행하기를 참조하세요.

이 예제는 다른 애플리케이션과 함께 사용할 수 없습니다. 예제가 시작되면 애플리케이션 자체 내에서만 사용자 지정 모드버스 명령을 교환할 수 있습니다. 클라이언트와 서버 간의 모든 상호 작용은 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.