From: xue <2144439972@qq.com> Date: Fri, 10 Apr 2026 11:30:29 +0000 (+0800) Subject: 1 X-Git-Url: http://git.snicker.com.cn/sitemap.xml?a=commitdiff_plain;h=4196d10f6f10ade2a4415337f6bf74bbdcff20b2;p=mirrors%2Ffactory_tool.git 1 --- diff --git a/config_manager.cpp b/config_manager.cpp index 7216de6..37697e2 100644 --- a/config_manager.cpp +++ b/config_manager.cpp @@ -1,4 +1,4 @@ -#include "config_manager.h" +#include "config_manager.h" #include #include #include @@ -55,8 +55,6 @@ void ConfigManager::setDefaultConfig() testConfig["portName"] = ""; testConfig["audioVolume"] = "2.0"; testConfig["audioLevelStandard"] = "中"; - testConfig["expectedModemVer"] = ""; - testConfig["expectedPYVer"] = ""; testConfig["expectedVer"] = ""; testConfig["bluetoothAddress"] = ""; testConfig["bluetoothRSSI"] = ""; @@ -68,10 +66,15 @@ void ConfigManager::setDefaultConfig() testCheckboxes["vib"] = true; testCheckboxes["btn"] = true; testCheckboxes["mic"] = true; - testCheckboxes["charge"] = true; - testCheckboxes["ntc"] = true; + testCheckboxes["tp"] = true; + testCheckboxes["bright"] = true; + testCheckboxes["battery"] = true; testCheckboxes["gsensor"] = true; + testCheckboxes["leaklight"] = true; testCheckboxes["bt"] = true; + testCheckboxes["sechip"] = true; + testCheckboxes["gps"] = true; + testCheckboxes["deepsleep"] = true; testCheckboxes["4g"] = true; defaultConfig["testCheckboxes"] = testCheckboxes; diff --git a/factory_tool.pro b/factory_tool.pro index 047d1cf..5b5038e 100644 --- a/factory_tool.pro +++ b/factory_tool.pro @@ -15,16 +15,12 @@ CODECFORSRC = UTF-8 SOURCES += \ main.cpp \ mainwindow.cpp \ - config_manager.cpp \ - qrcode_barcode_generator.cpp \ - qrcodegen.cpp + config_manager.cpp # 头文件列表 HEADERS += \ mainwindow.h \ - config_manager.h \ - qrcode_barcode_generator.h \ - qrcodegen.hpp + config_manager.h # UI 文件列表(关键:必须包含 mainwindow.ui) FORMS += \ diff --git a/mainwindow.cpp b/mainwindow.cpp index d3894f6..4239790 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,4 +1,4 @@ -#include "mainwindow.h" +#include "mainwindow.h" #include "ui_mainwindow.h" #include "config_manager.h" #include @@ -36,7 +36,7 @@ MainWindow::MainWindow(QWidget *parent) , ui(new Ui::MainWindow) { ui->setupUi(this); - setWindowTitle("雷鸟产测工具 V1.7"); + setWindowTitle("FD272产测工具 V1.0"); // 确保控制台输出使用UTF-8编码 QTextCodec *utf8Codec = QTextCodec::codecForName("UTF-8"); @@ -62,71 +62,47 @@ MainWindow::MainWindow(QWidget *parent) // 设置产测按钮默认蓝色样式 ui->openButton_2->setStyleSheet("QPushButton { background-color: #4A90E2; color: white; padding: 5px 10px; border: none; border-radius: 3px; }"); - // 调整二维码/条形码区域的布局比例:上方二维码占 2/3,下方条形码占 1/3 - if (ui->verticalLayout_4) { - ui->verticalLayout_4->setStretch(0, 0); // checkbox - ui->verticalLayout_4->setStretch(1, 2); // qr view - ui->verticalLayout_4->setStretch(2, 1); // barcode view - } - if (ui->verticalLayout_2) { - ui->verticalLayout_2->setStretch(0, 0); // checkbox - ui->verticalLayout_2->setStretch(1, 2); // qr view - ui->verticalLayout_2->setStretch(2, 1); // barcode view - } // 初始化产测相关变量 lcdTestTimer = new QTimer(this); motorTestTimer = new QTimer(this); - test4GTimer = new QTimer(this); - signal4GTimer = new QTimer(this); - cat1SignalTimer = new QTimer(this); testTimeoutTimer = new QTimer(this); + gSensorIntervalTimer = new QTimer(this); currentLcdColor = 0; - initRetry4GCount = 0; - signalTest4GCount = 0; - signalReceived4GCount = 0; - signalGood4GCount = 0; - pppCommandCount = 0; - hasReceivedSignalSim = false; - hasReceivedFirstSignal = false; - cat1SignalTestCount = 0; - cat1SignalReceivedCount = 0; - cat1SignalGoodCount = 0; - cat1Signal99Count = 0; - cat1SignalTotalValue = 0; - hasReceivedPppInfo = false; isProductionTestRunning = false; currentTestStep = TEST_IDLE; lcdTestPopupShown = false; motorTestPopupShown = false; + touchTestPopupShown = false; + brightTestPopupShown = false; + deepsleepTestPopupShown = false; isWaitingForUserConfirm = false; lcdBacklightOn = false; // 初始化LCD背光状态为关闭 keyTestStarted = false; keyTestDialog = nullptr; - audioTestPopupShown = false; - audioPlaybackPopupShown = false; - audioRecordTimer = new QTimer(this); - audioTestDialog = nullptr; - audioDialogLabel = nullptr; - audioDialogInfo = nullptr; - audioStartButton = nullptr; - audioPassButton = nullptr; - audioFailButton = nullptr; - audioRetestButton = nullptr; - audioMicMiddleConsecutiveCount = 0; - audioMicTotalPassCount = 0; - audioTestFinished = false; + keyRotateDetected = false; + audioTestRoundIndex = 0; + audioTestAnyPass = false; + audioTestAwaitingMicLevel = false; bluetoothTestTimer = new QTimer(this); + gpsTestTimer = new QTimer(this); + gpsPollTimer = new QTimer(this); bluetoothAddresses.clear(); bluetoothTestFinished = false; + gpsTestFinished = false; + gpsInfoSeenIn10Sec = false; + gpsMaxNumSats = 0; + gpsMaxTrace = 0; + gpsMaxCn = 0; detectedBluetoothAddress = ""; - detectedBluetoothRSSI = 0; - pingTestTimer = new QTimer(this); - pingTestFinished = false; - pingSuccessCount = 0; - currentAudioStep = AUDIO_PLAYING_1KHZ; - currentModemVersion = ""; - currentSimStatus = -1; + detectedBluetoothRSSI = -128; + bluetoothMaxRssi = -128; + leaklightGreen = 0; + leaklightRed = 0; + leaklightIr = 0; + leaklightGreenSeen = false; + leaklightRedSeen = false; + leaklightIrSeen = false; // 初始化日志文件相关变量 testLogFile = nullptr; testLogStream = nullptr; @@ -155,39 +131,25 @@ MainWindow::MainWindow(QWidget *parent) // 连接定时器信号 connect(lcdTestTimer, &QTimer::timeout, this, &MainWindow::onLcdTestTimer); connect(motorTestTimer, &QTimer::timeout, this, &MainWindow::onMotorTestTimer); - connect(test4GTimer, &QTimer::timeout, this, &MainWindow::on4GTestTimeout); - connect(signal4GTimer, &QTimer::timeout, this, &MainWindow::on4GSignalTimer); - connect(cat1SignalTimer, &QTimer::timeout, this, &MainWindow::onCat1SignalTimer); connect(testTimeoutTimer, &QTimer::timeout, this, &MainWindow::onTestTimeout); - connect(audioRecordTimer, &QTimer::timeout, this, &MainWindow::onAudioRecordTimer); + connect(gSensorIntervalTimer, &QTimer::timeout, this, &MainWindow::onGsensorIntervalTimeout); connect(bluetoothTestTimer, &QTimer::timeout, this, &MainWindow::onBluetoothTestTimer); - connect(pingTestTimer, &QTimer::timeout, this, &MainWindow::onPingTestTimeout); - - // 连接ICCID和IMEI输入框的信号 - connect(ui->ICCIDlineEdit, &QLineEdit::textChanged, this, &MainWindow::onICCIDLineEditTextChanged); - connect(ui->IMEIlineEdit, &QLineEdit::textChanged, this, &MainWindow::onIMEILineEditTextChanged); - - // 连接ICCID和IMEI复选框的信号 - connect(ui->checkBox_iccid, &QCheckBox::stateChanged, this, &MainWindow::onICCIDCheckBoxStateChanged); - connect(ui->checkBox_imei, &QCheckBox::stateChanged, this, &MainWindow::onIMEICheckBoxStateChanged); - - // 初始化GraphicsView的场景 - ui->graphicsView_iccid_qr->setScene(new QGraphicsScene(this)); - ui->graphicsView_iccid_barcode->setScene(new QGraphicsScene(this)); - ui->graphicsView_imei_qr->setScene(new QGraphicsScene(this)); - ui->graphicsView_imei_barcode->setScene(new QGraphicsScene(this)); - - // 设置GraphicsView的背景和缩放 - ui->graphicsView_iccid_qr->setBackgroundBrush(QBrush(Qt::white)); - ui->graphicsView_iccid_barcode->setBackgroundBrush(QBrush(Qt::white)); - ui->graphicsView_imei_qr->setBackgroundBrush(QBrush(Qt::white)); - ui->graphicsView_imei_barcode->setBackgroundBrush(QBrush(Qt::white)); - - // 设置GraphicsView的缩放模式,使其能够自适应 - ui->graphicsView_iccid_qr->setRenderHint(QPainter::Antialiasing, false); - ui->graphicsView_iccid_barcode->setRenderHint(QPainter::Antialiasing, false); - ui->graphicsView_imei_qr->setRenderHint(QPainter::Antialiasing, false); - ui->graphicsView_imei_barcode->setRenderHint(QPainter::Antialiasing, false); + connect(gpsTestTimer, &QTimer::timeout, this, &MainWindow::onGpsTestTimer); + connect(gpsPollTimer, &QTimer::timeout, this, &MainWindow::onGpsPollTimer); + + // 初始化G-sensor采集状态 + gSensorCollecting = false; + gSensorHasStart = false; + gSensorHasLast = false; + gSensorErrorDetected = false; + gSensorAnySample = false; + gSensorAllSamplesInRange = true; + gSensorStartX = 0.0; + gSensorStartY = 0.0; + gSensorStartZ = 0.0; + gSensorLastX = 0.0; + gSensorLastY = 0.0; + gSensorLastZ = 0.0; } MainWindow::~MainWindow() @@ -510,534 +472,10 @@ void MainWindow::CheckVibratorFunc(const QString& data) } } -// 提取IMEI信息(支持多种格式) -void MainWindow::ShowPppInfoIMEI(const QString& data) -{ - QString imeiInfo; - - qDebug() << "开始提取IMEI,原始数据:" << data.trimmed(); - - // 优先处理直接以数字开头的格式(最常见的新格式) - QRegExp imeiPattern("^(\\d{15})"); - if (imeiPattern.indexIn(data.trimmed()) == 0) { - imeiInfo = imeiPattern.cap(1); - qDebug() << "使用数字开头格式提取IMEI:" << imeiInfo; - } - // 方法2:查找"IMEI:"标识的格式(传统格式) - else { - int start = data.indexOf("IMEI:", 0, Qt::CaseInsensitive); - if (start != -1) { - // 计算IMEI号码的起始位置(跳过"IMEI: ") - const QString key = "IMEI:"; - start += key.length(); - - // 跳过可能的空格 - while (start < data.length() && data.at(start) == ' ') { - start++; - } - - // 查找行结束标志(换行符或字符串结束) - int end = data.indexOf('\n', start); - if (end == -1) { - end = data.indexOf('\r', start); - if (end == -1) { - end = data.length(); - } - } - - // 提取IMEI信息 - imeiInfo = data.mid(start, end - start).trimmed(); - qDebug() << "使用IMEI:格式提取IMEI:" << imeiInfo; - } - // 方法3:查找"imei:"标识的格式 - else { - start = data.indexOf("imei:", 0, Qt::CaseInsensitive); - if (start != -1) { - // 计算IMEI号码的起始位置(跳过"imei:") - const QString key = "imei:"; - start += key.length(); - - // 查找@符号作为结束标志 - int end = data.indexOf('@', start); - if (end == -1) { - // 如果没有@符号,查找行结束标志 - end = data.indexOf('\n', start); - if (end == -1) { - end = data.indexOf('\r', start); - if (end == -1) { - end = data.length(); - } - } - } - - // 提取IMEI信息 - imeiInfo = data.mid(start, end - start).trimmed(); - qDebug() << "使用imei:格式提取IMEI:" << imeiInfo; - } else { - qDebug() << "未找到任何IMEI格式,数据:" << data.trimmed(); - return; - } - } - } - - // 验证IMEI格式是否有效 - if (!imeiInfo.isEmpty() && isValidIMEI(imeiInfo)) { - // 显示到IMEI文本框 - ui->IMEIlineEdit->setText(imeiInfo); - qDebug() << "最终提取的IMEI:" << imeiInfo; - // 尝试重命名日志文件(如果ICCID也已获取) - renameLogFileWithDeviceInfo(); - } else { - if (imeiInfo.isEmpty()) { - qDebug() << "IMEI信息为空,数据:" << data.trimmed(); - } else { - qDebug() << "IMEI格式不正确,不显示到界面,IMEI:" << imeiInfo; - ui->statusbar->showMessage("IMEI格式不正确: " + imeiInfo, 3000); - } - } -} - -// 提取ICCID信息(支持多种格式) -void MainWindow::ShowPppInfoICCID(const QString& data) -{ - QString iccidInfo; - - // 方法1:使用正则表达式提取@iccid:后面的数字字母组合 - QRegExp iccidRegex("@?iccid:([0-9A-Fa-f]{19,20})(?:@|\\s|$)", Qt::CaseInsensitive); - if (iccidRegex.indexIn(data) != -1) { - iccidInfo = iccidRegex.cap(1); - qDebug() << "使用正则表达式提取ICCID:" << iccidInfo; - } else { - // 方法2:传统方式查找"ICCID:" - int start = data.indexOf("ICCID:", 0, Qt::CaseInsensitive); - if (start == -1) { - qDebug() << "未找到ICCID:标识"; - return; - } - - // 计算ICCID号码的起始位置(跳过"ICCID:") - start += 6; // "ICCID:"的长度 - - // 跳过可能的空格 - while (start < data.length() && data.at(start) == ' ') { - start++; - } - - // 查找ICCID结束标志(@符号、换行符或字符串结束) - int end = data.indexOf('@', start); - if (end == -1) { - end = data.indexOf('\n', start); - if (end == -1) { - end = data.indexOf('\r', start); - if (end == -1) { - end = data.length(); - } - } - } - - // 提取ICCID信息 - iccidInfo = data.mid(start, end - start).trimmed(); - qDebug() << "使用传统方式提取ICCID:" << iccidInfo; - } - - // 验证ICCID格式是否有效 - if (isValidICCID(iccidInfo)) { - // 显示到ICCID文本框 - ui->ICCIDlineEdit->setText(iccidInfo); - qDebug() << "提取的ICCID:" << iccidInfo; - // 尝试重命名日志文件(如果IMEI也已获取) - renameLogFileWithDeviceInfo(); - } else { - qDebug() << "ICCID格式不正确,不显示到界面,ICCID:" << iccidInfo; - ui->statusbar->showMessage("ICCID格式不正确: " + iccidInfo, 3000); - } -} - -// 提取Modem Version信息(支持多种格式) -void MainWindow::ShowPppInfoModemVer(const QString& data) -{ - QString modemVerInfo; - int start; - - // 方法1:查找"Modem Version:"标识的格式(传统格式) - start = data.indexOf("Modem Version:", 0, Qt::CaseInsensitive); - if (start != -1) { - // 计算版本信息的起始位置(跳过"Modem Version: ") - const QString key = "Modem Version:"; - start += key.length(); - - // 跳过可能的空格 - while (start < data.length() && data.at(start) == ' ') { - start++; - } - - // 查找行结束标志(换行符或字符串结束) - int end = data.indexOf('\n', start); - if (end == -1) { - end = data.indexOf('\r', start); - if (end == -1) { - end = data.length(); - } - } - - // 提取Modem版本信息 - modemVerInfo = data.mid(start, end - start).trimmed(); - } - // 方法2:查找"@modemVer:"标识的格式(新格式) - else { - start = data.indexOf("@modemVer:", 0, Qt::CaseInsensitive); - if (start != -1) { - // 计算版本信息的起始位置(跳过"@modemVer:") - const QString key = "@modemVer:"; - start += key.length(); - - // 查找行结束标志(换行符或字符串结束) - int end = data.indexOf('\n', start); - if (end == -1) { - end = data.indexOf('\r', start); - if (end == -1) { - end = data.length(); - } - } - - // 提取Modem版本信息 - modemVerInfo = data.mid(start, end - start).trimmed(); - qDebug() << "使用@modemVer:格式提取Modem Version:" << modemVerInfo; - } - // 方法3:查找"modemver(...)"括号格式 - else { - // 使用正则表达式提取括号内的内容 - QRegExp modemPattern("modemver\\(([^)]+)\\)", Qt::CaseInsensitive); - if (modemPattern.indexIn(data) != -1) { - modemVerInfo = modemPattern.cap(1); - qDebug() << "使用括号格式提取Modem Version:" << modemVerInfo; - } else { - qDebug() << "未找到Modem Version信息,数据格式:" << data.trimmed(); - return; - } - } - } - - // 验证Modem Version是否有效(不为空即可) - if (!modemVerInfo.isEmpty()) { - // 保存到成员变量 - currentModemVersion = modemVerInfo; - // 显示到ModemVer文本框 - ui->ModemVerlineEdit->setText(modemVerInfo); - qDebug() << "提取的Modem Version:" << modemVerInfo; - qDebug() << "MODEM类型判断: " << (isEGModem() ? "EG系列" : "非EG系列"); - - // 清理版本字符串:移除特殊字符,去除首尾空格 - modemVerInfo = modemVerInfo.replace("!", "").trimmed(); - currentModemVersion = modemVerInfo; - ui->ModemVerlineEdit->setText(modemVerInfo); - - // 校验期望MODEM_VER - QString expectedModem = ui->expectedModemVerLineEdit ? ui->expectedModemVerLineEdit->text().trimmed() : QString(); - // 对期望值也进行相同的清理处理 - if (!expectedModem.isEmpty()) { - expectedModem = expectedModem.replace("!", "").trimmed(); - } - - qDebug() << "提取的Modem Version:" << modemVerInfo; - qDebug() << "期望的Modem Version:" << expectedModem; - - // 使用大小写不敏感对比,更宽松 - if (!expectedModem.isEmpty() && modemVerInfo.compare(expectedModem, Qt::CaseInsensitive) != 0) { - ui->statusbar->showMessage("MODEM版本不一致: 实际=" + modemVerInfo + " 期望=" + expectedModem, 5000); - QMessageBox::critical(this, "版本校验失败", "MODEM_VER不一致,测试失败!\n实际: " + modemVerInfo + "\n期望: " + expectedModem); - isProductionTestRunning = false; - currentTestStep = TEST_COMPLETED; - showFinalTestResult(); - return; - } - } else { - qDebug() << "Modem Version信息为空,数据:" << data.trimmed(); - } -} - -// 提取PYVER Version信息(支持多种格式) -void MainWindow::ShowPppInfoPYVer(const QString& data) -{ - QString PYVerInfo; - int start; - - // 方法1:查找"Modem Version:"标识的格式(传统格式) - start = data.indexOf("PYVER Version:", 0, Qt::CaseInsensitive); - if (start != -1) { - // 计算版本信息的起始位置(跳过"Modem Version: ") - const QString key = "PYVER Version:"; - start += key.length(); - - // 跳过可能的空格 - while (start < data.length() && data.at(start) == ' ') { - start++; - } - - // 查找结束位置:优先查找@modemVer:(如果PYVer和ModemVer在同一行) - int end = data.indexOf("@modemVer:", start, Qt::CaseInsensitive); - if (end == -1) { - // 如果没有@modemVer:,查找行结束标志(换行符或字符串结束) - end = data.indexOf('\n', start); - if (end == -1) { - end = data.indexOf('\r', start); - if (end == -1) { - end = data.length(); - } - } - } - - // 提取PYVer版本信息(到@modemVer:之前或行尾) - PYVerInfo = data.mid(start, end - start).trimmed(); - } - // 方法2:查找"@pyver:"标识的格式(新格式) - else { - start = data.indexOf("@pyver:", 0, Qt::CaseInsensitive); - if (start != -1) { - // 计算版本信息的起始位置(跳过"@pyver:") - const QString key = "@pyver:"; - start += key.length(); - - // 查找结束位置:优先查找@modemVer:(如果PYVer和ModemVer在同一行) - int end = data.indexOf("@modemVer:", start, Qt::CaseInsensitive); - if (end == -1) { - // 如果没有@modemVer:,查找行结束标志(换行符或字符串结束) - end = data.indexOf('\n', start); - if (end == -1) { - end = data.indexOf('\r', start); - if (end == -1) { - end = data.length(); - } - } - } - - // 提取PYVer版本信息(到@modemVer:之前或行尾) - PYVerInfo = data.mid(start, end - start).trimmed(); - qDebug() << "使用@pyver:格式提取pyver:" << PYVerInfo; - } - // 方法3:查找"modemver(...)"括号格式 - else { - // 使用正则表达式提取括号内的内容 - QRegExp PYPattern("pyver\\(([^)]+)\\)", Qt::CaseInsensitive); - if (PYPattern.indexIn(data) != -1) { - PYVerInfo = PYPattern.cap(1); - qDebug() << "使用括号格式提取pvver :" << PYVerInfo; - } else { - qDebug() << "使用括号格式提取pvver :" << data.trimmed(); - return; - } - } - } - - // 验证PYVER Version是否有效(不为空即可) - if (!PYVerInfo.isEmpty()) { - // 清理版本字符串:移除特殊字符,去除首尾空格 - PYVerInfo = PYVerInfo.replace("!", "").trimmed(); - - // 保存到成员变量 - currentPYVerVersion = PYVerInfo; - // 显示到PYVer文本框 - ui->PYVerlineEdit->setText(PYVerInfo); - qDebug() << "提取的PYVER Version:" << PYVerInfo; - qDebug() << "MODEM类型判断: " << (isEGModem() ? "EG系列" : "非EG系列"); - - // 校验期望PYVER - QString expectedPYVER = ui->expectedPYVerLineEdit ? ui->expectedPYVerLineEdit->text().trimmed() : QString(); - // 对期望值也进行相同的清理处理 - if (!expectedPYVER.isEmpty()) { - expectedPYVER = expectedPYVER.replace("!", "").trimmed(); - } - - qDebug() << "提取的PYVER Version:" << PYVerInfo; - qDebug() << "期望的PYVER Version:" << expectedPYVER; - - // 使用大小写不敏感对比,更宽松 - if (!expectedPYVER.isEmpty() && PYVerInfo.compare(expectedPYVER, Qt::CaseInsensitive) != 0) { - ui->statusbar->showMessage("PYVER版本不一致: 实际=" + PYVerInfo + " 期望=" + expectedPYVER, 5000); - QMessageBox::critical(this, "版本校验失败", "PYVER不一致,测试失败!\n实际: " + PYVerInfo + "\n期望: " + expectedPYVER); - isProductionTestRunning = false; - currentTestStep = TEST_COMPLETED; - showFinalTestResult(); - return; - } - } else { - qDebug() << "PYVER Version信息为空,数据:" << data.trimmed(); - } -} - -// IMEI格式验证函数 - IMEI应为15位数字 -bool MainWindow::isValidIMEI(const QString& imei) -{ - // 检查是否为空 - if (imei.isEmpty()) { - return false; - } - - // 检查长度是否为15位 - if (imei.length() != 15) { - qDebug() << "IMEI长度不正确,应为15位,实际为:" << imei.length() << "位,IMEI:" << imei; - return false; - } - - // 检查是否全为数字 - for (int i = 0; i < imei.length(); i++) { - if (!imei.at(i).isDigit()) { - qDebug() << "IMEI包含非数字字符,IMEI:" << imei; - return false; - } - } - - qDebug() << "IMEI格式验证通过:" << imei; - return true; -} - -// ICCID格式验证函数 - ICCID应为19-20位字母数字组合 -bool MainWindow::isValidICCID(const QString& iccid) -{ - // 检查是否为空 - if (iccid.isEmpty()) { - return false; - } - - // 检查长度是否为19-20位 - if (iccid.length() < 19 || iccid.length() > 20) { - qDebug() << "ICCID长度不正确,应为19-20位,实际为:" << iccid.length() << "位,ICCID:" << iccid; - return false; - } - - // 检查是否为字母数字组合(允许数字和字母) - for (int i = 0; i < iccid.length(); i++) { - QChar ch = iccid.at(i); - if (!ch.isDigit() && !ch.isLetter()) { - qDebug() << "ICCID包含非法字符(只允许数字和字母),ICCID:" << iccid; - return false; - } - } - - qDebug() << "ICCID格式验证通过:" << iccid; - return true; -} - -// MODEM类型判断函数 - 判断是否为EG系列MODEM -bool MainWindow::isEGModem() const -{ - bool result = currentModemVersion.startsWith("EG", Qt::CaseInsensitive); - - if(!ui->PYVerlineEdit->text().isEmpty()) - { - qDebug() << "存在PYVER版本,判断为EG版本" << currentPYVerVersion; - return true; - } - // else { - // qDebug() << "不存在PYVER版本,判断为非EG版本" << PYVerInfo; - // return false; - // } - - qDebug() << "isEGModem() 调用 - MODEM版本:" << currentModemVersion << ",结果:" << (result ? "EG系列" : "非EG系列"); - return result; -} - -// 解析MESSAGE_4G_NETWORK_STAT_CHANGED格式数据 +// 解析MESSAGE_4G_NETWORK_STAT_CHANGED格式数据(4G功能已移除,保留空实现以兼容旧日志) void MainWindow::parseNetworkStatChanged(const QString& data) { - qDebug() << "开始解析MESSAGE_4G_NETWORK_STAT_CHANGED数据:" << data.trimmed(); - - // 使用正则表达式解析各个字段 - // 格式:netstat(0),signal(99),sim(1),network(GSM),imei(860132064650112),iccid(898604B7192590313291),operator(-1) - - // 解析netstat - QRegExp netstatRegex("netstat\\((\\d+)\\)"); - if (netstatRegex.indexIn(data) != -1) { - int netstat = netstatRegex.cap(1).toInt(); - qDebug() << "解析到netstat值:" << netstat; - } - - // 解析signal值 - QRegExp signalRegex("signal\\((\\d+)\\)"); - if (signalRegex.indexIn(data) != -1) { - int signalValue = signalRegex.cap(1).toInt(); - qDebug() << "解析到signal值:" << signalValue; - - // 将signal值显示到界面 - appendToLogFile(QString("Network Stat Signal值: %1\n").arg(signalValue)); - ui->statusbar->showMessage(QString("检测到Network Stat Signal值: %1").arg(signalValue), 2000); - - // 如果正在进行4G测试,处理signal值 - if (isProductionTestRunning && (currentTestStep == TEST_PPP_INIT || currentTestStep == TEST_PPP_SIGNAL)) { - // 构造类似@sim:@signal:格式的数据供现有逻辑处理 - QString simulatedData = QString("@sim:%1@signal:%2@").arg(1).arg(signalValue); // 假设sim为1 - - // 解析sim值(从数据中提取实际值) - QRegExp simRegex("sim\\((\\d+)\\)"); - if (simRegex.indexIn(data) != -1) { - int simValue = simRegex.cap(1).toInt(); - simulatedData = QString("@sim:%1@signal:%2@").arg(simValue).arg(signalValue); - qDebug() << "解析到sim值:" << simValue; - - // 保存SIM卡状态 - currentSimStatus = simValue; - qDebug() << "更新SIM卡状态:" << (currentSimStatus == 1 ? "已插入" : "未插入"); - } - - qDebug() << "构造模拟数据供现有逻辑处理:" << simulatedData; - checkSignalAndSimValues(simulatedData); - } - } - - // 解析sim值 - QRegExp simRegex("sim\\((\\d+)\\)"); - if (simRegex.indexIn(data) != -1) { - int simValue = simRegex.cap(1).toInt(); - qDebug() << "解析到sim值:" << simValue; - - // 保存SIM卡状态 - currentSimStatus = simValue; - qDebug() << "更新SIM卡状态:" << (currentSimStatus == 1 ? "已插入" : "未插入"); - } - - // 解析network类型 - QRegExp networkRegex("network\\(([^)]+)\\)"); - if (networkRegex.indexIn(data) != -1) { - QString networkType = networkRegex.cap(1); - qDebug() << "解析到network类型:" << networkType; - } - - // 解析IMEI - QRegExp imeiRegex("imei\\((\\d{15})\\)"); - if (imeiRegex.indexIn(data) != -1) { - QString imei = imeiRegex.cap(1); - qDebug() << "解析到IMEI:" << imei; - - // 验证IMEI格式 - if (isValidIMEI(imei)) { - ui->IMEIlineEdit->setText(imei); - qDebug() << "IMEI格式验证通过,已更新到界面:" << imei; - } else { - qDebug() << "IMEI格式验证失败:" << imei; - } - } - - // 解析ICCID - QRegExp iccidRegex("iccid\\(([0-9A-Fa-f]{19,20})\\)"); - if (iccidRegex.indexIn(data) != -1) { - QString iccid = iccidRegex.cap(1); - qDebug() << "解析到ICCID:" << iccid; - - // 验证ICCID格式 - if (isValidICCID(iccid)) { - ui->ICCIDlineEdit->setText(iccid); - qDebug() << "ICCID格式验证通过,已更新到界面:" << iccid; - } else { - qDebug() << "ICCID格式验证失败:" << iccid; - } - } - - // 解析operator - QRegExp operatorRegex("operator\\(([^)]+)\\)"); - if (operatorRegex.indexIn(data) != -1) { - QString operatorValue = operatorRegex.cap(1); - qDebug() << "解析到operator值:" << operatorValue; - } + Q_UNUSED(data); } // 串口数据接收处理函数 @@ -1155,115 +593,14 @@ void MainWindow::readSerialData() } } - // 检测设备信息数据(去掉[PPP_INFO]限制,支持多种格式)(大小写不敏感) - // 检测包含@sim:的数据(可能是IMEI数据和4G信号数据) - if (line.contains("@sim:") || processLine.contains("@sim:")) { - qDebug() << "检测到包含@sim:的数据:"; - qDebug() << " 原始line:" << line.trimmed(); - qDebug() << " processLine:" << processLine.trimmed(); - - // 优先处理processLine - QString dataToProcess = processLine.contains("@sim:") ? processLine : line; - - // 提取IMEI(15位数字) - QRegExp imeiRegex("(\\d{15})"); - if (imeiRegex.indexIn(dataToProcess) != -1) { - QString extractedImei = imeiRegex.cap(1); - qDebug() << "从@sim:数据中提取到IMEI:" << extractedImei; - // 验证IMEI格式 - if (isValidIMEI(extractedImei)) { - ui->IMEIlineEdit->setText(extractedImei); - } else { - qDebug() << "从@sim:数据中提取的IMEI格式不正确:" << extractedImei; - ui->statusbar->showMessage("IMEI格式不正确: " + extractedImei, 3000); - } - } else { - qDebug() << "在@sim:数据中未找到15位数字"; - ShowPppInfoIMEI(dataToProcess); - } - - // 检查是否正在进行4G测试,如果是则解析signal和sim值进行判断 - if (isProductionTestRunning && (currentTestStep == TEST_PPP_INIT || currentTestStep == TEST_PPP_SIGNAL)) { - checkSignalAndSimValues(dataToProcess); - } - } - // 检测传统格式的IMEI数据:IMEI:开头的格式 - else if (line.indexOf("IMEI:", 0, Qt::CaseInsensitive) != -1 || processLine.indexOf("IMEI:", 0, Qt::CaseInsensitive) != -1) { - qDebug() << "接收到IMEI数据:" << line.trimmed(); - qDebug() << "原始数据长度:" << line.length() << "内容:" << line.toUtf8().toHex(); - if (processLine.indexOf("IMEI:", 0, Qt::CaseInsensitive) != -1) { - ShowPppInfoIMEI(processLine); - } else { - ShowPppInfoIMEI(line); - } - } - // 检测新格式的IMEI数据:imei:开头的格式 - else if (line.indexOf("imei:", 0, Qt::CaseInsensitive) != -1 || processLine.indexOf("imei:", 0, Qt::CaseInsensitive) != -1) { - qDebug() << "接收到新格式IMEI数据:" << line.trimmed(); - if (processLine.indexOf("imei:", 0, Qt::CaseInsensitive) != -1) { - ShowPppInfoIMEI(processLine); - } else { - ShowPppInfoIMEI(line); - } - } - - if (line.indexOf("ICCID:", 0, Qt::CaseInsensitive) != -1) { - qDebug() << "接收到ICCID数据:" << line.trimmed(); - ShowPppInfoICCID(line); - } - - if (line.indexOf("Modem Version:", 0, Qt::CaseInsensitive) != -1) { - qDebug() << "接收到Modem Version数据:" << line.trimmed(); - ShowPppInfoModemVer(line); - } - // 检测新格式的Modem Version数据:@modemVer:开头的格式 - else if (line.indexOf("@modemVer:", 0, Qt::CaseInsensitive) != -1) { - qDebug() << "接收到新格式Modem Version数据:" << line.trimmed(); - ShowPppInfoModemVer(line); - } - // 检测括号格式的Modem Version数据:modemver(...)格式 - else if (line.indexOf("modemver(", 0, Qt::CaseInsensitive) != -1) { - qDebug() << "接收到括号格式Modem Version数据:" << line.trimmed(); - ShowPppInfoModemVer(line); - } - - if (line.indexOf("pyver:", 0, Qt::CaseInsensitive) != -1) { - qDebug() << "接收到pyver数据:" << line.trimmed(); - ShowPppInfoPYVer(line); - } - // 检测新格式的Modem Version数据:@modemVer:开头的格式 - else if (line.indexOf("@pyVer:", 0, Qt::CaseInsensitive) != -1) { - qDebug() << "接收到新格式接收到括号格式pyver数据数据:" << line.trimmed(); - ShowPppInfoPYVer(line); - } - // 检测括号格式的Modem Version数据:modemver(...)格式 - else if (line.indexOf("pyver(", 0, Qt::CaseInsensitive) != -1) { - qDebug() << "接收到括号格式pyver数据:" << line.trimmed(); - ShowPppInfoPYVer(line); - } - - // 检测MESSAGE_4G_NETWORK_STAT_CHANGED格式数据 - if (line.contains("MESSAGE_4G_NETWORK_STAT_CHANGED")) { - qDebug() << "接收到MESSAGE_4G_NETWORK_STAT_CHANGED数据:" << line.trimmed(); - parseNetworkStatChanged(line); - } + // 4G/PPP 相关解析已移除 // 检测工厂模式进入确认(用于产测) if (line.contains("fac:enter or exit facotry mode")) { checkFactoryModeResult(line); } - // 检测PPP初始化结果(用于产测) - if (line.contains("PPP environment initialized successfully") || - line.contains("PPP init failed")) { - check4GInitResult(line); - } - - // 检测4G信号质量结果(用于产测) - if (line.contains("Overall LTE Quality") || line.contains("LTE Signal Quality") || - line.contains("RSRQ:") || line.contains("RSRP:") || line.contains("总体评级")) { - check4GSignalResult(line); - } + // 4G/PPP 相关解析已移除 // 检测LCD测试结果(用于产测) if (line.contains("LCD test")) { @@ -1276,15 +613,13 @@ void MainWindow::readSerialData() } // 检测按键测试结果(用于产测) - if (line.contains("fac:id")) { + if (line.contains("fac:id") || line.contains("testPresenter::OnRotate")) { checkKeyTestResult(line); } // 检测录音测试结果(用于产测) - if (line.contains("MEDIAAUDIORECORD") || line.contains("MEDIAMUSICPLAY") || - line.contains("Start Audio Capturer SUCCESS") || - line.contains("fac:mic level:")) { - qDebug() << "录音测试相关数据匹配:" << line.trimmed(); + if ((line.contains("AudioTest", Qt::CaseInsensitive) || line.contains("mic level", Qt::CaseInsensitive)) && + isProductionTestRunning && currentTestStep == TEST_AUDIO) { checkAudioTestResult(line); } @@ -1294,48 +629,41 @@ void MainWindow::readSerialData() } // 检测G-sensor测试结果(用于产测) - if (line.contains("gsensor") || line.contains("GSENSOR") || line.contains("fac:gsensor")) { - qDebug() << "检测到gsensor相关数据,调用checkGsensorTestResult:" << line.trimmed(); + // 设备输出格式可能为:XYZ: X=-1.28,Y=8.22,Z=-4.60 + if (line.contains("XYZ:") || line.contains("gsensor") || line.contains("GSENSOR") || line.contains("fac:gsensor")) { + qDebug() << "检测到gsensor/XYZ相关数据,调用checkGsensorTestResult:" << line.trimmed(); checkGsensorTestResult(line); } + + // 检测漏光测试结果 + if (line.contains("leaklight:", Qt::CaseInsensitive) || line.contains("green:", Qt::CaseInsensitive) || + line.contains("red:", Qt::CaseInsensitive) || line.contains("ir:", Qt::CaseInsensitive)) { + checkLeaklightTestResult(line); + } // 检测蓝牙测试结果(用于产测) - if (line.contains("fac:Device:") || line.contains("[BtScan]") || line.contains("BTSCANTEST")) { + // 扩大触发条件:fac:Device、[BtScan]、BTSCANTEST、RSSI+Device、RSSI+MAC、或RSSI+MAC地址格式(xx:xx:xx:xx:xx:xx) + bool btRelated = line.contains("fac:Device:") || line.contains("[BtScan]") || line.contains("BTSCANTEST") || + (line.contains("RSSI", Qt::CaseInsensitive) && (line.contains("Device", Qt::CaseInsensitive) || line.contains("MAC", Qt::CaseInsensitive) || line.count(':') >= 5)); + if (btRelated && isProductionTestRunning && currentTestStep == TEST_BLUETOOTH) { qDebug() << "检测到蓝牙测试相关数据,调用checkBluetoothTestResult:" << line.trimmed(); checkBluetoothTestResult(line); } - // 检测CAT1信号测试结果 - 只在CAT1测试期间且包含特定格式时处理 - if (hasReceivedFirstSignal && isProductionTestRunning && line.contains("signal:")) { - // 根据MODEM类型过滤特定格式的数据 - bool shouldProcess = false; - if (isEGModem()) { - // EG系列:只处理rx_buffer格式,跳过cmd格式 - shouldProcess = line.contains("rx_buffer:") && !line.contains("cmd ") && !line.contains("bufferlen"); - } else { - // N7系列:处理PPPSIGNALINFO相关格式 - shouldProcess = line.contains("RSRP:") || line.contains("signal:"); - } - - if (shouldProcess) { - qDebug() << "检测到CAT1信号相关数据:" << line.trimmed(); - checkCat1SignalResult(line); - } else { - qDebug() << "跳过CAT1信号数据(格式不匹配):" << line.left(50) + "..."; - } + // 检测SE芯片测试结果(cywee is active / cywee is not active) + if (line.contains("cywee", Qt::CaseInsensitive)) { + checkSeChipTestResult(line); } - // 检测Ping测试结果 - if (isProductionTestRunning && currentTestStep == TEST_PPP_PING && - (line.contains("PING", Qt::CaseInsensitive) || - line.contains("bytes from", Qt::CaseInsensitive) || - line.contains("Reply from", Qt::CaseInsensitive) || - line.contains("timeout", Qt::CaseInsensitive) || - line.contains("unreachable", Qt::CaseInsensitive))) { - qDebug() << "检测到Ping测试相关数据:" << line.trimmed(); - checkPingTestResult(line); + // 检测GPS测试结果(GPSINFO / trace 等) + if ((line.contains("GPSINFO", Qt::CaseInsensitive) || line.contains("num_sats", Qt::CaseInsensitive) || + line.contains("trace", Qt::CaseInsensitive)) && + isProductionTestRunning && currentTestStep == TEST_GPS) { + checkGpsTestResult(line); } + // 4G/PPP/Ping/CAT1 相关解析已移除 + // 检测LCD背光状态 if (line.contains("LCD backlight turned on")) { lcdBacklightOn = true; @@ -1406,7 +734,6 @@ void MainWindow::on_ProductionTestButton_clicked() if (isTestItemEnabled("lcd")) { currentTestStep = TEST_LCD; currentLcdColor = 1; // 从红色开始 - initRetry4GCount = 0; ui->statusbar->showMessage("开始产测流程...", 2000); qDebug() << "开始产测流程"; @@ -1419,7 +746,6 @@ void MainWindow::on_ProductionTestButton_clicked() // LCD未启用,跳过到下一个启用的测试 currentTestStep = TEST_LCD; // 先设置为LCD,然后skipToNextEnabledTest会跳过 currentLcdColor = 1; // 初始化LCD颜色 - initRetry4GCount = 0; ui->statusbar->showMessage("开始产测流程...", 2000); qDebug() << "开始产测流程,LCD测试未启用,跳过"; @@ -1654,23 +980,16 @@ void MainWindow::performMotorTestContinue() motorTestPopupShown = false; updateTestStatus("motor", STATUS_TESTING); - ui->statusbar->showMessage("马达测试中 - 开启2秒", 2000); + ui->statusbar->showMessage("马达测试中 - 开启2秒后确认", 2000); - // 发送马达开启指令 serial->write("AT^VIBRATORTIMER=1\r\n"); qDebug() << "发送马达开启指令: AT^VIBRATORTIMER=1"; - // 先停止可能正在运行的定时器 if (motorTestTimer->isActive()) { motorTestTimer->stop(); - qDebug() << "警告:马达定时器已在运行,先停止"; } - - // 设置2秒定时器 - qDebug() << "启动马达定时器,2秒后关闭马达"; motorTestTimer->start(2000); - // 启动10秒超时检测 testTimeoutTimer->start(10000); } @@ -1683,14 +1002,10 @@ void MainWindow::onMotorTestTimer() return; } - qDebug() << "马达测试时间到,关闭马达"; - ui->statusbar->showMessage("马达测试中 - 关闭", 1000); - - // 发送马达关闭指令 - serial->write("AT^VIBRATORTIMER=0\r\n"); - qDebug() << "发送马达关闭指令: AT^VIBRATORTIMER=0"; + qDebug() << "马达测试时间到,显示确认弹窗"; + ui->statusbar->showMessage("马达测试 - 请确认", 1000); - // 马达测试完成,显示确认弹窗 + // 马达测试完成,显示确认弹窗(不再发送 VIBRATORTIMER=0) qDebug() << "马达测试完成,显示确认弹窗"; // 如果弹窗还没有显示过,则显示确认弹窗 @@ -1703,82 +1018,7 @@ void MainWindow::onMotorTestTimer() } } -// 4G测试函数 -void MainWindow::perform4GTest() -{ - // 检查4G测试是否启用 - if (!isTestItemEnabled("ppp")) { - qDebug() << "4G测试未启用,跳过"; - updateTestStatus("ppp", STATUS_UNTESTED); - // 4G测试是最后一个测试,如果未启用,直接完成测试 - if (isProductionTestRunning) { - currentTestStep = TEST_COMPLETED; - isProductionTestRunning = false; - QString fileName = saveTestRecord(); - showFinalTestResult(); - } - return; - } - - if (!isProductionTestRunning) { - qDebug() << "4G测试跳过 - 产测未运行"; - return; - } - - initRetry4GCount++; - qDebug() << "4G测试 - 初始化尝试次数:" << initRetry4GCount; - - if (initRetry4GCount == 1) { - ui->recvTextEdit->append("========== 开始4G测试 =========="); - } else { - ui->recvTextEdit->append(QString("========== 4G测试重试 (第%1次) ==========").arg(initRetry4GCount)); - } - updateTestStatus("ppp", STATUS_TESTING); - - // 4G电源已在初始化阶段开关,这里直接使用CAT1GETSIGNAL获取信号 - qDebug() << "4G电源已在初始化阶段开关,直接开始4G信号质量检测"; - ui->statusbar->showMessage("4G测试中 - 开始信号强度检测...", 3000); - - // 重置标志 - hasReceivedSignalSim = false; - hasReceivedPppInfo = true; // 标记为已收到,跳过PPP等待 - hasReceivedFirstSignal = false; // 重置,确保能重新启动CAT1测试 - pppCommandCount = 0; - - // 初始化CAT1信号测试计数器 - cat1SignalTestCount = 0; - cat1SignalReceivedCount = 0; - cat1SignalGoodCount = 0; - cat1Signal99Count = 0; - cat1SignalTotalValue = 0; - - // 停止可能正在运行的定时器 - if (cat1SignalTimer->isActive()) { - cat1SignalTimer->stop(); - qDebug() << "停止CAT1信号测试定时器"; - } - if (signal4GTimer->isActive()) { - signal4GTimer->stop(); - qDebug() << "停止4G信号检测定时器"; - } - - // 直接开始CAT1信号测试,使用CAT1GETSIGNAL指令 - currentTestStep = TEST_PPP_SIGNAL; - qDebug() << "开始CAT1信号测试,发送AT^CAT1GETSIGNAL指令"; - ui->recvTextEdit->append("========== 开始4G信号质量测试 =========="); - ui->statusbar->showMessage("开始CAT1信号测试,每2秒检测一次...", 3000); - - // 启动CAT1信号测试定时器,每2秒发送一次 - cat1SignalTimer->start(2000); - - // 立即发送第一次CAT1信号检测指令 - serial->write("AT^CAT1GETSIGNAL\r\n"); - cat1SignalTestCount++; - qDebug() << "发送第" << cat1SignalTestCount << "次CAT1信号检测指令: AT^CAT1GETSIGNAL"; - ui->recvTextEdit->append(">>> 发送指令: AT^CAT1GETSIGNAL"); -} - -// 检查4G初始化结果 +// 检查4G初始化结果(4G功能已移除) // 检查工厂模式进入结果 void MainWindow::checkFactoryModeResult(const QString& data) { @@ -1870,164 +1110,36 @@ void MainWindow::checkFactoryModeResult(const QString& data) } } -void MainWindow::check4GInitResult(const QString& data) +// 检查LCD测试结果 +void MainWindow::checkLcdTestResult(const QString& data) { - if (!isProductionTestRunning || currentTestStep != TEST_PPP_INIT) { - return; - } - - // 收到回复,停止超时定时器 - if (test4GTimer->isActive()) { - test4GTimer->stop(); - qDebug() << "收到4G回复,停止超时定时器"; - } - - if (data.contains("PPP environment initialized successfully")) { - qDebug() << "4G初始化通过(已废弃,不再使用PPP相关指令)"; - // PPP相关指令已失效,不再处理 + // 检测LCD测试回复信息,并且只在LCD测试阶段且未显示过弹窗时弹出 + // 修改:在最后一个颜色(蓝色)显示时才弹窗,让用户观察完整过程 + if (data.contains("LCD test") && currentTestStep == TEST_LCD && + !lcdTestPopupShown && isProductionTestRunning && currentLcdColor >= 3) { - } else if (data.contains("PPP init failed")) { - qDebug() << "4G初始化失败,重试中..."; - ui->statusbar->showMessage("4G初始化失败,重试中...", 2000); + qDebug() << "检测到LCD测试回复:" << data.trimmed(); + qDebug() << "准备显示LCD测试确认弹窗"; - // 如果重试次数过多,可以设置上限 - if (initRetry4GCount >= 10) { - qDebug() << "4G初始化重试次数过多,停止测试"; - ui->statusbar->showMessage("4G初始化失败次数过多,测试停止", 5000); - updateTestStatus("ppp", STATUS_FAILED); - isProductionTestRunning = false; - currentTestStep = TEST_IDLE; - - // 保存测试记录到Excel(失败记录) - QString fileName = saveTestRecord(); - - // 显示最终测试结果弹窗 - showFinalTestResult(); - // ui->ProductionTestButton->setText("开始产测"); - return; - } + // 标记弹窗已显示,避免重复弹出 + lcdTestPopupShown = true; + isWaitingForUserConfirm = true; - // 先发送停止指令,然后延迟重试 - qDebug() << "4G失败重试前发送停止指令"; - serial->write("AT^VIBRATORTIMER=0\r\n"); // 停止马达 - serial->write("AT^LCDTEST=2\r\n"); // 退出LCD测试 + // 注意:不暂停LCD定时器,让颜色切换继续进行 + // LCD颜色切换应该独立于用户确认过程 + qDebug() << "显示LCD确认弹窗,但颜色切换继续进行"; - QTimer::singleShot(1000, this, [this]() { - qDebug() << "停止指令已发送,开始4G失败重试"; - perform4GTest(); - }); + // LCD测试回复已检测到,弹窗处理由performLcdTest中的定时器处理 + + // 不需要在这里处理弹窗,由performLcdTest中的定时器处理 } } -// 4G测试超时处理 -void MainWindow::on4GTestTimeout() +// 显示LCD测试确认弹窗 +void MainWindow::showLcdTestConfirmDialog() { - test4GTimer->stop(); - - if (!isProductionTestRunning || currentTestStep != TEST_PPP_INIT) { - qDebug() << "4G超时但测试状态已改变,忽略"; - return; - } - - // 检查当前是在等待PPP回复还是等待signal和sim数据 - if (pppCommandCount <= 3) { - // 前3次,等待PPP数据超时 - if (hasReceivedPppInfo) { - qDebug() << "PPP数据等待超时,但已收到PPPINFO,继续正常流程"; - return; - } - - qDebug() << "PPP数据第" << pppCommandCount << "次等待超时,准备重试"; - - // 重新等待PPP数据 - QTimer::singleShot(1000, this, [this]() { - if (isProductionTestRunning) { - sendPppCommand(); - } - }); - return; - } else { - // 超过3次后,等待signal和sim数据超时 - if (hasReceivedSignalSim) { - qDebug() << "4G测试超时,但已收到signal和sim数据,测试已完成"; - return; // 已经在checkSignalAndSimValues中处理了结果 - } - - qDebug() << "4G测试超时,5秒内未收到signal和sim数据,判断为失败"; - ui->statusbar->showMessage("4G测试失败 - 5秒内未收到signal和sim数据", 3000); - } - - // 直接判断为失败,不再重试 - updateTestStatus("ppp", STATUS_FAILED); - isProductionTestRunning = false; - currentTestStep = TEST_COMPLETED; - - // 保存测试记录 - QString fileName = saveTestRecord(); - - // 显示最终测试结果弹窗 - showFinalTestResult(); - return; - - // 检查重试次数(这部分代码现在不会执行到) - if (initRetry4GCount >= 10) { - qDebug() << "4G初始化重试次数过多,停止测试"; - ui->statusbar->showMessage("4G初始化失败次数过多,测试停止", 5000); - updateTestStatus("ppp", STATUS_FAILED); - isProductionTestRunning = false; - currentTestStep = TEST_IDLE; - - // 保存测试记录到Excel(失败记录) - QString fileName = saveTestRecord(); - - // 显示最终测试结果弹窗 - showFinalTestResult(); - return; - } - - // 先发送停止指令,然后延迟重试 - qDebug() << "PPP重试前发送停止指令"; - serial->write("AT^VIBRATORTIMER=0\r\n"); // 停止马达 - // serial->write("AT^LCDTEST=2\r\n"); // 退出LCD测试 - 已屏蔽 - - QTimer::singleShot(1000, this, [this]() { - if (isProductionTestRunning && currentTestStep == TEST_PPP_INIT) { - qDebug() << "停止指令已发送,开始PPP重试"; - perform4GTest(); - } - }); -} - -// 检查LCD测试结果 -void MainWindow::checkLcdTestResult(const QString& data) -{ - // 检测LCD测试回复信息,并且只在LCD测试阶段且未显示过弹窗时弹出 - // 修改:在最后一个颜色(蓝色)显示时才弹窗,让用户观察完整过程 - if (data.contains("LCD test") && currentTestStep == TEST_LCD && - !lcdTestPopupShown && isProductionTestRunning && currentLcdColor >= 3) { - - qDebug() << "检测到LCD测试回复:" << data.trimmed(); - qDebug() << "准备显示LCD测试确认弹窗"; - - // 标记弹窗已显示,避免重复弹出 - lcdTestPopupShown = true; - isWaitingForUserConfirm = true; - - // 注意:不暂停LCD定时器,让颜色切换继续进行 - // LCD颜色切换应该独立于用户确认过程 - qDebug() << "显示LCD确认弹窗,但颜色切换继续进行"; - - // LCD测试回复已检测到,弹窗处理由performLcdTest中的定时器处理 - - // 不需要在这里处理弹窗,由performLcdTest中的定时器处理 - } -} - -// 显示LCD测试确认弹窗 -void MainWindow::showLcdTestConfirmDialog() -{ - if (lcdTestPopupShown) { - qDebug() << "LCD确认弹窗已显示过,跳过"; + if (lcdTestPopupShown) { + qDebug() << "LCD确认弹窗已显示过,跳过"; return; } @@ -2040,7 +1152,7 @@ void MainWindow::showLcdTestConfirmDialog() // 弹出确认对话框让测试人员确认LCD显示是否正常 QMessageBox msgBox(this); msgBox.setWindowTitle("LCD测试确认"); - msgBox.setText("LCD测试正在进行,请观察LCD是否正常显示红、绿、蓝色切换"); + msgBox.setText("LCD测试正在进行,请观察LCD是否正常显示红、绿、蓝、白色切换"); msgBox.setInformativeText("观察完成后请选择LCD显示结果:"); msgBox.setIcon(QMessageBox::Question); @@ -2349,12 +1461,22 @@ void MainWindow::showMotorTestConfirmDialog() if (msgBox.clickedButton() == successButton) { // 测试人员确认马达震动正常 qDebug() << "测试人员确认:马达震动正常"; + if (serial && serial->isOpen()) { + serial->write("AT^VIBRATORTIMER=0\r\n"); + qDebug() << "马达测试通过:发送停止马达指令 AT^VIBRATORTIMER=0"; + ui->recvTextEdit->append(">>> 发送指令: AT^VIBRATORTIMER=0 (测试通过,关闭马达)"); + } ui->statusbar->showMessage("马达测试通过 - 震动正常!", 3000); updateTestStatus("motor", STATUS_SUCCESS); } else if (msgBox.clickedButton() == failButton) { // 测试人员确认马达震动异常 qDebug() << "测试人员确认:马达震动异常"; + if (serial && serial->isOpen()) { + serial->write("AT^VIBRATORTIMER=0\r\n"); + qDebug() << "马达测试失败:发送停止马达指令 AT^VIBRATORTIMER=0"; + ui->recvTextEdit->append(">>> 发送指令: AT^VIBRATORTIMER=0 (测试失败,关闭马达)"); + } ui->statusbar->showMessage("马达测试失败 - 震动异常!", 3000); updateTestStatus("motor", STATUS_FAILED); @@ -2401,6 +1523,302 @@ void MainWindow::showMotorTestConfirmDialog() } } +// 执行触控测试(AT^TESTJUMP=TP) +void MainWindow::performTouchTest() +{ + if (!isProductionTestRunning || currentTestStep != TEST_TOUCH) { + return; + } + if (!isTestItemEnabled("tp")) { + qDebug() << "触控测试未启用,跳过"; + updateTestStatus("tp", STATUS_UNTESTED); + skipToNextEnabledTest(); + return; + } + + if (testTimeoutTimer->isActive()) { + testTimeoutTimer->stop(); + } + + ui->recvTextEdit->append("========== 开始触控测试(TESTJUMP=TP) =========="); + updateTestStatus("tp", STATUS_TESTING); + ui->statusbar->showMessage("触控测试中:将进入触控测试界面,请等待判断弹窗", 3000); + + // 1) 每次跳转前先回到产测界面 + if (serial && serial->isOpen()) { + ui->recvTextEdit->append(">>> 发送指令: AT^ENTERTESTVIEW (进入产测界面)"); + serial->write("AT^ENTERTESTVIEW\r\n"); + qDebug() << "发送指令: AT^ENTERTESTVIEW (触控测试前)"; + } + + // 2) 延迟后进入对应测试跳转界面 + QTimer::singleShot(300, this, [this]() { + if (!isProductionTestRunning || currentTestStep != TEST_TOUCH) return; + if (!serial || !serial->isOpen()) return; + + ui->recvTextEdit->append(">>> 发送指令: AT^TESTJUMP=TP"); + serial->write("AT^TESTJUMP=TP\r\n"); + qDebug() << "发送指令: AT^TESTJUMP=TP"; + + // 3) 进入界面后弹窗让用户判断 + QTimer::singleShot(500, this, [this]() { + if (isProductionTestRunning && currentTestStep == TEST_TOUCH) { + showTouchTestConfirmDialog(); + } + }); + }); +} + +// 触控测试确认弹窗 +void MainWindow::showTouchTestConfirmDialog() +{ + if (touchTestPopupShown) { + return; + } + + touchTestPopupShown = true; + isWaitingForUserConfirm = true; + + QMessageBox msgBox(this); + msgBox.setWindowTitle("触控测试确认"); + msgBox.setIcon(QMessageBox::Question); + msgBox.setText("触控测试已进入,请测试人员判断触控是否正常。"); + msgBox.setInformativeText("请选择触控测试结果:"); + msgBox.resize(420, 200); + + QPushButton *successButton = msgBox.addButton("✓ 测试通过", QMessageBox::AcceptRole); + QPushButton *failButton = msgBox.addButton("✗ 测试失败", QMessageBox::RejectRole); + QPushButton *retestButton = msgBox.addButton("🔄 重新测试", QMessageBox::ResetRole); + + successButton->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; padding: 12px 20px; border-radius: 4px; font-weight: bold; font-size: 16px; }"); + failButton->setStyleSheet("QPushButton { background-color: #f44336; color: white; padding: 12px 20px; border-radius: 4px; font-weight: bold; font-size: 16px; }"); + retestButton->setStyleSheet("QPushButton { background-color: #FF9800; color: white; padding: 12px 20px; border-radius: 4px; font-weight: bold; font-size: 16px; }"); + + msgBox.exec(); + + isWaitingForUserConfirm = false; + + if (msgBox.clickedButton() == successButton) { + ui->statusbar->showMessage("触控测试通过", 2000); + updateTestStatus("tp", STATUS_SUCCESS); + } else if (msgBox.clickedButton() == failButton) { + ui->statusbar->showMessage("触控测试失败", 2000); + updateTestStatus("tp", STATUS_FAILED); + } else if (msgBox.clickedButton() == retestButton) { + // 重新测试 + touchTestPopupShown = false; + isWaitingForUserConfirm = false; + QTimer::singleShot(300, this, [this]() { + if (isProductionTestRunning && currentTestStep == TEST_TOUCH) { + performTouchTest(); + } + }); + return; + } + + touchTestPopupShown = false; + if (isProductionTestRunning && currentTestStep == TEST_TOUCH) { + skipToNextEnabledTest(); + } +} + +// 执行亮度测试(AT^BRIGHTRAMP) +void MainWindow::performBrightTest() +{ + if (!isProductionTestRunning || currentTestStep != TEST_BRIGHT) { + return; + } + if (!isTestItemEnabled("bright")) { + qDebug() << "亮度测试未启用,跳过"; + updateTestStatus("bright", STATUS_UNTESTED); + skipToNextEnabledTest(); + return; + } + + if (testTimeoutTimer->isActive()) { + testTimeoutTimer->stop(); + } + + ui->recvTextEdit->append("========== 开始亮度测试(AT^BRIGHTRAMP) =========="); + updateTestStatus("bright", STATUS_TESTING); + ui->statusbar->showMessage("亮度测试中:自动调节亮度,请观察后确认", 3000); + + if (!serial || !serial->isOpen()) { + return; + } + ui->recvTextEdit->append(">>> 发送指令: AT^BRIGHTRAMP"); + serial->write("AT^BRIGHTRAMP\r\n"); + qDebug() << "发送指令: AT^BRIGHTRAMP"; + + QTimer::singleShot(500, this, [this]() { + if (isProductionTestRunning && currentTestStep == TEST_BRIGHT) { + showBrightTestConfirmDialog(); + } + }); +} + +void MainWindow::showBrightTestConfirmDialog() +{ + if (brightTestPopupShown) { + return; + } + + brightTestPopupShown = true; + isWaitingForUserConfirm = true; + + QMessageBox msgBox(this); + msgBox.setWindowTitle("亮度测试确认"); + msgBox.setIcon(QMessageBox::Question); + msgBox.setText("亮度测试已进入,请测试人员判断亮度是否正常。"); + msgBox.setInformativeText("请选择亮度测试结果:"); + msgBox.resize(420, 200); + + QPushButton *successButton = msgBox.addButton("✓ 测试通过", QMessageBox::AcceptRole); + QPushButton *failButton = msgBox.addButton("✗ 测试失败", QMessageBox::RejectRole); + QPushButton *retestButton = msgBox.addButton("🔄 重新测试", QMessageBox::ResetRole); + + successButton->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; padding: 12px 20px; border-radius: 4px; font-weight: bold; font-size: 16px; }"); + failButton->setStyleSheet("QPushButton { background-color: #f44336; color: white; padding: 12px 20px; border-radius: 4px; font-weight: bold; font-size: 16px; }"); + retestButton->setStyleSheet("QPushButton { background-color: #FF9800; color: white; padding: 12px 20px; border-radius: 4px; font-weight: bold; font-size: 16px; }"); + + msgBox.exec(); + + isWaitingForUserConfirm = false; + + if (msgBox.clickedButton() == successButton) { + ui->statusbar->showMessage("亮度测试通过", 2000); + updateTestStatus("bright", STATUS_SUCCESS); + } else if (msgBox.clickedButton() == failButton) { + ui->statusbar->showMessage("亮度测试失败", 2000); + updateTestStatus("bright", STATUS_FAILED); + } else if (msgBox.clickedButton() == retestButton) { + brightTestPopupShown = false; + isWaitingForUserConfirm = false; + QTimer::singleShot(300, this, [this]() { + if (isProductionTestRunning && currentTestStep == TEST_BRIGHT) { + performBrightTest(); + } + }); + return; + } + + brightTestPopupShown = false; + if (isProductionTestRunning && currentTestStep == TEST_BRIGHT) { + skipToNextEnabledTest(); + } +} + +// 执行功耗测试(AT^TESTJUMP=DEEPSLEEP,进入后由人工确认) +void MainWindow::performDeepsleepTest() +{ + if (!isProductionTestRunning || currentTestStep != TEST_DEEPSLEEP) { + return; + } + if (!isTestItemEnabled("deepsleep")) { + qDebug() << "功耗测试未启用,跳过"; + updateTestStatus("deepsleep", STATUS_UNTESTED); + skipToNextEnabledTest(); + return; + } + + if (testTimeoutTimer->isActive()) { + testTimeoutTimer->stop(); + } + + ui->recvTextEdit->append("========== 开始功耗测试(TESTJUMP=DEEPSLEEP) =========="); + updateTestStatus("deepsleep", STATUS_TESTING); + ui->statusbar->showMessage("功耗测试:将进入深度睡眠测试界面,请等待确认弹窗", 3000); + + if (serial && serial->isOpen()) { + ui->recvTextEdit->append(">>> 发送指令: AT^ENTERTESTVIEW (进入产测界面)"); + serial->write("AT^ENTERTESTVIEW\r\n"); + qDebug() << "发送指令: AT^ENTERTESTVIEW (功耗测试前)"; + } + + QTimer::singleShot(300, this, [this]() { + if (!isProductionTestRunning || currentTestStep != TEST_DEEPSLEEP) { + return; + } + if (!serial || !serial->isOpen()) { + return; + } + ui->recvTextEdit->append(">>> 发送指令: AT^TESTJUMP=DEEPSLEEP"); + serial->write("AT^TESTJUMP=DEEPSLEEP\r\n"); + qDebug() << "发送指令: AT^TESTJUMP=DEEPSLEEP"; + + // 仅进入界面不会真正进深睡,需再发 FACTORYDEEPSLEEPENTER + QTimer::singleShot(400, this, [this]() { + if (!isProductionTestRunning || currentTestStep != TEST_DEEPSLEEP) { + return; + } + if (!serial || !serial->isOpen()) { + return; + } + ui->recvTextEdit->append(">>> 发送指令: AT^FACTORYDEEPSLEEPENTER"); + serial->write("AT^FACTORYDEEPSLEEPENTER\r\n"); + qDebug() << "发送指令: AT^FACTORYDEEPSLEEPENTER"; + appendToLogFile(">>> 发送指令: AT^FACTORYDEEPSLEEPENTER\n"); + + QTimer::singleShot(500, this, [this]() { + if (isProductionTestRunning && currentTestStep == TEST_DEEPSLEEP) { + showDeepsleepTestConfirmDialog(); + } + }); + }); + }); +} + +void MainWindow::showDeepsleepTestConfirmDialog() +{ + if (deepsleepTestPopupShown) { + return; + } + + deepsleepTestPopupShown = true; + isWaitingForUserConfirm = true; + + QMessageBox msgBox(this); + msgBox.setWindowTitle("功耗测试确认"); + msgBox.setIcon(QMessageBox::Question); + msgBox.setText("设备已进入深度睡眠(功耗)测试界面,请测试人员根据功耗/唤醒表现判断是否正常。"); + msgBox.setInformativeText("请选择功耗测试结果:"); + msgBox.resize(420, 200); + + QPushButton *successButton = msgBox.addButton("✓ 测试通过", QMessageBox::AcceptRole); + QPushButton *failButton = msgBox.addButton("✗ 测试失败", QMessageBox::RejectRole); + QPushButton *retestButton = msgBox.addButton("🔄 重新测试", QMessageBox::ResetRole); + + successButton->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; padding: 12px 20px; border-radius: 4px; font-weight: bold; font-size: 16px; }"); + failButton->setStyleSheet("QPushButton { background-color: #f44336; color: white; padding: 12px 20px; border-radius: 4px; font-weight: bold; font-size: 16px; }"); + retestButton->setStyleSheet("QPushButton { background-color: #FF9800; color: white; padding: 12px 20px; border-radius: 4px; font-weight: bold; font-size: 16px; }"); + + msgBox.exec(); + + isWaitingForUserConfirm = false; + + if (msgBox.clickedButton() == successButton) { + ui->statusbar->showMessage("功耗测试通过", 2000); + updateTestStatus("deepsleep", STATUS_SUCCESS); + } else if (msgBox.clickedButton() == failButton) { + ui->statusbar->showMessage("功耗测试失败", 2000); + updateTestStatus("deepsleep", STATUS_FAILED); + } else if (msgBox.clickedButton() == retestButton) { + deepsleepTestPopupShown = false; + isWaitingForUserConfirm = false; + QTimer::singleShot(300, this, [this]() { + if (isProductionTestRunning && currentTestStep == TEST_DEEPSLEEP) { + performDeepsleepTest(); + } + }); + return; + } + + deepsleepTestPopupShown = false; + if (isProductionTestRunning && currentTestStep == TEST_DEEPSLEEP) { + skipToNextEnabledTest(); + } +} + // 执行按键测试 void MainWindow::performKeyTest() { @@ -2434,48 +1852,58 @@ void MainWindow::performKeyTest() } qDebug() << "开始按键测试"; - ui->recvTextEdit->append("========== 开始按键测试 =========="); + ui->recvTextEdit->append("========== 开始按键测试(TESTJUMP=KEY) =========="); updateTestStatus("key", STATUS_TESTING); - ui->statusbar->showMessage("按键测试中,请按下设备上的按键", 10000); + ui->statusbar->showMessage("按键测试中,请按键0并执行旋转动作", 10000); - // 重置按键测试状态 + // 重置按键测试状态(仅按键0 + 旋转) keyTestStarted = true; for (int i = 0; i < 4; i++) { keyTestStatus[i] = 0; } + keyRotateDetected = false; + + // 先跳转产测主界面,再跳KEY测试界面 + if (serial && serial->isOpen()) { + ui->recvTextEdit->append(">>> 发送指令: AT^ENTERTESTVIEW (进入产测界面)"); + serial->write("AT^ENTERTESTVIEW\r\n"); + } + QTimer::singleShot(300, this, [this]() { + if (!isProductionTestRunning || currentTestStep != TEST_KEY) { + return; + } + if (serial && serial->isOpen()) { + ui->recvTextEdit->append(">>> 发送指令: AT^TESTJUMP=KEY"); + serial->write("AT^TESTJUMP=KEY\r\n"); + } + }); // 创建按键测试弹窗 keyTestDialog = new QDialog(this); keyTestDialog->setWindowTitle("按键测试"); - keyTestDialog->setFixedSize(450, 280); // 调大尺寸 + keyTestDialog->setFixedSize(460, 250); keyTestDialog->setModal(true); // 设置布局 QVBoxLayout *layout = new QVBoxLayout(keyTestDialog); - QLabel *instructionLabel = new QLabel("请按下设备上的按键,按钮会变色表示检测到"); + QLabel *instructionLabel = new QLabel("请检测按键0与旋转事件;检测到后按钮会变绿"); layout->addWidget(instructionLabel); - - // 创建按键按钮网格 - QGridLayout *gridLayout = new QGridLayout(); - - // 创建4个按键按钮 - for (int i = 0; i < 4; i++) { - keyButtons[i] = new QPushButton(QString("按键 %1").arg(i)); - keyButtons[i]->setFixedSize(60, 40); - keyButtons[i]->setStyleSheet( - "QPushButton { " - "background-color: #f0f0f0; " - "border: 2px solid #888888; " - "border-radius: 5px; " - "font-weight: bold; " - "}" - ); - - // 将按钮添加到网格布局(2x2) - gridLayout->addWidget(keyButtons[i], i / 2, i % 2); - } - - layout->addLayout(gridLayout); + + // 仅保留按键0和旋转两个检测项 + QHBoxLayout *indicators = new QHBoxLayout(); + keyButtons[0] = new QPushButton("按键 0"); + keyButtons[0]->setFixedSize(120, 44); + keyButtons[0]->setStyleSheet("QPushButton { background-color: #f0f0f0; border: 2px solid #888888; border-radius: 5px; font-weight: bold; }"); + + keyButtons[1] = new QPushButton("旋转"); + keyButtons[1]->setFixedSize(120, 44); + keyButtons[1]->setStyleSheet("QPushButton { background-color: #f0f0f0; border: 2px solid #888888; border-radius: 5px; font-weight: bold; }"); + keyButtons[2] = nullptr; + keyButtons[3] = nullptr; + + indicators->addWidget(keyButtons[0]); + indicators->addWidget(keyButtons[1]); + layout->addLayout(indicators); // 添加测试结果按钮 QHBoxLayout *buttonLayout = new QHBoxLayout(); @@ -2580,59 +2008,53 @@ void MainWindow::checkKeyTestResult(const QString& data) QRegExp keyPattern("fac:id\\s*:\\s*(\\d+)"); if (keyPattern.indexIn(data) != -1) { int keyId = keyPattern.cap(1).toInt(); - - if (keyId >= 0 && keyId <= 3) { + if (keyId == 0) { qDebug() << "检测到按键按下: ID =" << keyId; - - // 标记该按键已测试 - keyTestStatus[keyId] = 1; - - // 更新按钮颜色 - if (keyButtons[keyId]) { - keyButtons[keyId]->setStyleSheet( + keyTestStatus[0] = 1; + if (keyButtons[0]) { + keyButtons[0]->setStyleSheet( "QPushButton { " - "background-color: #4CAF50; " // 绿色表示已测试 + "background-color: #4CAF50; " "color: white; " "border: 2px solid #45a049; " "border-radius: 5px; " "font-weight: bold; " "}" ); - keyButtons[keyId]->setText(QString("按键 %1 ✓").arg(keyId)); + keyButtons[0]->setText("按键 0 ✓"); } - - ui->statusbar->showMessage(QString("检测到按键 %1").arg(keyId), 1000); - - // 检查是否所有按键都已测试 - bool allKeysTested = true; - for (int i = 0; i < 4; i++) { - if (keyTestStatus[i] == 0) { - allKeysTested = false; - break; - } - } - - if (allKeysTested) { - qDebug() << "所有按键都已检测到,自动通过按键测试"; - ui->statusbar->showMessage("所有按键检测完成,自动通过测试", 2000); - - // 延迟1秒后自动关闭弹窗并标记为通过 - QTimer::singleShot(1000, [this]() { - if (keyTestDialog && isProductionTestRunning && currentTestStep == TEST_KEY) { - keyTestDialog->accept(); - qDebug() << "按键测试自动通过"; - updateTestStatus("key", STATUS_SUCCESS); - - // 继续下一个测试 - qDebug() << "=== 按键测试自动完成,推进到下一个测试 ==="; - keyTestStarted = false; - skipToNextEnabledTest(); - } - }); - } else { - qDebug() << "按键 " << keyId << " 检测完成,等待其他按键或用户选择"; + ui->statusbar->showMessage("检测到按键 0", 1000); + } + } + + // 旋转事件:testPresenter::OnRotate + if (data.contains("testPresenter::OnRotate")) { + keyRotateDetected = true; + if (keyButtons[1]) { + keyButtons[1]->setStyleSheet( + "QPushButton { " + "background-color: #4CAF50; " + "color: white; " + "border: 2px solid #45a049; " + "border-radius: 5px; " + "font-weight: bold; " + "}" + ); + keyButtons[1]->setText("旋转 ✓"); + } + ui->statusbar->showMessage("检测到旋转事件 OnRotate", 1000); + } + + // 条件满足自动通过:按键0 + 旋转 + if (keyTestStatus[0] == 1 && keyRotateDetected) { + QTimer::singleShot(500, [this]() { + if (keyTestDialog && isProductionTestRunning && currentTestStep == TEST_KEY) { + keyTestDialog->accept(); + updateTestStatus("key", STATUS_SUCCESS); + keyTestStarted = false; + skipToNextEnabledTest(); } - } + }); } } @@ -2734,87 +2156,34 @@ void MainWindow::checkBuildVerResult(const QString& data) serial->write("AT^PMADDVOTE\r\n"); qDebug() << "发送AT^PMADDVOTE指令(第2次): AT^PMADDVOTE"; - // 如果4G测试已启用,先关闭和开启4G电源 - if (isTestItemEnabled("ppp")) { - qDebug() << "4G测试已启用,在初始化阶段先关闭和开启4G电源"; - // 先发送AT^CAT1POWER=0关闭CAT1电源 - serial->write("AT^CAT1POWER=0\r\n"); - qDebug() << "发送CAT1电源关闭指令: AT^CAT1POWER=0 (初始化阶段)"; - - // 延迟1秒后发送AT^CAT1POWER=1开启CAT1电源 - QTimer::singleShot(1000, this, [this]() { - if (isProductionTestRunning && serial->isOpen()) { - serial->write("AT^CAT1POWER=1\r\n"); - qDebug() << "发送CAT1电源开启指令: AT^CAT1POWER=1 (初始化阶段)"; - - // 延迟1秒后发送AT^CAT1LOGLEVEL=4指令 + // 开机初始化完成后,进入产测界面(满足AT^ENTERTESTVIEW要求) + if (serial->isOpen()) { + ui->recvTextEdit->append(">>> 发送指令: AT^ENTERTESTVIEW (进入产测界面)"); + serial->write("AT^ENTERTESTVIEW\r\n"); + qDebug() << "发送AT^ENTERTESTVIEW (进入产测界面)"; + } + + // 初始化完成后,切换到LCD测试阶段(如果启用) + QTimer::singleShot(1000, this, [this]() { + if (isProductionTestRunning) { + if (isTestItemEnabled("lcd")) { + qDebug() << "AT^PMADDVOTE指令发送完成,开始LCD测试"; + ui->statusbar->showMessage("初始化完成,开始LCD测试", 2000); + currentTestStep = TEST_LCD; QTimer::singleShot(1000, this, [this]() { - if (isProductionTestRunning && serial->isOpen()) { - serial->write("AT^CAT1LOGLEVEL=4\r\n"); - qDebug() << "发送CAT1日志级别设置指令: AT^CAT1LOGLEVEL=4 (初始化阶段)"; - - // 延迟1秒后切换到LCD测试阶段(如果启用) - QTimer::singleShot(1000, this, [this]() { - if (isProductionTestRunning) { - // 检查LCD测试是否启用 - if (isTestItemEnabled("lcd")) { - qDebug() << "4G电源初始化完成,开始LCD测试"; - ui->statusbar->showMessage("初始化完成,开始LCD测试", 2000); - - // 切换到LCD测试阶段 - currentTestStep = TEST_LCD; - - // 延迟1秒后开始LCD测试 - QTimer::singleShot(1000, this, [this]() { - if (isProductionTestRunning && currentTestStep == TEST_LCD) { - qDebug() << "开始LCD测试流程"; - performLcdTest(); - } - }); - } else { - qDebug() << "4G电源初始化完成,LCD测试未启用,跳过"; - ui->statusbar->showMessage("初始化完成,LCD测试未启用,跳过", 2000); - - // LCD未启用,跳过到下一个启用的测试 - currentTestStep = TEST_LCD; - skipToNextEnabledTest(); - } - } - }); + if (isProductionTestRunning && currentTestStep == TEST_LCD) { + qDebug() << "开始LCD测试流程"; + performLcdTest(); } }); + } else { + qDebug() << "AT^PMADDVOTE指令发送完成,LCD测试未启用,跳过"; + ui->statusbar->showMessage("初始化完成,LCD测试未启用,跳过", 2000); + currentTestStep = TEST_LCD; + skipToNextEnabledTest(); } - }); - } else { - // 4G测试未启用,直接切换到LCD测试阶段 - QTimer::singleShot(1000, this, [this]() { - if (isProductionTestRunning) { - // 检查LCD测试是否启用 - if (isTestItemEnabled("lcd")) { - qDebug() << "AT^PMADDVOTE指令发送完成,开始LCD测试"; - ui->statusbar->showMessage("初始化完成,开始LCD测试", 2000); - - // 切换到LCD测试阶段 - currentTestStep = TEST_LCD; - - // 延迟1秒后开始LCD测试 - QTimer::singleShot(1000, this, [this]() { - if (isProductionTestRunning && currentTestStep == TEST_LCD) { - qDebug() << "开始LCD测试流程"; - performLcdTest(); - } - }); - } else { - qDebug() << "AT^PMADDVOTE指令发送完成,LCD测试未启用,跳过"; - ui->statusbar->showMessage("初始化完成,LCD测试未启用,跳过", 2000); - - // LCD未启用,跳过到下一个启用的测试 - currentTestStep = TEST_LCD; - skipToNextEnabledTest(); - } - } - }); - } + } + }); } }); } @@ -2825,6 +2194,9 @@ void MainWindow::updateTestStatus(const QString& testName, TestStatus status) { QString iconText, statusText, iconColor, textColor; + // 用于时间记录的统一名称(charge/ntc不计入,由battery代表) + QString timeKey = (testName == "audio") ? "mic" : ((testName == "charge" || testName == "ntc") ? QString() : testName); + switch (status) { case STATUS_UNTESTED: iconText = "○"; @@ -2837,64 +2209,95 @@ void MainWindow::updateTestStatus(const QString& testName, TestStatus status) statusText = "测试中"; iconColor = "#FFA500"; // 橙色 textColor = "#FFA500"; + if (!timeKey.isEmpty()) { + testStartTimeMap[timeKey] = QDateTime::currentDateTime(); + testDurationSecMap.remove(timeKey); + } break; case STATUS_SUCCESS: iconText = "✓"; statusText = "通过"; iconColor = "#4CAF50"; // 绿色 textColor = "#4CAF50"; + if (!timeKey.isEmpty() && testStartTimeMap.contains(timeKey)) { + qint64 secs = testStartTimeMap[timeKey].secsTo(QDateTime::currentDateTime()); + testDurationSecMap[timeKey] = secs; + testStartTimeMap.remove(timeKey); + int m = secs / 60, s = secs % 60; + statusText = QString("通过 (%1:%2)").arg(m).arg(s, 2, 10, QChar('0')); + } break; case STATUS_FAILED: iconText = "✗"; statusText = "失败"; iconColor = "#F44336"; // 红色 textColor = "#F44336"; + if (!timeKey.isEmpty() && testStartTimeMap.contains(timeKey)) { + qint64 secs = testStartTimeMap[timeKey].secsTo(QDateTime::currentDateTime()); + testDurationSecMap[timeKey] = secs; + testStartTimeMap.remove(timeKey); + int m = secs / 60, s = secs % 60; + statusText = QString("失败 (%1:%2)").arg(m).arg(s, 2, 10, QChar('0')); + } break; } - QString iconStyle = QString("color: %1; font-size: 18px; font-weight: bold;").arg(iconColor); - QString textStyle = QString("color: %1;").arg(textColor); - + QString rowBg; + if (status == STATUS_SUCCESS) { + rowBg = "#dff5df"; + } else if (status == STATUS_FAILED) { + rowBg = "#ffe0e0"; + } + + // 从第一个状态标签获取基准字体,确保状态切换时字体不变 + QFont baseFont = ui->lcdStatusText->font(); + int ptSize = baseFont.pointSize() > 0 ? baseFont.pointSize() : 10; + QString fontCss = QString("font-family: \"%1\"; font-size: %2pt; font-weight: %3;") + .arg(baseFont.family()).arg(ptSize).arg(baseFont.bold() ? 700 : 400); + QString iconFontCss = "font-size: 18px; font-weight: bold;"; + + const QString iconStyle = rowBg.isEmpty() + ? QString("color: %1; %2").arg(iconColor, iconFontCss) + : QString("color: %1; %2 background-color: %3;").arg(iconColor, iconFontCss, rowBg); + const QString textStyle = rowBg.isEmpty() + ? QString("color: %1; %2").arg(textColor, fontCss) + : QString("color: %1; %2 background-color: %3;").arg(textColor, fontCss, rowBg); + const QString labelStyle = rowBg.isEmpty() ? "" : QString("background-color: %1; %2").arg(rowBg, fontCss); + + auto applyRow = [&](QLabel* nameLabel, QLabel* iconLabel, QLabel* statusLabel) { + if (nameLabel) nameLabel->setStyleSheet(labelStyle); + iconLabel->setText(iconText); + iconLabel->setStyleSheet(iconStyle); + statusLabel->setText(statusText); + statusLabel->setStyleSheet(textStyle); + }; + if (testName == "lcd") { - ui->lcdStatusIcon->setText(iconText); - ui->lcdStatusIcon->setStyleSheet(iconStyle); - ui->lcdStatusText->setText(statusText); - ui->lcdStatusText->setStyleSheet(textStyle); + applyRow(ui->lcdTestLabel, ui->lcdStatusIcon, ui->lcdStatusText); + } else if (testName == "tp") { + applyRow(ui->tpTestLabel, ui->tpStatusIcon, ui->tpStatusText); + } else if (testName == "bright") { + applyRow(ui->brightTestLabel, ui->brightStatusIcon, ui->brightStatusText); } else if (testName == "motor") { - ui->motorStatusIcon->setText(iconText); - ui->motorStatusIcon->setStyleSheet(iconStyle); - ui->motorStatusText->setText(statusText); - ui->motorStatusText->setStyleSheet(textStyle); + applyRow(ui->motorTestLabel, ui->motorStatusIcon, ui->motorStatusText); } else if (testName == "key") { - ui->keyStatusIcon->setText(iconText); - ui->keyStatusIcon->setStyleSheet(iconStyle); - ui->keyStatusText->setText(statusText); - ui->keyStatusText->setStyleSheet(textStyle); - } else if (testName == "audio") { - ui->audioStatusIcon->setText(iconText); - ui->audioStatusIcon->setStyleSheet(iconStyle); - ui->audioStatusText->setText(statusText); - ui->audioStatusText->setStyleSheet(textStyle); + applyRow(ui->keyTestLabel, ui->keyStatusIcon, ui->keyStatusText); + } else if (testName == "deepsleep") { + applyRow(ui->deepsleepTestLabel, ui->deepsleepStatusIcon, ui->deepsleepStatusText); + } else if (testName == "mic" || testName == "audio") { + applyRow(ui->audioTestLabel, ui->audioStatusIcon, ui->audioStatusText); } else if (testName == "battery") { - ui->batteryStatusIcon->setText(iconText); - ui->batteryStatusIcon->setStyleSheet(iconStyle); - ui->batteryStatusText->setText(statusText); - ui->batteryStatusText->setStyleSheet(textStyle); + applyRow(ui->batteryTestLabel, ui->batteryStatusIcon, ui->batteryStatusText); } else if (testName == "gsensor") { - ui->gsensorStatusIcon->setText(iconText); - ui->gsensorStatusIcon->setStyleSheet(iconStyle); - ui->gsensorStatusText->setText(statusText); - ui->gsensorStatusText->setStyleSheet(textStyle); + applyRow(ui->gsensorTestLabel, ui->gsensorStatusIcon, ui->gsensorStatusText); } else if (testName == "bluetooth") { - ui->bluetoothStatusIcon->setText(iconText); - ui->bluetoothStatusIcon->setStyleSheet(iconStyle); - ui->bluetoothStatusText->setText(statusText); - ui->bluetoothStatusText->setStyleSheet(textStyle); - } else if (testName == "ppp") { - ui->pppStatusIcon->setText(iconText); - ui->pppStatusIcon->setStyleSheet(iconStyle); - ui->pppStatusText->setText(statusText); - ui->pppStatusText->setStyleSheet(textStyle); + applyRow(ui->bluetoothTestLabel, ui->bluetoothStatusIcon, ui->bluetoothStatusText); + } else if (testName == "sechip") { + applyRow(ui->sechipTestLabel, ui->sechipStatusIcon, ui->sechipStatusText); + } else if (testName == "gps") { + applyRow(ui->gpsTestLabel, ui->gpsStatusIcon, ui->gpsStatusText); + } else if (testName == "leaklight") { + applyRow(ui->leaklightTestLabel, ui->leaklightStatusIcon, ui->leaklightStatusText); } else if (testName == "charge" || testName == "ntc") { // charge和ntc是电池测试的子项,它们的状态用于判定,但不单独显示在UI中 // 它们的状态会影响battery的整体状态,但这里不更新UI @@ -2911,65 +2314,81 @@ void MainWindow::updateOverallStatus() // 检查各项测试状态(只检查成功状态,并根据checkbox决定是否参与判定) bool allEnabledTestsPassed = true; + auto isPassed = [](const QString& t) { return t.startsWith("通过"); }; // 检查启用的测试项 if (isTestItemEnabled("lcd")) { - bool lcdSuccess = ui->lcdStatusText->text() == "通过"; - if (!lcdSuccess) allEnabledTestsPassed = false; + if (!isPassed(ui->lcdStatusText->text())) allEnabledTestsPassed = false; + } + if (isTestItemEnabled("tp")) { + if (!isPassed(ui->tpStatusText->text())) allEnabledTestsPassed = false; + } + if (isTestItemEnabled("bright")) { + if (!isPassed(ui->brightStatusText->text())) allEnabledTestsPassed = false; } if (isTestItemEnabled("motor")) { - bool motorSuccess = ui->motorStatusText->text() == "通过"; - if (!motorSuccess) allEnabledTestsPassed = false; + if (!isPassed(ui->motorStatusText->text())) allEnabledTestsPassed = false; } if (isTestItemEnabled("key")) { - bool keySuccess = ui->keyStatusText->text() == "通过"; - if (!keySuccess) allEnabledTestsPassed = false; + if (!isPassed(ui->keyStatusText->text())) allEnabledTestsPassed = false; } - if (isTestItemEnabled("audio")) { - bool audioSuccess = ui->audioStatusText->text() == "通过"; - if (!audioSuccess) allEnabledTestsPassed = false; + if (isTestItemEnabled("deepsleep")) { + if (!isPassed(ui->deepsleepStatusText->text())) allEnabledTestsPassed = false; } - // 电池测试:检查充电和NTC(如果启用) - // 注意:charge和ntc的状态通过updateTestStatus("charge", ...)和updateTestStatus("ntc", ...)更新 - // 但由于UI中可能没有独立的charge和ntc状态显示,这里使用battery状态作为综合判定 - // 如果charge或ntc任一启用,都需要检查battery状态 - if (isTestItemEnabled("charge") || isTestItemEnabled("ntc")) { - bool batterySuccess = ui->batteryStatusText->text() == "通过"; - if (!batterySuccess) allEnabledTestsPassed = false; + if (isTestItemEnabled("mic")) { + if (!isPassed(ui->audioStatusText->text())) allEnabledTestsPassed = false; + } + if (isTestItemEnabled("battery")) { + if (!isPassed(ui->batteryStatusText->text())) allEnabledTestsPassed = false; } if (isTestItemEnabled("gsensor")) { - bool gsensorSuccess = ui->gsensorStatusText->text() == "通过"; - if (!gsensorSuccess) allEnabledTestsPassed = false; + if (!isPassed(ui->gsensorStatusText->text())) allEnabledTestsPassed = false; } if (isTestItemEnabled("bluetooth")) { - bool bluetoothSuccess = ui->bluetoothStatusText->text() == "通过"; - if (!bluetoothSuccess) allEnabledTestsPassed = false; + if (!isPassed(ui->bluetoothStatusText->text())) allEnabledTestsPassed = false; } - if (isTestItemEnabled("ppp")) { - bool pppSuccess = ui->pppStatusText->text() == "通过"; - if (!pppSuccess) allEnabledTestsPassed = false; + if (isTestItemEnabled("leaklight")) { + if (!isPassed(ui->leaklightStatusText->text())) allEnabledTestsPassed = false; + } + if (isTestItemEnabled("sechip")) { + if (!isPassed(ui->sechipStatusText->text())) allEnabledTestsPassed = false; + } + if (isTestItemEnabled("gps")) { + if (!isPassed(ui->gpsStatusText->text())) allEnabledTestsPassed = false; + } + // 计算各测试项总耗时 + qint64 totalSecs = 0; + for (auto it = testDurationSecMap.constBegin(); it != testDurationSecMap.constEnd(); ++it) { + totalSecs += it.value(); + } + QString totalTimeStr; + if (totalSecs > 0) { + int tm = totalSecs / 60, ts = totalSecs % 60; + totalTimeStr = QString("%1分%2秒").arg(tm).arg(ts); } - QString overallIcon, overallText, overallColor; if (allEnabledTestsPassed) { - // 所有启用的测试都通过 overallIcon = "✓"; - overallText = "通过"; + overallText = totalTimeStr.isEmpty() ? "通过" : QString("通过 · 总耗时 %1").arg(totalTimeStr); overallColor = "#4CAF50"; } else { - // 任何其他情况(失败、测试中、等待开始等)都显示为未通过 overallIcon = "✗"; - overallText = "未通过"; + overallText = totalTimeStr.isEmpty() ? "未通过" : QString("未通过 · 总耗时 %1").arg(totalTimeStr); overallColor = "#F44336"; } QString overallStyle = QString("color: %1; font-size: 18px; font-weight: bold;").arg(overallColor); QString textStyle = QString("color: %1;").arg(overallColor); + // 整体状态同样保持字体不变 + int overallPt = ui->overallStatusText->font().pointSize(); + if (overallPt <= 0) overallPt = 10; + QString overallFontCss = QString("font-family: \"%1\"; font-size: %2pt;") + .arg(ui->overallStatusText->font().family()).arg(overallPt); ui->overallStatusIcon->setText(overallIcon); ui->overallStatusIcon->setStyleSheet(overallStyle); ui->overallStatusText->setText(overallText); - ui->overallStatusText->setStyleSheet(textStyle); + ui->overallStatusText->setStyleSheet(textStyle + overallFontCss); } // 保存测试记录到CSV文件(可用Excel打开) @@ -2978,189 +2397,85 @@ QString MainWindow::saveTestRecord() QDateTime endTime = QDateTime::currentDateTime(); QString startTimeStr = testStartTime.toString("yyyy-MM-dd hh:mm:ss"); QString endTimeStr = endTime.toString("yyyy-MM-dd hh:mm:ss"); - + // 计算测试总耗时 qint64 totalSeconds = testStartTime.secsTo(endTime); int minutes = totalSeconds / 60; int seconds = totalSeconds % 60; QString durationStr = QString("%1分%2秒").arg(minutes).arg(seconds); - - QString date = endTime.toString("yyyy-MM-dd"); - + // 获取当前测试数据 QString ver = ui->VerlineEdit->text().trimmed(); - QString imei = ui->IMEIlineEdit->text().trimmed(); - QString iccid = ui->ICCIDlineEdit->text().trimmed(); - QString modem = ui->ModemVerlineEdit->text().trimmed(); - QString pyver = ui->PYVerlineEdit->text().trimmed(); - - // 检查ICCID是否有效,如果无效则不写入记录 - if (iccid.isEmpty() || iccid == "未获取") { - qDebug() << "ICCID为空或未获取,跳过保存测试记录"; - ui->statusbar->showMessage("ICCID未获取,测试记录未保存", 3000); - return QString(); // 返回空字符串表示未保存 - } - - // 检查ICCID格式是否正确 - if (!isValidICCID(iccid)) { - qDebug() << "ICCID格式不正确,跳过保存测试记录,ICCID:" << iccid; - ui->statusbar->showMessage("ICCID格式不正确,测试记录未保存: " + iccid, 3000); - return QString(); // 返回空字符串表示未保存 - } - - // 检查IMEI格式是否正确(如果IMEI不为空) - if (!imei.isEmpty() && !isValidIMEI(imei)) { - qDebug() << "IMEI格式不正确,跳过保存测试记录,IMEI:" << imei; - ui->statusbar->showMessage("IMEI格式不正确,测试记录未保存: " + imei, 3000); - return QString(); // 返回空字符串表示未保存 - } - - // 调试信息:输出获取到的值 - qDebug() << "保存时获取的数据:"; - qDebug() << " Ver:" << (ver.isEmpty() ? "空" : ver); - qDebug() << " IMEI:" << (imei.isEmpty() ? "空" : imei); - qDebug() << " ICCID:" << iccid; - qDebug() << " Modem:" << (modem.isEmpty() ? "空" : modem); - qDebug() << " PYVER:" << (pyver.isEmpty() ? "空" : pyver); - + // 获取各项测试结果 QString lcdResult = ui->lcdStatusText->text(); + QString tpResult = ui->tpStatusText->text(); + QString brightResult = ui->brightStatusText->text(); QString motorResult = ui->motorStatusText->text(); QString keyResult = ui->keyStatusText->text(); + QString deepsleepResult = ui->deepsleepStatusText->text(); QString audioResult = ui->audioStatusText->text(); QString batteryResult = ui->batteryStatusText->text(); QString gsensorResult = ui->gsensorStatusText->text(); + QString leaklightResult = ui->leaklightStatusText->text(); QString bluetoothResult = ui->bluetoothStatusText->text(); - QString pppResult = ui->pppStatusText->text(); + QString sechipResult = ui->sechipStatusText->text(); + QString gpsResult = ui->gpsStatusText->text(); QString overallResult = ui->overallStatusText->text(); - - // 创建保存目录(在源码同一路径下) + + // 创建保存目录 QString appPath = QCoreApplication::applicationDirPath(); QString saveDir = appPath + "/产测记录"; QDir dir; if (!dir.exists(saveDir)) { dir.mkpath(saveDir); } - - // CSV文件路径(统一命名) + QString csvFileName = "产测记录.csv"; QString csvFilePath = saveDir + "/" + csvFileName; - - // 检查是否是新文件(需要写入表头) + bool isNewFile = !QFile::exists(csvFilePath); - - // 构建新的测试数据行 - QString newDataLine = QString("%1,%2,%3,%4,%5,%6,%7,%8,%9,%10,%11,%12,%13,%14,%15,%16\n") - .arg(startTimeStr) - .arg(endTimeStr) - .arg(durationStr) - .arg(ver.isEmpty() ? "未获取" : ver) - .arg(imei.isEmpty() ? "未获取" : imei) - .arg(iccid.isEmpty() ? "未获取" : iccid) - .arg(modem.isEmpty() ? "未获取" : modem) - .arg(pyver.isEmpty() ? "未获取" : pyver) - .arg(lcdResult) - .arg(motorResult) - .arg(keyResult) - .arg(audioResult) - .arg(batteryResult) - .arg(gsensorResult) - .arg(bluetoothResult) - .arg(pppResult) - .arg(overallResult); - - QString finalContent; - - if (isNewFile) { - // 新文件,写入表头和数据 - finalContent += "开始时间,结束时间,测试耗时,软件版本,IMEI,ICCID,Modem版本,PYVER版本,LCD测试,马达测试,按键测试,录音测试,电池测试,G-sensor测试,蓝牙测试,4G测试,整体结果\n"; - finalContent += newDataLine; - qDebug() << "创建新的CSV文件:" << csvFilePath; - } else { - // 现有文件,需要检查ICCID是否重复 - QFile existingFile(csvFilePath); - if (!existingFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox::warning(this, "读取失败", - QString("无法读取现有测试记录文件:\n%1").arg(csvFilePath)); - return QString(); - } - - // 读取所有现有数据 - QStringList existingLines; - QTextStream readStream(&existingFile); - while (!readStream.atEnd()) { - existingLines << readStream.readLine(); - } - existingFile.close(); - - // 构建最终内容 - if (!existingLines.isEmpty()) { - finalContent = existingLines[0] + "\n"; // 表头 - - bool iccidFound = false; - - // 检查每一行数据,查找相同ICCID - for (int i = 1; i < existingLines.size(); i++) { - QString line = existingLines[i]; - if (line.trimmed().isEmpty()) continue; - - // 分割CSV行,获取ICCID字段(第6列,索引为5) - QStringList fields = line.split(","); - if (fields.size() >= 6) { - QString existingIccid = fields[5].trimmed(); - - if (existingIccid == iccid) { - // 找到相同ICCID,替换为新记录 - finalContent += newDataLine; - iccidFound = true; - qDebug() << "找到相同ICCID,替换记录:" << iccid; - } else { - // 保留不同ICCID的记录 - finalContent += line + "\n"; - } - } else { - // 格式不正确的行,保留 - finalContent += line + "\n"; - } - } - - // 如果没有找到相同ICCID,追加新记录 - if (!iccidFound) { - finalContent += newDataLine; - qDebug() << "未找到相同ICCID,追加新记录:" << iccid; - } - } else { - // 文件为空,重建 - finalContent = "开始时间,结束时间,测试耗时,软件版本,IMEI,ICCID,Modem版本,PYVER版本,LCD测试,马达测试,按键测试,录音测试,电池测试,G-sensor测试,蓝牙测试,4G测试,整体结果\n"; - finalContent += newDataLine; - } - } - - // 写入最终内容到文件 + + const QString header = + "开始时间,结束时间,测试耗时,软件版本,LCD测试,触控测试,亮度测试,马达测试,按键测试,功耗测试,录音测试,电池测试,G-sensor测试,漏光测试,蓝牙测试,SE芯片测试,GPS测试,整体结果\n"; + + QString newDataLine = QString("%1,%2,%3,%4,%5,%6,%7,%8,%9,%10,%11,%12,%13,%14,%15,%16,%17,%18\n") + .arg(startTimeStr) + .arg(endTimeStr) + .arg(durationStr) + .arg(ver.isEmpty() ? "未获取" : ver) + .arg(lcdResult) + .arg(tpResult) + .arg(brightResult) + .arg(motorResult) + .arg(keyResult) + .arg(deepsleepResult) + .arg(audioResult) + .arg(batteryResult) + .arg(gsensorResult) + .arg(leaklightResult) + .arg(bluetoothResult) + .arg(sechipResult) + .arg(gpsResult) + .arg(overallResult); + QFile csvFile(csvFilePath); - if (!csvFile.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::warning(this, "保存失败", - QString("无法创建测试记录文件:\n%1\n错误:%2") - .arg(csvFilePath) - .arg(csvFile.errorString())); - return QString(); // 返回空字符串表示保存失败 + if (!csvFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { + QMessageBox::warning(this, "保存失败", + QString("无法写入测试记录文件:\n%1\n错误:%2") + .arg(csvFilePath) + .arg(csvFile.errorString())); + return QString(); } - - // 使用系统本地编码写入数据(Windows下通常是GBK) - QByteArray localData = finalContent.toLocal8Bit(); - csvFile.write(localData); - qDebug() << "使用本地编码写入数据,大小:" << localData.size() << "字节"; - + + if (isNewFile) { + csvFile.write(header.toLocal8Bit()); + } + csvFile.write(newDataLine.toLocal8Bit()); csvFile.close(); - - qDebug() << "测试记录已保存到:" << csvFilePath; - qDebug() << "记录内容: 开始时间=" << startTimeStr << ", 结束时间=" << endTimeStr << ", 耗时=" << durationStr - << ", Ver=" << ver << ", IMEI=" << imei << ", ICCID=" << iccid << ", Modem=" << modem - << ", PYVER=" << pyver << ", LCD=" << lcdResult << ", 马达=" << motorResult - << ", 按键=" << keyResult << ", PPP=" << pppResult - << ", 整体=" << overallResult; - - // 显示保存成功消息 + + qDebug() << "测试记录已追加保存到:" << csvFilePath; + ui->statusbar->showMessage(QString("测试记录已保存: %1").arg(csvFileName), 3000); // 关闭测试日志文件(测试完成) @@ -3192,146 +2507,6 @@ QString MainWindow::saveTestRecord() } -// 检查signal和sim值进行4G测试判断 -void MainWindow::checkSignalAndSimValues(const QString& data) -{ - qDebug() << "开始检查signal和sim值,数据:" << data.trimmed(); - - // 标记已收到signal和sim数据 - hasReceivedSignalSim = true; - - // 停止超时检测,因为已经收到数据 - if (test4GTimer->isActive()) { - test4GTimer->stop(); - qDebug() << "收到signal和sim数据,停止4G测试超时检测"; - } - - // 解析sim值:@sim:1@ - QRegExp simRegex("@sim:(\\d+)@"); - int simValue = -1; - if (simRegex.indexIn(data) != -1) { - simValue = simRegex.cap(1).toInt(); - qDebug() << "解析到sim值:" << simValue; - - // 保存SIM卡状态 - currentSimStatus = simValue; - qDebug() << "更新SIM卡状态:" << (currentSimStatus == 1 ? "已插入" : "未插入"); - } else { - qDebug() << "未找到sim值"; - return; - } - - // 解析signal值:@signal:31@ - QRegExp signalRegex("@signal:(\\d+)@"); - int signalValue = -1; - if (signalRegex.indexIn(data) != -1) { - signalValue = signalRegex.cap(1).toInt(); - qDebug() << "解析到signal值:" << signalValue; - - // 将signal值显示到界面 - appendToLogFile(QString("Signal值: %1\n").arg(signalValue)); - ui->statusbar->showMessage(QString("检测到Signal值: %1").arg(signalValue), 2000); - } else { - qDebug() << "未找到signal值"; - return; - } - - // 移除signal=99的立即失败逻辑,改为在CAT1测试中统计处理 - - // 如果是第一次收到signal值,启动CAT1信号测试 - if (!hasReceivedFirstSignal) { - hasReceivedFirstSignal = true; - qDebug() << "收到第一次signal值:" << signalValue << ",启动CAT1信号测试"; - - // 停止signal4GTimer,避免与CAT1测试冲突导致重复弹窗 - if (signal4GTimer->isActive()) { - signal4GTimer->stop(); - qDebug() << "停止signal4GTimer,改用CAT1信号测试"; - } - - // 初始化CAT1信号测试计数器 - cat1SignalTestCount = 0; - cat1SignalReceivedCount = 0; - cat1SignalGoodCount = 0; - cat1Signal99Count = 0; - cat1SignalTotalValue = 0; - // 移除了cat1ProcessedSignals的清理,改用时间戳方式 - - // 启动CAT1信号测试定时器,每2秒发送一次 - cat1SignalTimer->start(2000); - ui->statusbar->showMessage("开始CAT1信号测试,每2秒检测一次...", 3000); - - // 立即发送第一次CAT1信号检测指令 - serial->write("AT^CAT1GETSIGNAL\r\n"); - cat1SignalTestCount++; - qDebug() << "发送第" << cat1SignalTestCount << "次CAT1信号检测指令: AT^CAT1GETSIGNAL"; - return; - } - - // 如果已经启动了CAT1信号测试,不再执行旧的判断逻辑 - if (hasReceivedFirstSignal) { - qDebug() << "已启动CAT1信号测试,跳过旧的signal判断逻辑"; - return; - } - - // 判断4G测试是否通过(这段代码理论上不会执行到) - bool simOk = (simValue == 1); - bool signalOk = (signalValue > 25); - bool testPassed = simOk && signalOk; - - qDebug() << "4G测试判断结果:"; - qDebug() << " sim值:" << simValue << "(期望: 1)" << "检查结果:" << (simOk ? "通过" : "失败"); - qDebug() << " signal值:" << signalValue << "(期望: >25)" << "检查结果:" << (signalOk ? "通过" : "失败"); - qDebug() << " 最终测试结果:" << (testPassed ? "通过" : "失败"); - qDebug() << " 警告:此代码不应该执行到,因为已启动CAT1测试"; - - if (testPassed) { - // 测试通过 - ui->statusbar->showMessage(QString("4G测试通过 - SIM: %1, Signal: %2").arg(simValue).arg(signalValue), 3000); - updateTestStatus("ppp", STATUS_SUCCESS); - - // 完成产测 - if (isProductionTestRunning) { - currentTestStep = TEST_COMPLETED; - isProductionTestRunning = false; - qDebug() << "4G测试通过,产测流程完成"; - - // 保存测试记录 - QString fileName = saveTestRecord(); - - // 显示最终测试结果弹窗 - showFinalTestResult(); - } - } else { - // 测试失败 - QString failReason; - if (simValue != 1) { - failReason += QString("SIM卡状态异常(值:%1,期望:1) ").arg(simValue); - } - if (signalValue == 99) { - failReason += QString("无效信号值(值:%1)").arg(signalValue); - } else if (signalValue <= 25) { - failReason += QString("信号强度不足(值:%1,期望:>25)").arg(signalValue); - } - - ui->statusbar->showMessage(QString("4G测试失败 - %1").arg(failReason), 3000); - updateTestStatus("ppp", STATUS_FAILED); - - // 完成产测(失败) - if (isProductionTestRunning) { - currentTestStep = TEST_COMPLETED; - isProductionTestRunning = false; - qDebug() << "4G测试失败,产测流程完成"; - - // 保存测试记录 - QString fileName = saveTestRecord(); - - // 显示最终测试结果弹窗 - showFinalTestResult(); - } - } -} - // 解析wstp_print格式的BTMAC和CyweeChipInfo数据 void MainWindow::parseWstpPrintData(const QString& data) { @@ -3388,37 +2563,6 @@ void MainWindow::parseWstpPrintData(const QString& data) } } -// 发送PPP指令 -void MainWindow::sendPppCommand() -{ - if (!isProductionTestRunning || currentTestStep != TEST_PPP_INIT) { - qDebug() << "PPP数据等待跳过 - 测试状态不正确"; - return; - } - - pppCommandCount++; - qDebug() << "等待PPP数据,第" << pppCommandCount << "次"; - - if (pppCommandCount <= 3) { - // 前3次等待PPP相关数据 - ui->statusbar->showMessage(QString("4G测试中 - 等待PPP数据(%1/3)").arg(pppCommandCount), 3000); - - // 不再发送PPPENVINIT指令,直接等待设备自动发送PPPINFO - - // 启动5秒超时定时器,等待PPPINFO回复 - test4GTimer->start(5000); - qDebug() << "启动5秒超时定时器,等待PPPINFO回复"; - } else { - // 超过3次后,改为等待signal和sim数据 - qDebug() << "PPP数据已等待3次,改为等待signal和sim数据"; - ui->statusbar->showMessage("4G测试中 - 等待signal和sim数据...", 5000); - - // 启动5秒超时检测,等待signal和sim数据 - test4GTimer->start(5000); - qDebug() << "启动5秒超时检测,等待signal和sim数据"; - } -} - // 打开记录文件夹 void MainWindow::on_openRecordsFolderButton_clicked() { @@ -3457,12 +2601,9 @@ void MainWindow::on_openButton_2_clicked() // 如果正在测试,则停止测试 lcdTestTimer->stop(); motorTestTimer->stop(); - test4GTimer->stop(); - signal4GTimer->stop(); testTimeoutTimer->stop(); - cat1SignalTimer->stop(); + gSensorIntervalTimer->stop(); bluetoothTestTimer->stop(); - pingTestTimer->stop(); isProductionTestRunning = false; currentTestStep = TEST_IDLE; @@ -3487,30 +2628,13 @@ void MainWindow::on_openButton_2_clicked() // 开始产测流程 isProductionTestRunning = true; testStartTime = QDateTime::currentDateTime(); // 记录测试开始时间 + testStartTimeMap.clear(); + testDurationSecMap.clear(); currentTestStep = TEST_RESET; currentLcdColor = 1; // 从红色开始 - initRetry4GCount = 0; - signalTest4GCount = 0; - signalReceived4GCount = 0; - signalGood4GCount = 0; - pppCommandCount = 0; - hasReceivedSignalSim = false; - hasReceivedPppInfo = false; - - // 在开始新测试前,检查是否需要保存当前UI日志 - QString currentLogContent = ui->recvTextEdit->toPlainText(); - QString currentImei = ui->IMEIlineEdit->text().trimmed(); - QString currentIccid = ui->ICCIDlineEdit->text().trimmed(); - - // 如果UI有日志内容且IMEI/ICCID已知,则保存日志 - if (!currentLogContent.trimmed().isEmpty() && - !currentImei.isEmpty() && currentImei != "未获取" && - !currentIccid.isEmpty() && currentIccid != "未获取") { - qDebug() << "开始新测试前,保存当前UI日志"; - saveRecvTextEditToLog(); - } else { - qDebug() << "UI日志为空或IMEI/ICCID未知,放弃保存,直接清空UI"; - } + + // 在开始新测试前,尽量保存当前UI日志(如果有内容) + saveRecvTextEditToLog(); // 创建测试日志文件 createTestLogFile(); @@ -3527,34 +2651,36 @@ void MainWindow::on_openButton_2_clicked() // 清空设备信息 ui->VerlineEdit->clear(); // 清空版本信息 - ui->IMEIlineEdit->clear(); // 清空IMEI - ui->ICCIDlineEdit->clear(); // 清空ICCID - ui->ModemVerlineEdit->clear(); // 清空调制解调器版本 - ui->PYVerlineEdit->clear(); // 清空python版本 // 重置弹窗状态 lcdTestPopupShown = false; motorTestPopupShown = false; + touchTestPopupShown = false; + brightTestPopupShown = false; + deepsleepTestPopupShown = false; isWaitingForUserConfirm = false; keyTestStarted = false; - audioTestPopupShown = false; - audioPlaybackPopupShown = false; // 重置状态指示器 updateTestStatus("lcd", STATUS_UNTESTED); + updateTestStatus("tp", STATUS_UNTESTED); + updateTestStatus("bright", STATUS_UNTESTED); updateTestStatus("motor", STATUS_UNTESTED); updateTestStatus("key", STATUS_UNTESTED); - updateTestStatus("audio", STATUS_UNTESTED); + updateTestStatus("deepsleep", STATUS_UNTESTED); + updateTestStatus("mic", STATUS_UNTESTED); updateTestStatus("battery", STATUS_UNTESTED); updateTestStatus("gsensor", STATUS_UNTESTED); + updateTestStatus("leaklight", STATUS_UNTESTED); updateTestStatus("bluetooth", STATUS_UNTESTED); - updateTestStatus("ppp", STATUS_UNTESTED); + updateTestStatus("sechip", STATUS_UNTESTED); + updateTestStatus("gps", STATUS_UNTESTED); qDebug() << "已清空所有UI内容,准备开始新的产测"; ui->statusbar->showMessage("开始产测流程...", 2000); qDebug() << "=== 开始产测流程 ==="; - qDebug() << "流程: 产测开启(AT^FACTORY=1) → 设备重启 → 等待BuildVer → LCD测试(红绿蓝各1s) → 马达测试(开启2s) → 按键测试 → 录音测试(录音3s+播放) → 电池测试(电压检测) → G-sensor测试 → 4G测试(初始化+信号检测) → 退出产测(AT^FACTORY=0)"; + qDebug() << "流程: 产测开启(AT^FACTORY=1) → 设备重启 → … → 全部完成后弹窗确认 → AT^POWEROFF(关机)"; qDebug() << "LCD指令映射: 进入=AT^LCDTEST=1, 红色=AT^LCDTEST=6, 绿色=AT^LCDTEST=3, 蓝色=AT^LCDTEST=4, 退出=AT^LCDTEST=2"; // 发送产测开启指令,等待工厂模式确认 @@ -3577,571 +2703,193 @@ void MainWindow::on_openButton_2_clicked() testTimeoutTimer->start(10000); } -// 执行录音测试 +// 执行录音测试(配置了等级标准时共测 3 轮,任一轮「实际等级 >= 设置等级」则整体通过) void MainWindow::performAudioTest() { - qDebug() << "*** performAudioTest 被调用 ***"; - qDebug() << "当前状态检查 - isProductionTestRunning:" << isProductionTestRunning; - qDebug() << "当前状态检查 - currentTestStep:" << currentTestStep; - qDebug() << "当前状态检查 - TEST_AUDIO:" << TEST_AUDIO; - - // 检查录音测试是否启用 - if (!isTestItemEnabled("audio")) { + if (!isTestItemEnabled("mic")) { qDebug() << "录音测试未启用,跳过"; - updateTestStatus("audio", STATUS_UNTESTED); + updateTestStatus("mic", STATUS_UNTESTED); skipToNextEnabledTest(); return; } if (!isProductionTestRunning || currentTestStep != TEST_AUDIO) { - qDebug() << "*** performAudioTest 条件不满足,退出 ***"; + qDebug() << "录音测试条件不满足,退出"; return; } - - qDebug() << "开始录音测试(自动模式:AT^AUDIOTEST)"; - ui->recvTextEdit->append("========== 开始录音测试 =========="); - // 录音测试前,无论LCD状态如何,都强制关闭LCD - // 因为按键测试时按下按键可能会自动开启LCD,但状态可能没有及时更新 - // 为了确保LCD关闭,无论状态如何都发送关闭命令 - qDebug() << "录音测试前强制关闭LCD(无论状态如何)"; - qDebug() << "当前LCD状态 - LCD测试启用:" << isTestItemEnabled("lcd") << "LCD背光状态:" << lcdBacklightOn; - serial->write("AT^LCDOFF\r\n"); - qDebug() << "发送指令: AT^LCDOFF (关闭LCD)"; - ui->recvTextEdit->append(">>> 发送指令: AT^LCDOFF (关闭LCD,录音测试前强制关闭)"); - - // 更新LCD状态为关闭(因为发送了关闭命令) - lcdBacklightOn = false; + qDebug() << "开始录音测试"; + appendToLogFile("\n========== 开始录音测试 ==========\n"); + updateTestStatus("mic", STATUS_TESTING); + audioTestRoundIndex = 0; + audioTestAnyPass = false; + audioTestAwaitingMicLevel = false; + + QString standard = ui->audioLevelStandardLineEdit->text().trimmed(); + if (standard.isEmpty()) { + appendToLogFile(">>> 未配置等级标准:单轮采样,不做等级校验\n"); + ui->statusbar->showMessage("录音测试中(未配标准,单轮),请对着麦克风说话...", 5000); + } else { + appendToLogFile(QString(">>> 已配置等级标准「%1」:共 3 轮,任一轮达标即通过\n").arg(standard)); + ui->statusbar->showMessage("录音测试 1/3:请对着麦克风说话...", 5000); + } - // LCD关闭后延迟1秒再进行下一步 - QTimer::singleShot(1000, this, [this]() { - if (isProductionTestRunning && currentTestStep == TEST_AUDIO) { - performAudioTestContinue(); - } - }); + // 录音前关闭LCD(与V1.7一致) + if (isTestItemEnabled("lcd") && lcdBacklightOn) { + serial->write("AT^LCDOFF\r\n"); + appendToLogFile(">>> 发送指令: AT^LCDOFF (录音测试前关闭LCD)\n"); + qDebug() << "录音测试前发送AT^LCDOFF"; + } + + beginAudioTestAttempt(0); } -// 继续录音测试(LCD关闭后或无需关闭LCD时调用) -void MainWindow::performAudioTestContinue() +void MainWindow::beginAudioTestAttempt(int roundIndex) { - if (!isProductionTestRunning || currentTestStep != TEST_AUDIO) { + if (!isProductionTestRunning || currentTestStep != TEST_AUDIO || !serial || !serial->isOpen()) { + qDebug() << "录音测试:产测已停止或串口未打开"; return; } - - updateTestStatus("audio", STATUS_TESTING); - ui->statusbar->showMessage("录音测试中:正在发送AUDIOTEST指令...", 3000); - // 重置录音测试相关状态 - audioMicMiddleConsecutiveCount = 0; - audioMicTotalPassCount = 0; - audioTestFinished = false; - - // 获取音频测试通过标准(从UI中读取) - audioLevelStandard = "中"; // 默认值 - if (ui->recordVolumelineEdit) { - QString levelText = ui->recordVolumelineEdit->text().trimmed(); - if (!levelText.isEmpty()) { - audioLevelStandard = levelText; - } + audioTestRoundIndex = roundIndex; + audioTestAwaitingMicLevel = true; + + QString standard = ui->audioLevelStandardLineEdit->text().trimmed(); + if (!standard.isEmpty()) { + ui->statusbar->showMessage(QString("录音测试 %1/3:请对着麦克风说话...").arg(roundIndex + 1), 5000); } - qDebug() << "音频测试通过标准设置为:" << audioLevelStandard; - // 发送录音测试指令:先 AT^AUDIOTEST=5 再 AT^AUDIOTEST=7 <音量> - QTimer::singleShot(0, this, [this]() { - if (!isProductionTestRunning || currentTestStep != TEST_AUDIO) { - qDebug() << "录音测试已结束或步骤变化,取消发送AUDIOTEST指令"; + QString volumeStr = ui->audioVolumeLineEdit->text().trimmed(); + if (volumeStr.isEmpty()) volumeStr = "2.0"; + + QTimer::singleShot(200, this, [this, volumeStr]() { + if (!isProductionTestRunning || currentTestStep != TEST_AUDIO || !serial || !serial->isOpen()) { + qDebug() << "录音测试:产测已停止或串口未打开"; return; } - - QString audioCmd1 = "AT^AUDIOTEST=5\r\n"; - serial->write(audioCmd1.toUtf8()); - qDebug() << "发送录音测试指令1:" << audioCmd1.trimmed(); - ui->recvTextEdit->append(">>> 发送指令: AT^AUDIOTEST=5"); - - // 稍微延迟后发送第二条指令(带音量参数) - QTimer::singleShot(200, this, [this]() { - if (!isProductionTestRunning || currentTestStep != TEST_AUDIO) { - qDebug() << "录音测试已结束或步骤变化,取消发送第二条AUDIOTEST指令"; + serial->write("AT^AUDIOTEST=5\r\n"); + appendToLogFile(">>> 发送指令: AT^AUDIOTEST=5\n"); + qDebug() << "发送AT^AUDIOTEST=5"; + }); + + QTimer::singleShot(500, this, [this, volumeStr]() { + if (!isProductionTestRunning || currentTestStep != TEST_AUDIO || !serial || !serial->isOpen()) return; - } + QString cmd = QString("AT^AUDIOTEST=7 %1\r\n").arg(volumeStr); + serial->write(cmd.toUtf8()); + appendToLogFile(QString(">>> 发送指令: %1\n").arg(cmd.trimmed())); + qDebug() << "发送" << cmd.trimmed(); + testTimeoutTimer->start(15000); // 15秒超时 + }); +} - // 从界面读取音量文本框的值 - QString volumeStr; - if (ui->audioVolumeLineEdit) { - volumeStr = ui->audioVolumeLineEdit->text().trimmed(); - } +void MainWindow::checkAudioTestResult(const QString& data) +{ + if (!isProductionTestRunning || currentTestStep != TEST_AUDIO) + return; + if (!audioTestAwaitingMicLevel) + return; + + // 解析 mic level: 静音/极低/低/中/高 + QRegExp micPattern("mic\\s*level\\s*[::]\\s*(\\S+)", Qt::CaseInsensitive); + if (micPattern.indexIn(data) == -1) + return; + + QString micLevel = micPattern.cap(1).trimmed(); + audioTestAwaitingMicLevel = false; + testTimeoutTimer->stop(); + + qDebug() << "录音测试解析到mic level:" << micLevel; + appendToLogFile(QString(">>> 录音测试 第%1轮: mic level=%2\n").arg(audioTestRoundIndex + 1).arg(micLevel)); + ui->recvTextEdit->append(QString(">>> 录音测试 第%1轮: mic level=%2").arg(audioTestRoundIndex + 1).arg(micLevel)); + + // 结束本轮采集 + if (serial && serial->isOpen()) { + serial->write("AT^AUDIOTEST=6\r\n"); + appendToLogFile(">>> 发送指令: AT^AUDIOTEST=6 (结束本轮音频测试)\n"); + qDebug() << "发送AT^AUDIOTEST=6"; + } + + QString standard = ui->audioLevelStandardLineEdit->text().trimmed(); + if (standard.isEmpty()) { + updateTestStatus("mic", STATUS_SUCCESS); + ui->statusbar->showMessage(QString("录音测试通过(等级:%1,未配置标准不校验)").arg(micLevel), 3000); + skipToNextEnabledTest(); + return; + } + + // 等级排序:静音(0) < 极低(1) < 低(2) < 中(3) < 高(4);达标条件:micRank >= stdRank + auto levelRank = [](const QString& s) -> int { + if (s == QStringLiteral("静音")) return 0; + if (s == QStringLiteral("极低")) return 1; + if (s == QStringLiteral("低")) return 2; + if (s == QStringLiteral("中")) return 3; + if (s == QStringLiteral("高")) return 4; + return -1; + }; + + int micRank = levelRank(micLevel); + int stdRank = levelRank(standard); + + if (micRank < 0 || stdRank < 0) { + updateTestStatus("mic", STATUS_FAILED); + ui->statusbar->showMessage(QString("录音测试失败:无法解析等级(实际:%1, 标准:%2)").arg(micLevel).arg(standard), 3000); + skipToNextEnabledTest(); + return; + } - float volume = 2; // 默认音量为2 - bool ok = false; - if (!volumeStr.isEmpty()) { - float v = volumeStr.toFloat(&ok); - if (ok && v >= 0) { - volume = v; - } else { - qDebug() << "音量文本框内容非法,使用默认音量 2,原始值:" << volumeStr; - } - } else { - qDebug() << "音量文本框为空,使用默认音量 2"; - } + const bool roundPass = (micRank >= stdRank); + if (roundPass) { + audioTestAnyPass = true; + appendToLogFile(QString(">>> 第%1轮达标(%2 >= 标准%3)\n").arg(audioTestRoundIndex + 1).arg(micLevel).arg(standard)); + } else { + appendToLogFile(QString(">>> 第%1轮未达标(%2 < 标准%3)\n").arg(audioTestRoundIndex + 1).arg(micLevel).arg(standard)); + } + + const int completedRound = audioTestRoundIndex; + const int nextRound = completedRound + 1; - QString audioCmd2 = QString("AT^AUDIOTEST=7 %1\r\n").arg(volume); - serial->write(audioCmd2.toUtf8()); - qDebug() << "发送录音测试指令2:" << audioCmd2.trimmed(); - ui->recvTextEdit->append(QString(">>> 发送指令: AT^AUDIOTEST=7 %1").arg(volume)); + if (nextRound < 3) { + QTimer::singleShot(400, this, [this, nextRound]() { + if (!isProductionTestRunning || currentTestStep != TEST_AUDIO) { + return; + } + beginAudioTestAttempt(nextRound); }); - }); + return; + } - // 启动 8 秒定时器,在此期间统计"fac:mic level:中" - if (audioRecordTimer->isActive()) { - audioRecordTimer->stop(); + if (audioTestAnyPass) { + updateTestStatus("mic", STATUS_SUCCESS); + ui->statusbar->showMessage(QString("录音测试通过(3轮中至少一轮达标,标准:%1)").arg(standard), 3000); + } else { + updateTestStatus("mic", STATUS_FAILED); + ui->statusbar->showMessage(QString("录音测试失败:3轮均未达到标准%1").arg(standard), 3000); } - audioRecordTimer->start(8000); + skipToNextEnabledTest(); } -// 显示录音测试弹窗 -void MainWindow::showAudioTestDialog() +// 执行电池测试 +void MainWindow::performBatteryTest() { - if (audioTestPopupShown || !isProductionTestRunning || currentTestStep != TEST_AUDIO) { - qDebug() << "录音测试弹窗已显示或条件不满足,跳过显示"; + // 检查电池测试是否启用(充电或NTC任一启用即可) + if (!isTestItemEnabled("battery")) { + qDebug() << "电池测试未启用(充电和NTC都未启用),跳过"; + updateTestStatus("battery", STATUS_UNTESTED); + updateTestStatus("charge", STATUS_UNTESTED); + updateTestStatus("ntc", STATUS_UNTESTED); + skipToNextEnabledTest(); return; } - // 如果已经有录音测试弹窗存在,先关闭它 - if (audioTestDialog) { - qDebug() << "检测到已存在的录音测试弹窗,先关闭"; - audioTestDialog->close(); - audioTestDialog->deleteLater(); - audioTestDialog = nullptr; + if (!isProductionTestRunning || currentTestStep != TEST_BATTERY) { + return; } - - // 创建录音测试对话框 - audioTestDialog = new QDialog(this); - audioTestDialog->setWindowTitle("录音测试"); - audioTestDialog->setFixedSize(520, 350); - audioTestDialog->setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint | Qt::WindowTitleHint); - - // 设置对话框样式 - audioTestDialog->setStyleSheet( - "QDialog { " - "background-color: #f0f0f0; " - "border-radius: 8px; " - "}" - ); - - // 使用垂直布局管理器 - QVBoxLayout *mainLayout = new QVBoxLayout(audioTestDialog); - mainLayout->setSpacing(15); - mainLayout->setContentsMargins(20, 20, 20, 20); - - // 创建主标签 - audioDialogLabel = new QLabel("🎤 录音测试"); - audioDialogLabel->setAlignment(Qt::AlignCenter); - audioDialogLabel->setStyleSheet( - "QLabel { " - "font-size: 18px; " - "font-weight: bold; " - "background-color: #4CAF50; " - "color: white; " - "border-radius: 4px; " - "padding: 12px; " - "}" - ); - mainLayout->addWidget(audioDialogLabel); - - // 创建信息标签 - audioDialogInfo = new QLabel("🎤 请点击下方按钮开始录音测试\n流程:开始录音→1s后播放1kHz音频→2s后停止播放和录音→1s后播放录音→3s后停止"); - audioDialogInfo->setAlignment(Qt::AlignCenter); - audioDialogInfo->setWordWrap(true); - audioDialogInfo->setMinimumHeight(80); - audioDialogInfo->setStyleSheet( - "QLabel { " - "font-size: 14px; " - "background-color: white; " - "border: 1px solid #ddd; " - "border-radius: 4px; " - "padding: 15px; " - "color: #333; " - "}" - ); - mainLayout->addWidget(audioDialogInfo); - - // 创建开始录音按钮布局 - QHBoxLayout *startLayout = new QHBoxLayout(); - startLayout->setSpacing(10); - - audioStartButton = new QPushButton("🎤 开始录音测试"); - audioStartButton->setMinimumHeight(50); - audioStartButton->setStyleSheet( - "QPushButton { " - "background-color: #2196F3; " - "color: white; " - "padding: 12px 20px; " - "border-radius: 4px; " - "font-weight: bold; " - "font-size: 16px; " - "}" - ); - startLayout->addWidget(audioStartButton); - mainLayout->addLayout(startLayout); - - // 创建测试结果按钮布局 - QHBoxLayout *resultLayout = new QHBoxLayout(); - resultLayout->setSpacing(10); - - // 创建按钮 - audioPassButton = new QPushButton("✓ 录音正常"); - audioPassButton->setMinimumHeight(45); - audioPassButton->setStyleSheet( - "QPushButton { " - "background-color: #4CAF50; " - "color: white; " - "padding: 10px 15px; " - "border-radius: 4px; " - "font-weight: bold; " - "font-size: 14px; " - "} " - "QPushButton:disabled { " - "background-color: #cccccc; " - "color: #666666; " - "}" - ); - audioPassButton->setEnabled(true); // 允许直接点击通过 - - audioFailButton = new QPushButton("✗ 录音异常"); - audioFailButton->setMinimumHeight(45); - audioFailButton->setStyleSheet( - "QPushButton { " - "background-color: #f44336; " - "color: white; " - "padding: 10px 15px; " - "border-radius: 4px; " - "font-weight: bold; " - "font-size: 14px; " - "}" - ); - audioFailButton->setEnabled(false); // 初始禁用 - - audioRetestButton = new QPushButton("🔄 重新测试"); - audioRetestButton->setMinimumHeight(45); - audioRetestButton->setStyleSheet( - "QPushButton { " - "background-color: #FF9800; " - "color: white; " - "padding: 10px 15px; " - "border-radius: 4px; " - "font-weight: bold; " - "font-size: 14px; " - "}" - ); - audioRetestButton->setEnabled(false); // 初始禁用 - - resultLayout->addWidget(audioPassButton); - resultLayout->addWidget(audioFailButton); - resultLayout->addWidget(audioRetestButton); - - // 添加按钮布局到主布局 - mainLayout->addLayout(resultLayout); - - // 连接开始录音按钮事件 - connect(audioStartButton, &QPushButton::clicked, [this]() { - qDebug() << "用户点击开始录音测试"; - - // 禁用开始按钮,更新界面状态 - audioStartButton->setEnabled(false); - audioDialogInfo->setText("🎤 正在开始录音...\n请保持安静"); - - // 发送开始录音指令 - QString recordCommand = "AT^MEDIAAUDIORECORD=xx,mp3,16000,1,16,/user/res/music/1.mp3\r\n"; - serial->write(recordCommand.toUtf8()); - qDebug() << "发送录音开始指令:" << recordCommand.trimmed(); - - // 延迟1秒后发送播放1kHz音频指令 - QTimer::singleShot(1000, this, [this]() { - if (audioTestDialog && isProductionTestRunning) { - audioDialogInfo->setText("📢 正在播放1kHz测试音频(2秒)\n同时进行录音,请注意听声音..."); - - // 发送播放1kHz音频指令 - QString play1khzCommand = "AT^MEDIAMUSICPLAY=xx,/user/res/music/1khz.mp3,1,0\r\n"; - serial->write(play1khzCommand.toUtf8()); - qDebug() << "发送播放1kHz音频指令:" << play1khzCommand.trimmed(); - - // 设置当前步骤为播放1kHz音频,启动2秒定时器 - currentAudioStep = AUDIO_PLAYING_1KHZ; - audioRecordTimer->start(2000); - } - }); - }); - - // 连接测试结果按钮事件 - connect(audioPassButton, &QPushButton::clicked, [this]() { - if (audioTestDialog) { - audioTestDialog->accept(); - qDebug() << "用户确认:录音测试通过"; - ui->statusbar->showMessage("录音测试通过", 2000); - updateTestStatus("audio", STATUS_SUCCESS); - - // 继续下一个测试 - skipToNextEnabledTest(); - } - }); - - connect(audioFailButton, &QPushButton::clicked, [this]() { - if (audioTestDialog) { - audioTestDialog->accept(); - qDebug() << "用户确认:录音测试失败"; - ui->statusbar->showMessage("录音测试失败", 2000); - updateTestStatus("audio", STATUS_FAILED); - - // 继续下一个测试 - skipToNextEnabledTest(); - } - }); - - connect(audioRetestButton, &QPushButton::clicked, [this]() { - qDebug() << "用户选择重新测试录音"; - - // 发送停止指令 - serial->write("AT^MEDIAMUSICSTOP\r\n"); - serial->write("AT^MEDIAAUDIORECORDSTOP\r\n"); - - // 重置界面状态 - audioStartButton->setEnabled(true); - audioPassButton->setEnabled(false); - audioFailButton->setEnabled(false); - audioRetestButton->setEnabled(false); - audioDialogInfo->setText("🎤 请点击下方按钮开始录音测试\n流程:开始录音→1s后播放1kHz音频→2s后停止播放和录音→1s后播放录音→3s后停止"); - - // 重置录音测试状态 - currentAudioStep = AUDIO_STARTING_RECORD; - audioRecordTimer->stop(); - - qDebug() << "录音测试已重置,等待用户重新开始"; - }); - - // 显示对话框 - audioTestPopupShown = true; - audioTestDialog->show(); - qDebug() << "录音测试弹窗已显示"; -} - -// 录音测试定时器 - 8秒超时检查“fac:mic level:中” -void MainWindow::onAudioRecordTimer() -{ - audioRecordTimer->stop(); - - if (!isProductionTestRunning || currentTestStep != TEST_AUDIO) { - return; - } - - if (audioTestFinished) { - qDebug() << "录音测试已在8秒内完成,不再处理超时"; - return; - } - - // 8秒内未检测到连续3个或累计6个满足要求的Level,判定录音测试失败 - qDebug() << QString("录音测试8秒超时,连续计数: %1,累计计数: %2,未达到通过标准(连续3次或累计6次),测试失败") - .arg(audioMicMiddleConsecutiveCount) - .arg(audioMicTotalPassCount); - ui->statusbar->showMessage(QString("录音测试失败:8秒内未达到通过标准(连续3次或累计6次满足要求 >= %1)").arg(audioLevelStandard), 3000); - ui->recvTextEdit->append(QString(">>> 音频测试超时失败: 连续 %1 次,累计 %2 次 (要求: 连续3次或累计6次 >= %3)") - .arg(audioMicMiddleConsecutiveCount) - .arg(audioMicTotalPassCount) - .arg(audioLevelStandard)); - updateTestStatus("audio", STATUS_FAILED); - - audioTestFinished = true; - - // 发送结束音频测试指令 - QString audioEndCmd = "AT^AUDIOTEST=6\r\n"; - serial->write(audioEndCmd.toUtf8()); - qDebug() << "发送结束音频测试指令:" << audioEndCmd.trimmed(); - - // 继续后续测试 - skipToNextEnabledTest(); -} - -// 解析音频等级(从文字或rms_db数值) -QString MainWindow::parseAudioLevel(const QString& data) -{ - // 尝试从 "fac:mic level:" 后面提取值 - QRegExp levelPattern("fac:mic level:(.+)"); - if (levelPattern.indexIn(data) != -1) { - QString levelStr = levelPattern.cap(1).trimmed(); - - // 如果是数字(rms_db值),转换为等级文字 - bool ok; - double rms_db = levelStr.toDouble(&ok); - if (ok) { - // 根据rms_db值转换为等级 - if (rms_db < -60.0) { - return "静音"; - } else if (rms_db < -40.0) { - return "极低"; - } else if (rms_db < -20.0) { - return "低"; - } else if (rms_db < -6.0) { - return "中"; - } else if (rms_db < 0.0) { - return "高"; - } else { - return "最大"; - } - } else { - // 已经是文字,直接返回(去除可能的空格和换行) - return levelStr; - } - } - - return ""; -} - -// 获取音频等级数值(用于比较) -// 返回值:静音=0, 极低=1, 低=2, 中=3, 高=4, 最大=5 -int MainWindow::getAudioLevelValue(const QString& level) -{ - if (level == "静音") { - return 0; - } else if (level == "极低") { - return 1; - } else if (level == "低") { - return 2; - } else if (level == "中") { - return 3; - } else if (level == "高") { - return 4; - } else if (level == "最大") { - return 5; - } - return -1; // 未知等级 -} - -// 判断检测到的等级是否满足要求 -// 如果设置"低",那么"低"、"中"、"高"、"最大"都可以通过,但"极低"和"静音"不能通过 -bool MainWindow::isAudioLevelPass(const QString& detectedLevel, const QString& requiredLevel) -{ - int detectedValue = getAudioLevelValue(detectedLevel); - int requiredValue = getAudioLevelValue(requiredLevel); - - if (detectedValue == -1 || requiredValue == -1) { - return false; // 未知等级,不通过 - } - - // 检测到的等级值 >= 要求的等级值,则通过 - return detectedValue >= requiredValue; -} - -// 检查录音测试结果 -void MainWindow::checkAudioTestResult(const QString& data) -{ - if (!isProductionTestRunning || currentTestStep != TEST_AUDIO) { - return; - } - - qDebug() << "录音测试收到数据:" << data.trimmed(); - - // 只在录音测试过程中统计"fac:mic level:"的值 - if (data.contains("fac:mic level:")) { - // 解析检测到的音频等级 - QString detectedLevel = parseAudioLevel(data); - - if (detectedLevel.isEmpty()) { - qDebug() << "无法解析音频等级,跳过"; - return; - } - - qDebug() << QString("解析到音频等级: %1 (要求: >= %2)").arg(detectedLevel).arg(audioLevelStandard); - - // 判断是否满足要求 - bool isPass = isAudioLevelPass(detectedLevel, audioLevelStandard); - - if (isPass) { - // 满足要求,连续计数和累计计数都增加 - audioMicMiddleConsecutiveCount++; - audioMicTotalPassCount++; - - qDebug() << QString("检测到满足要求的等级 \"%1\" (>= %2),连续计数: %3,累计计数: %4") - .arg(detectedLevel) - .arg(audioLevelStandard) - .arg(audioMicMiddleConsecutiveCount) - .arg(audioMicTotalPassCount); - - ui->recvTextEdit->append(QString(">>> 音频测试: 检测到等级 %1 (>= %2),连续 %3 次,累计 %4 次") - .arg(detectedLevel) - .arg(audioLevelStandard) - .arg(audioMicMiddleConsecutiveCount) - .arg(audioMicTotalPassCount)); - } else { - // 不满足要求,连续计数清零,但累计计数不变 - qDebug() << QString("检测到不满足要求的等级 \"%1\" (< %2),重置连续计数") - .arg(detectedLevel) - .arg(audioLevelStandard); - audioMicMiddleConsecutiveCount = 0; - - ui->recvTextEdit->append(QString(">>> 音频测试: 检测到等级 %1 (< %2,不满足要求),连续计数已重置") - .arg(detectedLevel) - .arg(audioLevelStandard)); - } - - // 判断是否通过: - // 1. 连续3次满足要求,或者 - // 2. 累计6次满足要求(即使不是连续的) - bool testPassed = false; - QString passReason; - - if (audioMicMiddleConsecutiveCount >= 3) { - testPassed = true; - passReason = QString("连续 %1 次满足要求").arg(audioMicMiddleConsecutiveCount); - } else if (audioMicTotalPassCount >= 6) { - testPassed = true; - passReason = QString("累计 %1 次满足要求").arg(audioMicTotalPassCount); - } - - if (!audioTestFinished && testPassed) { - qDebug() << QString("录音测试通过:%1 (要求: >= %2)").arg(passReason).arg(audioLevelStandard); - ui->statusbar->showMessage(QString("录音测试通过:%1").arg(passReason), 3000); - ui->recvTextEdit->append(QString(">>> 音频测试通过: %1").arg(passReason)); - updateTestStatus("audio", STATUS_SUCCESS); - - audioTestFinished = true; - - // 停止8秒定时器,避免再触发超时失败 - if (audioRecordTimer->isActive()) { - audioRecordTimer->stop(); - } - - // 发送结束音频测试指令 - QString audioEndCmd = "AT^AUDIOTEST=6\r\n"; - serial->write(audioEndCmd.toUtf8()); - ui->recvTextEdit->append(">>> 发送指令: AT^AUDIOTEST=6 (结束音频测试)"); - qDebug() << "发送结束音频测试指令:" << audioEndCmd.trimmed(); - - // 继续后续测试 - skipToNextEnabledTest(); - } - } -} - -// 执行电池测试 -void MainWindow::performBatteryTest() -{ - // 检查电池测试是否启用(充电或NTC任一启用即可) - if (!isTestItemEnabled("charge") && !isTestItemEnabled("ntc")) { - qDebug() << "电池测试未启用(充电和NTC都未启用),跳过"; - updateTestStatus("battery", STATUS_UNTESTED); - updateTestStatus("charge", STATUS_UNTESTED); - updateTestStatus("ntc", STATUS_UNTESTED); - skipToNextEnabledTest(); - return; - } - - if (!isProductionTestRunning || currentTestStep != TEST_BATTERY) { - return; - } - - qDebug() << "开始电池测试"; - ui->recvTextEdit->append("========== 开始电池测试 =========="); - updateTestStatus("battery", STATUS_TESTING); - ui->statusbar->showMessage("电池测试中,正在获取电池电压...", 3000); + + qDebug() << "开始电池测试"; + ui->recvTextEdit->append("========== 开始电池测试 =========="); + updateTestStatus("battery", STATUS_TESTING); + ui->statusbar->showMessage("电池测试中,正在获取电池电压...", 3000); // 延迟100ms后发送电池电量查询指令,确保串口已准备好 QTimer::singleShot(100, this, [this]() { @@ -4288,8 +3036,8 @@ void MainWindow::checkBatteryTestResult(const QString& data) // 充电状态检查(根据checkbox决定是否判定) // 注意:充电状态每次都解析,但只有charge checkbox选中时才用充电状态来判断是否通过 - bool chargeCheckEnabled = isTestItemEnabled("charge"); - bool ntcCheckEnabled = isTestItemEnabled("ntc"); + bool chargeCheckEnabled = isTestItemEnabled("battery"); + bool ntcCheckEnabled = isTestItemEnabled("battery"); // 拼接检测结果日志(包含所有已获取的字段) // 注意:充电状态和温度数据每次都解析并记录,无论checkbox是否选中 @@ -4345,10 +3093,9 @@ void MainWindow::checkBatteryTestResult(const QString& data) ui->statusbar->showMessage("充电检查失败 - 设备未充电", 2000); ui->recvTextEdit->append(">>> 充电检查失败:设备未充电"); - // 显示充电失败对话框(每次失败都显示,让用户可以选择重试) + // 显示充电失败对话框(用户点确认会在此函数内调用skipToNextEnabledTest并返回) showBatteryChargeFailDialog(); - - // 注意:不直接返回,继续检查NTC(如果启用),然后根据所有检查结果来决定是否通过 + return; // 充电失败后不再继续后续NTC等逻辑,已由弹窗处理 } else if (chargeFound && charge != -1 && charge != 0 && charge != 1) { // 检测到charge字段但值不是0或1,可能是异常值 qDebug() << QString("充电检查:检测到异常charge值 %1,期望0或1").arg(charge); @@ -4410,7 +3157,8 @@ void MainWindow::checkBatteryTestResult(const QString& data) // NTC温度检查(根据checkbox决定是否判定) // 注意:温度数据每次都解析,但只有NTC checkbox选中时才用温度>0来判断是否通过 - bool isVoltageNormal = (voltage >= 3.3 && voltage <= 4.4); + // 电压范围:3.3V-4.6V(支持充满电时4.5V+的测量值) + bool isVoltageNormal = (voltage >= 3.3 && voltage <= 4.6); bool isTempNormal = true; // 默认温度正常(未检测到温度时不影响结果) // 温度数据每次都解析(已经在上面解析了),这里只判断是否满足条件 @@ -4484,7 +3232,7 @@ void MainWindow::checkBatteryTestResult(const QString& data) failReasons << "NTC温度检查失败"; } if (!isVoltageNormal) { - failReasons << QString("电压异常: %1V(正常范围3.3V-4.4V)").arg(voltage); + failReasons << QString("电压异常: %1V(正常范围3.3V-4.6V)").arg(voltage); } failMsg += failReasons.join(","); @@ -4501,7 +3249,7 @@ void MainWindow::checkBatteryTestResult(const QString& data) } } -// 执行G-sensor测试 +// 执行G-sensor测试(10秒内计算XYZ变化) void MainWindow::performGsensorTest() { // 检查G-sensor测试是否启用 @@ -4511,132 +3259,303 @@ void MainWindow::performGsensorTest() skipToNextEnabledTest(); return; } - + if (!isProductionTestRunning || currentTestStep != TEST_GSENSOR) { return; } - qDebug() << "开始G-sensor测试"; + qDebug() << "开始G-sensor测试(10秒XYZ变化计算)"; ui->recvTextEdit->append("========== 开始G-sensor测试 =========="); updateTestStatus("gsensor", STATUS_TESTING); - ui->statusbar->showMessage("G-sensor测试中,正在获取sensor ID...", 3000); - - // 发送G-sensor查询指令 - serial->write("AT^GETCYWEEID\r\n"); - qDebug() << "发送G-sensor测试指令: AT^GETCYWEEID"; - - // 启动10秒超时检测 - testTimeoutTimer->start(10000); + ui->statusbar->showMessage("G-sensor测试中:AT^GSENSORTEST=1(10秒)", 3000); + + // 重置采集状态 + gSensorCollecting = false; + gSensorHasStart = false; + gSensorHasLast = false; + gSensorErrorDetected = false; + gSensorAnySample = false; + gSensorAllSamplesInRange = true; + gSensorStartX = 0.0; + gSensorStartY = 0.0; + gSensorStartZ = 0.0; + gSensorLastX = 0.0; + gSensorLastY = 0.0; + gSensorLastZ = 0.0; + + // 防止通用超时干扰 + if (testTimeoutTimer->isActive()) { + testTimeoutTimer->stop(); + } + if (gSensorIntervalTimer->isActive()) { + gSensorIntervalTimer->stop(); + } + + // 1) 延迟一点后开始打印XYZ + QTimer::singleShot(300, this, [this]() { + if (!isProductionTestRunning || currentTestStep != TEST_GSENSOR) { + return; + } + if (!serial || !serial->isOpen()) { + return; + } + + ui->recvTextEdit->append(">>> 发送指令: AT^GSENSORTEST=1 (开始检测与打印)"); + serial->write("AT^GSENSORTEST=1\r\n"); + qDebug() << "发送指令: AT^GSENSORTEST=1"; + + gSensorCollecting = true; + ui->statusbar->showMessage("G-sensor检测与打印中...(10秒)", 2000); + + // 10秒后停止检测并计算 + gSensorIntervalTimer->start(10000); + }); } -// 检查G-sensor测试结果 +// 检查G-sensor测试结果(解析XYZ) void MainWindow::checkGsensorTestResult(const QString& data) { - qDebug() << "=== checkGsensorTestResult 被调用 ==="; - qDebug() << "isProductionTestRunning:" << isProductionTestRunning; - qDebug() << "currentTestStep:" << currentTestStep << "(TEST_GSENSOR=" << TEST_GSENSOR << ")"; - if (!isProductionTestRunning || currentTestStep != TEST_GSENSOR) { - qDebug() << "G-sensor测试条件不满足,跳过处理"; return; } - - qDebug() << "G-sensor测试收到数据:" << data.trimmed(); - - // 检测G-sensor ID,格式可能为:fac:gsensor id : 0x13 或类似 - if (data.contains("gsensor") && data.contains("id")) { - QRegExp gsensorPattern("(?:fac:)?gsensor\\s+id\\s*[:]\\s*(0?[xX]?[0-9A-Fa-f]+)"); - qDebug() << "尝试匹配G-sensor模式,输入数据:" << data.trimmed(); - qDebug() << "正则表达式模式:" << gsensorPattern.pattern(); - if (gsensorPattern.indexIn(data) != -1) { - // 停止超时检测,因为已经收到响应 - if (testTimeoutTimer->isActive()) { - testTimeoutTimer->stop(); - qDebug() << "G-sensor测试收到响应,停止超时检测"; - } - - qDebug() << "G-sensor正则表达式匹配通过!"; - QString gsensorId = gsensorPattern.cap(1).toUpper(); // 转换为大写便于比较 - qDebug() << "检测到G-sensor ID:" << gsensorId; - - // 检查G-sensor ID是否为有效值(期望值为0X13) - // 支持多种格式: 0X13, 0x13, 13, 19 (十进制) - bool isValidId = false; - QString expectedId = "0X13"; - - // 统一转换为大写便于比较 - QString upperGsensorId = gsensorId.toUpper(); - - if (upperGsensorId == "0X13" || upperGsensorId == "13") { - isValidId = true; - } else { - // 尝试解析数值进行比较 - bool ok; - int decimalValue = 0; - - // 如果以0X或0x开头,按十六进制解析 - if (upperGsensorId.startsWith("0X")) { - decimalValue = upperGsensorId.mid(2).toInt(&ok, 16); - } else { - // 尝试直接按十六进制解析 - decimalValue = upperGsensorId.toInt(&ok, 16); - // 如果十六进制解析失败,尝试十进制解析 - if (!ok) { - decimalValue = upperGsensorId.toInt(&ok, 10); - } - } - - if (ok && decimalValue == 0x13) { - isValidId = true; + + const QString trimmed = data.trimmed(); + + if (trimmed.contains("ERROR", Qt::CaseInsensitive) || trimmed.contains("FAIL", Qt::CaseInsensitive)) { + gSensorErrorDetected = true; + return; + } + + if (!gSensorCollecting) { + return; // 非采集窗口不做解析 + } + + // 解析格式:XYZ: X=-1.28,Y=8.22,Z=-4.60 + if (trimmed.contains("XYZ:", Qt::CaseInsensitive)) { + QRegExp xyzPattern("XYZ\\s*:\\s*X\\s*=\\s*([-]?[0-9]+\\.?[0-9]*),\\s*Y\\s*=\\s*([-]?[0-9]+\\.?[0-9]*),\\s*Z\\s*=\\s*([-]?[0-9]+\\.?[0-9]*)", + Qt::CaseInsensitive); + if (xyzPattern.indexIn(trimmed) != -1) { + bool okX = false, okY = false, okZ = false; + double x = xyzPattern.cap(1).toDouble(&okX); + double y = xyzPattern.cap(2).toDouble(&okY); + double z = xyzPattern.cap(3).toDouble(&okZ); + + if (okX && okY && okZ) { + ui->recvTextEdit->append(QString(">>> G-sensor采样: XYZ: X=%1,Y=%2,Z=%3") + .arg(x, 0, 'f', 2).arg(y, 0, 'f', 2).arg(z, 0, 'f', 2)); + gSensorAnySample = true; + // 起始XYZ:第一次解析到的点 + if (!gSensorHasStart) { + gSensorHasStart = true; + gSensorStartX = x; + gSensorStartY = y; + gSensorStartZ = z; + ui->statusbar->showMessage(QString("G-sensor起始XYZ已记录:X=%1 Y=%2 Z=%3").arg(x).arg(y).arg(z), 1500); } - } - - if (isValidId) { - qDebug() << "G-sensor ID有效,测试通过"; - ui->statusbar->showMessage(QString("G-sensor测试通过 - ID: %1").arg(gsensorId), 2000); - updateTestStatus("gsensor", STATUS_SUCCESS); - } else { - qDebug() << "G-sensor ID不匹配期望值,测试失败"; - ui->statusbar->showMessage(QString("G-sensor测试失败 - ID: %1 (期望: 0X13)").arg(gsensorId), 2000); - updateTestStatus("gsensor", STATUS_FAILED); - } - - // G-sensor测试完成,推进到下一个测试 - if (isProductionTestRunning && currentTestStep == TEST_GSENSOR) { - qDebug() << "G-sensor测试完成,推进到下一个测试"; - skipToNextEnabledTest(); - } - } else { - qDebug() << "G-sensor正则表达式匹配失败,数据:" << data.trimmed(); - - // 手动测试特定格式:fac:gsensor id : 0x13 - if (data.contains("gsensor id") && data.contains("0x13")) { - qDebug() << "手动检测到 fac:gsensor id : 0x13 格式,强制通过测试"; - ui->statusbar->showMessage("G-sensor测试通过 - ID: 0x13 (手动检测)", 2000); - updateTestStatus("gsensor", STATUS_SUCCESS); - - // G-sensor测试完成,推进到下一个测试 - if (isProductionTestRunning && currentTestStep == TEST_GSENSOR) { - qDebug() << "G-sensor测试完成(手动),推进到下一个测试"; - skipToNextEnabledTest(); + + // 末尾XYZ:10秒窗口内最后一次解析到的点 + gSensorHasLast = true; + gSensorLastX = x; + gSensorLastY = y; + gSensorLastZ = z; + + // 每条采样都要满足阈值范围 + auto parseDoubleOrInvalid = [](const QString& s, bool& ok) -> double { + QString t = s.trimmed(); + if (t.isEmpty()) { + ok = false; + return 0.0; + } + return t.toDouble(&ok); + }; + bool okXMin = false, okXMax = false, okYMin = false, okYMax = false, okZMin = false, okZMax = false; + double xMin = parseDoubleOrInvalid(ui->gsensorDeltaXMinLineEdit->text(), okXMin); + double xMax = parseDoubleOrInvalid(ui->gsensorDeltaXMaxLineEdit->text(), okXMax); + double yMin = parseDoubleOrInvalid(ui->gsensorDeltaYMinLineEdit->text(), okYMin); + double yMax = parseDoubleOrInvalid(ui->gsensorDeltaYMaxLineEdit->text(), okYMax); + double zMin = parseDoubleOrInvalid(ui->gsensorDeltaZMinLineEdit->text(), okZMin); + double zMax = parseDoubleOrInvalid(ui->gsensorDeltaZMaxLineEdit->text(), okZMax); + + bool samplePass = true; + if (okXMin && x < xMin) samplePass = false; + if (okXMax && x > xMax) samplePass = false; + if (okYMin && y < yMin) samplePass = false; + if (okYMax && y > yMax) samplePass = false; + if (okZMin && z < zMin) samplePass = false; + if (okZMax && z > zMax) samplePass = false; + + if (!samplePass) { + gSensorAllSamplesInRange = false; + ui->recvTextEdit->append(">>> G-sensor采样超限"); } } } - } else { - qDebug() << "数据不包含gsensor和id关键字,跳过G-sensor处理"; } - - // 单独检查错误响应 - if (data.contains("ERROR") || data.contains("FAIL")) { - qDebug() << "G-sensor测试失败 - 设备响应错误"; - ui->statusbar->showMessage("G-sensor测试失败 - 设备无响应", 2000); +} + +// 10秒到时:发送GSENSORTEST=0并计算变化 +void MainWindow::onGsensorIntervalTimeout() +{ + if (!isProductionTestRunning || currentTestStep != TEST_GSENSOR) { + return; + } + + gSensorIntervalTimer->stop(); + + if (serial && serial->isOpen()) { + ui->recvTextEdit->append(">>> 发送指令: AT^GSENSORTEST=0 (停止检测与打印)"); + serial->write("AT^GSENSORTEST=0\r\n"); + qDebug() << "发送指令: AT^GSENSORTEST=0"; + } + + // 给设备一点时间把最后一行XYZ打印出来 + QTimer::singleShot(500, this, [this]() { + if (!isProductionTestRunning || currentTestStep != TEST_GSENSOR) { + return; + } + gSensorCollecting = false; + finalizeGsensorTest(); + }); +} + +// 结束G-sensor判定:要求每条采样都在阈值范围内 +void MainWindow::finalizeGsensorTest() +{ + if (!isProductionTestRunning || currentTestStep != TEST_GSENSOR) { + return; + } + + if (gSensorErrorDetected) { + ui->statusbar->showMessage("G-sensor测试失败:设备返回ERROR/FAIL", 3000); updateTestStatus("gsensor", STATUS_FAILED); - - // G-sensor测试失败,仍然继续下一个测试 - if (isProductionTestRunning && currentTestStep == TEST_GSENSOR) { - qDebug() << "G-sensor测试失败,继续下一个测试"; - skipToNextEnabledTest(); + skipToNextEnabledTest(); + return; + } + + if (!gSensorAnySample) { + ui->statusbar->showMessage("G-sensor测试失败:10秒内未采到XYZ数据", 3000); + updateTestStatus("gsensor", STATUS_FAILED); + skipToNextEnabledTest(); + return; + } + + // 读取UI阈值(允许空:表示不校验该边界) + auto parseDoubleOrInvalid = [](const QString& s, bool& ok) -> double { + QString t = s.trimmed(); + if (t.isEmpty()) { + ok = false; + return 0.0; + } + return t.toDouble(&ok); + }; + + bool okXMin = false, okXMax = false, okYMin = false, okYMax = false, okZMin = false, okZMax = false; + parseDoubleOrInvalid(ui->gsensorDeltaXMinLineEdit->text(), okXMin); + parseDoubleOrInvalid(ui->gsensorDeltaXMaxLineEdit->text(), okXMax); + parseDoubleOrInvalid(ui->gsensorDeltaYMinLineEdit->text(), okYMin); + parseDoubleOrInvalid(ui->gsensorDeltaYMaxLineEdit->text(), okYMax); + parseDoubleOrInvalid(ui->gsensorDeltaZMinLineEdit->text(), okZMin); + parseDoubleOrInvalid(ui->gsensorDeltaZMaxLineEdit->text(), okZMax); + + // okXMin/okXMax 只有在“输入非空且可转 double”时才为 true + const bool xMinSet = okXMin; + const bool xMaxSet = okXMax; + const bool yMinSet = okYMin; + const bool yMaxSet = okYMax; + const bool zMinSet = okZMin; + const bool zMaxSet = okZMax; + + if (!(xMinSet || xMaxSet || yMinSet || yMaxSet || zMinSet || zMaxSet)) { + // 未配置任何阈值时:只要采到有效 XYZ 即判定通过(有值就为通过) + ui->statusbar->showMessage("G-sensor测试通过:未配置阈值,已采到有效XYZ数据", 3000); + updateTestStatus("gsensor", STATUS_SUCCESS); + skipToNextEnabledTest(); + return; + } + + if (gSensorAllSamplesInRange) { + ui->statusbar->showMessage("G-sensor测试通过:10秒内所有XYZ采样均在阈值范围", 3000); + updateTestStatus("gsensor", STATUS_SUCCESS); + } else { + ui->statusbar->showMessage("G-sensor测试失败:存在超限采样", 3000); + updateTestStatus("gsensor", STATUS_FAILED); + } + + skipToNextEnabledTest(); +} + +// 执行漏光测试 +void MainWindow::performLeaklightTest() +{ + if (!isTestItemEnabled("leaklight")) { + qDebug() << "漏光测试未启用,跳过"; + updateTestStatus("leaklight", STATUS_UNTESTED); + skipToNextEnabledTest(); + return; + } + + if (!isProductionTestRunning || currentTestStep != TEST_LEAKLIGHT) { + return; + } + + updateTestStatus("leaklight", STATUS_TESTING); + ui->recvTextEdit->append("========== 开始漏光测试 =========="); + ui->recvTextEdit->append(">>> 发送指令: AT^LEAKLIGHT"); + ui->statusbar->showMessage("漏光测试中,等待green/red/ir数据...", 3000); + + leaklightGreen = 0; + leaklightRed = 0; + leaklightIr = 0; + leaklightGreenSeen = false; + leaklightRedSeen = false; + leaklightIrSeen = false; + + if (serial && serial->isOpen()) { + serial->write("AT^LEAKLIGHT\r\n"); + } + + testTimeoutTimer->start(10000); +} + +void MainWindow::checkLeaklightTestResult(const QString& data) +{ + if (!isProductionTestRunning || currentTestStep != TEST_LEAKLIGHT) { + return; + } + + QRegExp greenPattern("green\\s*:\\s*(\\d+)", Qt::CaseInsensitive); + QRegExp redPattern("red\\s*:\\s*(\\d+)", Qt::CaseInsensitive); + QRegExp irPattern("ir\\s*:\\s*(\\d+)", Qt::CaseInsensitive); + + if (greenPattern.indexIn(data) != -1) { + leaklightGreen = greenPattern.cap(1).toInt(); + leaklightGreenSeen = true; + ui->recvTextEdit->append(QString(">>> 漏光数据 green: %1").arg(leaklightGreen)); + } + if (redPattern.indexIn(data) != -1) { + leaklightRed = redPattern.cap(1).toInt(); + leaklightRedSeen = true; + ui->recvTextEdit->append(QString(">>> 漏光数据 red: %1").arg(leaklightRed)); + } + if (irPattern.indexIn(data) != -1) { + leaklightIr = irPattern.cap(1).toInt(); + leaklightIrSeen = true; + ui->recvTextEdit->append(QString(">>> 漏光数据 ir: %1").arg(leaklightIr)); + } + + if (leaklightGreenSeen && leaklightRedSeen && leaklightIrSeen) { + if (testTimeoutTimer->isActive()) { + testTimeoutTimer->stop(); } + const bool pass = (leaklightGreen != 0 && leaklightRed != 0 && leaklightIr != 0); + if (pass) { + updateTestStatus("leaklight", STATUS_SUCCESS); + ui->statusbar->showMessage("漏光测试通过(green/red/ir均非0)", 2500); + } else { + updateTestStatus("leaklight", STATUS_FAILED); + ui->statusbar->showMessage("漏光测试失败(存在0值)", 2500); + } + skipToNextEnabledTest(); } } @@ -4658,39 +3577,33 @@ void MainWindow::performBluetoothTest() } qDebug() << "开始蓝牙测试"; - ui->recvTextEdit->append("========== 开始蓝牙测试 =========="); + appendToLogFile("\n========== 开始蓝牙测试 ==========\n"); updateTestStatus("bluetooth", STATUS_TESTING); // 重置蓝牙测试相关状态 bluetoothAddresses.clear(); + bluetoothAddressRssiMap.clear(); bluetoothTestFinished = false; detectedBluetoothAddress = ""; - detectedBluetoothRSSI = 0; - - // 检查是否使用指定地址+RSSI模式 - QString targetAddress = ui->bluetoothAddressLineEdit->text().trimmed().toUpper(); - QString rssiThresholdStr = ui->bluetoothRSSILineEdit->text().trimmed(); - - if (!targetAddress.isEmpty() && !rssiThresholdStr.isEmpty()) { - // 使用指定地址+RSSI模式 - bool ok; - int rssiThreshold = rssiThresholdStr.toInt(&ok); - if (ok) { - ui->statusbar->showMessage(QString("蓝牙测试中:扫描指定地址 %1,RSSI阈值 %2").arg(targetAddress).arg(rssiThreshold), 3000); - qDebug() << "蓝牙测试模式:指定地址+RSSI,地址:" << targetAddress << ",RSSI阈值:" << rssiThreshold; - } else { - ui->statusbar->showMessage("蓝牙测试中:RSSI阈值格式错误,使用3设备模式", 3000); - qDebug() << "RSSI阈值格式错误,切换到3设备模式"; - targetAddress = ""; - } + detectedBluetoothRSSI = -128; // 初始化为很弱的值 + bluetoothMaxRssi = -128; + + QString expectAddr = configManager.getTestConfig("bluetoothAddress", "").trimmed().toUpper(); + QString expectRssiStr = configManager.getTestConfig("bluetoothRSSI", "").trimmed(); + bool hasTargetAddr = !expectAddr.isEmpty(); + bool hasRssiThresh = !expectRssiStr.isEmpty(); + if (hasTargetAddr && hasRssiThresh) { + ui->statusbar->showMessage(QString("蓝牙测试中:必须扫到目标%1且该设备RSSI>%2dBm").arg(expectAddr).arg(expectRssiStr), 3000); + } else if (hasTargetAddr) { + ui->statusbar->showMessage(QString("蓝牙测试中:必须扫到目标设备 %1").arg(expectAddr), 3000); + } else if (hasRssiThresh) { + ui->statusbar->showMessage(QString("蓝牙测试中:未设目标地址时,最大RSSI需>%1dBm").arg(expectRssiStr), 3000); } else { - // 使用3设备模式 - ui->statusbar->showMessage("蓝牙测试中:正在开启蓝牙扫描(3设备模式)...", 3000); - qDebug() << "蓝牙测试模式:3设备模式"; + ui->statusbar->showMessage("蓝牙测试中:需扫描到3个设备且至少一个RSSI≥-90dBm通过", 3000); } // 先发送蓝牙使能指令 - ui->recvTextEdit->append(">>> 发送指令: AT^BTENABLE (启用蓝牙)"); + appendToLogFile(">>> 发送指令: AT^BTENABLE (启用蓝牙)\n"); serial->write("AT^BTENABLE\r\n"); qDebug() << "发送蓝牙使能指令: AT^BTENABLE"; @@ -4702,12 +3615,12 @@ void MainWindow::performBluetoothTest() } QString bluetoothCmd = "AT^BTSCANTEST=1\r\n"; - ui->recvTextEdit->append(">>> 发送指令: AT^BTSCANTEST=1 (开始蓝牙扫描)"); + appendToLogFile(">>> 发送指令: AT^BTSCANTEST=1 (开始蓝牙扫描)\n"); serial->write(bluetoothCmd.toUtf8()); qDebug() << "发送蓝牙测试开启指令:" << bluetoothCmd.trimmed(); - // 启动15秒超时检测 - bluetoothTestTimer->start(15000); + // 启动30秒超时检测 + bluetoothTestTimer->start(30000); }); } @@ -4725,11 +3638,17 @@ void MainWindow::checkBluetoothTestResult(const QString& data) qDebug() << "蓝牙测试收到数据:" << data.trimmed(); - // 解析蓝牙设备地址和RSSI:支持两种格式 + // 解析蓝牙设备地址和RSSI:支持多种格式 // 格式1: fac:Device: 1C:1D:2B:68:82:68, RSSI=-89 dBm // 格式2: [BtScan] Device: 1C:1D:2B:68:82:68, RSSI=-89 dBm - QRegExp bluetoothPattern1("fac:Device:\\s*([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})\\s*,\\s*RSSI=(-?\\d+)\\s*dBm"); - QRegExp bluetoothPattern2("\\[BtScan\\]\\s*Device:\\s*([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})\\s*,\\s*RSSI=(-?\\d+)\\s*dBm"); + // 格式3: MAC=20:A5:CB:CF:BD:8B,RSSI=-74dBm 或 MAC=20:A5:CB:CF:BD:8B, RSSI=-74 dBm + // 格式4: Device: 20:A5:CB:CF:BD:8B, RSSI=-74 + // 格式5: 任意包含 MAC地址(xx:xx:xx:xx:xx:xx) 和 RSSI=数字 的行 + QRegExp bluetoothPattern1("fac:Device:\\s*([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})\\s*,\\s*RSSI=(-?\\d+)\\s*dBm?"); + QRegExp bluetoothPattern2("\\[BtScan\\]\\s*Device:\\s*([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})\\s*,\\s*RSSI=(-?\\d+)\\s*dBm?"); + QRegExp bluetoothPattern3("MAC\\s*=\\s*([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})\\s*,\\s*RSSI\\s*=\\s*(-?\\d+)\\s*dBm?", Qt::CaseInsensitive); + QRegExp bluetoothPattern4("Device\\s*:\\s*([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})\\s*,\\s*RSSI\\s*=\\s*(-?\\d+)", Qt::CaseInsensitive); + QRegExp bluetoothPattern5("([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})\\s*[, ].*RSSI\\s*[=:]?\\s*(-?\\d+)", Qt::CaseInsensitive); QString address; int rssi = 0; @@ -4740,123 +3659,93 @@ void MainWindow::checkBluetoothTestResult(const QString& data) address = bluetoothPattern1.cap(1).toUpper(); rssi = bluetoothPattern1.cap(2).toInt(&rssiOk); matched = true; - qDebug() << "使用格式1 (fac:Device:) 解析"; + qDebug() << "蓝牙解析: 格式1 (fac:Device:)"; } else if (bluetoothPattern2.indexIn(data) != -1) { address = bluetoothPattern2.cap(1).toUpper(); rssi = bluetoothPattern2.cap(2).toInt(&rssiOk); matched = true; - qDebug() << "使用格式2 ([BtScan] Device:) 解析"; + qDebug() << "蓝牙解析: 格式2 ([BtScan] Device:)"; + } else if (bluetoothPattern3.indexIn(data) != -1) { + address = bluetoothPattern3.cap(1).toUpper(); + rssi = bluetoothPattern3.cap(2).toInt(&rssiOk); + matched = true; + qDebug() << "蓝牙解析: 格式3 (MAC=...)"; + } else if (bluetoothPattern4.indexIn(data) != -1) { + address = bluetoothPattern4.cap(1).toUpper(); + rssi = bluetoothPattern4.cap(2).toInt(&rssiOk); + matched = true; + qDebug() << "蓝牙解析: 格式4 (Device:...)"; + } else if (bluetoothPattern5.indexIn(data) != -1) { + address = bluetoothPattern5.cap(1).toUpper(); + rssi = bluetoothPattern5.cap(2).toInt(&rssiOk); + matched = true; + qDebug() << "蓝牙解析: 格式5 (通用MAC+RSSI)"; } if (matched && rssiOk) { qDebug() << "解析到蓝牙地址:" << address << ",RSSI:" << rssi; - // 打印到UI:检测到蓝牙设备 - ui->recvTextEdit->append(QString(">>> 检测到蓝牙设备: MAC=%1, RSSI=%2 dBm").arg(address).arg(rssi)); - - // 检查是否使用指定地址+RSSI模式 - QString targetAddress = ui->bluetoothAddressLineEdit->text().trimmed().toUpper(); - QString rssiThresholdStr = ui->bluetoothRSSILineEdit->text().trimmed(); + if (!bluetoothAddresses.contains(address)) { + bluetoothAddresses.insert(address); + appendToLogFile(QString(">>> 新蓝牙设备:MAC=%1,RSSI=%2dBm(已检测到%3个不同设备)\n") + .arg(address).arg(rssi).arg(bluetoothAddresses.size())); + } + // 更新该地址的最佳RSSI(同一设备可能多次上报,取信号最强的) + if (!bluetoothAddressRssiMap.contains(address) || rssi > bluetoothAddressRssiMap[address]) { + bluetoothAddressRssiMap[address] = rssi; + } + if (rssi > bluetoothMaxRssi) { + bluetoothMaxRssi = rssi; + } + + QString expectAddr = configManager.getTestConfig("bluetoothAddress", "").trimmed().toUpper(); + QString expectRssiStr = configManager.getTestConfig("bluetoothRSSI", "").trimmed(); + bool hasTargetAddr = !expectAddr.isEmpty(); + bool hasRssiThresh = !expectRssiStr.isEmpty(); + int expectRssi = hasRssiThresh ? expectRssiStr.toInt() : 0; + + bool pass = false; + if (hasTargetAddr && hasRssiThresh) { + int addrRssi = bluetoothAddressRssiMap.value(expectAddr, -128); + pass = bluetoothAddresses.contains(expectAddr) && addrRssi > expectRssi; + } else if (hasTargetAddr) { + pass = bluetoothAddresses.contains(expectAddr); + } else if (hasRssiThresh) { + pass = (bluetoothMaxRssi > expectRssi); + } else { + pass = (bluetoothAddresses.size() >= 3 && bluetoothMaxRssi >= -90); + } - if (!targetAddress.isEmpty() && !rssiThresholdStr.isEmpty()) { - // 指定地址+RSSI模式 - bool ok; - int rssiThreshold = rssiThresholdStr.toInt(&ok); - if (ok && address == targetAddress) { - qDebug() << "检测到目标地址:" << address << ",RSSI:" << rssi << ",阈值:" << rssiThreshold; - detectedBluetoothAddress = address; - detectedBluetoothRSSI = rssi; - - if (rssi >= rssiThreshold) { - // RSSI满足要求,测试通过 - qDebug() << "蓝牙测试通过:检测到指定地址" << address << ",RSSI" << rssi << "大于等于阈值" << rssiThreshold; - ui->recvTextEdit->append(QString(">>> 蓝牙测试通过: 目标地址 %1, RSSI=%2 dBm (>= 阈值 %3)").arg(address).arg(rssi).arg(rssiThreshold)); - ui->statusbar->showMessage(QString("蓝牙测试通过:地址 %1,RSSI %2").arg(address).arg(rssi), 3000); - updateTestStatus("bluetooth", STATUS_SUCCESS); - - bluetoothTestFinished = true; - - // 停止10秒定时器 - if (bluetoothTestTimer->isActive()) { - bluetoothTestTimer->stop(); - } - - // 发送结束蓝牙扫描指令 - QString bluetoothEndCmd = "AT^BTSCANTEST=2\r\n"; - ui->recvTextEdit->append(">>> 发送指令: AT^BTSCANTEST=2 (结束蓝牙扫描)"); - serial->write(bluetoothEndCmd.toUtf8()); - qDebug() << "发送蓝牙测试结束指令:" << bluetoothEndCmd.trimmed(); - - // 延迟200ms后发送蓝牙禁用指令 - QTimer::singleShot(200, this, [this]() { - if (isProductionTestRunning && currentTestStep == TEST_BLUETOOTH) { - ui->recvTextEdit->append(">>> 发送指令: AT^BTDISABLE (禁用蓝牙)"); - serial->write("AT^BTDISABLE\r\n"); - qDebug() << "发送蓝牙禁用指令: AT^BTDISABLE"; - - // 继续后续测试 - QTimer::singleShot(300, this, [this]() { - if (isProductionTestRunning && currentTestStep == TEST_BLUETOOTH) { - skipToNextEnabledTest(); - } - }); - } - }); - } else { - qDebug() << "RSSI不满足要求:" << rssi << "<" << rssiThreshold; - ui->recvTextEdit->append(QString(">>> 蓝牙测试: 目标地址 %1, RSSI=%2 dBm (< 阈值 %3,不满足要求)").arg(address).arg(rssi).arg(rssiThreshold)); - ui->statusbar->showMessage(QString("蓝牙测试:检测到地址 %1,但RSSI %2 小于阈值 %3").arg(address).arg(rssi).arg(rssiThreshold), 2000); - } + if (pass) { + updateTestStatus("bluetooth", STATUS_SUCCESS); + if (hasTargetAddr) { + int addrRssi = bluetoothAddressRssiMap.value(expectAddr, -128); + ui->statusbar->showMessage(QString("蓝牙测试通过:目标%1,RSSI=%2dBm").arg(expectAddr).arg(addrRssi), 3000); + } else if (hasRssiThresh) { + ui->statusbar->showMessage(QString("蓝牙测试通过:最大RSSI=%1dBm").arg(bluetoothMaxRssi), 3000); + } else { + ui->statusbar->showMessage(QString("蓝牙测试通过:检测到%1个设备,最大RSSI=%2dBm").arg(bluetoothAddresses.size()).arg(bluetoothMaxRssi), 3000); } - } else { - // 3设备模式 - if (!bluetoothAddresses.contains(address)) { - bluetoothAddresses.insert(address); - qDebug() << "检测到新的蓝牙地址:" << address << ",当前已检测到" << bluetoothAddresses.size() << "个不同地址"; - ui->recvTextEdit->append(QString(">>> 新蓝牙设备: MAC=%1, RSSI=%2 dBm (已检测到 %3 个不同设备)").arg(address).arg(rssi).arg(bluetoothAddresses.size())); - ui->statusbar->showMessage(QString("蓝牙测试:已检测到 %1 个不同地址").arg(bluetoothAddresses.size()), 2000); - - // 检查是否达到3个及以上不同的蓝牙地址 - if (bluetoothAddresses.size() >= 3) { - qDebug() << "蓝牙测试通过:已检测到" << bluetoothAddresses.size() << "个不同的蓝牙地址"; - ui->recvTextEdit->append(QString(">>> 蓝牙测试通过: 已检测到 %1 个不同设备 (要求>=3)").arg(bluetoothAddresses.size())); - ui->statusbar->showMessage(QString("蓝牙测试通过:检测到 %1 个不同地址").arg(bluetoothAddresses.size()), 3000); - updateTestStatus("bluetooth", STATUS_SUCCESS); - - bluetoothTestFinished = true; - - // 停止10秒定时器 - if (bluetoothTestTimer->isActive()) { - bluetoothTestTimer->stop(); - } - - // 发送结束蓝牙扫描指令 - QString bluetoothEndCmd = "AT^BTSCANTEST=2\r\n"; - ui->recvTextEdit->append(">>> 发送指令: AT^BTSCANTEST=2 (结束蓝牙扫描)"); - serial->write(bluetoothEndCmd.toUtf8()); - qDebug() << "发送蓝牙测试结束指令:" << bluetoothEndCmd.trimmed(); - - // 延迟200ms后发送蓝牙禁用指令 - QTimer::singleShot(200, this, [this]() { + bluetoothTestFinished = true; + if (bluetoothTestTimer->isActive()) { + bluetoothTestTimer->stop(); + } + + QString bluetoothEndCmd = "AT^BTSCANTEST=2\r\n"; + ui->recvTextEdit->append(">>> 发送指令: AT^BTSCANTEST=2 (结束蓝牙扫描)"); + serial->write(bluetoothEndCmd.toUtf8()); + + QTimer::singleShot(200, this, [this]() { + if (isProductionTestRunning && currentTestStep == TEST_BLUETOOTH) { + ui->recvTextEdit->append(">>> 发送指令: AT^BTDISABLE (禁用蓝牙)"); + serial->write("AT^BTDISABLE\r\n"); + QTimer::singleShot(300, this, [this]() { if (isProductionTestRunning && currentTestStep == TEST_BLUETOOTH) { - ui->recvTextEdit->append(">>> 发送指令: AT^BTDISABLE (禁用蓝牙)"); - serial->write("AT^BTDISABLE\r\n"); - qDebug() << "发送蓝牙禁用指令: AT^BTDISABLE"; - - // 继续后续测试 - QTimer::singleShot(300, this, [this]() { - if (isProductionTestRunning && currentTestStep == TEST_BLUETOOTH) { - skipToNextEnabledTest(); - } - }); + skipToNextEnabledTest(); } }); } - } else { - qDebug() << "检测到重复的蓝牙地址:" << address << ",已存在,跳过"; - // 即使是重复地址,也打印到UI(但标记为重复) - ui->recvTextEdit->append(QString(">>> 重复蓝牙设备: MAC=%1, RSSI=%2 dBm (已存在)").arg(address).arg(rssi)); - } + }); } } } @@ -4871,34 +3760,29 @@ void MainWindow::onBluetoothTestTimer() } if (bluetoothTestFinished) { - qDebug() << "蓝牙测试已在10秒内完成,不再处理超时"; - return; - } + qDebug() << "蓝牙测试已在30秒窗口内完成,不再处理超时"; + return; + } - // 检查是否使用指定地址+RSSI模式 - QString targetAddress = ui->bluetoothAddressLineEdit->text().trimmed().toUpper(); - QString rssiThresholdStr = ui->bluetoothRSSILineEdit->text().trimmed(); - - if (!targetAddress.isEmpty() && !rssiThresholdStr.isEmpty()) { - // 指定地址+RSSI模式超时 - bool ok; - int rssiThreshold = rssiThresholdStr.toInt(&ok); - if (ok) { - if (detectedBluetoothAddress.isEmpty()) { - qDebug() << "蓝牙测试10秒超时,未检测到指定地址" << targetAddress; - ui->statusbar->showMessage(QString("蓝牙测试失败:10秒内未检测到指定地址 %1").arg(targetAddress), 3000); - } else { - qDebug() << "蓝牙测试10秒超时,检测到指定地址" << detectedBluetoothAddress << "但RSSI" << detectedBluetoothRSSI << "小于阈值" << rssiThreshold; - ui->statusbar->showMessage(QString("蓝牙测试失败:检测到地址 %1,但RSSI %2 小于阈值 %3").arg(detectedBluetoothAddress).arg(detectedBluetoothRSSI).arg(rssiThreshold), 3000); - } + QString expectAddr = configManager.getTestConfig("bluetoothAddress", "").trimmed().toUpper(); + QString expectRssiStr = configManager.getTestConfig("bluetoothRSSI", "").trimmed(); + bool hasTargetAddr = !expectAddr.isEmpty(); + bool hasRssiThresh = !expectRssiStr.isEmpty(); + int expectRssi = hasRssiThresh ? expectRssiStr.toInt() : 0; + if (hasTargetAddr && hasRssiThresh) { + if (!bluetoothAddresses.contains(expectAddr)) { + ui->statusbar->showMessage(QString("蓝牙测试失败:未扫到目标 %1").arg(expectAddr), 3000); } else { - qDebug() << "蓝牙测试10秒超时,RSSI阈值格式错误"; - ui->statusbar->showMessage("蓝牙测试失败:RSSI阈值格式错误", 3000); + int addrRssi = bluetoothAddressRssiMap.value(expectAddr, -128); + ui->statusbar->showMessage(QString("蓝牙测试失败:目标%1 RSSI=%2dBm,需>%3dBm").arg(expectAddr).arg(addrRssi).arg(expectRssi), 3000); } + } else if (hasTargetAddr) { + ui->statusbar->showMessage(QString("蓝牙测试失败:未扫到目标设备 %1").arg(expectAddr), 3000); + } else if (hasRssiThresh) { + ui->statusbar->showMessage(QString("蓝牙测试失败:最大RSSI=%1dBm,需>%2dBm").arg(bluetoothMaxRssi).arg(expectRssi), 3000); } else { - // 3设备模式超时 - qDebug() << "蓝牙测试10秒超时,仅检测到" << bluetoothAddresses.size() << "个不同地址(需要3个及以上),测试失败"; - ui->statusbar->showMessage(QString("蓝牙测试失败:10秒内仅检测到 %1 个不同地址(需要3个及以上)").arg(bluetoothAddresses.size()), 3000); + qDebug() << "蓝牙测试30秒超时,检测到" << bluetoothAddresses.size() << "个设备,最大RSSI=" << bluetoothMaxRssi << "(需要>=3且至少一个RSSI>=-90)"; + ui->statusbar->showMessage(QString("蓝牙测试失败:检测到%1个设备,最大RSSI=%2dBm(需要>=3且至少一个≥-90dBm)").arg(bluetoothAddresses.size()).arg(bluetoothMaxRssi), 3000); } updateTestStatus("bluetooth", STATUS_FAILED); @@ -4928,496 +3812,214 @@ void MainWindow::onBluetoothTestTimer() }); } -// 检查CAT1信号测试结果 -void MainWindow::checkCat1SignalResult(const QString& data) +// 执行SE芯片测试 +void MainWindow::performSeChipTest() { - if (!isProductionTestRunning || !hasReceivedFirstSignal) { + if (!isTestItemEnabled("sechip")) { + qDebug() << "SE芯片测试未启用,跳过"; + updateTestStatus("sechip", STATUS_UNTESTED); + skipToNextEnabledTest(); return; } - - qDebug() << "CAT1信号测试收到数据:" << data.trimmed(); - qDebug() << "当前MODEM版本:" << currentModemVersion << ",类型判断:" << (isEGModem() ? "EG系列" : "非EG系列"); - - // 只在CAT1测试期间统计信号值 - if (cat1SignalReceivedCount >= 5) { - qDebug() << "CAT1测试已接收5次响应,跳过统计"; + + if (!isProductionTestRunning || currentTestStep != TEST_SECHIP) { return; } - - // 格式过滤已在调用前完成,这里不需要再次过滤 - - // 解析信号值,可能的格式:signal:31 或 CAT1GETSIGNAL OK signal:31 - QRegExp signalRegex("signal:(\\d+)"); - if (signalRegex.indexIn(data) != -1) { - int signalValue = signalRegex.cap(1).toInt(); - - // 改进的重复检查:基于信号值和时间戳的组合,避免相同信号值被误判为重复 - // 只有在短时间内收到完全相同的数据包才认为是重复 - static QString lastProcessedData; - static qint64 lastProcessedTime = 0; - qint64 currentTime = QDateTime::currentMSecsSinceEpoch(); - - // 如果是相同的数据且时间间隔小于100ms,则认为是重复数据 - if (data.trimmed() == lastProcessedData && (currentTime - lastProcessedTime) < 100) { - qDebug() << "跳过重复的CAT1数据(时间间隔:" << (currentTime - lastProcessedTime) << "ms)"; - return; - } - - // 更新最后处理的数据和时间 - lastProcessedData = data.trimmed(); - lastProcessedTime = currentTime; - - // 增加接收计数器 - cat1SignalReceivedCount++; - - qDebug() << "CAT1信号测试解析到signal值:" << signalValue << ",接收次数:" << cat1SignalReceivedCount << "/5"; - - // 将CAT1 signal值显示到界面 - appendToLogFile(QString("CAT1 Signal值: %1 (%2/5)\n").arg(signalValue).arg(cat1SignalReceivedCount)); - - // 统计信号值(用于计算平均值) - if (signalValue == 99) { - cat1Signal99Count++; - // signal=99不计入平均值计算 - qDebug() << "CAT1信号值为99,无效信号次数:" << cat1Signal99Count << "/" << cat1SignalReceivedCount; - ui->statusbar->showMessage(QString("CAT1信号测试 (%1/5) 信号值:99 (无效信号)").arg(cat1SignalReceivedCount), 2000); - } else { - // 累加有效信号值用于计算平均值 - cat1SignalTotalValue += signalValue; - if (signalValue > 25) { - cat1SignalGoodCount++; - } - - // 计算当前平均值(排除signal=99的值) - int validCount = cat1SignalReceivedCount - cat1Signal99Count; - double averageSignal = validCount > 0 ? (double)cat1SignalTotalValue / validCount : 0; - - qDebug() << "CAT1信号值" << signalValue << ",累计总和:" << cat1SignalTotalValue << ",当前平均值:" << averageSignal << ",有效次数:" << validCount << "/" << cat1SignalReceivedCount; - ui->statusbar->showMessage(QString("CAT1信号测试 (%1/5) 信号值:%2 平均值:%3").arg(cat1SignalReceivedCount).arg(signalValue).arg(averageSignal, 0, 'f', 1), 2000); - } - } else { - qDebug() << "CAT1信号测试未能解析signal值,数据:" << data.trimmed(); - } -} -// 执行4G信号质量测试 -void MainWindow::perform4GSignalTest() -{ - if (!isProductionTestRunning || currentTestStep != TEST_PPP_SIGNAL) { - return; + updateTestStatus("sechip", STATUS_TESTING); + appendToLogFile("\n========== 开始SE芯片测试 ==========\n"); + appendToLogFile(">>> 发送指令: AT^GETCYWEEACTIVE\n"); + ui->statusbar->showMessage("SE芯片测试中,等待cywee active响应...", 3000); + + if (serial && serial->isOpen()) { + serial->write("AT^GETCYWEEACTIVE\r\n"); } - qDebug() << "开始4G信号质量测试(统一使用CAT1GETSIGNAL)"; - ui->recvTextEdit->append("========== 开始4G信号质量测试 =========="); - ui->statusbar->showMessage("4G信号质量测试中...", 5000); - - // 统一使用CAT1GETSIGNAL测试方式,不再区分MODEM类型,不再使用PPP相关指令 - // 初始化CAT1信号测试计数器 - cat1SignalTestCount = 0; - cat1SignalReceivedCount = 0; - cat1SignalGoodCount = 0; - cat1Signal99Count = 0; - cat1SignalTotalValue = 0; - hasReceivedFirstSignal = true; // 标记为已启动,直接开始CAT1测试 - - // 启动CAT1信号测试定时器,每2秒发送一次 - cat1SignalTimer->start(2000); - ui->statusbar->showMessage("开始CAT1信号测试,每2秒检测一次...", 3000); - - // 立即发送第一次CAT1信号检测指令 - serial->write("AT^CAT1GETSIGNAL\r\n"); - cat1SignalTestCount++; - qDebug() << "发送第" << cat1SignalTestCount << "次CAT1信号检测指令: AT^CAT1GETSIGNAL"; - ui->recvTextEdit->append(">>> 发送指令: AT^CAT1GETSIGNAL"); + testTimeoutTimer->start(10000); } -// 4G信号检测定时器槽函数 -void MainWindow::on4GSignalTimer() +// 检查SE芯片测试结果 +void MainWindow::checkSeChipTestResult(const QString& data) { - if (!isProductionTestRunning || currentTestStep != TEST_PPP_SIGNAL) { - signal4GTimer->stop(); + if (!isProductionTestRunning || currentTestStep != TEST_SECHIP) { return; } + + // 打印SE芯片原始响应到产测日志(appendToLogFile会同时写入recvTextEdit、logtextEdit和testLogFile) + appendToLogFile(QString(">>> SE芯片原始响应: %1\n").arg(data.trimmed())); - if (isEGModem()) { - // EG系列MODEM:超时处理,如果长时间没有收到CAT1信号数据则失败 - signal4GTimer->stop(); - qDebug() << "EG系列MODEM等待CAT1信号数据超时,测试失败"; - ui->statusbar->showMessage("等待CAT1信号数据超时,测试失败", 5000); - - // 显示4G重复测试弹窗 - if (isProductionTestRunning) { - show4GRetryDialog("等待CAT1信号数据超时"); - } else { - updateTestStatus("ppp", STATUS_FAILED); + if (data.contains("cywee is active !!!", Qt::CaseInsensitive)) { + if (testTimeoutTimer->isActive()) { + testTimeoutTimer->stop(); } - } else { - // 非EG系列MODEM:统一使用CAT1GETSIGNAL,不再使用PPPSIGNALINFO - // 这个分支理论上不会执行到,因为perform4GSignalTest已经统一使用CAT1GETSIGNAL - signal4GTimer->stop(); - qDebug() << "4G信号检测超时,测试失败"; - ui->statusbar->showMessage("4G信号检测超时,测试失败", 5000); - - // 显示4G重复测试弹窗 - if (isProductionTestRunning) { - show4GRetryDialog("4G信号检测超时"); - } else { - updateTestStatus("ppp", STATUS_FAILED); + updateTestStatus("sechip", STATUS_SUCCESS); + ui->statusbar->showMessage("SE芯片测试通过(cywee is active)", 2500); + appendToLogFile(">>> SE芯片: cywee is active !!! (通过)\n"); + skipToNextEnabledTest(); + } else if (data.contains("cywee is not active !!!", Qt::CaseInsensitive)) { + if (testTimeoutTimer->isActive()) { + testTimeoutTimer->stop(); } + updateTestStatus("sechip", STATUS_FAILED); + ui->statusbar->showMessage("SE芯片测试失败(cywee is not active)", 2500); + appendToLogFile(">>> SE芯片: cywee is not active !!! (失败)\n"); + skipToNextEnabledTest(); } } -// CAT1信号测试定时器槽函数 -void MainWindow::onCat1SignalTimer() +// 执行GPS测试 +void MainWindow::performGpsTest() { - if (!isProductionTestRunning || !hasReceivedFirstSignal) { - cat1SignalTimer->stop(); - return; - } - - // 检查是否已经接收到5次响应 - if (cat1SignalReceivedCount >= 5) { - cat1SignalTimer->stop(); - - // 计算平均信号值并判断测试结果 - int validCount = cat1SignalReceivedCount - cat1Signal99Count; - double averageSignal = validCount > 0 ? (double)cat1SignalTotalValue / validCount : 0; - - qDebug() << "CAT1信号测试完成,统计结果:"; - qDebug() << " 发送指令次数:" << cat1SignalTestCount; - qDebug() << " 接收响应次数:" << cat1SignalReceivedCount; - qDebug() << " 有效信号次数:" << validCount; - qDebug() << " 无效信号(99)次数:" << cat1Signal99Count; - qDebug() << " 信号值总和:" << cat1SignalTotalValue; - qDebug() << " 平均信号值:" << averageSignal; - - if (cat1Signal99Count >= 5) { - // 连续5次都是99,判定为失败 - qDebug() << "CAT1信号测试失败,5次测试均为无效信号值99"; - ui->statusbar->showMessage("CAT1信号测试失败 - 连续5次无效信号值(99)", 3000); - - // 停止CAT1定时器 - cat1SignalTimer->stop(); - - // 显示4G重复测试弹窗 - if (isProductionTestRunning) { - show4GRetryDialog("连续5次无效信号值(99)"); - } else { - updateTestStatus("ppp", STATUS_FAILED); - } - } else if (validCount > 0 && averageSignal >= 25.0) { - // 平均信号值大于等于25,判定为通过 - qDebug() << "CAT1信号测试通过,平均信号值" << averageSignal << "大于等于25"; - ui->statusbar->showMessage(QString("CAT1信号测试通过 - 平均信号值:%1").arg(averageSignal, 0, 'f', 1), 3000); - - // CAT1信号测试通过后,进行Ping测试 - if (isProductionTestRunning) { - qDebug() << "CAT1信号测试通过,开始Ping测试"; - currentTestStep = TEST_PPP_PING; - performPingTest(); - } else { - updateTestStatus("ppp", STATUS_SUCCESS); - } - } else { - // 平均信号值小于25或没有有效数据,判定为失败 - QString failReason; - if (validCount == 0) { - failReason = "没有有效信号数据"; - } else { - failReason = QString("平均信号值%1小于25").arg(averageSignal, 0, 'f', 1); - } - - qDebug() << "CAT1信号测试失败," << failReason; - ui->statusbar->showMessage(QString("CAT1信号测试失败 - %1").arg(failReason), 3000); - - // 停止CAT1定时器 - cat1SignalTimer->stop(); - - // 显示4G重复测试弹窗 - if (isProductionTestRunning) { - show4GRetryDialog(failReason); - } else { - updateTestStatus("ppp", STATUS_FAILED); - } - } + if (!isTestItemEnabled("gps")) { + qDebug() << "GPS测试未启用,跳过"; + updateTestStatus("gps", STATUS_UNTESTED); + skipToNextEnabledTest(); return; } - - // 统一使用CAT1GETSIGNAL指令 - serial->write("AT^CAT1GETSIGNAL\r\n"); - cat1SignalTestCount++; - qDebug() << "发送第" << cat1SignalTestCount << "次CAT1信号检测指令: AT^CAT1GETSIGNAL"; -} -// Ping测试函数 -void MainWindow::performPingTest() -{ - if (!isProductionTestRunning || currentTestStep != TEST_PPP_PING) { - qDebug() << "Ping测试跳过 - 测试状态不正确"; + if (!isProductionTestRunning || currentTestStep != TEST_GPS) { return; } - - qDebug() << "========== 开始Ping测试 =========="; - ui->recvTextEdit->append("========== 开始Ping测试 =========="); - ui->statusbar->showMessage("Ping测试中 - 检查网络连通性...", 3000); - - // 重置Ping测试状态 - pingTestFinished = false; - pingSuccessCount = 0; - - // 从配置管理器获取Ping指令 - QString pingCmd = configManager.getPingCommand(); - - if (pingCmd.isEmpty()) { - qDebug() << "Ping指令未配置,使用默认指令"; - pingCmd = "AT^PING=www.163.com"; - } - - // 确保指令以\r\n结尾 - if (!pingCmd.endsWith("\r\n")) { - pingCmd += "\r\n"; - } - - // 发送Ping指令 - serial->write(pingCmd.toUtf8()); - qDebug() << "发送Ping指令:" << pingCmd.trimmed(); - appendToLogFile(QString("发送Ping指令: %1\n").arg(pingCmd.trimmed())); - - // 启动超时定时器(30秒超时) - pingTestTimer->start(30000); + + updateTestStatus("gps", STATUS_TESTING); + appendToLogFile("\n========== 开始GPS测试 ==========\n"); + ui->statusbar->showMessage("GPS测试中:10秒内应收到信息,30秒内 trace 卫星数≥3 通过", 5000); + + gpsTestFinished = false; + gpsInfoSeenIn10Sec = false; + gpsMaxNumSats = 0; + gpsMaxTrace = 0; + gpsMaxCn = 0; + + // 1. 先切换到GPS测试界面 + ui->recvTextEdit->append(">>> 发送指令: AT^ENTERTESTVIEW (进入产测界面)"); + serial->write("AT^ENTERTESTVIEW\r\n"); + QTimer::singleShot(300, this, [this]() { + if (!isProductionTestRunning || currentTestStep != TEST_GPS) return; + ui->recvTextEdit->append(">>> 发送指令: AT^TESTJUMP=GPS (切换到GPS测试界面)"); + serial->write("AT^TESTJUMP=GPS\r\n"); + QTimer::singleShot(500, this, [this]() { + if (!isProductionTestRunning || currentTestStep != TEST_GPS) return; + // 2. 开启GPS + ui->recvTextEdit->append(">>> 发送指令: AT^GPS=1 (开启GPS)"); + serial->write("AT^GPS=1\r\n"); + QTimer::singleShot(500, this, [this]() { + if (!isProductionTestRunning || currentTestStep != TEST_GPS) return; + // 3. 开始轮询AT^GPSINFO,每2秒发一次 + ui->recvTextEdit->append(">>> 发送指令: AT^GPSINFO (获取GPS信息)"); + serial->write("AT^GPSINFO\r\n"); + gpsPollTimer->start(2000); + gpsTestTimer->start(30000); + }); + }); + }); } -// 检查Ping测试结果 -void MainWindow::checkPingTestResult(const QString& data) +// 检查GPS测试结果 +void MainWindow::checkGpsTestResult(const QString& data) { - if (!isProductionTestRunning || currentTestStep != TEST_PPP_PING || pingTestFinished) { + if (!isProductionTestRunning || currentTestStep != TEST_GPS || gpsTestFinished) { return; - } - - qDebug() << "Ping测试收到数据:" << data.trimmed(); - - // 检测Ping成功的关键字 - // 可能的成功响应格式: - // "PING OK" 或 "ping success" 或包含 "bytes from" 等 - if (data.contains("PING OK", Qt::CaseInsensitive) || - data.contains("ping success", Qt::CaseInsensitive) || - data.contains("bytes from", Qt::CaseInsensitive) || - data.contains("Reply from", Qt::CaseInsensitive)) { - - pingSuccessCount++; - qDebug() << "检测到Ping成功响应,成功次数:" << pingSuccessCount; - appendToLogFile(QString("Ping成功响应: %1\n").arg(data.trimmed())); - - // 只要检测到一次成功就认为网络连通 - if (pingSuccessCount >= 1) { - pingTestFinished = true; - pingTestTimer->stop(); - - qDebug() << "Ping测试通过 - 网络连通"; - ui->statusbar->showMessage("Ping测试通过 - 网络连通", 3000); - ui->recvTextEdit->append("========== Ping测试通过 =========="); - appendToLogFile("========== Ping测试通过 ==========\n"); - - // 4G测试完全通过 - updateTestStatus("ppp", STATUS_SUCCESS); - - // 完成产测 - if (isProductionTestRunning) { - currentTestStep = TEST_COMPLETED; - isProductionTestRunning = false; - qDebug() << "Ping测试通过,产测流程完成"; - - // 保存测试记录 - QString fileName = saveTestRecord(); - - // 显示最终测试结果弹窗 - showFinalTestResult(); - } + } + + if (!gpsInfoSeenIn10Sec) { + gpsInfoSeenIn10Sec = true; // 收到第一条信息即满足10秒内看信息 + } + + // 打印AT^GPSINFO原始响应到产测日志 + appendToLogFile(QString(">>> GPS原始响应: %1\n").arg(data.trimmed())); + + // 解析 GPSINFO: stat=1, num_sats=0, valid_star=0, vga=2, quality=300000.00, last_ms=... + QRegExp numSatsPattern("num_sats\\s*=\\s*(\\d+)", Qt::CaseInsensitive); + if (numSatsPattern.indexIn(data) != -1) { + int numSats = numSatsPattern.cap(1).toInt(); + if (numSats > gpsMaxNumSats) { + gpsMaxNumSats = numSats; } + appendToLogFile(QString(">>> GPS信息: num_sats=%1\n").arg(numSats)); } - // 检测Ping失败的关键字 - else if (data.contains("PING FAIL", Qt::CaseInsensitive) || - data.contains("ping failed", Qt::CaseInsensitive) || - data.contains("timeout", Qt::CaseInsensitive) || - data.contains("unreachable", Qt::CaseInsensitive)) { - - pingTestFinished = true; - pingTestTimer->stop(); - - qDebug() << "Ping测试失败 - 网络不通"; - ui->statusbar->showMessage("Ping测试失败 - 网络不通", 3000); - ui->recvTextEdit->append("========== Ping测试失败 =========="); - appendToLogFile(QString("Ping失败响应: %1\n").arg(data.trimmed())); - appendToLogFile("========== Ping测试失败 ==========\n"); - - // 显示4G重试对话框 - if (isProductionTestRunning) { - show4GRetryDialog("Ping测试失败 - 网络不通"); - } else { - updateTestStatus("ppp", STATUS_FAILED); + + // 卫星数判定以 trace 为准(如 trace=3、trace:3、trace 3) + QRegExp tracePattern("trace\\s*[=:]?\\s*(\\d+)", Qt::CaseInsensitive); + if (tracePattern.indexIn(data) != -1) { + int tr = tracePattern.cap(1).toInt(); + if (tr > gpsMaxTrace) { + gpsMaxTrace = tr; + } + appendToLogFile(QString(">>> GPS信息: trace=%1\n").arg(tr)); + } + + // 解析CN值(可能格式:cn=30 或 CN:30 或类似) + QRegExp cnPattern("cn\\s*[=:]\\s*(\\d+)", Qt::CaseInsensitive); + if (cnPattern.indexIn(data) != -1) { + int cn = cnPattern.cap(1).toInt(); + if (cn > gpsMaxCn) { + gpsMaxCn = cn; } } + + // 实时判断:trace 卫星数≥3 即通过 + if (gpsInfoSeenIn10Sec && gpsMaxTrace >= 3) { + gpsTestTimer->stop(); + gpsPollTimer->stop(); + gpsTestFinished = true; + updateTestStatus("gps", STATUS_SUCCESS); + ui->statusbar->showMessage(QString("GPS测试通过:trace卫星数=%1").arg(gpsMaxTrace), 3000); + ui->recvTextEdit->append(QString(">>> GPS测试通过:trace=%1 (num_sats记录=%2, CNmax=%3)") + .arg(gpsMaxTrace).arg(gpsMaxNumSats).arg(gpsMaxCn)); + serial->write("AT^GPS=0\r\n"); + ui->recvTextEdit->append(">>> 发送指令: AT^GPS=0 (关闭GPS)"); + skipToNextEnabledTest(); + } } -// Ping测试超时处理 -void MainWindow::onPingTestTimeout() +// GPS轮询:每2秒发送AT^GPSINFO +void MainWindow::onGpsPollTimer() { - pingTestTimer->stop(); - - if (!isProductionTestRunning || currentTestStep != TEST_PPP_PING || pingTestFinished) { - qDebug() << "Ping超时但测试状态已改变,忽略"; + if (!isProductionTestRunning || currentTestStep != TEST_GPS || gpsTestFinished) { + gpsPollTimer->stop(); return; } - - pingTestFinished = true; - - qDebug() << "Ping测试超时 - 30秒内未收到响应"; - ui->statusbar->showMessage("Ping测试超时 - 网络连通性检查失败", 3000); - ui->recvTextEdit->append("========== Ping测试超时 =========="); - appendToLogFile("========== Ping测试超时 - 30秒内未收到响应 ==========\n"); - - // 显示4G重试对话框 - if (isProductionTestRunning) { - show4GRetryDialog("Ping测试超时"); - } else { - updateTestStatus("ppp", STATUS_FAILED); + if (serial && serial->isOpen()) { + serial->write("AT^GPSINFO\r\n"); } } -// 检查4G信号质量结果 -void MainWindow::check4GSignalResult(const QString& data) +// GPS测试30秒超时处理 +void MainWindow::onGpsTestTimer() { - if (!isProductionTestRunning || currentTestStep != TEST_PPP_SIGNAL) { + gpsTestTimer->stop(); + gpsPollTimer->stop(); + + if (!isProductionTestRunning || currentTestStep != TEST_GPS) { return; } - - // 如果已经启动了CAT1信号测试,则不再使用旧的4G信号测试逻辑 - if (hasReceivedFirstSignal) { - qDebug() << "已启动CAT1信号测试,跳过旧的4G信号测试逻辑"; + + if (gpsTestFinished) { return; } - - qDebug() << "4G信号检测收到数据:" << data.trimmed(); - - // 保存静态变量来跟踪RSRQ和RSRP值 - static double currentRsrq = -6.8; // 默认示例值 - static double currentRsrp = -82; // 默认示例值 - static int rsrqRaw = 26; // 原始RSRQ值 - static int rsrpRaw = 58; // 原始RSRP值 - static bool rsrqReceived = false; - static bool rsrpReceived = false; - - // 解析APP|[PPP] RSRQ格式 - QRegExp appRsrqPattern("APP\\|\\[PPP\\]\\s*RSRQ:\\s*(\\d+)\\s*\\(([\\d\\.-]+)\\s*dB\\)"); - if (appRsrqPattern.indexIn(data) != -1) { - rsrqRaw = appRsrqPattern.cap(1).toInt(); - currentRsrq = appRsrqPattern.cap(2).toDouble(); - rsrqReceived = true; - qDebug() << "解析到APP|[PPP] RSRQ值:" << rsrqRaw << "(" << currentRsrq << "dB)"; - - // 输出RSRQ评级(按要求格式) - QString rsrqGrade = getRsrqGrade(currentRsrq); - QString rsrqOutput = QString("RSRQ: %1 (%2 dB:%3)").arg(rsrqRaw).arg(currentRsrq).arg(rsrqGrade); - qDebug() << rsrqOutput; - appendToLogFile(rsrqOutput + "\n"); - return; // 每行单独处理 - } - - // 解析APP|[PPP] RSRP格式 - QRegExp appRsrpPattern("APP\\|\\[PPP\\]\\s*RSRP:\\s*(\\d+)\\s*\\(([\\d\\.-]+)\\s*dBm\\)"); - if (appRsrpPattern.indexIn(data) != -1) { - rsrpRaw = appRsrpPattern.cap(1).toInt(); - currentRsrp = appRsrpPattern.cap(2).toDouble(); - rsrpReceived = true; - qDebug() << "解析到APP|[PPP] RSRP值:" << rsrpRaw << "(" << currentRsrp << "dBm)"; - - // 输出RSRP评级(按要求格式) - QString rsrpGrade = getRsrpGrade(currentRsrp); - QString rsrpOutput = QString("RSRP: %1 (%2 dBm:%3)").arg(rsrpRaw).arg(currentRsrp).arg(rsrpGrade); - qDebug() << rsrpOutput; - appendToLogFile(rsrpOutput + "\n"); - return; // 每行单独处理 - } - - // 解析Overall LTE Quality格式 - if (data.contains("APP|[PPP] Overall LTE Quality:") || data.contains("总体评级:")) { - // 收到一次有效的信号质量数据 - signalReceived4GCount++; - qDebug() << "收到第" << signalReceived4GCount << "次有效4G信号数据"; - - // 如果还没收到RSRQ和RSRP,使用模拟数据进行示例显示 - if (!rsrqReceived) { - QString rsrqGrade = getRsrqGrade(currentRsrq); - QString rsrqOutput = QString("RSRQ: %1 (%2 dB:%3)").arg(rsrqRaw).arg(currentRsrq).arg(rsrqGrade); - qDebug() << "模拟RSRQ输出:" << rsrqOutput; - ui->recvTextEdit->insertPlainText(rsrqOutput + "\n"); - ui->recvTextEdit->moveCursor(QTextCursor::End); - } - - if (!rsrpReceived) { - QString rsrpGrade = getRsrpGrade(currentRsrp); - QString rsrpOutput = QString("RSRP: %1 (%2 dBm:%3)").arg(rsrpRaw).arg(currentRsrp).arg(rsrpGrade); - qDebug() << "模拟RSRP输出:" << rsrpOutput; - ui->recvTextEdit->insertPlainText(rsrpOutput + "\n"); - ui->recvTextEdit->moveCursor(QTextCursor::End); - } - - // 输出完整的信号评估 - QString overallAssessment = getOverallSignalAssessment(currentRsrp, currentRsrq); - QString overallOutput = QString("总体评级:%1").arg(overallAssessment); - qDebug() << overallOutput; - appendToLogFile(overallOutput + "\n"); - - // 检查信号质量是否良好以上 - bool rsrpGood = data.contains("覆盖极好") || data.contains("覆盖良好") || - overallAssessment.contains("覆盖强度极好") || overallAssessment.contains("覆盖强度良好"); - bool rsrqGood = data.contains("质量极佳") || data.contains("质量良好") || - overallAssessment.contains("信号质量极佳") || overallAssessment.contains("信号质量良好"); - bool isSignalGood = rsrpGood && rsrqGood; - - qDebug() << "信号质量检查: RSRP良好以上=" << rsrpGood << ", RSRQ良好以上=" << rsrqGood << ", 综合判断=" << isSignalGood; - - if (data.contains("LTE信号良好") || isSignalGood) { - signalGood4GCount++; - qDebug() << "检测到LTE信号良好以上,当前良好次数:" << signalGood4GCount << "/5,已接收数据次数:" << signalReceived4GCount << "/5"; - ui->statusbar->showMessage(QString("LTE信号良好 (%1/5) 数据(%2/5)").arg(signalGood4GCount).arg(signalReceived4GCount), 2000); + + gpsTestFinished = true; + + bool pass = gpsInfoSeenIn10Sec && gpsMaxTrace >= 3; + if (pass) { + updateTestStatus("gps", STATUS_SUCCESS); + ui->statusbar->showMessage(QString("GPS测试通过:trace卫星数=%1").arg(gpsMaxTrace), 3000); + } else { + updateTestStatus("gps", STATUS_FAILED); + QString reason; + if (!gpsInfoSeenIn10Sec) { + reason = "10秒内未收到GPS信息"; } else { - qDebug() << "LTE信号质量不达标(需要良好以上),已接收数据次数:" << signalReceived4GCount << "/5"; - ui->statusbar->showMessage(QString("LTE信号质量不达标 数据(%1/5)").arg(signalReceived4GCount), 2000); - } - - // 检查是否已收到5次数据 - if (signalReceived4GCount >= 5) { - signal4GTimer->stop(); - - if (signalGood4GCount >= 5) { - // 5次数据都是良好以上,测试通过 - qDebug() << "5次4G信号数据都达到良好以上标准,测试通过"; - ui->statusbar->showMessage("4G信号测试通过 - 5次信号都达到良好以上!", 5000); - updateTestStatus("ppp", STATUS_SUCCESS); - } else { - // 有信号质量不达标,测试失败 - qDebug() << "4G信号测试失败 - 只有" << signalGood4GCount << "次达到良好以上标准"; - ui->statusbar->showMessage(QString("4G信号测试失败 - 只有%1次达到良好标准").arg(signalGood4GCount), 5000); - updateTestStatus("ppp", STATUS_FAILED); - } - - // 完成产测 - currentTestStep = TEST_COMPLETED; - isProductionTestRunning = false; - qDebug() << "产测流程完成"; - - // 保存测试记录 - QString fileName = saveTestRecord(); - - // 显示最终测试结果弹窗 - showFinalTestResult(); + reason = QString("trace卫星数=%1(需要≥3)").arg(gpsMaxTrace); } - - // 重置状态标志,为下次检测准备 - rsrqReceived = false; - rsrpReceived = false; + ui->statusbar->showMessage(QString("GPS测试失败:%1").arg(reason), 3000); + ui->recvTextEdit->append(QString(">>> GPS测试失败:%1").arg(reason)); } + + serial->write("AT^GPS=0\r\n"); + ui->recvTextEdit->append(">>> 发送指令: AT^GPS=0 (关闭GPS)"); + skipToNextEnabledTest(); } // 获取RSRQ信号质量评级 @@ -5528,12 +4130,9 @@ void MainWindow::showBatteryChargeFailDialog() chargeFailDialog.exec(); if (chargeFailDialog.clickedButton() == confirmButton) { - qDebug() << "用户确认电池充电失败,标记测试失败"; + qDebug() << "用户确认电池充电失败,标记测试失败,推进到下一项测试"; updateTestStatus("battery", STATUS_FAILED); - - // 继续下一个测试 if (isProductionTestRunning && currentTestStep == TEST_BATTERY) { - qDebug() << "电池测试失败确认,继续下一个测试"; skipToNextEnabledTest(); } } else if (chargeFailDialog.clickedButton() == retryButton) { @@ -5567,125 +4166,22 @@ void MainWindow::showBatteryChargeFailDialog() } } -// 显示4G重复测试弹窗 -void MainWindow::show4GRetryDialog(const QString& failReason) -{ - QMessageBox retryDialog(this); - retryDialog.setWindowTitle("4G测试失败"); - retryDialog.setText("📶 4G信号测试失败"); - - // 构建详细的失败信息,包括SIM卡状态 - QString detailedInfo = QString("测试失败原因:%1").arg(failReason); - - // 添加SIM卡状态信息 - if (currentSimStatus == 0) { - detailedInfo += "\n\n⚠️ SIM卡状态:未插入"; - detailedInfo += "\n请插入SIM卡后重新测试"; - } else if (currentSimStatus == 1) { - detailedInfo += "\n\n✅ SIM卡状态:已插入"; - } else { - detailedInfo += "\n\n❓ SIM卡状态:未知"; - } - - detailedInfo += "\n\n是否重新进行4G测试?"; - - retryDialog.setInformativeText(detailedInfo); - retryDialog.setIcon(QMessageBox::Warning); - - // 添加自定义按钮 - QPushButton *retryButton = retryDialog.addButton("重新测试", QMessageBox::AcceptRole); - QPushButton *skipButton = retryDialog.addButton("跳过测试", QMessageBox::RejectRole); - - retryDialog.exec(); - - if (retryDialog.clickedButton() == retryButton) { - qDebug() << "用户选择重新进行4G测试"; - ui->statusbar->showMessage("重新开始4G测试...", 2000); - - // 重置4G测试状态 - hasReceivedFirstSignal = false; - cat1SignalTestCount = 0; - cat1SignalReceivedCount = 0; - cat1SignalGoodCount = 0; - cat1Signal99Count = 0; - cat1SignalTotalValue = 0; - // 移除了cat1ProcessedSignals的清理,改用时间戳方式 - - // 重置4G测试计数器,确保重新测试时从头开始 - initRetry4GCount = 0; - signalTest4GCount = 0; - signalReceived4GCount = 0; - signalGood4GCount = 0; - pppCommandCount = 0; - hasReceivedSignalSim = false; - hasReceivedPppInfo = false; - currentSimStatus = -1; // 重置SIM卡状态为未知 - - // 停止所有4G相关定时器 - if (cat1SignalTimer->isActive()) { - cat1SignalTimer->stop(); - } - if (signal4GTimer->isActive()) { - signal4GTimer->stop(); - } - if (test4GTimer->isActive()) { - test4GTimer->stop(); - } - - // 重新开始4G测试 - updateTestStatus("ppp", STATUS_TESTING); - currentTestStep = TEST_PPP_INIT; - perform4GTest(); - - } else if (retryDialog.clickedButton() == skipButton) { - qDebug() << "用户选择跳过4G测试,标记测试失败"; - updateTestStatus("ppp", STATUS_FAILED); - - // 停止所有4G相关定时器,确保测试完全停止 - if (cat1SignalTimer->isActive()) { - cat1SignalTimer->stop(); - qDebug() << "停止CAT1信号测试定时器"; - } - if (signal4GTimer->isActive()) { - signal4GTimer->stop(); - qDebug() << "停止4G信号检测定时器"; - } - if (test4GTimer->isActive()) { - test4GTimer->stop(); - qDebug() << "停止4G测试超时定时器"; - } - - // 重置4G测试相关状态 - hasReceivedFirstSignal = false; - hasReceivedSignalSim = false; - hasReceivedPppInfo = false; - - // 完成产测(失败) - if (isProductionTestRunning) { - currentTestStep = TEST_COMPLETED; - isProductionTestRunning = false; - qDebug() << "用户跳过4G测试,产测流程完成"; - - // 保存测试记录 - QString fileName = saveTestRecord(); - - // 显示最终测试结果弹窗 - showFinalTestResult(); - } - } -} - void MainWindow::showFinalTestResult() { // 获取各项测试结果 QString lcdResult = ui->lcdStatusText->text(); + QString tpResult = ui->tpStatusText->text(); + QString brightResult = ui->brightStatusText->text(); QString motorResult = ui->motorStatusText->text(); QString keyResult = ui->keyStatusText->text(); + QString deepsleepResult = ui->deepsleepStatusText->text(); QString audioResult = ui->audioStatusText->text(); QString batteryResult = ui->batteryStatusText->text(); QString gsensorResult = ui->gsensorStatusText->text(); + QString leaklightResult = ui->leaklightStatusText->text(); QString bluetoothResult = ui->bluetoothStatusText->text(); - QString pppResult = ui->pppStatusText->text(); + QString sechipResult = ui->sechipStatusText->text(); + QString gpsResult = ui->gpsStatusText->text(); QString overallResult = ui->overallStatusText->text(); // 创建结果弹窗 @@ -5696,16 +4192,33 @@ void MainWindow::showFinalTestResult() QString resultContent = QString( "===== 产测完成 =====\n\n" "📱 LCD测试:%1\n" - "🔧 马达测试:%2\n" - "⌨️ 按键测试:%3\n" - "🎤 录音测试:%4\n" - "🔋 电池测试:%5\n" - "📡 G-sensor测试:%6\n" - "📶 蓝牙测试:%7\n" - "🌐 4G测试:%8\n\n" - "📊 整体结果:%9" - ).arg(lcdResult, motorResult, keyResult, audioResult, - batteryResult, gsensorResult, bluetoothResult, pppResult, overallResult); + "🖐️ 触控测试:%2\n" + "🌞 亮度测试:%3\n" + "🔧 马达测试:%4\n" + "⌨️ 按键测试:%5\n" + "⚡ 功耗测试:%6\n" + "🎤 录音测试:%7\n" + "🔋 电池测试:%8\n" + "📡 G-sensor测试:%9\n" + "❤️ 漏光测试:%10\n" + "📶 蓝牙测试:%11\n" + "🔒 SE芯片测试:%12\n" + "📍 GPS测试:%13\n" + "📊 整体结果:%14" + ).arg(lcdResult) + .arg(tpResult) + .arg(brightResult) + .arg(motorResult) + .arg(keyResult) + .arg(deepsleepResult) + .arg(audioResult) + .arg(batteryResult) + .arg(gsensorResult) + .arg(leaklightResult) + .arg(bluetoothResult) + .arg(sechipResult) + .arg(gpsResult) + .arg(overallResult); resultDialog.setText(resultContent); @@ -5755,15 +4268,14 @@ void MainWindow::showFinalTestResult() qDebug() << "显示最终测试结果弹窗"; resultDialog.exec(); - // 用户点击确定后的处理 - qDebug() << "用户确认测试结果,发送退出产测指令"; + // 用户点击确定后的处理:关机,不执行重启/RESTORE + qDebug() << "用户确认测试结果,发送关机指令"; - // 发送退出产测指令 if (serial->isOpen()) { - ui->recvTextEdit->append(">>> 发送指令: AT^RESTORE (退出产测)"); - serial->write("AT^RESTORE\r\n"); - qDebug() << "发送退出产测指令: AT^RESTORE"; - ui->statusbar->showMessage("已退出产测模式", 3000); + ui->recvTextEdit->append(">>> 发送指令: AT^POWEROFF (产测结束关机)"); + serial->write("AT^POWEROFF\r\n"); + qDebug() << "发送关机指令: AT^POWEROFF"; + ui->statusbar->showMessage("已发送关机指令", 3000); } // 恢复产测按钮状态 - 变回蓝色并启用 @@ -5809,6 +4321,10 @@ void MainWindow::onTestTimeout() testName = "motor"; testDescription = "马达测试"; break; + case TEST_AUDIO: + testName = "mic"; + testDescription = "录音测试"; + break; case TEST_BATTERY: testName = "battery"; testDescription = "电池测试"; @@ -5817,10 +4333,22 @@ void MainWindow::onTestTimeout() testName = "gsensor"; testDescription = "G-sensor测试"; break; + case TEST_LEAKLIGHT: + testName = "leaklight"; + testDescription = "漏光测试"; + break; case TEST_BLUETOOTH: testName = "bluetooth"; testDescription = "蓝牙测试"; break; + case TEST_SECHIP: + testName = "sechip"; + testDescription = "SE芯片测试"; + break; + case TEST_GPS: + testName = "gps"; + testDescription = "GPS测试"; + break; default: qDebug() << "未知的测试步骤超时:" << currentTestStep; return; @@ -5891,15 +4419,27 @@ void MainWindow::onTestTimeout() case TEST_MOTOR: performMotorTest(); break; + case TEST_AUDIO: + performAudioTest(); + break; case TEST_BATTERY: performBatteryTest(); break; case TEST_GSENSOR: performGsensorTest(); break; + case TEST_LEAKLIGHT: + performLeaklightTest(); + break; case TEST_BLUETOOTH: performBluetoothTest(); break; + case TEST_SECHIP: + performSeChipTest(); + break; + case TEST_GPS: + performGpsTest(); + break; default: break; } @@ -5946,51 +4486,41 @@ void MainWindow::clearProductionTestState() qDebug() << "停止马达测试定时器"; } - if (test4GTimer->isActive()) { - test4GTimer->stop(); - qDebug() << "停止4G测试定时器"; - } - - if (signal4GTimer->isActive()) { - signal4GTimer->stop(); - qDebug() << "停止4G信号检测定时器"; - } - - if (cat1SignalTimer->isActive()) { - cat1SignalTimer->stop(); - qDebug() << "停止CAT1信号测试定时器"; - } - if (testTimeoutTimer->isActive()) { testTimeoutTimer->stop(); qDebug() << "停止通用超时定时器"; } + + if (gSensorIntervalTimer->isActive()) { + gSensorIntervalTimer->stop(); + qDebug() << "停止G-sensor采集定时器"; + } + if (gpsTestTimer->isActive()) { + gpsTestTimer->stop(); + qDebug() << "停止GPS测试定时器"; + } + if (gpsPollTimer->isActive()) { + gpsPollTimer->stop(); + qDebug() << "停止GPS轮询定时器"; + } + + // 清空测试时间记录 + testStartTimeMap.clear(); + testDurationSecMap.clear(); // 重置产测状态变量 isProductionTestRunning = false; currentTestStep = TEST_IDLE; currentLcdColor = 0; - initRetry4GCount = 0; - signalTest4GCount = 0; - signalReceived4GCount = 0; - signalGood4GCount = 0; - hasReceivedFirstSignal = false; - cat1SignalTestCount = 0; - cat1SignalReceivedCount = 0; - cat1SignalGoodCount = 0; - cat1Signal99Count = 0; - cat1SignalTotalValue = 0; - // 移除了cat1ProcessedSignals的清理,改用时间戳方式 - currentModemVersion = ""; // 初始化MODEM版本为空 - currentSimStatus = -1; // 重置SIM卡状态为未知 // 重置弹窗状态 lcdTestPopupShown = false; motorTestPopupShown = false; + touchTestPopupShown = false; + brightTestPopupShown = false; + deepsleepTestPopupShown = false; isWaitingForUserConfirm = false; keyTestStarted = false; - audioTestPopupShown = false; - audioPlaybackPopupShown = false; // 重置按键测试状态 for (int i = 0; i < 4; i++) { @@ -6016,18 +4546,15 @@ void MainWindow::on_agingTestButton_clicked() // 清空设备信息 ui->VerlineEdit->clear(); // 清空版本信息 - ui->IMEIlineEdit->clear(); // 清空IMEI - ui->ICCIDlineEdit->clear(); // 清空ICCID - ui->ModemVerlineEdit->clear(); // 清空调制解调器版本 - ui->PYVerlineEdit->clear(); // 清空python版本 // 重置弹窗状态 lcdTestPopupShown = false; motorTestPopupShown = false; + touchTestPopupShown = false; + brightTestPopupShown = false; + deepsleepTestPopupShown = false; isWaitingForUserConfirm = false; keyTestStarted = false; - audioTestPopupShown = false; - audioPlaybackPopupShown = false; // 不清除状态指示器,保留上次测试结果供查看 // 用户可以一边查看测试结果,一边执行老化测试 @@ -6044,18 +4571,15 @@ void MainWindow::on_agingTestButton_2_clicked() // 清空设备信息 ui->VerlineEdit->clear(); // 清空版本信息 - ui->IMEIlineEdit->clear(); // 清空IMEI - ui->ICCIDlineEdit->clear(); // 清空ICCID - ui->ModemVerlineEdit->clear(); // 清空调制解调器版本 - ui->PYVerlineEdit->clear(); // 清空python版本 // 重置弹窗状态 lcdTestPopupShown = false; motorTestPopupShown = false; + touchTestPopupShown = false; + brightTestPopupShown = false; + deepsleepTestPopupShown = false; isWaitingForUserConfirm = false; keyTestStarted = false; - audioTestPopupShown = false; - audioPlaybackPopupShown = false; // 不清除状态指示器,保留上次测试结果供查看 // 用户可以一边查看测试结果,一边执行恢复出厂操作 @@ -6072,18 +4596,15 @@ void MainWindow::on_openButton_3_clicked() // 清空设备信息 ui->VerlineEdit->clear(); // 清空版本信息 - ui->IMEIlineEdit->clear(); // 清空IMEI - ui->ICCIDlineEdit->clear(); // 清空ICCID - ui->ModemVerlineEdit->clear(); // 清空调制解调器版本 - ui->PYVerlineEdit->clear(); // 清空python版本 // 重置弹窗状态 lcdTestPopupShown = false; motorTestPopupShown = false; + touchTestPopupShown = false; + brightTestPopupShown = false; + deepsleepTestPopupShown = false; isWaitingForUserConfirm = false; keyTestStarted = false; - audioTestPopupShown = false; - audioPlaybackPopupShown = false; // 不清除状态指示器,保留上次测试结果供查看 @@ -6338,10 +4859,6 @@ void MainWindow::on_getVersionInfoButton_clicked() // 首先清除所有版本信息相关的LineEdit ui->VerlineEdit->clear(); - ui->IMEIlineEdit->clear(); - ui->ModemVerlineEdit->clear(); - ui->PYVerlineEdit->clear(); - ui->ICCIDlineEdit->clear(); ui->factoryModeLineEdit->clear(); ui->factoryModeLineEdit->setStyleSheet(""); // 清除样式 @@ -6395,31 +4912,39 @@ void MainWindow::saveTestConfigToSettings() QString portText = ui->portNameBox->currentText(); configManager.setTestConfig("portName", portText); - // 保存音频测试参数 - configManager.setTestConfig("audioVolume", ui->audioVolumeLineEdit->text()); - if (ui->recordVolumelineEdit) { - configManager.setTestConfig("audioLevelStandard", ui->recordVolumelineEdit->text().trimmed()); - } - // 保存期望版本信息 - configManager.setTestConfig("expectedModemVer", ui->expectedModemVerLineEdit->text()); - configManager.setTestConfig("expectedPYVer", ui->expectedPYVerLineEdit->text()); configManager.setTestConfig("expectedVer", ui->expectedVerLineEdit->text()); // 保存蓝牙测试参数 configManager.setTestConfig("bluetoothAddress", ui->bluetoothAddressLineEdit->text()); configManager.setTestConfig("bluetoothRSSI", ui->bluetoothRSSILineEdit->text()); + // 保存录音测试参数 + configManager.setTestConfig("audioVolume", ui->audioVolumeLineEdit->text()); + configManager.setTestConfig("audioLevelStandard", ui->audioLevelStandardLineEdit->text()); + // 保存测试项checkbox状态 configManager.setTestCheckbox("lcd", ui->checkBox_lcd->isChecked()); configManager.setTestCheckbox("vib", ui->checkBox_vib->isChecked()); configManager.setTestCheckbox("btn", ui->checkBox_btn->isChecked()); - configManager.setTestCheckbox("mic", ui->checkBox_mic->isChecked()); - configManager.setTestCheckbox("charge", ui->checkBox_charge->isChecked()); - configManager.setTestCheckbox("ntc", ui->checkBox_ntc->isChecked()); + configManager.setTestCheckbox("tp", ui->checkBox_tp->isChecked()); + configManager.setTestCheckbox("bright", ui->checkBox_bright->isChecked()); + configManager.setTestCheckbox("deepsleep", ui->checkBox_deepsleep->isChecked()); + configManager.setTestCheckbox("battery", ui->checkBox_battery->isChecked()); + configManager.setTestCheckbox("leaklight", ui->checkBox_leaklight->isChecked()); // gsensor checkbox可能不存在,跳过 configManager.setTestCheckbox("bt", ui->checkBox_bt->isChecked()); - configManager.setTestCheckbox("4g", ui->checkBox_4g->isChecked()); + configManager.setTestCheckbox("mic", ui->checkBox_mic->isChecked()); + configManager.setTestCheckbox("sechip", ui->checkBox_sechip->isChecked()); + configManager.setTestCheckbox("gps", ui->checkBox_gps->isChecked()); + + // 保存G-sensor XYZ变化标准(10秒) + configManager.setTestConfig("gsensorDeltaXMin", ui->gsensorDeltaXMinLineEdit->text()); + configManager.setTestConfig("gsensorDeltaXMax", ui->gsensorDeltaXMaxLineEdit->text()); + configManager.setTestConfig("gsensorDeltaYMin", ui->gsensorDeltaYMinLineEdit->text()); + configManager.setTestConfig("gsensorDeltaYMax", ui->gsensorDeltaYMaxLineEdit->text()); + configManager.setTestConfig("gsensorDeltaZMin", ui->gsensorDeltaZMinLineEdit->text()); + configManager.setTestConfig("gsensorDeltaZMax", ui->gsensorDeltaZMaxLineEdit->text()); // 保存到文件 configManager.saveConfig(); @@ -6443,36 +4968,39 @@ void MainWindow::loadTestConfigFromSettings() } } - // 恢复音频测试参数 - QString audioVolume = configManager.getTestConfig("audioVolume", "2.0"); - ui->audioVolumeLineEdit->setText(audioVolume); - - QString audioLevelStandard = configManager.getTestConfig("audioLevelStandard", "中"); - if (ui->recordVolumelineEdit) { - ui->recordVolumelineEdit->setText(audioLevelStandard); - } - // 恢复期望版本信息 - ui->expectedModemVerLineEdit->setText(configManager.getTestConfig("expectedModemVer", "")); - ui->expectedPYVerLineEdit->setText(configManager.getTestConfig("expectedPYVer", "")); ui->expectedVerLineEdit->setText(configManager.getTestConfig("expectedVer", "")); // 恢复蓝牙测试参数 ui->bluetoothAddressLineEdit->setText(configManager.getTestConfig("bluetoothAddress", "")); ui->bluetoothRSSILineEdit->setText(configManager.getTestConfig("bluetoothRSSI", "")); + // 恢复录音测试参数 + ui->audioVolumeLineEdit->setText(configManager.getTestConfig("audioVolume", "2.0")); + ui->audioLevelStandardLineEdit->setText(configManager.getTestConfig("audioLevelStandard", "中")); + // 恢复测试项checkbox状态 ui->checkBox_lcd->setChecked(configManager.getTestCheckbox("lcd", true)); ui->checkBox_vib->setChecked(configManager.getTestCheckbox("vib", true)); ui->checkBox_btn->setChecked(configManager.getTestCheckbox("btn", true)); - ui->checkBox_mic->setChecked(configManager.getTestCheckbox("mic", true)); - ui->checkBox_charge->setChecked(configManager.getTestCheckbox("charge", true)); - ui->checkBox_ntc->setChecked(configManager.getTestCheckbox("ntc", true)); + ui->checkBox_tp->setChecked(configManager.getTestCheckbox("tp", true)); + ui->checkBox_bright->setChecked(configManager.getTestCheckbox("bright", true)); + ui->checkBox_deepsleep->setChecked(configManager.getTestCheckbox("deepsleep", true)); + ui->checkBox_battery->setChecked(configManager.getTestCheckbox("battery", configManager.getTestCheckbox("charge", true))); + ui->checkBox_leaklight->setChecked(configManager.getTestCheckbox("leaklight", true)); // gsensor checkbox可能不存在,跳过 ui->checkBox_bt->setChecked(configManager.getTestCheckbox("bt", true)); - ui->checkBox_4g->setChecked(configManager.getTestCheckbox("4g", true)); - ui->checkBox_bt->setChecked(configManager.getTestCheckbox("bt", true)); - ui->checkBox_4g->setChecked(configManager.getTestCheckbox("4g", true)); + ui->checkBox_mic->setChecked(configManager.getTestCheckbox("mic", true)); + ui->checkBox_sechip->setChecked(configManager.getTestCheckbox("sechip", true)); + ui->checkBox_gps->setChecked(configManager.getTestCheckbox("gps", true)); + + // 恢复G-sensor XYZ变化标准(10秒) + ui->gsensorDeltaXMinLineEdit->setText(configManager.getTestConfig("gsensorDeltaXMin", "")); + ui->gsensorDeltaXMaxLineEdit->setText(configManager.getTestConfig("gsensorDeltaXMax", "")); + ui->gsensorDeltaYMinLineEdit->setText(configManager.getTestConfig("gsensorDeltaYMin", "")); + ui->gsensorDeltaYMaxLineEdit->setText(configManager.getTestConfig("gsensorDeltaYMax", "")); + ui->gsensorDeltaZMinLineEdit->setText(configManager.getTestConfig("gsensorDeltaZMin", "")); + ui->gsensorDeltaZMaxLineEdit->setText(configManager.getTestConfig("gsensorDeltaZMax", "")); qDebug() << "测试项checkbox状态已恢复"; @@ -6488,19 +5016,27 @@ bool MainWindow::isTestItemEnabled(const QString& testName) const return ui->checkBox_vib->isChecked(); } else if (testName == "btn" || testName == "key") { return ui->checkBox_btn->isChecked(); + } else if (testName == "tp" || testName == "touch") { + return ui->checkBox_tp->isChecked(); + } else if (testName == "bright") { + return ui->checkBox_bright->isChecked(); + } else if (testName == "deepsleep") { + return ui->checkBox_deepsleep->isChecked(); } else if (testName == "mic" || testName == "audio") { return ui->checkBox_mic->isChecked(); - } else if (testName == "charge") { - return ui->checkBox_charge->isChecked(); - } else if (testName == "ntc") { - return ui->checkBox_ntc->isChecked(); + } else if (testName == "charge" || testName == "ntc" || testName == "battery") { + return ui->checkBox_battery->isChecked(); } else if (testName == "gsensor") { // gsensor checkbox可能不存在,默认启用 return true; + } else if (testName == "leaklight") { + return ui->checkBox_leaklight->isChecked(); } else if (testName == "bt" || testName == "bluetooth") { return ui->checkBox_bt->isChecked(); - } else if (testName == "4g" || testName == "ppp") { - return ui->checkBox_4g->isChecked(); + } else if (testName == "sechip") { + return ui->checkBox_sechip->isChecked(); + } else if (testName == "gps") { + return ui->checkBox_gps->isChecked(); } return true; // 默认启用 } @@ -6517,7 +5053,49 @@ void MainWindow::skipToNextEnabledTest() // 根据当前测试步骤,推进到下一个启用的测试项 if (currentTestStep == TEST_LCD) { - // LCD测试完成,检查马达测试 + // LCD测试完成:按需求依次进入 触控 -> 亮度 -> 马达 + if (isTestItemEnabled("tp")) { + currentTestStep = TEST_TOUCH; + qDebug() << "skipToNextEnabledTest: 推进到触控测试"; + performTouchTest(); + } else if (isTestItemEnabled("bright")) { + currentTestStep = TEST_BRIGHT; + qDebug() << "skipToNextEnabledTest: 推进到亮度测试"; + performBrightTest(); + } else { + // 触控/亮度都未启用,直接检查马达 + if (isTestItemEnabled("motor")) { + currentTestStep = TEST_MOTOR; + qDebug() << "skipToNextEnabledTest: 推进到马达测试"; + performMotorTest(); + } else { + qDebug() << "马达测试未启用,跳过"; + updateTestStatus("motor", STATUS_UNTESTED); + currentTestStep = TEST_MOTOR; // 推进到下一个测试步骤 + skipToNextEnabledTest(); // 递归继续下一个 + } + } + } else if (currentTestStep == TEST_TOUCH) { + // 触控测试完成:检查亮度 -> 马达 + if (isTestItemEnabled("bright")) { + currentTestStep = TEST_BRIGHT; + qDebug() << "skipToNextEnabledTest: 推进到亮度测试"; + performBrightTest(); + } else { + // 直接检查马达 + if (isTestItemEnabled("motor")) { + currentTestStep = TEST_MOTOR; + qDebug() << "skipToNextEnabledTest: 推进到马达测试"; + performMotorTest(); + } else { + qDebug() << "马达测试未启用,跳过"; + updateTestStatus("motor", STATUS_UNTESTED); + currentTestStep = TEST_MOTOR; + skipToNextEnabledTest(); + } + } + } else if (currentTestStep == TEST_BRIGHT) { + // 亮度测试完成:检查马达 if (isTestItemEnabled("motor")) { currentTestStep = TEST_MOTOR; qDebug() << "skipToNextEnabledTest: 推进到马达测试"; @@ -6525,8 +5103,8 @@ void MainWindow::skipToNextEnabledTest() } else { qDebug() << "马达测试未启用,跳过"; updateTestStatus("motor", STATUS_UNTESTED); - currentTestStep = TEST_MOTOR; // 推进到下一个测试步骤 - skipToNextEnabledTest(); // 递归继续下一个 + currentTestStep = TEST_MOTOR; + skipToNextEnabledTest(); } } else if (currentTestStep == TEST_MOTOR) { // 马达测试完成,检查按键测试 @@ -6535,7 +5113,7 @@ void MainWindow::skipToNextEnabledTest() qDebug() << " isWaitingForUserConfirm:" << isWaitingForUserConfirm; qDebug() << " motorTestPopupShown:" << motorTestPopupShown; - if (isWaitingForUserConfirm && motorTestPopupShown) { + if (isWaitingForUserConfirm && (motorTestPopupShown || touchTestPopupShown || brightTestPopupShown)) { qDebug() << "skipToNextEnabledTest: 马达测试正在等待用户确认,暂不推进到按键测试"; return; // 等待用户确认完成后再推进 } @@ -6552,26 +5130,51 @@ void MainWindow::skipToNextEnabledTest() skipToNextEnabledTest(); // 递归继续下一个 } } else if (currentTestStep == TEST_KEY) { - // 按键测试完成,检查录音测试 - if (isTestItemEnabled("audio")) { + // 按键测试完成:功耗测试 -> 录音 -> 电池 + if (isTestItemEnabled("deepsleep")) { + currentTestStep = TEST_DEEPSLEEP; + qDebug() << "skipToNextEnabledTest: 推进到功耗测试"; + performDeepsleepTest(); + } else if (isTestItemEnabled("mic")) { currentTestStep = TEST_AUDIO; + qDebug() << "skipToNextEnabledTest: 推进到录音测试"; performAudioTest(); + } else if (isTestItemEnabled("battery")) { + currentTestStep = TEST_BATTERY; + performBatteryTest(); } else { - qDebug() << "录音测试未启用,跳过"; - updateTestStatus("audio", STATUS_UNTESTED); - currentTestStep = TEST_AUDIO; // 推进到下一个测试步骤 - skipToNextEnabledTest(); // 递归继续下一个 + qDebug() << "功耗/录音/电池测试均未启用,跳过"; + updateTestStatus("deepsleep", STATUS_UNTESTED); + updateTestStatus("mic", STATUS_UNTESTED); + updateTestStatus("battery", STATUS_UNTESTED); + currentTestStep = TEST_BATTERY; + skipToNextEnabledTest(); + } + } else if (currentTestStep == TEST_DEEPSLEEP) { + if (isTestItemEnabled("mic")) { + currentTestStep = TEST_AUDIO; + qDebug() << "skipToNextEnabledTest: 推进到录音测试"; + performAudioTest(); + } else if (isTestItemEnabled("battery")) { + currentTestStep = TEST_BATTERY; + performBatteryTest(); + } else { + qDebug() << "录音和电池测试均未启用,跳过"; + updateTestStatus("mic", STATUS_UNTESTED); + updateTestStatus("battery", STATUS_UNTESTED); + currentTestStep = TEST_BATTERY; + skipToNextEnabledTest(); } } else if (currentTestStep == TEST_AUDIO) { - // 录音测试完成,检查电池测试(如果充电或ntc任一启用,都需要执行电池测试) - if (isTestItemEnabled("charge") || isTestItemEnabled("ntc")) { + // 录音测试完成,检查电池测试 + if (isTestItemEnabled("battery")) { currentTestStep = TEST_BATTERY; performBatteryTest(); } else { qDebug() << "电池测试未启用,跳过"; updateTestStatus("battery", STATUS_UNTESTED); - currentTestStep = TEST_BATTERY; // 推进到下一个测试步骤 - skipToNextEnabledTest(); // 递归继续下一个 + currentTestStep = TEST_BATTERY; + skipToNextEnabledTest(); } } else if (currentTestStep == TEST_BATTERY) { // 电池测试完成,检查G-sensor测试 @@ -6585,7 +5188,18 @@ void MainWindow::skipToNextEnabledTest() skipToNextEnabledTest(); // 递归继续下一个 } } else if (currentTestStep == TEST_GSENSOR) { - // G-sensor测试完成,检查蓝牙测试 + // G-sensor测试完成,检查漏光测试 + if (isTestItemEnabled("leaklight")) { + currentTestStep = TEST_LEAKLIGHT; + performLeaklightTest(); + } else { + qDebug() << "漏光测试未启用,跳过"; + updateTestStatus("leaklight", STATUS_UNTESTED); + currentTestStep = TEST_LEAKLIGHT; + skipToNextEnabledTest(); + } + } else if (currentTestStep == TEST_LEAKLIGHT) { + // 漏光测试完成,检查蓝牙测试 if (isTestItemEnabled("bluetooth")) { currentTestStep = TEST_BLUETOOTH; performBluetoothTest(); @@ -6596,19 +5210,33 @@ void MainWindow::skipToNextEnabledTest() skipToNextEnabledTest(); // 递归继续下一个 } } else if (currentTestStep == TEST_BLUETOOTH) { - // 蓝牙测试完成,检查4G测试 - if (isTestItemEnabled("ppp")) { - currentTestStep = TEST_PPP_INIT; - perform4GTest(); + // 蓝牙测试完成,检查SE芯片测试 + if (isTestItemEnabled("sechip")) { + currentTestStep = TEST_SECHIP; + performSeChipTest(); } else { - qDebug() << "4G测试未启用,跳过"; - updateTestStatus("ppp", STATUS_UNTESTED); - // 所有测试完成 - currentTestStep = TEST_COMPLETED; - isProductionTestRunning = false; - QString fileName = saveTestRecord(); - showFinalTestResult(); + qDebug() << "SE芯片测试未启用,跳过"; + updateTestStatus("sechip", STATUS_UNTESTED); + currentTestStep = TEST_SECHIP; + skipToNextEnabledTest(); } + } else if (currentTestStep == TEST_SECHIP) { + // SE芯片测试完成,检查GPS测试 + if (isTestItemEnabled("gps")) { + currentTestStep = TEST_GPS; + performGpsTest(); + } else { + qDebug() << "GPS测试未启用,跳过"; + updateTestStatus("gps", STATUS_UNTESTED); + currentTestStep = TEST_GPS; + skipToNextEnabledTest(); + } + } else if (currentTestStep == TEST_GPS) { + // GPS测试完成,所有测试完成 + currentTestStep = TEST_COMPLETED; + isProductionTestRunning = false; + saveTestRecord(); + showFinalTestResult(); } else { qDebug() << "未知的测试步骤,无法推进"; } @@ -6625,15 +5253,6 @@ void MainWindow::saveRecvTextEditToLog() return; } - // 检查IMEI和ICCID - QString imei = ui->IMEIlineEdit->text().trimmed(); - QString iccid = ui->ICCIDlineEdit->text().trimmed(); - - if (imei.isEmpty() || imei == "未获取" || iccid.isEmpty() || iccid == "未获取") { - qDebug() << "IMEI或ICCID未获取,跳过保存日志。IMEI:" << imei << "ICCID:" << iccid; - return; - } - // 创建日志目录 QString appPath = QCoreApplication::applicationDirPath(); QString logsDir = appPath + "/产测记录/logs"; @@ -6644,11 +5263,9 @@ void MainWindow::saveRecvTextEditToLog() // 生成日志文件名 QDateTime now = QDateTime::currentDateTime(); - QString fileName = QString("%1_%2_%3_%4_UI日志.log") + QString fileName = QString("%1_%2_UI日志.log") .arg(now.toString("yyyyMMdd")) - .arg(now.toString("hhmmss")) - .arg(imei) - .arg(iccid); + .arg(now.toString("hhmmss")); QString filePath = logsDir + "/" + fileName; @@ -6658,8 +5275,6 @@ void MainWindow::saveRecvTextEditToLog() QTextStream stream(&logFile); stream.setCodec("UTF-8"); stream << QString("=== UI日志保存时间: %1 ===\n").arg(now.toString("yyyy-MM-dd hh:mm:ss")); - stream << QString("IMEI: %1\n").arg(imei); - stream << QString("ICCID: %1\n").arg(iccid); stream << "========================================\n"; stream << logContent; logFile.close(); @@ -6674,11 +5289,9 @@ void MainWindow::saveRecvTextEditToLog() QString fullLogContent = ui->logtextEdit->toPlainText(); if (!fullLogContent.trimmed().isEmpty()) { // 生成完整日志文件名(加full后缀) - QString fullFileName = QString("%1_%2_%3_%4_UI日志full.log") + QString fullFileName = QString("%1_%2_UI日志full.log") .arg(now.toString("yyyyMMdd")) - .arg(now.toString("hhmmss")) - .arg(imei) - .arg(iccid); + .arg(now.toString("hhmmss")); QString fullFilePath = logsDir + "/" + fullFileName; @@ -6687,8 +5300,6 @@ void MainWindow::saveRecvTextEditToLog() QTextStream fullStream(&fullLogFile); fullStream.setCodec("UTF-8"); fullStream << QString("=== 完整日志保存时间: %1 ===\n").arg(now.toString("yyyy-MM-dd hh:mm:ss")); - fullStream << QString("IMEI: %1\n").arg(imei); - fullStream << QString("ICCID: %1\n").arg(iccid); fullStream << "========================================\n"; fullStream << fullLogContent; fullLogFile.close(); @@ -6712,17 +5323,6 @@ void MainWindow::createTestLogFile() // 获取当前时间 QDateTime now = QDateTime::currentDateTime(); - // 获取IMEI和ICCID(可能还没有,使用"未知"作为占位符) - QString imei = ui->IMEIlineEdit->text().trimmed(); - QString iccid = ui->ICCIDlineEdit->text().trimmed(); - - if (imei.isEmpty()) { - imei = "未知"; - } - if (iccid.isEmpty()) { - iccid = "未知"; - } - // 创建日志目录(在CSV记录目录下的logs子目录) QString appPath = QCoreApplication::applicationDirPath(); QString logsDir = appPath + "/产测记录/logs"; @@ -6731,12 +5331,10 @@ void MainWindow::createTestLogFile() dir.mkpath(logsDir); } - // 生成文件名:yyyymmdd_hhmmss_imei_iccid.log - QString fileName = QString("%1_%2_%3_%4.log") + // 生成文件名:yyyymmdd_hhmmss.log + QString fileName = QString("%1_%2.log") .arg(now.toString("yyyyMMdd")) - .arg(now.toString("hhmmss")) - .arg(imei) - .arg(iccid); + .arg(now.toString("hhmmss")); testLogFilePath = logsDir + "/" + fileName; @@ -6754,8 +5352,6 @@ void MainWindow::createTestLogFile() // 写入日志文件头 *testLogStream << QString("=== 测试开始时间: %1 ===\n").arg(now.toString("yyyy-MM-dd hh:mm:ss")); - *testLogStream << QString("IMEI: %1\n").arg(imei); - *testLogStream << QString("ICCID: %1\n").arg(iccid); *testLogStream << "========================================\n"; testLogStream->flush(); @@ -6802,221 +5398,7 @@ void MainWindow::closeTestLogFile() // 使用设备信息重命名日志文件 void MainWindow::renameLogFileWithDeviceInfo() { - // 如果日志文件未打开或路径为空,不处理 - if (!testLogFile || testLogFilePath.isEmpty()) { - return; - } - - // 获取IMEI和ICCID - QString imei = ui->IMEIlineEdit->text().trimmed(); - QString iccid = ui->ICCIDlineEdit->text().trimmed(); - - // 如果IMEI和ICCID都还是"未知"或为空,不重命名 - if (imei.isEmpty() || imei == "未知" || iccid.isEmpty() || iccid == "未知") { - return; - } - - // 从当前文件名中提取时间部分 - QFileInfo fileInfo(testLogFilePath); - QString baseName = fileInfo.baseName(); - QStringList parts = baseName.split("_"); - - if (parts.size() >= 2) { - QString datePart = parts[0]; // yyyymmdd - QString timePart = parts[1]; // hhmmss - - // 生成新文件名 - QString newFileName = QString("%1_%2_%3_%4.log") - .arg(datePart) - .arg(timePart) - .arg(imei) - .arg(iccid); - - QString newFilePath = fileInfo.absolutePath() + "/" + newFileName; - - // 如果新文件名与当前文件名不同,重命名 - if (newFilePath != testLogFilePath) { - // 先关闭文件 - if (testLogFile->isOpen()) { - testLogFile->close(); - } - - // 重命名文件 - QFile oldFile(testLogFilePath); - if (oldFile.rename(newFilePath)) { - testLogFilePath = newFilePath; - qDebug() << "日志文件已重命名:" << newFilePath; - - // 重新打开文件(追加模式) - if (testLogFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { - testLogStream = new QTextStream(testLogFile); - testLogStream->setCodec("UTF-8"); - } else { - qDebug() << "重命名后无法重新打开日志文件:" << newFilePath; - delete testLogFile; - testLogFile = nullptr; - testLogStream = nullptr; - } - } else { - qDebug() << "无法重命名日志文件:" << oldFile.errorString(); - // 重新打开原文件 - if (testLogFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { - testLogStream = new QTextStream(testLogFile); - testLogStream->setCodec("UTF-8"); - } - } - } - } -} - -// ICCID输入框文本改变时触发 -void MainWindow::onICCIDLineEditTextChanged(const QString& text) -{ - refreshICCIDCodes(); -} - -// IMEI输入框文本改变时触发 -void MainWindow::onIMEILineEditTextChanged(const QString& text) -{ - refreshIMEICodes(); -} - -// ICCID复选框状态改变时触发 -void MainWindow::onICCIDCheckBoxStateChanged(int state) -{ - Q_UNUSED(state); - refreshICCIDCodes(); -} - -// IMEI复选框状态改变时触发 -void MainWindow::onIMEICheckBoxStateChanged(int state) -{ - Q_UNUSED(state); - refreshIMEICodes(); -} - -// 刷新ICCID的二维码和条形码 -void MainWindow::refreshICCIDCodes() -{ - QString text = ui->ICCIDlineEdit->text().trimmed(); - if (ui->checkBox_iccid->isChecked() && !text.isEmpty()) { - qDebug() << "刷新ICCID二维码和条形码:" << text; - generateQRCode(text, ui->graphicsView_iccid_qr); - generateBarcode(text, ui->graphicsView_iccid_barcode); - } else { - // 如果未选中或文本为空,清空显示 - if (ui->graphicsView_iccid_qr->scene()) { - ui->graphicsView_iccid_qr->scene()->clear(); - } - if (ui->graphicsView_iccid_barcode->scene()) { - ui->graphicsView_iccid_barcode->scene()->clear(); - } - } -} - -// 刷新IMEI的二维码和条形码 -void MainWindow::refreshIMEICodes() -{ - QString text = ui->IMEIlineEdit->text().trimmed(); - if (ui->checkBox_imei->isChecked() && !text.isEmpty()) { - qDebug() << "刷新IMEI二维码和条形码:" << text; - generateQRCode(text, ui->graphicsView_imei_qr); - generateBarcode(text, ui->graphicsView_imei_barcode); - } else { - // 如果未选中或文本为空,清空显示 - if (ui->graphicsView_imei_qr->scene()) { - ui->graphicsView_imei_qr->scene()->clear(); - } - if (ui->graphicsView_imei_barcode->scene()) { - ui->graphicsView_imei_barcode->scene()->clear(); - } - } -} - -// 生成二维码 -void MainWindow::generateQRCode(const QString& text, QGraphicsView* view) -{ - if (!view || text.isEmpty()) { - return; - } - - // 获取视图的实际可用尺寸(减去边距) - QSize viewSize = view->size(); - int margin = 10; - int availableSize = qMin(viewSize.width(), viewSize.height()) - 2 * margin; - if (availableSize < 100) { - availableSize = 200; // 最小尺寸 - } - - // 创建二维码图片,使用视图的实际尺寸 - QPixmap qrPixmap = QRCodeBarcodeGenerator::createQRCodePixmap(text, availableSize); - - // 清空场景并添加二维码图片 - if (!view->scene()) { - view->setScene(new QGraphicsScene(this)); - } - view->scene()->clear(); - QGraphicsPixmapItem* item = view->scene()->addPixmap(qrPixmap); - - // 设置场景大小 - view->scene()->setSceneRect(0, 0, qrPixmap.width(), qrPixmap.height()); - - // 铺满窗口,保持宽高比 - view->fitInView(item, Qt::KeepAspectRatio); - - // 确保视图更新 - view->update(); -} - -// 窗口大小改变时刷新二维码和条形码 -void MainWindow::resizeEvent(QResizeEvent* event) -{ - QMainWindow::resizeEvent(event); - - // 延迟刷新,确保窗口大小已经更新 - QTimer::singleShot(100, this, [this]() { - refreshICCIDCodes(); - refreshIMEICodes(); - }); -} - -// 生成条形码 -void MainWindow::generateBarcode(const QString& text, QGraphicsView* view) -{ - if (!view || text.isEmpty()) { - return; - } - - // 获取视图的实际可用尺寸(减去边距) - QSize viewSize = view->size(); - int margin = 10; - int availableWidth = viewSize.width() - 2 * margin; - int availableHeight = viewSize.height() - 2 * margin; - - if (availableWidth < 200) { - availableWidth = 300; // 最小宽度 - } - if (availableHeight < 50) { - availableHeight = 100; // 最小高度 - } - - // 创建条形码图片,使用视图的实际尺寸 - QPixmap barcodePixmap = QRCodeBarcodeGenerator::createBarcodePixmap(text, availableWidth, availableHeight); - - // 清空场景并添加条形码图片 - if (!view->scene()) { - view->setScene(new QGraphicsScene(this)); - } - view->scene()->clear(); - QGraphicsPixmapItem* item = view->scene()->addPixmap(barcodePixmap); - - // 设置场景大小 - view->scene()->setSceneRect(0, 0, barcodePixmap.width(), barcodePixmap.height()); - - // 铺满窗口,保持宽高比 - view->fitInView(item, Qt::KeepAspectRatio); - - // 确保视图更新 - view->update(); + // 已移除 IMEI/ICCID 相关功能,不再按设备信息重命名日志 + return; } diff --git a/mainwindow.h b/mainwindow.h index e0861d3..cc16ffa 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -17,13 +17,12 @@ #include #include #include +#include #include #include #include "config_manager.h" -#include "qrcode_barcode_generator.h" QT_BEGIN_NAMESPACE -class QGraphicsView; namespace Ui { class MainWindow; } QT_END_NAMESPACE @@ -43,8 +42,6 @@ public: ~MainWindow(); protected: - void resizeEvent(QResizeEvent* event) override; // 窗口大小改变时刷新二维码和条形码 - private slots: void on_refreshButton_clicked(); // 刷新串口列表 void on_openButton_clicked(); // 打开/关闭串口 @@ -62,16 +59,10 @@ private slots: void performLcdColorTest(); // LCD颜色测试 void performMotorTest(); // 马达测试 void performMotorTestContinue(); // 继续马达测试(LCD关闭后调用) - void perform4GTest(); // 4G测试 - void perform4GSignalTest(); // 4G信号质量测试 void onLcdTestTimer(); // LCD测试定时器 void onMotorTestTimer(); // 马达测试定时器 - void on4GTestTimeout(); // 4G测试超时 - void on4GSignalTimer(); // 4G信号检测定时器 void onTestTimeout(); // 通用测试超时处理 void checkFactoryModeResult(const QString& data); // 检查工厂模式进入结果 - void check4GInitResult(const QString& data); // 检查4G初始化结果 - void check4GSignalResult(const QString& data); // 检查4G信号质量结果 QString getRsrqGrade(double rsrq); // 获取RSRQ信号质量评级 QString getRsrpGrade(double rsrp); // 获取RSRP信号覆盖强度评级 QString getOverallSignalAssessment(double rsrp, double rsrq); // 获取整体信号评估 @@ -82,24 +73,34 @@ private slots: void checkBuildVerResult(const QString& data); // 检查BuildVer启动完成 void showLcdTestConfirmDialog(); // 显示LCD测试确认弹窗 void showMotorTestConfirmDialog(); // 显示马达测试确认弹窗 + void performTouchTest(); // 触控测试(AT^TESTJUMP=TP) + void performBrightTest(); // 亮度测试(AT^TESTJUMP=BRIGHT) + void showTouchTestConfirmDialog(); // 触控测试确认弹窗 + void showBrightTestConfirmDialog(); // 亮度测试确认弹窗 void performKeyTest(); // 执行按键测试 void checkKeyTestResult(const QString& data); // 检查按键测试结果 void showKeyTestConfirmDialog(bool allKeysTested); // 显示按键测试确认弹窗 - void performAudioTest(); // 执行录音测试 - void performAudioTestContinue(); // 继续录音测试(LCD关闭后调用) - void showAudioTestDialog(); // 显示录音测试弹窗 - void onAudioRecordTimer(); // 录音测试定时器(3秒录音) + void performDeepsleepTest(); // 功耗:ENTERTESTVIEW -> TESTJUMP=DEEPSLEEP -> FACTORYDEEPSLEEPENTER + void showDeepsleepTestConfirmDialog(); // 功耗测试确认弹窗 + void performAudioTest(); // 执行录音测试(有标准时共3轮,任一轮达标即通过) + void beginAudioTestAttempt(int roundIndex); // 发送单轮 AT^AUDIOTEST void checkAudioTestResult(const QString& data); // 检查录音测试结果 - QString parseAudioLevel(const QString& data); // 解析音频等级(从文字或rms_db数值) - int getAudioLevelValue(const QString& level); // 获取音频等级数值(用于比较) - bool isAudioLevelPass(const QString& detectedLevel, const QString& requiredLevel); // 判断检测到的等级是否满足要求 void performBatteryTest(); // 执行电池测试 void checkBatteryTestResult(const QString& data); // 检查电池测试结果 void performGsensorTest(); // 执行G-sensor测试 void checkGsensorTestResult(const QString& data); // 检查G-sensor测试结果 + void onGsensorIntervalTimeout(); // 10秒后关闭GSENSORTEST并计算变化 + void performLeaklightTest(); // 心率漏光测试 + void checkLeaklightTestResult(const QString& data); // 检查漏光测试结果 void performBluetoothTest(); // 执行蓝牙测试 void checkBluetoothTestResult(const QString& data); // 检查蓝牙测试结果 void onBluetoothTestTimer(); // 蓝牙测试超时处理 + void performSeChipTest(); // 执行SE芯片测试(AT^GETCYWEEACTIVE) + void checkSeChipTestResult(const QString& data); // 检查SE芯片测试结果 + void performGpsTest(); // 执行GPS测试 + void checkGpsTestResult(const QString& data); // 检查GPS测试结果 + void onGpsTestTimer(); // GPS测试30秒超时处理 + void onGpsPollTimer(); // GPS信息轮询定时器 void updateTestStatus(const QString& testName, TestStatus status); void updateOverallStatus(); QString saveTestRecord(); // 保存测试记录到Excel(静默保存) @@ -108,23 +109,11 @@ private slots: void appendToLogFile(const QString& text); // 追加内容到日志文件(同时显示到recvTextEdit) void closeTestLogFile(); // 关闭测试日志文件 void renameLogFileWithDeviceInfo(); // 使用设备信息重命名日志文件 - void checkSignalAndSimValues(const QString& data); // 检查signal和sim值进行4G测试判断 void on_openButton_4_clicked(); - void sendPppCommand(); // 发送PPP指令 void parseWstpPrintData(const QString& data); // 解析wstp_print格式的BTMAC和CyweeChipInfo数据 - void onCat1SignalTimer(); - void checkCat1SignalResult(const QString& data); - void show4GRetryDialog(const QString& failReason); - void performPingTest(); // Ping测试 - void checkPingTestResult(const QString& data); // 检查Ping测试结果 - void onPingTestTimeout(); // Ping测试超时 - bool isEGModem() const; - bool isValidIMEI(const QString& imei); - bool isValidICCID(const QString& iccid); - void on_agingTestButton_clicked(); void on_agingTestButton_2_clicked(); @@ -146,60 +135,49 @@ private: // 产测相关变量 QTimer *lcdTestTimer; // LCD测试定时器 QTimer *motorTestTimer; // 马达测试定时器 - QTimer *test4GTimer; // 4G测试超时定时器 - QTimer *signal4GTimer; // 4G信号检测定时器 QTimer *testTimeoutTimer; // 通用测试超时定时器 + QTimer *gSensorIntervalTimer; // G-sensor打印10秒后停止 - QTimer *cat1SignalTimer; // CAT1信号测试定时器 QTimer *bluetoothTestTimer; // 蓝牙测试定时器 - QTimer *pingTestTimer; // Ping测试超时定时器 + QTimer *gpsTestTimer; // GPS测试30秒总超时定时器 + QTimer *gpsPollTimer; // GPS信息轮询定时器(每2秒发AT^GPSINFO) int currentLcdColor; // 当前LCD测试颜色 (1=红, 2=绿, 3=蓝) - int initRetry4GCount; // 4G初始化重试次数 - bool hasReceivedFirstSignal; - int cat1SignalTestCount; - int cat1SignalReceivedCount; - int cat1SignalGoodCount; - int cat1Signal99Count; - int cat1SignalTotalValue; - int signalTest4GCount; // 4G信号检测发送次数 - int signalReceived4GCount; // 4G信号接收到有效数据次数 - int signalGood4GCount; // 4G信号良好以上计数 - int pppCommandCount; // PPP指令发送次数计数器 - bool hasReceivedSignalSim; // 是否已收到signal和sim数据 - bool hasReceivedPppInfo; // 是否已收到PPPINFO相关打印 - QString currentModemVersion; // 当前MODEM版本 - QString currentPYVerVersion; // 当前python版本 - int currentSimStatus; // 当前SIM卡状态:1=插入,0=未插入,-1=未知 bool isProductionTestRunning; // 产测是否正在运行 QDateTime testStartTime; // 测试开始时间 bool lcdTestPopupShown; // LCD测试弹窗是否已显示 bool motorTestPopupShown; // 马达测试弹窗是否已显示 + bool touchTestPopupShown; // 触控测试弹窗是否已显示 + bool brightTestPopupShown; // 亮度测试弹窗是否已显示 + bool deepsleepTestPopupShown; // 功耗测试弹窗是否已显示 bool isWaitingForUserConfirm; // 是否正在等待用户确认 bool lcdBacklightOn; // LCD背光状态:true=开启,false=关闭 bool keyTestStarted; // 按键测试是否已开始 int keyTestStatus[4]; // 按键测试状态 0=未测试 1=已测试 QDialog *keyTestDialog; // 按键测试弹窗 QPushButton *keyButtons[4]; // 按键测试按钮 - bool audioTestPopupShown; // 录音测试弹窗是否已显示 - bool audioPlaybackPopupShown; // 录音播放确认弹窗是否已显示 - QTimer *audioRecordTimer; // 录音测试定时器 - QDialog *audioTestDialog; // 录音测试对话框 - QLabel *audioDialogLabel; // 录音对话框标签 - QLabel *audioDialogInfo; // 录音对话框信息 - QPushButton *audioStartButton; // 开始录音按钮 - QPushButton *audioPassButton; // 录音通过按钮 - QPushButton *audioFailButton; // 录音失败按钮 - QPushButton *audioRetestButton; // 重新测试按钮 - int audioMicMiddleConsecutiveCount; // 连续检测到设置的标准Level的次数 - int audioMicTotalPassCount; // 累计检测到满足要求的Level的次数(包括非连续的) - QString audioLevelStandard; // 音频测试通过标准("中"或"低"等) - bool audioTestFinished; // 录音测试是否已完成(成功或失败) QSet bluetoothAddresses; // 蓝牙测试检测到的不同蓝牙地址集合 + QMap bluetoothAddressRssiMap; // 地址->最佳RSSI(用于配置了广播设备时校验信号强度) bool bluetoothTestFinished; // 蓝牙测试是否已完成(成功或失败) QString detectedBluetoothAddress; // 检测到的指定蓝牙地址 int detectedBluetoothRSSI; // 检测到的指定蓝牙地址的RSSI值 - bool pingTestFinished; // Ping测试是否已完成(成功或失败) - int pingSuccessCount; // Ping成功次数 + int bluetoothMaxRssi; // 未配置广播设备时:检测到的最大RSSI(用于>=3设备且至少一个>=−45dBm判断) + bool gpsTestFinished; // GPS测试是否已完成 + bool gpsInfoSeenIn10Sec; // 10秒内是否已收到GPS信息 + int gpsMaxNumSats; // GPS测试期间 num_sats 最大值(仅日志) + int gpsMaxTrace; // GPS测试期间 trace 卫星数最大值(判定用,≥3通过) + int gpsMaxCn; // GPS测试期间最大CN值(仅日志) + QMap testStartTimeMap; // 各项测试开始时间 + QMap testDurationSecMap; // 各项测试耗时(秒) + bool keyRotateDetected; // 按键测试中是否检测到旋转事件 + int audioTestRoundIndex; // 录音测试当前轮次 0..2(有等级标准时共3轮) + bool audioTestAnyPass; // 三轮中是否至少一轮满足等级标准 + bool audioTestAwaitingMicLevel; // 本轮是否仍在等待首条 mic level(防重复解析) + int leaklightGreen; + int leaklightRed; + int leaklightIr; + bool leaklightGreenSeen; + bool leaklightRedSeen; + bool leaklightIrSeen; // 日志文件相关变量 QFile *testLogFile; // 测试日志文件对象 QTextStream *testLogStream; // 测试日志文件流 @@ -218,30 +196,23 @@ private: int currentVersionCommandIndex; // 当前执行的版本信息指令索引 QTimer *versionCommandTimer; // 版本信息指令发送定时器 - enum AudioTestStep { - AUDIO_STARTING_RECORD, // 开始录音 - AUDIO_PLAYING_1KHZ, // 正在播放1kHz音频 - AUDIO_STOPPING_1KHZ, // 停止播放1kHz音频 - AUDIO_STOPPING_RECORD, // 停止录音 - AUDIO_PLAYING_RECORDED, // 播放录制的音频 - AUDIO_STOPPING_PLAYBACK // 停止播放录制的音频 - } currentAudioStep; - enum ProductionTestStep { TEST_IDLE, TEST_RESET, TEST_WAIT_BUILDVER, TEST_LCD, + TEST_TOUCH, + TEST_BRIGHT, TEST_MOTOR, TEST_KEY, + TEST_DEEPSLEEP, TEST_AUDIO, TEST_BATTERY, TEST_GSENSOR, + TEST_LEAKLIGHT, TEST_BLUETOOTH, - TEST_PPP_INIT, - TEST_PPP_UPDATE, - TEST_PPP_SIGNAL, - TEST_PPP_PING, + TEST_SECHIP, + TEST_GPS, TEST_COMPLETED } currentTestStep; // 十六进制与字节数组转换工具函数 @@ -250,12 +221,6 @@ private: // 数据提取函数 void ShowSoftVer(const QString& data); void CheckVibratorFunc(const QString& data); - // PPP_INFO格式数据提取函数 - void ShowPppInfoIMEI(const QString& data); - void ShowPppInfoICCID(const QString& data); - void ShowPppInfoModemVer(const QString& data); - void ShowPppInfoPYVer(const QString& data); - // CAT1信号测试相关函数 void parseNetworkStatChanged(const QString& data); // 开机检测和版本信息获取相关函数 @@ -273,15 +238,23 @@ private: // 测试项checkbox检查辅助函数 bool isTestItemEnabled(const QString& testName) const; void skipToNextEnabledTest(); // 跳过当前测试项,推进到下一个启用的测试项 + + // G-sensor XYZ变化计算:解析UI阈值并更新测试结果 + void finalizeGsensorTest(); + + // G-sensor XYZ窗口采集状态 + bool gSensorCollecting; // 是否正在收集XYZ数据 + bool gSensorHasStart; // 是否已记录起始XYZ + bool gSensorHasLast; // 是否已记录末尾XYZ + bool gSensorErrorDetected; // 是否检测到错误/异常响应 + bool gSensorAnySample; // 10秒窗口内是否采到至少一组XYZ + bool gSensorAllSamplesInRange; // 10秒窗口内是否每组XYZ都在阈值范围内 + double gSensorStartX; + double gSensorStartY; + double gSensorStartZ; + double gSensorLastX; + double gSensorLastY; + double gSensorLastZ; - // 二维码和条形码生成相关函数 - void onICCIDLineEditTextChanged(const QString& text); // ICCID输入框文本改变时触发 - void onIMEILineEditTextChanged(const QString& text); // IMEI输入框文本改变时触发 - void onICCIDCheckBoxStateChanged(int state); // ICCID复选框状态改变时触发 - void onIMEICheckBoxStateChanged(int state); // IMEI复选框状态改变时触发 - void generateQRCode(const QString& text, QGraphicsView* view); // 生成二维码 - void generateBarcode(const QString& text, QGraphicsView* view); // 生成条形码 - void refreshICCIDCodes(); // 刷新ICCID的二维码和条形码 - void refreshIMEICodes(); // 刷新IMEI的二维码和条形码 }; #endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui index 1a63dab..487cae4 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -280,125 +280,56 @@ background-color: #F57C00; - - - 6 - - - - - - 0 - 0 - - - - - Agra - 14 - - - - 测试音频播放音量 - - - - - - - - Arial - 14 - - - - - - - 0.0~2.0, 取值标准,麦克风能收音的最低喇叭音量。 - - - 2.0 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 例如: 2 - - - - - - - - - - - 录音音量标准: - - - - - - - 可选:高,中,低,极低,静音。连续出现三个指定的音量标准,视为测试通过。 - - - 中 - - - 可选:高,中,低,极低,静音 - - - - - - - - - - - Qt::Horizontal - - - - - - - + - 目标MODEM版本: + 录音音量: - + - 为空不校验Modem版本是否正确 + AT^AUDIOTEST音量参数,例如2.0 Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + 例如: 2.0 + - + - 目标PY版本: + 目标录音等级: - + - 为空不校验Py版本是否正确 + 填写静音/极低/低/中/高之一:作为最低达标档位(设备上报 mic level 响度不低于该档则本轮通过)。留空不校验等级。 Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + 例如: 中 + + + + + + + Qt::Horizontal + + + + + @@ -465,6 +396,235 @@ background-color: #F57C00; + + + + G-sensor XYZ变化标准(10秒,留空表示不校验): + + + + + + + ΔX: + + + + + + + 6 + + + + + + 1 + 0 + + + + + 0 + 28 + + + + + 16777215 + 28 + + + + 最小 例如: -1.0 + + + + + + + ~ + + + Qt::AlignCenter + + + + + + + + 1 + 0 + + + + + 0 + 28 + + + + + 16777215 + 28 + + + + 最大 例如: 1.0 + + + + + + + + + ΔY: + + + + + + + 6 + + + + + + 1 + 0 + + + + + 0 + 28 + + + + + 16777215 + 28 + + + + 最小 例如: -1.0 + + + + + + + ~ + + + Qt::AlignCenter + + + + + + + + 1 + 0 + + + + + 0 + 28 + + + + + 16777215 + 28 + + + + 最大 例如: 1.0 + + + + + + + + + ΔZ: + + + + + + + 6 + + + + + + 1 + 0 + + + + + 0 + 28 + + + + + 16777215 + 28 + + + + 最小 例如: -1.0 + + + + + + + ~ + + + Qt::AlignCenter + + + + + + + + 1 + 0 + + + + + 0 + 28 + + + + + 16777215 + 28 + + + + 最大 例如: 1.0 + + + + + @@ -569,65 +729,6 @@ background-color: #F57C00; - - - - IMEI: - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - ICCID - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Modem: - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - PYVER - - - - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - @@ -642,7 +743,7 @@ background-color: #F57C00; - + @@ -664,7 +765,7 @@ background-color: #F57C00; - + @@ -732,19 +833,69 @@ background-color: #F57C00; 15 - + + + 4 + + + + + LCD测试: + + + + + + + color: #888888; font-size: 18px; font-weight: bold; + + + ○ + + + Qt::AlignCenter + + + + + + + color: #888888; + + + 未测试 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 4 - + - LCD测试: + 触控测试: - + color: #888888; font-size: 18px; font-weight: bold; @@ -757,7 +908,7 @@ background-color: #F57C00; - + color: #888888; @@ -767,7 +918,57 @@ background-color: #F57C00; - + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 4 + + + + + 亮度测试: + + + + + + + color: #888888; font-size: 18px; font-weight: bold; + + + ○ + + + Qt::AlignCenter + + + + + + + color: #888888; + + + 未测试 + + + + + Qt::Horizontal @@ -881,6 +1082,56 @@ background-color: #F57C00; + + + + 4 + + + + + 功耗测试: + + + + + + + color: #888888; font-size: 18px; font-weight: bold; + + + ○ + + + Qt::AlignCenter + + + + + + + color: #888888; + + + 未测试 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -1031,6 +1282,56 @@ background-color: #F57C00; + + + + 4 + + + + + 漏光测试: + + + + + + + color: #888888; font-size: 18px; font-weight: bold; + + + ○ + + + Qt::AlignCenter + + + + + + + color: #888888; + + + 未测试 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -1082,19 +1383,69 @@ background-color: #F57C00; - + + + 4 + + + + + SE芯片测试: + + + + + + + color: #888888; font-size: 18px; font-weight: bold; + + + ○ + + + Qt::AlignCenter + + + + + + + color: #888888; + + + 未测试 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 4 - + - 4G测试: + GPS测试: - + color: #888888; font-size: 18px; font-weight: bold; @@ -1107,7 +1458,7 @@ background-color: #F57C00; - + color: #888888; @@ -1117,7 +1468,7 @@ background-color: #F57C00; - + Qt::Horizontal @@ -1197,59 +1548,87 @@ background-color: #F57C00; - + LCD - + + + + 触控测试 + + + + + + + 马达 + + + + 按键 - - + + - 蓝牙测试 + 亮度测试 - - + + - 电池温度 + 录音测试 - + - 录音 + 电池测试 - - + + - 马达 + 蓝牙测试 - + - 4G测试 + 漏光测试 - + + + SE芯片 + + + + + + + 功耗测试 + + + + + - 充电 + GPS测试 @@ -1263,42 +1642,17 @@ background-color: #F57C00; - - - - - - - ICCID 二维码 - - - - - - - - - - - - - - - - - IMEI 二维码 - - - - - - - - - - - - + + + Qt::Vertical + + + + 20 + 10 + + + diff --git a/qrcode_barcode_generator.cpp b/qrcode_barcode_generator.cpp deleted file mode 100644 index 9af6a4f..0000000 --- a/qrcode_barcode_generator.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include "qrcode_barcode_generator.h" -#include "qrcodegen.hpp" -#include -#include -#include -#include -#include -#include -#include - -using qrcodegen::QrCode; - -// 使用 qrcodegen 库创建真正可识别的二维码图片 -QPixmap QRCodeBarcodeGenerator::createQRCodePixmap(const QString& text, int size) -{ - // 防御性检查 - if (text.isEmpty() || size <= 0) { - return QPixmap(); - } - - QPixmap pixmap(size, size); - pixmap.fill(Qt::white); - - // 使用 qrcodegen 生成 QR 矩阵 - QByteArray utf8 = text.toUtf8(); - QrCode qr = QrCode::encodeText(utf8.constData(), QrCode::Ecc::LOW); - int modules = qr.getSize(); // 模块数(21~177) - - // 计算模块尺寸和静区(白边),保证每个模块足够大且居中 - int marginModules = 4; // 静区 4 个模块 - int totalModules = modules + marginModules * 2; - int moduleSize = size / totalModules; - if (moduleSize < 2) { - moduleSize = 2; // 每个模块至少 2 像素 - } - int qrSize = moduleSize * totalModules; - int offset = (size - qrSize) / 2; // 居中偏移 - - QPainter painter(&pixmap); - painter.setRenderHint(QPainter::Antialiasing, false); // 关闭抗锯齿,保持锐利 - painter.setRenderHint(QPainter::SmoothPixmapTransform, false); - painter.setPen(Qt::NoPen); - painter.setBrush(Qt::black); - - // 绘制所有为 1 的模块 - for (int y = 0; y < modules; ++y) { - for (int x = 0; x < modules; ++x) { - if (qr.getModule(x, y)) { - int px = offset + (x + marginModules) * moduleSize; - int py = offset + (y + marginModules) * moduleSize; - painter.drawRect(px, py, moduleSize, moduleSize); - } - } - } - - return pixmap; -} - -// 创建条形码图片(Code128格式简化版) -QPixmap QRCodeBarcodeGenerator::createBarcodePixmap(const QString& text, int width, int height) -{ - QPixmap pixmap(width, height); - pixmap.fill(Qt::white); - - QPainter painter(&pixmap); - painter.setRenderHint(QPainter::Antialiasing, false); // 关闭抗锯齿,使条形码更清晰 - painter.setRenderHint(QPainter::SmoothPixmapTransform, false); - - // 计算条形码区域(留出文本空间) - int margin = 10; - int textHeight = 20; - int barAreaHeight = height - 2 * margin - textHeight; - int barY = margin; - - // 根据宽度和文本长度动态计算条宽,确保条形码清晰可扫 - QByteArray data = text.toUtf8(); - int totalBars = data.length() * 8 + 6; // 数据条 + 起始码 + 结束码 - int availableWidth = width - 2 * margin; - - // 计算条宽,确保至少2像素,最多不超过5像素 - int barWidth = qMax(2, qMin(5, availableWidth / totalBars)); - if (barWidth < 2) { - barWidth = 2; // 最小条宽 - } - - int x = margin; - - painter.setPen(QPen(Qt::black, barWidth)); - painter.setBrush(QBrush(Qt::black)); - - // 起始码(两个宽条) - drawBar(&painter, x, barY, barWidth * 2, barAreaHeight); - x += barWidth * 3; - drawBar(&painter, x, barY, barWidth * 2, barAreaHeight); - x += barWidth * 3; - - // 根据文本内容生成条形码模式 - for (int i = 0; i < data.length() && x < width - margin - barWidth * 6; i++) { - unsigned char ch = data[i]; - // 每个字符用8个条表示(1个条 = 1位) - for (int bit = 0; bit < 8; bit++) { - if (x >= width - margin - barWidth * 6) break; - - bool isBlack = (ch >> (7 - bit)) & 1; - if (isBlack) { - drawBar(&painter, x, barY, barWidth, barAreaHeight); - } - x += barWidth; - } - } - - // 结束码(两个宽条) - if (x < width - margin - barWidth * 6) { - drawBar(&painter, x, barY, barWidth * 2, barAreaHeight); - x += barWidth * 3; - drawBar(&painter, x, barY, barWidth * 2, barAreaHeight); - } - - // 在底部添加文本 - painter.setPen(QPen(Qt::black, 1)); - QFont font = painter.font(); - font.setPointSize(qMax(8, height / 15)); // 根据高度调整字体大小 - painter.setFont(font); - QRect textRect(margin, height - textHeight, width - 2 * margin, textHeight); - painter.drawText(textRect, Qt::AlignCenter, text); - - return pixmap; -} - -// 绘制定位图案(QR码的三个角) -void QRCodeBarcodeGenerator::drawFinderPattern(QPainter* painter, int x, int y, int size, int moduleSize) -{ - // 外框(7x7模块) - painter->fillRect(x, y, size, size, Qt::black); - // 内框(5x5模块) - painter->fillRect(x + moduleSize, y + moduleSize, size - 2 * moduleSize, - size - 2 * moduleSize, Qt::white); - // 中心点(3x3模块) - painter->fillRect(x + 2 * moduleSize, y + 2 * moduleSize, size - 4 * moduleSize, - size - 4 * moduleSize, Qt::black); -} - -// 绘制条形码的一条 -void QRCodeBarcodeGenerator::drawBar(QPainter* painter, int x, int y, int width, int height) -{ - painter->fillRect(x, y, width, height, Qt::black); -} - diff --git a/qrcode_barcode_generator.h b/qrcode_barcode_generator.h deleted file mode 100644 index 10276a0..0000000 --- a/qrcode_barcode_generator.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef QRCODE_BARCODE_GENERATOR_H -#define QRCODE_BARCODE_GENERATOR_H - -#include -#include - -class QRCodeBarcodeGenerator -{ -public: - // 创建二维码图片(简化版本,使用简单的矩阵模式) - static QPixmap createQRCodePixmap(const QString& text, int size); - - // 创建条形码图片(Code128格式简化版) - static QPixmap createBarcodePixmap(const QString& text, int width, int height); - -private: - // 绘制定位图案(QR码的三个角) - static void drawFinderPattern(class QPainter* painter, int x, int y, int size, int moduleSize); - - // 绘制条形码的一条 - static void drawBar(class QPainter* painter, int x, int y, int width, int height); -}; - -#endif // QRCODE_BARCODE_GENERATOR_H - diff --git a/qrcodegen.cpp b/qrcodegen.cpp deleted file mode 100644 index 693e7ff..0000000 --- a/qrcodegen.cpp +++ /dev/null @@ -1,829 +0,0 @@ -/* - * QR Code generator library (C++) - * - * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/page/qr-code-generator-library - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - The Software is provided "as is", without warranty of any kind, express or - * implied, including but not limited to the warranties of merchantability, - * fitness for a particular purpose and noninfringement. In no event shall the - * authors or copyright holders be liable for any claim, damages or other - * liability, whether in an action of contract, tort or otherwise, arising from, - * out of or in connection with the Software or the use or other dealings in the - * Software. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "qrcodegen.hpp" - -using std::int8_t; -using std::uint8_t; -using std::size_t; -using std::vector; - - -namespace qrcodegen { - -/*---- Class QrSegment ----*/ - -QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) : - modeBits(mode) { - numBitsCharCount[0] = cc0; - numBitsCharCount[1] = cc1; - numBitsCharCount[2] = cc2; -} - - -int QrSegment::Mode::getModeBits() const { - return modeBits; -} - - -int QrSegment::Mode::numCharCountBits(int ver) const { - return numBitsCharCount[(ver + 7) / 17]; -} - - -const QrSegment::Mode QrSegment::Mode::NUMERIC (0x1, 10, 12, 14); -const QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13); -const QrSegment::Mode QrSegment::Mode::BYTE (0x4, 8, 16, 16); -const QrSegment::Mode QrSegment::Mode::KANJI (0x8, 8, 10, 12); -const QrSegment::Mode QrSegment::Mode::ECI (0x7, 0, 0, 0); - - -QrSegment QrSegment::makeBytes(const vector &data) { - if (data.size() > static_cast(INT_MAX)) - throw std::length_error("Data too long"); - BitBuffer bb; - for (uint8_t b : data) - bb.appendBits(b, 8); - return QrSegment(Mode::BYTE, static_cast(data.size()), std::move(bb)); -} - - -QrSegment QrSegment::makeNumeric(const char *digits) { - BitBuffer bb; - int accumData = 0; - int accumCount = 0; - int charCount = 0; - for (; *digits != '\0'; digits++, charCount++) { - char c = *digits; - if (c < '0' || c > '9') - throw std::domain_error("String contains non-numeric characters"); - accumData = accumData * 10 + (c - '0'); - accumCount++; - if (accumCount == 3) { - bb.appendBits(static_cast(accumData), 10); - accumData = 0; - accumCount = 0; - } - } - if (accumCount > 0) // 1 or 2 digits remaining - bb.appendBits(static_cast(accumData), accumCount * 3 + 1); - return QrSegment(Mode::NUMERIC, charCount, std::move(bb)); -} - - -QrSegment QrSegment::makeAlphanumeric(const char *text) { - BitBuffer bb; - int accumData = 0; - int accumCount = 0; - int charCount = 0; - for (; *text != '\0'; text++, charCount++) { - const char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text); - if (temp == nullptr) - throw std::domain_error("String contains unencodable characters in alphanumeric mode"); - accumData = accumData * 45 + static_cast(temp - ALPHANUMERIC_CHARSET); - accumCount++; - if (accumCount == 2) { - bb.appendBits(static_cast(accumData), 11); - accumData = 0; - accumCount = 0; - } - } - if (accumCount > 0) // 1 character remaining - bb.appendBits(static_cast(accumData), 6); - return QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb)); -} - - -vector QrSegment::makeSegments(const char *text) { - // Select the most efficient segment encoding automatically - vector result; - if (*text == '\0'); // Leave result empty - else if (isNumeric(text)) - result.push_back(makeNumeric(text)); - else if (isAlphanumeric(text)) - result.push_back(makeAlphanumeric(text)); - else { - vector bytes; - for (; *text != '\0'; text++) - bytes.push_back(static_cast(*text)); - result.push_back(makeBytes(bytes)); - } - return result; -} - - -QrSegment QrSegment::makeEci(long assignVal) { - BitBuffer bb; - if (assignVal < 0) - throw std::domain_error("ECI assignment value out of range"); - else if (assignVal < (1 << 7)) - bb.appendBits(static_cast(assignVal), 8); - else if (assignVal < (1 << 14)) { - bb.appendBits(2, 2); - bb.appendBits(static_cast(assignVal), 14); - } else if (assignVal < 1000000L) { - bb.appendBits(6, 3); - bb.appendBits(static_cast(assignVal), 21); - } else - throw std::domain_error("ECI assignment value out of range"); - return QrSegment(Mode::ECI, 0, std::move(bb)); -} - - -QrSegment::QrSegment(const Mode &md, int numCh, const std::vector &dt) : - mode(&md), - numChars(numCh), - data(dt) { - if (numCh < 0) - throw std::domain_error("Invalid value"); -} - - -QrSegment::QrSegment(const Mode &md, int numCh, std::vector &&dt) : - mode(&md), - numChars(numCh), - data(std::move(dt)) { - if (numCh < 0) - throw std::domain_error("Invalid value"); -} - - -int QrSegment::getTotalBits(const vector &segs, int version) { - int result = 0; - for (const QrSegment &seg : segs) { - int ccbits = seg.mode->numCharCountBits(version); - if (seg.numChars >= (1L << ccbits)) - return -1; // The segment's length doesn't fit the field's bit width - if (4 + ccbits > INT_MAX - result) - return -1; // The sum will overflow an int type - result += 4 + ccbits; - if (seg.data.size() > static_cast(INT_MAX - result)) - return -1; // The sum will overflow an int type - result += static_cast(seg.data.size()); - } - return result; -} - - -bool QrSegment::isNumeric(const char *text) { - for (; *text != '\0'; text++) { - char c = *text; - if (c < '0' || c > '9') - return false; - } - return true; -} - - -bool QrSegment::isAlphanumeric(const char *text) { - for (; *text != '\0'; text++) { - if (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr) - return false; - } - return true; -} - - -const QrSegment::Mode &QrSegment::getMode() const { - return *mode; -} - - -int QrSegment::getNumChars() const { - return numChars; -} - - -const std::vector &QrSegment::getData() const { - return data; -} - - -const char *QrSegment::ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; - - - -/*---- Class QrCode ----*/ - -int QrCode::getFormatBits(Ecc ecl) { - switch (ecl) { - case Ecc::LOW : return 1; - case Ecc::MEDIUM : return 0; - case Ecc::QUARTILE: return 3; - case Ecc::HIGH : return 2; - default: throw std::logic_error("Unreachable"); - } -} - - -QrCode QrCode::encodeText(const char *text, Ecc ecl) { - vector segs = QrSegment::makeSegments(text); - return encodeSegments(segs, ecl); -} - - -QrCode QrCode::encodeBinary(const vector &data, Ecc ecl) { - vector segs{QrSegment::makeBytes(data)}; - return encodeSegments(segs, ecl); -} - - -QrCode QrCode::encodeSegments(const vector &segs, Ecc ecl, - int minVersion, int maxVersion, int mask, bool boostEcl) { - if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) - throw std::invalid_argument("Invalid value"); - - // Find the minimal version number to use - int version, dataUsedBits; - for (version = minVersion; ; version++) { - int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available - dataUsedBits = QrSegment::getTotalBits(segs, version); - if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) - break; // This version number is found to be suitable - if (version >= maxVersion) { // All versions in the range could not fit the given data - std::ostringstream sb; - if (dataUsedBits == -1) - sb << "Segment too long"; - else { - sb << "Data length = " << dataUsedBits << " bits, "; - sb << "Max capacity = " << dataCapacityBits << " bits"; - } - throw data_too_long(sb.str()); - } - } - assert(dataUsedBits != -1); - - // Increase the error correction level while the data still fits in the current version number - for (Ecc newEcl : {Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high - if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) - ecl = newEcl; - } - - // Concatenate all segments to create the data bit string - BitBuffer bb; - for (const QrSegment &seg : segs) { - bb.appendBits(static_cast(seg.getMode().getModeBits()), 4); - bb.appendBits(static_cast(seg.getNumChars()), seg.getMode().numCharCountBits(version)); - bb.insert(bb.end(), seg.getData().begin(), seg.getData().end()); - } - assert(bb.size() == static_cast(dataUsedBits)); - - // Add terminator and pad up to a byte if applicable - size_t dataCapacityBits = static_cast(getNumDataCodewords(version, ecl)) * 8; - assert(bb.size() <= dataCapacityBits); - bb.appendBits(0, std::min(4, static_cast(dataCapacityBits - bb.size()))); - bb.appendBits(0, (8 - static_cast(bb.size() % 8)) % 8); - assert(bb.size() % 8 == 0); - - // Pad with alternating bytes until data capacity is reached - for (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) - bb.appendBits(padByte, 8); - - // Pack bits into bytes in big endian - vector dataCodewords(bb.size() / 8); - for (size_t i = 0; i < bb.size(); i++) - dataCodewords.at(i >> 3) |= (bb.at(i) ? 1 : 0) << (7 - (i & 7)); - - // Create the QR Code object - return QrCode(version, ecl, dataCodewords, mask); -} - - -QrCode::QrCode(int ver, Ecc ecl, const vector &dataCodewords, int msk) : - // Initialize fields and check arguments - version(ver), - errorCorrectionLevel(ecl) { - if (ver < MIN_VERSION || ver > MAX_VERSION) - throw std::domain_error("Version value out of range"); - if (msk < -1 || msk > 7) - throw std::domain_error("Mask value out of range"); - size = ver * 4 + 17; - size_t sz = static_cast(size); - modules = vector >(sz, vector(sz)); // Initially all light - isFunction = vector >(sz, vector(sz)); - - // Compute ECC, draw modules - drawFunctionPatterns(); - const vector allCodewords = addEccAndInterleave(dataCodewords); - drawCodewords(allCodewords); - - // Do masking - if (msk == -1) { // Automatically choose best mask - long minPenalty = LONG_MAX; - for (int i = 0; i < 8; i++) { - applyMask(i); - drawFormatBits(i); - long penalty = getPenaltyScore(); - if (penalty < minPenalty) { - msk = i; - minPenalty = penalty; - } - applyMask(i); // Undoes the mask due to XOR - } - } - assert(0 <= msk && msk <= 7); - mask = msk; - applyMask(msk); // Apply the final choice of mask - drawFormatBits(msk); // Overwrite old format bits - - isFunction.clear(); - isFunction.shrink_to_fit(); -} - - -int QrCode::getVersion() const { - return version; -} - - -int QrCode::getSize() const { - return size; -} - - -QrCode::Ecc QrCode::getErrorCorrectionLevel() const { - return errorCorrectionLevel; -} - - -int QrCode::getMask() const { - return mask; -} - - -bool QrCode::getModule(int x, int y) const { - return 0 <= x && x < size && 0 <= y && y < size && module(x, y); -} - - -void QrCode::drawFunctionPatterns() { - // Draw horizontal and vertical timing patterns - for (int i = 0; i < size; i++) { - setFunctionModule(6, i, i % 2 == 0); - setFunctionModule(i, 6, i % 2 == 0); - } - - // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) - drawFinderPattern(3, 3); - drawFinderPattern(size - 4, 3); - drawFinderPattern(3, size - 4); - - // Draw numerous alignment patterns - const vector alignPatPos = getAlignmentPatternPositions(); - size_t numAlign = alignPatPos.size(); - for (size_t i = 0; i < numAlign; i++) { - for (size_t j = 0; j < numAlign; j++) { - // Don't draw on the three finder corners - if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) - drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j)); - } - } - - // Draw configuration data - drawFormatBits(0); // Dummy mask value; overwritten later in the constructor - drawVersion(); -} - - -void QrCode::drawFormatBits(int msk) { - // Calculate error correction code and pack bits - int data = getFormatBits(errorCorrectionLevel) << 3 | msk; // errCorrLvl is uint2, msk is uint3 - int rem = data; - for (int i = 0; i < 10; i++) - rem = (rem << 1) ^ ((rem >> 9) * 0x537); - int bits = (data << 10 | rem) ^ 0x5412; // uint15 - assert(bits >> 15 == 0); - - // Draw first copy - for (int i = 0; i <= 5; i++) - setFunctionModule(8, i, getBit(bits, i)); - setFunctionModule(8, 7, getBit(bits, 6)); - setFunctionModule(8, 8, getBit(bits, 7)); - setFunctionModule(7, 8, getBit(bits, 8)); - for (int i = 9; i < 15; i++) - setFunctionModule(14 - i, 8, getBit(bits, i)); - - // Draw second copy - for (int i = 0; i < 8; i++) - setFunctionModule(size - 1 - i, 8, getBit(bits, i)); - for (int i = 8; i < 15; i++) - setFunctionModule(8, size - 15 + i, getBit(bits, i)); - setFunctionModule(8, size - 8, true); // Always dark -} - - -void QrCode::drawVersion() { - if (version < 7) - return; - - // Calculate error correction code and pack bits - int rem = version; // version is uint6, in the range [7, 40] - for (int i = 0; i < 12; i++) - rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); - long bits = static_cast(version) << 12 | rem; // uint18 - assert(bits >> 18 == 0); - - // Draw two copies - for (int i = 0; i < 18; i++) { - bool bit = getBit(bits, i); - int a = size - 11 + i % 3; - int b = i / 3; - setFunctionModule(a, b, bit); - setFunctionModule(b, a, bit); - } -} - - -void QrCode::drawFinderPattern(int x, int y) { - for (int dy = -4; dy <= 4; dy++) { - for (int dx = -4; dx <= 4; dx++) { - int dist = std::max(std::abs(dx), std::abs(dy)); // Chebyshev/infinity norm - int xx = x + dx, yy = y + dy; - if (0 <= xx && xx < size && 0 <= yy && yy < size) - setFunctionModule(xx, yy, dist != 2 && dist != 4); - } - } -} - - -void QrCode::drawAlignmentPattern(int x, int y) { - for (int dy = -2; dy <= 2; dy++) { - for (int dx = -2; dx <= 2; dx++) - setFunctionModule(x + dx, y + dy, std::max(std::abs(dx), std::abs(dy)) != 1); - } -} - - -void QrCode::setFunctionModule(int x, int y, bool isDark) { - size_t ux = static_cast(x); - size_t uy = static_cast(y); - modules .at(uy).at(ux) = isDark; - isFunction.at(uy).at(ux) = true; -} - - -bool QrCode::module(int x, int y) const { - return modules.at(static_cast(y)).at(static_cast(x)); -} - - -vector QrCode::addEccAndInterleave(const vector &data) const { - if (data.size() != static_cast(getNumDataCodewords(version, errorCorrectionLevel))) - throw std::invalid_argument("Invalid argument"); - - // Calculate parameter numbers - int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[static_cast(errorCorrectionLevel)][version]; - int blockEccLen = ECC_CODEWORDS_PER_BLOCK [static_cast(errorCorrectionLevel)][version]; - int rawCodewords = getNumRawDataModules(version) / 8; - int numShortBlocks = numBlocks - rawCodewords % numBlocks; - int shortBlockLen = rawCodewords / numBlocks; - - // Split data into blocks and append ECC to each block - vector > blocks; - const vector rsDiv = reedSolomonComputeDivisor(blockEccLen); - for (int i = 0, k = 0; i < numBlocks; i++) { - vector dat(data.cbegin() + k, data.cbegin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1))); - k += static_cast(dat.size()); - const vector ecc = reedSolomonComputeRemainder(dat, rsDiv); - if (i < numShortBlocks) - dat.push_back(0); - dat.insert(dat.end(), ecc.cbegin(), ecc.cend()); - blocks.push_back(std::move(dat)); - } - - // Interleave (not concatenate) the bytes from every block into a single sequence - vector result; - for (size_t i = 0; i < blocks.at(0).size(); i++) { - for (size_t j = 0; j < blocks.size(); j++) { - // Skip the padding byte in short blocks - if (i != static_cast(shortBlockLen - blockEccLen) || j >= static_cast(numShortBlocks)) - result.push_back(blocks.at(j).at(i)); - } - } - assert(result.size() == static_cast(rawCodewords)); - return result; -} - - -void QrCode::drawCodewords(const vector &data) { - if (data.size() != static_cast(getNumRawDataModules(version) / 8)) - throw std::invalid_argument("Invalid argument"); - - size_t i = 0; // Bit index into the data - // Do the funny zigzag scan - for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair - if (right == 6) - right = 5; - for (int vert = 0; vert < size; vert++) { // Vertical counter - for (int j = 0; j < 2; j++) { - size_t x = static_cast(right - j); // Actual x coordinate - bool upward = ((right + 1) & 2) == 0; - size_t y = static_cast(upward ? size - 1 - vert : vert); // Actual y coordinate - if (!isFunction.at(y).at(x) && i < data.size() * 8) { - modules.at(y).at(x) = getBit(data.at(i >> 3), 7 - static_cast(i & 7)); - i++; - } - // If this QR Code has any remainder bits (0 to 7), they were assigned as - // 0/false/light by the constructor and are left unchanged by this method - } - } - } - assert(i == data.size() * 8); -} - - -void QrCode::applyMask(int msk) { - if (msk < 0 || msk > 7) - throw std::domain_error("Mask value out of range"); - size_t sz = static_cast(size); - for (size_t y = 0; y < sz; y++) { - for (size_t x = 0; x < sz; x++) { - bool invert; - switch (msk) { - case 0: invert = (x + y) % 2 == 0; break; - case 1: invert = y % 2 == 0; break; - case 2: invert = x % 3 == 0; break; - case 3: invert = (x + y) % 3 == 0; break; - case 4: invert = (x / 3 + y / 2) % 2 == 0; break; - case 5: invert = x * y % 2 + x * y % 3 == 0; break; - case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; - case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; - default: throw std::logic_error("Unreachable"); - } - modules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x)); - } - } -} - - -long QrCode::getPenaltyScore() const { - long result = 0; - - // Adjacent modules in row having same color, and finder-like patterns - for (int y = 0; y < size; y++) { - bool runColor = false; - int runX = 0; - std::array runHistory = {}; - for (int x = 0; x < size; x++) { - if (module(x, y) == runColor) { - runX++; - if (runX == 5) - result += PENALTY_N1; - else if (runX > 5) - result++; - } else { - finderPenaltyAddHistory(runX, runHistory); - if (!runColor) - result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; - runColor = module(x, y); - runX = 1; - } - } - result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3; - } - // Adjacent modules in column having same color, and finder-like patterns - for (int x = 0; x < size; x++) { - bool runColor = false; - int runY = 0; - std::array runHistory = {}; - for (int y = 0; y < size; y++) { - if (module(x, y) == runColor) { - runY++; - if (runY == 5) - result += PENALTY_N1; - else if (runY > 5) - result++; - } else { - finderPenaltyAddHistory(runY, runHistory); - if (!runColor) - result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; - runColor = module(x, y); - runY = 1; - } - } - result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3; - } - - // 2*2 blocks of modules having same color - for (int y = 0; y < size - 1; y++) { - for (int x = 0; x < size - 1; x++) { - bool color = module(x, y); - if ( color == module(x + 1, y) && - color == module(x, y + 1) && - color == module(x + 1, y + 1)) - result += PENALTY_N2; - } - } - - // Balance of dark and light modules - int dark = 0; - for (const vector &row : modules) { - for (bool color : row) { - if (color) - dark++; - } - } - int total = size * size; // Note that size is odd, so dark/total != 1/2 - // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)% - int k = static_cast((std::abs(dark * 20L - total * 10L) + total - 1) / total) - 1; - assert(0 <= k && k <= 9); - result += k * PENALTY_N4; - assert(0 <= result && result <= 2568888L); // Non-tight upper bound based on default values of PENALTY_N1, ..., N4 - return result; -} - - -vector QrCode::getAlignmentPatternPositions() const { - if (version == 1) - return vector(); - else { - int numAlign = version / 7 + 2; - int step = (version * 8 + numAlign * 3 + 5) / (numAlign * 4 - 4) * 2; - vector result; - for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step) - result.insert(result.begin(), pos); - result.insert(result.begin(), 6); - return result; - } -} - - -int QrCode::getNumRawDataModules(int ver) { - if (ver < MIN_VERSION || ver > MAX_VERSION) - throw std::domain_error("Version number out of range"); - int result = (16 * ver + 128) * ver + 64; - if (ver >= 2) { - int numAlign = ver / 7 + 2; - result -= (25 * numAlign - 10) * numAlign - 55; - if (ver >= 7) - result -= 36; - } - assert(208 <= result && result <= 29648); - return result; -} - - -int QrCode::getNumDataCodewords(int ver, Ecc ecl) { - return getNumRawDataModules(ver) / 8 - - ECC_CODEWORDS_PER_BLOCK [static_cast(ecl)][ver] - * NUM_ERROR_CORRECTION_BLOCKS[static_cast(ecl)][ver]; -} - - -vector QrCode::reedSolomonComputeDivisor(int degree) { - if (degree < 1 || degree > 255) - throw std::domain_error("Degree out of range"); - // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. - // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. - vector result(static_cast(degree)); - result.at(result.size() - 1) = 1; // Start off with the monomial x^0 - - // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), - // and drop the highest monomial term which is always 1x^degree. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - uint8_t root = 1; - for (int i = 0; i < degree; i++) { - // Multiply the current product by (x - r^i) - for (size_t j = 0; j < result.size(); j++) { - result.at(j) = reedSolomonMultiply(result.at(j), root); - if (j + 1 < result.size()) - result.at(j) ^= result.at(j + 1); - } - root = reedSolomonMultiply(root, 0x02); - } - return result; -} - - -vector QrCode::reedSolomonComputeRemainder(const vector &data, const vector &divisor) { - vector result(divisor.size()); - for (uint8_t b : data) { // Polynomial division - uint8_t factor = b ^ result.at(0); - result.erase(result.begin()); - result.push_back(0); - for (size_t i = 0; i < result.size(); i++) - result.at(i) ^= reedSolomonMultiply(divisor.at(i), factor); - } - return result; -} - - -uint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) { - // Russian peasant multiplication - int z = 0; - for (int i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >> 7) * 0x11D); - z ^= ((y >> i) & 1) * x; - } - assert(z >> 8 == 0); - return static_cast(z); -} - - -int QrCode::finderPenaltyCountPatterns(const std::array &runHistory) const { - int n = runHistory.at(1); - assert(n <= size * 3); - bool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && runHistory.at(4) == n && runHistory.at(5) == n; - return (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0) - + (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0); -} - - -int QrCode::finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const { - if (currentRunColor) { // Terminate dark run - finderPenaltyAddHistory(currentRunLength, runHistory); - currentRunLength = 0; - } - currentRunLength += size; // Add light border to final run - finderPenaltyAddHistory(currentRunLength, runHistory); - return finderPenaltyCountPatterns(runHistory); -} - - -void QrCode::finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const { - if (runHistory.at(0) == 0) - currentRunLength += size; // Add light border to initial run - std::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, runHistory.end()); - runHistory.at(0) = currentRunLength; -} - - -bool QrCode::getBit(long x, int i) { - return ((x >> i) & 1) != 0; -} - - -/*---- Tables of constants ----*/ - -const int QrCode::PENALTY_N1 = 3; -const int QrCode::PENALTY_N2 = 3; -const int QrCode::PENALTY_N3 = 40; -const int QrCode::PENALTY_N4 = 10; - - -const int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = { - // Version: (note that index 0 is for padding, and is set to an illegal value) - //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low - {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium - {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile - {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High -}; - -const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = { - // Version: (note that index 0 is for padding, and is set to an illegal value) - //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low - {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium - {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile - {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High -}; - - -data_too_long::data_too_long(const std::string &msg) : - std::length_error(msg) {} - - - -/*---- Class BitBuffer ----*/ - -BitBuffer::BitBuffer() - : std::vector() {} - - -void BitBuffer::appendBits(std::uint32_t val, int len) { - if (len < 0 || len > 31 || val >> len != 0) - throw std::domain_error("Value out of range"); - for (int i = len - 1; i >= 0; i--) // Append bit by bit - this->push_back(((val >> i) & 1) != 0); -} - -} diff --git a/qrcodegen.hpp b/qrcodegen.hpp deleted file mode 100644 index 077d365..0000000 --- a/qrcodegen.hpp +++ /dev/null @@ -1,550 +0,0 @@ -/* - * QR Code generator library (C++) - * - * Copyright (c) Project Nayuki. (MIT License) - * https://www.nayuki.io/page/qr-code-generator-library - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - The Software is provided "as is", without warranty of any kind, express or - * implied, including but not limited to the warranties of merchantability, - * fitness for a particular purpose and noninfringement. In no event shall the - * authors or copyright holders be liable for any claim, damages or other - * liability, whether in an action of contract, tort or otherwise, arising from, - * out of or in connection with the Software or the use or other dealings in the - * Software. - */ - -#pragma once - -#include -#include -#include -#include -#include - - -namespace qrcodegen { - -/* - * A segment of character/binary/control data in a QR Code symbol. - * Instances of this class are immutable. - * The mid-level way to create a segment is to take the payload data - * and call a static factory function such as QrSegment::makeNumeric(). - * The low-level way to create a segment is to custom-make the bit buffer - * and call the QrSegment() constructor with appropriate values. - * This segment class imposes no length restrictions, but QR Codes have restrictions. - * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. - * Any segment longer than this is meaningless for the purpose of generating QR Codes. - */ -class QrSegment final { - - /*---- Public helper enumeration ----*/ - - /* - * Describes how a segment's data bits are interpreted. Immutable. - */ - public: class Mode final { - - /*-- Constants --*/ - - public: static const Mode NUMERIC; - public: static const Mode ALPHANUMERIC; - public: static const Mode BYTE; - public: static const Mode KANJI; - public: static const Mode ECI; - - - /*-- Fields --*/ - - // The mode indicator bits, which is a uint4 value (range 0 to 15). - private: int modeBits; - - // Number of character count bits for three different version ranges. - private: int numBitsCharCount[3]; - - - /*-- Constructor --*/ - - private: Mode(int mode, int cc0, int cc1, int cc2); - - - /*-- Methods --*/ - - /* - * (Package-private) Returns the mode indicator bits, which is an unsigned 4-bit value (range 0 to 15). - */ - public: int getModeBits() const; - - /* - * (Package-private) Returns the bit width of the character count field for a segment in - * this mode in a QR Code at the given version number. The result is in the range [0, 16]. - */ - public: int numCharCountBits(int ver) const; - - }; - - - - /*---- Static factory functions (mid level) ----*/ - - /* - * Returns a segment representing the given binary data encoded in - * byte mode. All input byte vectors are acceptable. Any text string - * can be converted to UTF-8 bytes and encoded as a byte mode segment. - */ - public: static QrSegment makeBytes(const std::vector &data); - - - /* - * Returns a segment representing the given string of decimal digits encoded in numeric mode. - */ - public: static QrSegment makeNumeric(const char *digits); - - - /* - * Returns a segment representing the given text string encoded in alphanumeric mode. - * The characters allowed are: 0 to 9, A to Z (uppercase only), space, - * dollar, percent, asterisk, plus, hyphen, period, slash, colon. - */ - public: static QrSegment makeAlphanumeric(const char *text); - - - /* - * Returns a list of zero or more segments to represent the given text string. The result - * may use various segment modes and switch modes to optimize the length of the bit stream. - */ - public: static std::vector makeSegments(const char *text); - - - /* - * Returns a segment representing an Extended Channel Interpretation - * (ECI) designator with the given assignment value. - */ - public: static QrSegment makeEci(long assignVal); - - - /*---- Public static helper functions ----*/ - - /* - * Tests whether the given string can be encoded as a segment in numeric mode. - * A string is encodable iff each character is in the range 0 to 9. - */ - public: static bool isNumeric(const char *text); - - - /* - * Tests whether the given string can be encoded as a segment in alphanumeric mode. - * A string is encodable iff each character is in the following set: 0 to 9, A to Z - * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. - */ - public: static bool isAlphanumeric(const char *text); - - - - /*---- Instance fields ----*/ - - /* The mode indicator of this segment. Accessed through getMode(). */ - private: const Mode *mode; - - /* The length of this segment's unencoded data. Measured in characters for - * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. - * Always zero or positive. Not the same as the data's bit length. - * Accessed through getNumChars(). */ - private: int numChars; - - /* The data bits of this segment. Accessed through getData(). */ - private: std::vector data; - - - /*---- Constructors (low level) ----*/ - - /* - * Creates a new QR Code segment with the given attributes and data. - * The character count (numCh) must agree with the mode and the bit buffer length, - * but the constraint isn't checked. The given bit buffer is copied and stored. - */ - public: QrSegment(const Mode &md, int numCh, const std::vector &dt); - - - /* - * Creates a new QR Code segment with the given parameters and data. - * The character count (numCh) must agree with the mode and the bit buffer length, - * but the constraint isn't checked. The given bit buffer is moved and stored. - */ - public: QrSegment(const Mode &md, int numCh, std::vector &&dt); - - - /*---- Methods ----*/ - - /* - * Returns the mode field of this segment. - */ - public: const Mode &getMode() const; - - - /* - * Returns the character count field of this segment. - */ - public: int getNumChars() const; - - - /* - * Returns the data bits of this segment. - */ - public: const std::vector &getData() const; - - - // (Package-private) Calculates the number of bits needed to encode the given segments at - // the given version. Returns a non-negative number if successful. Otherwise returns -1 if a - // segment has too many characters to fit its length field, or the total bits exceeds INT_MAX. - public: static int getTotalBits(const std::vector &segs, int version); - - - /*---- Private constant ----*/ - - /* The set of all legal characters in alphanumeric mode, where - * each character value maps to the index in the string. */ - private: static const char *ALPHANUMERIC_CHARSET; - -}; - - - -/* - * A QR Code symbol, which is a type of two-dimension barcode. - * Invented by Denso Wave and described in the ISO/IEC 18004 standard. - * Instances of this class represent an immutable square grid of dark and light cells. - * The class provides static factory functions to create a QR Code from text or binary data. - * The class covers the QR Code Model 2 specification, supporting all versions (sizes) - * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. - * - * Ways to create a QR Code object: - * - High level: Take the payload data and call QrCode::encodeText() or QrCode::encodeBinary(). - * - Mid level: Custom-make the list of segments and call QrCode::encodeSegments(). - * - Low level: Custom-make the array of data codeword bytes (including - * segment headers and final padding, excluding error correction codewords), - * supply the appropriate version number, and call the QrCode() constructor. - * (Note that all ways require supplying the desired error correction level.) - */ -class QrCode final { - - /*---- Public helper enumeration ----*/ - - /* - * The error correction level in a QR Code symbol. - */ - public: enum class Ecc { - LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords - MEDIUM , // The QR Code can tolerate about 15% erroneous codewords - QUARTILE, // The QR Code can tolerate about 25% erroneous codewords - HIGH , // The QR Code can tolerate about 30% erroneous codewords - }; - - - // Returns a value in the range 0 to 3 (unsigned 2-bit integer). - private: static int getFormatBits(Ecc ecl); - - - - /*---- Static factory functions (high level) ----*/ - - /* - * Returns a QR Code representing the given Unicode text string at the given error correction level. - * As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer - * UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible - * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than - * the ecl argument if it can be done without increasing the version. - */ - public: static QrCode encodeText(const char *text, Ecc ecl); - - - /* - * Returns a QR Code representing the given binary data at the given error correction level. - * This function always encodes using the binary segment mode, not any text mode. The maximum number of - * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. - * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. - */ - public: static QrCode encodeBinary(const std::vector &data, Ecc ecl); - - - /*---- Static factory functions (mid level) ----*/ - - /* - * Returns a QR Code representing the given segments with the given encoding parameters. - * The smallest possible QR Code version within the given range is automatically - * chosen for the output. Iff boostEcl is true, then the ECC level of the result - * may be higher than the ecl argument if it can be done without increasing the - * version. The mask number is either between 0 to 7 (inclusive) to force that - * mask, or -1 to automatically choose an appropriate mask (which may be slow). - * This function allows the user to create a custom sequence of segments that switches - * between modes (such as alphanumeric and byte) to encode text in less space. - * This is a mid-level API; the high-level API is encodeText() and encodeBinary(). - */ - public: static QrCode encodeSegments(const std::vector &segs, Ecc ecl, - int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters - - - - /*---- Instance fields ----*/ - - // Immutable scalar parameters: - - /* The version number of this QR Code, which is between 1 and 40 (inclusive). - * This determines the size of this barcode. */ - private: int version; - - /* The width and height of this QR Code, measured in modules, between - * 21 and 177 (inclusive). This is equal to version * 4 + 17. */ - private: int size; - - /* The error correction level used in this QR Code. */ - private: Ecc errorCorrectionLevel; - - /* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). - * Even if a QR Code is created with automatic masking requested (mask = -1), - * the resulting object still has a mask value between 0 and 7. */ - private: int mask; - - // Private grids of modules/pixels, with dimensions of size*size: - - // The modules of this QR Code (false = light, true = dark). - // Immutable after constructor finishes. Accessed through getModule(). - private: std::vector > modules; - - // Indicates function modules that are not subjected to masking. Discarded when constructor finishes. - private: std::vector > isFunction; - - - - /*---- Constructor (low level) ----*/ - - /* - * Creates a new QR Code with the given version number, - * error correction level, data codeword bytes, and mask number. - * This is a low-level API that most users should not use directly. - * A mid-level API is the encodeSegments() function. - */ - public: QrCode(int ver, Ecc ecl, const std::vector &dataCodewords, int msk); - - - - /*---- Public instance methods ----*/ - - /* - * Returns this QR Code's version, in the range [1, 40]. - */ - public: int getVersion() const; - - - /* - * Returns this QR Code's size, in the range [21, 177]. - */ - public: int getSize() const; - - - /* - * Returns this QR Code's error correction level. - */ - public: Ecc getErrorCorrectionLevel() const; - - - /* - * Returns this QR Code's mask, in the range [0, 7]. - */ - public: int getMask() const; - - - /* - * Returns the color of the module (pixel) at the given coordinates, which is false - * for light or true for dark. The top left corner has the coordinates (x=0, y=0). - * If the given coordinates are out of bounds, then false (light) is returned. - */ - public: bool getModule(int x, int y) const; - - - - /*---- Private helper methods for constructor: Drawing function modules ----*/ - - // Reads this object's version field, and draws and marks all function modules. - private: void drawFunctionPatterns(); - - - // Draws two copies of the format bits (with its own error correction code) - // based on the given mask and this object's error correction level field. - private: void drawFormatBits(int msk); - - - // Draws two copies of the version bits (with its own error correction code), - // based on this object's version field, iff 7 <= version <= 40. - private: void drawVersion(); - - - // Draws a 9*9 finder pattern including the border separator, - // with the center module at (x, y). Modules can be out of bounds. - private: void drawFinderPattern(int x, int y); - - - // Draws a 5*5 alignment pattern, with the center module - // at (x, y). All modules must be in bounds. - private: void drawAlignmentPattern(int x, int y); - - - // Sets the color of a module and marks it as a function module. - // Only used by the constructor. Coordinates must be in bounds. - private: void setFunctionModule(int x, int y, bool isDark); - - - // Returns the color of the module at the given coordinates, which must be in range. - private: bool module(int x, int y) const; - - - /*---- Private helper methods for constructor: Codewords and masking ----*/ - - // Returns a new byte string representing the given data with the appropriate error correction - // codewords appended to it, based on this object's version and error correction level. - private: std::vector addEccAndInterleave(const std::vector &data) const; - - - // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire - // data area of this QR Code. Function modules need to be marked off before this is called. - private: void drawCodewords(const std::vector &data); - - - // XORs the codeword modules in this QR Code with the given mask pattern. - // The function modules must be marked and the codeword bits must be drawn - // before masking. Due to the arithmetic of XOR, calling applyMask() with - // the same mask value a second time will undo the mask. A final well-formed - // QR Code needs exactly one (not zero, two, etc.) mask applied. - private: void applyMask(int msk); - - - // Calculates and returns the penalty score based on state of this QR Code's current modules. - // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. - private: long getPenaltyScore() const; - - - - /*---- Private helper functions ----*/ - - // Returns an ascending list of positions of alignment patterns for this version number. - // Each position is in the range [0,177), and are used on both the x and y axes. - // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. - private: std::vector getAlignmentPatternPositions() const; - - - // Returns the number of data bits that can be stored in a QR Code of the given version number, after - // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. - // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. - private: static int getNumRawDataModules(int ver); - - - // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any - // QR Code of the given version number and error correction level, with remainder bits discarded. - // This stateless pure function could be implemented as a (40*4)-cell lookup table. - private: static int getNumDataCodewords(int ver, Ecc ecl); - - - // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be - // implemented as a lookup table over all possible parameter values, instead of as an algorithm. - private: static std::vector reedSolomonComputeDivisor(int degree); - - - // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. - private: static std::vector reedSolomonComputeRemainder(const std::vector &data, const std::vector &divisor); - - - // Returns the product of the two given field elements modulo GF(2^8/0x11D). - // All inputs are valid. This could be implemented as a 256*256 lookup table. - private: static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y); - - - // Can only be called immediately after a light run is added, and - // returns either 0, 1, or 2. A helper function for getPenaltyScore(). - private: int finderPenaltyCountPatterns(const std::array &runHistory) const; - - - // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). - private: int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const; - - - // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). - private: void finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const; - - - // Returns true iff the i'th bit of x is set to 1. - private: static bool getBit(long x, int i); - - - /*---- Constants and tables ----*/ - - // The minimum version number supported in the QR Code Model 2 standard. - public: static constexpr int MIN_VERSION = 1; - - // The maximum version number supported in the QR Code Model 2 standard. - public: static constexpr int MAX_VERSION = 40; - - - // For use in getPenaltyScore(), when evaluating which mask is best. - private: static const int PENALTY_N1; - private: static const int PENALTY_N2; - private: static const int PENALTY_N3; - private: static const int PENALTY_N4; - - - private: static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41]; - private: static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41]; - -}; - - - -/*---- Public exception class ----*/ - -/* - * Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include: - * - Decrease the error correction level if it was greater than Ecc::LOW. - * - If the encodeSegments() function was called with a maxVersion argument, then increase - * it if it was less than QrCode::MAX_VERSION. (This advice does not apply to the other - * factory functions because they search all versions up to QrCode::MAX_VERSION.) - * - Split the text data into better or optimal segments in order to reduce the number of bits required. - * - Change the text or binary data to be shorter. - * - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric). - * - Propagate the error upward to the caller/user. - */ -class data_too_long : public std::length_error { - - public: explicit data_too_long(const std::string &msg); - -}; - - - -/* - * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment. - */ -class BitBuffer final : public std::vector { - - /*---- Constructor ----*/ - - // Creates an empty bit buffer (length 0). - public: BitBuffer(); - - - - /*---- Method ----*/ - - // Appends the given number of low-order bits of the given value - // to this buffer. Requires 0 <= len <= 31 and val < 2^len. - public: void appendBits(std::uint32_t val, int len); - -}; - -} -