GrabBag/App/HoleDetection/HoleDetectionApp/Presenter/Src/HoleDetectionPresenter.cpp

692 lines
27 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "HoleDetectionPresenter.h"
#include "VrError.h"
#include "VrLog.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QtCore/QString>
#include <QtCore/QStandardPaths>
#include <QtCore/QFile>
#include <QtCore/QDateTime>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <QImage>
#include <QThread>
#include <atomic>
#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<IYHoleDetectionStatus>()) 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<IYHoleDetectionStatus>()) pStatus->OnStatusUpdate("配置管理器初始化失败");
return ERR_CODE(DEV_CONFIG_ERR);
}
fprintf(stderr, "[DIAG] ConfigManager initialized OK\n"); fflush(stderr);
// 注册配置变化监听器
m_pConfigListener = std::make_shared<ConfigChangeListenerProxy>(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<IYHoleDetectionStatus>()) pStatus->OnStatusUpdate("TCP服务器初始化失败");
m_bTCPConnected = false;
} else {
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) 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<IYHoleDetectionStatus>()) pStatus->OnStatusUpdate("PLC通信初始化失败");
} else {
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) pStatus->OnStatusUpdate("PLC通信初始化成功");
}
#endif
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) pStatus->OnStatusUpdate("设备初始化完成");
CheckAndUpdateWorkStatus();
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) 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\n",
xmlParams.ransacParam.distanceThreshold,
xmlParams.ransacParam.maxIterations,
xmlParams.ransacParam.minPlanePoints,
xmlParams.ransacParam.maxPlanes,
xmlParams.ransacParam.growthZThreshold,
xmlParams.ransacParam.minPlaneRatio,
xmlParams.ransacParam.maxNormalAngleDeg);
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<int>(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<std::pair<EVzResultDataType, SVzLaserLineData>>& detectionDataCache)
{
LOG_INFO("[Algo Thread] Start real detection task using algorithm\n");
// 1. 获取缓存的点云数据(已由基类验证非空)
unsigned int lineNum = detectionDataCache.size();
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) {
pStatus->OnStatusUpdate("扫描线数:" + std::to_string(lineNum) + ",正在算法检测...");
}
// 检查检测处理器是否已初始化
if (!m_pDetectPresenter) {
LOG_ERROR("DetectPresenter is null, cannot proceed with detection\n");
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) {
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<int>(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<IYHoleDetectionStatus>()) {
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());
ERR_CODE_RETURN(nRet);
// 8. 通知UI检测结果
detectionResult.cameraIndex = m_currentCameraIndex;
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) {
pStatus->OnDetectionResult(detectionResult);
}
// 更新状态
QString statusMsg = QString("检测完成,发现%1个孔洞").arg(detectionResult.positions.size());
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) 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<IYHoleDetectionStatus>()) {
pStatus->OnStatusUpdate("配置已更新,算法参数重新加载成功");
}
} else {
LOG_ERROR("Failed to reload algorithm parameters after config change, error: %d\n", result);
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) {
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<IYHoleDetectionStatus>()) {
if (cameraIndex == 1) {
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) pStatus->OnCamera1StatusChanged(isConnected);
} else if (cameraIndex == 2) {
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) pStatus->OnCamera2StatusChanged(isConnected);
}
// 获取相机名称用于状态消息
QString cameraName;
int arrayIndex = cameraIndex - 1;
if (arrayIndex >= 0 && arrayIndex < static_cast<int>(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<IYHoleDetectionStatus>()) pStatus->OnStatusUpdate(statusMsg.toStdString());
}
// 检查并更新工作状态
CheckAndUpdateWorkStatus();
}
// 实现BasePresenter虚函数工作状态变化通知
void HoleDetectionPresenter::OnWorkStatusChanged(WorkStatus status)
{
auto pStatus = GetStatusCallback<IYHoleDetectionStatus>();
if (pStatus) {
pStatus->OnWorkStatusChanged(status);
}
}
// 实现BasePresenter虚函数相机数量变化通知
void HoleDetectionPresenter::OnCameraCountChanged(int count)
{
auto pStatus = GetStatusCallback<IYHoleDetectionStatus>();
if (pStatus) {
pStatus->OnCameraCountChanged(count);
}
}
// 实现BasePresenter虚函数状态文字更新通知
void HoleDetectionPresenter::OnStatusUpdate(const std::string& statusMessage)
{
auto pStatus = GetStatusCallback<IYHoleDetectionStatus>();
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<IYHoleDetectionStatus>()) {
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<IYHoleDetectionStatus>()) {
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<IYHoleDetectionStatus>()) {
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<IYHoleDetectionStatus>()) {
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<float>(pos.x);
coord.y = static_cast<float>(pos.y);
coord.z = static_cast<float>(pos.z);
// 根据姿态输出顺序配置调整输出
switch (poseOutputOrder) {
case POSE_ORDER_RPY:
coord.roll = static_cast<float>(pos.roll);
coord.pitch = static_cast<float>(pos.pitch);
coord.yaw = static_cast<float>(pos.yaw);
break;
case POSE_ORDER_RYP:
coord.roll = static_cast<float>(pos.roll);
coord.pitch = static_cast<float>(pos.yaw);
coord.yaw = static_cast<float>(pos.pitch);
break;
case POSE_ORDER_PRY:
coord.roll = static_cast<float>(pos.pitch);
coord.pitch = static_cast<float>(pos.roll);
coord.yaw = static_cast<float>(pos.yaw);
break;
case POSE_ORDER_PYR:
coord.roll = static_cast<float>(pos.pitch);
coord.pitch = static_cast<float>(pos.yaw);
coord.yaw = static_cast<float>(pos.roll);
break;
case POSE_ORDER_YRP:
coord.roll = static_cast<float>(pos.yaw);
coord.pitch = static_cast<float>(pos.roll);
coord.yaw = static_cast<float>(pos.pitch);
break;
case POSE_ORDER_YPR:
coord.roll = static_cast<float>(pos.yaw);
coord.pitch = static_cast<float>(pos.pitch);
coord.yaw = static_cast<float>(pos.roll);
break;
default:
coord.roll = static_cast<float>(pos.roll);
coord.pitch = static_cast<float>(pos.pitch);
coord.yaw = static_cast<float>(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<IYHoleDetectionStatus>()) {
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<IYHoleDetectionStatus>()) {
pStatus->OnStatusUpdate("数据完成通知失败");
}
// 恢复轮询
m_pPLCModbusClient->ResumePhotoRequestPolling();
return;
}
LOG_INFO("Hole position sent successfully and PLC notified\n");
if (auto pStatus = GetStatusCallback<IYHoleDetectionStatus>()) {
pStatus->OnStatusUpdate("已发送孔洞坐标到PLC");
}
// 发送完成,恢复拍照请求轮询
m_pPLCModbusClient->ResumePhotoRequestPolling();
}
#endif // PLC Modbus 功能已禁用