GrabBag/App/RodWeldSeam/RodWeldSeamApp/Presenter/Src/RodWeldSeamPresenter.cpp
2026-05-18 23:39:05 +08:00

783 lines
28 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 "RodWeldSeamPresenter.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 <QImage>
#include <QThread>
#include <atomic>
#include <cstring>
#include "Version.h"
#include "VrTimeUtils.h"
#include "VrDateUtils.h"
#include "SG_baseDataType.h"
#include "VrConvert.h"
#include "DetectPresenter.h"
#include "PathManager.h"
RodWeldSeamPresenter::RodWeldSeamPresenter(QObject *parent)
: BasePresenter(parent)
, m_pConfigManager(nullptr)
, m_pDetectPresenter(nullptr)
{
// 基类已经创建了相机重连定时器和检测数据缓存
}
RodWeldSeamPresenter::~RodWeldSeamPresenter()
{
// 基类会自动处理:相机重连定时器、算法检测线程、检测数据缓存、相机设备资源
// 释放ConfigManager析构函数会自动调用Shutdown
if (m_pConfigManager) {
delete m_pConfigManager;
m_pConfigManager = nullptr;
}
// 释放检测处理器
if(m_pDetectPresenter)
{
delete m_pDetectPresenter;
m_pDetectPresenter = nullptr;
}
}
int RodWeldSeamPresenter::InitApp()
{
LOG_DEBUG("Start APP Version: %s\n", RODWELDSEAM_FULL_VERSION_STRING);
// 初始化连接状态
SetWorkStatus(WorkStatus::InitIng);
m_pDetectPresenter = new DetectPresenter();
int nRet = SUCCESS;
// 创建 ConfigManager 实例
m_pConfigManager = new ConfigManager();
if (!m_pConfigManager) {
LOG_ERROR("Failed to create ConfigManager instance\n");
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) pStatus->OnStatusUpdate("配置管理器创建失败");
return ERR_CODE(DEV_CONFIG_ERR);
}
// 初始化 ConfigManager
if (!m_pConfigManager->Initialize()) {
LOG_ERROR("Failed to initialize ConfigManager\n");
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) pStatus->OnStatusUpdate("配置管理器初始化失败");
return ERR_CODE(DEV_CONFIG_ERR);
}
LOG_INFO("Configuration loaded successfully\n");
// 获取配置结果
ConfigResult configResult = m_pConfigManager->GetConfigResult();
// 调用基类InitCamera进行相机初始化bRGB=false, bSwing=true
InitCamera(configResult.cameraList, false, true);
LOG_INFO("Camera initialization completed. Connected cameras: %zu, default camera index: %d\n",
m_vrEyeDeviceList.size(), m_currentCameraIndex);
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) pStatus->OnStatusUpdate("设备初始化完成");
CheckAndUpdateWorkStatus();
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) pStatus->OnStatusUpdate("配置初始化成功");
return SUCCESS;
}
// 初始化算法参数实现BasePresenter纯虚函数
int RodWeldSeamPresenter::InitAlgoParams()
{
LOG_DEBUG("initializing algorithm parameters\n");
QString exePath = QCoreApplication::applicationFilePath();
// 清空现有的手眼标定矩阵列表
m_clibMatrixList.clear();
// 从 ConfigManager 获取配置结果
ConfigResult configResult = m_pConfigManager->GetConfigResult();
// 从 config.xml 加载手眼标定矩阵列表(支持多相机)
for (const auto& handEyeMatrix : configResult.handEyeCalibMatrixList) {
CalibMatrix calibMatrix;
memcpy(calibMatrix.clibMatrix, handEyeMatrix.matrix, sizeof(double) * 16);
m_clibMatrixList.push_back(calibMatrix);
LOG_INFO("Loaded hand-eye calibration matrix for camera %d:\n", handEyeMatrix.cameraIndex);
QString clibMatrixStr;
for (int i = 0; i < 4; ++i) {
clibMatrixStr.clear();
for (int j = 0; j < 4; ++j) {
clibMatrixStr += QString::asprintf("%8.4f ", calibMatrix.clibMatrix[i * 4 + j]);
}
LOG_INFO(" %s\n", clibMatrixStr.toStdString().c_str());
}
}
LOG_INFO("Total loaded %zu hand-eye calibration matrices\n", m_clibMatrixList.size());
const VrAlgorithmParams& xmlParams = configResult.algorithmParams;
LOG_INFO("Loaded XML params - Rod: rodDiameter=%.1f, rodLen=%.1f\n",
xmlParams.rodParam.rodDiameter,
xmlParams.rodParam.rodLen);
LOG_INFO("Loaded XML params - WeldSeam: weldSeanRange=%.1f\n",
xmlParams.weldSeamParam.weldSeanRange);
LOG_INFO("Loaded XML params - Filter: continuityTh=%.1f, outlierTh=%.1f\n",
xmlParams.filterParam.continuityTh, xmlParams.filterParam.outlierTh);
LOG_INFO("Algorithm parameters initialized successfully\n");
return SUCCESS;
}
// 获取算法版本
QString RodWeldSeamPresenter::GetAlgoVersion() const
{
return DetectPresenter::GetAlgoVersion();
}
// 手眼标定矩阵管理方法实现
CalibMatrix RodWeldSeamPresenter::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 RodWeldSeamPresenter::CheckAndUpdateWorkStatus()
{
if (m_bCameraConnected) {
SetWorkStatus(WorkStatus::Ready);
} else {
SetWorkStatus(WorkStatus::Error);
}
}
// 实现BasePresenter纯虚函数执行算法检测
int RodWeldSeamPresenter::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(GetStatusCallback<IYRodWeldSeamStatus>()){
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) pStatus->OnStatusUpdate("扫描线数:" + std::to_string(lineNum) + ",正在算法检测...");
}
// 检查检测处理器是否已初始化
if (!m_pDetectPresenter) {
LOG_ERROR("DetectPresenter is null, cannot proceed with detection\n");
if (GetStatusCallback<IYRodWeldSeamStatus>()) {
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) pStatus->OnStatusUpdate("检测处理器未初始化");
}
return ERR_CODE(DEV_NOT_FIND);
}
CVrTimeUtils oTimeUtils;
// 获取当前使用的手眼标定矩阵
const CalibMatrix currentClibMatrix = GetClibMatrix(m_currentCameraIndex - 1);
// 从 ConfigManager 获取算法参数和调试参数
VrAlgorithmParams algorithmParams = m_pConfigManager->GetAlgorithmParams();
ConfigResult configResult = m_pConfigManager->GetConfigResult();
VrDebugParam debugParam = configResult.debugParam;
int eulerOrder = configResult.eulerOrder;
const int calibIdx = m_currentCameraIndex - 1;
if (calibIdx >= 0 && calibIdx < static_cast<int>(configResult.handEyeCalibMatrixList.size())) {
eulerOrder = configResult.handEyeCalibMatrixList[calibIdx].eulerOrder;
} else if (!configResult.handEyeCalibMatrixList.empty()) {
eulerOrder = configResult.handEyeCalibMatrixList[0].eulerOrder;
}
const int dirVectorInvert = configResult.dirVectorInvert;
const int longAxisDir = configResult.longAxisDir;
LOG_INFO("[Algo Thread] Using euler order: %d, dir vector invert: %d, long axis dir: %d\n", eulerOrder, dirVectorInvert, longAxisDir);
DetectionResult detectionResult;
int nRet = m_pDetectPresenter->DetectWeldSeam(m_currentCameraIndex, detectionDataCache,
algorithmParams, debugParam, m_dataLoader,
currentClibMatrix.clibMatrix, eulerOrder, dirVectorInvert,
longAxisDir, detectionResult);
// 根据项目类型选择处理方式
if (GetStatusCallback<IYRodWeldSeamStatus>()) {
QString err = QString("错误:%1").arg(nRet);
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) pStatus->OnStatusUpdate(QString("检测%1").arg(SUCCESS == nRet ? "成功": err).toStdString());
}
LOG_INFO("[Algo Thread] sx_rebarWeldSeamPositioning detected %zu objects time : %.2f ms\n", detectionResult.positions.size(), oTimeUtils.GetElapsedTimeInMilliSec());
ERR_CODE_RETURN(nRet);
// 8. 通知UI检测结果
detectionResult.cameraIndex = m_currentCameraIndex;
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) {
pStatus->OnDetectionResult(detectionResult);
}
// 更新状态
QString statusMsg = QString("检测完成,发现%1个焊缝").arg(detectionResult.positions.size());
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) pStatus->OnStatusUpdate(statusMsg.toStdString());
// 缓存检测结果重置索引首次发送index=0下次写10从1开始
{
std::lock_guard<std::mutex> lock(m_resultCacheMutex);
m_lastDetectionResult = detectionResult;
m_resultIndex = 1;
}
// 发送第0个结果
_SendDetectionResultToModbus(detectionResult, m_currentCameraIndex, 0);
// 9. 检测完成后,将工作状态更新为"完成"
SetWorkStatus(WorkStatus::Completed);
// 恢复到就绪状态
SetWorkStatus(WorkStatus::Ready);
return SUCCESS;
}
// 实现配置改变通知接口
void RodWeldSeamPresenter::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 (GetStatusCallback<IYRodWeldSeamStatus>()) {
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) pStatus->OnStatusUpdate("配置已更新,算法参数重新加载成功");
}
} else {
LOG_ERROR("Failed to reload algorithm parameters after config change, error: %d\n", result);
if (GetStatusCallback<IYRodWeldSeamStatus>()) {
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) pStatus->OnStatusUpdate("配置更新后算法参数重新加载失败");
}
}
}
// 根据相机索引获取调平参数
SSG_planeCalibPara RodWeldSeamPresenter::_GetCameraCalibParam(int cameraIndex)
{
// 查找指定相机索引的调平参数
SSG_planeCalibPara calibParam;
// 使用单位矩阵(未校准状态)
double identityMatrix[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
for (int i = 0; i < 9; i++) {
calibParam.planeCalib[i] = identityMatrix[i];
calibParam.invRMatrix[i] = identityMatrix[i];
}
calibParam.planeHeight = -1.0; // 使用默认高度
// 从 ConfigManager 获取算法参数
VrAlgorithmParams algorithmParams = m_pConfigManager->GetAlgorithmParams();
for (const auto& cameraParam : algorithmParams.planeCalibParam.cameraCalibParams) {
if (cameraParam.cameraIndex == cameraIndex) {
// 根据isCalibrated标志决定使用标定矩阵还是单位矩阵
if (cameraParam.isCalibrated) {
// 使用实际的标定矩阵
for (int i = 0; i < 9; i++) {
calibParam.planeCalib[i] = cameraParam.planeCalib[i];
calibParam.invRMatrix[i] = cameraParam.invRMatrix[i];
}
calibParam.planeHeight = cameraParam.planeHeight;
}
}
}
return calibParam;
}
// 实现BasePresenter纯虚函数相机状态变化通知
void RodWeldSeamPresenter::OnCameraStatusChanged(int cameraIndex, bool isConnected)
{
LOG_INFO("Camera %d status changed: %s\n", cameraIndex, isConnected ? "connected" : "disconnected");
// 通知UI更新相机状态
if (GetStatusCallback<IYRodWeldSeamStatus>()) {
if (cameraIndex == 1) {
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) pStatus->OnCamera1StatusChanged(isConnected);
} else if (cameraIndex == 2) {
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) 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<IYRodWeldSeamStatus>()) pStatus->OnStatusUpdate(statusMsg.toStdString());
}
// 检查并更新工作状态
CheckAndUpdateWorkStatus();
}
// 实现BasePresenter虚函数工作状态变化通知
void RodWeldSeamPresenter::OnWorkStatusChanged(WorkStatus status)
{
auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>();
if (pStatus) {
pStatus->OnWorkStatusChanged(status);
}
}
// 实现BasePresenter虚函数相机数量变化通知
void RodWeldSeamPresenter::OnCameraCountChanged(int count)
{
auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>();
if (pStatus) {
pStatus->OnCameraCountChanged(count);
}
}
// 实现BasePresenter虚函数状态文字更新通知
void RodWeldSeamPresenter::OnStatusUpdate(const std::string& statusMessage)
{
auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>();
if (pStatus) {
pStatus->OnStatusUpdate(statusMessage);
}
}
// ============ 实现 ICameraLevelCalculator 接口 ============
bool RodWeldSeamPresenter::CalculatePlaneCalibration(
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& scanData,
double planeCalib[9],
double& planeHeight,
double invRMatrix[9])
{
try {
// 检查是否有足够的扫描数据
if (scanData.empty()) {
LOG_ERROR("No scan data available for plane calibration\n");
return false;
}
LOG_INFO("Calculating plane calibration from %zu scan lines\n", scanData.size());
// 转换为算法需要的XYZ格式
LaserDataLoader dataLoader;
std::vector<std::vector<SVzNL3DPosition>> xyzData;
int convertResult = dataLoader.ConvertToSVzNL3DPosition(scanData, xyzData);
if (convertResult != SUCCESS || xyzData.empty()) {
LOG_WARNING("Failed to convert data to XYZ format or no XYZ data available\n");
return false;
}
// 钢筋焊缝定位项目暂时使用简单的平面拟合
// 使用默认的单位矩阵
double identity[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
memcpy(planeCalib, identity, sizeof(double) * 9);
memcpy(invRMatrix, identity, sizeof(double) * 9);
planeHeight = -1.0;
LOG_INFO("Plane calibration calculated successfully: height=%.3f\n", planeHeight);
return true;
} catch (const std::exception& e) {
LOG_ERROR("Exception in CalculatePlaneCalibration: %s\n", e.what());
return false;
} catch (...) {
LOG_ERROR("Unknown exception in CalculatePlaneCalibration\n");
return false;
}
}
// ============ 实现 ICameraLevelResultSaver 接口 ============
bool RodWeldSeamPresenter::SaveLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9],
int cameraIndex, const QString& cameraName)
{
try {
if (!m_pConfigManager) {
LOG_ERROR("ConfigManager is null, cannot save leveling results\n");
return false;
}
// 验证传入的相机参数
if (cameraIndex <= 0) {
LOG_ERROR("Invalid camera index: %d\n", cameraIndex);
return false;
}
if (cameraName.isEmpty()) {
LOG_ERROR("Camera name is empty\n");
return false;
}
// 获取当前配置
QString configPath = PathManager::GetInstance().GetConfigFilePath();
LOG_INFO("Config path: %s\n", configPath.toUtf8().constData());
SystemConfig systemConfig = m_pConfigManager->GetConfig();
// 创建或更新指定相机的调平参数
VrCameraPlaneCalibParam cameraParam;
cameraParam.cameraIndex = cameraIndex;
cameraParam.cameraName = cameraName.toStdString();
cameraParam.planeHeight = planeHeight;
cameraParam.isCalibrated = true;
// 复制校准矩阵
for (int i = 0; i < 9; i++) {
cameraParam.planeCalib[i] = planeCalib[i];
cameraParam.invRMatrix[i] = invRMatrix[i];
}
// 更新配置中的相机校准参数
systemConfig.configResult.algorithmParams.planeCalibParam.SetCameraCalibParam(cameraParam);
// 更新并保存配置
if (!m_pConfigManager->UpdateFullConfig(systemConfig)) {
LOG_ERROR("Failed to update config with leveling results\n");
return false;
}
if (!m_pConfigManager->SaveConfigToFile(configPath.toStdString())) {
LOG_ERROR("Failed to save config file with leveling results\n");
return false;
}
LOG_INFO("Leveling results saved successfully for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData());
LOG_INFO("Plane height: %.3f\n", planeHeight);
LOG_INFO("Calibration marked as completed\n");
return true;
} catch (const std::exception& e) {
LOG_ERROR("Exception in SaveLevelingResults: %s\n", e.what());
return false;
}
}
bool RodWeldSeamPresenter::LoadLevelingResults(int cameraIndex, const QString& cameraName,
double planeCalib[9], double& planeHeight, double invRMatrix[9])
{
try {
if (!m_pConfigManager) {
LOG_ERROR("ConfigManager is null, cannot load calibration data\n");
return false;
}
// 从ConfigManager获取配置结果
ConfigResult configResult = m_pConfigManager->GetConfigResult();
// 获取指定相机的标定参数
VrCameraPlaneCalibParam cameraParamValue;
if (!configResult.algorithmParams.planeCalibParam.GetCameraCalibParam(cameraIndex, cameraParamValue) || !cameraParamValue.isCalibrated) {
LOG_INFO("No calibration data found for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData());
return false;
}
// 复制标定数据
for (int i = 0; i < 9; i++) {
planeCalib[i] = cameraParamValue.planeCalib[i];
invRMatrix[i] = cameraParamValue.invRMatrix[i];
}
planeHeight = cameraParamValue.planeHeight;
LOG_INFO("Calibration data loaded successfully for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData());
LOG_INFO("Plane height: %.3f\n", planeHeight);
return true;
} catch (const std::exception& e) {
LOG_ERROR("Exception in LoadLevelingResults: %s\n", e.what());
return false;
}
}
// 反初始化
void RodWeldSeamPresenter::DeinitApp()
{
LOG_DEBUG("Deinitializing RodWeldSeamPresenter\n");
// 停止检测
StopDetection();
// 释放ConfigManager析构函数会自动调用Shutdown
if (m_pConfigManager) {
delete m_pConfigManager;
m_pConfigManager = nullptr;
}
// 释放检测处理器
if (m_pDetectPresenter) {
delete m_pDetectPresenter;
m_pDetectPresenter = nullptr;
}
LOG_DEBUG("RodWeldSeamPresenter deinitialized\n");
}
// 触发检测
bool RodWeldSeamPresenter::TriggerDetection(int cameraIndex)
{
// 设置相机索引
if (cameraIndex > 0) {
SetDefaultCameraIndex(cameraIndex);
}
// 检查是否已连接相机
if (!m_bCameraConnected) {
LOG_WARNING("Camera not connected, cannot trigger detection\n");
return false;
}
// 触发检测
int ret = StartDetection(cameraIndex, false);
if (ret != SUCCESS) {
LOG_ERROR("Failed to trigger detection, error: %d\n", ret);
return false;
}
return true;
}
// 加载文件并检测
int RodWeldSeamPresenter::LoadAndDetect(const QString& fileName)
{
LOG_INFO("Loading data from file: %s\n", fileName.toStdString().c_str());
// 使用基类的方法加载调试数据并执行检测
return LoadDebugDataAndDetect(fileName.toStdString());
}
// 重连相机
void RodWeldSeamPresenter::ReconnectCamera()
{
LOG_INFO("Attempting to reconnect cameras\n");
TryReconnectCameras();
}
// 获取算法参数
RodWeldSeamPresenter::AlgoParams RodWeldSeamPresenter::GetAlgoParams() const
{
AlgoParams params;
if (m_pConfigManager) {
VrAlgorithmParams algorithmParams = m_pConfigManager->GetAlgorithmParams();
params.rodParam = algorithmParams.rodParam;
params.weldSeamParam = algorithmParams.weldSeamParam;
params.cornerParam = algorithmParams.cornerParam;
params.filterParam = algorithmParams.filterParam;
params.growParam = algorithmParams.growParam;
} else {
// 使用默认参数
params.rodParam.rodDiameter = 16.0;
params.rodParam.rodLen = 50.0;
params.weldSeamParam.weldSeanRange = 100.0;
}
return params;
}
// 设置算法参数
void RodWeldSeamPresenter::SetAlgoParams(const AlgoParams& params)
{
if (!m_pConfigManager) {
LOG_WARNING("ConfigManager not initialized, cannot set algorithm params\n");
return;
}
// 获取当前配置
VrAlgorithmParams algorithmParams = m_pConfigManager->GetAlgorithmParams();
// 更新参数
algorithmParams.rodParam = params.rodParam;
algorithmParams.weldSeamParam = params.weldSeamParam;
algorithmParams.cornerParam = params.cornerParam;
algorithmParams.filterParam = params.filterParam;
algorithmParams.growParam = params.growParam;
// 更新到ConfigManager
m_pConfigManager->UpdateAlgorithmParams(algorithmParams);
LOG_INFO("Algorithm parameters updated\n");
}
// ============ ModbusTCP Server 相关实现 ============
void RodWeldSeamPresenter::OnModbusWriteCallback(uint16_t startAddress, const uint16_t* data, uint16_t count)
{
// 检测触发寄存器 (地址 0):写入非零值触发检测或读取缓存结果
if (startAddress == 0 && count >= 1 && data[0] != 0) {
int cmd = data[0]; // 1=触发相机1, 2=触发相机2, 10=读取下一个缓存结果
LOG_INFO("Modbus command received: cmd=%d\n", cmd);
// 清除触发寄存器
uint16_t clearValue = 0;
WriteModbusRegisters(0, &clearValue, 1);
if (cmd == 10) {
// 读取缓存中的下一个焊缝结果
_SendNextCachedResult();
} else {
// 新检测触发,索引归零
{
std::lock_guard<std::mutex> lock(m_resultCacheMutex);
m_resultIndex = 0;
}
// 设置状态为"正在检测"
uint16_t detecting = 0;
WriteModbusRegisters(1, &detecting, 1);
// 触发对应相机检测
TriggerDetection(cmd);
}
}
}
void RodWeldSeamPresenter::OnModbusServerStatusChanged(bool isConnected)
{
LOG_INFO("Modbus server status changed: %s\n", isConnected ? "connected" : "disconnected");
if (auto pStatus = GetStatusCallback<IYRodWeldSeamStatus>()) {
pStatus->OnRobotConnectionChanged(isConnected);
}
}
void RodWeldSeamPresenter::_SendDetectionResultToModbus(const DetectionResult& result, int cameraIndex, int index)
{
if (!IsModbusServerRunning()) {
LOG_WARNING("Modbus server not running, skip sending result\n");
return;
}
// 从配置获取字节序
ConfigResult configResult = m_pConfigManager->GetConfigResult();
bool bigEndian = (configResult.byteOrder == 0); // 0=大端序, 1=小端序
// 地址 1状态码0=正在检测1=成功2=失败3=结果为空/无更多结果)
uint16_t statusCode = 1; // 默认成功
if (!result.success) {
statusCode = 2; // 检测失败
} else if (result.positions.empty() || index >= (int)result.positions.size()) {
statusCode = 3; // 成功但结果为空或索引越界
}
WriteModbusRegisters(1, &statusCode, 1);
// 仅在状态码为成功时输出焊缝数据(地址 2~29
if (statusCode == 1) {
// 地址 2焊缝类型0=未知1=点焊2=焊缝3=焊缝间隙)
// 地址 3当前焊缝索引从 1 开始计数,对应页面"焊缝 N"
// 地址 4焊缝总数
// 地址 5保留
uint16_t weldType = 0;
if (index < (int)result.weldSeamInfoList.size()) {
weldType = (uint16_t)result.weldSeamInfoList[index].weldType;
}
uint16_t headerRegs[4] = {
weldType,
(uint16_t)(index + 1),
(uint16_t)result.positions.size(),
0 // 保留
};
WriteModbusRegisters(2, headerRegs, 4);
// 地址 6~29中心(3) + 起点(3) + 终点(3) + RPY(3) = 12 个 float = 24 个寄存器
float values[12] = {0};
if (index < (int)result.weldSeamInfoList.size()) {
const auto& info = result.weldSeamInfoList[index];
values[0] = (float)info.centerX;
values[1] = (float)info.centerY;
values[2] = (float)info.centerZ;
values[3] = (float)info.startPtX;
values[4] = (float)info.startPtY;
values[5] = (float)info.startPtZ;
values[6] = (float)info.endPtX;
values[7] = (float)info.endPtY;
values[8] = (float)info.endPtZ;
} else {
// 焊缝信息缺失时,回退使用 positions 中的中心坐标
const auto& pos = result.positions[index];
values[0] = (float)pos.x;
values[1] = (float)pos.y;
values[2] = (float)pos.z;
}
// RPY 始终从 positions 取(已由算法将轴向/法向折算为欧拉角)
const auto& pos = result.positions[index];
values[9] = (float)pos.roll;
values[10] = (float)pos.pitch;
values[11] = (float)pos.yaw;
uint16_t regs[24];
memcpy(regs, values, sizeof(regs));
// 大端序时交换每个 float 的高低字序x86 小端 → Modbus 大端字序)
if (bigEndian) {
for (int i = 0; i < 12; i++) {
std::swap(regs[i * 2], regs[i * 2 + 1]);
}
}
WriteModbusRegisters(6, regs, 24);
}
LOG_INFO("Detection result sent via Modbus, camera: %d, index: %d, status: %d\n",
cameraIndex, index, statusCode);
}
void RodWeldSeamPresenter::_SendNextCachedResult()
{
if (!IsModbusServerRunning()) {
LOG_WARNING("Modbus server not running, skip sending cached result\n");
return;
}
std::lock_guard<std::mutex> lock(m_resultCacheMutex);
if (m_lastDetectionResult.positions.empty() ||
m_resultIndex >= (int)m_lastDetectionResult.positions.size()) {
// 无更多结果写状态码3
uint16_t statusCode = 3;
WriteModbusRegisters(1, &statusCode, 1);
LOG_INFO("No more cached results, index=%d, total=%zu\n",
m_resultIndex, m_lastDetectionResult.positions.size());
return;
}
LOG_INFO("Sending cached result index=%d, total=%zu\n",
m_resultIndex, m_lastDetectionResult.positions.size());
_SendDetectionResultToModbus(m_lastDetectionResult,
m_lastDetectionResult.cameraIndex,
m_resultIndex);
m_resultIndex++;
}