Modbus自定义命令
示例展示了如何处理自定义Modbus功能码。
该示例在单个应用程序中充当Modbus客户端和服务器。两者之间的连接通过Modbus TCP建立。它用于发送和接收自定义Modbus请求,并根据请求和响应调整其内部状态。
示例的主要目的是提供一个关于如何实现处理自定义Modbus功能码的Modbus客户端或Modbus服务器的示例代码。
用户定义的Modbus功能码
Modbus协议支持范围在1 - 127(十六进制0x01 - 0x7F)的功能码。大多数功能码都有明确的定义并公开文档记录。然而,有两个范围可以用于用户自定义功能。那分别是65 - 72(十六进制0x41 - 0x48)和100 - 110(十六进制0x64 - 0x6E)。用户可以从这些范围中选择功能码,并以某种自定义方式处理它们。
该应用程序使用功能码65(十六进制0x41)来实现CustomRead
命令,并使用功能码66(十六进制0x42)来实现CustomWrite
命令。在这个例子中,自定义命令用于简单地读取和写入保持寄存器。
发送自定义Modbus命令
通过使用QModbusClient::sendRawRequest()方法发送自定义Modbus命令。此方法需要生成一个带有所需功能码和参数列表的QModbusRequest对象,这些参数将被编码到一个QByteArray中。
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 运行示例,打开 欢迎 模式并从 示例 中选择示例。有关更多信息,请访问 构建和运行示例。
此示例不能与其它应用程序一起使用。一旦开始示例,它只可以在应用程序内部交换自定义 Modbus 命令。客户端和服务器之间的所有交互都使用 Modbus TCP 协议。
© 2024 The Qt Company Ltd. 本文档中包含的贡献的文档版权属于其各自的所有者。本文档是根据自由软件基金会出版的 GNU 自由文档许可协议版本 1.3 的条款许可的。Qt 和相关标志是 The Qt Company Ltd 在芬兰以及/或其他国家和地区的 商标。所有其他商标属其各自所有者所有。