Qt6 tutorial three (15) Modbus communication

An introduction to Modbus protocol

Modbus was born in 1979 and Modicon was later acquired by Schneider Electric. Modbus provides a common language for devices and equipment to communicate with each other.
Modbus has become the industry standard for communication protocols in the industrial field and is now a commonly used connection method between industrial electronic equipment. Modbus is currently the most widely used protocol in the industrial field.

Modbus advantages:

  1. The Modbus protocol standard is open, published and has no copyright requirements;
  2. Modbus protocol supports a variety of electrical interfaces, including RS232, RS485, TCP/IP, etc., and can also be transmitted on various media, such as twisted pair, optical fiber, infrared, wireless, etc.;
  3. The Modbus protocol message frame format is simple, compact, and easy to understand. It is simple for users to understand and use, easy for manufacturers to develop and integrate, and facilitates the formation of industrial control networks.

Modbus communication process:

  1. Modbus is a communication protocol with one master and multiple slaves: only one device can send requests in Modbus communication.
  2. The master can only send a request to one slave at the same time. Only one data is transmitted on the bus at a time, that is, the master sends and the slave responds. If the master does not send, there is no data communication on the bus.
  3. The slave will not send messages to the master itself, but can only reply to message requests sent from the master.
  4. Modbus does not have a busy mechanism to determine whether the slave machine is receiving normally through software.

Modbus storage area reading and writing

  1. The Modbus protocol stipulates four storage areas: 0, 1, 3, and 4. Areas 0 and 4 are readable and writable, and areas 1 and 3 are read-only.
  2. When the host obtains data from the slave, it only needs to tell the slave the starting address of the data and how many bytes of data to obtain. In fact, it is reading and writing the actual storage space corresponding to the slave device.

Modbus protocol type

There are four common Modbus protocol types:

  1. Modbus-RTU: Only supports serial port, uses compact hexadecimal to represent data, and subsequent commands/data have a cyclic redundancy check checksum. RTU mode is more commonly used.
  2. Modbus-ASCII: only supports serial port, uses Ascii code to represent data, every 8Bit byte is sent as two ASCII characters, and adopts vertical redundancy checksum;
  3. Modbus-TCP: Supports network ports, uses MODBUS as the application layer protocol and TCP/IP as the lower layer protocol. And the slave address is not required, but the MBAP message header is required. Because TCP itself has the ability to check errors, the protocol does not require error checking .

  4. ModbusPlus: It is a typical token ring network that completely defines the performance indicators of communication protocols, network structures, connecting cables (or optical cables) and installation tools.

Three transmission modes of Modbus protocol

Modbus protocol is an application layer message transmission protocol, including three message types: ASCII, RTU, and TCP. The standard Modbus protocol physical layer interfaces include RS232, RS422, RS485 and Ethernet interfaces, which communicate in master/slave mode.

  1. ASCII: uses LRC check;
  2. RTU (remote terminal control system): adopts 16-bit CRC check;
  3. TCP/IP: No checksum is used because the TCP protocol is a reliable connection-oriented protocol;


4 types of operation objects of Modbus

The four operating objects of Modbus are coils, discrete inputs DiscreteInputs, input registers InputRegisters, and holding registers HoldingRegisters. The read and write permissions of the four operating objects are as follows:
coil: output bit of PLC, switching value, readable and writable in MODbus;
discrete input: input bit of PLC, switching value, read-only in Modbus;
input register: PLC Registers that can only be changed from the analog input end are read-only in MODBUS;
holding registers: registers used to output analog signals in PLC, are readable and writable in MODBUS;

2 Qt case demonstration based on ModBus-TCP


The Modbus protocol is a master/slave architecture protocol. One node is the master node, and other nodes participating in communication using the Modbus protocol are slave nodes. Each slave device has a unique address. In serial and MB+ networks, only the node designated as the master node can initiate a command (on an Ethernet network, any device can send a Modbus command, but usually only one master node device initiates the command). Generally, the host computer is used as the master station and the PLC is used as the slave station.


Modbus TCP/IP protocol format

As shown in the figure above, the message is mainly divided into two parts, namely the protocol header (MBAP Header) and PDU. PDU also contains two parts: function code and data.

Next, let’s take a look directly at Qt’s official Modbus-tcp example: realizing the communication function of the master and slave stations;

//Qt中几个常用的串口modbus类

QModbusRtuSerialSlave    //modbus串口通信方式下的服务器类
QModbusRtuSerialMaster   //串口通信方式下的客户端类
QModbusServer            // QModbusServer类接收和处理modbus的请求。
QModbusDataUnit         //存储接收和发送数据的类,数据类型为1bit和16bit
QModbusReply            //客户端访问服务器后得到的回复(如客户端读服务器数据时包含数据信息)

Main site key code:


#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "settingsdialog.h"
#include "writeregistermodel.h"

#include <QModbusTcpClient>
#include <QModbusRtuSerialClient>
#include <QStandardItemModel>
#include <QStatusBar>
#include <QUrl>

enum ModbusConnection {
    Serial,
    Tcp
};

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //初始化设置对话框类
    m_settingsDialog = new SettingsDialog(this);

    //初始化菜单操作
    initActions();

    //初始化写入寄存器模型
    writeModel = new WriteRegisterModel(this);
    //设置地址起始位置
    writeModel->setStartAddress(ui->writeAddress->value());
    //设置写入的地址位数量
    writeModel->setNumberOfValues(ui->writeSize->currentText());

    //把要写入的数据使用模型的方式,给视图展示出来
    ui->writeValueTable->setModel(writeModel);
    //设置隐藏的列为第二列
    ui->writeValueTable->hideColumn(2);

    //当视图有更新时,同步刷新列表
    connect(writeModel, &WriteRegisterModel::updateViewport,
            ui->writeValueTable->viewport(), QOverload<>::of(&QWidget::update));

    //添加写入数据单元的类型: 主要有五种,分别是 无效、线圈、离散输入、输入寄存器、保持寄存器
    ui->writeTable->addItem(tr("线圈"), QModbusDataUnit::Coils);
    ui->writeTable->addItem(tr("离散输入"), QModbusDataUnit::DiscreteInputs);
    ui->writeTable->addItem(tr("输入寄存器"), QModbusDataUnit::InputRegisters);
    ui->writeTable->addItem(tr("保持寄存器"), QModbusDataUnit::HoldingRegisters);

#if QT_CONFIG(modbus_serialport)
    //设置连接类型,有串口和TCP两种方式
    ui->connectType->setCurrentIndex(0);
    //使用当前选择的连接方式进行与服务器端连接
    onConnectTypeChanged(0);
#else
    // 锁定串行端口选项
    ui->connectType->setCurrentIndex(1);
    //使用当前选择的连接方式进行与服务器端连接
    onConnectTypeChanged(1);
    //禁用选择框
    ui->connectType->setEnabled(false);
#endif

    //创建标准项模型对象,一共10行一列
    auto model = new QStandardItemModel(10, 1, this);
    //循环添加项到标准模型里面
    for (int i = 0; i < 10; ++i)
        model->setItem(i, new QStandardItem(QStringLiteral("%1").arg(i + 1)));
    ui->writeSize->setModel(model);
    ui->writeSize->setCurrentText("10");

    //把写入位变化及时更新至写入注册模型中
    connect(ui->writeSize, &QComboBox::currentTextChanged,
            writeModel, &WriteRegisterModel::setNumberOfValues);

    //把开始地址更新至写入模型中
    connect(ui->writeAddress, &QSpinBox::valueChanged, writeModel, &WriteRegisterModel::setStartAddress);

    //把起始位之前的位选择功能禁用
    connect(ui->writeAddress, &QSpinBox::valueChanged, this, [this, model](int i) {
        int lastPossibleIndex = 0;
        const int currentIndex = ui->writeSize->currentIndex();
        for (int ii = 0; ii < 10; ++ii) {
            if (ii < (10 - i)) {
                lastPossibleIndex = ii;
                model->item(ii)->setEnabled(true);
            } else {
                model->item(ii)->setEnabled(false);
            }
        }
        if (currentIndex > lastPossibleIndex)
            ui->writeSize->setCurrentIndex(lastPossibleIndex);
    });
}

MainWindow::~MainWindow()
{
    if (modbusDevice)
        modbusDevice->disconnectDevice();
    delete modbusDevice;

    delete ui;
}

//完成每个菜单的响应处理,即绑定菜单的信号与槽,最终在槽函数里面完成菜单的响应逻辑
void MainWindow::initActions()
{
    ui->actionConnect->setEnabled(true);

    ui->actionDisconnect->setEnabled(false);
    ui->actionExit->setEnabled(true);
    ui->actionOptions->setEnabled(true);


    connect(ui->connectButton, &QPushButton::clicked,
            this, &MainWindow::onConnectButtonClicked);
    connect(ui->actionConnect, &QAction::triggered,
            this, &MainWindow::onConnectButtonClicked);
    connect(ui->actionDisconnect, &QAction::triggered,
            this, &MainWindow::onConnectButtonClicked);
    connect(ui->readButton, &QPushButton::clicked,
            this, &MainWindow::onReadButtonClicked);
    connect(ui->writeButton, &QPushButton::clicked,
            this, &MainWindow::onWriteButtonClicked);
    connect(ui->readWriteButton, &QPushButton::clicked,
            this, &MainWindow::onReadWriteButtonClicked);
    connect(ui->connectType, &QComboBox::currentIndexChanged,
            this, &MainWindow::onConnectTypeChanged);
    connect(ui->writeTable, &QComboBox::currentIndexChanged,
            this, &MainWindow::onWriteTableChanged);

    connect(ui->actionExit, &QAction::triggered, this, &QMainWindow::close);
    connect(ui->actionOptions, &QAction::triggered, m_settingsDialog, &QDialog::show);
}


//检测连接类型
void MainWindow::onConnectTypeChanged(int index)
{
    //如果客户端已连接,则断开
    if (modbusDevice) {
        modbusDevice->disconnectDevice();
        delete modbusDevice;
        modbusDevice = nullptr;
    }

    //转换连接类型的数字为当前定义的枚举,即Serial或Tcp
    auto type = static_cast<ModbusConnection>(index);
    //如果是串口
    if (type == Serial) {
#if QT_CONFIG(modbus_serialport)
        //初始化QModbusRtuSerialClient类,它表示Modbus客户端,并且使用串行总线与Modbus服务器进行通信
        modbusDevice = new QModbusRtuSerialClient(this);
#endif
    }
    //如果是TCP
    else if (type == Tcp) {
        //初始化QModbusTcpClient类,它是Modbus-TCP客户端设备的接口类
        modbusDevice = new QModbusTcpClient(this);

        //如果端口设置没有设置,则把默认值设置为127.0.0.1:502
        if (ui->portEdit->text().isEmpty())
            ui->portEdit->setText(QLatin1String("127.0.0.1:502"));
    }

    connect(modbusDevice, &QModbusClient::errorOccurred, [this](QModbusDevice::Error) {
        statusBar()->showMessage(modbusDevice->errorString(), 5000);
    });

    if (!modbusDevice) {
        ui->connectButton->setDisabled(true);
        statusBar()->showMessage(tr("无法创建Modbus客户端."), 5000);
    } else {
        connect(modbusDevice, &QModbusClient::stateChanged,
                this, &MainWindow::onModbusStateChanged);
    }
}

//配置一些主站相关的参数
void MainWindow::onConnectButtonClicked()
{
    if (!modbusDevice)
        return;

    statusBar()->clearMessage();
    if (modbusDevice->state() != QModbusDevice::ConnectedState) {
        if (static_cast<ModbusConnection>(ui->connectType->currentIndex()) == Serial) {
            modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,
                ui->portEdit->text());
#if QT_CONFIG(modbus_serialport)
            //设置串口的几个关键参数:parity,baud,dataBits,stopBits
            modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,
                m_settingsDialog->settings().parity);
            modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
                m_settingsDialog->settings().baud);
            modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,
                m_settingsDialog->settings().dataBits);
            modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,
                m_settingsDialog->settings().stopBits);
#endif
        } else {
            //设置连接的IP和端口
            const QUrl url = QUrl::fromUserInput(ui->portEdit->text());
            modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, url.port());
            modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, url.host());
        }
        modbusDevice->setTimeout(m_settingsDialog->settings().responseTime);
        modbusDevice->setNumberOfRetries(m_settingsDialog->settings().numberOfRetries);
        if (!modbusDevice->connectDevice()) {
            statusBar()->showMessage(tr("连接失败: ") + modbusDevice->errorString(), 5000);
        } else {
            ui->actionConnect->setEnabled(false);
            ui->actionDisconnect->setEnabled(true);
        }
    } else {
        modbusDevice->disconnectDevice();
        ui->actionConnect->setEnabled(true);
        ui->actionDisconnect->setEnabled(false);
    }
}



//监听连接状态
void MainWindow::onModbusStateChanged(int state)
{
    bool connected = (state != QModbusDevice::UnconnectedState);
    ui->actionConnect->setEnabled(!connected);
    ui->actionDisconnect->setEnabled(connected);

    if (state == QModbusDevice::UnconnectedState)
        ui->connectButton->setText(tr("连接"));
    else if (state == QModbusDevice::ConnectedState)
        ui->connectButton->setText(tr("断开"));
}

//读取从站(服务器端)的数据
void MainWindow::onReadButtonClicked()
{
    if (!modbusDevice)
        return;

    ui->readValue->clear();
    statusBar()->clearMessage();

    //向服务器请求读取数据,参数含义为:数据单元和服务器地址
    if (auto *reply = modbusDevice->sendReadRequest(readRequest(), ui->serverEdit->value())) {
        //判断答复状态,若还没结束则读取请求过来的数据
        if (!reply->isFinished())
            //开始读取数据
            connect(reply, &QModbusReply::finished, this, &MainWindow::onReadReady);
        else
            delete reply; // 广播回复立即返回
    } else {
        statusBar()->showMessage(tr("读取错误: ") + modbusDevice->errorString(), 5000);
    }
}


//读取数据
void MainWindow::onReadReady()
{
    auto reply = qobject_cast<QModbusReply *>(sender());
    if (!reply)
        return;

    //没有错误则开始读取数据
    if (reply->error() == QModbusDevice::NoError) {
        //记录读取的数据
        const QModbusDataUnit unit = reply->result();
        //依次取出各个位的数据
        for (qsizetype i = 0, total = unit.valueCount(); i < total; ++i) {
            const QString entry = tr("地址: %1, 值: %2").
                    arg(unit.startAddress() + i).
                    arg(QString::number(unit.value(i),unit.registerType() <= QModbusDataUnit::Coils ? 10 : 16));
            //把每一个位的数据放入列表中显示出来,包括地址和值
            ui->readValue->addItem(entry);
        }
        //若通信异常,则显示对应的错误信息
    } else if (reply->error() == QModbusDevice::ProtocolError) {
        statusBar()->showMessage(tr("读取响应错误: %1 (Modbus异常: 0x%2)").
                                    arg(reply->errorString()).
                                    arg(reply->rawResult().exceptionCode(), -1, 16), 5000);
    } else {
        statusBar()->showMessage(tr("读取响应错误: %1 (编码: 0x%2)").
                                    arg(reply->errorString()).
                                    arg(reply->error(), -1, 16), 5000);
    }

    reply->deleteLater();
}


//写数据
void MainWindow::onWriteButtonClicked()
{
    if (!modbusDevice)
        return;
    statusBar()->clearMessage();

    //获取写入数据的包装类型
    QModbusDataUnit writeUnit = writeRequest();
    //获取需要写入的区域:线圈、保持寄存器
    QModbusDataUnit::RegisterType table = writeUnit.registerType();
    //writeUnit.valueCount()返回寄存器中数据单元的起始地址。
    for (qsizetype i = 0, total = writeUnit.valueCount(); i < total; ++i) {
        //如果写入的区域是线圈,则可以写入
        if (table == QModbusDataUnit::Coils)
            //写入线圈的0至结束地址
            writeUnit.setValue(i, writeModel->m_coils[i + writeUnit.startAddress()]);
        else
            //写入保持寄存器0至结束地址
            writeUnit.setValue(i, writeModel->m_holdingRegisters[i + writeUnit.startAddress()]);
    }

    //开始写入数据,参数为要写入的封装数据和服务器地址
    if (auto *reply = modbusDevice->sendWriteRequest(writeUnit, ui->serverEdit->value())) {
        //检测写入状态
        if (!reply->isFinished()) {
            connect(reply, &QModbusReply::finished, this, [this, reply]() {
                if (reply->error() == QModbusDevice::ProtocolError) {
                    statusBar()->showMessage(tr("写入响应错误: %1 (Modbus异常: 0x%2)")
                        .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16),
                        5000);
                } else if (reply->error() != QModbusDevice::NoError) {
                    statusBar()->showMessage(tr("写入响应错误: %1 (编码: 0x%2)").
                        arg(reply->errorString()).arg(reply->error(), -1, 16), 5000);
                }
                reply->deleteLater();
            });
        } else {
            // broadcast replies return immediately
            reply->deleteLater();
        }
    } else {
        statusBar()->showMessage(tr("写错误: ") + modbusDevice->errorString(), 5000);
    }
}

//读写数据: 只有保持寄存器支持同时读写
void MainWindow::onReadWriteButtonClicked()
{
    if (!modbusDevice)
        return;
    ui->readValue->clear();
    statusBar()->clearMessage();

    //写请求
    QModbusDataUnit writeUnit = writeRequest();
    QModbusDataUnit::RegisterType table = writeUnit.registerType();
    for (qsizetype i = 0, total = writeUnit.valueCount(); i < total; ++i) {
        if (table == QModbusDataUnit::Coils)
            writeUnit.setValue(i, writeModel->m_coils[i + writeUnit.startAddress()]);
        else
            writeUnit.setValue(i, writeModel->m_holdingRegisters[i + writeUnit.startAddress()]);
    }

    //读写请求
    if (auto *reply = modbusDevice->sendReadWriteRequest(readRequest(), writeUnit,
        ui->serverEdit->value())) {
        if (!reply->isFinished())
            connect(reply, &QModbusReply::finished, this, &MainWindow::onReadReady);
        else
            delete reply; // broadcast replies return immediately
    } else {
        statusBar()->showMessage(tr("读错误: ") + modbusDevice->errorString(), 5000);
    }
}


//检测写入区域的选择: 线圈、离散输入、输入寄存器、保持寄存器
void MainWindow::onWriteTableChanged(int index)
{
    //判断是线圈还是保持寄存器
    const bool coilsOrHolding = index == 0 || index == 3;
    //如果选择的是线圈或保持寄存器
    if (coilsOrHolding) {
        //隐藏第2、3列
        ui->writeValueTable->setColumnHidden(1, index != 0);
        ui->writeValueTable->setColumnHidden(2, index != 3);
        //根据列内容的大小调整第一列的大小。
        ui->writeValueTable->resizeColumnToContents(0);
    }

    //若写入区域为保持寄存器,则启用读写按钮
    ui->readWriteButton->setEnabled(index == 3);
    //若写入区域为线圈或保持寄存器,则启用读写按钮
    ui->writeButton->setEnabled(coilsOrHolding);
    //若写入区域为保持寄存器,则启用写入区域的所有字节输入框
    ui->writeGroupBox->setEnabled(coilsOrHolding);
}



//包装需要读取的数据
QModbusDataUnit MainWindow::readRequest() const
{
    //获取当前需要读取的区域:线圈、离散输入、输入寄存器、保持寄存器
    const auto table =
        static_cast<QModbusDataUnit::RegisterType>(ui->writeTable->currentData().toInt());

    int startAddress = ui->readAddress->value();
    Q_ASSERT(startAddress >= 0 && startAddress < 10);

    // 不要超过10个条目,在需要读取的字节数与有效数据的字节数之间获取较小的值
    quint16 numberOfEntries = qMin(ui->readSize->currentText().toUShort(), quint16(10 - startAddress));
    //返回需要读取的数据单元,参数的含义分别是要读取的区域、开始地址、读取的字节数
    return QModbusDataUnit(table, startAddress, numberOfEntries);
}




//包装写入的数据
QModbusDataUnit MainWindow::writeRequest() const
{
    //获取写入数据的区域类型:线圈、离散输入、输入寄存器、保持寄存器
    const auto table =
        static_cast<QModbusDataUnit::RegisterType>(ui->writeTable->currentData().toInt());
     //开始地址
    int startAddress = ui->writeAddress->value();
    Q_ASSERT(startAddress >= 0 && startAddress < 10);

    //  不要超过10个条目,在需要读取的字节数与有效数据的字节数之间获取较小的值
    quint16 numberOfEntries = qMin(ui->writeSize->currentText().toUShort(), quint16(10 - startAddress));
    //返回需要读取的数据单元,参数的含义分别是要读取的区域、开始地址、读取的字节数
    return QModbusDataUnit(table, startAddress, numberOfEntries);
}



Key code of slave station:


#include "mainwindow.h"
#include "settingsdialog.h"
#include "ui_mainwindow.h"

#include <QModbusRtuSerialServer>
#include <QModbusTcpServer>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
#include <QStatusBar>
#include <QUrl>

//枚举连接类型
enum ModbusConnection {
    Serial,
    Tcp
};

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setupWidgetContainers();

    //设置默认连接类型
#if QT_CONFIG(modbus_serialport)
    ui->connectType->setCurrentIndex(0);
    onCurrentConnectTypeChanged(0);
#else
    // 锁定串行端口选项,设置连接类型
    ui->connectType->setCurrentIndex(1);
    onCurrentConnectTypeChanged(1);
    ui->connectType->setEnabled(false);
#endif

    m_settingsDialog = new SettingsDialog(this);
    initActions();
}

MainWindow::~MainWindow()
{
    if (modbusDevice)
        modbusDevice->disconnectDevice();
    delete modbusDevice;

    delete ui;
}

//初始化菜单
void MainWindow::initActions()
{
    ui->actionConnect->setEnabled(true);
    ui->actionDisconnect->setEnabled(false);
    ui->actionExit->setEnabled(true);
    ui->actionOptions->setEnabled(true);

    connect(ui->connectButton, &QPushButton::clicked,
            this, &MainWindow::onConnectButtonClicked);
    connect(ui->actionConnect, &QAction::triggered,
            this, &MainWindow::onConnectButtonClicked);
    connect(ui->actionDisconnect, &QAction::triggered,
            this, &MainWindow::onConnectButtonClicked);
    connect(ui->connectType, &QComboBox::currentIndexChanged,
            this, &MainWindow::onCurrentConnectTypeChanged);

    connect(ui->actionExit, &QAction::triggered, this, &QMainWindow::close);
    connect(ui->actionOptions, &QAction::triggered, m_settingsDialog, &QDialog::show);
}

//类型变化检测,根据选中的不同连接类型创建不同的对象
void MainWindow::onCurrentConnectTypeChanged(int index)
{
    if (modbusDevice) {
        modbusDevice->disconnect();
        delete modbusDevice;
        modbusDevice = nullptr;
    }

    //获取连接类型并强制转换为枚举
    auto type = static_cast<ModbusConnection>(index);
    //串口连接方式
    if (type == Serial) {
#if QT_CONFIG(modbus_serialport)
        modbusDevice = new QModbusRtuSerialServer(this);
#endif
    }
    //TCP连接方式
    else if (type == Tcp) {
        modbusDevice = new QModbusTcpServer(this);
        if (ui->portEdit->text().isEmpty())
            ui->portEdit->setText(QLatin1String("127.0.0.1:502"));
    }
    //若类型为serial时,启用仅监听按钮,否则禁用
    ui->listenOnlyBox->setEnabled(type == Serial);

    //如果对象为空,则提示错误信息
    if (!modbusDevice) {
        ui->connectButton->setDisabled(true);
        statusBar()->showMessage(tr("Could not create Modbus server."), 5000);
    } else {
        //对象创建成功后,初始化各个数据区的参数设置
        QModbusDataUnitMap reg;
        reg.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, 10 });
        reg.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, 10 });
        reg.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, 10 });
        reg.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 10 });

        modbusDevice->setMap(reg);

        connect(modbusDevice, &QModbusServer::dataWritten,
                this, &MainWindow::updateWidgets);
        connect(modbusDevice, &QModbusServer::stateChanged,
                this, &MainWindow::onStateChanged);
        connect(modbusDevice, &QModbusServer::errorOccurred,
                this, &MainWindow::handleDeviceError);

        connect(ui->listenOnlyBox, &QCheckBox::toggled, this, [this](bool toggled) {
            if (modbusDevice)
                modbusDevice->setValue(QModbusServer::ListenOnlyMode, toggled);
        });
        emit ui->listenOnlyBox->toggled(ui->listenOnlyBox->isChecked());
        connect(ui->setBusyBox, &QCheckBox::toggled, this, [this](bool toggled) {
            if (modbusDevice)
                modbusDevice->setValue(QModbusServer::DeviceBusy, toggled ? 0xffff : 0x0000);
        });
        emit ui->setBusyBox->toggled(ui->setBusyBox->isChecked());

        setupDeviceData();
    }
}

//处理设备错误
void MainWindow::handleDeviceError(QModbusDevice::Error newError)
{
    if (newError == QModbusDevice::NoError || !modbusDevice)
        return;

    statusBar()->showMessage(modbusDevice->errorString(), 5000);
}


//连接客户端
void MainWindow::onConnectButtonClicked()
{
    //是否处于未连接状态
    bool intendToConnect = (modbusDevice->state() == QModbusDevice::UnconnectedState);

    statusBar()->clearMessage();

    //按照不同的连接方式创建不同的对象
    if (intendToConnect) {
        //若为serial连接方式,则设置相关参数
        if (static_cast<ModbusConnection>(ui->connectType->currentIndex()) == Serial) {
            modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,
                ui->portEdit->text());
#if QT_CONFIG(modbus_serialport)
            modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,
                m_settingsDialog->settings().parity);
            modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
                m_settingsDialog->settings().baud);
            modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,
                m_settingsDialog->settings().dataBits);
            modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,
                m_settingsDialog->settings().stopBits);
#endif
        }
        //拖尾TCP方式,则设置其IP和端口
        else {
            const QUrl url = QUrl::fromUserInput(ui->portEdit->text());
            modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, url.port());
            modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, url.host());
        }
        //设置服务器地址
        modbusDevice->setServerAddress(ui->serverEdit->text().toInt());
        if (!modbusDevice->connectDevice()) {
            statusBar()->showMessage(tr("Connect failed: ") + modbusDevice->errorString(), 5000);
        } else {
            ui->actionConnect->setEnabled(false);
            ui->actionDisconnect->setEnabled(true);
        }
    } else {
        modbusDevice->disconnectDevice();
        ui->actionConnect->setEnabled(true);
        ui->actionDisconnect->setEnabled(false);
    }
}

//连接状态检测
void MainWindow::onStateChanged(int state)
{
    bool connected = (state != QModbusDevice::UnconnectedState);
    ui->actionConnect->setEnabled(!connected);
    ui->actionDisconnect->setEnabled(connected);

    if (state == QModbusDevice::UnconnectedState)
        ui->connectButton->setText(tr("Connect"));
    else if (state == QModbusDevice::ConnectedState)
        ui->connectButton->setText(tr("Disconnect"));
}

//线圈变化处理函数
void MainWindow::coilChanged(int id)
{
    QAbstractButton *button = coilButtons.button(id);
    bitChanged(id, QModbusDataUnit::Coils, button->isChecked());
}

//离散输入量处理函数
void MainWindow::discreteInputChanged(int id)
{
    QAbstractButton *button = discreteButtons.button(id);
    bitChanged(id, QModbusDataUnit::DiscreteInputs, button->isChecked());
}

//bit位处理函数
void MainWindow::bitChanged(int id, QModbusDataUnit::RegisterType table, bool value)
{
    if (!modbusDevice)
        return;

    if (!modbusDevice->setData(table, quint16(id), value))
        statusBar()->showMessage(tr("Could not set data: ") + modbusDevice->errorString(), 5000);
}

//设置寄存器
void MainWindow::setRegister(const QString &value)
{
    if (!modbusDevice)
        return;

    const QString objectName = QObject::sender()->objectName();
    if (registers.contains(objectName)) {
        bool ok = true;
        const quint16 id = quint16(QObject::sender()->property("ID").toUInt());
        if (objectName.startsWith(QStringLiteral("inReg")))
            ok = modbusDevice->setData(QModbusDataUnit::InputRegisters, id, value.toUShort(&ok, 16));
        else if (objectName.startsWith(QStringLiteral("holdReg")))
            ok = modbusDevice->setData(QModbusDataUnit::HoldingRegisters, id, value.toUShort(&ok, 16));

        if (!ok)
            statusBar()->showMessage(tr("Could not set register: ") + modbusDevice->errorString(),
                                     5000);
    }
}

//更新窗口部件
void MainWindow::updateWidgets(QModbusDataUnit::RegisterType table, int address, int size)
{
    for (int i = 0; i < size; ++i) {
        quint16 value;
        QString text;
        switch (table) {
        case QModbusDataUnit::Coils:
            modbusDevice->data(QModbusDataUnit::Coils, quint16(address + i), &value);
            coilButtons.button(address + i)->setChecked(value);
            break;
        case QModbusDataUnit::HoldingRegisters:
            modbusDevice->data(QModbusDataUnit::HoldingRegisters, quint16(address + i), &value);
            registers.value(QStringLiteral("holdReg_%1").arg(address + i))->setText(text
                .setNum(value, 16));
            break;
        default:
            break;
        }
    }
}



//设置设备数据
void MainWindow::setupDeviceData()
{
    if (!modbusDevice)
        return;

    //遍历所有bit位信息写入寄存器
    for (quint16 i = 0; i < coilButtons.buttons().count(); ++i)
        modbusDevice->setData(QModbusDataUnit::Coils, i, coilButtons.button(i)->isChecked());

    for (quint16 i = 0; i < discreteButtons.buttons().count(); ++i) {
        modbusDevice->setData(QModbusDataUnit::DiscreteInputs, i,
            discreteButtons.button(i)->isChecked());
    }

    bool ok;
    for (QLineEdit *widget : qAsConst(registers)) {
        if (widget->objectName().startsWith(QStringLiteral("inReg"))) {
            modbusDevice->setData(QModbusDataUnit::InputRegisters, quint16(widget->property("ID").toUInt()),
                widget->text().toUShort(&ok, 16));
        } else if (widget->objectName().startsWith(QStringLiteral("holdReg"))) {
            modbusDevice->setData(QModbusDataUnit::HoldingRegisters, quint16(widget->property("ID").toUInt()),
                widget->text().toUShort(&ok, 16));
        }
    }
}

//设置窗口容器
void MainWindow::setupWidgetContainers()
{
    coilButtons.setExclusive(false);
    discreteButtons.setExclusive(false);

    QRegularExpression regexp(QStringLiteral("coils_(?<ID>\\d+)"));
    const QList<QCheckBox *> coils = findChildren<QCheckBox *>(regexp);
    for (QCheckBox *cbx : coils)
        coilButtons.addButton(cbx, regexp.match(cbx->objectName()).captured("ID").toInt());
    connect(&coilButtons, &QButtonGroup::idClicked, this, &MainWindow::coilChanged);

    regexp.setPattern(QStringLiteral("disc_(?<ID>\\d+)"));
    const QList<QCheckBox *> discs = findChildren<QCheckBox *>(regexp);
    for (QCheckBox *cbx : discs)
        discreteButtons.addButton(cbx, regexp.match(cbx->objectName()).captured("ID").toInt());
    connect(&discreteButtons, &QButtonGroup::idClicked, this, &MainWindow::discreteInputChanged);

    regexp.setPattern(QLatin1String("(in|hold)Reg_(?<ID>\\d+)"));
    const QList<QLineEdit *> qle = findChildren<QLineEdit *>(regexp);
    for (QLineEdit *lineEdit : qle) {
        registers.insert(lineEdit->objectName(), lineEdit);
        lineEdit->setProperty("ID", regexp.match(lineEdit->objectName()).captured("ID").toInt());
        lineEdit->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9a-f]{0,4}"),
            QRegularExpression::CaseInsensitiveOption), this));
        connect(lineEdit, &QLineEdit::textChanged, this, &MainWindow::setRegister);
    }
}

running result:

Complete code download link:

https://download.csdn.net/download/XiaoWang_csdn/87616287

Next blog:

https://blog.csdn.net/XiaoWang_csdn/article/details/129789509

 Previous blog:

Qt6 Tutorial Three (14) Serial Communication_Tossing Ape King Shen Bing’s Blog-CSDN Blog

Je suppose que tu aimes

Origine blog.csdn.net/XiaoWang_csdn/article/details/129778075
conseillé
Classement