赞
踩
Modbus诞生于1979年 莫迪康公司 后来被施耐德电气公司收购。Modbus提供通用语言用于彼此通信的设备和设备。
Modbus已经成为工业领域通信协议的业界标准,并且现在是工业电子设备之间常用的连接方式。Modbus作为目前工业领域应用最广泛的协议。
Modbus优点:
Modbus通信过程:
Modbus存储区读写
Modbus协议类型
Modbus协议类型常见的有几下四种:
Modbus-TCP:支持网口,把MODBUS作为应用层协议,TCP/IP作为下层协议。并且不需要从从机地址,而是需要MBAP报文头,因为TCP本身就具有校验差错的能力故该协议不需要差错校验。
Modbus协议的三种传输模式
Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。
Modbus的4种操作对象
Modbus的4种操作对象分别是线圈Coils、离散输入DiscreteInputs、输入寄存器InputRegisters、保持寄存器HoldingRegisters。四种操作对象的读写权限如下:
线圈:PLC的输出位,开关量,在MOdbus中可读可写;
离散输入:PLC的输入位,开关量,在Modbus中只读;
输入寄存器:PLC中只能从模拟量输入端改变的寄存器,在MODBUS中只读;
保持寄存器:PLC中用于输出模拟量信号的寄存器,在MODBUS中可读可写;
Modbus协议是一个master/slave架构的协议。有一个节点是master节点,其他使用Modbus协议参与通信的节点是slave节点。每一个slave设备都有一个唯一的地址。在串行和MB+网络中,只有被指定为主节点的节点可以启动一个命令(在以太网上,任何一个设备都能发送一个Modbus命令,但是通常也只有一个主节点设备启动指令)。一般都是上位机做主站,PLC做从站。
Modbus TCP/IP协议格式
如上图所示,报文主要分为两部分,分别是协议头(MBAP Header)和PDU。PDU 又包含功能码(Function code)和数据(Data)两部分。
接下来我们直接来看看Qt官方的Modbus-tcp实例:实现主从站的通信功能;
- //Qt中几个常用的串口modbus类
-
- QModbusRtuSerialSlave //modbus串口通信方式下的服务器类
- QModbusRtuSerialMaster //串口通信方式下的客户端类
- QModbusServer // QModbusServer类接收和处理modbus的请求。
- QModbusDataUnit //存储接收和发送数据的类,数据类型为1bit和16bit
- QModbusReply //客户端访问服务器后得到的回复(如客户端读服务器数据时包含数据信息)
主站关键代码:
-
- #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);
- }
-
-
-
从站关键代码:
-
- #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);
- }
- }
运行效果:
完整代码下载链接:
https://download.csdn.net/download/XiaoWang_csdn/87616287
下一篇博客:
https://blog.csdn.net/XiaoWang_csdn/article/details/129789509
上一篇博客:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。