#include "HoleDetectionPresenter.h" #include "VrError.h" #include "VrLog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Version.h" #include "VrTimeUtils.h" #include "VrDateUtils.h" #include "VrConvert.h" #include "TCPServerProtocol.h" #include "DetectPresenter.h" #include "PathManager.h" // 配置变化监听器代理实现 void ConfigChangeListenerProxy::OnSystemConfigChanged(const SystemConfig& config) { if (m_presenter) { LOG_INFO("ConfigChangeListenerProxy: config changed, reloading algorithm parameters\n"); m_presenter->InitAlgoParams(); } } HoleDetectionPresenter::HoleDetectionPresenter(QObject *parent) : BasePresenter(parent) , m_pConfigManager(nullptr) , m_pDetectPresenter(nullptr) , m_pTCPServer(nullptr) , m_bTCPConnected(false) { // 基类已经创建了相机重连定时器和检测数据缓存 } QString HoleDetectionPresenter::GetAlgoVersion() const { return DetectPresenter::GetAlgoVersion(); } HoleDetectionPresenter::~HoleDetectionPresenter() { // 基类会自动处理:相机重连定时器、算法检测线程、检测数据缓存、相机设备资源 // 释放ConfigManager if (m_pConfigManager) { m_pConfigManager->Shutdown(); delete m_pConfigManager; m_pConfigManager = nullptr; } // 释放TCP服务器 if (m_pTCPServer) { m_pTCPServer->Deinitialize(); delete m_pTCPServer; m_pTCPServer = nullptr; } // 释放检测处理器 if (m_pDetectPresenter) { delete m_pDetectPresenter; m_pDetectPresenter = nullptr; } } int HoleDetectionPresenter::InitApp() { LOG_DEBUG("Start APP Version: %s\n", HOLEDETECTION_FULL_VERSION_STRING); fprintf(stderr, "[DIAG] InitApp started, version: %s\n", HOLEDETECTION_FULL_VERSION_STRING); fflush(stderr); // 初始化连接状态 SetWorkStatus(WorkStatus::InitIng); fprintf(stderr, "[DIAG] Creating DetectPresenter...\n"); fflush(stderr); m_pDetectPresenter = new DetectPresenter(); fprintf(stderr, "[DIAG] DetectPresenter created OK\n"); fflush(stderr); int nRet = SUCCESS; // 创建 ConfigManager 实例 fprintf(stderr, "[DIAG] Creating ConfigManager...\n"); fflush(stderr); m_pConfigManager = new ConfigManager(); if (!m_pConfigManager) { LOG_ERROR("Failed to create ConfigManager instance\n"); if (auto pStatus = GetStatusCallback()) pStatus->OnStatusUpdate("配置管理器创建失败"); return ERR_CODE(DEV_CONFIG_ERR); } fprintf(stderr, "[DIAG] ConfigManager created OK\n"); fflush(stderr); // 初始化 ConfigManager fprintf(stderr, "[DIAG] Initializing ConfigManager...\n"); fflush(stderr); if (!m_pConfigManager->Initialize()) { LOG_ERROR("Failed to initialize ConfigManager\n"); if (auto pStatus = GetStatusCallback()) pStatus->OnStatusUpdate("配置管理器初始化失败"); return ERR_CODE(DEV_CONFIG_ERR); } fprintf(stderr, "[DIAG] ConfigManager initialized OK\n"); fflush(stderr); // 注册配置变化监听器 m_pConfigListener = std::make_shared(this); m_pConfigManager->AddConfigChangeListener(m_pConfigListener); LOG_INFO("Configuration loaded successfully\n"); fprintf(stderr, "[DIAG] Configuration loaded OK\n"); fflush(stderr); // 获取配置结果 ConfigResult configResult = m_pConfigManager->GetConfigResult(); fprintf(stderr, "[DIAG] Camera count from config: %zu\n", configResult.cameraList.size()); fflush(stderr); // 调用基类InitCamera进行相机初始化(bRGB=false, bSwing=true) fprintf(stderr, "[DIAG] Calling InitCamera...\n"); fflush(stderr); InitCamera(configResult.cameraList, false, true); fprintf(stderr, "[DIAG] InitCamera completed OK\n"); fflush(stderr); LOG_INFO("Camera initialization completed. Connected cameras: %zu, default camera index: %d\n", m_vrEyeDeviceList.size(), m_currentCameraIndex); // 初始化TCP服务器 fprintf(stderr, "[DIAG] Initializing TCP server...\n"); fflush(stderr); nRet = InitTCPServer(); if (nRet != 0) { if (auto pStatus = GetStatusCallback()) pStatus->OnStatusUpdate("TCP服务器初始化失败"); m_bTCPConnected = false; } else { if (auto pStatus = GetStatusCallback()) pStatus->OnStatusUpdate("TCP服务器初始化成功"); } fprintf(stderr, "[DIAG] TCP server init result: %d\n", nRet); fflush(stderr); #if 0 // 初始化 PLC Modbus 客户端 nRet = InitPLCModbus(); if (nRet != 0) { LOG_WARNING("PLC Modbus initialization failed, continuing without PLC communication\n"); if (auto pStatus = GetStatusCallback()) pStatus->OnStatusUpdate("PLC通信初始化失败"); } else { if (auto pStatus = GetStatusCallback()) pStatus->OnStatusUpdate("PLC通信初始化成功"); } #endif if (auto pStatus = GetStatusCallback()) pStatus->OnStatusUpdate("设备初始化完成"); CheckAndUpdateWorkStatus(); if (auto pStatus = GetStatusCallback()) pStatus->OnStatusUpdate("配置初始化成功"); fprintf(stderr, "[DIAG] InitApp completed successfully\n"); fflush(stderr); return SUCCESS; } // 初始化算法参数(实现BasePresenter纯虚函数) int HoleDetectionPresenter::InitAlgoParams() { fprintf(stderr, "[DIAG] InitAlgoParams started\n"); fflush(stderr); LOG_DEBUG("initializing algorithm parameters\n"); QString exePath = QCoreApplication::applicationFilePath(); // 清空现有的手眼标定矩阵列表 m_clibMatrixList.clear(); // 从 ConfigManager 获取配置结果(包含多相机手眼标定矩阵列表) ConfigResult configResult = m_pConfigManager->GetConfigResult(); // 从 config.xml 加载所有相机的手眼标定矩阵 for (size_t i = 0; i < configResult.handEyeCalibMatrixList.size(); i++) { CalibMatrix calibMatrix; memcpy(calibMatrix.clibMatrix, configResult.handEyeCalibMatrixList[i].matrix, sizeof(double) * 16); m_clibMatrixList.push_back(calibMatrix); LOG_INFO("Loaded hand-eye calibration matrix for camera %zu from config.xml:\n", i + 1); QString clibMatrixStr; for (int r = 0; r < 4; ++r) { clibMatrixStr.clear(); for (int c = 0; c < 4; ++c) { clibMatrixStr += QString::asprintf("%8.4f ", calibMatrix.clibMatrix[r * 4 + c]); } LOG_INFO(" %s\n", clibMatrixStr.toStdString().c_str()); } } // 如果没有配置任何矩阵,添加一个默认单位矩阵 if (m_clibMatrixList.empty()) { CalibMatrix defaultMatrix; double initClibMatrix[16] = { 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 }; memcpy(defaultMatrix.clibMatrix, initClibMatrix, sizeof(initClibMatrix)); m_clibMatrixList.push_back(defaultMatrix); LOG_WARNING("No hand-eye calibration matrices found, using identity matrix\n"); } LOG_INFO("Total loaded %zu hand-eye calibration matrices\n", m_clibMatrixList.size()); const VrAlgorithmParams& xmlParams = configResult.algorithmParams; LOG_INFO("Loaded XML params - Ransac: distanceThreshold=%.2f, maxIterations=%d, minPlanePoints=%d, maxPlanes=%d, growthZThreshold=%.2f, minPlaneRatio=%.2f, maxNormalAngleDeg=%.1f, maxDistFromPlane=%.2f\n", xmlParams.ransacParam.distanceThreshold, xmlParams.ransacParam.maxIterations, xmlParams.ransacParam.minPlanePoints, xmlParams.ransacParam.maxPlanes, xmlParams.ransacParam.growthZThreshold, xmlParams.ransacParam.minPlaneRatio, xmlParams.ransacParam.maxNormalAngleDeg, xmlParams.ransacParam.maxDistFromPlane); LOG_INFO("Loaded XML params - Detection: angleThresholdPos=%.1f, angleThresholdNeg=%.1f, angleSearchDistance=%.2f, minPitDepth=%.2f\n", xmlParams.detectionParam.angleThresholdPos, xmlParams.detectionParam.angleThresholdNeg, xmlParams.detectionParam.angleSearchDistance, xmlParams.detectionParam.minPitDepth); LOG_INFO("Loaded XML params - Detection: minRadius=%.2f, maxRadius=%.2f, expansionSize1=%d, expansionSize2=%d, minVTransitionPoints=%d\n", xmlParams.detectionParam.minRadius, xmlParams.detectionParam.maxRadius, xmlParams.detectionParam.expansionSize1, xmlParams.detectionParam.expansionSize2, xmlParams.detectionParam.minVTransitionPoints); LOG_INFO("Loaded XML params - Detection: jumpThresholdResidual=%.2f, gapJumpThresholdResidual=%.2f, maxGapPointsInLine=%d, minFeatureSpan=%.2f, residualSmoothWindow=%d\n", xmlParams.detectionParam.jumpThresholdResidual, xmlParams.detectionParam.gapJumpThresholdResidual, xmlParams.detectionParam.maxGapPointsInLine, xmlParams.detectionParam.minFeatureSpan, xmlParams.detectionParam.residualSmoothWindow); LOG_INFO("Loaded XML params - Detection: slopeAngleThreshold=%.2f, edgeBoundaryFilterDist=%.2f\n", xmlParams.detectionParam.slopeAngleThreshold, xmlParams.detectionParam.edgeBoundaryFilterDist); LOG_INFO("Loaded XML params - Filter: maxEccentricity=%.5f, minAngularCoverage=%.1f, maxRadiusFitRatio=%.2f, minQualityScore=%.2f\n", xmlParams.filterParam.maxEccentricity, xmlParams.filterParam.minAngularCoverage, xmlParams.filterParam.maxRadiusFitRatio, xmlParams.filterParam.minQualityScore); LOG_INFO("Loaded XML params - Filter: maxPlaneResidual=%.2f, maxAngularGap=%.2f, minInlierRatio=%.2f, minHoleDepth=%.2f\n", xmlParams.filterParam.maxPlaneResidual, xmlParams.filterParam.maxAngularGap, xmlParams.filterParam.minInlierRatio, xmlParams.filterParam.minHoleDepth); LOG_INFO("Loaded XML params - SortMode: %d\n", xmlParams.sortMode); LOG_INFO("Algorithm parameters initialized successfully\n"); fprintf(stderr, "[DIAG] InitAlgoParams completed OK\n"); fflush(stderr); return SUCCESS; } // 手眼标定矩阵管理方法实现 CalibMatrix HoleDetectionPresenter::GetClibMatrix(int index) const { CalibMatrix clibMatrix; double initClibMatrix[16] = { 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 }; memcpy(clibMatrix.clibMatrix, initClibMatrix, sizeof(initClibMatrix)); if (index >= 0 && index < static_cast(m_clibMatrixList.size())) { clibMatrix = m_clibMatrixList[index]; memcpy(clibMatrix.clibMatrix, m_clibMatrixList[index].clibMatrix, sizeof(initClibMatrix)); } else { LOG_WARNING("Invalid hand-eye calibration matrix\n"); } return clibMatrix; } void HoleDetectionPresenter::CheckAndUpdateWorkStatus() { if (m_bCameraConnected) { SetWorkStatus(WorkStatus::Ready); } else { SetWorkStatus(WorkStatus::Error); } } // 实现BasePresenter纯虚函数:执行算法检测 int HoleDetectionPresenter::ProcessAlgoDetection(std::vector>& detectionDataCache) { LOG_INFO("[Algo Thread] Start real detection task using algorithm\n"); // 1. 获取缓存的点云数据(已由基类验证非空) unsigned int lineNum = detectionDataCache.size(); if (auto pStatus = GetStatusCallback()) { pStatus->OnStatusUpdate("扫描线数:" + std::to_string(lineNum) + ",正在算法检测..."); } // 检查检测处理器是否已初始化 if (!m_pDetectPresenter) { LOG_ERROR("DetectPresenter is null, cannot proceed with detection\n"); if (auto pStatus = GetStatusCallback()) { pStatus->OnStatusUpdate("检测处理器未初始化"); } return ERR_CODE(DEV_NOT_FIND); } CVrTimeUtils oTimeUtils; // 获取当前使用的手眼标定矩阵 const CalibMatrix currentClibMatrix = GetClibMatrix(m_currentCameraIndex - 1); // 打印手眼标定矩阵 LOG_INFO("[Algo Thread] Hand-Eye Calibration Matrix (Camera %d):\n", m_currentCameraIndex); LOG_INFO(" [%.4f, %.4f, %.4f, %.4f]\n", currentClibMatrix.clibMatrix[0], currentClibMatrix.clibMatrix[1], currentClibMatrix.clibMatrix[2], currentClibMatrix.clibMatrix[3]); LOG_INFO(" [%.4f, %.4f, %.4f, %.4f]\n", currentClibMatrix.clibMatrix[4], currentClibMatrix.clibMatrix[5], currentClibMatrix.clibMatrix[6], currentClibMatrix.clibMatrix[7]); LOG_INFO(" [%.4f, %.4f, %.4f, %.4f]\n", currentClibMatrix.clibMatrix[8], currentClibMatrix.clibMatrix[9], currentClibMatrix.clibMatrix[10], currentClibMatrix.clibMatrix[11]); LOG_INFO(" [%.4f, %.4f, %.4f, %.4f]\n", currentClibMatrix.clibMatrix[12], currentClibMatrix.clibMatrix[13], currentClibMatrix.clibMatrix[14], currentClibMatrix.clibMatrix[15]); // 从 ConfigManager 获取算法参数和调试参数 VrAlgorithmParams algorithmParams = m_pConfigManager->GetAlgorithmParams(); ConfigResult configResult = m_pConfigManager->GetConfigResult(); VrDebugParam debugParam = configResult.debugParam; // 获取旋转顺序配置(从对应相机的标定矩阵中获取) int eulerOrder = 11; // 默认外旋ZYX int calibIdx = m_currentCameraIndex - 1; if (calibIdx >= 0 && calibIdx < static_cast(configResult.handEyeCalibMatrixList.size())) { eulerOrder = configResult.handEyeCalibMatrixList[calibIdx].eulerOrder; } LOG_INFO("[Algo Thread] Using euler order: %d (camera %d)\n", eulerOrder, m_currentCameraIndex); // 获取方向向量反向配置 int dirVectorInvert = configResult.plcRobotServerConfig.dirVectorInvert; LOG_INFO("[Algo Thread] Using dir vector invert: %d\n", dirVectorInvert); HoleDetectionResult detectionResult; int nRet = m_pDetectPresenter->DetectHoles(m_currentCameraIndex, detectionDataCache, algorithmParams, debugParam, m_dataLoader, currentClibMatrix.clibMatrix, eulerOrder, dirVectorInvert, m_currentRobotPose, detectionResult); // 根据项目类型选择处理方式 if (auto pStatus = GetStatusCallback()) { QString err = QString("错误:%1").arg(nRet); pStatus->OnStatusUpdate(QString("检测%1").arg(SUCCESS == nRet ? "成功": err).toStdString()); } LOG_INFO("[Algo Thread] DetectHoles detected %zu holes (candidates=%d, filtered=%d) time : %.2f ms\n", detectionResult.positions.size(), detectionResult.totalCandidates, detectionResult.filteredCount, oTimeUtils.GetElapsedTimeInMilliSec()); // 8. 通知UI检测结果 detectionResult.cameraIndex = m_currentCameraIndex; if (auto pStatus = GetStatusCallback()) { pStatus->OnDetectionResult(detectionResult); } // 更新状态 QString statusMsg = QString("检测完成,发现%1个孔洞").arg(detectionResult.positions.size()); if (auto pStatus = GetStatusCallback()) pStatus->OnStatusUpdate(statusMsg.toStdString()); // 发送检测结果给TCP客户端 _SendDetectionResultToTCP(detectionResult, m_currentCameraIndex); // 9. 检测完成后,将工作状态更新为"完成" SetWorkStatus(WorkStatus::Completed); // 恢复到就绪状态 SetWorkStatus(WorkStatus::Ready); return SUCCESS; } // 实现配置改变通知接口 void HoleDetectionPresenter::OnConfigChanged(const ConfigResult& configResult) { LOG_INFO("Configuration changed notification received, reloading algorithm parameters\n"); // 重新初始化算法参数 int result = InitAlgoParams(); if (result == SUCCESS) { LOG_INFO("Algorithm parameters reloaded successfully after config change\n"); if (auto pStatus = GetStatusCallback()) { pStatus->OnStatusUpdate("配置已更新,算法参数重新加载成功"); } } else { LOG_ERROR("Failed to reload algorithm parameters after config change, error: %d\n", result); if (auto pStatus = GetStatusCallback()) { pStatus->OnStatusUpdate("配置更新后算法参数重新加载失败"); } } } // 实现BasePresenter纯虚函数:相机状态变化通知 void HoleDetectionPresenter::OnCameraStatusChanged(int cameraIndex, bool isConnected) { LOG_INFO("Camera %d status changed: %s\n", cameraIndex, isConnected ? "connected" : "disconnected"); // 通知UI更新相机状态 if (GetStatusCallback()) { if (cameraIndex == 1) { if (auto pStatus = GetStatusCallback()) pStatus->OnCamera1StatusChanged(isConnected); } else if (cameraIndex == 2) { if (auto pStatus = GetStatusCallback()) pStatus->OnCamera2StatusChanged(isConnected); } // 获取相机名称用于状态消息 QString cameraName; int arrayIndex = cameraIndex - 1; if (arrayIndex >= 0 && arrayIndex < static_cast(m_vrEyeDeviceList.size())) { cameraName = QString::fromStdString(m_vrEyeDeviceList[arrayIndex].first); } else { cameraName = QString("相机%1").arg(cameraIndex); } QString statusMsg = QString("%1%2").arg(cameraName).arg(isConnected ? "已连接" : "已断开"); if (auto pStatus = GetStatusCallback()) pStatus->OnStatusUpdate(statusMsg.toStdString()); } // 检查并更新工作状态 CheckAndUpdateWorkStatus(); } // 实现BasePresenter虚函数:工作状态变化通知 void HoleDetectionPresenter::OnWorkStatusChanged(WorkStatus status) { auto pStatus = GetStatusCallback(); if (pStatus) { pStatus->OnWorkStatusChanged(status); } } // 实现BasePresenter虚函数:相机数量变化通知 void HoleDetectionPresenter::OnCameraCountChanged(int count) { auto pStatus = GetStatusCallback(); if (pStatus) { pStatus->OnCameraCountChanged(count); } } // 实现BasePresenter虚函数:状态文字更新通知 void HoleDetectionPresenter::OnStatusUpdate(const std::string& statusMessage) { auto pStatus = GetStatusCallback(); if (pStatus) { pStatus->OnStatusUpdate(statusMessage); } } // ============ PLC Modbus 相关实现 ============ #if 0 // PLC Modbus 功能已禁用,使用TCP文本协议替代 int HoleDetectionPresenter::InitPLCModbus() { LOG_INFO("Initializing PLC Modbus client\n"); // 从配置获取 PLC 和机械臂的 IP 地址 ConfigResult configResult = m_pConfigManager->GetConfigResult(); // 从配置文件获取 PLC 配置 std::string plcIP = configResult.plcRobotServerConfig.plcServerIp; int plcPort = configResult.plcRobotServerConfig.plcServerPort; // 检查是否配置了 PLC 通信(如果为空则跳过初始化) if (plcIP.empty()) { LOG_INFO("PLC IP not configured, skipping PLC Modbus initialization\n"); return SUCCESS; } // 创建 PLCModbusClient 实例 m_pPLCModbusClient = new PLCModbusClient(); // 设置回调函数 m_pPLCModbusClient->SetPhotoTriggerCallback([this](int cameraIndex) { OnPLCPhotoRequested(cameraIndex); }); m_pPLCModbusClient->SetConnectionStateCallback([this](bool plcConnected) { m_bPLCConnected = plcConnected; LOG_INFO("PLC connection state changed: PLC=%s\n", plcConnected ? "connected" : "disconnected"); // 通知 UI 更新连接状态(PLC连接状态关联到机械臂状态指示器) if (auto pStatus = GetStatusCallback()) { pStatus->OnRobotConnectionChanged(plcConnected); std::string statusMsg = std::string("PLC:") + (plcConnected ? "已连接" : "断开"); pStatus->OnStatusUpdate(statusMsg); } }); m_pPLCModbusClient->SetErrorCallback([this](const std::string& errorMsg) { LOG_ERROR("PLC Modbus error: %s\n", errorMsg.c_str()); if (auto pStatus = GetStatusCallback()) { pStatus->OnStatusUpdate(std::string("PLC错误: ") + errorMsg); } }); // 设置重连回调,通知用户正在尝试重连 m_pPLCModbusClient->SetReconnectingCallback([this](const std::string& device, int attempt) { LOG_INFO("Attempting to reconnect %s (attempt %d)\n", device.c_str(), attempt); if (auto pStatus = GetStatusCallback()) { std::string statusMsg = device + "正在重连(第" + std::to_string(attempt) + "次)..."; pStatus->OnStatusUpdate(statusMsg); } }); // 构建寄存器地址配置 PLCModbusClient::RegisterConfig regConfig; regConfig.addrPhotoRequest = configResult.plcRobotServerConfig.registerConfig.addrPhotoRequest; regConfig.addrDataComplete = configResult.plcRobotServerConfig.registerConfig.addrDataComplete; regConfig.addrCoordDataStart = configResult.plcRobotServerConfig.registerConfig.addrCoordDataStart; regConfig.byteOrder = configResult.plcRobotServerConfig.byteOrder; // 初始化连接 bool initResult = m_pPLCModbusClient->Initialize(plcIP, plcPort, regConfig); if (!initResult) { LOG_ERROR("Failed to initialize PLC Modbus client\n"); return ERR_CODE(DEV_OPEN_ERR); } // 启动轮询(100ms 间隔) m_pPLCModbusClient->StartPolling(100); LOG_INFO("PLC Modbus client initialized successfully\n"); return SUCCESS; } void HoleDetectionPresenter::OnPLCPhotoRequested(int cameraIndex) { LOG_INFO("PLC photo request received for camera %d\n", cameraIndex); // 暂停拍照请求轮询(检测期间不再读取拍照请求寄存器) if (m_pPLCModbusClient) { m_pPLCModbusClient->PausePhotoRequestPolling(); } // 清除 PLC 的拍照请求标志(写0到拍照请求寄存器) if (m_pPLCModbusClient) { m_pPLCModbusClient->ClearPhotoRequest(); } // 设置当前相机索引 SetDefaultCameraIndex(cameraIndex); // 触发检测(使用基类的 StartDetection 方法) int nRet = StartDetection(cameraIndex, false); // 非自动模式 if (nRet != SUCCESS) { LOG_ERROR("Failed to trigger detection from PLC request, error: %d\n", nRet); if (auto pStatus = GetStatusCallback()) { pStatus->OnStatusUpdate("PLC触发检测失败"); } // 检测失败时也要恢复轮询 if (m_pPLCModbusClient) { m_pPLCModbusClient->ResumePhotoRequestPolling(); } } } void HoleDetectionPresenter::SendCoordinateDataToPLC(const HoleDetectionResult& result) { if (!m_pPLCModbusClient) { LOG_WARNING("PLC Modbus client not initialized, cannot send coordinate data\n"); return; } if (!m_pPLCModbusClient->IsPLCConnected()) { LOG_WARNING("PLC not connected, cannot send coordinate data\n"); // 恢复轮询 m_pPLCModbusClient->ResumePhotoRequestPolling(); return; } // 检查检测结果(errorCode == 0 表示成功) if (result.errorCode != 0) { LOG_WARNING("Invalid detection result, skipping coordinate send\n"); // 恢复轮询 m_pPLCModbusClient->ResumePhotoRequestPolling(); return; } // 检查是否有孔洞位置数据 if (result.positions.empty()) { LOG_WARNING("No hole positions found, skipping coordinate send\n"); // 恢复轮询 m_pPLCModbusClient->ResumePhotoRequestPolling(); return; } // 获取姿态输出顺序配置 ConfigResult configResult = m_pConfigManager->GetConfigResult(); int poseOutputOrder = configResult.plcRobotServerConfig.poseOutputOrder; LOG_INFO("Using pose output order: %d\n", poseOutputOrder); // 只发送第一个孔洞的坐标数据 const auto& pos = result.positions[0]; PLCModbusClient::CoordinateData coord; // 使用 float 类型存储坐标数据 coord.x = static_cast(pos.x); coord.y = static_cast(pos.y); coord.z = static_cast(pos.z); // 根据姿态输出顺序配置调整输出 switch (poseOutputOrder) { case POSE_ORDER_RPY: coord.roll = static_cast(pos.roll); coord.pitch = static_cast(pos.pitch); coord.yaw = static_cast(pos.yaw); break; case POSE_ORDER_RYP: coord.roll = static_cast(pos.roll); coord.pitch = static_cast(pos.yaw); coord.yaw = static_cast(pos.pitch); break; case POSE_ORDER_PRY: coord.roll = static_cast(pos.pitch); coord.pitch = static_cast(pos.roll); coord.yaw = static_cast(pos.yaw); break; case POSE_ORDER_PYR: coord.roll = static_cast(pos.pitch); coord.pitch = static_cast(pos.yaw); coord.yaw = static_cast(pos.roll); break; case POSE_ORDER_YRP: coord.roll = static_cast(pos.yaw); coord.pitch = static_cast(pos.roll); coord.yaw = static_cast(pos.pitch); break; case POSE_ORDER_YPR: coord.roll = static_cast(pos.yaw); coord.pitch = static_cast(pos.pitch); coord.yaw = static_cast(pos.roll); break; default: coord.roll = static_cast(pos.roll); coord.pitch = static_cast(pos.pitch); coord.yaw = static_cast(pos.yaw); break; } LOG_INFO("Hole: X=%.5f, Y=%.5f, Z=%.5f, R=%.5f, P=%.5f, Y=%.5f (order=%d)\n", coord.x, coord.y, coord.z, coord.roll, coord.pitch, coord.yaw, poseOutputOrder); LOG_INFO("Sending hole position to PLC\n"); // 发送坐标数据到 PLC bool sendResult = m_pPLCModbusClient->SendCoordinateToPLC(coord); if (!sendResult) { LOG_ERROR("Failed to send coordinate data to PLC\n"); if (auto pStatus = GetStatusCallback()) { pStatus->OnStatusUpdate("坐标数据发送失败"); } // 恢复轮询 m_pPLCModbusClient->ResumePhotoRequestPolling(); return; } // 通知 PLC 数据输出完成 bool notifyResult = m_pPLCModbusClient->NotifyDataComplete(); if (!notifyResult) { LOG_ERROR("Failed to notify PLC data complete\n"); if (auto pStatus = GetStatusCallback()) { pStatus->OnStatusUpdate("数据完成通知失败"); } // 恢复轮询 m_pPLCModbusClient->ResumePhotoRequestPolling(); return; } LOG_INFO("Hole position sent successfully and PLC notified\n"); if (auto pStatus = GetStatusCallback()) { pStatus->OnStatusUpdate("已发送孔洞坐标到PLC"); } // 发送完成,恢复拍照请求轮询 m_pPLCModbusClient->ResumePhotoRequestPolling(); } #endif // PLC Modbus 功能已禁用