更新了拆包伯朗特的协议;提取协议到common,logcommon

This commit is contained in:
yiyi 2026-01-29 00:13:23 +08:00
parent b0108a4602
commit c6c3c2edeb
39 changed files with 1969 additions and 642 deletions

View File

@ -83,9 +83,8 @@ MainWindow::MainWindow(QWidget *parent)
QGraphicsScene* scene = new QGraphicsScene(this);
ui->detect_image->setScene(scene);
// 初始化日志模型
m_logModel = new QStringListModel(this);
ui->detect_log->setModel(m_logModel);
// 初始化日志辅助类
m_logHelper = new DetectLogHelper(ui->detect_log, this);
// 注册自定义类型,使其能够在信号槽中跨线程传递
qRegisterMetaType<DetectionResult>("DetectionResult");
@ -98,12 +97,6 @@ MainWindow::MainWindow(QWidget *parent)
// 连接检测结果更新信号槽
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
// 连接日志更新信号槽
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
// 连接清空日志信号槽
connect(this, &MainWindow::logClearRequested, this, &MainWindow::clearDetectionLogUI);
// 初始化右键菜单
setupContextMenu();
@ -129,14 +122,18 @@ MainWindow::~MainWindow()
void MainWindow::updateStatusLog(const QString& message)
{
// 通过信号槽机制更新detect_log控件
emit logUpdateRequested(message);
// 使用日志辅助类更新日志
if (m_logHelper) {
m_logHelper->appendLog(message);
}
}
void MainWindow::clearDetectionLog()
{
// 通过信号槽机制清空日志
emit logClearRequested();
// 使用日志辅助类清空日志
if (m_logHelper) {
m_logHelper->clearLog();
}
}
void MainWindow::Init()
@ -369,65 +366,13 @@ void MainWindow::updateWorkStatusLabel(WorkStatus status)
void MainWindow::updateDetectionResultDisplay(const DetectionResult& result)
{
// 显示检测结果图像
updateDetectionLog(tr("检测结果更新"));
updateStatusLog(tr("检测结果更新"));
displayImage(result.image);
// 更新检测结果到列表
addDetectionResult(result);
}
void MainWindow::updateDetectionLog(const QString& message)
{
// 在UI线程中更新detect_log控件QListView
if (!m_logModel) return;
// 获取当前数据
QStringList logList = m_logModel->stringList();
// 检查是否与最后一条消息相同
if (message == m_lastLogMessage && !logList.isEmpty()) {
// 相同消息,增加计数并替换最后一条
m_lastLogCount++;
// 添加时间戳
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
// 替换最后一条
logList[logList.size() - 1] = logEntry;
} else {
// 新消息,重置计数
m_lastLogMessage = message;
m_lastLogCount = 1;
// 添加时间戳
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
// 添加新的日志条目
logList.append(logEntry);
}
// 更新模型
m_logModel->setStringList(logList);
// 自动滚动到最底部
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
ui->detect_log->scrollTo(lastIndex);
}
void MainWindow::clearDetectionLogUI()
{
// 在UI线程中清空检测日志
if (m_logModel) {
m_logModel->setStringList(QStringList());
}
// 重置日志计数器
m_lastLogMessage.clear();
m_lastLogCount = 0;
}
void MainWindow::on_btn_start_clicked()
{
// 检查Presenter是否已初始化

View File

@ -16,6 +16,7 @@
#include "dialogalgoarg.h"
#include "CommonDialogCameraLevel.h"
#include "DeviceStatusWidget.h"
#include "DetectLogHelper.h"
#include "BagThreadPositionPresenter.h"
QT_BEGIN_NAMESPACE
@ -51,12 +52,6 @@ signals:
// 检测结果更新信号
void detectionResultUpdateRequested(const DetectionResult& result);
// 日志更新信号
void logUpdateRequested(const QString& message);
// 清空日志信号
void logClearRequested();
private slots:
// 工作状态更新槽函数
@ -65,12 +60,6 @@ private slots:
// 检测结果更新槽函数
void updateDetectionResultDisplay(const DetectionResult& result);
// 日志更新槽函数
void updateDetectionLog(const QString& message);
// 清空日志槽函数
void clearDetectionLogUI();
// 处理相机点击事件
void onCameraClicked(int cameraIndex);
@ -103,8 +92,8 @@ private:
// 业务逻辑处理类
BagThreadPositionPresenter* m_presenter = nullptr;
// 日志模型
QStringListModel* m_logModel = nullptr;
// 日志辅助类
DetectLogHelper* m_logHelper = nullptr;
// 设备状态显示widget的引用
DeviceStatusWidget* m_deviceStatusWidget = nullptr;
@ -134,10 +123,5 @@ private:
void setupContextMenu();
void showContextMenu(const QPoint& pos, QGraphicsView* view);
bool saveDetectionDataToFile(const QString& filePath, int cameraIndex);
private:
// 日志去重相关
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
int m_lastLogCount = 0; // 最后一条日志的重复次数
};
#endif // MAINWINDOW_H

View File

@ -40,9 +40,10 @@ public:
/**
* @brief
* @param multiTargetData
* @param errorCode 0-2201
* @return 0--
*/
int SendCoordinateData(const MultiTargetData& multiTargetData);
int SendCoordinateData(const MultiTargetData& multiTargetData, int errorCode = 0);
/**
* @brief
@ -73,9 +74,16 @@ public:
* @param dsID dsID
* @param address
* @param successValue
* @param failValue
* @param emptyValue
* @param startValue /
*/
void SetPostCoordinateParams(const std::string& dsID, int address, int successValue, int failValue);
void SetPostCoordinateParams(const std::string& dsID, int address, int successValue, int emptyValue, int startValue);
/**
* @brief /
* @return 0--
*/
int SendStartDetectCommand();
/**
* @brief Z轴偏移量
@ -138,7 +146,8 @@ private:
std::string m_strPostCoordDsID; // 坐标发送后附加命令的dsID
int m_nPostCoordAddress; // 坐标发送后附加命令的地址
int m_nPostCoordSuccessValue; // 检测成功时的值
int m_nPostCoordFailValue; // 检测失败时的值
int m_nPostCoordEmptyValue; // 检测结果空时的值
int m_nPostCoordStartValue; // 开启检测/检测失败时的值
double m_dZOffset; // 第二个坐标Z轴偏移量
double m_dZMin; // Z轴最小值
double m_dZMax; // Z轴最大值

View File

@ -25,16 +25,17 @@ public:
int DetectBag( std::vector<SVzNL3DLaserLine>& detectionDataCache,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
DetectionResult& detectionResult);
DetectionResult& detectionResult,
double planeHeightOffset = -10.0);
int DetectBag( std::vector<SVzNLXYZRGBDLaserLine>& detectionDataCache,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
@ -42,15 +43,16 @@ public:
const RGB& rgbColorPattern,
const double frontColorTemplate[RGN_HIST_SIZE],
const double backColorTemplate[RGN_HIST_SIZE],
DetectionResult& detectionResult);
DetectionResult& detectionResult,
double planeHeightOffset = -10.0);
// 新增处理统一数据格式和项目类型的DetectBag函数
int DetectBag( int cameraIndex,
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
ProjectType projectType,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
@ -58,7 +60,8 @@ public:
const RGB& rgbColorPattern,
const double frontColorTemplate[RGN_HIST_SIZE],
const double backColorTemplate[RGN_HIST_SIZE],
DetectionResult& detectionResult);
DetectionResult& detectionResult,
double planeHeightOffset = -10.0);
};

View File

@ -36,6 +36,7 @@ struct CurrentExecutionParams
int cameraIndex; // 相机序号
VrCameraParams cameraParam; // 相机参数
SSG_planeCalibPara planeCalibParam; // 调平参数
double planeHeightOffset = -10.0; // 平面高度偏移量
CalibMatrix handEyeCalibMatrix; // 手眼标定参数
VrBagParam bagParam; // 包裹参数(尺寸)
const TCPClient* pClient = nullptr; // 请求的客户端指针

View File

@ -2,6 +2,7 @@
#include "json/json.h"
#include "VrLog.h"
#include "VrError.h"
#include "SG_errCode.h"
#include <sstream>
#include <iomanip>
#include <thread>
@ -15,7 +16,8 @@ BolunteProtocol::BolunteProtocol()
, m_strPostCoordDsID("www.hc-system.com.RemoteMonitor")
, m_nPostCoordAddress(800)
, m_nPostCoordSuccessValue(10)
, m_nPostCoordFailValue(12)
, m_nPostCoordEmptyValue(12)
, m_nPostCoordStartValue(13)
, m_dZOffset(300.0)
, m_dZMin(-1500.0)
, m_dZMax(2500.0)
@ -120,7 +122,7 @@ int BolunteProtocol::SendPhotoResult(const std::string& packID, int camID, bool
}
}
int BolunteProtocol::SendCoordinateData(const MultiTargetData& multiTargetData)
int BolunteProtocol::SendCoordinateData(const MultiTargetData& multiTargetData, int errorCode)
{
if (!m_bConnected)
{
@ -128,46 +130,102 @@ int BolunteProtocol::SendCoordinateData(const MultiTargetData& multiTargetData)
return ERR_CODE(NET_ERR_NOTINIT);
}
std::string jsonStr = GenerateCoordinateJson(multiTargetData);
bool ret = m_pTcpServer->SendAllData(jsonStr.c_str(), jsonStr.length());
bool hasTargets = !multiTargetData.targets.empty();
// 根据检测结果和错误码确定返回值
// 1. 正常(有结果):发送坐标 + 附加指令 10
// 2. 空垛errorCode == -2201只发送附加指令 12
// 3. 错误(相机开启失败、没有结果且不是空垛):只发送附加指令 13
int resultValue;
if (hasTargets) {
resultValue = m_nPostCoordSuccessValue; // 10
} else if (SX_BAG_TRAY_EMPTY == errorCode) {
resultValue = m_nPostCoordEmptyValue; // 12
} else {
resultValue = m_nPostCoordStartValue; // 13
}
// 正常情况:发送坐标数据
if (hasTargets) {
std::string jsonStr = GenerateCoordinateJson(multiTargetData);
bool ret = m_pTcpServer->SendAllData(jsonStr.c_str(), jsonStr.length());
if (!ret) {
LOG_ERROR("Failed to send coordinate data\n");
return ERR_CODE(NET_ERR_SEND_DATA);
}
LOG_INFO("Sent coordinate data: count=%d\n", multiTargetData.count);
}
// 发送附加命令
if (!m_strPostCoordDsID.empty())
{
if (hasTargets) {
// 正常情况延迟100ms后发送附加指令
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// 生成附加命令JSON
VA::Json::Value postCmd;
postCmd["dsID"] = m_strPostCoordDsID;
postCmd["reqType"] = "command";
VA::Json::Value cmdData(VA::Json::arrayValue);
cmdData.append("rewriteData");
cmdData.append(std::to_string(m_nPostCoordAddress));
cmdData.append(std::to_string(resultValue));
cmdData.append("0");
postCmd["cmdData"] = cmdData;
VA::Json::FastWriter writer;
std::string postCmdStr = writer.write(postCmd);
m_pTcpServer->SendAllData(postCmdStr.c_str(), postCmdStr.length());
LOG_INFO("Sent post coordinate command: hasTargets=%d, errorCode=%d, resultValue=%d\n", hasTargets, errorCode, resultValue);
}
return SUCCESS;
}
int BolunteProtocol::SendStartDetectCommand()
{
if (!m_bConnected)
{
LOG_ERROR("TCP server has no connected client\n");
return ERR_CODE(NET_ERR_NOTINIT);
}
// 如果附加指令dsID为空则不发送
if (m_strPostCoordDsID.empty())
{
LOG_INFO("PostCoordinateDsID is empty, skip sending start detect command\n");
return SUCCESS;
}
// 生成开启检测附加命令JSON
VA::Json::Value postCmd;
postCmd["dsID"] = m_strPostCoordDsID;
postCmd["reqType"] = "command";
VA::Json::Value cmdData(VA::Json::arrayValue);
cmdData.append("rewriteData");
cmdData.append(std::to_string(m_nPostCoordAddress));
cmdData.append(std::to_string(m_nPostCoordStartValue));
cmdData.append("0");
postCmd["cmdData"] = cmdData;
VA::Json::FastWriter writer;
std::string postCmdStr = writer.write(postCmd);
bool ret = m_pTcpServer->SendAllData(postCmdStr.c_str(), postCmdStr.length());
if (ret)
{
LOG_INFO("Sent coordinate data: count=%d\n", multiTargetData.count);
// 100ms后发送附加命令根据检测结果拼接成功/失败值
if (!m_strPostCoordDsID.empty())
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 根据检测结果选择成功/失败值
bool detectionSuccess = !multiTargetData.targets.empty();
int resultValue = detectionSuccess ? m_nPostCoordSuccessValue : m_nPostCoordFailValue;
// 动态生成附加命令JSON
VA::Json::Value postCmd;
postCmd["dsID"] = m_strPostCoordDsID;
postCmd["reqType"] = "command";
VA::Json::Value cmdData(VA::Json::arrayValue);
cmdData.append("rewriteData");
cmdData.append(std::to_string(m_nPostCoordAddress));
cmdData.append(std::to_string(resultValue));
cmdData.append("0");
postCmd["cmdData"] = cmdData;
VA::Json::FastWriter writer;
std::string postCmdStr = writer.write(postCmd);
m_pTcpServer->SendAllData(postCmdStr.c_str(), postCmdStr.length());
LOG_INFO("Sent post coordinate command: success=%d, value=%d\n", detectionSuccess, resultValue);
}
LOG_INFO("Sent start detect command: value=%d\n", m_nPostCoordStartValue);
return SUCCESS;
}
else
{
LOG_ERROR("Failed to send coordinate data\n");
LOG_ERROR("Failed to send start detect command\n");
return ERR_CODE(NET_ERR_SEND_DATA);
}
}
@ -192,12 +250,13 @@ void BolunteProtocol::SetDsID(const std::string& dsID)
m_strDsID = dsID;
}
void BolunteProtocol::SetPostCoordinateParams(const std::string& dsID, int address, int successValue, int failValue)
void BolunteProtocol::SetPostCoordinateParams(const std::string& dsID, int address, int successValue, int emptyValue, int startValue)
{
m_strPostCoordDsID = dsID;
m_nPostCoordAddress = address;
m_nPostCoordSuccessValue = successValue;
m_nPostCoordFailValue = failValue;
m_nPostCoordEmptyValue = emptyValue;
m_nPostCoordStartValue = startValue;
}
void BolunteProtocol::SetZOffset(double zOffset)

View File

@ -11,12 +11,13 @@ DetectPresenter::~DetectPresenter()
}
int DetectPresenter::DetectBag(std::vector<SVzNL3DLaserLine>& detectionDataCache,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
DetectionResult& detectionResult)
DetectionResult& detectionResult,
double planeHeightOffset)
{
if (detectionDataCache.empty()) {
LOG_WARNING("No cached detection data available\n");
@ -25,7 +26,7 @@ int DetectPresenter::DetectBag(std::vector<SVzNL3DLaserLine>& detectionDataCache
// 2. 数据预处理:调平和去除地面(使用当前相机的调平参数)
for (size_t i = 0; i < detectionDataCache.size(); i++) {
sg_lineDataR(&detectionDataCache[i], cameraCalibParam.planeCalib, cameraCalibParam.planeHeight);
sg_lineDataR(&detectionDataCache[i], cameraCalibParam.planeCalib, cameraCalibParam.planeHeight + planeHeightOffset);
}
// 3. 调用算法检测接口(使用当前相机的调平参数)
@ -77,10 +78,8 @@ int DetectPresenter::DetectBag(std::vector<SVzNL3DLaserLine>& detectionDataCache
detectionResult.positions.push_back(pos);
if(debugParam.enableDebug && debugParam.printDetailLog){
LOG_INFO("[Algo Thread] Object %zu Eye Coords: X=%.2f, Y=%.2f, Z=%.2f\n",
i, obj.centerPos.x, obj.centerPos.y, obj.centerPos.z);
LOG_INFO("[Algo Thread] Object %zu Robot Coords: X=%.2f, Y=%.2f, Z=%.2f, Roll=%.2f, Pitch=%.2f, Yaw=%.2f\n",
i, pos.x, pos.y, pos.z, pos.roll, pos.pitch, pos.yaw);
LOG_INFO("[Algo Thread] Object %zu Eye Coords: X=%.2f, Y=%.2f, Z=%.2f\n", i, obj.centerPos.x, obj.centerPos.y, obj.centerPos.z);
LOG_INFO("[Algo Thread] Object %zu Robot Coords: X=%.2f, Y=%.2f, Z=%.2f, Roll=%.2f, Pitch=%.2f, Yaw=%.2f\n", i, pos.x, pos.y, pos.z, pos.roll, pos.pitch, pos.yaw);
}
}
@ -90,8 +89,8 @@ int DetectPresenter::DetectBag(std::vector<SVzNL3DLaserLine>& detectionDataCache
int DetectPresenter::DetectBag(std::vector<SVzNLXYZRGBDLaserLine>& detectionDataCache,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
@ -99,7 +98,8 @@ int DetectPresenter::DetectBag(std::vector<SVzNLXYZRGBDLaserLine>& detectionData
const RGB& rgbColorPattern,
const double frontColorTemplate[RGN_HIST_SIZE],
const double backColorTemplate[RGN_HIST_SIZE],
DetectionResult& detectionResult)
DetectionResult& detectionResult,
double planeHeightOffset)
{
if (detectionDataCache.empty()) {
@ -109,7 +109,7 @@ int DetectPresenter::DetectBag(std::vector<SVzNLXYZRGBDLaserLine>& detectionData
// 2. 数据预处理:调平和去除地面(使用当前相机的调平参数)
for (size_t i = 0; i < detectionDataCache.size(); i++) {
sg_lineDataR_RGBD(&detectionDataCache[i], cameraCalibParam.planeCalib, cameraCalibParam.planeHeight);
sg_lineDataR_RGBD(&detectionDataCache[i], cameraCalibParam.planeCalib, cameraCalibParam.planeHeight + planeHeightOffset);
}
@ -183,8 +183,8 @@ int DetectPresenter::DetectBag(
int cameraIndex,
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
ProjectType projectType,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const SG_bagPositionParam& algoParam,
const SSG_planeCalibPara& cameraCalibParam,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
@ -192,7 +192,8 @@ int DetectPresenter::DetectBag(
const RGB& rgbColorPattern,
const double frontColorTemplate[RGN_HIST_SIZE],
const double backColorTemplate[RGN_HIST_SIZE],
DetectionResult& detectionResult)
DetectionResult& detectionResult,
double planeHeightOffset)
{
if (laserLines.empty()) {
LOG_WARNING("No laser lines data available\n");
@ -234,7 +235,7 @@ int DetectPresenter::DetectBag(
algoParam.growParam.yDeviation_max, algoParam.growParam.zDeviation_max,
algoParam.growParam.maxLineSkipNum, algoParam.growParam.maxSkipDistance);
LOG_INFO(" Plane height: %.3f\n", cameraCalibParam.planeHeight);
LOG_INFO(" Plane height: %.3f offset: %.3f\n", cameraCalibParam.planeHeight, planeHeightOffset);
LOG_INFO(" Plane calibration matrix: [%.3f, %.3f, %.3f; %.3f, %.3f, %.3f; %.3f, %.3f, %.3f]\n",
cameraCalibParam.planeCalib[0], cameraCalibParam.planeCalib[1], cameraCalibParam.planeCalib[2],
cameraCalibParam.planeCalib[3], cameraCalibParam.planeCalib[4], cameraCalibParam.planeCalib[5],
@ -248,7 +249,8 @@ int DetectPresenter::DetectBag(
std::vector<SVzNLXYZRGBDLaserLine> rgbdData;
int convertResult = dataLoader.ConvertToSVzNLXYZRGBDLaserLine(laserLines, rgbdData);
if (convertResult == SUCCESS && !rgbdData.empty()) {
nRet = DetectBag(rgbdData, algoParam, cameraCalibParam, debugParam, dataLoader, clibMatrix, hsvCmpParam, rgbColorPattern, frontColorTemplate, backColorTemplate, detectionResult);
nRet = DetectBag(rgbdData, algoParam, cameraCalibParam, debugParam, dataLoader, clibMatrix, hsvCmpParam, rgbColorPattern, frontColorTemplate,
backColorTemplate, detectionResult, planeHeightOffset);
// 清理RGBD内存
dataLoader.FreeConvertedData(rgbdData);
@ -263,7 +265,7 @@ int DetectPresenter::DetectBag(
std::vector<SVzNL3DLaserLine> xyzData;
int convertResult = dataLoader.ConvertToSVzNL3DLaserLine(laserLines, xyzData);
if (convertResult == SUCCESS && !xyzData.empty()) {
nRet = DetectBag(xyzData, algoParam, cameraCalibParam, debugParam, dataLoader, clibMatrix, detectionResult);
nRet = DetectBag(xyzData, algoParam, cameraCalibParam, debugParam, dataLoader, clibMatrix, detectionResult, planeHeightOffset);
// 清理XYZ内存
dataLoader.FreeConvertedData(xyzData);

View File

@ -452,7 +452,8 @@ int GrabBagPresenter::InitBolunteProtocol()
configResult.bolunteConfig.postCoordinateDsID,
configResult.bolunteConfig.postCoordinateAddress,
configResult.bolunteConfig.postCoordinateSuccessValue,
configResult.bolunteConfig.postCoordinateFailValue);
configResult.bolunteConfig.postCoordinateEmptyValue,
configResult.bolunteConfig.postCoordinateStartValue);
// 设置连接状态回调
m_pBolunteProtocol->SetConnectionCallback([this](bool connected) {
@ -1546,7 +1547,8 @@ int GrabBagPresenter::_DetectTask()
m_pParameterManager->GetHsvCmpParam(),
m_pParameterManager->GetRgbColorPattern(),
m_pParameterManager->GetFrontColorTemplate(),
m_pParameterManager->GetBackColorTemplate(), detectionResult);
m_pParameterManager->GetBackColorTemplate(), detectionResult,
m_currentExecutionParams.planeHeightOffset);
LOG_INFO("[Algo Thread] DetectBag return: %d\n", nRet);
} catch (const std::exception& e) {
LOG_ERROR("[Algo Thread] Exception in DetectBag: %s\n", e.what());
@ -1567,10 +1569,6 @@ int GrabBagPresenter::_DetectTask()
m_pStatus->OnStatusUpdate(QString("检测%1").arg(SUCCESS == nRet ? "成功": err).toStdString());
}
if(SUCCESS != nRet){
ERR_CODE_RETURN(nRet);
}
LOG_INFO("[Algo Thread] algo detected %zu objects time : %.2f ms\n", detectionResult.positions.size(), oTimeUtils.GetElapsedTimeInMilliSec());
// 8. 返回检测结果
@ -1588,7 +1586,6 @@ int GrabBagPresenter::_DetectTask()
// 更新机械臂协议状态(发送转换后的目标位置数据)
_SendDetectionResultToRobot(detectionResult, m_currentExecutionParams.cameraIndex);
// 设置机械臂工作状态为相应相机工作完成相机ID从1开始
if (m_pRobotProtocol) {
uint16_t workStatus = (m_currentExecutionParams.cameraIndex == 1) ? RobotProtocol::WORK_STATUS_CAMERA1_DONE : RobotProtocol::WORK_STATUS_CAMERA2_DONE;
@ -1734,12 +1731,11 @@ void GrabBagPresenter::_SendDetectionResultToRobot(const DetectionResult& detect
// 设置相机ID
multiTargetData.cameraId = cameraIndex;
// 发送坐标数据
int result = m_pBolunteProtocol->SendCoordinateData(multiTargetData);
// 发送坐标数据,传递错误码以区分正常/空垛/错误
int result = m_pBolunteProtocol->SendCoordinateData(multiTargetData, detectionResult.errorCode);
if (result == SUCCESS) {
LOG_INFO("[Bolunte] SendCoordinateData successful for camera: %d, targets: %d\n",
cameraIndex, multiTargetData.count);
LOG_INFO("[Bolunte] SendCoordinateData successful for camera: %d, targets: %d\n", cameraIndex, multiTargetData.count);
if (m_pStatus) {
m_pStatus->OnStatusUpdate(QString("机械臂坐标发送成功").toStdString());
}
@ -1982,6 +1978,10 @@ bool GrabBagPresenter::OnBoluntePhotoCommand(int camID)
if (result != SUCCESS) {
LOG_ERROR("Detection failed for camera %d, error: %d\n", cameraIndex, result);
SetWorkStatus(WorkStatus::Error);
// 开启检测失败,发送开启检测失败附加指令
if (m_pBolunteProtocol && m_bBolunteConnected) {
m_pBolunteProtocol->SendStartDetectCommand();
}
return false;
}
@ -2282,6 +2282,7 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams()
m_currentExecutionParams.planeCalibParam.invRMatrix[i] = identityMatrix9[i];
}
m_currentExecutionParams.planeCalibParam.planeHeight = -1.0;
m_currentExecutionParams.planeHeightOffset = -10.0; // 默认高度偏移
// 3. 初始化手眼标定矩阵为单位矩阵(默认值)
double identityMatrix16[16] = {
@ -2357,6 +2358,7 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams()
m_currentExecutionParams.planeCalibParam.invRMatrix[i] = currentCamera->planeCalibParam.invRMatrix[i];
}
m_currentExecutionParams.planeCalibParam.planeHeight = currentCamera->planeCalibParam.planeHeight;
m_currentExecutionParams.planeHeightOffset = currentCamera->planeCalibParam.planeHeightOffset;
LOG_INFO("Using calibrated plane parameters from camera %s (height=%.3f)\n", currentCamera->cameraName.c_str(), currentCamera->planeCalibParam.planeHeight);
} else {
LOG_INFO("Camera %s is not calibrated, using identity matrix for plane calibration\n", currentCamera->cameraName.c_str());
@ -2403,6 +2405,7 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams(const WorkPositionConfig* w
m_currentExecutionParams.planeCalibParam.invRMatrix[i] = identityMatrix9[i];
}
m_currentExecutionParams.planeCalibParam.planeHeight = -1.0;
m_currentExecutionParams.planeHeightOffset = -10.0; // 默认高度偏移
// 3. 初始化手眼标定矩阵为单位矩阵(默认值)
double identityMatrix16[16] = {
@ -2450,11 +2453,11 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams(const WorkPositionConfig* w
m_currentExecutionParams.planeCalibParam.invRMatrix[i] = camera->planeCalibParam.invRMatrix[i];
}
m_currentExecutionParams.planeCalibParam.planeHeight = camera->planeCalibParam.planeHeight;
m_currentExecutionParams.planeHeightOffset = camera->planeCalibParam.planeHeightOffset;
LOG_INFO("Using calibrated plane parameters from camera %s (height=%.3f)\n",
camera->cameraName.c_str(), camera->planeCalibParam.planeHeight);
} else {
LOG_INFO("Camera %s is not calibrated, using identity matrix for plane calibration\n",
camera->cameraName.c_str());
LOG_INFO("Camera %s is not calibrated, using identity matrix for plane calibration\n", camera->cameraName.c_str());
}
// 6. 设置手眼标定参数(从相机配置中获取)
@ -2466,8 +2469,7 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams(const WorkPositionConfig* w
}
LOG_INFO("Using calibrated hand-eye matrix from camera %s\n", camera->cameraName.c_str());
} else {
LOG_INFO("Camera %s hand-eye is not calibrated, using identity matrix\n",
camera->cameraName.c_str());
LOG_INFO("Camera %s hand-eye is not calibrated, using identity matrix\n", camera->cameraName.c_str());
}
LOG_INFO("Current execution parameters updated successfully\n");

View File

@ -2,7 +2,7 @@
#define VERSION_H
#define GRABBAG_VERSION_STRING "1.3.5"
#define GRABBAG_BUILD_STRING "5"
#define GRABBAG_BUILD_STRING "7"
#define GRABBAG_FULL_VERSION_STRING "V" GRABBAG_VERSION_STRING "_" GRABBAG_BUILD_STRING
// 获取版本信息的便捷函数

View File

@ -1,8 +1,10 @@
# 1.3.5
## build_6 & 7 20260128
1. 修改协议附属命令结果增加失败回复13
## build_5 20260126
1. 修改协议附属命令结果
## build_4 20260124
1. 修复算法检测线程在析构时未退出的问题

View File

@ -392,7 +392,8 @@ void DialogCamera::LoadBolunteConfig()
ui->lineEdit_bolunte_postcmd->setText(QString::fromStdString(configResult->bolunteConfig.postCoordinateDsID));
ui->spinBox_bolunte_postaddr->setValue(configResult->bolunteConfig.postCoordinateAddress);
ui->spinBox_bolunte_postsuccess->setValue(configResult->bolunteConfig.postCoordinateSuccessValue);
ui->spinBox_bolunte_postfail->setValue(configResult->bolunteConfig.postCoordinateFailValue);
ui->spinBox_bolunte_postempty->setValue(configResult->bolunteConfig.postCoordinateEmptyValue);
ui->spinBox_bolunte_poststart->setValue(configResult->bolunteConfig.postCoordinateStartValue);
LOG_INFO("Loaded Bolunte config: port=%d, zOffset=%.1f\n",
configResult->bolunteConfig.port,
@ -424,7 +425,8 @@ bool DialogCamera::SaveBolunteConfig()
configResult->bolunteConfig.postCoordinateDsID = ui->lineEdit_bolunte_postcmd->text().toStdString();
configResult->bolunteConfig.postCoordinateAddress = ui->spinBox_bolunte_postaddr->value();
configResult->bolunteConfig.postCoordinateSuccessValue = ui->spinBox_bolunte_postsuccess->value();
configResult->bolunteConfig.postCoordinateFailValue = ui->spinBox_bolunte_postfail->value();
configResult->bolunteConfig.postCoordinateEmptyValue = ui->spinBox_bolunte_postempty->value();
configResult->bolunteConfig.postCoordinateStartValue = ui->spinBox_bolunte_poststart->value();
LOG_INFO("Saving Bolunte config: port=%d, zOffset=%.1f\n",
configResult->bolunteConfig.port,

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>660</width>
<height>650</height>
<height>690</height>
</rect>
</property>
<property name="windowTitle">
@ -226,7 +226,7 @@ QPushButton:disabled {
<x>50</x>
<y>330</y>
<width>560</width>
<height>261</height>
<height>301</height>
</rect>
</property>
<property name="font">
@ -669,7 +669,7 @@ background-color: rgb(47, 48, 52);</string>
<number>10</number>
</property>
</widget>
<widget class="QLabel" name="label_bolunte_postfail">
<widget class="QLabel" name="label_bolunte_postempty">
<property name="geometry">
<rect>
<x>360</x>
@ -687,10 +687,10 @@ background-color: rgb(47, 48, 52);</string>
<string notr="true">color: rgb(221, 225, 233);</string>
</property>
<property name="text">
<string>失败值:</string>
<string>值:</string>
</property>
</widget>
<widget class="QSpinBox" name="spinBox_bolunte_postfail">
<widget class="QSpinBox" name="spinBox_bolunte_postempty">
<property name="geometry">
<rect>
<x>420</x>
@ -718,12 +718,61 @@ background-color: rgb(47, 48, 52);</string>
<number>12</number>
</property>
</widget>
<widget class="QLabel" name="label_bolunte_poststart">
<property name="geometry">
<rect>
<x>20</x>
<y>260</y>
<width>80</width>
<height>30</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(221, 225, 233);</string>
</property>
<property name="text">
<string>开启值:</string>
</property>
</widget>
<widget class="QSpinBox" name="spinBox_bolunte_poststart">
<property name="geometry">
<rect>
<x>110</x>
<y>260</y>
<width>80</width>
<height>30</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(221, 225, 233);
background-color: rgb(47, 48, 52);</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>13</number>
</property>
</widget>
</widget>
<widget class="QPushButton" name="btn_save">
<property name="geometry">
<rect>
<x>190</x>
<y>600</y>
<y>640</y>
<width>111</width>
<height>38</height>
</rect>
@ -744,7 +793,7 @@ background-color: rgb(47, 48, 52);</string>
<property name="geometry">
<rect>
<x>340</x>
<y>600</y>
<y>640</y>
<width>111</width>
<height>38</height>
</rect>

View File

@ -987,6 +987,9 @@ void DialogCameraLevel::checkAndDisplayCalibrationStatus(int workPosIndex, int c
.arg(cameraName));
ui->label_level_result->setAlignment(Qt::AlignCenter);
}
// 加载高度偏移值
loadHeightOffset(workPosIndex, cameraIndex);
}
// 设置调平时的状态回调
@ -1015,15 +1018,125 @@ void DialogCameraLevel::restorePresenterStatusCallback()
LOG_DEBUG("Presenter status callback already restored, skipping\n");
return;
}
if (!m_presenter) {
LOG_ERROR("Presenter is null, cannot restore status callback\n");
return;
}
// 恢复Presenter的状态回调 - 使用GrabBagPresenter的静态回调
m_presenter->SetCameraStatusCallback(&GrabBagPresenter::_StaticCameraNotify, m_presenter);
LOG_INFO("Presenter status callback restored for all cameras\n");
}
// 加载高度偏移值
void DialogCameraLevel::loadHeightOffset(int workPosIndex, int cameraIndex)
{
if (!m_pConfigResult || !m_presenter) {
return;
}
if (workPosIndex < 0 || workPosIndex >= static_cast<int>(m_pConfigResult->workPositions.size())) {
return;
}
if (cameraIndex < 0 || cameraIndex >= static_cast<int>(m_cameraList.size())) {
return;
}
const auto& workPos = m_pConfigResult->workPositions[workPosIndex];
int configCameraIndex = cameraIndex + 1; // 转换为1-based索引
// 查找对应的相机配置
for (const auto& cam : workPos.cameras) {
if (cam.cameraIndex == configCameraIndex) {
ui->doubleSpinBox_heightoffset->setValue(cam.planeCalibParam.planeHeightOffset);
LOG_INFO("Loaded height offset: %.2f for camera %d at work position %s\n",
cam.planeCalibParam.planeHeightOffset, configCameraIndex,
workPos.name.c_str());
return;
}
}
// 如果没找到,使用默认值
ui->doubleSpinBox_heightoffset->setValue(-10.0);
}
// 保存高度偏移值
bool DialogCameraLevel::saveHeightOffset(int workPosIndex, int cameraIndex, double heightOffset)
{
if (!m_pConfigResult || !m_presenter) {
LOG_ERROR("ConfigResult or Presenter is null\n");
return false;
}
if (workPosIndex < 0 || workPosIndex >= static_cast<int>(m_pConfigResult->workPositions.size())) {
LOG_ERROR("Invalid work position index: %d\n", workPosIndex);
return false;
}
if (cameraIndex < 0 || cameraIndex >= static_cast<int>(m_cameraList.size())) {
LOG_ERROR("Invalid camera index: %d\n", cameraIndex);
return false;
}
try {
IVrConfig* config = m_presenter->GetConfig();
if (!config) {
LOG_ERROR("Config is null\n");
return false;
}
QString configPath = PathManager::GetInstance().GetConfigFilePath();
ConfigResult configResult = config->LoadConfig(configPath.toStdString());
const auto& workPos = m_pConfigResult->workPositions[workPosIndex];
int configCameraIndex = cameraIndex + 1;
// 查找并更新对应的相机配置
for (auto& wp : configResult.workPositions) {
if (wp.id == workPos.id) {
for (auto& cam : wp.cameras) {
if (cam.cameraIndex == configCameraIndex) {
cam.planeCalibParam.planeHeightOffset = heightOffset;
// 保存配置
bool saveResult = config->SaveConfig(configPath.toStdString(), configResult);
if (saveResult) {
LOG_INFO("Height offset saved: %.2f for camera %d at work position %s\n",
heightOffset, configCameraIndex, workPos.name.c_str());
}
return saveResult;
}
}
break;
}
}
LOG_ERROR("Camera config not found for camera index %d\n", configCameraIndex);
return false;
} catch (const std::exception& e) {
LOG_ERROR("Exception in saveHeightOffset: %s\n", e.what());
return false;
}
}
// 保存高度偏移按钮点击
void DialogCameraLevel::on_btn_save_heightoffset_clicked()
{
if (m_currentWorkPosIndex < 0 || m_currentCameraIndex < 0) {
StyledMessageBox::warning(this, "错误", "请先选择工位和相机!");
return;
}
double heightOffset = ui->doubleSpinBox_heightoffset->value();
if (saveHeightOffset(m_currentWorkPosIndex, m_currentCameraIndex, heightOffset)) {
StyledMessageBox::information(this, "成功", "高度偏移已保存!");
} else {
StyledMessageBox::warning(this, "失败", "保存高度偏移失败!");
}
}

View File

@ -39,6 +39,7 @@ private slots:
void on_btn_cancel_clicked();
void on_combo_camera_currentIndexChanged(int index);
void on_combo_workposition_currentIndexChanged(int index);
void on_btn_save_heightoffset_clicked();
private:
Ui::DialogCameraLevel *ui;
@ -115,6 +116,10 @@ private:
// 检查并显示相机标定状态
void checkAndDisplayCalibrationStatus(int workPosIndex, int cameraIndex);
// 加载和保存高度偏移
void loadHeightOffset(int workPosIndex, int cameraIndex);
bool saveHeightOffset(int workPosIndex, int cameraIndex, double heightOffset);
};
#endif // DialogCameraLevel_H

View File

@ -20,7 +20,7 @@
<property name="geometry">
<rect>
<x>20</x>
<y>150</y>
<y>190</y>
<width>111</width>
<height>31</height>
</rect>
@ -37,6 +37,93 @@
<string>调平结果:</string>
</property>
</widget>
<widget class="QLabel" name="label_heightoffset">
<property name="geometry">
<rect>
<x>20</x>
<y>150</y>
<width>111</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(221, 225, 233);</string>
</property>
<property name="text">
<string>高度偏移:</string>
</property>
</widget>
<widget class="QDoubleSpinBox" name="doubleSpinBox_heightoffset">
<property name="geometry">
<rect>
<x>140</x>
<y>150</y>
<width>120</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(221, 225, 233);
background-color: rgb(47, 48, 52);</string>
</property>
<property name="minimum">
<double>-1000.000000000000000</double>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>-10.000000000000000</double>
</property>
<property name="suffix">
<string> mm</string>
</property>
</widget>
<widget class="QPushButton" name="btn_save_heightoffset">
<property name="geometry">
<rect>
<x>280</x>
<y>150</y>
<width>80</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">
QPushButton {
color: rgb(221, 225, 233);
background-color: rgb(0, 120, 212);
border: none;
border-radius: 3px;
padding: 5px 10px;
}
QPushButton:hover {
background-color: rgb(0, 140, 232);
}
QPushButton:pressed {
background-color: rgb(0, 100, 192);
}
</string>
</property>
<property name="text">
<string>保存</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
@ -107,9 +194,9 @@
<property name="geometry">
<rect>
<x>140</x>
<y>150</y>
<y>190</y>
<width>501</width>
<height>241</height>
<height>201</height>
</rect>
</property>
<property name="font">

View File

@ -70,7 +70,8 @@ struct BolunteConfig
std::string postCoordinateDsID = "www.hc-system.com.RemoteMonitor"; // 坐标发送后附加命令的dsID
int postCoordinateAddress = 800; // 坐标发送后附加命令的地址
int postCoordinateSuccessValue = 10; // 检测成功时的值
int postCoordinateFailValue = 12; // 检测失败时的值
int postCoordinateEmptyValue = 12; // 检测结果空时的值
int postCoordinateStartValue = 13; // 开启检测/检测失败时的值
};
/**

View File

@ -269,7 +269,9 @@ ConfigResult CVrConfig::LoadConfig(const std::string& filePath)
cameraParam.isCalibrated = cameraCalibParamElement->BoolAttribute("isCalibrated");
if (cameraCalibParamElement->Attribute("planeHeight"))
cameraParam.planeHeight = cameraCalibParamElement->DoubleAttribute("planeHeight");
if (cameraCalibParamElement->Attribute("planeHeightOffset"))
cameraParam.planeHeightOffset = cameraCalibParamElement->DoubleAttribute("planeHeightOffset");
// 读取旋转矩阵planeCalib[9] (3x3矩阵)
if (cameraCalibParamElement->Attribute("planeCalib_00"))
cameraParam.planeCalib[0] = cameraCalibParamElement->DoubleAttribute("planeCalib_00");
@ -494,10 +496,25 @@ ConfigResult CVrConfig::LoadConfig(const std::string& filePath)
result.bolunteConfig.postCoordinateSuccessValue = postSuccessElement->IntText();
}
XMLElement* postFailElement = bolunteConfigElement->FirstChildElement("PostCoordinateFailValue");
if (postFailElement)
XMLElement* postEmptyElement = bolunteConfigElement->FirstChildElement("PostCoordinateEmptyValue");
if (postEmptyElement)
{
result.bolunteConfig.postCoordinateFailValue = postFailElement->IntText();
result.bolunteConfig.postCoordinateEmptyValue = postEmptyElement->IntText();
}
else
{
// 兼容旧配置:如果没有 EmptyValue尝试读取旧的 FailValue
XMLElement* postFailElement = bolunteConfigElement->FirstChildElement("PostCoordinateFailValue");
if (postFailElement)
{
result.bolunteConfig.postCoordinateEmptyValue = postFailElement->IntText();
}
}
XMLElement* postStartElement = bolunteConfigElement->FirstChildElement("PostCoordinateStartValue");
if (postStartElement)
{
result.bolunteConfig.postCoordinateStartValue = postStartElement->IntText();
}
}
@ -649,7 +666,8 @@ bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResu
cameraCalibParamElement->SetAttribute("cameraName", cameraParam.cameraName.c_str());
cameraCalibParamElement->SetAttribute("isCalibrated", cameraParam.isCalibrated);
cameraCalibParamElement->SetAttribute("planeHeight", cameraParam.planeHeight);
cameraCalibParamElement->SetAttribute("planeHeightOffset", cameraParam.planeHeightOffset);
// 保存旋转矩阵planeCalib[9] (3x3矩阵)
cameraCalibParamElement->SetAttribute("planeCalib_00", cameraParam.planeCalib[0]);
cameraCalibParamElement->SetAttribute("planeCalib_01", cameraParam.planeCalib[1]);
@ -788,9 +806,13 @@ bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResu
boluntePostSuccessElement->SetText(configResult.bolunteConfig.postCoordinateSuccessValue);
bolunteConfigElement->InsertEndChild(boluntePostSuccessElement);
XMLElement* boluntePostFailElement = doc.NewElement("PostCoordinateFailValue");
boluntePostFailElement->SetText(configResult.bolunteConfig.postCoordinateFailValue);
bolunteConfigElement->InsertEndChild(boluntePostFailElement);
XMLElement* boluntePostEmptyElement = doc.NewElement("PostCoordinateEmptyValue");
boluntePostEmptyElement->SetText(configResult.bolunteConfig.postCoordinateEmptyValue);
bolunteConfigElement->InsertEndChild(boluntePostEmptyElement);
XMLElement* boluntePostStartElement = doc.NewElement("PostCoordinateStartValue");
boluntePostStartElement->SetText(configResult.bolunteConfig.postCoordinateStartValue);
bolunteConfigElement->InsertEndChild(boluntePostStartElement);
// 保存工作点配置(新增)
SaveWorkPositions(doc, root, configResult.workPositions);

View File

@ -135,6 +135,8 @@ void CVrConfig::ParseWorkPositions(XMLElement* workPositionsElement, ConfigResul
cam.planeCalibParam.isCalibrated = planeCalibElement->BoolAttribute("isCalibrated");
if (planeCalibElement->Attribute("planeHeight"))
cam.planeCalibParam.planeHeight = planeCalibElement->DoubleAttribute("planeHeight");
if (planeCalibElement->Attribute("planeHeightOffset"))
cam.planeCalibParam.planeHeightOffset = planeCalibElement->DoubleAttribute("planeHeightOffset");
ParseMatrix3x3(planeCalibElement, "planeCalib", cam.planeCalibParam.planeCalib);
ParseMatrix3x3(planeCalibElement, "invRMatrix", cam.planeCalibParam.invRMatrix);
@ -258,17 +260,20 @@ void CVrConfig::SaveWorkPositions(XMLDocument& doc, XMLElement* root, const std:
if (!cam.defaultPackageId.empty())
cameraElement->SetAttribute("defaultPackageId", cam.defaultPackageId.c_str());
// 保存平面校准参数
if (cam.planeCalibParam.isCalibrated)
// 保存平面校准参数即使未校准也保存planeHeightOffset
{
XMLElement* planeCalibElement = doc.NewElement("PlaneCalib");
planeCalibElement->SetAttribute("cameraIndex", cam.planeCalibParam.cameraIndex);
planeCalibElement->SetAttribute("cameraName", cam.planeCalibParam.cameraName.c_str());
planeCalibElement->SetAttribute("isCalibrated", cam.planeCalibParam.isCalibrated);
planeCalibElement->SetAttribute("planeHeight", cam.planeCalibParam.planeHeight);
planeCalibElement->SetAttribute("planeHeightOffset", cam.planeCalibParam.planeHeightOffset);
SaveMatrix3x3(planeCalibElement, "planeCalib", cam.planeCalibParam.planeCalib);
SaveMatrix3x3(planeCalibElement, "invRMatrix", cam.planeCalibParam.invRMatrix);
if (cam.planeCalibParam.isCalibrated)
{
SaveMatrix3x3(planeCalibElement, "planeCalib", cam.planeCalibParam.planeCalib);
SaveMatrix3x3(planeCalibElement, "invRMatrix", cam.planeCalibParam.invRMatrix);
}
cameraElement->InsertEndChild(planeCalibElement);
}

View File

@ -279,6 +279,7 @@
<PostCoordinateDsID>www.hc-system.com.RemoteMonitor</PostCoordinateDsID>
<PostCoordinateAddress>800</PostCoordinateAddress>
<PostCoordinateSuccessValue>10</PostCoordinateSuccessValue>
<PostCoordinateFailValue>12</PostCoordinateFailValue>
<PostCoordinateEmptyValue>12</PostCoordinateEmptyValue>
<PostCoordinateStartValue>13</PostCoordinateStartValue>
</BolunteConfig>
</GrabBagConfig>

View File

@ -96,10 +96,9 @@ MainWindow::MainWindow(QWidget *parent)
// 为第二个图像视图初始化GraphicsScene
QGraphicsScene* scene2 = new QGraphicsScene(this);
ui->detect_image_2->setScene(scene2);
// 初始化日志模型
m_logModel = new QStringListModel(this);
ui->detect_log->setModel(m_logModel);
// 初始化日志辅助类
m_logHelper = new DetectLogHelper(ui->detect_log, this);
// 注册自定义类型,使其能够在信号槽中跨线程传递
qRegisterMetaType<DetectionResult>("DetectionResult");
@ -108,16 +107,10 @@ MainWindow::MainWindow(QWidget *parent)
// 连接工作状态更新信号槽
connect(this, &MainWindow::workStatusUpdateRequested, this, &MainWindow::updateWorkStatusLabel);
// 连接检测结果更新信号槽
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
// 连接日志更新信号槽
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
// 连接清空日志信号槽
connect(this, &MainWindow::logClearRequested, this, &MainWindow::clearDetectionLogUI);
// 初始化右键菜单
setupContextMenu();
@ -143,17 +136,18 @@ MainWindow::~MainWindow()
void MainWindow::updateStatusLog(const QString& message)
{
// 更新状态栏
// statusBar()->showMessage(message);
// 通过信号槽机制更新detect_log控件
emit logUpdateRequested(message);
// 使用日志辅助类更新日志
if (m_logHelper) {
m_logHelper->appendLog(message);
}
}
void MainWindow::clearDetectionLog()
{
// 通过信号槽机制清空日志
emit logClearRequested();
// 使用日志辅助类清空日志
if (m_logHelper) {
m_logHelper->clearLog();
}
}
void MainWindow::Init()
@ -447,70 +441,18 @@ void MainWindow::updateDetectionResultDisplay(const DetectionResult& result)
// 根据相机索引决定显示在哪个图像视图上
if (result.cameraIndex == 2) {
// 第二个相机的结果显示在detect_image_2上
updateDetectionLog(tr("相机2检测结果更新"));
updateStatusLog(tr("相机2检测结果更新"));
displayImageInSecondView(result.image);
} else {
// 第一个相机或默认显示在detect_image上
updateDetectionLog(tr("相机1检测结果更新"));
updateStatusLog(tr("相机1检测结果更新"));
displayImage(result.image);
}
// 更新检测结果到列表
addDetectionResult(result);
}
void MainWindow::updateDetectionLog(const QString& message)
{
// 在UI线程中更新detect_log控件QListView
if (!m_logModel) return;
// 获取当前数据
QStringList logList = m_logModel->stringList();
// 检查是否与最后一条消息相同
if (message == m_lastLogMessage && !logList.isEmpty()) {
// 相同消息,增加计数并替换最后一条
m_lastLogCount++;
// 添加时间戳
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
// 替换最后一条
logList[logList.size() - 1] = logEntry;
} else {
// 新消息,重置计数
m_lastLogMessage = message;
m_lastLogCount = 1;
// 添加时间戳
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
// 添加新的日志条目
logList.append(logEntry);
}
// 更新模型
m_logModel->setStringList(logList);
// 自动滚动到最底部
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
ui->detect_log->scrollTo(lastIndex);
}
void MainWindow::clearDetectionLogUI()
{
// 在UI线程中清空检测日志
if (m_logModel) {
m_logModel->setStringList(QStringList());
}
// 重置日志计数器
m_lastLogMessage.clear();
m_lastLogCount = 0;
}
void MainWindow::on_btn_start_clicked()
{
// 检查Presenter是否已初始化

View File

@ -16,6 +16,7 @@
#include "dialogalgoarg.h"
#include "CommonDialogCameraLevel.h"
#include "DeviceStatusWidget.h"
#include "DetectLogHelper.h"
#include "ParticleSizePresenter.h"
QT_BEGIN_NAMESPACE
@ -47,30 +48,18 @@ public:
signals:
// 工作状态更新信号
void workStatusUpdateRequested(WorkStatus status);
// 检测结果更新信号
void detectionResultUpdateRequested(const DetectionResult& result);
// 日志更新信号
void logUpdateRequested(const QString& message);
// 清空日志信号
void logClearRequested();
private slots:
// 工作状态更新槽函数
void updateWorkStatusLabel(WorkStatus status);
// 检测结果更新槽函数
void updateDetectionResultDisplay(const DetectionResult& result);
// 日志更新槽函数
void updateDetectionLog(const QString& message);
// 清空日志槽函数
void clearDetectionLogUI();
// 处理相机点击事件
void onCameraClicked(int cameraIndex);
@ -102,10 +91,10 @@ private:
// 业务逻辑处理类
ParticleSizePresenter* m_presenter = nullptr;
// 日志模型
QStringListModel* m_logModel = nullptr;
// 日志辅助类
DetectLogHelper* m_logHelper = nullptr;
// 设备状态显示widget的引用
DeviceStatusWidget* m_deviceStatusWidget = nullptr;
@ -148,9 +137,5 @@ private:
private:
QLabel* m_cpuSerialLabel = nullptr; // CPU序列号标签
// 日志去重相关
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
int m_lastLogCount = 0; // 最后一条日志的重复次数
};
#endif // MAINWINDOW_H

View File

@ -83,9 +83,8 @@ MainWindow::MainWindow(QWidget *parent)
QGraphicsScene* scene = new QGraphicsScene(this);
ui->detect_image->setScene(scene);
// 初始化日志模型
m_logModel = new QStringListModel(this);
ui->detect_log->setModel(m_logModel);
// 初始化日志辅助类
m_logHelper = new DetectLogHelper(ui->detect_log, this);
// 注册自定义类型,使其能够在信号槽中跨线程传递
qRegisterMetaType<DetectionResult>("DetectionResult");
@ -98,12 +97,6 @@ MainWindow::MainWindow(QWidget *parent)
// 连接检测结果更新信号槽
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
// 连接日志更新信号槽
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
// 连接清空日志信号槽
connect(this, &MainWindow::logClearRequested, this, &MainWindow::clearDetectionLogUI);
// 初始化右键菜单
setupContextMenu();
@ -129,14 +122,18 @@ MainWindow::~MainWindow()
void MainWindow::updateStatusLog(const QString& message)
{
// 通过信号槽机制更新detect_log控件
emit logUpdateRequested(message);
// 使用日志辅助类更新日志
if (m_logHelper) {
m_logHelper->appendLog(message);
}
}
void MainWindow::clearDetectionLog()
{
// 通过信号槽机制清空日志
emit logClearRequested();
// 使用日志辅助类清空日志
if (m_logHelper) {
m_logHelper->clearLog();
}
}
void MainWindow::Init()
@ -374,65 +371,13 @@ void MainWindow::updateWorkStatusLabel(WorkStatus status)
void MainWindow::updateDetectionResultDisplay(const DetectionResult& result)
{
// 显示检测结果图像
updateDetectionLog(tr("检测结果更新"));
updateStatusLog(tr("检测结果更新"));
displayImage(result.image);
// 更新检测结果到列表
addDetectionResult(result);
}
void MainWindow::updateDetectionLog(const QString& message)
{
// 在UI线程中更新detect_log控件QListView
if (!m_logModel) return;
// 获取当前数据
QStringList logList = m_logModel->stringList();
// 检查是否与最后一条消息相同
if (message == m_lastLogMessage && !logList.isEmpty()) {
// 相同消息,增加计数并替换最后一条
m_lastLogCount++;
// 添加时间戳
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
// 替换最后一条
logList[logList.size() - 1] = logEntry;
} else {
// 新消息,重置计数
m_lastLogMessage = message;
m_lastLogCount = 1;
// 添加时间戳
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
// 添加新的日志条目
logList.append(logEntry);
}
// 更新模型
m_logModel->setStringList(logList);
// 自动滚动到最底部
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
ui->detect_log->scrollTo(lastIndex);
}
void MainWindow::clearDetectionLogUI()
{
// 在UI线程中清空检测日志
if (m_logModel) {
m_logModel->setStringList(QStringList());
}
// 重置日志计数器
m_lastLogMessage.clear();
m_lastLogCount = 0;
}
void MainWindow::on_btn_start_clicked()
{
// 检查Presenter是否已初始化

View File

@ -16,6 +16,7 @@
#include "dialogalgoarg.h"
#include "CommonDialogCameraLevel.h"
#include "DeviceStatusWidget.h"
#include "DetectLogHelper.h"
#include "ScrewPositionPresenter.h"
QT_BEGIN_NAMESPACE
@ -51,12 +52,6 @@ signals:
// 检测结果更新信号
void detectionResultUpdateRequested(const DetectionResult& result);
// 日志更新信号
void logUpdateRequested(const QString& message);
// 清空日志信号
void logClearRequested();
private slots:
// 工作状态更新槽函数
@ -65,12 +60,6 @@ private slots:
// 检测结果更新槽函数
void updateDetectionResultDisplay(const DetectionResult& result);
// 日志更新槽函数
void updateDetectionLog(const QString& message);
// 清空日志槽函数
void clearDetectionLogUI();
// 处理相机点击事件
void onCameraClicked(int cameraIndex);
@ -103,8 +92,8 @@ private:
// 业务逻辑处理类
ScrewPositionPresenter* m_presenter = nullptr;
// 日志模型
QStringListModel* m_logModel = nullptr;
// 日志辅助类
DetectLogHelper* m_logHelper = nullptr;
// 设备状态显示widget的引用
DeviceStatusWidget* m_deviceStatusWidget = nullptr;
@ -134,10 +123,5 @@ private:
void setupContextMenu();
void showContextMenu(const QPoint& pos, QGraphicsView* view);
bool saveDetectionDataToFile(const QString& filePath, int cameraIndex);
private:
// 日志去重相关
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
int m_lastLogCount = 0; // 最后一条日志的重复次数
};
#endif // MAINWINDOW_H

View File

@ -86,12 +86,8 @@ MainWindow::MainWindow(QWidget *parent)
// 设置版本信息显示
setupVersionDisplay();
// 初始化日志模型
m_logModel = new QStringListModel(this);
ui->detect_log->setModel(m_logModel);
// 连接日志更新信号
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
// 初始化日志辅助类
m_logHelper = new DetectLogHelper(ui->detect_log, this);
m_presenter->Init();
}
@ -197,18 +193,18 @@ void MainWindow::on_btn_test_clicked()
m_measureResultWidget->clearAllResults();
}
// emit logUpdateRequested(QString("正在加载调试数据: %1").arg(fileName));
// if (m_logHelper) m_logHelper->appendLog(QString("正在加载调试数据: %1").arg(fileName));
// 在后台线程中执行加载和检测
std::thread t([this, fileName]() {
int result = m_presenter->LoadDebugDataAndDetect(fileName.toStdString());
if (result == 0) {
QMetaObject::invokeMethod(this, [this]() {
emit logUpdateRequested("调试数据加载和检测成功");
if (m_logHelper) m_logHelper->appendLog("调试数据加载和检测成功");
}, Qt::QueuedConnection);
} else {
QMetaObject::invokeMethod(this, [this, result]() {
emit logUpdateRequested(QString("调试数据复检失败: %1").arg(result));
if (m_logHelper) m_logHelper->appendLog(QString("调试数据复检失败: %1").arg(result));
}, Qt::QueuedConnection);
}
});
@ -224,7 +220,7 @@ void MainWindow::on_btn_start_clicked()
// 启动所有相机的检测
m_presenter->StartAllDetection();
emit logUpdateRequested("已启动所有相机检测");
if (m_logHelper) m_logHelper->appendLog("已启动所有相机检测");
}
void MainWindow::on_btn_stop_clicked()
@ -235,7 +231,7 @@ void MainWindow::on_btn_stop_clicked()
// 停止所有相机的检测
m_presenter->StopAllDetection();
emit logUpdateRequested("已停止所有相机检测");
if (m_logHelper) m_logHelper->appendLog("已停止所有相机检测");
}
void MainWindow::on_btn_camera_config_clicked()
@ -341,7 +337,7 @@ void MainWindow::onDeviceClicked(const QString& deviceName)
}
if (foundIndex < 0) {
emit logUpdateRequested(QString("未找到相机: %1").arg(deviceName));
if (m_logHelper) m_logHelper->appendLog(QString("未找到相机: %1").arg(deviceName));
return;
}
@ -352,7 +348,7 @@ void MainWindow::onDeviceClicked(const QString& deviceName)
// 直接开始检测
m_presenter->ResetDetect(foundIndex);
emit logUpdateRequested(QString("已启动相机 \"%1\" 的检测").arg(deviceName));
if (m_logHelper) m_logHelper->appendLog(QString("已启动相机 \"%1\" 的检测").arg(deviceName));
}
void MainWindow::onConfigSaved()
@ -374,7 +370,7 @@ void MainWindow::onTileRightClicked(int index, const QString& alias)
// 检查点击的是否是当前检测的相机
if (index != currentDetectIndex) {
emit logUpdateRequested(QString("设备 \"%1\" 无缓存数据").arg(alias));
if (m_logHelper) m_logHelper->appendLog(QString("设备 \"%1\" 无缓存数据").arg(alias));
StyledMessageBox::information(this, "提示",
QString("设备 \"%1\" 无缓存数据\n\n当前缓存的是设备 %2 的数据")
.arg(alias)
@ -385,7 +381,7 @@ void MainWindow::onTileRightClicked(int index, const QString& alias)
// 检查是否有缓存数据
int cacheSize = m_presenter->GetDetectionDataCacheSize();
if (cacheSize <= 0) {
emit logUpdateRequested(QString("设备 \"%1\" 无缓存数据").arg(alias));
if (m_logHelper) m_logHelper->appendLog(QString("设备 \"%1\" 无缓存数据").arg(alias));
StyledMessageBox::information(this, "提示", QString("设备 \"%1\" 无缓存数据").arg(alias));
return;
}
@ -409,10 +405,10 @@ void MainWindow::onTileRightClicked(int index, const QString& alias)
// 调用Presenter保存数据
int result = m_presenter->SaveDetectionDataToFile(filePath.toStdString());
if (result == 0) {
emit logUpdateRequested(QString("检测数据已保存: %1").arg(filePath));
if (m_logHelper) m_logHelper->appendLog(QString("检测数据已保存: %1").arg(filePath));
StyledMessageBox::information(this, "成功", QString("检测数据已保存到:\n%1").arg(filePath));
} else {
emit logUpdateRequested(QString("保存检测数据失败: %1").arg(result));
if (m_logHelper) m_logHelper->appendLog(QString("保存检测数据失败: %1").arg(result));
StyledMessageBox::warning(this, "失败", QString("保存检测数据失败,错误码: %1").arg(result));
}
}
@ -420,7 +416,7 @@ void MainWindow::onTileRightClicked(int index, const QString& alias)
// IWheelMeasureStatus接口方法实现
void MainWindow::OnStatusUpdate(const QString &statusMessage)
{
emit logUpdateRequested(statusMessage);
if (m_logHelper) m_logHelper->appendLog(statusMessage);
LOG_DEBUG("Status update: %s \n", statusMessage.toStdString().c_str());
}
@ -477,14 +473,14 @@ void MainWindow::OnMeasureResult(const WheelMeasureResult &result)
void MainWindow::OnCameraConnected(const QString &cameraName)
{
QString message = QString("相机已连接: %1").arg(cameraName);
emit logUpdateRequested(message);
if (m_logHelper) m_logHelper->appendLog(message);
LOG_DEBUG("%s \n", message.toStdString().c_str());
}
void MainWindow::OnCameraDisconnected(const QString &cameraName)
{
QString message = QString("相机断开连接: %1").arg(cameraName);
emit logUpdateRequested(message);
if (m_logHelper) m_logHelper->appendLog(message);
LOG_DEBUG("%s \n", message.toStdString().c_str());
}
@ -516,13 +512,13 @@ void MainWindow::OnWorkStatusChangedImpl(WorkStatus status)
}
QString message = QString("工作状态: %1").arg(statusStr);
emit logUpdateRequested(message);
if (m_logHelper) m_logHelper->appendLog(message);
LOG_DEBUG("%s \n", message.toStdString().c_str());
}
void MainWindow::OnErrorOccurred(const QString &errorMessage)
{
emit logUpdateRequested("错误: " + errorMessage);
if (m_logHelper) m_logHelper->appendLog("错误: " + errorMessage);
LOG_ERROR("Error occurred: %s \n", errorMessage.toStdString().c_str());
}
@ -563,43 +559,3 @@ void MainWindow::setupVersionDisplay()
LOG_INFO("Version display initialized: %s\n", versionText.toStdString().c_str());
}
void MainWindow::updateDetectionLog(const QString& message)
{
// 在UI线程中更新detect_log控件QListView
if (!m_logModel) return;
// 获取当前数据
QStringList logList = m_logModel->stringList();
// 获取当前时间
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss");
// 检查是否与最后一条消息相同
if (message == m_lastLogMessage && !logList.isEmpty()) {
// 相同消息,增加计数并替换最后一条
m_lastLogCount++;
QString lastEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
logList.replace(logList.size() - 1, lastEntry);
} else {
// 新消息,重置计数
m_lastLogMessage = message;
m_lastLogCount = 1;
// 限制日志条数最多保留200条
if (logList.size() >= 200) {
logList.removeFirst();
}
// 添加新的日志条目
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
logList.append(logEntry);
}
// 更新模型
m_logModel->setStringList(logList);
// 自动滚动到最底部
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
ui->detect_log->scrollTo(lastIndex);
}

View File

@ -10,6 +10,7 @@
#include <QStringListModel>
#include "Presenter/Inc/WheelMeasurePresenter.h"
#include "IWheelMeasureStatus.h"
#include "DetectLogHelper.h"
// 前向声明widgets
class ImageGridWidget;
@ -30,9 +31,6 @@ class MainWindow : public QMainWindow, public IWheelMeasureStatus
{
Q_OBJECT
signals:
void logUpdateRequested(const QString& message);
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
@ -64,7 +62,6 @@ private slots:
void onConfigSaved();
void onDeviceClicked(const QString& deviceName);
void onTileRightClicked(int index, const QString& alias);
void updateDetectionLog(const QString& message);
private:
void setupVersionDisplay();
@ -80,10 +77,8 @@ private:
WheelMeasurePresenter* m_presenter = nullptr;
QLabel* m_versionLabel = nullptr;
// 日志相关
QStringListModel* m_logModel = nullptr;
QString m_lastLogMessage;
int m_lastLogCount = 0;
// 日志辅助类
DetectLogHelper* m_logHelper = nullptr;
};
#endif // MAINWINDOW_H

View File

@ -95,10 +95,9 @@ MainWindow::MainWindow(QWidget *parent)
// 为第二个图像视图初始化GraphicsScene
QGraphicsScene* scene2 = new QGraphicsScene(this);
ui->detect_image_2->setScene(scene2);
// 初始化日志模型
m_logModel = new QStringListModel(this);
ui->detect_log->setModel(m_logModel);
// 初始化日志辅助类
m_logHelper = new DetectLogHelper(ui->detect_log, this);
// 注册自定义类型,使其能够在信号槽中跨线程传递
qRegisterMetaType<DetectionResult>("DetectionResult");
@ -107,16 +106,10 @@ MainWindow::MainWindow(QWidget *parent)
// 连接工作状态更新信号槽
connect(this, &MainWindow::workStatusUpdateRequested, this, &MainWindow::updateWorkStatusLabel);
// 连接检测结果更新信号槽
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
// 连接日志更新信号槽
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
// 连接清空日志信号槽
connect(this, &MainWindow::logClearRequested, this, &MainWindow::clearDetectionLogUI);
// 初始化右键菜单
setupContextMenu();
@ -139,17 +132,18 @@ MainWindow::~MainWindow()
void MainWindow::updateStatusLog(const QString& message)
{
// 更新状态栏
// statusBar()->showMessage(message);
// 通过信号槽机制更新detect_log控件
emit logUpdateRequested(message);
// 使用日志辅助类更新日志
if (m_logHelper) {
m_logHelper->appendLog(message);
}
}
void MainWindow::clearDetectionLog()
{
// 通过信号槽机制清空日志
emit logClearRequested();
// 使用日志辅助类清空日志
if (m_logHelper) {
m_logHelper->clearLog();
}
}
void MainWindow::Init()
@ -497,70 +491,18 @@ void MainWindow::updateDetectionResultDisplay(const DetectionResult& result)
// 根据相机索引决定显示在哪个图像视图上
if (result.cameraIndex == 2) {
// 第二个相机的结果显示在detect_image_2上
updateDetectionLog(tr("相机2检测结果更新"));
updateStatusLog(tr("相机2检测结果更新"));
displayImageInSecondView(result.image);
} else {
// 第一个相机或默认显示在detect_image上
updateDetectionLog(tr("相机1检测结果更新"));
updateStatusLog(tr("相机1检测结果更新"));
displayImage(result.image);
}
// 更新检测结果到列表
addDetectionResult(result);
}
void MainWindow::updateDetectionLog(const QString& message)
{
// 在UI线程中更新detect_log控件QListView
if (!m_logModel) return;
// 获取当前数据
QStringList logList = m_logModel->stringList();
// 检查是否与最后一条消息相同
if (message == m_lastLogMessage && !logList.isEmpty()) {
// 相同消息,增加计数并替换最后一条
m_lastLogCount++;
// 添加时间戳
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
// 替换最后一条
logList[logList.size() - 1] = logEntry;
} else {
// 新消息,重置计数
m_lastLogMessage = message;
m_lastLogCount = 1;
// 添加时间戳
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
// 添加新的日志条目
logList.append(logEntry);
}
// 更新模型
m_logModel->setStringList(logList);
// 自动滚动到最底部
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
ui->detect_log->scrollTo(lastIndex);
}
void MainWindow::clearDetectionLogUI()
{
// 在UI线程中清空检测日志
if (m_logModel) {
m_logModel->setStringList(QStringList());
}
// 重置日志计数器
m_lastLogMessage.clear();
m_lastLogCount = 0;
}
void MainWindow::on_btn_start_clicked()
{
// 检查Presenter是否已初始化

View File

@ -16,6 +16,7 @@
#include "dialogalgoarg.h"
#include "CommonDialogCameraLevel.h"
#include "DeviceStatusWidget.h"
#include "DetectLogHelper.h"
#include "WorkpiecePresenter.h"
QT_BEGIN_NAMESPACE
@ -47,30 +48,18 @@ public:
signals:
// 工作状态更新信号
void workStatusUpdateRequested(WorkStatus status);
// 检测结果更新信号
void detectionResultUpdateRequested(const DetectionResult& result);
// 日志更新信号
void logUpdateRequested(const QString& message);
// 清空日志信号
void logClearRequested();
private slots:
// 工作状态更新槽函数
void updateWorkStatusLabel(WorkStatus status);
// 检测结果更新槽函数
void updateDetectionResultDisplay(const DetectionResult& result);
// 日志更新槽函数
void updateDetectionLog(const QString& message);
// 清空日志槽函数
void clearDetectionLogUI();
// 处理相机点击事件
void onCameraClicked(int cameraIndex);
@ -102,10 +91,10 @@ private:
// 业务逻辑处理类
WorkpiecePresenter* m_presenter = nullptr;
// 日志模型
QStringListModel* m_logModel = nullptr;
// 日志辅助类
DetectLogHelper* m_logHelper = nullptr;
// 设备状态显示widget的引用
DeviceStatusWidget* m_deviceStatusWidget = nullptr;
@ -148,9 +137,5 @@ private:
private:
QLabel* m_cpuSerialLabel = nullptr; // CPU序列号标签
// 日志去重相关
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
int m_lastLogCount = 0; // 最后一条日志的重复次数
};
#endif // MAINWINDOW_H

View File

@ -97,9 +97,8 @@ MainWindow::MainWindow(QWidget *parent)
QGraphicsScene* scene2 = new QGraphicsScene(this);
ui->detect_image_2->setScene(scene2);
// 初始化日志模型
m_logModel = new QStringListModel(this);
ui->detect_log->setModel(m_logModel);
// 初始化日志辅助类
m_logHelper = new DetectLogHelper(ui->detect_log, this);
// 注册自定义类型,使其能够在信号槽中跨线程传递
qRegisterMetaType<WorkpieceHoleDetectionResult>("WorkpieceHoleDetectionResult");
@ -112,12 +111,6 @@ MainWindow::MainWindow(QWidget *parent)
// 连接检测结果更新信号槽
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
// 连接日志更新信号槽
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
// 连接清空日志信号槽
connect(this, &MainWindow::logClearRequested, this, &MainWindow::clearDetectionLogUI);
// 初始化右键菜单
setupContextMenu();
@ -140,17 +133,18 @@ MainWindow::~MainWindow()
void MainWindow::updateStatusLog(const QString& message)
{
// 更新状态栏
// statusBar()->showMessage(message);
// 通过信号槽机制更新detect_log控件
emit logUpdateRequested(message);
// 使用日志辅助类更新日志
if (m_logHelper) {
m_logHelper->appendLog(message);
}
}
void MainWindow::clearDetectionLog()
{
// 通过信号槽机制清空日志
emit logClearRequested();
// 使用日志辅助类清空日志
if (m_logHelper) {
m_logHelper->clearLog();
}
}
void MainWindow::Init()
@ -403,11 +397,11 @@ void MainWindow::updateDetectionResultDisplay(const WorkpieceHoleDetectionResult
// 根据相机索引决定显示在哪个图像视图上
if (result.cameraIndex == 2) {
// 第二个相机的结果显示在detect_image_2上
updateDetectionLog(tr("相机2检测结果更新"));
updateStatusLog(tr("相机2检测结果更新"));
displayImageInSecondView(result.image);
} else {
// 第一个相机或默认显示在detect_image上
updateDetectionLog(tr("相机1检测结果更新"));
updateStatusLog(tr("相机1检测结果更新"));
displayImage(result.image);
}
@ -415,58 +409,6 @@ void MainWindow::updateDetectionResultDisplay(const WorkpieceHoleDetectionResult
addDetectionResult(result);
}
void MainWindow::updateDetectionLog(const QString& message)
{
// 在UI线程中更新detect_log控件QListView
if (!m_logModel) return;
// 获取当前数据
QStringList logList = m_logModel->stringList();
// 检查是否与最后一条消息相同
if (message == m_lastLogMessage && !logList.isEmpty()) {
// 相同消息,增加计数并替换最后一条
m_lastLogCount++;
// 添加时间戳
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
// 替换最后一条
logList[logList.size() - 1] = logEntry;
} else {
// 新消息,重置计数
m_lastLogMessage = message;
m_lastLogCount = 1;
// 添加时间戳
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
// 添加新的日志条目
logList.append(logEntry);
}
// 更新模型
m_logModel->setStringList(logList);
// 自动滚动到最底部
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
ui->detect_log->scrollTo(lastIndex);
}
void MainWindow::clearDetectionLogUI()
{
// 在UI线程中清空检测日志
if (m_logModel) {
m_logModel->setStringList(QStringList());
}
// 重置日志计数器
m_lastLogMessage.clear();
m_lastLogCount = 0;
}
void MainWindow::on_btn_start_clicked()
{
// 检查Presenter是否已初始化

View File

@ -16,6 +16,7 @@
#include "dialogalgoarg.h"
#include "CommonDialogCameraLevel.h"
#include "DeviceStatusWidget.h"
#include "DetectLogHelper.h"
#include "WorkpieceHolePresenter.h"
QT_BEGIN_NAMESPACE
@ -51,12 +52,6 @@ signals:
// 检测结果更新信号
void detectionResultUpdateRequested(const WorkpieceHoleDetectionResult& result);
// 日志更新信号
void logUpdateRequested(const QString& message);
// 清空日志信号
void logClearRequested();
private slots:
// 工作状态更新槽函数
@ -65,12 +60,6 @@ private slots:
// 检测结果更新槽函数
void updateDetectionResultDisplay(const WorkpieceHoleDetectionResult& result);
// 日志更新槽函数
void updateDetectionLog(const QString& message);
// 清空日志槽函数
void clearDetectionLogUI();
// 处理相机点击事件
void onCameraClicked(int cameraIndex);
@ -103,8 +92,8 @@ private:
// 业务逻辑处理类
WorkpieceHolePresenter* m_presenter = nullptr;
// 日志模型
QStringListModel* m_logModel = nullptr;
// 日志辅助类
DetectLogHelper* m_logHelper = nullptr;
// 设备状态显示widget的引用
DeviceStatusWidget* m_deviceStatusWidget = nullptr;
@ -148,9 +137,5 @@ private:
private:
QLabel* m_cpuSerialLabel = nullptr; // CPU序列号标签
// 日志去重相关
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
int m_lastLogCount = 0; // 最后一条日志的重复次数
};
#endif // MAINWINDOW_H

View File

@ -25,6 +25,7 @@ INCLUDEPATH += $$PWD/../../SDK/Device/VzNLSDK/Inc
INCLUDEPATH += $$PWD/../../Module/ShareMem/Inc # ConfigMonitor
INCLUDEPATH += $$PWD/../../Module/ModbusTCPServer/Inc # ModbusTCP服务器接口
INCLUDEPATH += $$PWD/../../Module/AuthModule/Inc # 授权模块
INCLUDEPATH += $$PWD/../../VrNets/TCPServer/Inc # TCP服务器接口
# 头文件
HEADERS += \
@ -35,7 +36,9 @@ HEADERS += \
Inc/VrCommonConfig.h \
Inc/ConfigMonitor.h \
Inc/BaseConfigManager.h \
Inc/ConfigEncryption.h
Inc/ConfigEncryption.h \
Inc/ProtocolCommon.h \
Inc/TCPServerProtocol.h
# 源文件
SOURCES += \
@ -43,7 +46,8 @@ SOURCES += \
Src/PathManager.cpp \
Src/BasePresenter.cpp \
Src/ConfigMonitor.cpp \
Src/ConfigEncryption.cpp
Src/ConfigEncryption.cpp \
Src/TCPServerProtocol.cpp
# 注意: BaseConfigManager.cpp 不在这里编译
# 它需要在各个应用中编译,因为它依赖应用特定的 IVrConfig.h

View File

@ -0,0 +1,72 @@
#ifndef PROTOCOLCOMMON_H
#define PROTOCOLCOMMON_H
#include <functional>
#include <vector>
#include <cstdint>
/**
* @brief
*/
struct RobotCoordinate {
float x; // X坐标
float y; // Y坐标
float z; // Z坐标
float rx; // X轴旋转
float ry; // Y轴旋转
float rz; // Z轴旋转
};
/**
* @brief x,y,z,rz
*/
struct TargetPosition {
float x; // X坐标
float y; // Y坐标
float z; // Z坐标
float rz; // Z轴旋转yaw角
uint16_t cameraId; // 相机ID从1开始编号
};
/**
* @brief
*/
struct MultiTargetData {
uint16_t count; // 目标数量
uint16_t cameraId; // 当前检测的相机ID从1开始编号
std::vector<TargetPosition> targets; // 目标位置列表
MultiTargetData() : count(0), cameraId(1) {} // 默认相机ID为1
};
/**
* @brief
*/
enum ConnectionStatus {
STATUS_DISCONNECTED = 0, // 断开连接
STATUS_CONNECTED = 1, // 已连接
STATUS_IDLE = 2, // 空闲状态
STATUS_WORKING = 3, // 工作中
STATUS_ERROR = 4 // 错误状态
};
/**
* @brief
* @param connected true-false-
*/
using ConnectionCallback = std::function<void(bool connected)>;
/**
* @brief
* @param startWork true-false-
* @param cameraId ID112...
*/
using WorkSignalCallback = std::function<bool(bool startWork, int cameraId)>;
// 工作状态定义(公共常量)
static const uint16_t WORK_STATUS_IDLE = 0; // 空闲
static const uint16_t WORK_STATUS_CAMERA1_DONE = 1; // 相机1工作完成
static const uint16_t WORK_STATUS_CAMERA2_DONE = 2; // 相机2工作完成
static const uint16_t WORK_STATUS_BUSY = 3; // 忙碌
#endif // PROTOCOLCOMMON_H

View File

@ -0,0 +1,151 @@
#ifndef TCPSERVERPROTOCOL_H
#define TCPSERVERPROTOCOL_H
#include <functional>
#include <vector>
#include <string>
#include <memory>
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include "IYTCPServer.h"
#include "ProtocolCommon.h"
/**
* @brief TCP服务器协议封装类
*
* TCP/IP通信协议基类
* -
* - JSON格式数据交换
* - start_detection/ScanRequest指令
* -
*/
class TCPServerProtocol
{
public:
/**
* @brief
*/
struct DetectionResultData {
int code = 0; // 响应码0-成功,其他-错误
bool success = true; // 是否成功
QString message = "检测成功"; // 响应消息
qint64 timestamp = 0; // 时间戳(毫秒)
std::vector<std::vector<QJsonObject>> result; // 检测结果3D坐标数组
};
/**
* @brief 使
*/
using TCPStatus = ConnectionStatus;
/**
* @brief
* @param success
* @param cameraIndex -1
* @param timestamp
*/
using DetectionTriggerCallback = std::function<bool(bool success, int cameraIndex, qint64 timestamp)>;
public:
TCPServerProtocol();
virtual ~TCPServerProtocol();
/**
* @brief TCP服务器
* @param port TCP端口号5020
* @return 0--
*/
int Initialize(uint16_t port = 5020);
/**
* @brief
*/
void Deinitialize();
/**
* @brief
* @param result
* @param pClient nullptr则发送给所有客户端
* @return 0--
*/
int SendDetectionResult(const QJsonObject& result, const TCPClient* pClient = nullptr);
/**
* @brief
* @return
*/
TCPStatus GetConnectionStatus() const;
/**
* @brief
* @param callback
*/
void SetConnectionCallback(const ConnectionCallback& callback);
/**
* @brief
* @param callback
*/
void SetDetectionTriggerCallback(const DetectionTriggerCallback& callback);
/**
* @brief
* @return true-false-
*/
bool IsRunning() const;
protected:
/**
* @brief TCP客户端连接事件处理
* @param pClient
* @param eventType
*/
virtual void OnTCPEvent(const TCPClient* pClient, TCPServerEventType eventType);
/**
* @brief TCP数据接收处理
* @param pClient
* @param pData
* @param nLen
*/
virtual void OnTCPDataReceived(const TCPClient* pClient, const char* pData, unsigned int nLen);
/**
* @brief JSON命令
* @param pClient
* @param jsonData JSON数据
*/
virtual void ParseJSONCommand(const TCPClient* pClient, const QJsonObject& jsonData);
/**
* @brief
* @param pClient
* @param timestamp
*/
virtual void HandleStartDetectionCommand(const TCPClient* pClient, qint64 timestamp);
/**
* @brief
* @param pClient
* @param code
* @param message
* @param timestamp
*/
void SendErrorResponse(const TCPClient* pClient, int code, const QString& message, qint64 timestamp);
protected:
// TCP服务器相关
IYTCPServer* m_pTCPServer; // TCP服务器实例
bool m_bServerRunning; // 服务器运行状态
uint16_t m_nPort; // TCP端口
// 连接状态
TCPStatus m_connectionStatus; // TCP连接状态
// 回调函数
ConnectionCallback m_connectionCallback; // 连接状态回调
DetectionTriggerCallback m_detectionTriggerCallback; // 检测触发回调
};
#endif // TCPSERVERPROTOCOL_H

View File

@ -104,6 +104,7 @@ struct VrCameraPlaneCalibParam
std::string cameraName = ""; // 相机名称
double planeCalib[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 旋转矩阵(默认单位矩阵)
double planeHeight = -1.0; // 参考平面的高度,用于去除地面数据
double planeHeightOffset = -10.0; // 平面高度偏移量,用于数据预处理时去除地面的余量
double invRMatrix[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 逆旋转矩阵(默认单位矩阵)
bool isCalibrated = false; // 是否已经校准
@ -114,6 +115,7 @@ struct VrCameraPlaneCalibParam
cameraName = other.cameraName;
memcpy(planeCalib, other.planeCalib, sizeof(planeCalib));
planeHeight = other.planeHeight;
planeHeightOffset = other.planeHeightOffset;
memcpy(invRMatrix, other.invRMatrix, sizeof(invRMatrix));
isCalibrated = other.isCalibrated;
}
@ -125,6 +127,7 @@ struct VrCameraPlaneCalibParam
: cameraIndex(other.cameraIndex)
, cameraName(other.cameraName)
, planeHeight(other.planeHeight)
, planeHeightOffset(other.planeHeightOffset)
, isCalibrated(other.isCalibrated) {
memcpy(planeCalib, other.planeCalib, sizeof(planeCalib));
memcpy(invRMatrix, other.invRMatrix, sizeof(invRMatrix));

View File

@ -0,0 +1,252 @@
#include "TCPServerProtocol.h"
#include "VrLog.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDateTime>
TCPServerProtocol::TCPServerProtocol()
: m_pTCPServer(nullptr)
, m_bServerRunning(false)
, m_nPort(5020)
, m_connectionStatus(STATUS_DISCONNECTED)
{
}
TCPServerProtocol::~TCPServerProtocol()
{
Deinitialize();
}
int TCPServerProtocol::Initialize(uint16_t port)
{
LOG_DEBUG("Initializing TCP server protocol on port %d\n", port);
m_nPort = port;
// 创建TCP服务器实例
if (!VrCreatYTCPServer(&m_pTCPServer)) {
LOG_ERROR("Failed to create TCP server instance\n");
return -1;
}
// 初始化TCP服务器
if (!m_pTCPServer->Init(port)) {
LOG_ERROR("Failed to initialize TCP server on port %d\n", port);
delete m_pTCPServer;
m_pTCPServer = nullptr;
return -2;
}
// 设置事件回调
m_pTCPServer->SetEventCallback([this](const TCPClient* pClient, TCPServerEventType eventType) {
this->OnTCPEvent(pClient, eventType);
});
// 启动TCP服务器
if (!m_pTCPServer->Start([this](const TCPClient* pClient, const char* pData, const unsigned int nLen) {
this->OnTCPDataReceived(pClient, pData, nLen);
})) {
LOG_ERROR("Failed to start TCP server\n");
m_pTCPServer->Close();
delete m_pTCPServer;
m_pTCPServer = nullptr;
return -3;
}
m_bServerRunning = true;
m_connectionStatus = STATUS_CONNECTED;
LOG_DEBUG("TCP server protocol initialized successfully on port %d\n", port);
return 0;
}
void TCPServerProtocol::Deinitialize()
{
if (m_pTCPServer) {
LOG_DEBUG("Stopping TCP server protocol\n");
m_bServerRunning = false;
m_connectionStatus = ConnectionStatus::STATUS_DISCONNECTED;
m_pTCPServer->Stop();
m_pTCPServer->Close();
delete m_pTCPServer;
m_pTCPServer = nullptr;
LOG_DEBUG("TCP server protocol stopped\n");
}
}
int TCPServerProtocol::SendDetectionResult(const QJsonObject& result, const TCPClient* pClient)
{
if (!m_pTCPServer || !m_bServerRunning) {
LOG_ERROR("TCP server is not running\n");
return -1;
}
// 转换为JSON字符串
QJsonDocument doc(result);
QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
// 发送数据
bool success = false;
if (pClient) {
// 发送给指定客户端
success = m_pTCPServer->SendData(pClient, jsonData.constData(), jsonData.size());
LOG_DEBUG("Sent detection result to specific client, size: %d bytes\n", jsonData.size());
} else {
// 发送给所有客户端
success = m_pTCPServer->SendAllData(jsonData.constData(), jsonData.size());
LOG_DEBUG("Sent detection result to all clients, size: %d bytes\n", jsonData.size());
}
if (!success) {
LOG_ERROR("Failed to send detection result\n");
return -2;
}
return 0;
}
TCPServerProtocol::TCPStatus TCPServerProtocol::GetConnectionStatus() const
{
return m_connectionStatus;
}
void TCPServerProtocol::SetConnectionCallback(const ConnectionCallback& callback)
{
m_connectionCallback = callback;
}
void TCPServerProtocol::SetDetectionTriggerCallback(const DetectionTriggerCallback& callback)
{
m_detectionTriggerCallback = callback;
}
bool TCPServerProtocol::IsRunning() const
{
return m_bServerRunning;
}
void TCPServerProtocol::OnTCPEvent(const TCPClient* pClient, TCPServerEventType eventType)
{
switch (eventType) {
case TCP_EVENT_CLIENT_CONNECTED:
LOG_DEBUG("TCP client connected: %p\n", pClient);
m_connectionStatus = STATUS_CONNECTED;
if (m_connectionCallback) {
m_connectionCallback(true);
}
break;
case TCP_EVENT_CLIENT_DISCONNECTED:
LOG_DEBUG("TCP client disconnected: %p\n", pClient);
// 注意:这里不立即设置为断开状态,因为可能还有其他客户端连接
if (m_connectionCallback) {
m_connectionCallback(false);
}
break;
case TCP_EVENT_CLIENT_EXCEPTION:
LOG_WARNING("TCP client exception: %p\n", pClient);
if (m_connectionCallback) {
m_connectionCallback(false);
}
break;
}
}
void TCPServerProtocol::OnTCPDataReceived(const TCPClient* pClient, const char* pData, unsigned int nLen)
{
if (!pData || nLen == 0) {
LOG_WARNING("Received empty data from client %p\n", pClient);
return;
}
LOG_DEBUG("Received TCP data from client %p, size: %u bytes\n", pClient, nLen);
// 解析JSON数据
QByteArray data(pData, nLen);
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
LOG_ERROR("JSON parse error: %s\n", error.errorString().toStdString().c_str());
SendErrorResponse(pClient, -1, "JSON格式错误", 0);
return;
}
if (!doc.isObject()) {
LOG_ERROR("Received JSON is not an object\n");
SendErrorResponse(pClient, -2, "JSON数据必须是对象格式", 0);
return;
}
ParseJSONCommand(pClient, doc.object());
}
void TCPServerProtocol::ParseJSONCommand(const TCPClient* pClient, const QJsonObject& jsonData)
{
// 获取消息类型和时间戳
QString messageType = jsonData["MessageType"].toString();
QString timestamp = jsonData["Timestamp"].toString();
LOG_DEBUG("Received MessageType: %s, Timestamp: %s\n", messageType.toStdString().c_str(), timestamp.toStdString().c_str());
if (messageType == "ScanRequest") {
HandleStartDetectionCommand(pClient, QDateTime::currentMSecsSinceEpoch());
} else {
LOG_WARNING("Unknown MessageType: %s\n", messageType.toStdString().c_str());
SendErrorResponse(pClient, -3, QString("未知消息类型: %1").arg(messageType), QDateTime::currentMSecsSinceEpoch());
}
}
void TCPServerProtocol::HandleStartDetectionCommand(const TCPClient* pClient, qint64 timestamp)
{
LOG_DEBUG("Handling start_detection command, timestamp: %lld\n", timestamp);
// 先发送ScanResponse确认收到请求
QJsonObject response;
response["MessageType"] = "ScanResponse";
response["Timestamp"] = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
QJsonObject data;
data["Status"] = "Accepted";
response["Data"] = data;
SendDetectionResult(response, pClient);
bool success = false;
int cameraIndex = -1; // -1表示所有相机
// 触发检测回调
if (m_detectionTriggerCallback) {
success = m_detectionTriggerCallback(true, cameraIndex, timestamp);
} else {
LOG_WARNING("Detection trigger callback not set\n");
SendErrorResponse(pClient, -4, "检测服务未准备就绪", timestamp);
return;
}
if (!success) {
LOG_ERROR("Failed to trigger detection\n");
SendErrorResponse(pClient, -5, "检测启动失败", timestamp);
return;
}
// 注意:这里不立即发送结果,实际的检测结果将通过 SendDetectionResult 方法异步发送
LOG_DEBUG("Detection triggered successfully, waiting for results...\n");
}
void TCPServerProtocol::SendErrorResponse(const TCPClient* pClient, int code, const QString& message, qint64 timestamp)
{
QJsonObject response;
response["MessageType"] = "Error";
response["Timestamp"] = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
QJsonObject data;
data["ErrorCode"] = code;
data["ErrorMessage"] = message;
response["Data"] = data;
SendDetectionResult(response, pClient);
}

View File

@ -0,0 +1,198 @@
#ifndef DETECTIMAGEWIDGET_H
#define DETECTIMAGEWIDGET_H
#include <QWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QImage>
#include <QMenu>
#include <QAction>
#include <QLabel>
#include <QScrollArea>
#include <QStackedWidget>
#include <functional>
/**
* @brief
*
*
*/
class ImageZoomView : public QWidget
{
Q_OBJECT
public:
explicit ImageZoomView(QWidget* parent = nullptr);
/**
* @brief
* @param image
* @param title
*/
void setImage(const QImage& image, const QString& title = "");
/**
* @brief
*/
void resetZoom();
signals:
/**
* @brief 退
*/
void exitRequested();
protected:
void wheelEvent(QWheelEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void showEvent(QShowEvent* event) override;
private:
void updateImageDisplay();
QScrollArea* m_scrollArea = nullptr;
QLabel* m_imageLabel = nullptr;
QLabel* m_titleLabel = nullptr;
QLabel* m_infoLabel = nullptr;
QImage m_originalImage;
double m_scaleFactor = 1.0;
bool m_dragging = false;
bool m_needResetZoom = false;
QPoint m_lastPos;
static constexpr double MIN_SCALE = 0.1;
static constexpr double MAX_SCALE = 10.0;
static constexpr double SCALE_STEP = 0.1;
};
/**
* @brief
*
*
* -
* -
* -
*/
class DetectImageWidget : public QWidget
{
Q_OBJECT
public:
/**
* @brief
* @param filePath
* @return true-false-
*/
using SaveDataCallback = std::function<bool(const QString& filePath)>;
explicit DetectImageWidget(QWidget* parent = nullptr);
~DetectImageWidget();
/**
* @brief
* @param image
*/
void setImage(const QImage& image);
/**
* @brief
* @return
*/
QImage getImage() const;
/**
* @brief
*/
void clearImage();
/**
* @brief
* @param title
*/
void setTitle(const QString& title);
/**
* @brief
*/
QString title() const { return m_title; }
/**
* @brief /
* @param enabled
*/
void setSaveDataEnabled(bool enabled);
/**
* @brief
* @param callback
*/
void setSaveDataCallback(const SaveDataCallback& callback);
/**
* @brief
* @param text
*/
void setSaveDataMenuText(const QString& text);
/**
* @brief
*/
void showZoomView();
/**
* @brief
*/
void showNormalView();
signals:
/**
* @brief
*/
void clicked();
/**
* @brief
*/
void rightClicked();
/**
* @brief
* @param widget
*/
void saveDataRequested(DetectImageWidget* widget);
protected:
void resizeEvent(QResizeEvent* event) override;
bool eventFilter(QObject* obj, QEvent* event) override;
private slots:
void onSaveDataTriggered();
void showContextMenu(const QPoint& pos);
private:
void setupUI();
void updateImageDisplay();
// 界面元素
QStackedWidget* m_stackedWidget = nullptr;
QGraphicsView* m_graphicsView = nullptr;
QGraphicsScene* m_scene = nullptr;
ImageZoomView* m_zoomView = nullptr;
// 右键菜单
QMenu* m_contextMenu = nullptr;
QAction* m_saveDataAction = nullptr;
// 数据
QImage m_image;
QString m_title;
bool m_saveDataEnabled = false;
SaveDataCallback m_saveDataCallback;
};
#endif // DETECTIMAGEWIDGET_H

View File

@ -0,0 +1,111 @@
#ifndef DETECTLOGHELPER_H
#define DETECTLOGHELPER_H
#include <QObject>
#include <QListView>
#include <QStringListModel>
#include <QDateTime>
/**
* @brief
*
* QListView
* - "x2", "x3"
* -
* -
* - 线
*
* 使
* 1. mainwindow.h : DetectLogHelper* m_logHelper;
* 2. : m_logHelper = new DetectLogHelper(ui->detect_log, this);
* 3. : m_logHelper->appendLog("消息");
* 4. : m_logHelper->clearLog();
*/
class DetectLogHelper : public QObject
{
Q_OBJECT
public:
/**
* @brief
* @param listView QListView ui->detect_log
* @param parent
*/
explicit DetectLogHelper(QListView* listView, QObject *parent = nullptr);
~DetectLogHelper();
/**
* @brief 线线
* @param message
*/
void appendLog(const QString& message);
/**
* @brief 线线
*/
void clearLog();
/**
* @brief
* @param format "hh:mm:ss"
*/
void setTimestampFormat(const QString& format);
/**
* @brief
* @param show true
*/
void setShowTimestamp(bool show);
/**
* @brief
* @param enable true
*/
void setDeduplicationEnabled(bool enable);
/**
* @brief QStringListModel
* @return QStringListModel
*/
QStringListModel* model() const;
signals:
/**
* @brief 使线
*/
void logUpdateRequested(const QString& message);
/**
* @brief 使线
*/
void logClearRequested();
private slots:
/**
* @brief UI线程中更新日志
*/
void updateLogInUI(const QString& message);
/**
* @brief UI线程中清空日志
*/
void clearLogInUI();
private:
QListView* m_listView;
QStringListModel* m_logModel;
// 日志去重相关
QString m_lastLogMessage; // 最后一条日志消息(不含时间戳)
int m_lastLogCount; // 最后一条日志的重复次数
// 配置项
QString m_timestampFormat; // 时间戳格式
bool m_showTimestamp; // 是否显示时间戳
bool m_deduplicationEnabled; // 是否启用去重
// 初始化信号槽连接
void initConnections();
};
#endif // DETECTLOGHELPER_H

View File

@ -0,0 +1,447 @@
#include "DetectImageWidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QScrollBar>
#include <QWheelEvent>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QResizeEvent>
#include <QShowEvent>
#include <QEvent>
#include <QGraphicsPixmapItem>
#include <QFileDialog>
#include <QStandardPaths>
#include <QDir>
#include <QDateTime>
#include <cmath>
// ============================================================================
// ImageZoomView 实现
// ============================================================================
ImageZoomView::ImageZoomView(QWidget* parent)
: QWidget(parent)
{
setStyleSheet("background-color: rgb(25, 26, 28);");
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(10, 10, 10, 10);
mainLayout->setSpacing(10);
// 顶部标题栏
QHBoxLayout* topLayout = new QHBoxLayout();
m_titleLabel = new QLabel(this);
m_titleLabel->setStyleSheet("QLabel { color: rgb(221, 225, 233); font-size: 16px; font-weight: bold; background: transparent; }");
topLayout->addWidget(m_titleLabel);
topLayout->addStretch();
// 返回按钮
QPushButton* btnBack = new QPushButton(QString::fromUtf8("返回"), this);
btnBack->setFixedSize(80, 30);
btnBack->setStyleSheet(
"QPushButton { background-color: rgb(60, 62, 68); color: rgb(221, 225, 233); "
"border: 1px solid rgb(80, 82, 88); border-radius: 5px; font-size: 14px; }"
"QPushButton:hover { background-color: rgb(80, 82, 88); }"
"QPushButton:pressed { background-color: rgb(50, 52, 58); }"
);
connect(btnBack, &QPushButton::clicked, this, &ImageZoomView::exitRequested);
topLayout->addWidget(btnBack);
mainLayout->addLayout(topLayout);
// 滚动区域
m_scrollArea = new QScrollArea(this);
m_scrollArea->setWidgetResizable(false);
m_scrollArea->setAlignment(Qt::AlignCenter);
m_scrollArea->setStyleSheet(
"QScrollArea { background-color: rgb(38, 40, 47); border: none; }"
"QScrollBar:vertical { background-color: rgb(38, 40, 47); width: 12px; }"
"QScrollBar::handle:vertical { background-color: rgb(80, 82, 88); border-radius: 6px; min-height: 20px; }"
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }"
"QScrollBar:horizontal { background-color: rgb(38, 40, 47); height: 12px; }"
"QScrollBar::handle:horizontal { background-color: rgb(80, 82, 88); border-radius: 6px; min-width: 20px; }"
"QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; }"
);
m_imageLabel = new QLabel(m_scrollArea);
m_imageLabel->setAlignment(Qt::AlignCenter);
m_imageLabel->setStyleSheet("background-color: rgb(38, 40, 47);");
m_imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
m_imageLabel->setScaledContents(false);
m_scrollArea->setWidget(m_imageLabel);
mainLayout->addWidget(m_scrollArea, 1);
// 底部信息栏
m_infoLabel = new QLabel(this);
m_infoLabel->setStyleSheet("QLabel { color: rgb(180, 180, 180); font-size: 12px; background: transparent; }");
m_infoLabel->setText(QString::fromUtf8("滚轮缩放 | 拖拽平移 | 双击还原 | 点击返回按钮退出"));
mainLayout->addWidget(m_infoLabel);
setFocusPolicy(Qt::StrongFocus);
}
void ImageZoomView::setImage(const QImage& image, const QString& title)
{
m_originalImage = image;
m_titleLabel->setText(title);
m_needResetZoom = true;
updateImageDisplay();
}
void ImageZoomView::showEvent(QShowEvent* event)
{
QWidget::showEvent(event);
if (m_needResetZoom && !m_originalImage.isNull()) {
m_needResetZoom = false;
QMetaObject::invokeMethod(this, &ImageZoomView::resetZoom, Qt::QueuedConnection);
}
}
void ImageZoomView::updateImageDisplay()
{
if (m_originalImage.isNull()) {
m_imageLabel->setText(QString::fromUtf8("无图像"));
return;
}
int newWidth = static_cast<int>(m_originalImage.width() * m_scaleFactor);
int newHeight = static_cast<int>(m_originalImage.height() * m_scaleFactor);
QImage scaledImage = m_originalImage.scaled(newWidth, newHeight,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
m_imageLabel->setPixmap(QPixmap::fromImage(scaledImage));
m_imageLabel->resize(scaledImage.size());
QString info = QString::fromUtf8("缩放: %1% | 尺寸: %2x%3 | 滚轮缩放 | 拖拽平移 | 双击还原")
.arg(static_cast<int>(m_scaleFactor * 100))
.arg(m_originalImage.width())
.arg(m_originalImage.height());
m_infoLabel->setText(info);
}
void ImageZoomView::resetZoom()
{
if (m_originalImage.isNull()) return;
QSize viewSize = m_scrollArea->viewport()->size();
double scaleX = static_cast<double>(viewSize.width() - 20) / m_originalImage.width();
double scaleY = static_cast<double>(viewSize.height() - 20) / m_originalImage.height();
m_scaleFactor = qMin(scaleX, scaleY);
m_scaleFactor = qMax(m_scaleFactor, MIN_SCALE);
updateImageDisplay();
m_scrollArea->horizontalScrollBar()->setValue(
(m_imageLabel->width() - m_scrollArea->viewport()->width()) / 2);
m_scrollArea->verticalScrollBar()->setValue(
(m_imageLabel->height() - m_scrollArea->viewport()->height()) / 2);
}
void ImageZoomView::wheelEvent(QWheelEvent* event)
{
if (m_originalImage.isNull()) return;
double delta = event->angleDelta().y() > 0 ? SCALE_STEP : -SCALE_STEP;
double newScale = m_scaleFactor + delta;
newScale = qMax(newScale, MIN_SCALE);
newScale = qMin(newScale, MAX_SCALE);
if (qAbs(newScale - m_scaleFactor) > 0.001) {
QScrollBar* hBar = m_scrollArea->horizontalScrollBar();
QScrollBar* vBar = m_scrollArea->verticalScrollBar();
double hRatio = hBar->maximum() > 0 ? static_cast<double>(hBar->value()) / hBar->maximum() : 0.5;
double vRatio = vBar->maximum() > 0 ? static_cast<double>(vBar->value()) / vBar->maximum() : 0.5;
m_scaleFactor = newScale;
updateImageDisplay();
hBar->setValue(static_cast<int>(hRatio * hBar->maximum()));
vBar->setValue(static_cast<int>(vRatio * vBar->maximum()));
}
event->accept();
}
void ImageZoomView::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
m_dragging = true;
m_lastPos = event->pos();
setCursor(Qt::ClosedHandCursor);
}
QWidget::mousePressEvent(event);
}
void ImageZoomView::mouseMoveEvent(QMouseEvent* event)
{
if (m_dragging) {
QPoint delta = event->pos() - m_lastPos;
m_lastPos = event->pos();
QScrollBar* hBar = m_scrollArea->horizontalScrollBar();
QScrollBar* vBar = m_scrollArea->verticalScrollBar();
hBar->setValue(hBar->value() - delta.x());
vBar->setValue(vBar->value() - delta.y());
}
QWidget::mouseMoveEvent(event);
}
void ImageZoomView::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
m_dragging = false;
setCursor(Qt::ArrowCursor);
}
QWidget::mouseReleaseEvent(event);
}
void ImageZoomView::mouseDoubleClickEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
resetZoom();
}
QWidget::mouseDoubleClickEvent(event);
}
void ImageZoomView::keyPressEvent(QKeyEvent* event)
{
switch (event->key()) {
case Qt::Key_Escape:
emit exitRequested();
break;
case Qt::Key_Plus:
case Qt::Key_Equal:
m_scaleFactor = qMin(m_scaleFactor + SCALE_STEP, MAX_SCALE);
updateImageDisplay();
break;
case Qt::Key_Minus:
m_scaleFactor = qMax(m_scaleFactor - SCALE_STEP, MIN_SCALE);
updateImageDisplay();
break;
case Qt::Key_0:
m_scaleFactor = 1.0;
updateImageDisplay();
break;
case Qt::Key_F:
resetZoom();
break;
default:
QWidget::keyPressEvent(event);
}
}
void ImageZoomView::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
if (!m_originalImage.isNull()) {
resetZoom();
}
}
// ============================================================================
// DetectImageWidget 实现
// ============================================================================
DetectImageWidget::DetectImageWidget(QWidget* parent)
: QWidget(parent)
{
setupUI();
}
DetectImageWidget::~DetectImageWidget()
{
}
void DetectImageWidget::setupUI()
{
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// 使用 QStackedWidget 切换普通视图和放大视图
m_stackedWidget = new QStackedWidget(this);
// 普通视图 - QGraphicsView
m_graphicsView = new QGraphicsView(this);
m_scene = new QGraphicsScene(this);
m_graphicsView->setScene(m_scene);
m_graphicsView->setRenderHint(QPainter::Antialiasing);
m_graphicsView->setRenderHint(QPainter::SmoothPixmapTransform);
m_graphicsView->setDragMode(QGraphicsView::NoDrag);
m_graphicsView->setStyleSheet("QGraphicsView { background-color: rgb(47, 48, 52); border: none; }");
m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_graphicsView->setCursor(Qt::PointingHandCursor);
// 设置点击事件过滤
m_graphicsView->viewport()->installEventFilter(this);
m_stackedWidget->addWidget(m_graphicsView);
// 放大视图
m_zoomView = new ImageZoomView(this);
connect(m_zoomView, &ImageZoomView::exitRequested, this, &DetectImageWidget::showNormalView);
m_stackedWidget->addWidget(m_zoomView);
mainLayout->addWidget(m_stackedWidget);
// 默认显示普通视图
m_stackedWidget->setCurrentIndex(0);
// 设置右键菜单
m_graphicsView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_graphicsView, &QGraphicsView::customContextMenuRequested,
this, &DetectImageWidget::showContextMenu);
// 创建右键菜单
m_contextMenu = new QMenu(this);
m_saveDataAction = new QAction(QString::fromUtf8("保存检测数据"), this);
m_saveDataAction->setEnabled(false);
// 设置菜单样式
m_contextMenu->setStyleSheet(
"QMenu { background-color: rgb(60, 62, 68); color: rgb(221, 225, 233); border: 1px solid rgb(80, 82, 88); }"
"QMenu::item { padding: 8px 25px; }"
"QMenu::item:selected { background-color: rgb(80, 82, 88); }"
"QMenu::item:disabled { color: rgb(120, 120, 120); }"
);
m_contextMenu->addAction(m_saveDataAction);
connect(m_saveDataAction, &QAction::triggered, this, &DetectImageWidget::onSaveDataTriggered);
}
bool DetectImageWidget::eventFilter(QObject* obj, QEvent* event)
{
if (obj == m_graphicsView->viewport()) {
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton) {
// 点击放大
if (!m_image.isNull()) {
showZoomView();
emit clicked();
}
return true;
}
}
}
return QWidget::eventFilter(obj, event);
}
void DetectImageWidget::setImage(const QImage& image)
{
m_image = image;
updateImageDisplay();
}
QImage DetectImageWidget::getImage() const
{
return m_image;
}
void DetectImageWidget::clearImage()
{
m_image = QImage();
m_scene->clear();
}
void DetectImageWidget::setTitle(const QString& title)
{
m_title = title;
}
void DetectImageWidget::setSaveDataEnabled(bool enabled)
{
m_saveDataEnabled = enabled;
m_saveDataAction->setEnabled(enabled);
}
void DetectImageWidget::setSaveDataCallback(const SaveDataCallback& callback)
{
m_saveDataCallback = callback;
}
void DetectImageWidget::setSaveDataMenuText(const QString& text)
{
m_saveDataAction->setText(text);
}
void DetectImageWidget::showZoomView()
{
if (m_image.isNull()) {
return;
}
m_zoomView->setImage(m_image, m_title);
m_stackedWidget->setCurrentIndex(1);
m_zoomView->setFocus();
}
void DetectImageWidget::showNormalView()
{
m_stackedWidget->setCurrentIndex(0);
}
void DetectImageWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
updateImageDisplay();
}
void DetectImageWidget::updateImageDisplay()
{
if (m_image.isNull()) {
return;
}
m_scene->clear();
QPixmap pixmap = QPixmap::fromImage(m_image);
m_scene->addPixmap(pixmap);
m_scene->setSceneRect(pixmap.rect());
// 自适应显示
m_graphicsView->fitInView(m_scene->sceneRect(), Qt::KeepAspectRatio);
}
void DetectImageWidget::showContextMenu(const QPoint& pos)
{
// 检查是否有数据可保存
if (!m_saveDataEnabled) {
return;
}
m_contextMenu->exec(m_graphicsView->mapToGlobal(pos));
}
void DetectImageWidget::onSaveDataTriggered()
{
// 发射信号,让外部处理保存逻辑
emit saveDataRequested(this);
// 如果设置了回调,也调用回调
if (m_saveDataCallback) {
// 获取默认保存路径
QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss");
QString defaultFileName = QString("%1/detect_data_%2.pcd").arg(defaultPath).arg(timestamp);
QString filePath = QFileDialog::getSaveFileName(
this,
QString::fromUtf8("保存检测数据"),
defaultFileName,
QString::fromUtf8("点云文件 (*.pcd);;所有文件 (*.*)")
);
if (!filePath.isEmpty()) {
m_saveDataCallback(filePath);
}
}
}

View File

@ -0,0 +1,132 @@
#include "DetectLogHelper.h"
DetectLogHelper::DetectLogHelper(QListView* listView, QObject *parent)
: QObject(parent)
, m_listView(listView)
, m_logModel(nullptr)
, m_lastLogCount(0)
, m_timestampFormat("hh:mm:ss")
, m_showTimestamp(true)
, m_deduplicationEnabled(true)
{
if (m_listView) {
// 创建并设置 QStringListModel
m_logModel = new QStringListModel(this);
m_listView->setModel(m_logModel);
// 设置 QListView 属性
m_listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_listView->setSelectionMode(QAbstractItemView::NoSelection);
m_listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
}
initConnections();
}
DetectLogHelper::~DetectLogHelper()
{
}
void DetectLogHelper::initConnections()
{
// 连接信号槽,支持跨线程更新
connect(this, &DetectLogHelper::logUpdateRequested,
this, &DetectLogHelper::updateLogInUI,
Qt::QueuedConnection);
connect(this, &DetectLogHelper::logClearRequested,
this, &DetectLogHelper::clearLogInUI,
Qt::QueuedConnection);
}
void DetectLogHelper::appendLog(const QString& message)
{
// 通过信号槽机制确保在UI线程中更新
emit logUpdateRequested(message);
}
void DetectLogHelper::clearLog()
{
// 通过信号槽机制确保在UI线程中清空
emit logClearRequested();
}
void DetectLogHelper::setTimestampFormat(const QString& format)
{
m_timestampFormat = format;
}
void DetectLogHelper::setShowTimestamp(bool show)
{
m_showTimestamp = show;
}
void DetectLogHelper::setDeduplicationEnabled(bool enable)
{
m_deduplicationEnabled = enable;
}
QStringListModel* DetectLogHelper::model() const
{
return m_logModel;
}
void DetectLogHelper::updateLogInUI(const QString& message)
{
if (!m_logModel || !m_listView) return;
// 获取当前数据
QStringList logList = m_logModel->stringList();
// 构建日志条目
QString logEntry;
if (m_deduplicationEnabled && message == m_lastLogMessage && !logList.isEmpty()) {
// 相同消息,增加计数并替换最后一条
m_lastLogCount++;
if (m_showTimestamp) {
QString timestamp = QDateTime::currentDateTime().toString(m_timestampFormat);
logEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
} else {
logEntry = QString("%1 (x%2)").arg(message).arg(m_lastLogCount);
}
// 替换最后一条
logList[logList.size() - 1] = logEntry;
} else {
// 新消息,重置计数
m_lastLogMessage = message;
m_lastLogCount = 1;
if (m_showTimestamp) {
QString timestamp = QDateTime::currentDateTime().toString(m_timestampFormat);
logEntry = QString("[%1] %2").arg(timestamp).arg(message);
} else {
logEntry = message;
}
// 添加新的日志条目
logList.append(logEntry);
}
// 更新模型
m_logModel->setStringList(logList);
// 自动滚动到最底部
if (!logList.isEmpty()) {
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
m_listView->scrollTo(lastIndex);
}
}
void DetectLogHelper::clearLogInUI()
{
if (m_logModel) {
m_logModel->setStringList(QStringList());
}
// 重置日志计数器
m_lastLogMessage.clear();
m_lastLogCount = 0;
}

View File

@ -29,7 +29,9 @@ HEADERS += \
Inc/CommonDialogCamera.h \
Inc/CommonDialogCameraLevel.h \
Inc/DialogImageViewer.h \
Inc/AuthView.h
Inc/AuthView.h \
Inc/DetectLogHelper.h \
Inc/DetectImageWidget.h
SOURCES += \
Src/StyledMessageBox.cpp \
@ -38,7 +40,9 @@ SOURCES += \
Src/CommonDialogCamera.cpp \
Src/CommonDialogCameraLevel.cpp \
Src/DialogImageViewer.cpp \
Src/AuthView.cpp
Src/AuthView.cpp \
Src/DetectLogHelper.cpp \
Src/DetectImageWidget.cpp
FORMS += \
DeviceStatusWidget.ui \