#include "mainwidget.h" MainWidget::MainWidget(QWidget *parent) : QWidget(parent), settings("TurnMeOn", "SerialAsst") { //初始化各参数列表 BaudrateList << "256000" << "230400" << "128000" << "115200" << "76800" << "57600" << "43000" << "38400" << "19200" << "14400" << "9600" << "4800" << "2400" << "1200"; StopbitsList << "1" << "1.5" << "2"; DatabitsList << "8" << "7" << "6" << "5"; ParityList << tr("无") << tr("奇校验") << tr("偶校验"); FlowControlList << tr("无") << tr("硬件") << tr("软件"); //初始化ComboBox COMBox = new QComboBox(); COMBox->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); BaudrateBox = new QComboBox(); BaudrateBox->addItems(BaudrateList); BaudrateBox->setCurrentIndex(settings.value("Baud rate", 0).toInt()); StopbitsBox = new QComboBox(); StopbitsBox->addItems(StopbitsList); StopbitsBox->setCurrentIndex(settings.value("Stop bits", 0).toInt()); DatabitsBox = new QComboBox(); DatabitsBox->addItems(DatabitsList); DatabitsBox->setCurrentIndex(settings.value("Data bits", 0).toInt()); ParityBox = new QComboBox(); ParityBox->addItems(ParityList); ParityBox->setCurrentIndex(settings.value("Parity", 0).toInt()); FlowControlBox = new QComboBox(); FlowControlBox->addItems(FlowControlList); FlowControlBox->setCurrentIndex(settings.value("Flow Control", 0).toInt()); //初始化label COMLabel = new QLabel(tr("串口号")); BaudrateLabel = new QLabel(tr("波特率")); StopbitsLabel = new QLabel(tr("停止位")); DatabitsLabel = new QLabel(tr("数据位")); ParityLabel = new QLabel(tr("校验位")); FlowControlLabel = new QLabel(tr("流控")); //发送与接受区域 RecvArea = new QPlainTextEdit(); RecvArea->setReadOnly(true); QPalette pal; pal.setColor(QPalette::Base, Qt::black); pal.setColor(QPalette::Text, Qt::green); RecvArea->setPalette(pal); SendArea = new QPlainTextEdit(); QFont areaFont = SendArea->font(); areaFont.setPointSize(11); SendArea->setFont(areaFont); areaFont.setPointSize(11); RecvArea->setFont(areaFont); // RecvArea->setFont(QFont(tr("Microsoft YaHei UI"), 10)); // SendArea->setFont(QFont(tr("Microsoft YaHei UI Light"), 12)); SendArea->installEventFilter(this); //按钮 OpenButton = new QPushButton(tr("打开串口")); SendButton = new QPushButton(tr("发送")); SendButton->setDisabled(true); SendButton->setToolTip(tr("Ctrl+Enter")); ClearRecvButton = new QPushButton(tr("清除接收")); ClearSendButton = new QPushButton(tr("清除发送")); connect(OpenButton, &QPushButton::clicked, this, &MainWidget::OpenSerial); connect(SendButton, &QPushButton::clicked, this, &MainWidget::SendContent); connect(ClearRecvButton, &QPushButton::clicked, this, &MainWidget::ClearRecv); connect(ClearSendButton, &QPushButton::clicked, this, &MainWidget::ClearSend); //复选框 NewLineBox = new QCheckBox(tr("发送新行")); TimerBox = new QCheckBox(tr("定时发送")); HexSend = new QCheckBox(tr("发送16进制")); HexRecv = new QCheckBox(tr("接收16进制")); HexSend->setToolTip(tr("以空格作为间隔符, \n非法字符及其后面的字符将被忽略")); // RTS和 DTR复选框 RTSBox = new QCheckBox(tr("RTS")); DTRBox = new QCheckBox(tr("DTR")); flowControlLayout = new QHBoxLayout(); flowControlLayout->addWidget(RTSBox); flowControlLayout->addWidget(DTRBox); //绑定复选框信号 connect(NewLineBox, &QCheckBox::stateChanged, this, &MainWidget::detNewLine); connect(TimerBox, &QCheckBox::stateChanged, this, &MainWidget::ControlSendTimer); connect(HexSend, &QCheckBox::stateChanged, this, &MainWidget::detHex); connect(HexRecv, &QCheckBox::stateChanged, this, &MainWidget::detRecvHex); connect(RTSBox, &QCheckBox::stateChanged, this, &MainWidget::RTSControl); connect(DTRBox, &QCheckBox::stateChanged, this, &MainWidget::DTRControl); //定时发送spinbox TimerSpin = new QSpinBox(); TimerSpin->setSuffix(tr(" ms")); TimerSpin->setMaximum(1000000); TimerSpin->setValue(1000); //valueChanged有多个重载 connect(TimerSpin, static_cast(&QSpinBox::valueChanged), this, &MainWidget::changeSendTimer); //初始化布局 leftLlayout = new QFormLayout(); leftLlayout->addRow(BaudrateLabel, BaudrateBox); leftLlayout->addRow(StopbitsLabel, StopbitsBox); leftLlayout->addRow(DatabitsLabel, DatabitsBox); leftLlayout->addRow(ParityLabel, ParityBox); leftLlayout->addRow(FlowControlLabel, FlowControlBox); leftLlayout->addRow(flowControlLayout); leftLlayout->addRow(COMLabel, COMBox); leftLlayout->addRow(OpenButton); leftLlayout->setAlignment(OpenButton, Qt::AlignVCenter); leftLlayout->setContentsMargins(15, 25, 15, 25); leftLlayout->setHorizontalSpacing(20); leftLlayout->setVerticalSpacing(20); paramGroup = new QGroupBox(tr("串口参数")); paramGroup->setLayout(leftLlayout); paramGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); cvlayout = new QVBoxLayout(); cvlayout->addWidget(RecvArea, 7); cvlayout->addWidget(SendArea, 2); bottomLayout = new QHBoxLayout(); bottomLayout->addWidget(ClearSendButton, 0, Qt::AlignLeft); bottomLayout->addWidget(HexSend); bottomLayout->addWidget(NewLineBox, 0, Qt::AlignLeft); bottomLayout->addWidget(TimerBox, 0, Qt::AlignRight); bottomLayout->addWidget(TimerSpin, 1, Qt::AlignLeft); bottomLayout->addWidget(SendButton, 0, Qt::AlignRight); rightLayout = new QVBoxLayout(); rightLayout->addWidget(ClearRecvButton, 0, Qt::AlignCenter); rightLayout->addWidget(HexRecv); centralLayout = new QGridLayout(this); centralLayout->addWidget(paramGroup, 0, 0, 2, 1); centralLayout->addLayout(bottomLayout, 1, 1); centralLayout->addLayout(cvlayout, 0, 1, 1, 1); centralLayout->addLayout(rightLayout, 0, 2, Qt::AlignTop); centralLayout->setColumnMinimumWidth(0, 150); centralLayout->setColumnMinimumWidth(2, 100); centralLayout->setColumnStretch(0, 0); centralLayout->setColumnStretch(1, 2); centralLayout->setColumnStretch(2, 0); setLayout(centralLayout); //计时器 初始化 CheckTimer = new QTimer(this); CheckTimer->start(1000); SendTimer = new QTimer(this); connect(CheckTimer, &QTimer::timeout, this, &MainWidget::CheckSerials); connect(SendTimer, &QTimer::timeout, SendButton, &QPushButton::click); //初始化串口列表 CheckSerials(); //开始串口进程 serialController = new SerialController(this); //connect开关串口控制信号 以及是否成功的返回信号 connect(this, &MainWidget::requestOpen, serialController, &SerialController::openSerial); connect(this, &MainWidget::requestClose, serialController, &SerialController::closeSerial); connect(serialController, &SerialController::openSuccess, this, &MainWidget::serialOpened); connect(serialController, &SerialController::openFailed, this, &MainWidget::serialNotOpened); connect(serialController, &SerialController::closeSuccess, this, &MainWidget::serialClosed); //connect波特率等参数的控制信号 connect(this, &MainWidget::setBaudRate, serialController, &SerialController::getBaudrate); connect(this, &MainWidget::setStopBits, serialController, &SerialController::getStopbits); connect(this, &MainWidget::setDataBits, serialController, &SerialController::getDatabits); connect(this, &MainWidget::setParity, serialController, &SerialController::getParity); connect(this, &MainWidget::setFlowControl, serialController, &SerialController::getFlowControl); connect(BaudrateBox, &QComboBox::currentTextChanged, serialController, &SerialController::getBaudrate); connect(StopbitsBox, &QComboBox::currentTextChanged, serialController, &SerialController::getStopbits); connect(DatabitsBox, &QComboBox::currentTextChanged, serialController, &SerialController::getDatabits); connect(ParityBox, &QComboBox::currentTextChanged, serialController, &SerialController::getParity); connect(this, &MainWidget::changeRTS, serialController, &SerialController::contrloRTS); connect(this, &MainWidget::changeDTR, serialController, &SerialController::controlDTR); connect(this, &MainWidget::sendData, serialController, &SerialController::writeData); connect(serialController, &SerialController::recvData, this, &MainWidget::getRecv); } void MainWidget::CheckSerials() { //不断检查可用串口列表,并与当前列表进行比较,若发生变化则重新生成列表 emit sendDateTime(QDateTime::currentDateTime().toString(Qt::ISODate)); //更新状态栏时间 QList SerialList = QSerialPortInfo::availablePorts(); if (!SerialList.isEmpty()) { QStringList TmpComList, TmpPortNameList, TmpDesList; for (QSerialPortInfo &serial : SerialList) { TmpComList << serial.portName() + " " + serial.description(); TmpPortNameList << serial.portName(); TmpDesList << serial.description(); } if (COMList != TmpComList) { //TODO: 可用串口发生改变却不为空时,保持串口打开状态,及其他细节的处理 //只在串口发生变化时刷新ComboBox COMList = TmpComList; PortNameList = TmpPortNameList; DescList = TmpDesList; COMBox->setDisabled(false); COMBox->clear(); // COMBox->addItems(PortNameList); COMBox->addItems(COMList); OpenButton->setDisabled(false); for (int i = 0; i < COMList.count(); i++) //为串口列表增加ToolTip { COMBox->setItemData(i, DescList[i], Qt::ToolTipRole); } } } else //可用串口为空时发送关闭串口信号 { COMBox->clear(); COMList.clear(); COMBox->addItem(tr("(空)")); COMBox->setDisabled(true); OpenButton->setDisabled(true); CloseSerial(); isOpened = false; } } MainWidget::~MainWidget() { // SerialThr.terminate(); settings.setValue("Baud rate", BaudrateBox->currentIndex()); settings.setValue("Stop bits", StopbitsBox->currentIndex()); settings.setValue("Data bits", DatabitsBox->currentIndex()); settings.setValue("Parity", ParityBox->currentIndex()); settings.setValue("Flow Control", FlowControlBox->currentIndex()); } void MainWidget::serialOpened() { isOpened = true; int portIndex = COMBox->currentIndex(); emit sendStatus(QString(PortNameList[portIndex] + tr(" ") + DescList[portIndex])); //串口关闭时无法设置RTS,DTR信号(但此时checkbox仍然是可操作的),串口打开时发送stateChanged信号来应用改变 emit RTSBox->stateChanged(RTSBox->checkState()); emit DTRBox->stateChanged(DTRBox->checkState()); //相应控件可用性做出改变(setDisabled) ActionAttachToSerial(true); } void MainWidget::serialNotOpened() { //设置状态栏 并发出警告音 //TODO: 此处应有更容易察觉的提示 emit sendStatus(tr("串口打开失败")); QApplication::beep(); } void MainWidget::serialClosed() { isOpened = false; //相应控件可用性做出改变(setDisabled) ActionAttachToSerial(false); emit sendStatus(tr("串口已关闭")); } void MainWidget::getRecv(const QByteArray &recv) { RecvArea->moveCursor(QTextCursor::End); //需要时将受到的数据进行16进制转换 //TODO: 支持更多编码(可选择) if (!isRecvHex) RecvArea->textCursor().insertText(QString::fromLocal8Bit(recv)); else { if (!RecvArea->toPlainText().isEmpty()) RecvArea->textCursor().insertText(QChar(HEX_SEPARATOR)); RecvArea->textCursor().insertText(QString(recv.toHex(HEX_SEPARATOR))); } } void MainWidget::OpenSerial() { QString portName = COMBox->currentText(); emit setBaudRate(BaudrateBox->currentText()); emit setStopBits(StopbitsBox->currentText()); emit setDataBits(DatabitsBox->currentText()); emit setParity(ParityBox->currentText()); emit setFlowControl(FlowControlBox->currentText()); emit requestOpen(portName); } void MainWidget::CloseSerial() { emit requestClose(); } void MainWidget::ClearRecv() { RecvArea->clear(); } void MainWidget::ClearSend() { SendArea->clear(); } void MainWidget::detNewLine(int state) { if (state == 2) isSendNewLine = true; else if (state == 0) isSendNewLine = false; } void MainWidget::ControlSendTimer(int state) { if (state == 0) SendTimer->stop(); else if (state == 2) SendTimer->start(TimerSpin->value()); } void MainWidget::changeSendTimer() { ControlSendTimer(TimerBox->checkState()); } void MainWidget::detHex(int state) { //TODO: 换行也可以作为16进制的分隔符 if (state == 2) { isSendHex = true; // SendArea->setPlainText(SendArea->toPlainText().toLocal8Bit().toHex(' ')); } else if (state == 0) { isSendHex = false; // QString tmpstr = SendArea->toPlainText(); // SendArea->clear(); // SendArea->setPlainText(HexStringToString(tmpstr)); } } void MainWidget::detRecvHex(int state) { if (state == 2) { isRecvHex = true; // RecvArea->setPlainText(RecvArea->toPlainText().toLocal8Bit().toHex(' ')); // RecvArea->moveCursor(QTextCursor::End); // RecvArea->insertPlainText(tr(" ")); } else if (state == 0) { isRecvHex = false; // QString tmpstr = RecvArea->toPlainText(); // RecvArea->clear(); // RecvArea->setPlainText(HexStringToString(tmpstr)); } } void MainWidget::RTSControl(int state) { if(isOpened) { if(state == 2) emit changeRTS(true); else if(state == 0) emit changeRTS(false); } } void MainWidget::DTRControl(int state) { if(isOpened) { if(state == 2) emit changeDTR(true); else if(state == 0) emit changeDTR(false); } } QByteArray MainWidget::HexStringToString(const QString &hexstr) { //将内容为16进制字符串的string转换成16进制对应的内容 QStringList list = hexstr.split(' '); QByteArray bytes; char currentByte{0}; bool hasReadFirstChar = false; for (auto ch : hexstr) { auto ascii = ch.toLatin1(); if (ch == ' ') { // 允许空格 continue; } auto numHex = convertAsciiToHex(ascii); if (numHex < 0) { return bytes; } if (!hasReadFirstChar) { currentByte += (numHex - '0') << 4; hasReadFirstChar = true; } else { currentByte += numHex; bytes.append(currentByte); hasReadFirstChar = false; currentByte = 0; } } return bytes; } char MainWidget::convertAsciiToHex(char ascii) { if (ascii >= '0' && ascii <= '9') { return ascii - '0'; } else if (ascii >= 'a' && ascii <= 'f') { return 10 + ascii - 'a'; } else if (ascii >= 'A' && ascii <= 'F') { return 10 + ascii - 'A'; } else { return -1; } } void MainWidget::ActionAttachToSerial(bool set) { //根据串口开关状态决定一些控件的可用性 if(set) { OpenButton->setText(tr("关闭串口")); COMBox->setDisabled(true); SendButton->setDisabled(false); disconnect(OpenButton, &QPushButton::clicked, this, &MainWidget::OpenSerial); connect(OpenButton, &QPushButton::clicked, this, &MainWidget::CloseSerial); } else { OpenButton->setText(tr("打开串口")); COMBox->setDisabled(false); SendButton->setDisabled(true); disconnect(OpenButton, &QPushButton::clicked, this, &MainWidget::CloseSerial); connect(OpenButton, &QPushButton::clicked, this, &MainWidget::OpenSerial); } } void MainWidget::SendContent() { QString content = SendArea->toPlainText(); QByteArray bytes; if (isSendNewLine) content += "\n"; if (isSendHex) { bytes = HexStringToString(content); } else { bytes = content.toLatin1(); } emit sendData(bytes); } bool MainWidget::eventFilter(QObject *watched, QEvent *event) { if (watched == SendArea && event->type() == QEvent::KeyPress) { QKeyEvent *e = reinterpret_cast(event); if (e->modifiers() == Qt::ControlModifier) { switch (e->key()) { case Qt::Key_Enter: case Qt::Key_Return: SendButton->click(); break; default: break; } } } return QWidget::eventFilter(watched, event); }