Commande personnalisée Modbus
L'exemple montre comment gérer les codes de fonction Modbus personnalisés.
L'exemple fait office de client et de serveur Modbus dans une seule application. La connexion entre les deux est établie via Modbus TCP. Il est utilisé pour envoyer et recevoir des requêtes Modbus personnalisées et ajuste ses états internes en fonction de la requête et de la réponse.
L'objectif principal de l'exemple est de fournir un code de démonstration sur la manière de mettre en œuvre un client ou un serveur Modbus gérant des codes de fonction Modbus personnalisés.

Codes de fonction Modbus définis par l'utilisateur
Le protocole Modbus prend en charge les codes de fonction dans la plage 1 - 127 (0x01 - 0x7F HEX). La plupart des codes de fonction sont bien définis et documentés publiquement. Toutefois, deux plages peuvent être utilisées pour les fonctions définies par l'utilisateur. Il s'agit de 65 - 72 (0x41 - 48 HEX) et 100 - 110 (0x64 - 0x6E HEX). L'utilisateur peut sélectionner des codes de fonction dans ces plages et les traiter de manière personnalisée.
Cette application utilise le code de fonction 65 (0x41 HEX) pour mettre en œuvre la commande CustomRead et le code de fonction 66 (0x42 HEX) pour mettre en œuvre la commande CustomWrite. Dans cet exemple, les commandes personnalisées sont utilisées pour lire et écrire simplement le code Holding Registers.
Envoi de commandes Modbus personnalisées
Les commandes Modbus personnalisées sont envoyées à l'aide de la méthode QModbusClient::sendRawRequest(). Cette méthode nécessite de générer un objet QModbusRequest avec le code de fonction souhaité et une liste d'arguments qui seront encodés dans un objet QByteArray:
QModbusDataUnit unité { QModbusDataUnit::HoldingRegisters, ui->startAddress->value(), qMin(ui->numberOfRegisters->currentText().toUShort(), quint16(10 - ui->startAddress->value())) // ne pas dépasser 10 entrées} ; 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() } ;
La méthode QModbusClient::sendRawRequest() renvoie un objet QModbusReply qui peut être utilisé pour vérifier les erreurs comme d'habitude :
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); }
Serveur Modbus personnalisé
Le serveur personnalisé est dérivé de la classe QModbusTcpServer. Il surcharge la méthode 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; };
La classe de serveur de base appelle la méthode processPrivateRequest() lorsqu'elle reçoit une commande avec un code de fonction personnalisé.
L'implémentation personnalisée traite la commande CustomRead en générant un QModbusResponse avec les valeurs des registres demandés :
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()); }
Le traitement de la commande CustomWrite comprend l'extraction des nouvelles valeurs à partir de la commande QModbusPdu reçue, la mise à jour de la valeur réelle et le renvoi d'une commande QModbusResponse avec les registres qui ont été effectivement mis à jour :
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); }
Client Modbus personnalisé
Le client personnalisé est dérivé de la classe QModbusTcpClient. Il surcharge la méthode 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; };
La classe client de base appelle la méthode processPrivateResponse() pour traiter les réponses du serveur avec des codes de fonction personnalisés.
L'implémentation personnalisée traite les réponses avec les codes de fonction CustomRead et 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); }
La réponse CustomRead est traitée en décodant le code QModbusPdu fourni et en extrayant les valeurs des registres demandés :
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; }
La réponse CustomWrite est traitée en validant simplement les paramètres de la réponse :
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; }
Exécution de l'exemple
Pour exécuter l'exemple de Qt Creatorouvrez le mode Welcome et sélectionnez l'exemple à partir de Examples. Pour plus d'informations, voir Qt Creator: Tutoriel : Construire et exécuter.
Cet exemple ne peut pas être utilisé avec d'autres applications. Une fois que l'exemple est lancé, il ne peut échanger des commandes Modbus personnalisées qu'au sein de l'application elle-même. Toutes les interactions entre le client et le serveur utilisent le protocole 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.