当前位置:   article > 正文

Qt6教程之三(15) Modbus通信_qt modbus

qt modbus

Modbus协议简介

Modbus诞生于1979年 莫迪康公司 后来被施耐德电气公司收购。Modbus提供通用语言用于彼此通信的设备和设备。
Modbus已经成为工业领域通信协议的业界标准,并且现在是工业电子设备之间常用的连接方式。Modbus作为目前工业领域应用最广泛的协议。

Modbus优点:

  1. Modbus协议标准开放、公开发表且无版权要求;
  2. Modbus协议支持多种电气接口,包括RS232、RS485、TCP/IP等,还可以在各种介质上传输,如双绞线、光纤、红外、无线等;
  3. Modbus协议消息帧格式简单、紧凑、通俗易懂。用户理解和使用简单,厂商容易开发和集成,方便形成工业控制网络。

Modbus通信过程:

  1. Modbus是一主多从的通信协议: Modbus通信中只有一个设备可以发送请求。
  2. 主机在同一时间内只能向一个从机发送请求,总线上每次只有一个数据进行传输,即主机发送,从机应答,主机不发送,总线上就没有数据通信。
  3. 从机不会自己发送消息给主站,只能回复从主机发送的消息请求。
  4. Modbus并没有忙机制判断,需要通过软件的方式来判断是否从机是否正常接收。

Modbus存储区读写

  1. Modbus协议规定了4个存储区 分别是0 1 3 4区 其中0区和4区是可读可写,1区和3区是只读。
  2. 主机向从机获取数据时,只需要告诉从机数据的起始地址,还有获取多少字节的数据,实际上就是对从机设备对应的实际存储空间进行读写。

Modbus协议类型

Modbus协议类型常见的有几下四种:

  1. Modbus-RTU:  只支持串口, 使用紧凑的十六进制表示数据,后续的命令/数据带有循环冗余校验的校验和。RTU模式比较常用。
  2. Modbus-ASCII:只支持串口, 采用Ascii码表示数据,每8Bit 字节作为两个ASCII字符发送,采用纵向冗余校验的校验和;
  3. Modbus-TCP:支持网口,把MODBUS作为应用层协议,TCP/IP作为下层协议。并且不需要从从机地址,而是需要MBAP报文头,因为TCP本身就具有校验差错的能力故该协议不需要差错校验

  4. ModbusPlus:是一种典型的令牌环网,完整定义了通讯协议、网络结构、连接电缆(或者光缆)以及安装工具等方面的性能指标。

Modbus协议的三种传输模式

Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。

  1. ASCII:  采用LRC校验;
  2. RTU(远程终端控制系统): 采用16 位CRC校验;
  3. TCP/IP:不使用校验,因为TCP协议是一个面向连接的可靠协议;


Modbus的4种操作对象

Modbus的4种操作对象分别是线圈Coils、离散输入DiscreteInputs、输入寄存器InputRegisters、保持寄存器HoldingRegisters。四种操作对象的读写权限如下:
线圈:PLC的输出位,开关量,在MOdbus中可读可写;
离散输入:PLC的输入位,开关量,在Modbus中只读;
输入寄存器:PLC中只能从模拟量输入端改变的寄存器,在MODBUS中只读;
保持寄存器:PLC中用于输出模拟量信号的寄存器,在MODBUS中可读可写;

二 基于ModBus-TCP的Qt案例示范


Modbus协议是一个master/slave架构的协议。有一个节点是master节点,其他使用Modbus协议参与通信的节点是slave节点。每一个slave设备都有一个唯一的地址。在串行和MB+网络中,只有被指定为主节点的节点可以启动一个命令(在以太网上,任何一个设备都能发送一个Modbus命令,但是通常也只有一个主节点设备启动指令)。一般都是上位机做主站,PLC做从站。


Modbus TCP/IP协议格式

如上图所示,报文主要分为两部分,分别是协议头(MBAP Header)和PDU。PDU 又包含功能码(Function code)和数据(Data)两部分。

接下来我们直接来看看Qt官方的Modbus-tcp实例:实现主从站的通信功能;

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

主站关键代码:

  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. #include "settingsdialog.h"
  4. #include "writeregistermodel.h"
  5. #include <QModbusTcpClient>
  6. #include <QModbusRtuSerialClient>
  7. #include <QStandardItemModel>
  8. #include <QStatusBar>
  9. #include <QUrl>
  10. enum ModbusConnection {
  11. Serial,
  12. Tcp
  13. };
  14. MainWindow::MainWindow(QWidget *parent)
  15. : QMainWindow(parent)
  16. , ui(new Ui::MainWindow)
  17. {
  18. ui->setupUi(this);
  19. //初始化设置对话框类
  20. m_settingsDialog = new SettingsDialog(this);
  21. //初始化菜单操作
  22. initActions();
  23. //初始化写入寄存器模型
  24. writeModel = new WriteRegisterModel(this);
  25. //设置地址起始位置
  26. writeModel->setStartAddress(ui->writeAddress->value());
  27. //设置写入的地址位数量
  28. writeModel->setNumberOfValues(ui->writeSize->currentText());
  29. //把要写入的数据使用模型的方式,给视图展示出来
  30. ui->writeValueTable->setModel(writeModel);
  31. //设置隐藏的列为第二列
  32. ui->writeValueTable->hideColumn(2);
  33. //当视图有更新时,同步刷新列表
  34. connect(writeModel, &WriteRegisterModel::updateViewport,
  35. ui->writeValueTable->viewport(), QOverload<>::of(&QWidget::update));
  36. //添加写入数据单元的类型: 主要有五种,分别是 无效、线圈、离散输入、输入寄存器、保持寄存器
  37. ui->writeTable->addItem(tr("线圈"), QModbusDataUnit::Coils);
  38. ui->writeTable->addItem(tr("离散输入"), QModbusDataUnit::DiscreteInputs);
  39. ui->writeTable->addItem(tr("输入寄存器"), QModbusDataUnit::InputRegisters);
  40. ui->writeTable->addItem(tr("保持寄存器"), QModbusDataUnit::HoldingRegisters);
  41. #if QT_CONFIG(modbus_serialport)
  42. //设置连接类型,有串口和TCP两种方式
  43. ui->connectType->setCurrentIndex(0);
  44. //使用当前选择的连接方式进行与服务器端连接
  45. onConnectTypeChanged(0);
  46. #else
  47. // 锁定串行端口选项
  48. ui->connectType->setCurrentIndex(1);
  49. //使用当前选择的连接方式进行与服务器端连接
  50. onConnectTypeChanged(1);
  51. //禁用选择框
  52. ui->connectType->setEnabled(false);
  53. #endif
  54. //创建标准项模型对象,一共10行一列
  55. auto model = new QStandardItemModel(10, 1, this);
  56. //循环添加项到标准模型里面
  57. for (int i = 0; i < 10; ++i)
  58. model->setItem(i, new QStandardItem(QStringLiteral("%1").arg(i + 1)));
  59. ui->writeSize->setModel(model);
  60. ui->writeSize->setCurrentText("10");
  61. //把写入位变化及时更新至写入注册模型中
  62. connect(ui->writeSize, &QComboBox::currentTextChanged,
  63. writeModel, &WriteRegisterModel::setNumberOfValues);
  64. //把开始地址更新至写入模型中
  65. connect(ui->writeAddress, &QSpinBox::valueChanged, writeModel, &WriteRegisterModel::setStartAddress);
  66. //把起始位之前的位选择功能禁用
  67. connect(ui->writeAddress, &QSpinBox::valueChanged, this, [this, model](int i) {
  68. int lastPossibleIndex = 0;
  69. const int currentIndex = ui->writeSize->currentIndex();
  70. for (int ii = 0; ii < 10; ++ii) {
  71. if (ii < (10 - i)) {
  72. lastPossibleIndex = ii;
  73. model->item(ii)->setEnabled(true);
  74. } else {
  75. model->item(ii)->setEnabled(false);
  76. }
  77. }
  78. if (currentIndex > lastPossibleIndex)
  79. ui->writeSize->setCurrentIndex(lastPossibleIndex);
  80. });
  81. }
  82. MainWindow::~MainWindow()
  83. {
  84. if (modbusDevice)
  85. modbusDevice->disconnectDevice();
  86. delete modbusDevice;
  87. delete ui;
  88. }
  89. //完成每个菜单的响应处理,即绑定菜单的信号与槽,最终在槽函数里面完成菜单的响应逻辑
  90. void MainWindow::initActions()
  91. {
  92. ui->actionConnect->setEnabled(true);
  93. ui->actionDisconnect->setEnabled(false);
  94. ui->actionExit->setEnabled(true);
  95. ui->actionOptions->setEnabled(true);
  96. connect(ui->connectButton, &QPushButton::clicked,
  97. this, &MainWindow::onConnectButtonClicked);
  98. connect(ui->actionConnect, &QAction::triggered,
  99. this, &MainWindow::onConnectButtonClicked);
  100. connect(ui->actionDisconnect, &QAction::triggered,
  101. this, &MainWindow::onConnectButtonClicked);
  102. connect(ui->readButton, &QPushButton::clicked,
  103. this, &MainWindow::onReadButtonClicked);
  104. connect(ui->writeButton, &QPushButton::clicked,
  105. this, &MainWindow::onWriteButtonClicked);
  106. connect(ui->readWriteButton, &QPushButton::clicked,
  107. this, &MainWindow::onReadWriteButtonClicked);
  108. connect(ui->connectType, &QComboBox::currentIndexChanged,
  109. this, &MainWindow::onConnectTypeChanged);
  110. connect(ui->writeTable, &QComboBox::currentIndexChanged,
  111. this, &MainWindow::onWriteTableChanged);
  112. connect(ui->actionExit, &QAction::triggered, this, &QMainWindow::close);
  113. connect(ui->actionOptions, &QAction::triggered, m_settingsDialog, &QDialog::show);
  114. }
  115. //检测连接类型
  116. void MainWindow::onConnectTypeChanged(int index)
  117. {
  118. //如果客户端已连接,则断开
  119. if (modbusDevice) {
  120. modbusDevice->disconnectDevice();
  121. delete modbusDevice;
  122. modbusDevice = nullptr;
  123. }
  124. //转换连接类型的数字为当前定义的枚举,即Serial或Tcp
  125. auto type = static_cast<ModbusConnection>(index);
  126. //如果是串口
  127. if (type == Serial) {
  128. #if QT_CONFIG(modbus_serialport)
  129. //初始化QModbusRtuSerialClient类,它表示Modbus客户端,并且使用串行总线与Modbus服务器进行通信
  130. modbusDevice = new QModbusRtuSerialClient(this);
  131. #endif
  132. }
  133. //如果是TCP
  134. else if (type == Tcp) {
  135. //初始化QModbusTcpClient类,它是Modbus-TCP客户端设备的接口类
  136. modbusDevice = new QModbusTcpClient(this);
  137. //如果端口设置没有设置,则把默认值设置为127.0.0.1:502
  138. if (ui->portEdit->text().isEmpty())
  139. ui->portEdit->setText(QLatin1String("127.0.0.1:502"));
  140. }
  141. connect(modbusDevice, &QModbusClient::errorOccurred, [this](QModbusDevice::Error) {
  142. statusBar()->showMessage(modbusDevice->errorString(), 5000);
  143. });
  144. if (!modbusDevice) {
  145. ui->connectButton->setDisabled(true);
  146. statusBar()->showMessage(tr("无法创建Modbus客户端."), 5000);
  147. } else {
  148. connect(modbusDevice, &QModbusClient::stateChanged,
  149. this, &MainWindow::onModbusStateChanged);
  150. }
  151. }
  152. //配置一些主站相关的参数
  153. void MainWindow::onConnectButtonClicked()
  154. {
  155. if (!modbusDevice)
  156. return;
  157. statusBar()->clearMessage();
  158. if (modbusDevice->state() != QModbusDevice::ConnectedState) {
  159. if (static_cast<ModbusConnection>(ui->connectType->currentIndex()) == Serial) {
  160. modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,
  161. ui->portEdit->text());
  162. #if QT_CONFIG(modbus_serialport)
  163. //设置串口的几个关键参数:parity,baud,dataBits,stopBits
  164. modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,
  165. m_settingsDialog->settings().parity);
  166. modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
  167. m_settingsDialog->settings().baud);
  168. modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,
  169. m_settingsDialog->settings().dataBits);
  170. modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,
  171. m_settingsDialog->settings().stopBits);
  172. #endif
  173. } else {
  174. //设置连接的IP和端口
  175. const QUrl url = QUrl::fromUserInput(ui->portEdit->text());
  176. modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, url.port());
  177. modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, url.host());
  178. }
  179. modbusDevice->setTimeout(m_settingsDialog->settings().responseTime);
  180. modbusDevice->setNumberOfRetries(m_settingsDialog->settings().numberOfRetries);
  181. if (!modbusDevice->connectDevice()) {
  182. statusBar()->showMessage(tr("连接失败: ") + modbusDevice->errorString(), 5000);
  183. } else {
  184. ui->actionConnect->setEnabled(false);
  185. ui->actionDisconnect->setEnabled(true);
  186. }
  187. } else {
  188. modbusDevice->disconnectDevice();
  189. ui->actionConnect->setEnabled(true);
  190. ui->actionDisconnect->setEnabled(false);
  191. }
  192. }
  193. //监听连接状态
  194. void MainWindow::onModbusStateChanged(int state)
  195. {
  196. bool connected = (state != QModbusDevice::UnconnectedState);
  197. ui->actionConnect->setEnabled(!connected);
  198. ui->actionDisconnect->setEnabled(connected);
  199. if (state == QModbusDevice::UnconnectedState)
  200. ui->connectButton->setText(tr("连接"));
  201. else if (state == QModbusDevice::ConnectedState)
  202. ui->connectButton->setText(tr("断开"));
  203. }
  204. //读取从站(服务器端)的数据
  205. void MainWindow::onReadButtonClicked()
  206. {
  207. if (!modbusDevice)
  208. return;
  209. ui->readValue->clear();
  210. statusBar()->clearMessage();
  211. //向服务器请求读取数据,参数含义为:数据单元和服务器地址
  212. if (auto *reply = modbusDevice->sendReadRequest(readRequest(), ui->serverEdit->value())) {
  213. //判断答复状态,若还没结束则读取请求过来的数据
  214. if (!reply->isFinished())
  215. //开始读取数据
  216. connect(reply, &QModbusReply::finished, this, &MainWindow::onReadReady);
  217. else
  218. delete reply; // 广播回复立即返回
  219. } else {
  220. statusBar()->showMessage(tr("读取错误: ") + modbusDevice->errorString(), 5000);
  221. }
  222. }
  223. //读取数据
  224. void MainWindow::onReadReady()
  225. {
  226. auto reply = qobject_cast<QModbusReply *>(sender());
  227. if (!reply)
  228. return;
  229. //没有错误则开始读取数据
  230. if (reply->error() == QModbusDevice::NoError) {
  231. //记录读取的数据
  232. const QModbusDataUnit unit = reply->result();
  233. //依次取出各个位的数据
  234. for (qsizetype i = 0, total = unit.valueCount(); i < total; ++i) {
  235. const QString entry = tr("地址: %1, 值: %2").
  236. arg(unit.startAddress() + i).
  237. arg(QString::number(unit.value(i),unit.registerType() <= QModbusDataUnit::Coils ? 10 : 16));
  238. //把每一个位的数据放入列表中显示出来,包括地址和值
  239. ui->readValue->addItem(entry);
  240. }
  241. //若通信异常,则显示对应的错误信息
  242. } else if (reply->error() == QModbusDevice::ProtocolError) {
  243. statusBar()->showMessage(tr("读取响应错误: %1 (Modbus异常: 0x%2)").
  244. arg(reply->errorString()).
  245. arg(reply->rawResult().exceptionCode(), -1, 16), 5000);
  246. } else {
  247. statusBar()->showMessage(tr("读取响应错误: %1 (编码: 0x%2)").
  248. arg(reply->errorString()).
  249. arg(reply->error(), -1, 16), 5000);
  250. }
  251. reply->deleteLater();
  252. }
  253. //写数据
  254. void MainWindow::onWriteButtonClicked()
  255. {
  256. if (!modbusDevice)
  257. return;
  258. statusBar()->clearMessage();
  259. //获取写入数据的包装类型
  260. QModbusDataUnit writeUnit = writeRequest();
  261. //获取需要写入的区域:线圈、保持寄存器
  262. QModbusDataUnit::RegisterType table = writeUnit.registerType();
  263. //writeUnit.valueCount()返回寄存器中数据单元的起始地址。
  264. for (qsizetype i = 0, total = writeUnit.valueCount(); i < total; ++i) {
  265. //如果写入的区域是线圈,则可以写入
  266. if (table == QModbusDataUnit::Coils)
  267. //写入线圈的0至结束地址
  268. writeUnit.setValue(i, writeModel->m_coils[i + writeUnit.startAddress()]);
  269. else
  270. //写入保持寄存器0至结束地址
  271. writeUnit.setValue(i, writeModel->m_holdingRegisters[i + writeUnit.startAddress()]);
  272. }
  273. //开始写入数据,参数为要写入的封装数据和服务器地址
  274. if (auto *reply = modbusDevice->sendWriteRequest(writeUnit, ui->serverEdit->value())) {
  275. //检测写入状态
  276. if (!reply->isFinished()) {
  277. connect(reply, &QModbusReply::finished, this, [this, reply]() {
  278. if (reply->error() == QModbusDevice::ProtocolError) {
  279. statusBar()->showMessage(tr("写入响应错误: %1 (Modbus异常: 0x%2)")
  280. .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16),
  281. 5000);
  282. } else if (reply->error() != QModbusDevice::NoError) {
  283. statusBar()->showMessage(tr("写入响应错误: %1 (编码: 0x%2)").
  284. arg(reply->errorString()).arg(reply->error(), -1, 16), 5000);
  285. }
  286. reply->deleteLater();
  287. });
  288. } else {
  289. // broadcast replies return immediately
  290. reply->deleteLater();
  291. }
  292. } else {
  293. statusBar()->showMessage(tr("写错误: ") + modbusDevice->errorString(), 5000);
  294. }
  295. }
  296. //读写数据: 只有保持寄存器支持同时读写
  297. void MainWindow::onReadWriteButtonClicked()
  298. {
  299. if (!modbusDevice)
  300. return;
  301. ui->readValue->clear();
  302. statusBar()->clearMessage();
  303. //写请求
  304. QModbusDataUnit writeUnit = writeRequest();
  305. QModbusDataUnit::RegisterType table = writeUnit.registerType();
  306. for (qsizetype i = 0, total = writeUnit.valueCount(); i < total; ++i) {
  307. if (table == QModbusDataUnit::Coils)
  308. writeUnit.setValue(i, writeModel->m_coils[i + writeUnit.startAddress()]);
  309. else
  310. writeUnit.setValue(i, writeModel->m_holdingRegisters[i + writeUnit.startAddress()]);
  311. }
  312. //读写请求
  313. if (auto *reply = modbusDevice->sendReadWriteRequest(readRequest(), writeUnit,
  314. ui->serverEdit->value())) {
  315. if (!reply->isFinished())
  316. connect(reply, &QModbusReply::finished, this, &MainWindow::onReadReady);
  317. else
  318. delete reply; // broadcast replies return immediately
  319. } else {
  320. statusBar()->showMessage(tr("读错误: ") + modbusDevice->errorString(), 5000);
  321. }
  322. }
  323. //检测写入区域的选择: 线圈、离散输入、输入寄存器、保持寄存器
  324. void MainWindow::onWriteTableChanged(int index)
  325. {
  326. //判断是线圈还是保持寄存器
  327. const bool coilsOrHolding = index == 0 || index == 3;
  328. //如果选择的是线圈或保持寄存器
  329. if (coilsOrHolding) {
  330. //隐藏第2、3列
  331. ui->writeValueTable->setColumnHidden(1, index != 0);
  332. ui->writeValueTable->setColumnHidden(2, index != 3);
  333. //根据列内容的大小调整第一列的大小。
  334. ui->writeValueTable->resizeColumnToContents(0);
  335. }
  336. //若写入区域为保持寄存器,则启用读写按钮
  337. ui->readWriteButton->setEnabled(index == 3);
  338. //若写入区域为线圈或保持寄存器,则启用读写按钮
  339. ui->writeButton->setEnabled(coilsOrHolding);
  340. //若写入区域为保持寄存器,则启用写入区域的所有字节输入框
  341. ui->writeGroupBox->setEnabled(coilsOrHolding);
  342. }
  343. //包装需要读取的数据
  344. QModbusDataUnit MainWindow::readRequest() const
  345. {
  346. //获取当前需要读取的区域:线圈、离散输入、输入寄存器、保持寄存器
  347. const auto table =
  348. static_cast<QModbusDataUnit::RegisterType>(ui->writeTable->currentData().toInt());
  349. int startAddress = ui->readAddress->value();
  350. Q_ASSERT(startAddress >= 0 && startAddress < 10);
  351. // 不要超过10个条目,在需要读取的字节数与有效数据的字节数之间获取较小的值
  352. quint16 numberOfEntries = qMin(ui->readSize->currentText().toUShort(), quint16(10 - startAddress));
  353. //返回需要读取的数据单元,参数的含义分别是要读取的区域、开始地址、读取的字节数
  354. return QModbusDataUnit(table, startAddress, numberOfEntries);
  355. }
  356. //包装写入的数据
  357. QModbusDataUnit MainWindow::writeRequest() const
  358. {
  359. //获取写入数据的区域类型:线圈、离散输入、输入寄存器、保持寄存器
  360. const auto table =
  361. static_cast<QModbusDataUnit::RegisterType>(ui->writeTable->currentData().toInt());
  362. //开始地址
  363. int startAddress = ui->writeAddress->value();
  364. Q_ASSERT(startAddress >= 0 && startAddress < 10);
  365. // 不要超过10个条目,在需要读取的字节数与有效数据的字节数之间获取较小的值
  366. quint16 numberOfEntries = qMin(ui->writeSize->currentText().toUShort(), quint16(10 - startAddress));
  367. //返回需要读取的数据单元,参数的含义分别是要读取的区域、开始地址、读取的字节数
  368. return QModbusDataUnit(table, startAddress, numberOfEntries);
  369. }

从站关键代码:

  1. #include "mainwindow.h"
  2. #include "settingsdialog.h"
  3. #include "ui_mainwindow.h"
  4. #include <QModbusRtuSerialServer>
  5. #include <QModbusTcpServer>
  6. #include <QRegularExpression>
  7. #include <QRegularExpressionValidator>
  8. #include <QStatusBar>
  9. #include <QUrl>
  10. //枚举连接类型
  11. enum ModbusConnection {
  12. Serial,
  13. Tcp
  14. };
  15. MainWindow::MainWindow(QWidget *parent)
  16. : QMainWindow(parent)
  17. , ui(new Ui::MainWindow)
  18. {
  19. ui->setupUi(this);
  20. setupWidgetContainers();
  21. //设置默认连接类型
  22. #if QT_CONFIG(modbus_serialport)
  23. ui->connectType->setCurrentIndex(0);
  24. onCurrentConnectTypeChanged(0);
  25. #else
  26. // 锁定串行端口选项,设置连接类型
  27. ui->connectType->setCurrentIndex(1);
  28. onCurrentConnectTypeChanged(1);
  29. ui->connectType->setEnabled(false);
  30. #endif
  31. m_settingsDialog = new SettingsDialog(this);
  32. initActions();
  33. }
  34. MainWindow::~MainWindow()
  35. {
  36. if (modbusDevice)
  37. modbusDevice->disconnectDevice();
  38. delete modbusDevice;
  39. delete ui;
  40. }
  41. //初始化菜单
  42. void MainWindow::initActions()
  43. {
  44. ui->actionConnect->setEnabled(true);
  45. ui->actionDisconnect->setEnabled(false);
  46. ui->actionExit->setEnabled(true);
  47. ui->actionOptions->setEnabled(true);
  48. connect(ui->connectButton, &QPushButton::clicked,
  49. this, &MainWindow::onConnectButtonClicked);
  50. connect(ui->actionConnect, &QAction::triggered,
  51. this, &MainWindow::onConnectButtonClicked);
  52. connect(ui->actionDisconnect, &QAction::triggered,
  53. this, &MainWindow::onConnectButtonClicked);
  54. connect(ui->connectType, &QComboBox::currentIndexChanged,
  55. this, &MainWindow::onCurrentConnectTypeChanged);
  56. connect(ui->actionExit, &QAction::triggered, this, &QMainWindow::close);
  57. connect(ui->actionOptions, &QAction::triggered, m_settingsDialog, &QDialog::show);
  58. }
  59. //类型变化检测,根据选中的不同连接类型创建不同的对象
  60. void MainWindow::onCurrentConnectTypeChanged(int index)
  61. {
  62. if (modbusDevice) {
  63. modbusDevice->disconnect();
  64. delete modbusDevice;
  65. modbusDevice = nullptr;
  66. }
  67. //获取连接类型并强制转换为枚举
  68. auto type = static_cast<ModbusConnection>(index);
  69. //串口连接方式
  70. if (type == Serial) {
  71. #if QT_CONFIG(modbus_serialport)
  72. modbusDevice = new QModbusRtuSerialServer(this);
  73. #endif
  74. }
  75. //TCP连接方式
  76. else if (type == Tcp) {
  77. modbusDevice = new QModbusTcpServer(this);
  78. if (ui->portEdit->text().isEmpty())
  79. ui->portEdit->setText(QLatin1String("127.0.0.1:502"));
  80. }
  81. //若类型为serial时,启用仅监听按钮,否则禁用
  82. ui->listenOnlyBox->setEnabled(type == Serial);
  83. //如果对象为空,则提示错误信息
  84. if (!modbusDevice) {
  85. ui->connectButton->setDisabled(true);
  86. statusBar()->showMessage(tr("Could not create Modbus server."), 5000);
  87. } else {
  88. //对象创建成功后,初始化各个数据区的参数设置
  89. QModbusDataUnitMap reg;
  90. reg.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, 10 });
  91. reg.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, 10 });
  92. reg.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, 10 });
  93. reg.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 10 });
  94. modbusDevice->setMap(reg);
  95. connect(modbusDevice, &QModbusServer::dataWritten,
  96. this, &MainWindow::updateWidgets);
  97. connect(modbusDevice, &QModbusServer::stateChanged,
  98. this, &MainWindow::onStateChanged);
  99. connect(modbusDevice, &QModbusServer::errorOccurred,
  100. this, &MainWindow::handleDeviceError);
  101. connect(ui->listenOnlyBox, &QCheckBox::toggled, this, [this](bool toggled) {
  102. if (modbusDevice)
  103. modbusDevice->setValue(QModbusServer::ListenOnlyMode, toggled);
  104. });
  105. emit ui->listenOnlyBox->toggled(ui->listenOnlyBox->isChecked());
  106. connect(ui->setBusyBox, &QCheckBox::toggled, this, [this](bool toggled) {
  107. if (modbusDevice)
  108. modbusDevice->setValue(QModbusServer::DeviceBusy, toggled ? 0xffff : 0x0000);
  109. });
  110. emit ui->setBusyBox->toggled(ui->setBusyBox->isChecked());
  111. setupDeviceData();
  112. }
  113. }
  114. //处理设备错误
  115. void MainWindow::handleDeviceError(QModbusDevice::Error newError)
  116. {
  117. if (newError == QModbusDevice::NoError || !modbusDevice)
  118. return;
  119. statusBar()->showMessage(modbusDevice->errorString(), 5000);
  120. }
  121. //连接客户端
  122. void MainWindow::onConnectButtonClicked()
  123. {
  124. //是否处于未连接状态
  125. bool intendToConnect = (modbusDevice->state() == QModbusDevice::UnconnectedState);
  126. statusBar()->clearMessage();
  127. //按照不同的连接方式创建不同的对象
  128. if (intendToConnect) {
  129. //若为serial连接方式,则设置相关参数
  130. if (static_cast<ModbusConnection>(ui->connectType->currentIndex()) == Serial) {
  131. modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,
  132. ui->portEdit->text());
  133. #if QT_CONFIG(modbus_serialport)
  134. modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,
  135. m_settingsDialog->settings().parity);
  136. modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
  137. m_settingsDialog->settings().baud);
  138. modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,
  139. m_settingsDialog->settings().dataBits);
  140. modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,
  141. m_settingsDialog->settings().stopBits);
  142. #endif
  143. }
  144. //拖尾TCP方式,则设置其IP和端口
  145. else {
  146. const QUrl url = QUrl::fromUserInput(ui->portEdit->text());
  147. modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, url.port());
  148. modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, url.host());
  149. }
  150. //设置服务器地址
  151. modbusDevice->setServerAddress(ui->serverEdit->text().toInt());
  152. if (!modbusDevice->connectDevice()) {
  153. statusBar()->showMessage(tr("Connect failed: ") + modbusDevice->errorString(), 5000);
  154. } else {
  155. ui->actionConnect->setEnabled(false);
  156. ui->actionDisconnect->setEnabled(true);
  157. }
  158. } else {
  159. modbusDevice->disconnectDevice();
  160. ui->actionConnect->setEnabled(true);
  161. ui->actionDisconnect->setEnabled(false);
  162. }
  163. }
  164. //连接状态检测
  165. void MainWindow::onStateChanged(int state)
  166. {
  167. bool connected = (state != QModbusDevice::UnconnectedState);
  168. ui->actionConnect->setEnabled(!connected);
  169. ui->actionDisconnect->setEnabled(connected);
  170. if (state == QModbusDevice::UnconnectedState)
  171. ui->connectButton->setText(tr("Connect"));
  172. else if (state == QModbusDevice::ConnectedState)
  173. ui->connectButton->setText(tr("Disconnect"));
  174. }
  175. //线圈变化处理函数
  176. void MainWindow::coilChanged(int id)
  177. {
  178. QAbstractButton *button = coilButtons.button(id);
  179. bitChanged(id, QModbusDataUnit::Coils, button->isChecked());
  180. }
  181. //离散输入量处理函数
  182. void MainWindow::discreteInputChanged(int id)
  183. {
  184. QAbstractButton *button = discreteButtons.button(id);
  185. bitChanged(id, QModbusDataUnit::DiscreteInputs, button->isChecked());
  186. }
  187. //bit位处理函数
  188. void MainWindow::bitChanged(int id, QModbusDataUnit::RegisterType table, bool value)
  189. {
  190. if (!modbusDevice)
  191. return;
  192. if (!modbusDevice->setData(table, quint16(id), value))
  193. statusBar()->showMessage(tr("Could not set data: ") + modbusDevice->errorString(), 5000);
  194. }
  195. //设置寄存器
  196. void MainWindow::setRegister(const QString &value)
  197. {
  198. if (!modbusDevice)
  199. return;
  200. const QString objectName = QObject::sender()->objectName();
  201. if (registers.contains(objectName)) {
  202. bool ok = true;
  203. const quint16 id = quint16(QObject::sender()->property("ID").toUInt());
  204. if (objectName.startsWith(QStringLiteral("inReg")))
  205. ok = modbusDevice->setData(QModbusDataUnit::InputRegisters, id, value.toUShort(&ok, 16));
  206. else if (objectName.startsWith(QStringLiteral("holdReg")))
  207. ok = modbusDevice->setData(QModbusDataUnit::HoldingRegisters, id, value.toUShort(&ok, 16));
  208. if (!ok)
  209. statusBar()->showMessage(tr("Could not set register: ") + modbusDevice->errorString(),
  210. 5000);
  211. }
  212. }
  213. //更新窗口部件
  214. void MainWindow::updateWidgets(QModbusDataUnit::RegisterType table, int address, int size)
  215. {
  216. for (int i = 0; i < size; ++i) {
  217. quint16 value;
  218. QString text;
  219. switch (table) {
  220. case QModbusDataUnit::Coils:
  221. modbusDevice->data(QModbusDataUnit::Coils, quint16(address + i), &value);
  222. coilButtons.button(address + i)->setChecked(value);
  223. break;
  224. case QModbusDataUnit::HoldingRegisters:
  225. modbusDevice->data(QModbusDataUnit::HoldingRegisters, quint16(address + i), &value);
  226. registers.value(QStringLiteral("holdReg_%1").arg(address + i))->setText(text
  227. .setNum(value, 16));
  228. break;
  229. default:
  230. break;
  231. }
  232. }
  233. }
  234. //设置设备数据
  235. void MainWindow::setupDeviceData()
  236. {
  237. if (!modbusDevice)
  238. return;
  239. //遍历所有bit位信息写入寄存器
  240. for (quint16 i = 0; i < coilButtons.buttons().count(); ++i)
  241. modbusDevice->setData(QModbusDataUnit::Coils, i, coilButtons.button(i)->isChecked());
  242. for (quint16 i = 0; i < discreteButtons.buttons().count(); ++i) {
  243. modbusDevice->setData(QModbusDataUnit::DiscreteInputs, i,
  244. discreteButtons.button(i)->isChecked());
  245. }
  246. bool ok;
  247. for (QLineEdit *widget : qAsConst(registers)) {
  248. if (widget->objectName().startsWith(QStringLiteral("inReg"))) {
  249. modbusDevice->setData(QModbusDataUnit::InputRegisters, quint16(widget->property("ID").toUInt()),
  250. widget->text().toUShort(&ok, 16));
  251. } else if (widget->objectName().startsWith(QStringLiteral("holdReg"))) {
  252. modbusDevice->setData(QModbusDataUnit::HoldingRegisters, quint16(widget->property("ID").toUInt()),
  253. widget->text().toUShort(&ok, 16));
  254. }
  255. }
  256. }
  257. //设置窗口容器
  258. void MainWindow::setupWidgetContainers()
  259. {
  260. coilButtons.setExclusive(false);
  261. discreteButtons.setExclusive(false);
  262. QRegularExpression regexp(QStringLiteral("coils_(?<ID>\\d+)"));
  263. const QList<QCheckBox *> coils = findChildren<QCheckBox *>(regexp);
  264. for (QCheckBox *cbx : coils)
  265. coilButtons.addButton(cbx, regexp.match(cbx->objectName()).captured("ID").toInt());
  266. connect(&coilButtons, &QButtonGroup::idClicked, this, &MainWindow::coilChanged);
  267. regexp.setPattern(QStringLiteral("disc_(?<ID>\\d+)"));
  268. const QList<QCheckBox *> discs = findChildren<QCheckBox *>(regexp);
  269. for (QCheckBox *cbx : discs)
  270. discreteButtons.addButton(cbx, regexp.match(cbx->objectName()).captured("ID").toInt());
  271. connect(&discreteButtons, &QButtonGroup::idClicked, this, &MainWindow::discreteInputChanged);
  272. regexp.setPattern(QLatin1String("(in|hold)Reg_(?<ID>\\d+)"));
  273. const QList<QLineEdit *> qle = findChildren<QLineEdit *>(regexp);
  274. for (QLineEdit *lineEdit : qle) {
  275. registers.insert(lineEdit->objectName(), lineEdit);
  276. lineEdit->setProperty("ID", regexp.match(lineEdit->objectName()).captured("ID").toInt());
  277. lineEdit->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9a-f]{0,4}"),
  278. QRegularExpression::CaseInsensitiveOption), this));
  279. connect(lineEdit, &QLineEdit::textChanged, this, &MainWindow::setRegister);
  280. }
  281. }

运行效果:

完整代码下载链接:

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

下一篇博客:

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

 上一篇博客:

Qt6教程之三(14) 串口通信_折腾猿王申兵的博客-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/人工智能uu/article/detail/971512
推荐阅读
相关标签
  

闽ICP备14008679号