Modbus 사용자 정의 명령
이 예제는 사용자 정의 Modbus 기능 코드를 처리하는 방법을 보여줍니다.
이 예제는 단일 애플리케이션 내에서 Modbus 클라이언트와 서버 역할을 모두 수행합니다. 두 구성 요소 간의 연결은 Modbus TCP를 통해 설정됩니다. 이 예제는 사용자 정의 Modbus 요청을 송수신하며, 요청 및 응답에 따라 내부 상태를 조정합니다.
이 예제의 주된 목적은 사용자 정의 Modbus 기능 코드를 처리하는 Modbus 클라이언트 또는 Modbus 서버를 구현하는 방법에 대한 데모 코드를 제공하는 것입니다.

사용자 정의 Modbus 기능 코드
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 을 읽고 쓰는 데 사용합니다.
사용자 정의 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()]);
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 에서 예제를 선택하십시오. 자세한 내용은 Qt Creator: 튜토리얼: 빌드 및 실행을 참조하십시오.
- Qt Extension for Visual Studio Code
Command Palette 에서 Qt: Open Qt examples 명령을 실행하고 목록에서 예제를 선택하십시오. 자세한 내용은 Qt Extension for Visual Studio Code: 튜토리얼: 빌드 및 실행을 참조하십시오.
이 예제는 다른 애플리케이션과 함께 사용할 수 없습니다. 예제가 시작되면 애플리케이션 내부에서만 사용자 정의 Modbus 명령을 교환할 수 있습니다. 클라이언트와 서버 간의 모든 상호 작용은 Modbus TCP 프로토콜을 사용합니다.
© 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.