#include "WheelMeasurePresenter.h" #include "PathManager.h" #include "VrLog.h" #include "VrError.h" // wheelArchHeigthMeasure SDK #include "wheelArchHeigthMeasure_Export.h" // PointCloudImageUtils #include "PointCloudImageUtils.h" #include #include #include #include #include #include WheelMeasurePresenter::WheelMeasurePresenter(QObject* parent) : BasePresenter(parent) { // 创建配置接口 IVrWheelMeasureConfig::CreateInstance(&m_config); LOG_INFO("ALGO_VERSION: %s \n", wd_wheelArchHeigthMeasureVersion()); // 设置TCP检测触发回调 m_tcpProtocol.SetDetectionTriggerCallback([this](int param) { return this->onTCPDetectionTriggered(param); }); } WheelMeasurePresenter::~WheelMeasurePresenter() { // 停止TCP服务器 m_tcpProtocol.Deinitialize(); // 清除状态回调,防止后续回调访问已销毁对象 m_statusUpdate = nullptr; // 停止检测 StopDetection(); // 处理待处理的 Qt 事件,确保 QueuedConnection 的回调不会访问已销毁对象 QCoreApplication::processEvents(); if (m_config) { delete m_config; m_config = nullptr; } } int WheelMeasurePresenter::InitApp() { LOG_INFO("WheelMeasurePresenter::InitApp() called\n"); SetWorkStatus(WorkStatus::InitIng); // 默认相机索引初始化为1(1-based) m_currentCameraIndex = 1; // 加载配置 QString configPath = PathManager::GetInstance().GetConfigFilePath(); if (!initializeConfig(configPath)) { LOG_ERROR("Failed to initialize config from: %s\n", configPath.toStdString().c_str()); if (m_statusUpdate) { m_statusUpdate->OnErrorOccurred("配置文件加载失败"); } return ERR_CODE(DEV_CONFIG_ERR); } // 传入扫描配置到基类 SetScanConfig(m_configResult.scanConfig); // 初始化相机 if (!initializeCameras()) { LOG_ERROR("Failed to initialize cameras\n"); if (m_statusUpdate) { m_statusUpdate->OnErrorOccurred("相机初始化失败"); } } // 初始化TCP服务器(从配置读取端口) int tcpPort = 5000; // 默认端口 if (!m_configResult.servers.empty()) { tcpPort = m_configResult.servers[0].port; } int tcpResult = m_tcpProtocol.Initialize(tcpPort); if (tcpResult != 0) { LOG_ERROR("Failed to initialize TCP server on port %d, error code: %d\n", tcpPort, tcpResult); if (m_statusUpdate) { m_statusUpdate->OnStatusUpdate(QString("TCP服务器启动失败,端口: %1").arg(tcpPort)); } } else { LOG_INFO("TCP server initialized successfully on port %d\n", tcpPort); if (m_statusUpdate) { m_statusUpdate->OnStatusUpdate(QString("TCP服务器已启动,端口: %1").arg(tcpPort)); } } LOG_INFO("WheelMeasurePresenter::InitApp() completed\n"); return SUCCESS; } int WheelMeasurePresenter::InitAlgoParams() { LOG_DEBUG("Initializing algorithm parameters\n"); // 算法参数已在配置加载时初始化 return SUCCESS; } int WheelMeasurePresenter::ProcessAlgoDetection(std::vector>& detectionDataCache) { LOG_INFO("ProcessAlgoDetection called, data lines: %zu\n", detectionDataCache.size()); // 处理检测数据 processScanData(detectionDataCache); return SUCCESS; } void WheelMeasurePresenter::OnCameraStatusChanged(int cameraIndex, bool isConnected) { LOG_INFO("Camera[%d] status changed: %s\n", cameraIndex, isConnected ? "connected" : "disconnected"); 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); } // 切换到主线程更新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)); } else { m_statusUpdate->OnCameraDisconnected(cameraName); m_statusUpdate->OnDeviceStatusChanged(cameraName, static_cast(DeviceStatus::Offline)); } }, Qt::QueuedConnection); } void WheelMeasurePresenter::OnWorkStatusChanged(WorkStatus status) { // 写入Modbus工作状态 (地址1) uint16_t statusValue = 0; switch (status) { case WorkStatus::InitIng: statusValue = 1; break; case WorkStatus::Ready: statusValue = 2; break; case WorkStatus::Working: statusValue = 3; break; case WorkStatus::Detecting: statusValue = 3; break; case WorkStatus::Completed: statusValue = 4; break; case WorkStatus::Error: statusValue = 5; break; default: statusValue = 0; break; } WriteModbusRegisters(1, &statusValue, 1); // 切换到主线程更新UI QMetaObject::invokeMethod(this, [this, status]() { if (m_statusUpdate) { m_statusUpdate->OnWorkStatusChanged(status); } }, Qt::QueuedConnection); // 扫描全部相机模式下检测完成:顺序模式推进下一个相机,批处理模式直接结束 if (status == WorkStatus::Completed && m_scanAllMode) { if (!m_scanAllList.empty()) { // 顺序模式:推进到下一个相机 m_scanAllIndex++; if (m_scanAllIndex < m_scanAllList.size()) { LOG_INFO("扫描全部相机:当前相机扫描完成,启动下一个相机 (%zu/%zu)\n", m_scanAllIndex + 1, m_scanAllList.size()); QMetaObject::invokeMethod(this, [this]() { startNextScanCamera(); }, Qt::QueuedConnection); return; } } // 顺序模式末尾 或 批处理模式所有批次完成:结束“扫描全部”流程 LOG_INFO("扫描全部相机:所有相机扫描完成\n"); bool bNeedSendTCP = m_isTCPTriggered; m_scanAllMode = false; m_isTCPTriggered = false; m_scanAllList.clear(); m_scanAllIndex = 0; if (bNeedSendTCP) { QMetaObject::invokeMethod(this, [this]() { sendTCPMeasureResults(); }, Qt::QueuedConnection); } } } void WheelMeasurePresenter::OnCameraCountChanged(int count) { 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()); // 切换到主线程更新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) 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) { if (!m_config) { LOG_ERROR("Config interface is null\n"); return false; } m_configResult = m_config->LoadConfig(configPath.toStdString()); // 设置调试参数到基类 SetDebugParam(m_configResult.debugParam); // 设置配置改变通知 m_config->SetConfigChangeNotify(this); LOG_INFO("Config loaded successfully, cameras: %zu\n", m_configResult.cameras.size()); return true; } bool WheelMeasurePresenter::initializeCameras() { // 转换相机配置为DeviceInfo列表 std::vector cameraList; for (const auto& cameraConfig : m_configResult.cameras) { if (!cameraConfig.enabled) { LOG_INFO("Camera %s is disabled, skipping\n", cameraConfig.name.c_str()); continue; } DeviceInfo deviceInfo; deviceInfo.index = cameraConfig.cameraIndex; deviceInfo.name = cameraConfig.name; deviceInfo.ip = cameraConfig.cameraIP; cameraList.push_back(deviceInfo); } // 调用基类InitCamera进行相机初始化(bRGB=false, bSwing=true) int result = InitCamera(cameraList, false, true); LOG_INFO("Camera initialization completed. Connected cameras: %zu\n", m_vrEyeDeviceList.size()); return result == SUCCESS; } QStringList WheelMeasurePresenter::getCameraNames() const { QStringList names; for (const auto& camera : m_vrEyeDeviceList) { names.append(QString::fromStdString(camera.first)); } return names; } QString WheelMeasurePresenter::GetAlgoVersion() const { const char* version = wd_wheelArchHeigthMeasureVersion(); return version ? QString::fromUtf8(version) : QString(); } void WheelMeasurePresenter::ResetDetect(int cameraIndex) { StopDetection(); // 清掉残留的“扫描全部相机”状态,避免之前 btn_start / TCP 触发被打断后 // 单设备完成时 OnWorkStatusChanged 误转发到下一个相机 if (m_scanAllMode || !m_scanAllList.empty()) { LOG_INFO("ResetDetect: 清掉残留的扫描全部状态 (mode=%d, listSize=%zu, idx=%zu)\n", m_scanAllMode ? 1 : 0, m_scanAllList.size(), m_scanAllIndex); } m_scanAllMode = false; m_isTCPTriggered = false; m_scanAllList.clear(); m_scanAllIndex = 0; // 设置当前相机索引(从0-based转换为1-based) m_currentCameraIndex = cameraIndex + 1; LOG_INFO("ResetDetect: cameraIndex=%d, m_currentCameraIndex=%d\n", cameraIndex, m_currentCameraIndex); // 设置工作状态为检测中 SetWorkStatus(WorkStatus::Working); // 清空数据 ClearDetectionDataCache(); // 注意:不调用 OnClearMeasureData(),因为 mainwindow 中的 onDeviceClicked // 已经调用了 clearDeviceResult(deviceName) 来清除指定设备的结果 // 重新开始检测(BasePresenter::StartDetection 期望 1-based 索引) StartDetection(m_currentCameraIndex); } void WheelMeasurePresenter::StartAllDetection() { LOG_INFO("Starting all cameras detection (button trigger)\n"); // 与TCP协议触发相同的全相机调度逻辑,但不发送TCP响应 startScanAllCameras(false); } void WheelMeasurePresenter::StopAllDetection() { LOG_INFO("Stop all detection requested\n"); StopDetection(); } void WheelMeasurePresenter::OnConfigChanged(const WheelMeasureConfigResult& configResult) { LOG_INFO("Config changed notification received\n"); m_configResult = configResult; // 更新基类扫描配置和调试参数 SetScanConfig(m_configResult.scanConfig); SetDebugParam(m_configResult.debugParam); emit configUpdated(); } void WheelMeasurePresenter::processScanData(std::vector>& detectionDataCache) { LOG_INFO("Processing scan data, lines: %zu\n", detectionDataCache.size()); // 打印扫描数据统计信息 int totalPoints = 0; for (const auto& linePair : detectionDataCache) { totalPoints += linePair.second.nPointCount; } LOG_INFO("Scan data statistics: %zu lines, %d total points\n", detectionDataCache.size(), totalPoints); // 打印调平参数 LOG_INFO("========== PlaneCalib Parameters ==========\n"); if (!m_configResult.planeCalibParams.empty()) { for (const auto& calibParam : m_configResult.planeCalibParams) { LOG_INFO("Camera[%d] %s: isCalibrated=%d, planeHeight=%.2f\n", calibParam.cameraIndex, calibParam.cameraName.c_str(), calibParam.isCalibrated ? 1 : 0, calibParam.planeHeight); LOG_INFO(" planeCalib: [%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f]\n", calibParam.planeCalib[0], calibParam.planeCalib[1], calibParam.planeCalib[2], calibParam.planeCalib[3], calibParam.planeCalib[4], calibParam.planeCalib[5], calibParam.planeCalib[6], calibParam.planeCalib[7], calibParam.planeCalib[8]); LOG_INFO(" invRMatrix: [%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f]\n", calibParam.invRMatrix[0], calibParam.invRMatrix[1], calibParam.invRMatrix[2], calibParam.invRMatrix[3], calibParam.invRMatrix[4], calibParam.invRMatrix[5], calibParam.invRMatrix[6], calibParam.invRMatrix[7], calibParam.invRMatrix[8]); } } else { LOG_INFO("No PlaneCalib parameters loaded!\n"); } // 打印算法参数 LOG_INFO("========== Algorithm Parameters ==========\n"); LOG_INFO("CornerParam: minEndingGap=%.2f, minEndingGap_z=%.2f, scale=%.2f, cornerTh=%.2f, jumpCornerTh_1=%.2f, jumpCornerTh_2=%.2f\n", m_configResult.algorithmParams.cornerParam.minEndingGap, m_configResult.algorithmParams.cornerParam.minEndingGap_z, m_configResult.algorithmParams.cornerParam.scale, m_configResult.algorithmParams.cornerParam.cornerTh, m_configResult.algorithmParams.cornerParam.jumpCornerTh_1, m_configResult.algorithmParams.cornerParam.jumpCornerTh_2); LOG_INFO("LineSegParam: segGapTh_y=%.2f, segGapTh_z=%.2f, maxDist=%.2f\n", m_configResult.algorithmParams.lineSegParam.segGapTh_y, m_configResult.algorithmParams.lineSegParam.segGapTh_z, m_configResult.algorithmParams.lineSegParam.maxDist); LOG_INFO("OutlierFilterParam: continuityTh=%.2f, outlierTh=%.2f\n", m_configResult.algorithmParams.filterParam.continuityTh, m_configResult.algorithmParams.filterParam.outlierTh); LOG_INFO("TreeGrowParam: yDeviation_max=%.2f, zDeviation_max=%.2f, maxLineSkipNum=%d, maxSkipDistance=%.2f, minLTypeTreeLen=%.2f, minVTypeTreeLen=%.2f\n", m_configResult.algorithmParams.growParam.yDeviation_max, m_configResult.algorithmParams.growParam.zDeviation_max, m_configResult.algorithmParams.growParam.maxLineSkipNum, m_configResult.algorithmParams.growParam.maxSkipDistance, m_configResult.algorithmParams.growParam.minLTypeTreeLen, m_configResult.algorithmParams.growParam.minVTypeTreeLen); LOG_INFO("DebugParam: enableDebug=%d, saveDebugImage=%d, printDetailLog=%d\n", m_configResult.debugParam.enableDebug, m_configResult.debugParam.saveDebugImage, m_configResult.debugParam.printDetailLog); LOG_INFO("============================================\n"); // ========== 转换检测数据为SDK所需格式(两种模式通用) ========== std::vector> scanLines; size_t validPointCount = 0; int convertResult = m_dataLoader.ConvertToSVzNL3DPosition(detectionDataCache, scanLines, &validPointCount); // 检查数据有效性 if (convertResult != SUCCESS || scanLines.empty()) { LOG_WARNING("Failed to convert data to XYZ format or no XYZ data available\n"); return; } // 检查线束数量和有效点数量 const size_t MIN_SCAN_LINES = 200; const size_t MIN_VALID_POINTS = 1000; if (scanLines.size() < MIN_SCAN_LINES || validPointCount < MIN_VALID_POINTS) { LOG_ERROR("Insufficient data for detection: scan lines=%zu (min=%zu), valid points=%zu (min=%zu)\n", scanLines.size(), MIN_SCAN_LINES, validPointCount, MIN_VALID_POINTS); if (m_statusUpdate) { QString errorMsg = QString("数据不足: 线束=%1(最少%2), 有效点=%3(最少%4)") .arg(scanLines.size()).arg(MIN_SCAN_LINES) .arg(validPointCount).arg(MIN_VALID_POINTS); m_statusUpdate->OnStatusUpdate(errorMsg); m_statusUpdate->OnErrorOccurred(errorMsg); } SetWorkStatus(WorkStatus::Error); return; } LOG_INFO("Data validation passed: scan lines=%zu, valid points=%zu, detectMode=%s\n", scanLines.size(), validPointCount, (m_detectMode == WheelDetectMode::Correction) ? "Correction" : "Wheel"); // 按当前检测模式分发 if (m_detectMode == WheelDetectMode::Correction) { processCorrectionDetection(scanLines); } else { processWheelDetection(scanLines); } } void WheelMeasurePresenter::processWheelDetection(std::vector>& scanLines) { LOG_INFO("========== Wheel Detection ==========\n"); // ========== 调用 wheelArchHeigthMeasure SDK ========== // 准备算法参数 SSG_cornerParam cornerParam; cornerParam.minEndingGap = m_configResult.algorithmParams.cornerParam.minEndingGap; cornerParam.minEndingGap_z = m_configResult.algorithmParams.cornerParam.minEndingGap_z; cornerParam.scale = m_configResult.algorithmParams.cornerParam.scale; cornerParam.cornerTh = m_configResult.algorithmParams.cornerParam.cornerTh; cornerParam.jumpCornerTh_1 = m_configResult.algorithmParams.cornerParam.jumpCornerTh_1; cornerParam.jumpCornerTh_2 = m_configResult.algorithmParams.cornerParam.jumpCornerTh_2; SSG_lineSegParam lineSegParam; lineSegParam.segGapTh_y = m_configResult.algorithmParams.lineSegParam.segGapTh_y; lineSegParam.segGapTh_z = m_configResult.algorithmParams.lineSegParam.segGapTh_z; lineSegParam.distScale = m_configResult.algorithmParams.lineSegParam.maxDist; SSG_outlierFilterParam filterParam; filterParam.continuityTh = m_configResult.algorithmParams.filterParam.continuityTh; filterParam.outlierTh = m_configResult.algorithmParams.filterParam.outlierTh; SSG_treeGrowParam growParam; growParam.yDeviation_max = m_configResult.algorithmParams.growParam.yDeviation_max; growParam.zDeviation_max = m_configResult.algorithmParams.growParam.zDeviation_max; growParam.maxLineSkipNum = m_configResult.algorithmParams.growParam.maxLineSkipNum; growParam.maxSkipDistance = m_configResult.algorithmParams.growParam.maxSkipDistance; growParam.minLTypeTreeLen = m_configResult.algorithmParams.growParam.minLTypeTreeLen; growParam.minVTypeTreeLen = m_configResult.algorithmParams.growParam.minVTypeTreeLen; // 3. 查找当前相机的调平参数(用于后续的ROI检测和调平处理) WheelCameraPlaneCalibParam* calibParam = getPlaneCalibParam(m_currentCameraIndex); // 3.5. 调用轮胎存在检测(使用ROI范围过滤)- 在调平处理之前 SVzNL3DRangeD wheelRoi3d; if (calibParam) { // 使用当前相机配置的ROI范围 wheelRoi3d.xRange.min = calibParam->wheelRoi3d_xMin; wheelRoi3d.xRange.max = calibParam->wheelRoi3d_xMax; wheelRoi3d.yRange.min = calibParam->wheelRoi3d_yMin; wheelRoi3d.yRange.max = calibParam->wheelRoi3d_yMax; wheelRoi3d.zRange.min = calibParam->wheelRoi3d_zMin; wheelRoi3d.zRange.max = calibParam->wheelRoi3d_zMax; LOG_INFO("Using ROI range for wheel presence detection:\n"); LOG_INFO(" X: [%.1f, %.1f], Y: [%.1f, %.1f], Z: [%.1f, %.1f]\n", wheelRoi3d.xRange.min, wheelRoi3d.xRange.max, wheelRoi3d.yRange.min, wheelRoi3d.yRange.max, wheelRoi3d.zRange.min, wheelRoi3d.zRange.max); } else { // 使用默认ROI范围 wheelRoi3d.xRange.min = -1000.0; wheelRoi3d.xRange.max = 1000.0; wheelRoi3d.yRange.min = -1000.0; wheelRoi3d.yRange.max = 1000.0; wheelRoi3d.zRange.min = -1000.0; wheelRoi3d.zRange.max = 1000.0; LOG_INFO("Using default ROI range for wheel presence detection: ±1000.0 mm\n"); } // 调用轮胎存在检测 bool wheelPresent = wd_wheelPresenseDetection(scanLines, wheelRoi3d); LOG_INFO("Wheel presence detection result: %s\n", wheelPresent ? "PRESENT" : "NOT PRESENT"); if (!wheelPresent) { LOG_WARNING("No wheel detected in ROI range, skipping measurement\n"); if (m_statusUpdate) { m_statusUpdate->OnStatusUpdate(QString("未检测到轮胎,请检查ROI范围配置")); } // 如果是TCP触发的检测,缓存401错误结果 if (m_isTCPTriggered) { WheelMeasureTCPProtocol::CameraMeasureResult tcpResult; tcpResult.cameraId = m_currentCameraIndex; tcpResult.errorCode = 401; // 工件为空 tcpResult.centerDistance = 0.0; tcpResult.archDistance = 0.0; m_tcpResults[m_currentCameraIndex] = tcpResult; LOG_INFO("TCP检测结果已缓存: 相机%d, 错误码=401 (未检测到轮胎)\n", m_currentCameraIndex); } return; } // 4. 准备调平参数并执行调平处理(使用当前相机的调平参数) SSG_planeCalibPara groundCalibPara; memset(&groundCalibPara, 0, sizeof(groundCalibPara)); // 初始化为单位矩阵 groundCalibPara.planeCalib[0] = 1.0; groundCalibPara.planeCalib[4] = 1.0; groundCalibPara.planeCalib[8] = 1.0; groundCalibPara.invRMatrix[0] = 1.0; groundCalibPara.invRMatrix[4] = 1.0; groundCalibPara.invRMatrix[8] = 1.0; groundCalibPara.planeHeight = 0.0; if (calibParam && calibParam->isCalibrated) { for (int i = 0; i < 9; ++i) { groundCalibPara.planeCalib[i] = calibParam->planeCalib[i]; groundCalibPara.invRMatrix[i] = calibParam->invRMatrix[i]; } groundCalibPara.planeHeight = calibParam->planeHeight; 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, -1); } } } else { LOG_WARN("No calibration data for camera %d, using default parameters\n", m_currentCameraIndex); } // 5. 调用算法 int errCode = 0; LOG_INFO("Calling wd_wheelArchHeigthMeasure...\n"); WD_wheelArchInfo wheelArchResult = wd_wheelArchHeigthMeasure( scanLines, cornerParam, lineSegParam, filterParam, growParam, groundCalibPara, &errCode); LOG_INFO("Algorithm completed with errCode=%d\n", errCode); // 5. 处理算法结果 WheelMeasureResult result; result.cameraName = getCurrentCameraName(); result.aliasName = result.cameraName; result.bResultValid = (errCode == 0); if (errCode == 0) { // 应用当前相机的修正系数(默认1.0) double correctionFactor = (calibParam && calibParam->correctionFactor > 0.0) ? calibParam->correctionFactor : 1.0; wheelArchResult.archToCenterHeigth *= correctionFactor; wheelArchResult.archToGroundHeigth *= correctionFactor; LOG_INFO("Applied correctionFactor=%.4f for camera %d\n", correctionFactor, m_currentCameraIndex); LOG_INFO("========== Wheel Arch Measurement Result ==========\n"); LOG_INFO("wheelArchPos: (%.3f, %.3f, %.3f)\n", wheelArchResult.wheelArchPos.x, wheelArchResult.wheelArchPos.y, wheelArchResult.wheelArchPos.z); LOG_INFO("wheelUpPos: (%.3f, %.3f, %.3f)\n", wheelArchResult.wheelUpPos.x, wheelArchResult.wheelUpPos.y, wheelArchResult.wheelUpPos.z); LOG_INFO("wheelDownPos: (%.3f, %.3f, %.3f)\n", wheelArchResult.wheelDownPos.x, wheelArchResult.wheelDownPos.y, wheelArchResult.wheelDownPos.z); LOG_INFO("archToCenterHeigth: %.3f archToGroundHeigth: %.3f mm\n", wheelArchResult.archToCenterHeigth, wheelArchResult.archToGroundHeigth); LOG_INFO("==================================================\n"); if (m_statusUpdate) { QString statusMsg = QString("轮眉高度: %1 mm, 到地面高度: %2 mm") .arg(wheelArchResult.archToCenterHeigth, 0, 'f', 3) .arg(wheelArchResult.archToGroundHeigth, 0, 'f', 3); m_statusUpdate->OnStatusUpdate(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; measureData.wheelUpPosX = wheelArchResult.wheelUpPos.x; measureData.wheelUpPosY = wheelArchResult.wheelUpPos.y; measureData.wheelUpPosZ = wheelArchResult.wheelUpPos.z; measureData.wheelDownPosX = wheelArchResult.wheelDownPos.x; measureData.wheelDownPosY = wheelArchResult.wheelDownPos.y; measureData.wheelDownPosZ = wheelArchResult.wheelDownPos.z; measureData.timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); result.result.push_back(measureData); // 使用 PointCloudImageUtils 生成带检测结果的图像 result.image = PointCloudImageUtils::GenerateWheelArchImage( scanLines, wheelArchResult.wheelArchPos, wheelArchResult.wheelUpPos, wheelArchResult.wheelDownPos, wheelArchResult.archToCenterHeigth, true); } else { LOG_ERROR("Algorithm failed with errCode=%d\n", errCode); if (m_statusUpdate) { m_statusUpdate->OnStatusUpdate(QString("算法检测失败,错误码: %1").arg(errCode)); } // 生成仅有点云的图像(无检测结果) SVzNL3DPoint emptyPoint = {0.0, 0.0, 0.0}; result.image = PointCloudImageUtils::GenerateWheelArchImage( scanLines, emptyPoint, emptyPoint, emptyPoint, 0.0, false); } result.bImageValid = !result.image.isNull(); // ========== 写入检测结果到 Modbus (地址2-25) ========== // 辅助lambda: float转两个uint16_t (大端模式) auto floatToUint16 = [](float value, uint16_t& high, uint16_t& low) { uint32_t bits; memcpy(&bits, &value, sizeof(float)); high = static_cast((bits >> 16) & 0xFFFF); low = static_cast(bits & 0xFFFF); }; uint16_t modbusData[24]; memset(modbusData, 0, sizeof(modbusData)); // 地址2: 设备序号 modbusData[0] = static_cast(m_currentCameraIndex); // 地址3: 结果有效 modbusData[1] = (errCode == 0) ? 1 : 0; if (errCode == 0) { // 地址4-5: 轮眉高度(到中心) floatToUint16(static_cast(wheelArchResult.archToCenterHeigth), modbusData[2], modbusData[3]); // 地址6-7: 轮眉X floatToUint16(static_cast(wheelArchResult.wheelArchPos.x), modbusData[4], modbusData[5]); // 地址8-9: 轮眉Y floatToUint16(static_cast(wheelArchResult.wheelArchPos.y), modbusData[6], modbusData[7]); // 地址10-11: 轮眉Z floatToUint16(static_cast(wheelArchResult.wheelArchPos.z), modbusData[8], modbusData[9]); // 地址12-13: 上点X floatToUint16(static_cast(wheelArchResult.wheelUpPos.x), modbusData[10], modbusData[11]); // 地址14-15: 上点Y floatToUint16(static_cast(wheelArchResult.wheelUpPos.y), modbusData[12], modbusData[13]); // 地址16-17: 上点Z floatToUint16(static_cast(wheelArchResult.wheelUpPos.z), modbusData[14], modbusData[15]); // 地址18-19: 下点X floatToUint16(static_cast(wheelArchResult.wheelDownPos.x), modbusData[16], modbusData[17]); // 地址20-21: 下点Y floatToUint16(static_cast(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, 24); LOG_INFO("Modbus: 写入检测结果到地址2-25, 设备=%d, 有效=%d\n", m_currentCameraIndex, modbusData[1]); // 检测完成后清零"检测控制"(地址0) uint16_t zero = 0; WriteModbusRegisters(0, &zero, 1); // 直接调用回调,不使用信号槽 if (m_statusUpdate) { m_statusUpdate->OnMeasureResult(result); } // 如果是TCP触发的检测,缓存结果 if (m_isTCPTriggered) { WheelMeasureTCPProtocol::CameraMeasureResult tcpResult; tcpResult.cameraId = m_currentCameraIndex; if (errCode == 0) { tcpResult.errorCode = 0; tcpResult.centerDistance = wheelArchResult.archToCenterHeigth; tcpResult.archDistance = wheelArchResult.archToGroundHeigth; } else { // 根据错误码映射 if (errCode == -1 || errCode == -2) { tcpResult.errorCode = 401; // 工件为空 } else { tcpResult.errorCode = 400; // 扫描/匹配失败 } } m_tcpResults[m_currentCameraIndex] = tcpResult; LOG_INFO("TCP检测结果已缓存: 相机%d, 错误码=%d\n", tcpResult.cameraId, tcpResult.errorCode); } } void WheelMeasurePresenter::processCorrectionDetection(std::vector>& scanLines) { LOG_INFO("========== Correction (Calibration Block) Detection ==========\n"); LOG_INFO("nominalLength=%.3f, yRange=[%.3f, %.3f]\n", m_configResult.correctionNominalLength, m_configResult.correctionYMin, m_configResult.correctionYMax); // 准备算法参数(仅用到 filterParam / growParam) SSG_outlierFilterParam filterParam; filterParam.continuityTh = m_configResult.algorithmParams.filterParam.continuityTh; filterParam.outlierTh = m_configResult.algorithmParams.filterParam.outlierTh; SSG_treeGrowParam growParam; growParam.yDeviation_max = m_configResult.algorithmParams.growParam.yDeviation_max; growParam.zDeviation_max = m_configResult.algorithmParams.growParam.zDeviation_max; growParam.maxLineSkipNum = m_configResult.algorithmParams.growParam.maxLineSkipNum; growParam.maxSkipDistance = m_configResult.algorithmParams.growParam.maxSkipDistance; growParam.minLTypeTreeLen = m_configResult.algorithmParams.growParam.minLTypeTreeLen; growParam.minVTypeTreeLen = m_configResult.algorithmParams.growParam.minVTypeTreeLen; SVzNLRangeD yRange; yRange.min = m_configResult.correctionYMin; yRange.max = m_configResult.correctionYMax; // 调用算法 int errCode = 0; double length = 0.0; #if _OUTPUT_DEBUG_DATA std::vector> debugData; // 算法标记类型的点,用于可视化 length = wd_getCalibrationBlockLength( scanLines, m_configResult.correctionNominalLength, filterParam, growParam, yRange, debugData, &errCode); #else length = wd_getCalibrationBlockLength( scanLines, m_configResult.correctionNominalLength, filterParam, growParam, yRange, &errCode); #endif LOG_INFO("wd_getCalibrationBlockLength: length=%.3f, errCode=%d\n", length, errCode); bool resultValid = (errCode == 0 && length > 0.0); // 应用当前相机的修正系数(默认1.0,与轮毂检测保持一致) WheelCameraPlaneCalibParam* calibParam = getPlaneCalibParam(m_currentCameraIndex); double correctionFactor = (calibParam && calibParam->correctionFactor > 0.0) ? calibParam->correctionFactor : 1.0; double rawLength = length; if (resultValid) { length *= correctionFactor; LOG_INFO("Applied correctionFactor=%.8f for camera %d: raw=%.3f -> corrected=%.3f\n", correctionFactor, m_currentCameraIndex, rawLength, length); } // 构造结果 WheelMeasureResult result; result.cameraName = getCurrentCameraName(); result.aliasName = result.cameraName; result.bResultValid = resultValid; if (resultValid) { if (m_statusUpdate) { QString statusMsg = QString("工装长度: %1 mm").arg(length, 0, 'f', 3); m_statusUpdate->OnStatusUpdate(statusMsg); } WheelMeasureData measureData; measureData.calibrationBlockLength = length; measureData.timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); result.result.push_back(measureData); } else { LOG_ERROR("Correction algorithm failed: errCode=%d, length=%.3f\n", errCode, rawLength); if (m_statusUpdate) { m_statusUpdate->OnStatusUpdate(QString("工装检测失败,错误码: %1").arg(errCode)); } } // 生成点云图像 + 叠加算法 debugData 标记点(按 nPointIdx 着色) { PointCloudCanvas canvas = PointCloudCanvas::Create(scanLines); if (canvas.isValid()) { #if _OUTPUT_DEBUG_DATA // 着色规则参考 wheelArchHeigthMeasure_test.cpp::_outputScanDataFile_RGBD // 工装检测调用 wd_getCalibrationBlockLength 时使用的同一渲染函数 // 性能优化: // 1) 共用一个 QPainter,避免每点 new/destroy(原 drawPoint 的最大瓶颈) // 2) 按 nPointIdx 分桶,每桶只 setPen/setBrush 一次 // 3) 灰色背景点 (其他/数量最多) 用 drawPoints 像素级输出 // 4) 关闭抗锯齿 QImage& img = canvas.image(); const int imgW = img.width(); const int imgH = img.height(); // 预投影并按类型分桶 std::vector bucket[6]; // 0/1/2/3/4/5;>5 计入 0 size_t totalDbgPts = 0; for (const auto& line : debugData) { for (const auto& pt : line) { QPoint p = canvas.project(pt.pt3D.x, pt.pt3D.y); if (p.x() < 0 || p.x() >= imgW || p.y() < 0 || p.y() >= imgH) continue; int t = pt.nPointIdx; if (t < 1 || t > 5) t = 0; bucket[t].push_back(p); ++totalDbgPts; } } QPainter painter(&img); // 不开启抗锯齿(大批量小点 AA 代价显著) auto drawEllipseBucket = [&painter](const std::vector& pts, const QColor& color, int size) { if (pts.empty()) return; painter.setPen(Qt::NoPen); painter.setBrush(color); const int half = size / 2; for (const QPoint& p : pts) { painter.drawEllipse(p.x() - half, p.y() - half, size, size); } }; const double kAlpha = 0.8; const QColor c1(int(0 * kAlpha), int(255 * kAlpha), int(0 * kAlpha)); const QColor c2(int(255 * kAlpha), int(0 * kAlpha), int(0 * kAlpha)); const QColor c3(0, 0, 250); const QColor c45(255, 255, 0); const QColor c0(100, 100, 100); // 灰色背景点(通常最多)用 drawPoints 单像素,速度最快 if (!bucket[0].empty()) { painter.setPen(QPen(c0, 1)); painter.drawPoints(bucket[0].data(), static_cast(bucket[0].size())); } drawEllipseBucket(bucket[1], c1, 2); drawEllipseBucket(bucket[2], c2, 4); drawEllipseBucket(bucket[3], c3, 3); // 4/5 同色合并 if (!bucket[4].empty() || !bucket[5].empty()) { std::vector merged; merged.reserve(bucket[4].size() + bucket[5].size()); merged.insert(merged.end(), bucket[4].begin(), bucket[4].end()); merged.insert(merged.end(), bucket[5].begin(), bucket[5].end()); drawEllipseBucket(merged, c45, 3); } painter.end(); LOG_INFO("Correction debugData rendered: %zu lines, %zu points (buckets 0/1/2/3/4/5 = %zu/%zu/%zu/%zu/%zu/%zu)\n", debugData.size(), totalDbgPts, bucket[0].size(), bucket[1].size(), bucket[2].size(), bucket[3].size(), bucket[4].size(), bucket[5].size()); #endif result.image = canvas.image(); } else { // 画布无效则退化为原始点云图 SVzNL3DPoint emptyPoint = {0.0, 0.0, 0.0}; result.image = PointCloudImageUtils::GenerateWheelArchImage( scanLines, emptyPoint, emptyPoint, emptyPoint, 0.0, false); } } result.bImageValid = !result.image.isNull(); // ========== 工装检测的 Modbus 写入 ========== // 地址2=设备序号, 地址3=结果有效, 地址4-5=工装长度(float),其余清零 auto floatToUint16 = [](float value, uint16_t& high, uint16_t& low) { uint32_t bits; memcpy(&bits, &value, sizeof(float)); high = static_cast((bits >> 16) & 0xFFFF); low = static_cast(bits & 0xFFFF); }; uint16_t modbusData[24]; memset(modbusData, 0, sizeof(modbusData)); modbusData[0] = static_cast(m_currentCameraIndex); modbusData[1] = resultValid ? 1 : 0; if (resultValid) { floatToUint16(static_cast(length), modbusData[2], modbusData[3]); } WriteModbusRegisters(2, modbusData, 24); LOG_INFO("Modbus(工装): 写入到地址2-25, 设备=%d, 有效=%d, 长度=%.3f\n", m_currentCameraIndex, modbusData[1], length); // 清零“检测控制”(地址0) uint16_t zero = 0; WriteModbusRegisters(0, &zero, 1); if (m_statusUpdate) { m_statusUpdate->OnMeasureResult(result); } // 工装模式不参与 TCP 协议响应(协议触发已强制切回轮毂模式,此处不会被命中) } QString WheelMeasurePresenter::getCurrentCameraName() const { QString cameraName; int enabledIndex = 0; for (const auto& cameraConfig : m_configResult.cameras) { if (cameraConfig.enabled) { enabledIndex++; if (enabledIndex == m_currentCameraIndex) { cameraName = QString::fromStdString(cameraConfig.name); break; } } } if (cameraName.isEmpty() && !m_vrEyeDeviceList.empty()) { cameraName = QString::fromStdString(m_vrEyeDeviceList[0].first); } if (cameraName.isEmpty()) { cameraName = QString("Camera%1").arg(m_currentCameraIndex); } return cameraName; } WheelCameraPlaneCalibParam* WheelMeasurePresenter::getPlaneCalibParam(int cameraIndex) { for (auto& param : m_configResult.planeCalibParams) { if (param.cameraIndex == cameraIndex) { return ¶m; } } return nullptr; } // ============ ICameraLevelCalculator 接口实现 ============ bool WheelMeasurePresenter::CalculatePlaneCalibration( const std::vector>& scanData, double planeCalib[9], double& planeHeight, double invRMatrix[9]) { LOG_INFO("CalculatePlaneCalibration called, scan lines: %zu\n", scanData.size()); // TODO: 调用调平算法库计算调平参数 // 暂时返回单位矩阵 planeCalib[0] = 1.0; planeCalib[1] = 0.0; planeCalib[2] = 0.0; planeCalib[3] = 0.0; planeCalib[4] = 1.0; planeCalib[5] = 0.0; planeCalib[6] = 0.0; planeCalib[7] = 0.0; planeCalib[8] = 1.0; invRMatrix[0] = 1.0; invRMatrix[1] = 0.0; invRMatrix[2] = 0.0; invRMatrix[3] = 0.0; invRMatrix[4] = 1.0; invRMatrix[5] = 0.0; invRMatrix[6] = 0.0; invRMatrix[7] = 0.0; invRMatrix[8] = 1.0; planeHeight = 0.0; return true; } // ============ ICameraLevelResultSaver 接口实现 ============ bool WheelMeasurePresenter::SaveLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9], int cameraIndex, const QString& cameraName) { LOG_INFO("SaveLevelingResults: Camera[%d] %s, planeHeight=%.2f\n", cameraIndex, cameraName.toStdString().c_str(), planeHeight); // 查找或创建调平参数 WheelCameraPlaneCalibParam* param = getPlaneCalibParam(cameraIndex); if (!param) { WheelCameraPlaneCalibParam newParam; newParam.cameraIndex = cameraIndex; newParam.cameraName = cameraName.toStdString(); m_configResult.planeCalibParams.push_back(newParam); param = &m_configResult.planeCalibParams.back(); } // 更新调平参数 param->isCalibrated = true; param->planeHeight = planeHeight; for (int i = 0; i < 9; ++i) { param->planeCalib[i] = planeCalib[i]; param->invRMatrix[i] = invRMatrix[i]; } // 保存配置到文件 QString configPath = PathManager::GetInstance().GetConfigFilePath(); bool success = m_config->SaveConfig(configPath.toStdString(), m_configResult); if (success) { LOG_INFO("Leveling results saved successfully\n"); } else { LOG_ERROR("Failed to save leveling results\n"); } return success; } bool WheelMeasurePresenter::LoadLevelingResults(int cameraIndex, const QString& cameraName, double planeCalib[9], double& planeHeight, double invRMatrix[9]) { LOG_INFO("LoadLevelingResults: Camera[%d] %s\n", cameraIndex, cameraName.toStdString().c_str()); WheelCameraPlaneCalibParam* param = getPlaneCalibParam(cameraIndex); if (!param || !param->isCalibrated) { LOG_WARN("No calibration data found for camera %d\n", cameraIndex); return false; } planeHeight = param->planeHeight; for (int i = 0; i < 9; ++i) { planeCalib[i] = param->planeCalib[i]; invRMatrix[i] = param->invRMatrix[i]; } LOG_INFO("Leveling results loaded: planeHeight=%.2f\n", planeHeight); return true; } // ============ 静态相机状态回调函数 ============ void WheelMeasurePresenter::_StaticCameraNotify(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam) { // 从pInfoParam获取this指针,转换回WheelMeasurePresenter*类型 WheelMeasurePresenter* pThis = reinterpret_cast(pInfoParam); if (pThis) { // 调用实例的非静态成员函数 pThis->_CameraNotify(eStatus, pExtData, nDataLength, pInfoParam); } } void WheelMeasurePresenter::_CameraNotify(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam) { LOG_DEBUG("[Camera Notify] received: status=%d\n", (int)eStatus); switch (eStatus) { case EVzDeviceWorkStatus::keDeviceWorkStatus_Offline: { LOG_WARNING("[Camera Notify] Camera device offline/disconnected\n"); // 通知UI相机状态变更 if (m_statusUpdate) { m_statusUpdate->OnCameraDisconnected(QString("Camera")); m_statusUpdate->OnStatusUpdate(QString("相机设备离线")); } break; } case EVzDeviceWorkStatus::keDeviceWorkStatus_Eye_Reconnect: { LOG_INFO("[Camera Notify] Camera device reconnecting\n"); if (m_statusUpdate) { m_statusUpdate->OnStatusUpdate(QString("相机设备重连中...")); } break; } case EVzDeviceWorkStatus::keDeviceWorkStatus_Eye_Comming: { LOG_INFO("[Camera Notify] Camera device connected\n"); if (m_statusUpdate) { m_statusUpdate->OnCameraConnected(QString("Camera")); m_statusUpdate->OnStatusUpdate(QString("相机设备已连接")); } break; } default: LOG_DEBUG("[Camera Notify] Unhandled status: %d\n", (int)eStatus); break; } } // ============ Modbus写寄存器回调处理 ============ void WheelMeasurePresenter::OnModbusWriteCallback(uint16_t startAddress, const uint16_t* data, uint16_t count) { LOG_INFO("OnModbusWriteCallback: address=%d, count=%d\n", startAddress, count); if (!data || count == 0) { return; } // 地址0: 写1-4直接开始检测对应设备 if (startAddress == 0) { int deviceIndex = data[0]; // 检查设备索引是否有效 (1-4) if (deviceIndex >= 1 && deviceIndex <= 4 && deviceIndex <= static_cast(m_vrEyeDeviceList.size())) { LOG_INFO("Modbus: 开始检测设备 %d\n", deviceIndex); // 使用 QMetaObject::invokeMethod 在主线程执行 QMetaObject::invokeMethod(this, [this, deviceIndex]() { ResetDetect(deviceIndex - 1); // ResetDetect 期望 0-based 索引 }, Qt::QueuedConnection); } else { LOG_WARNING("Modbus: 无效的设备索引: %d (有效范围: 1-%d)\n", deviceIndex, static_cast(m_vrEyeDeviceList.size())); } } } // ============ TCP协议回调处理 ============ bool WheelMeasurePresenter::onTCPDetectionTriggered(int param) { LOG_INFO("TCP检测触发,参数: %d\n", param); // 协议触发:强制切换到轮毂检测模式,并通知UI同步按钮状态 if (m_detectMode != WheelDetectMode::Wheel) { LOG_INFO("TCP触发:当前检测模式=%d,强制切换为轮毂检测\n", static_cast(m_detectMode)); m_detectMode = WheelDetectMode::Wheel; QMetaObject::invokeMethod(this, [this]() { if (m_statusUpdate) { m_statusUpdate->OnDetectModeChanged(static_cast(WheelDetectMode::Wheel)); } }, Qt::QueuedConnection); } // 走通用的“扫描全部相机”流程,并在结束时回TCP响应 return startScanAllCameras(true); } bool WheelMeasurePresenter::startScanAllCameras(bool bIsTCPTriggered) { // 清空之前的检测结果缓存与状态 m_tcpResults.clear(); m_scanAllMode = true; m_isTCPTriggered = bIsTCPTriggered; m_scanAllList.clear(); m_scanAllIndex = 0; // 统计启用的相机数量 int enabledCount = 0; for (const auto& cameraConfig : m_configResult.cameras) { if (cameraConfig.enabled) ++enabledCount; } if (enabledCount == 0) { LOG_WARNING("扫描全部相机:无启用的相机,无法启动检测\n"); m_scanAllMode = false; m_isTCPTriggered = false; return false; } // 清空之前的测量结果显示 QString tipMsg = bIsTCPTriggered ? QString("TCP触发:开始检测所有设备") : QString("开始所有设备的检测"); QMetaObject::invokeMethod(this, [this, tipMsg]() { if (m_statusUpdate) { m_statusUpdate->OnClearMeasureData(); m_statusUpdate->OnStatusUpdate(tipMsg); } }, Qt::QueuedConnection); int simCount = m_configResult.scanConfig.simultaneousCount; if (simCount == 1) { // 顺序模式:应用层逐个调度相机(每次 StartDetection(具体索引) 走 branch 1) for (int idx = 1; idx <= enabledCount; ++idx) { m_scanAllList.push_back(idx); } LOG_INFO("顺序启动 %d 个相机 (simultaneousCount=1, isTCP=%d)\n", enabledCount, bIsTCPTriggered ? 1 : 0); QMetaObject::invokeMethod(this, [this]() { startNextScanCamera(); }, Qt::QueuedConnection); } else { // 批处理模式:StartDetection(-1) 委托 BasePresenter 自动分批扫描所有相机 // - simultaneousCount=0: 所有相机同时扫描 // - simultaneousCount>1: 每批 N 个相机同时扫描 LOG_INFO("批处理启动 (simultaneousCount=%d, 启用相机=%d, isTCP=%d)\n", simCount, enabledCount, bIsTCPTriggered ? 1 : 0); QMetaObject::invokeMethod(this, [this]() { StartDetection(-1); }, Qt::QueuedConnection); } return true; } void WheelMeasurePresenter::startNextScanCamera() { if (m_scanAllIndex >= m_scanAllList.size()) { LOG_WARNING("startNextScanCamera: 索引越界\n"); if (m_isTCPTriggered) { sendTCPMeasureResults(); } m_scanAllMode = false; m_isTCPTriggered = false; return; } int cameraIdx = m_scanAllList[m_scanAllIndex]; LOG_INFO("扫描全部相机: 启动相机 %d (%zu/%zu)\n", cameraIdx, m_scanAllIndex + 1, m_scanAllList.size()); if (m_statusUpdate) { m_statusUpdate->OnStatusUpdate(QString("扫描相机 %1 (%2/%3)") .arg(cameraIdx) .arg(m_scanAllIndex + 1) .arg(m_scanAllList.size())); } // 调用 BasePresenter::StartDetection 指定相机扫描(branch 1: 单相机模式) StartDetection(cameraIdx); } void WheelMeasurePresenter::sendTCPMeasureResults() { // 计算期望的相机数量 int expectedCount = 0; for (const auto& cameraConfig : m_configResult.cameras) { if (cameraConfig.enabled) expectedCount++; } LOG_INFO("发送TCP测量结果,共 %d 个相机\n", m_tcpResults.size()); // 构建结果向量(按相机ID排序) std::vector results; // 按相机ID顺序添加结果 for (int cameraId = 1; cameraId <= expectedCount; ++cameraId) { if (m_tcpResults.contains(cameraId)) { results.push_back(m_tcpResults[cameraId]); } else { // 如果某个相机没有结果,添加失败结果 WheelMeasureTCPProtocol::CameraMeasureResult failResult; failResult.cameraId = cameraId; failResult.errorCode = 400; // 扫描失败 results.push_back(failResult); LOG_WARNING("相机 %d 没有检测结果,使用默认失败结果\n", cameraId); } } // 发送结果 int sendResult = m_tcpProtocol.SendMeasureResults(results); if (sendResult != 0) { LOG_ERROR("发送TCP测量结果失败,错误码: %d\n", sendResult); } else { LOG_INFO("TCP测量结果发送成功\n"); } // 清空缓存(流程标志已在 OnWorkStatusChanged 中提前清理) m_tcpResults.clear(); }