En esta página

Comando personalizado Modbus

El ejemplo muestra cómo manejar códigos de función Modbus personalizados.

El ejemplo actúa como cliente y servidor Modbus en una única aplicación. La conexión entre ambos se establece a través de Modbus TCP. Se utiliza para enviar y recibir peticiones Modbus personalizadas y ajusta sus estados internos en función de la petición y la respuesta.

El propósito principal del ejemplo es proporcionar algún código de demostración sobre cómo implementar un cliente Modbus o un servidor Modbus manejando códigos de función Modbus personalizados.

Códigos de Función Modbus Definidos por el Usuario

El protocolo Modbus soporta códigos de función en el rango 1 - 127 (0x01 - 0x7F HEX). La mayoría de los códigos de función están bien definidos y documentados públicamente. Sin embargo, hay dos rangos que pueden utilizarse para funciones definidas por el usuario. Estos son 65 - 72 (0x41 - 48 HEX) y 100 - 110 (0x64 - 0x6E HEX). El usuario puede seleccionar códigos de función de estos rangos y manejarlos de alguna manera personalizada.

Esta aplicación utiliza el código de función 65 (0x41 HEX) para implementar el comando CustomRead y el código de función 66 (0x42 HEX) para implementar el comando CustomWrite. En este ejemplo, los comandos personalizados se utilizan simplemente para leer y escribir en Holding Registers.

Enviando Comandos Modbus Personalizados

Los comandos Modbus personalizados se envían utilizando el método QModbusClient::sendRawRequest(). Este método requiere generar un objeto QModbusRequest con el código de función deseado y una lista de argumentos que serán codificados en un QByteArray:

    QModbusDataUnit unidad { QModbusDataUnit::HoldingRegisters, ui->startAddress->value(),        qMin(ui->numberOfRegisters->currentText().toUShort(),
            quint16(10 -  ui->startAddress->value())) // no pasar de 10 entradas}; 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() };

El método QModbusClient::sendRawRequest() devuelve un objeto QModbusReply que puede utilizarse para comprobar errores como de costumbre:

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

Servidor Modbus personalizado

El servidor personalizado deriva de la clase QModbusTcpServer. Anula el método 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 clase de servidor base llama al método processPrivateRequest() cuando se recibe un comando con un código de función personalizado.

La implementación personalizada gestiona el comando CustomRead generando un QModbusResponse con los valores de los registros solicitados:

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

El manejo del comando CustomWrite incluye la extracción de los nuevos valores del QModbusPdu recibido, haciendo la actualización del valor real, y devolviendo un QModbusResponse con los registros que fueron realmente actualizados:

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

Cliente Modbus Personalizado

El cliente personalizado se deriva de la clase QModbusTcpClient. Anula el método 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 clase cliente base llama al método processPrivateResponse() para procesar las respuestas del servidor con códigos de función personalizados.

La implementación personalizada procesa las respuestas con los códigos de función CustomRead y 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 respuesta CustomRead se gestiona decodificando el código QModbusPdu y extrayendo los valores de los registros solicitados:

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 respuesta CustomWrite se gestiona simplemente validando los parámetros de respuesta:

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

Ejecución del ejemplo

Para ejecutar el ejemplo desde Qt Creatorabra el modo Welcome y seleccione el ejemplo de Examples. Para más información, consulte Qt Creator: Tutorial: Construir y ejecutar.

Este ejemplo no puede utilizarse junto con otras aplicaciones. Una vez iniciado el ejemplo, sólo puede intercambiar comandos Modbus personalizados dentro de la propia aplicación. Todas las interacciones entre el cliente y el servidor utilizan el protocolo Modbus TCP.

Proyecto de ejemplo @ code.qt.io

© 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.