diff --git a/App/WheelMeasure/Doc/ModbusTCP_Protocol.md b/App/WheelMeasure/Doc/ModbusTCP_Protocol.md index 20714fa..00d2f77 100644 --- a/App/WheelMeasure/Doc/ModbusTCP_Protocol.md +++ b/App/WheelMeasure/Doc/ModbusTCP_Protocol.md @@ -25,6 +25,7 @@ | 18,19 | 下点X | R | FLOAT32 | 轮毂下点X坐标 (mm) | | 20,21 | 下点Y | R | FLOAT32 | 轮毂下点Y坐标 (mm) | | 22,23 | 下点Z | R | FLOAT32 | 轮毂下点Z坐标 (mm) | +| 24,25 | 到地面高度 | R | FLOAT32 | 轮眉到地面高度 (mm) | ## FLOAT32 格式 @@ -50,5 +51,5 @@ ``` 1. 写寄存器0 = 设备序号 (1-4) 2. 读寄存器1 等待状态变为 4 (完成) -3. 读地址2-23获取检测结果 +3. 读地址2-25获取检测结果 ``` diff --git a/App/WheelMeasure/Doc/wheelmeasure.mthings b/App/WheelMeasure/Doc/wheelmeasure.mthings index 1639336..fcabfc3 100644 --- a/App/WheelMeasure/Doc/wheelmeasure.mthings +++ b/App/WheelMeasure/Doc/wheelmeasure.mthings @@ -79,6 +79,11 @@ + + + + + diff --git a/App/WheelMeasure/WheelMeasureApp/Presenter/Src/WheelMeasurePresenter.cpp b/App/WheelMeasure/WheelMeasureApp/Presenter/Src/WheelMeasurePresenter.cpp index 5593231..3407d2c 100644 --- a/App/WheelMeasure/WheelMeasureApp/Presenter/Src/WheelMeasurePresenter.cpp +++ b/App/WheelMeasure/WheelMeasureApp/Presenter/Src/WheelMeasurePresenter.cpp @@ -19,11 +19,11 @@ WheelMeasurePresenter::WheelMeasurePresenter(QObject* parent) { // 创建配置接口 IVrWheelMeasureConfig::CreateInstance(&m_config); + LOG_INFO("ALGO_VERSION: %s \n", wd_wheelArchHeigthMeasureVersion()); } WheelMeasurePresenter::~WheelMeasurePresenter() { - // 释放配置接口 if (m_config) { delete m_config; m_config = nullptr; @@ -79,23 +79,27 @@ void WheelMeasurePresenter::OnCameraStatusChanged(int cameraIndex, bool isConnec { LOG_INFO("Camera[%d] status changed: %s\n", cameraIndex, isConnected ? "connected" : "disconnected"); - if (m_statusUpdate) { - // 从配置中获取相机名称(cameraIndex从1开始) - QString cameraName; - int enabledIndex = 0; - for (const auto& cameraConfig : m_configResult.cameras) { - if (cameraConfig.enabled) { - enabledIndex++; - if (enabledIndex == cameraIndex) { - cameraName = QString::fromStdString(cameraConfig.name); - break; - } + if (!m_statusUpdate) return; + + // 从配置中获取相机名称(cameraIndex从1开始) + QString cameraName; + int enabledIndex = 0; + for (const auto& cameraConfig : m_configResult.cameras) { + if (cameraConfig.enabled) { + enabledIndex++; + if (enabledIndex == cameraIndex) { + cameraName = QString::fromStdString(cameraConfig.name); + break; } } - if (cameraName.isEmpty()) { - cameraName = QString("Camera%1").arg(cameraIndex); - } + } + if (cameraName.isEmpty()) { + cameraName = QString("Camera%1").arg(cameraIndex); + } + // 切换到主线程更新UI + QMetaObject::invokeMethod(this, [this, cameraName, isConnected]() { + if (!m_statusUpdate) return; if (isConnected) { m_statusUpdate->OnCameraConnected(cameraName); m_statusUpdate->OnDeviceStatusChanged(cameraName, static_cast(DeviceStatus::Online)); @@ -103,13 +107,12 @@ void WheelMeasurePresenter::OnCameraStatusChanged(int cameraIndex, bool isConnec m_statusUpdate->OnCameraDisconnected(cameraName); m_statusUpdate->OnDeviceStatusChanged(cameraName, static_cast(DeviceStatus::Offline)); } - } + }, Qt::QueuedConnection); } void WheelMeasurePresenter::OnWorkStatusChanged(WorkStatus status) { // 写入Modbus工作状态 (地址1) - // 0=空闲, 1=初始化中, 2=就绪, 3=检测中, 4=完成, 5=错误 uint16_t statusValue = 0; switch (status) { case WorkStatus::InitIng: statusValue = 1; break; @@ -122,32 +125,45 @@ void WheelMeasurePresenter::OnWorkStatusChanged(WorkStatus status) } WriteModbusRegisters(1, &statusValue, 1); - // 直接使用公用的 WorkStatus,不再需要转换 - if (m_statusUpdate) { - m_statusUpdate->OnWorkStatusChanged(status); - } + // 切换到主线程更新UI + QMetaObject::invokeMethod(this, [this, status]() { + if (m_statusUpdate) { + m_statusUpdate->OnWorkStatusChanged(status); + } + }, Qt::QueuedConnection); } void WheelMeasurePresenter::OnCameraCountChanged(int count) { - if (m_statusUpdate) { - // 使用配置中的相机名称,因为此时 m_vrEyeDeviceList 可能还未填充 - QStringList cameraNames; - for (const auto& cameraConfig : m_configResult.cameras) { - if (cameraConfig.enabled) { - cameraNames.append(QString::fromStdString(cameraConfig.name)); - } + if (!m_statusUpdate) return; + + QStringList cameraNames; + for (const auto& cameraConfig : m_configResult.cameras) { + if (cameraConfig.enabled) { + cameraNames.append(QString::fromStdString(cameraConfig.name)); } - LOG_INFO("OnCameraCountChanged: count=%d, cameraNames=%d\n", count, cameraNames.size()); - m_statusUpdate->OnNeedShowImageCount(cameraNames); } + LOG_INFO("OnCameraCountChanged: count=%d, cameraNames=%d\n", count, cameraNames.size()); + + // 切换到主线程更新UI + QMetaObject::invokeMethod(this, [this, cameraNames]() { + if (m_statusUpdate) { + m_statusUpdate->OnNeedShowImageCount(cameraNames); + } + }, Qt::QueuedConnection); } void WheelMeasurePresenter::OnStatusUpdate(const std::string& statusMessage) { - if (m_statusUpdate) { - m_statusUpdate->OnStatusUpdate(QString::fromStdString(statusMessage)); - } + if (!m_statusUpdate) return; + + QString msg = QString::fromStdString(statusMessage); + // 切换到主线程更新UI + QMetaObject::invokeMethod(this, [this, msg]() { + if (m_statusUpdate) { + m_statusUpdate->OnStatusUpdate(msg); + } + }, Qt::QueuedConnection); } bool WheelMeasurePresenter::initializeConfig(const QString& configPath) @@ -465,10 +481,15 @@ void WheelMeasurePresenter::processScanData(std::vectorplaneHeight; LOG_INFO("Using calibrated plane parameters for camera %d\n", m_currentCameraIndex); - + LOG_INFO(" planeHeight: %.3f, errorCompensation: %.2f\n", calibParam->planeHeight, calibParam->errorCompensation); + if(calibParam){ + // 计算调平使用的地面高度(加上该相机的误差补偿) + double adjustedPlaneHeight = groundCalibPara.planeHeight + calibParam->errorCompensation; + LOG_INFO(" adjustedPlaneHeight (with compensation): %.3f\n", adjustedPlaneHeight); + for(size_t i = 0; i < scanLines.size(); i++){ - wd_horizonCamera_lineDataR(scanLines[i], calibParam->planeCalib, groundCalibPara.planeHeight); + wd_horizonCamera_lineDataR(scanLines[i], calibParam->planeCalib, adjustedPlaneHeight); } } } else { @@ -531,17 +552,18 @@ void WheelMeasurePresenter::processScanData(std::vectorOnStatusUpdate(statusMsg); } // 填充测量结果数据 WheelMeasureData measureData; measureData.archToCenterHeight = wheelArchResult.archToCenterHeigth; + measureData.archToGroundHeight = wheelArchResult.archToGroundHeigth; measureData.wheelArchPosX = wheelArchResult.wheelArchPos.x; measureData.wheelArchPosY = wheelArchResult.wheelArchPos.y; measureData.wheelArchPosZ = wheelArchResult.wheelArchPos.z; @@ -581,7 +603,7 @@ void WheelMeasurePresenter::processScanData(std::vector(bits & 0xFFFF); }; - uint16_t modbusData[22]; + uint16_t modbusData[24]; memset(modbusData, 0, sizeof(modbusData)); // 地址2: 设备序号 @@ -599,7 +621,7 @@ void WheelMeasurePresenter::processScanData(std::vector(wheelArchResult.archToCenterHeigth), modbusData[2], modbusData[3]); // 地址6-7: 轮眉X floatToUint16(static_cast(wheelArchResult.wheelArchPos.x), modbusData[4], modbusData[5]); @@ -619,10 +641,12 @@ void WheelMeasurePresenter::processScanData(std::vector(wheelArchResult.wheelDownPos.y), modbusData[18], modbusData[19]); // 地址22-23: 下点Z floatToUint16(static_cast(wheelArchResult.wheelDownPos.z), modbusData[20], modbusData[21]); + // 地址24-25: 到地面高度 + floatToUint16(static_cast(wheelArchResult.archToGroundHeigth), modbusData[22], modbusData[23]); } - WriteModbusRegisters(2, modbusData, 22); - LOG_INFO("Modbus: 写入检测结果到地址2-23, 设备=%d, 有效=%d\n", m_currentCameraIndex, modbusData[1]); + WriteModbusRegisters(2, modbusData, 24); + LOG_INFO("Modbus: 写入检测结果到地址2-25, 设备=%d, 有效=%d\n", m_currentCameraIndex, modbusData[1]); // 检测完成后清零"检测控制"(地址0) uint16_t zero = 0; @@ -638,7 +662,6 @@ void WheelMeasurePresenter::processScanData(std::vector= 0 && m_currentCameraIndex < static_cast(m_cameraList.size())) { + checkAndDisplayCalibrationStatus(m_currentCameraIndex); + } } void DialogCameraLevel::initializeCameraCombo() @@ -182,9 +188,7 @@ bool DialogCameraLevel::performCameraLeveling() // 获取选中的相机索引 int selectedIndex = ui->combo_camera->currentIndex(); - // 1. 设置调平状态回调 - setLevelingStatusCallback(); - + // 先检查索引有效性,再设置回调 if (selectedIndex < 0 || selectedIndex >= static_cast(m_cameraList.size())) { LOG_ERROR("Invalid camera index: %d\n", selectedIndex); return false; @@ -192,12 +196,16 @@ bool DialogCameraLevel::performCameraLeveling() LOG_INFO("Performing camera leveling with camera %d (index %d)\n", selectedIndex + 1, selectedIndex); + // 1. 设置调平状态回调(在索引检查之后) + setLevelingStatusCallback(); + // 2. 清空之前的扫描数据 clearScanDataCache(); // 3. 启动相机扫描地面数据 if (!startCameraScan(selectedIndex)) { LOG_ERROR("Failed to start camera scan for leveling\n"); + restorePresenterStatusCallback(); // 恢复回调 return false; } @@ -229,6 +237,7 @@ bool DialogCameraLevel::performCameraLeveling() if (!calculatePlaneCalibration(planeCalib, planeHeight, invRMatrix)) { LOG_ERROR("Failed to calculate plane calibration\n"); + restorePresenterStatusCallback(); // 恢复回调 return false; } @@ -249,16 +258,22 @@ bool DialogCameraLevel::performCameraLeveling() if (!saveLevelingResults(planeCalib, planeHeight, invRMatrix, cameraIndex, cameraName)) { LOG_ERROR("Failed to save leveling results\n"); + restorePresenterStatusCallback(); // 恢复回调 return false; } clearScanDataCache(); + + // 9. 调平完成后恢复回调 + restorePresenterStatusCallback(); + LOG_INFO("Camera leveling completed successfully\n"); return true; } catch (const std::exception& e) { LOG_ERROR("Exception in performCameraLeveling: %s\n", e.what()); + restorePresenterStatusCallback(); // 异常时也恢复回调 return false; } } @@ -471,12 +486,16 @@ bool DialogCameraLevel::saveLevelingResults(double planeCalib[9], double planeHe return false; } + // 获取UI中的误差补偿值 + double errorCompensation = ui->edit_error_compensation->text().toDouble(); + // 创建或更新相机调平参数 WheelCameraPlaneCalibParam cameraParam; cameraParam.cameraIndex = cameraIndex; cameraParam.cameraName = cameraName.toStdString(); cameraParam.planeHeight = planeHeight; cameraParam.isCalibrated = true; + cameraParam.errorCompensation = errorCompensation; // 复制校准矩阵 for (int i = 0; i < 9; i++) { @@ -508,7 +527,7 @@ bool DialogCameraLevel::saveLevelingResults(double planeCalib[9], double planeHe LOG_INFO("Leveling results saved successfully for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); - LOG_INFO("Plane height: %.3f\n", planeHeight); + LOG_INFO("Plane height: %.3f, Error compensation: %.2f\n", planeHeight, errorCompensation); return true; @@ -536,12 +555,18 @@ bool DialogCameraLevel::loadCameraCalibrationData(int cameraIndex, const QString } planeHeight = param.planeHeight; + // 加载该相机的误差补偿值到UI + ui->edit_error_compensation->setText(QString::number(param.errorCompensation, 'f', 1)); + LOG_INFO("Calibration data loaded successfully for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); return true; } } + // 没有找到标定数据时,设置默认误差补偿值 + ui->edit_error_compensation->setText(QString::number(-5.0, 'f', 1)); + LOG_INFO("No calibration data found for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); return false; @@ -627,3 +652,54 @@ void DialogCameraLevel::restorePresenterStatusCallback() LOG_INFO("Presenter status callback restored for all cameras\n"); } + +void DialogCameraLevel::on_btn_save_compensation_clicked() +{ + // 保存误差补偿值到当前相机的调平参数 + if (!m_pConfig || !m_pConfigResult) { + LOG_ERROR("Config is null, cannot save error compensation\n"); + return; + } + + if (m_currentCameraIndex < 0 || m_currentCameraIndex >= static_cast(m_cameraList.size())) { + LOG_WARNING("Invalid camera index: %d\n", m_currentCameraIndex); + return; + } + + // 获取UI中的误差补偿值 + double errorCompensation = ui->edit_error_compensation->text().toDouble(); + int cameraIndex = m_currentCameraIndex + 1; // 转换为1-based索引 + QString cameraName = QString::fromStdString(m_cameraList[m_currentCameraIndex].first); + + LOG_INFO("Saving error compensation for camera %d (%s): %.2f\n", + cameraIndex, cameraName.toUtf8().constData(), errorCompensation); + + // 查找或创建相机调平参数 + bool found = false; + for (auto& param : m_pConfigResult->planeCalibParams) { + if (param.cameraIndex == cameraIndex) { + param.errorCompensation = errorCompensation; + found = true; + break; + } + } + + // 如果没有找到现有记录,创建一个新的(仅包含误差补偿,其他值为默认) + if (!found) { + WheelCameraPlaneCalibParam newParam; + newParam.cameraIndex = cameraIndex; + newParam.cameraName = cameraName.toStdString(); + newParam.errorCompensation = errorCompensation; + newParam.isCalibrated = false; // 尚未标定 + m_pConfigResult->planeCalibParams.push_back(newParam); + } + + // 保存配置到文件 + QString configPath = PathManager::GetInstance().GetConfigFilePath(); + bool saveResult = m_pConfig->SaveConfig(configPath.toStdString(), *m_pConfigResult); + if (saveResult) { + LOG_INFO("Error compensation saved successfully for camera %d: %.2f\n", cameraIndex, errorCompensation); + } else { + LOG_ERROR("Failed to save error compensation\n"); + } +} diff --git a/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.h b/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.h index f73ab59..b2ff263 100644 --- a/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.h +++ b/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.h @@ -48,6 +48,7 @@ private slots: void on_btn_apply_clicked(); void on_btn_cancel_clicked(); void on_combo_camera_currentIndexChanged(int index); + void on_btn_save_compensation_clicked(); private: Ui::DialogCameraLevel *ui; diff --git a/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.ui b/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.ui index 1d8c4f9..9a72e57 100644 --- a/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.ui +++ b/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.ui @@ -180,6 +180,111 @@ background-color: rgb(47, 48, 52); + + + + + 100 + 0 + + + + + 16 + + + + color: rgb(221, 225, 233); + + + 误差补偿: + + + + + + + + 100 + 35 + + + + + 100 + 16777215 + + + + + 16 + + + + color: rgb(221, 225, 233); +background-color: rgb(47, 48, 52); + + + -5.0 + + + Qt::AlignCenter + + + + + + + + 14 + + + + color: rgb(221, 225, 233); + + + mm + + + + + + + + 60 + 35 + + + + + 60 + 35 + + + + + 14 + + + + QPushButton { + color: rgb(221, 225, 233); + background-color: rgb(60, 63, 65); + border: 1px solid #3B3D47; + border-radius: 4px; +} +QPushButton:hover { + background-color: rgb(80, 83, 85); +} +QPushButton:pressed { + background-color: rgb(45, 48, 50); +} + + + 保存 + + + diff --git a/App/WheelMeasure/WheelMeasureApp/mainwindow.cpp b/App/WheelMeasure/WheelMeasureApp/mainwindow.cpp index 3f4843f..e336137 100644 --- a/App/WheelMeasure/WheelMeasureApp/mainwindow.cpp +++ b/App/WheelMeasure/WheelMeasureApp/mainwindow.cpp @@ -78,12 +78,10 @@ MainWindow::MainWindow(QWidget *parent) connect(m_presenter, &WheelMeasurePresenter::configUpdated, this, &MainWindow::onConfigSaved); // 连接设备点击信号到重新检测槽函数 - connect(m_deviceStatusWidget, &DeviceStatusWidget::deviceClicked, - this, &MainWindow::onDeviceClicked); + connect(m_deviceStatusWidget, &DeviceStatusWidget::deviceClicked, this, &MainWindow::onDeviceClicked); // 连接图像右键点击信号到保存数据槽函数 - connect(m_gridView, &ImageGridWidget::tileRightClicked, - this, &MainWindow::onTileRightClicked); + connect(m_gridView, &ImageGridWidget::tileRightClicked, this, &MainWindow::onTileRightClicked); // 设置版本信息显示 setupVersionDisplay(); @@ -100,6 +98,11 @@ MainWindow::MainWindow(QWidget *parent) MainWindow::~MainWindow() { + // 先清除回调,防止后台线程继续调用 + if (m_presenter) { + m_presenter->setStatusUpdate(nullptr); + } + if (m_gridView) { delete m_gridView; m_gridView = nullptr; @@ -141,6 +144,7 @@ MainWindow::~MainWindow() } delete ui; + LOG_DEBUG("~MainWindow finish \n"); } void MainWindow::resizeEvent(QResizeEvent* event) @@ -353,9 +357,9 @@ void MainWindow::onDeviceClicked(const QString& deviceName) void MainWindow::onConfigSaved() { - if (m_presenter) { - m_presenter->Init(); - } + // 配置已在 WheelMeasurePresenter::OnConfigChanged 中更新 + // 算法参数和调平参数的修改不需要重新初始化相机 + LOG_INFO("Config saved, configuration updated in memory\n"); } void MainWindow::onTileRightClicked(int index, const QString& alias) diff --git a/App/WheelMeasure/WheelMeasureApp/mainwindow.ui b/App/WheelMeasure/WheelMeasureApp/mainwindow.ui index b4713e4..e3c56c8 100644 --- a/App/WheelMeasure/WheelMeasureApp/mainwindow.ui +++ b/App/WheelMeasure/WheelMeasureApp/mainwindow.ui @@ -322,9 +322,9 @@ border: none; 1340 - 760 + 800 560 - 230 + 190 @@ -347,7 +347,7 @@ border: none; 1340 230 560 - 520 + 560 diff --git a/App/WheelMeasure/WheelMeasureApp/widgets/MeasureResultListWidget.cpp b/App/WheelMeasure/WheelMeasureApp/widgets/MeasureResultListWidget.cpp index e63d316..36bb288 100644 --- a/App/WheelMeasure/WheelMeasureApp/widgets/MeasureResultListWidget.cpp +++ b/App/WheelMeasure/WheelMeasureApp/widgets/MeasureResultListWidget.cpp @@ -22,29 +22,29 @@ void DeviceResultCard::setupUI() " background-color: rgb(47, 48, 52); " " border: 1px solid rgb(60, 62, 68); " " border-radius: 8px; " - " padding: 10px; " + " padding: 6px; " "} " "DeviceResultCard:hover { " " border: 1px solid rgb(80, 82, 88); " "}" ); - setMinimumHeight(120); - setMaximumHeight(160); + setMinimumHeight(130); + setMaximumHeight(170); // 主布局:水平布局(左侧名称 | 右侧数据) QHBoxLayout* mainLayout = new QHBoxLayout(this); - mainLayout->setContentsMargins(12, 8, 12, 8); - mainLayout->setSpacing(15); + mainLayout->setContentsMargins(8, 4, 8, 4); + mainLayout->setSpacing(10); // 左侧:设备名称和状态 QVBoxLayout* leftLayout = new QVBoxLayout(); - leftLayout->setSpacing(4); + leftLayout->setSpacing(2); m_nameLabel = new QLabel(m_deviceName, this); m_nameLabel->setStyleSheet("QLabel { color: rgb(221, 225, 233); font-size: 16px; background: transparent; }"); m_nameLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - m_nameLabel->setMinimumWidth(80); + m_nameLabel->setMinimumWidth(60); m_statusLabel = new QLabel(QString::fromUtf8("等待检测"), this); m_statusLabel->setStyleSheet("QLabel { color: rgb(150, 150, 150); font-size: 16px; background: transparent; }"); @@ -65,21 +65,25 @@ void DeviceResultCard::setupUI() // 右侧:纵向显示数据 QVBoxLayout* rightLayout = new QVBoxLayout(); - rightLayout->setSpacing(2); + rightLayout->setSpacing(4); // 拱高 m_heightLabel = new QLabel(QString::fromUtf8("拱高: -- mm"), this); m_heightLabel->setStyleSheet("QLabel { color: rgb(100, 200, 100); font-size: 16px; background: transparent; }"); + // 到地面高度 + m_groundHeightLabel = new QLabel(QString::fromUtf8("到地面: -- mm"), this); + m_groundHeightLabel->setStyleSheet("QLabel { color: rgb(100, 180, 220); font-size: 16px; background: transparent; }"); + // 拱点位置 m_archPosLabel = new QLabel(QString::fromUtf8("拱点: X:-- Y:-- Z:--"), this); m_archPosLabel->setStyleSheet("QLabel { color: rgb(180, 180, 180); font-size: 14px; background: transparent; }"); - // 上位置 + // 上点位置 m_upPosLabel = new QLabel(QString::fromUtf8("上点: X:-- Y:-- Z:--"), this); m_upPosLabel->setStyleSheet("QLabel { color: rgb(180, 180, 180); font-size: 14px; background: transparent; }"); - // 下位置 + // 下点位置 m_downPosLabel = new QLabel(QString::fromUtf8("下点: X:-- Y:-- Z:--"), this); m_downPosLabel->setStyleSheet("QLabel { color: rgb(180, 180, 180); font-size: 14px; background: transparent; }"); @@ -89,6 +93,7 @@ void DeviceResultCard::setupUI() rightLayout->addStretch(); rightLayout->addWidget(m_heightLabel); + rightLayout->addWidget(m_groundHeightLabel); rightLayout->addWidget(m_archPosLabel); rightLayout->addWidget(m_upPosLabel); rightLayout->addWidget(m_downPosLabel); @@ -105,6 +110,10 @@ void DeviceResultCard::updateResult(const WheelMeasureData& data, bool isValid) m_heightLabel->setText(QString::fromUtf8("拱高: %1 mm").arg(data.archToCenterHeight, 0, 'f', 2)); m_heightLabel->setStyleSheet("QLabel { color: rgb(100, 200, 100); font-size: 16px; background: transparent; }"); + // 更新到地面高度 + m_groundHeightLabel->setText(QString::fromUtf8("到地面: %1 mm").arg(data.archToGroundHeight, 0, 'f', 2)); + m_groundHeightLabel->setStyleSheet("QLabel { color: rgb(100, 180, 220); font-size: 16px; background: transparent; }"); + // 更新拱点位置 QString archPosText = QString::fromUtf8("拱点: X:%1 Y:%2 Z:%3") .arg(data.wheelArchPosX, 0, 'f', 2) @@ -112,14 +121,14 @@ void DeviceResultCard::updateResult(const WheelMeasureData& data, bool isValid) .arg(data.wheelArchPosZ, 0, 'f', 2); m_archPosLabel->setText(archPosText); - // 更新上位置 + // 更新上点位置 QString upPosText = QString::fromUtf8("上点: X:%1 Y:%2 Z:%3") .arg(data.wheelUpPosX, 0, 'f', 2) .arg(data.wheelUpPosY, 0, 'f', 2) .arg(data.wheelUpPosZ, 0, 'f', 2); m_upPosLabel->setText(upPosText); - // 更新下位置 + // 更新下点位置 QString downPosText = QString::fromUtf8("下点: X:%1 Y:%2 Z:%3") .arg(data.wheelDownPosX, 0, 'f', 2) .arg(data.wheelDownPosY, 0, 'f', 2) @@ -140,6 +149,9 @@ void DeviceResultCard::updateResult(const WheelMeasureData& data, bool isValid) m_heightLabel->setText(QString::fromUtf8("拱高: -- mm")); m_heightLabel->setStyleSheet("QLabel { color: rgb(200, 100, 100); font-size: 16px; background: transparent; }"); + m_groundHeightLabel->setText(QString::fromUtf8("到地面: -- mm")); + m_groundHeightLabel->setStyleSheet("QLabel { color: rgb(200, 100, 100); font-size: 16px; background: transparent; }"); + m_archPosLabel->setText(QString::fromUtf8("拱点: X:-- Y:-- Z:--")); m_upPosLabel->setText(QString::fromUtf8("上点: X:-- Y:-- Z:--")); m_downPosLabel->setText(QString::fromUtf8("下点: X:-- Y:-- Z:--")); @@ -156,6 +168,8 @@ void DeviceResultCard::clearResult() { m_heightLabel->setText(QString::fromUtf8("拱高: -- mm")); m_heightLabel->setStyleSheet("QLabel { color: rgb(150, 150, 150); font-size: 16px; background: transparent; }"); + m_groundHeightLabel->setText(QString::fromUtf8("到地面: -- mm")); + m_groundHeightLabel->setStyleSheet("QLabel { color: rgb(150, 150, 150); font-size: 16px; background: transparent; }"); m_archPosLabel->setText(QString::fromUtf8("拱点: X:-- Y:-- Z:--")); m_upPosLabel->setText(QString::fromUtf8("上点: X:-- Y:-- Z:--")); m_downPosLabel->setText(QString::fromUtf8("下点: X:-- Y:-- Z:--")); diff --git a/App/WheelMeasure/WheelMeasureApp/widgets/MeasureResultListWidget.h b/App/WheelMeasure/WheelMeasureApp/widgets/MeasureResultListWidget.h index a2c877e..e8b6dbf 100644 --- a/App/WheelMeasure/WheelMeasureApp/widgets/MeasureResultListWidget.h +++ b/App/WheelMeasure/WheelMeasureApp/widgets/MeasureResultListWidget.h @@ -31,9 +31,10 @@ private: QString m_deviceName; QLabel* m_nameLabel = nullptr; QLabel* m_heightLabel = nullptr; + QLabel* m_groundHeightLabel = nullptr; // 到地面高度 QLabel* m_archPosLabel = nullptr; // 拱点位置 - QLabel* m_upPosLabel = nullptr; // 上位置 - QLabel* m_downPosLabel = nullptr; // 下位置 + QLabel* m_upPosLabel = nullptr; // 上点位置 + QLabel* m_downPosLabel = nullptr; // 下点位置 QLabel* m_timestampLabel = nullptr; QLabel* m_statusLabel = nullptr; }; diff --git a/App/WheelMeasure/WheelMeasureConfig/Inc/IVrWheelMeasureConfig.h b/App/WheelMeasure/WheelMeasureConfig/Inc/IVrWheelMeasureConfig.h index 49e34ab..e80d9f0 100644 --- a/App/WheelMeasure/WheelMeasureConfig/Inc/IVrWheelMeasureConfig.h +++ b/App/WheelMeasure/WheelMeasureConfig/Inc/IVrWheelMeasureConfig.h @@ -60,6 +60,7 @@ struct WheelCameraPlaneCalibParam double planeHeight = -1; // 地面高度 double invRMatrix[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; // 逆旋转矩阵(回到原坐标系) bool isCalibrated = false; // 是否已标定 + double errorCompensation = -5.0; // 误差补偿(mm) }; /** @@ -154,6 +155,7 @@ struct WheelMeasureData { int id = 0; // 测量ID double archToCenterHeight = 0.0; // 拱高到中心的高度 + double archToGroundHeight = 0.0; // 拱高到地面的高度 double wheelArchPosX = 0.0; // 拱点X坐标 double wheelArchPosY = 0.0; // 拱点Y坐标 double wheelArchPosZ = 0.0; // 拱点Z坐标 diff --git a/App/WheelMeasure/WheelMeasureConfig/Src/VrWheelMeasureConfig.cpp b/App/WheelMeasure/WheelMeasureConfig/Src/VrWheelMeasureConfig.cpp index 556bbb6..c827ebb 100644 --- a/App/WheelMeasure/WheelMeasureConfig/Src/VrWheelMeasureConfig.cpp +++ b/App/WheelMeasure/WheelMeasureConfig/Src/VrWheelMeasureConfig.cpp @@ -84,6 +84,12 @@ WheelMeasureConfigResult VrWheelMeasureConfig::LoadConfig(const std::string& fil calibParam.cameraName = xml.attributes().value("name").toString().toStdString(); calibParam.planeHeight = xml.attributes().value("planeHeight").toDouble(); calibParam.isCalibrated = xml.attributes().value("isCalibrated").toInt() != 0; + // 读取误差补偿参数,默认值为-5.0 + if (xml.attributes().hasAttribute("errorCompensation")) { + calibParam.errorCompensation = xml.attributes().value("errorCompensation").toDouble(); + } else { + calibParam.errorCompensation = -5.0; + } // 读取planeCalib矩阵 QString planeCalibStr = xml.attributes().value("planeCalib").toString(); @@ -321,6 +327,7 @@ bool VrWheelMeasureConfig::SaveConfig(const std::string& filePath, WheelMeasureC xml.writeAttribute("name", QString::fromStdString(calibParam.cameraName)); xml.writeAttribute("planeHeight", QString::number(calibParam.planeHeight, 'f', 6)); xml.writeAttribute("isCalibrated", QString::number(calibParam.isCalibrated ? 1 : 0)); + xml.writeAttribute("errorCompensation", QString::number(calibParam.errorCompensation, 'f', 2)); // 保存planeCalib矩阵 QStringList planeCalibList; diff --git a/App/WheelMeasure/WheelMeasureConfig/config/config.xml b/App/WheelMeasure/WheelMeasureConfig/config/config.xml index 1a8c8ee..1fbdce0 100644 --- a/App/WheelMeasure/WheelMeasureConfig/config/config.xml +++ b/App/WheelMeasure/WheelMeasureConfig/config/config.xml @@ -8,7 +8,7 @@ - diff --git a/AppAlgo/wheelArchHeigthMeasure/Inc/wheelArchHeigthMeasure_Export.h b/AppAlgo/wheelArchHeigthMeasure/Inc/wheelArchHeigthMeasure_Export.h index add0cba..d2b8912 100644 --- a/AppAlgo/wheelArchHeigthMeasure/Inc/wheelArchHeigthMeasure_Export.h +++ b/AppAlgo/wheelArchHeigthMeasure/Inc/wheelArchHeigthMeasure_Export.h @@ -15,6 +15,7 @@ typedef struct SVzNL3DPoint downLine[2]; SVzNL3DPoint centerLine[2]; double archToCenterHeigth; + double archToGroundHeigth; }WD_wheelArchInfo; //汾 diff --git a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/baseAlgorithm.dll b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/baseAlgorithm.dll index ac8f6f0..554b2e6 100644 Binary files a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/baseAlgorithm.dll and b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/baseAlgorithm.dll differ diff --git a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/baseAlgorithm.pdb b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/baseAlgorithm.pdb index a56c4c9..6949361 100644 Binary files a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/baseAlgorithm.pdb and b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/baseAlgorithm.pdb differ diff --git a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/wheelArchHeigthMeasure.dll b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/wheelArchHeigthMeasure.dll index bdde987..d82f70a 100644 Binary files a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/wheelArchHeigthMeasure.dll and b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/wheelArchHeigthMeasure.dll differ diff --git a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/wheelArchHeigthMeasure.pdb b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/wheelArchHeigthMeasure.pdb index ed2cd4b..7ad9709 100644 Binary files a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/wheelArchHeigthMeasure.pdb and b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Debug/wheelArchHeigthMeasure.pdb differ diff --git a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/baseAlgorithm.dll b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/baseAlgorithm.dll index 68ca831..ba323d3 100644 Binary files a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/baseAlgorithm.dll and b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/baseAlgorithm.dll differ diff --git a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/baseAlgorithm.pdb b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/baseAlgorithm.pdb index f9aaa84..367770d 100644 Binary files a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/baseAlgorithm.pdb and b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/baseAlgorithm.pdb differ diff --git a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/wheelArchHeigthMeasure.dll b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/wheelArchHeigthMeasure.dll index bb7d363..5ccbe29 100644 Binary files a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/wheelArchHeigthMeasure.dll and b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/wheelArchHeigthMeasure.dll differ diff --git a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/wheelArchHeigthMeasure.pdb b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/wheelArchHeigthMeasure.pdb index e99f746..e4ce5d9 100644 Binary files a/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/wheelArchHeigthMeasure.pdb and b/AppAlgo/wheelArchHeigthMeasure/Windows/x64/Release/wheelArchHeigthMeasure.pdb differ diff --git a/AppUtils/AppCommon/Inc/BasePresenter.h b/AppUtils/AppCommon/Inc/BasePresenter.h index 374f7c4..e8a784f 100644 --- a/AppUtils/AppCommon/Inc/BasePresenter.h +++ b/AppUtils/AppCommon/Inc/BasePresenter.h @@ -509,6 +509,9 @@ protected: // ModbusTCP服务器 IYModbusTCPServer* m_modbusServer = nullptr; + // 初始化线程 + std::thread m_initThread; + private: // 启动ModbusTCP服务(在Init中调用) int StartModbusServer(int port = 502); diff --git a/AppUtils/AppCommon/Src/BasePresenter.cpp b/AppUtils/AppCommon/Src/BasePresenter.cpp index 9005105..954e994 100644 --- a/AppUtils/AppCommon/Src/BasePresenter.cpp +++ b/AppUtils/AppCommon/Src/BasePresenter.cpp @@ -22,8 +22,10 @@ BasePresenter::BasePresenter(QObject *parent) BasePresenter::~BasePresenter() { - // 停止ModbusTCP服务器 - StopModbusServer(); + // 等待初始化线程完成 + if (m_initThread.joinable()) { + m_initThread.join(); + } // 停止检测线程 StopAlgoDetectThread(); @@ -31,8 +33,12 @@ BasePresenter::~BasePresenter() // 停止重连定时器 StopCameraReconnectTimer(); + // 停止ModbusTCP服务器 + StopModbusServer(); + // 清理相机设备 - for (auto& camera : m_vrEyeDeviceList) { + for (size_t i = 0; i < m_vrEyeDeviceList.size(); ++i) { + auto& camera = m_vrEyeDeviceList[i]; if (camera.second) { camera.second->CloseDevice(); delete camera.second; @@ -46,24 +52,30 @@ int BasePresenter::Init() { LOG_INFO("BasePresenter::Init()\n"); - int nRet = SUCCESS; + // 在后台线程中执行初始化 + m_initThread = std::thread([this]() { + int nRet = InitApp(); + if (nRet != SUCCESS) { + LOG_ERROR("InitApp failed: %d\n", nRet); + return; + } - nRet = InitApp(); - ERR_CODE_RETURN(nRet); + // 初始化算法参数 + nRet = InitAlgoParams(); + LOG_INFO("Algorithm parameters initialization result: %d\n", nRet); - // 初始化算法参数 - nRet = InitAlgoParams(); - LOG_INFO("Algorithm parameters initialization result : %d\n", nRet); + // 启动ModbusTCP服务 + int modbusRet = StartModbusServer(5020); + if (modbusRet == SUCCESS) { + LOG_INFO("ModbusTCP server started on port 5020\n"); + } else { + LOG_WARNING("Failed to start ModbusTCP server\n"); + } - // 启动ModbusTCP服务 - int modbusRet = StartModbusServer(5020); - if (modbusRet == SUCCESS) { - LOG_INFO("ModbusTCP server started on port 502(0)\n"); - } else { - LOG_WARNING("Failed to start ModbusTCP server\n"); - } + SetWorkStatus(WorkStatus::Ready); + }); - return nRet; + return SUCCESS; } int BasePresenter::StartDetection(int cameraIndex, bool isAuto) @@ -462,9 +474,8 @@ void BasePresenter::StartAlgoDetectThread() m_bAlgoDetectThreadRunning = true; - // 启动检测线程 + // 启动检测线程(不再detach,使用joinable线程) m_algoDetectThread = std::thread(&BasePresenter::AlgoDetectThreadFunc, this); - m_algoDetectThread.detach(); // 分离线程,让其独立运行 LOG_INFO("[BasePresenter] 算法检测线程已启动\n"); } @@ -482,7 +493,10 @@ void BasePresenter::StopAlgoDetectThread() // 唤醒可能在等待的线程 m_algoDetectCondition.notify_all(); - // 注意:因为线程使用了 detach(),所以不需要 join() + // 等待线程退出 + if (m_algoDetectThread.joinable()) { + m_algoDetectThread.join(); + } LOG_INFO("[BasePresenter] 算法检测线程已停止\n"); } @@ -628,29 +642,23 @@ void BasePresenter::StartCameraReconnectTimer() // 使用QMetaObject::invokeMethod确保在正确的线程中操作定时器 QMetaObject::invokeMethod(this, [this]() { if (!m_pCameraReconnectTimer) { - LOG_ERROR("[BasePresenter] m_pCameraReconnectTimer is nullptr!\n"); return; } if (m_pCameraReconnectTimer->isActive()) { - LOG_DEBUG("[BasePresenter] 相机重连定时器已在运行\n"); return; } m_pCameraReconnectTimer->start(); - LOG_INFO("[BasePresenter] 启动相机重连定时器(间隔: %d ms)\n", m_pCameraReconnectTimer->interval()); }, Qt::QueuedConnection); } void BasePresenter::StopCameraReconnectTimer() { - // 使用QMetaObject::invokeMethod确保在正确的线程中操作定时器 - QMetaObject::invokeMethod(this, [this]() { - if (m_pCameraReconnectTimer && m_pCameraReconnectTimer->isActive()) { - m_pCameraReconnectTimer->stop(); - LOG_INFO("[BasePresenter] 停止相机重连定时器\n"); - } - }, Qt::QueuedConnection); + // 直接停止定时器(析构时需要立即停止,不能用QueuedConnection) + if (m_pCameraReconnectTimer) { + m_pCameraReconnectTimer->stop(); + } } // ============ OnCameraReconnectTimer 实现 ============ @@ -768,7 +776,6 @@ int BasePresenter::StartModbusServer(int port) void BasePresenter::StopModbusServer() { if (m_modbusServer) { - LOG_INFO("[BasePresenter] 停止ModbusTCP服务器\n"); m_modbusServer->stop(); delete m_modbusServer; m_modbusServer = nullptr; diff --git a/Device/VrEyeDevice/Src/VrEyeDevice.cpp b/Device/VrEyeDevice/Src/VrEyeDevice.cpp index d190655..6a8e18b 100644 --- a/Device/VrEyeDevice/Src/VrEyeDevice.cpp +++ b/Device/VrEyeDevice/Src/VrEyeDevice.cpp @@ -427,7 +427,11 @@ void CVrEyeDevice::DebugThreadFunc() // 固定的点云文件路径 // const std::string debugFilePath = "C:\\project\\QT\\GrabBag\\TestData\\workpiece\\new\\test\\9_LazerData_Hi229229.txt"; // const std::string debugFilePath = "C:\\project\\QT\\GrabBag\\TestData\\bag\\LaserLine1_grid.txt"; +#ifdef _WIN32 const std::string debugFilePath = "C:\\project\\QT\\GrabBag\\TestData\\eye_test.txt"; +#else + const std::string debugFilePath = "/home/linaro/eye_test.txt"; +#endif LaserDataLoader loader; std::vector> laserLines; @@ -438,24 +442,24 @@ void CVrEyeDevice::DebugThreadFunc() // 加载点云文件 int ret = loader.LoadLaserScanData(debugFilePath, laserLines, lineNum, scanSpeed, maxTimeStamp, clockPerSecond); - if (ret != SUCCESS) { + if (ret == SUCCESS) { LOG_DEBUG("VR_EYE_DEBUG_MODE: Failed to load laser data from %s, error: %s\n",debugFilePath.c_str(), loader.GetLastError().c_str()); - return; - } // 循环读取并回调 - auto iter = laserLines.begin(); + auto iter = laserLines.begin(); - while (iter != laserLines.end()) { - if (m_debugCallback) { - auto& dataPair = *iter; - m_debugCallback(dataPair.first, &dataPair.second, m_debugCallbackParam); + while (iter != laserLines.end()) { + if (m_debugCallback) { + auto& dataPair = *iter; + m_debugCallback(dataPair.first, &dataPair.second, m_debugCallbackParam); + } + + iter++; + // 模拟采集间隔,约30ms + std::this_thread::sleep_for(std::chrono::milliseconds(2)); } - - iter++; - // 模拟采集间隔,约30ms - std::this_thread::sleep_for(std::chrono::milliseconds(2)); } + if (m_debugStatusCallback) { m_debugStatusCallback(keDeviceWorkStatus_Device_Swing_Finish, nullptr, 0, m_debugStatusCallbackParam); m_debugStatusCallback(keDeviceWorkStatus_Device_Auto_Stop, nullptr, 0, m_debugStatusCallbackParam); diff --git a/Module/ModbusTCPServer/Src/ModbusTCPServer.cpp b/Module/ModbusTCPServer/Src/ModbusTCPServer.cpp index 17494e6..d08df67 100644 --- a/Module/ModbusTCPServer/Src/ModbusTCPServer.cpp +++ b/Module/ModbusTCPServer/Src/ModbusTCPServer.cpp @@ -95,8 +95,10 @@ void ModbusTCPServer::stop() return; } + LOG_INFO("[ModbusTCPServer] stop()\n"); + m_shouldStop.store(true); - + // 关闭服务器socket以中断accept调用 if (m_serverSocket != -1) { #ifdef _WIN32 @@ -135,57 +137,69 @@ void ModbusTCPServer::stop() void ModbusTCPServer::serverLoop() { fd_set readfds; - int maxfd = m_serverSocket; - + while (!m_shouldStop.load()) { + // 检查服务器socket是否有效 + int serverSock = m_serverSocket; + if (serverSock == -1) { + break; // socket已关闭,退出循环 + } + FD_ZERO(&readfds); - FD_SET(m_serverSocket, &readfds); - - maxfd = m_serverSocket; - + FD_SET(serverSock, &readfds); + + int maxfd = serverSock; + // 添加所有客户端socket到监听集合 { std::lock_guard lock(m_clientsMutex); for (const auto& pair : m_clients) { int clientSocket = pair.first; - FD_SET(clientSocket, &readfds); - if (clientSocket > maxfd) { - maxfd = clientSocket; + if (clientSocket >= 0) { + FD_SET(clientSocket, &readfds); + if (clientSocket > maxfd) { + maxfd = clientSocket; + } } } } - + // 设置超时时间(1秒),避免阻塞太久 struct timeval timeout; timeout.tv_sec = 1; timeout.tv_usec = 0; - + int selectResult = select(maxfd + 1, &readfds, nullptr, nullptr, &timeout); - + + // 再次检查是否应该停止 + if (m_shouldStop.load()) { + break; + } + if (selectResult == -1) { if (!m_shouldStop.load()) { LOG_WARNING("select error: %s\n", strerror(errno)); } break; } - + if (selectResult == 0) { // 超时,继续循环 continue; } - + // 检查服务器socket是否有新连接 - if (FD_ISSET(m_serverSocket, &readfds)) { + if (serverSock >= 0 && FD_ISSET(serverSock, &readfds)) { handleNewConnection(); } - + // 检查客户端socket是否有数据 std::vector socketsToRemove; { std::lock_guard lock(m_clientsMutex); for (const auto& pair : m_clients) { int clientSocket = pair.first; - if (FD_ISSET(clientSocket, &readfds)) { + if (clientSocket >= 0 && FD_ISSET(clientSocket, &readfds)) { try { handleClientData(pair.second); } catch (...) { @@ -195,7 +209,7 @@ void ModbusTCPServer::serverLoop() } } } - + // 删除有问题的客户端连接 for (int socket : socketsToRemove) { removeClient(socket);