|
- /*******************************
- * Copyright (C) 2025.
- *
- * File Name: widget.cpp
- * Description: 显示与交互界面配置
- * Others:
- * Version: 1.0
- * Author: lipengpeng
- * Date: 2025-7-23
- *******************************/
- #include "widget.h"
- #include "ui_widget.h"
- #include <QSerialPortInfo>
- #include <QDebug>
- #include <QSerialPort>
- #include <QByteArray>
- #include <QString>
- #include <QVector>
- #include <QMessageBox>
-
- /**
- * @brief Widget 构造函数
- * @param parent 父窗口指针
- *
- * 初始化用户界面、Modbus对象和串口通信对象。
- * 配置串口参数并连接信号槽。
- * 检查可用串口并填充到下拉框。
- */
- Widget::Widget(QWidget *parent) :
- QWidget(parent),
- ui_(new Ui::Widget),
- serialComm_(new SerialCommunicator(this)),
- modbus_(new MyModbus(this))
- {
- ui_->setupUi(this);
- setFixedSize(700,500); //设置窗口界面大小
-
- // 连接信号槽(串口->界面)
- connect(ui_->btn_connect, &QPushButton::clicked,
- this, &Widget::btnConnectClicked);
- connect(ui_->btn_write, &QPushButton::clicked,
- this, &Widget::onBtnWriteClicked);
- connect(ui_->btn_read, &QPushButton::clicked,
- this, &Widget::onBtnReadClicked);
- connect(ui_->btn_clear_read, &QPushButton::clicked,
- this, &Widget::onBtnClearreadClicked);
- connect(ui_->btn_clear_date, &QPushButton::clicked,
- this, &Widget::onBtnCleardateClicked);
- connect(ui_->btn_save_date, &QPushButton::clicked,
- this, &Widget::onBtnSavedateClicked);
- connect(ui_->btn_read_date, &QPushButton::clicked,
- this, &Widget::onBtnReaddateClicked);
- connect(ui_->btn_refresh, &QPushButton::clicked,
- this, &Widget::onBtnRefreshClicked);
- connect(ui_->btn_batch_input, &QPushButton::clicked,
- this, &Widget::onBtnBatchInputClicked);
- connect(ui_->line_input, &QLineEdit::textChanged,
- this, &Widget::onTextChanged);
- connect(ui_->btn_checkmsg, &QPushButton::clicked,
- this, &Widget::onBtnCheckmsgClicked);
- //当串口接收到完整数据后,serialComm_会发出dataReceived信号
- connect(serialComm_, &SerialCommunicator::dataReceived,
- this, &Widget::onSerialDataReceived);
- //当串口状态改变时,serialComm_会发出statusChanged信号
- connect(serialComm_, &SerialCommunicator::statusChanged,
- this, &Widget::onSerialStatusChanged);
- //当发送数据超过超时重发次数后仍未收到数据,serialComm_会发出timeoutOccurred信号
- connect(serialComm_, &SerialCommunicator::timeoutOccurred,
- this, &Widget::onSerialTimeout);
- //当串口连接出现错误时(例如串口突然断开),serialComm_会发出physicalDisconnected信号
- connect(serialComm_, &SerialCommunicator::physicalDisconnected,
- this, &Widget::sicalDisconnected);
- connect(serialComm_, &SerialCommunicator::stationConnect,
- this, &Widget::staionConneting);
-
- //设置默认波特率、数据位、校验位
- ui_->combo_baud_rate->setCurrentIndex(3);
- ui_->combo_data_bit->setCurrentIndex(3);
- ui_->combo_check->setCurrentIndex(2);
-
- //串口未连接前锁定读取和写入按钮
- ui_->btn_read->setEnabled(false);
- ui_->btn_write->setEnabled(false);
-
- //检查当前可用串口
- QList<QSerialPortInfo> serialList = QSerialPortInfo::availablePorts();
- for(const QSerialPortInfo &serialInfo : serialList)
- {
- ui_->combo_serial_num->addItem(serialInfo.portName());
- }
- }
-
- /**
- * @brief Widget 析构函数
- *
- * 清理分配的资源:删除UI对象。
- */
- Widget::~Widget()
- {
- serialComm_->close();
- delete ui_;
- }
-
- bool Widget::readLineEdit()
- {
- bool ok;
- //配置从站参数
- quint16 stationAddress = ui_->line_station_address->text().toUShort(&ok);
- if (!ok)
- {
- QMessageBox::warning(this, "输入错误", "从站地址应为大于0的整数");
- return false;
- }
- quint16 functionCode;
- switch (ui_->combo_function_code->currentIndex())
- {
- case 0:
- functionCode = 0x01; break;
- case 1:
- functionCode = 0x03; break;
- case 2:
- functionCode = 0x0F; break;
- case 3:
- functionCode = 0x10; break;
- }
- quint16 startAddress = ui_->line_strat_address->text().toUShort(&ok);
- if (!ok || startAddress > 19999)
- {
- QMessageBox::warning(this, "输入错误", "起始地址必须为0-19999之间的整数");
- return false;
- }
- quint16 length = ui_->line_length->text().toUShort(&ok);
- if (!ok)
- {
- QMessageBox::warning(this, "输入错误", "数据长度应为大于0的整数");
- return false;
- }
-
- //将从站参数传入modbus中
- if (!modbus_->setStation(stationAddress,functionCode,startAddress,length))
- {
- QMessageBox::warning(this, "输入错误", "从站数据配置应大于0");
- return false;
- }
- return true;
- }
-
- /**
- * @brief 串口连接/断开按钮点击处理
- *
- * 根据当前按钮状态执行串口连接或断开操作。
- * 连接时读取界面配置的串口参数并尝试打开串口。
- * 成功连接后启用读写按钮,断开后禁用读写按钮。
- */
- void Widget::btnConnectClicked()
- {
- if (ui_->btn_connect->text() == "连接")
- {
- // 获取界面配置的串口参数2
- QString portName = ui_->combo_serial_num->currentText();
- QSerialPort::BaudRate baudRate = QSerialPort::BaudRate(ui_->combo_baud_rate->currentText().toInt());
- QSerialPort::DataBits dataBits = QSerialPort::DataBits(ui_->combo_data_bit->currentText().toInt());
- QSerialPort::Parity parity;
- switch (ui_->combo_check->currentIndex())
- {
- case 0: parity = QSerialPort::NoParity; break;
- case 1: parity = QSerialPort::OddParity; break;
- case 2: parity = QSerialPort::EvenParity; break;
- default: parity = QSerialPort::NoParity;
- }
- QSerialPort::StopBits stopBits = QSerialPort::StopBits(ui_->comboBox_stopBit->currentText().toInt());
-
- // 配置并打开串口
- serialComm_->setPortParams(portName, baudRate, dataBits, parity, stopBits);
- if (serialComm_->open())
- {
- ui_->btn_connect->setText("断开");
- ui_->btn_read->setEnabled(true);
- ui_->btn_write->setEnabled(true);
- }
- else
- {
- QMessageBox::warning(this, "提示", "串口连接失败");
- }
- }
- else
- {
- // 断开串口
- serialComm_->close();
- ui_->btn_connect->setText("连接");
- ui_->btn_read->setEnabled(false);
- ui_->btn_write->setEnabled(false);
- }
- }
-
- /**
- * @brief 写数据按钮点击处理
- *
- * 根据当前选择的功能码执行写线圈或写寄存器操作。
- * 验证输入数据格式,生成Modbus命令并发送。
- * 操作期间禁用读写按钮防止重复操作。
- */
- void Widget::onBtnWriteClicked()
- {
- // 校验起始地址
- switch (ui_->combo_function_code->currentIndex()) //判断当前功能码
- {
- case 2: //写多个线圈
- {
- QString sendData = ui_->line_input->text().trimmed(); //读取输入框中的数据
- //判断输入框中是否为空
- if (sendData.isEmpty())
- {
- QMessageBox::warning(this, "提示", "请至少输入一个数据");
- return;
- }
- sendData.remove(" "); //移除输入数据中的空格
- //判断输入数据是否为二进制格式
- for (QChar ch : sendData)
- {
- if (ch != '0' && ch != '1')
- {
- QMessageBox::warning(this, "提示", "只允许输入 0 或 1!");
- return;
- }
- }
-
- //拆分数据
- QVector<bool> coils;
- for (QChar ch : sendData)
- {
- coils.append(ch == '1');
- }
-
- //判断输入数据长度与设置的长度是否一致
- if (coils.size() != ui_->line_length->text().toUShort())
- {
- QMessageBox::warning(this, "提示", "输入数据数与设置的长度不匹配");
- return;
- }
-
- if (!readLineEdit())
- {
- return;
- }
- modbus_->writeCoil(coils); //使用WriteCoil方法生成命令报文
-
- serialComm_->sendData(modbus_->sendCommand()); //发送生成的命令报文
- //锁定按钮
- ui_->btn_read->setEnabled(false);
- ui_->btn_write->setEnabled(false);
-
- break;
- }
-
- case 3: //写多个寄存器
- {
- QString sendData = ui_->line_input->text().trimmed(); //读取数据
- //判断输入框中是否为空
- if (sendData.isEmpty())
- {
- QMessageBox::warning(this, "提示", "请输入完整的寄存器数据");
- return;
- }
-
- //拆分输入的寄存器数据
- QStringList sl = sendData.split(',');
- QVector<quint16> values;
- bool ok;
-
- // 检查输入是否是十进制
- bool isDecimal = true;
- for (QString s : sl)
- {
- s.toInt(&ok);
- if (!ok)
- {
- isDecimal = false;
- break;
- }
- }
-
- //若判断输入的数据不是10进制,则按16进制数据处理
- for (QString s : sl)
- {
- quint16 v;
- if (isDecimal)
- {
- // 如果是十进制输入
- v = s.toUShort(&ok, 10);
- if (!ok)
- {
- QMessageBox::warning(this, "提示", "请输入正确的十进制值(0-65535)");
- return;
- }
- }
- else
- {
- // 若输入数值不是十进制则按十六进制处理
- v = s.toUShort(&ok, 16);
- if (!ok)
- {
- QMessageBox::warning(this, "提示", "请输入正确的10进制或16进制值(0-FFFF),或检查逗号格式");
- return;
- }
- }
- values.append(v);
- }
- //判断输入数据长度与设置的长度是否一致
- if (values.size() != ui_->line_length->text().toUShort())
- {
- QMessageBox::warning(this, "提示", "输入数据数与设置的长度不匹配");
- return;
- }
-
- if (!readLineEdit())
- {
- return;
- }
-
- modbus_->writeRegister(values); //使用WriteRegister方法生成命令报文
-
- serialComm_->sendData(modbus_->sendCommand()); //发送生成的命令报文
- //锁定按钮
- ui_->btn_read->setEnabled(false);
- ui_->btn_write->setEnabled(false);
-
- break;
- }
- default:
- {
- //若当前操作设置错误则进行提示
- QMessageBox::warning(this, "提示", "请将“操作”切换为写线圈或写寄存器");
- break;
- }
-
- }
- }
-
- /**
- * @brief 读数据按钮点击处理
- *
- * 根据当前选择的功能码发送读线圈或读寄存器请求。
- * 生成Modbus命令并发送,操作期间禁用读写按钮。
- */
- void Widget::onBtnReadClicked()
- {
- //若当前操作设置错误则进行提示
- if (ui_->combo_function_code->currentIndex() == 2 ||
- ui_->combo_function_code->currentIndex() == 3)
- {
- QMessageBox::warning(this, "提示", "请将“操作”切换为读线圈或读寄存器");
- return;
- }
- if (!readLineEdit())
- {
- return;
- }
- modbus_->readCoilAndReg(); //使用ReadCoilAndReg方法生成命令报文
-
- serialComm_->sendData(modbus_->sendCommand()); //发送生成的命令报文
- //锁定按钮
- ui_->btn_read->setEnabled(false);
- ui_->btn_write->setEnabled(false);
-
- }
-
- /**
- * @brief 保存通信历史数据
- *
- * 调用外部函数保存通信历史到文件。
- */
- void Widget::onBtnSavedateClicked()
- {
- saveDate(this,ui_->text_information);
- }
-
- /**
- * @brief 读取通信历史数据
- *
- * 调用外部函数从文件加载通信历史。
- */
- void Widget::onBtnReaddateClicked()
- {
- readDate(this,ui_->text_information);
- }
-
- /**
- * @brief 清空状态通知文本框
- */
- void Widget::onBtnCleardateClicked()
- {
- ui_->text_information->clear();
- }
-
- /**
- * @brief 清空数据读取文本框
- */
- void Widget::onBtnClearreadClicked()
- {
- ui_->text_data->clear();
- }
-
- void Widget::sicalDisconnected()
- {
- ui_->btn_connect->setText("连接");
- ui_->btn_read->setEnabled(false);
- ui_->btn_write->setEnabled(false);
- QMessageBox::warning(this, "警告", "串口已断开连接");
- }
-
- /**
- * @brief 串口数据接收处理
- * @param data 接收到的原始字节数据
- *
- * 将接收到的数据交给Modbus解析,处理可能的错误,
- * 并根据当前功能码解析和显示线圈或寄存器数据。
- */
- void Widget::onSerialDataReceived(const QByteArray &data)
- {
- QByteArray revMessage = modbus_->receive(data); // 接收的原始数据交给Modbus解析
- // 启用操作按钮
- ui_->btn_read->setEnabled(true);
- ui_->btn_write->setEnabled(true);
- if (revMessage.isEmpty())
- {
- //若modbus检测响应报文格式错误则丢弃数据,返回值为空
- QMessageBox::warning(this, "提示", "返回报文站地址或CRC校验错误,请重试");
- return;
- }
-
- //显示接收到的响应报文
- ui_->text_information->append("接收报文:" + revMessage.toHex().toUpper());
-
- // 检查响应报文中是否带有错误码
- quint8 exCode = modbus_->errorCheck();
- if (exCode)
- {
- QString errorMsg;
- switch (exCode)
- {
- case 0x01: errorMsg = "非法功能码"; break;
- case 0x02: errorMsg = "非法数据地址"; break;
- case 0x03: errorMsg = "非法数据值"; break;
- case 0x04: errorMsg = "从站设备故障"; break;
- case 0x11: errorMsg = "数据域个数异常"; break;
- default: errorMsg = "未知异常"; break;
- }
- QMessageBox::warning(this, "异常响应",
- QString("错误码: 0x%1(%2)").arg(QString::number(exCode, 16).toUpper(), errorMsg));
- return;
- }
-
- // 解析并显示数据(根据功能码)
- switch (ui_->combo_function_code->currentIndex())
- {
- case 0:
- { // 读线圈
- QVector<bool> coils = modbus_->analReadCoil(); //使用AnalReadCoil解析线圈数据
- ui_->text_data->append("线圈状态:");
- for (int i = 0; i < ui_->line_length->text().toInt(); i++)
- {
- ui_->text_data->append(QString("线圈%1: %2").arg(i+1).arg(coils[i] ? "1" : "0"));
- }
- break;
- }
- case 1:
- { // 读寄存器
- QVector<quint16> regs = modbus_->analReadReg(); //使用AnalReadReg解析寄存器数据
- ui_->text_data->append("寄存器值:");
- for (int i = 0; i < ui_->line_length->text().toInt(); i++)
- {
- ui_->text_data->append(QString("寄存器%1: %2").arg(i+1).arg(regs[i]));
- }
- break;
- }
- }
- }
-
- /**
- * @brief 串口状态变化处理
- * @param status 状态描述字符串
- *
- * 在状态通知文本框中显示串口状态信息。
- */
- void Widget::onSerialStatusChanged(const QString &status)
- {
- ui_->text_information->append(status); // 显示状态信息(如连接成功、超时重发等)
- }
-
- void Widget::onTextChanged()
- {
- QString data = ui_->line_input->text();
- data.remove(" ");
- data.remove(",");
- ui_->line_number->setText(QString::number(data.size()));
- }
-
- void Widget::staionConneting(bool online)
- {
- if(online)
- {
- ui_->label_staion->setText("成功连接到从站");
- }
- else
- {
- ui_->label_staion->setText("未连接到从站");
- }
- }
-
- /**
- * @brief 串口通信超时处理
- *
- * 显示超时警告并重新启用操作按钮。
- */
- void Widget::onSerialTimeout()
- {
- QMessageBox::warning(this, "提示", "等待响应超时,请检查设备");
- ui_->btn_read->setEnabled(true);
- ui_->btn_write->setEnabled(true);
- }
-
- /**
- * @brief 检测当前可用串口
- *
- * 检查可用串口并填充到下拉框。
- */
- void Widget::onBtnRefreshClicked()
- {
- ui_->combo_serial_num->clear();
- QList<QSerialPortInfo> serialList = QSerialPortInfo::availablePorts();
- for(const QSerialPortInfo &serialInfo : serialList)
- {
- ui_->combo_serial_num->addItem(serialInfo.portName());
- }
- }
-
- void Widget::onBtnBatchInputClicked()
- {
- bool ok;
- int number = ui_->line_data_num->text().toInt(&ok);
- if (!ok || number <= 0 || number > 1000)
- {
- QMessageBox::warning(this, "输入错误", "请输入1-1000之间的整数");
- return;
- }
- QString inputData = ui_->line_data->text();
-
- for (int i = 0; i < number; i++)
- {
- ui_->line_input->insert(inputData);
- }
- }
-
- void Widget::onBtnCheckmsgClicked()
- {
- bool ok;
- quint16 stationAddress = ui_->line_station_address->text().toUShort(&ok);
- if (!ok || 0 == stationAddress)
- {
- QMessageBox::warning(this, "输入错误", "从站地址应为大于0的整数");
- return;
- }
- modbus_->setStation(stationAddress,0x01,0x01,0x01);
- modbus_->readCoilAndReg();
- serialComm_->setCheckMsg(modbus_->sendCommand());
- }
|