692 lines
28 KiB
C++
692 lines
28 KiB
C++
#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, 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<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());
|
||
|
||
// 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 功能已禁用
|