807 lines
27 KiB
C++
807 lines
27 KiB
C++
#include "BeltTearingPresenter.h"
|
||
#include "PathManager.h"
|
||
#include "version.h"
|
||
#include "PointCloudImageUtils.h"
|
||
#include "ImageProcessingWorker.h"
|
||
#include <QUuid>
|
||
#include <QDataStream>
|
||
#include <QJsonDocument>
|
||
#include <QJsonObject>
|
||
#include <QJsonArray>
|
||
#include <mutex>
|
||
#include <deque>
|
||
#include <QDateTime>
|
||
#include <QBuffer>
|
||
#include <QMutexLocker>
|
||
#include <QFile>
|
||
#include <QDir>
|
||
#include <iostream>
|
||
|
||
#include "VrLog.h"
|
||
#include "VrTimeUtils.h"
|
||
|
||
// 静态实例指针
|
||
BeltTearingPresenter* BeltTearingPresenter::s_instance = nullptr;
|
||
|
||
BeltTearingPresenter::BeltTearingPresenter(QObject *parent)
|
||
: QObject(parent)
|
||
, m_tcpServer(nullptr)
|
||
, m_cameraInitTimer(new QTimer(this))
|
||
, m_port(0)
|
||
, m_config(nullptr)
|
||
, m_eyeDevice(nullptr)
|
||
, m_cameraInitialized(false)
|
||
, m_cameraDetecting(false)
|
||
, m_lineCounter(0)
|
||
, m_imageWorker(new ImageProcessingWorker(this))
|
||
{
|
||
// 设置静态实例
|
||
s_instance = this;
|
||
|
||
// 打印版本信息
|
||
LOG_INFO("Initializing %s %s\n", getProductName().toStdString().c_str(), getVersionString().toStdString().c_str());
|
||
LOG_INFO("Build: %s\n", getBuildInfo().toStdString().c_str());
|
||
|
||
// 连接定时器信号
|
||
connect(m_cameraInitTimer, &QTimer::timeout, this, &BeltTearingPresenter::onCameraInitTimer);
|
||
|
||
// 连接工作线程信号(只连接图像生成信号)
|
||
connect(m_imageWorker, &ImageProcessingWorker::imageGenerated, this, &BeltTearingPresenter::onImageGenerated);
|
||
|
||
// 工作线程在构造函数中自动启动,无需手动调用start()
|
||
|
||
// 创建TCP服务器实例
|
||
if (!VrCreatYTCPServer(&m_tcpServer)) {
|
||
LOG_ERROR("Failed to create TCP server\n");
|
||
m_tcpServer = nullptr;
|
||
}
|
||
|
||
// 创建配置实例
|
||
IVrBeltTearingConfig::CreateInstance(&m_config);
|
||
if (m_config) {
|
||
m_config->SetConfigChangeNotify(this);
|
||
}
|
||
|
||
// 初始化SDK算法参数 - 使用配置系统的默认值
|
||
m_algorithmParam = configToSDKParam(BeltTearingConfigResult());
|
||
|
||
// 初始化相机
|
||
initializeCamera();
|
||
}
|
||
|
||
BeltTearingPresenter::~BeltTearingPresenter()
|
||
{
|
||
// 清除静态实例
|
||
s_instance = nullptr;
|
||
|
||
// 停止工作线程
|
||
if (m_imageWorker) {
|
||
m_imageWorker->stop();
|
||
// 析构函数会自动等待线程结束,无需手动调用wait()
|
||
delete m_imageWorker;
|
||
m_imageWorker = nullptr;
|
||
}
|
||
|
||
stopServer();
|
||
stopCamera();
|
||
|
||
// 清理激光线队列中的内存
|
||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||
for (auto& line : m_laserLineQueue) {
|
||
if (line.p3DPoint) {
|
||
free(line.p3DPoint);
|
||
line.p3DPoint = nullptr;
|
||
}
|
||
}
|
||
m_laserLineQueue.clear();
|
||
|
||
if (m_tcpServer) {
|
||
delete m_tcpServer;
|
||
m_tcpServer = nullptr;
|
||
}
|
||
|
||
if (m_config) {
|
||
delete m_config;
|
||
m_config = nullptr;
|
||
}
|
||
}
|
||
|
||
bool BeltTearingPresenter::loadConfiguration(const QString& configFilePath)
|
||
{
|
||
if (!m_config) {
|
||
LOG_WARNING("Config instance not created\n");
|
||
return false;
|
||
}
|
||
|
||
m_configFilePath = configFilePath;
|
||
|
||
try {
|
||
// 加载配置文件
|
||
m_configResult = m_config->LoadConfig(configFilePath.toStdString());
|
||
|
||
LOG_INFO("Configuration loaded successfully:\n");
|
||
LOG_INFO(" Server Port: %d\n", m_configResult.serverPort);
|
||
LOG_INFO(" Project Type: %d\n", static_cast<int>(m_configResult.projectType));
|
||
LOG_INFO(" Servers count: %d\n", m_configResult.servers.size());
|
||
|
||
// 应用算法参数
|
||
applyAlgorithmParameters(m_configResult.algorithmParams);
|
||
|
||
return true;
|
||
|
||
} catch (const std::exception& e) {
|
||
LOG_ERROR("Error loading configuration: %s\n", e.what());
|
||
return false;
|
||
}
|
||
}
|
||
|
||
void BeltTearingPresenter::applyAlgorithmParameters(const BeltTearingAlgorithmParams& params)
|
||
{
|
||
// 使用配置结构转换为SDK参数
|
||
BeltTearingConfigResult tempConfig;
|
||
tempConfig.algorithmParams = params;
|
||
m_algorithmParam = configToSDKParam(tempConfig);
|
||
|
||
// 应用算法参数
|
||
LOG_DEBUG("Applying SDK algorithm parameters...\n");
|
||
LOG_DEBUG(" Scan X Scale: %f\n", m_algorithmParam.scanXScale);
|
||
LOG_DEBUG(" Scan Y Scale: %f\n", m_algorithmParam.scanYScale);
|
||
LOG_DEBUG(" Difference Bin Threshold: %f\n", m_algorithmParam.differnceBinTh);
|
||
LOG_DEBUG(" Min Tear Length: %f\n", m_algorithmParam.tearingMinLen);
|
||
LOG_DEBUG(" Min Tear Gap: %f\n", m_algorithmParam.tearingMinGap);
|
||
LOG_DEBUG(" Same Gap Threshold: %f\n", m_algorithmParam.extractPara.sameGapTh);
|
||
LOG_DEBUG(" Gap Check Window: %d\n", m_algorithmParam.extractPara.gapChkWin);
|
||
|
||
// 监控参数
|
||
LOG_DEBUG(" Check Interval: %d ms\n", params.monitoringParam.checkInterval);
|
||
LOG_DEBUG(" Alert Threshold: %f\n", params.monitoringParam.alertThreshold);
|
||
|
||
// 调试参数
|
||
if (m_configResult.debugParam.enableDebug) {
|
||
LOG_DEBUG("Debug mode enabled:\n");
|
||
LOG_DEBUG(" Save debug images: %s\n", (m_configResult.debugParam.saveDebugImage ? "Yes" : "No"));
|
||
LOG_DEBUG(" Print detail log: %s\n", (m_configResult.debugParam.printDetailLog ? "Yes" : "No"));
|
||
}
|
||
}
|
||
|
||
void BeltTearingPresenter::OnConfigChanged(const BeltTearingConfigResult& configResult)
|
||
{
|
||
LOG_INFO("Configuration changed notification received\n");
|
||
|
||
// 更新配置结果
|
||
m_configResult = configResult;
|
||
|
||
// 重新应用算法参数
|
||
applyAlgorithmParameters(configResult.algorithmParams);
|
||
|
||
// 如果服务器端口改变,可能需要重启服务器
|
||
LOG_INFO("Server port changed, restarting server...\n");
|
||
stopServer();
|
||
startServer(configResult.serverPort);
|
||
}
|
||
|
||
void BeltTearingPresenter::sendTestData(std::string fileName){
|
||
if (!m_tcpServer) {
|
||
LOG_WARNING("TCP server not initialized, cannot send test data\n");
|
||
return;
|
||
}
|
||
|
||
// 判断文件类型
|
||
std::ifstream inputFile(fileName);
|
||
if (!inputFile.is_open()) {
|
||
LOG_WARN("UN open file \n");
|
||
return;
|
||
} else {
|
||
LOG_DEBUG("------------------------ \n");
|
||
}
|
||
|
||
std::string line;
|
||
std::vector<SVzNL3DPosition> sVzNLPostion;
|
||
sVzNLPostion.clear();
|
||
|
||
bool bFindLineNum = true;
|
||
int nLaserPointIdx = 0;
|
||
|
||
SVzLaserLineData pLaserLine;
|
||
|
||
while (std::getline(inputFile, line)) {
|
||
if (line.find("Line_") == 0) {
|
||
|
||
if(!sVzNLPostion.empty()){
|
||
pLaserLine.llFrameIdx = nLaserPointIdx;
|
||
pLaserLine.p3DPoint = sVzNLPostion.data();
|
||
pLaserLine.nPointCount = sVzNLPostion.size();
|
||
processPointCloudData(&pLaserLine);
|
||
}
|
||
sVzNLPostion.clear();
|
||
|
||
} else if (line.find("{") == 0) {
|
||
float lx, ly, rx, ry;
|
||
SVzNL3DPosition pos;
|
||
sscanf(line.c_str(), "{ %lf, %lf, %lf }-{ %f, %f}-{ %f, %f }",
|
||
&pos.pt3D.x, &pos.pt3D.y, &pos.pt3D.z,
|
||
&lx, &ly, &rx, &ry);
|
||
|
||
|
||
sVzNLPostion.push_back(pos);
|
||
}
|
||
|
||
// std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||
}
|
||
|
||
inputFile.close();
|
||
}
|
||
|
||
bool BeltTearingPresenter::initializeCamera()
|
||
{
|
||
if (m_eyeDevice) {
|
||
return true; // 已经初始化过
|
||
}
|
||
|
||
int result = IVrEyeDevice::CreateObject(&m_eyeDevice);
|
||
if (result != 0 || !m_eyeDevice) {
|
||
LOG_ERROR("Failed to create VrEyeDevice object, error code: %d\n", result);
|
||
return false;
|
||
}
|
||
|
||
result = m_eyeDevice->InitDevice();
|
||
if (result != 0) {
|
||
LOG_ERROR("Failed to initialize VrEyeDevice, error code: %d\n", result);
|
||
delete m_eyeDevice;
|
||
m_eyeDevice = nullptr;
|
||
return false;
|
||
}
|
||
|
||
// 设置状态回调
|
||
result = m_eyeDevice->SetStatusCallback(OnStatusCallback, this);
|
||
if (result != 0) {
|
||
LOG_WARNING("Failed to set status callback, error code: %d\n", result);
|
||
}
|
||
|
||
LOG_INFO("VrEyeDevice initialized successfully\n");
|
||
return true;
|
||
}
|
||
|
||
void BeltTearingPresenter::startCamera()
|
||
{
|
||
if (!m_eyeDevice) {
|
||
if (!initializeCamera()) {
|
||
// 启动定时器持续重试初始化
|
||
LOG_WARNING("Camera initialization failed, starting retry timer...\n");
|
||
m_cameraInitTimer->start(5000); // 每5秒重试一次
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 尝试打开设备
|
||
int result = m_eyeDevice->OpenDevice(nullptr, false, false, true);
|
||
if (result != 0) {
|
||
LOG_WARNING("Failed to open camera device, error code: %d, retrying...\n", result);
|
||
|
||
// 启动定时器持续重试连接
|
||
m_cameraInitTimer->start(3000); // 每3秒重试一次
|
||
return;
|
||
}
|
||
|
||
LOG_DEBUG("Camera started successfully \n");
|
||
|
||
// 停止重试定时器
|
||
m_cameraInitTimer->stop();
|
||
m_cameraInitialized = true;
|
||
|
||
// 开始检测
|
||
result = m_eyeDevice->StartDetect(OnPointCloudCallback, keResultDataType_Position, this);
|
||
LOG_DEBUG("Camera detection started, result: %d\n", result);
|
||
if (result != 0) {
|
||
LOG_ERROR("Failed to start detection, error code: %d\n", result);
|
||
return;
|
||
}
|
||
|
||
m_cameraDetecting = true;
|
||
}
|
||
|
||
void BeltTearingPresenter::stopCamera()
|
||
{
|
||
m_cameraInitTimer->stop();
|
||
|
||
if (m_eyeDevice) {
|
||
if (m_cameraDetecting) {
|
||
m_eyeDevice->StopDetect();
|
||
m_cameraDetecting = false;
|
||
}
|
||
|
||
if (m_cameraInitialized) {
|
||
m_eyeDevice->CloseDevice();
|
||
m_cameraInitialized = false;
|
||
}
|
||
|
||
delete m_eyeDevice;
|
||
m_eyeDevice = nullptr;
|
||
}
|
||
|
||
LOG_INFO("Camera stopped\n");
|
||
}
|
||
|
||
void BeltTearingPresenter::onCameraInitTimer()
|
||
{
|
||
LOG_DEBUG("Retrying camera initialization...\n");
|
||
|
||
// 尝试重新初始化和连接相机
|
||
if (!m_eyeDevice) {
|
||
if (!initializeCamera()) {
|
||
return; // 继续重试
|
||
}
|
||
}
|
||
|
||
// 尝试连接相机
|
||
int result = m_eyeDevice->OpenDevice(nullptr, false, false, true);
|
||
if (result == 0) {
|
||
// 连接成功,开始检测
|
||
m_cameraInitTimer->stop();
|
||
m_cameraInitialized = true;
|
||
|
||
result = m_eyeDevice->StartDetect(OnPointCloudCallback, keResultDataType_Position, this);
|
||
if (result == 0) {
|
||
m_cameraDetecting = true;
|
||
LOG_INFO("Camera connection restored successfully!\n");
|
||
} else {
|
||
LOG_ERROR("Failed to start detection after reconnection, error code: %d\n", result);
|
||
}
|
||
} else {
|
||
LOG_WARNING("Still unable to connect to camera, continuing retry...\n");
|
||
}
|
||
}
|
||
|
||
void BeltTearingPresenter::OnStatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam)
|
||
{
|
||
BeltTearingPresenter* presenter = static_cast<BeltTearingPresenter*>(pInfoParam);
|
||
if (!presenter) return;
|
||
|
||
LOG_DEBUG("Camera status changed: %d\n", static_cast<int>(eStatus));
|
||
|
||
// 处理相机状态变化
|
||
switch (eStatus) {
|
||
case keDeviceWorkStatus_Eye_Comming:
|
||
LOG_INFO("Camera connected\n");
|
||
break;
|
||
case keDeviceWorkStatus_Offline:
|
||
LOG_WARNING("Camera disconnected, attempting to reconnect...\n");
|
||
presenter->m_cameraInitialized = false;
|
||
presenter->m_cameraDetecting = false;
|
||
// 开始重连尝试
|
||
if (!presenter->m_cameraInitTimer->isActive()) {
|
||
presenter->m_cameraInitTimer->start(3000);
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void BeltTearingPresenter::OnPointCloudCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pParam)
|
||
{
|
||
if(keResultDataType_Position != eDataType) return;
|
||
|
||
BeltTearingPresenter* presenter = static_cast<BeltTearingPresenter*>(pParam);
|
||
if (!presenter || !pLaserLinePoint) return;
|
||
|
||
// 处理点云数据
|
||
presenter->processPointCloudData(pLaserLinePoint);
|
||
}
|
||
|
||
void BeltTearingPresenter::processPointCloudData(const SVzLaserLineData* pLaserLine)
|
||
{
|
||
if (!pLaserLine) return;
|
||
if(pLaserLine->nPointCount <= 0) return;
|
||
|
||
try {
|
||
// 将激光线数据添加到队列(用于图像生成)
|
||
addLaserLineToQueue(pLaserLine);
|
||
|
||
|
||
|
||
// 调用SDK算法
|
||
int errorCode = 0;
|
||
|
||
std::vector<SSG_beltTearingInfo> beltTearings_new;
|
||
std::vector<SSG_beltTearingInfo> beltTearings_growing;
|
||
std::vector<SSG_beltTearingInfo> beltTearings_ended;
|
||
std::vector<SSG_beltTearingInfo> beltTearings_unknown;
|
||
|
||
if(!m_bInitAlgo){
|
||
m_hLineWorkers.resize(pLaserLine->nPointCount);
|
||
|
||
sg_detectBeltTearing(
|
||
NULL, //空扫描线,用于复位内部静态变量
|
||
0,
|
||
0,
|
||
&errorCode,
|
||
m_hLineWorkers,
|
||
beltTearings_new,
|
||
beltTearings_growing,
|
||
beltTearings_ended,
|
||
beltTearings_unknown, //未判明,应用无需处理。
|
||
m_algorithmParam);
|
||
m_bInitAlgo = true;
|
||
}
|
||
|
||
// 使用SDK算法进行撕裂检测
|
||
SVzNL3DLaserLine algorithmData;
|
||
algorithmData.nTimeStamp = pLaserLine->llTimeStamp;
|
||
algorithmData.p3DPosition = static_cast<SVzNL3DPosition*>(pLaserLine->p3DPoint);
|
||
algorithmData.nPositionCnt = pLaserLine->nPointCount;
|
||
|
||
sg_detectBeltTearing(
|
||
&algorithmData,
|
||
static_cast<int>(pLaserLine->llFrameIdx),
|
||
algorithmData.nPositionCnt,
|
||
&errorCode,
|
||
m_hLineWorkers,
|
||
beltTearings_new,
|
||
beltTearings_growing,
|
||
beltTearings_ended,
|
||
beltTearings_unknown,
|
||
m_algorithmParam
|
||
);
|
||
|
||
// 合并所有检测结果
|
||
std::vector<SSG_beltTearingInfo> allResults;
|
||
allResults.reserve(beltTearings_new.size() + beltTearings_growing.size() +
|
||
beltTearings_ended.size() + beltTearings_unknown.size());
|
||
|
||
allResults.insert(allResults.end(), beltTearings_new.begin(), beltTearings_new.end());
|
||
// allResults.insert(allResults.end(), beltTearings_growing.begin(), beltTearings_growing.end());
|
||
allResults.insert(allResults.end(), beltTearings_ended.begin(), beltTearings_ended.end());
|
||
// allResults.insert(allResults.end(), beltTearings_unknown.begin(), beltTearings_unknown.end());
|
||
|
||
LOG_DEBUG("line count : %d algo detect count: %d (new:%d, growing:%d, ended:%d, unknown:%d)[%d]\n",
|
||
algorithmData.nPositionCnt,
|
||
allResults.size(), beltTearings_new.size(), beltTearings_growing.size(),
|
||
beltTearings_ended.size(), beltTearings_unknown.size(), errorCode);
|
||
|
||
// 发送检测结果
|
||
if (!allResults.empty()) {
|
||
sendTearingResults(allResults);
|
||
}
|
||
|
||
} catch (const std::exception& e) {
|
||
LOG_ERROR("Error in point cloud data processing: %s\n", e.what());
|
||
}
|
||
}
|
||
|
||
void BeltTearingPresenter::sendTearingResults(const std::vector<SSG_beltTearingInfo>& results)
|
||
{
|
||
if (results.empty() || m_clients.isEmpty() || !m_tcpServer) {
|
||
return;
|
||
}
|
||
|
||
// 将检测结果转换为JSON格式
|
||
QJsonArray tearingArray;
|
||
|
||
for (const auto& result : results) {
|
||
QJsonObject tearingObj;
|
||
// tearingObj["level"] = QString::number(result.level);
|
||
tearingObj["id"] = QString::number(result.tearID);
|
||
tearingObj["tearStatus"] = QString::number(static_cast<int>(result.tearStatus));
|
||
tearingObj["tearType"] = QString::number(result.tearType);
|
||
tearingObj["statLineIdx"] = QString::number(result.statLineIdx);
|
||
tearingObj["endLineIdx"] = QString::number(result.endLineIdx);
|
||
tearingObj["tearDepth"] = QString::number(result.tearDepth);
|
||
tearingObj["tearWidth"] = QString::number(result.tearWidth);
|
||
// tearingObj["tearLength"] = QString::number(result.tearLength);
|
||
tearingObj["roiLeft"] = QString::number(result.roi.left);
|
||
tearingObj["roiRight"] = QString::number(result.roi.right);
|
||
tearingObj["roiTop"] = QString::number(result.roi.top);
|
||
tearingObj["roiBottom"] = QString::number(result.roi.bottom);
|
||
// tearingObj["imageFile"] = QString::fromStdString(result.imageFile);
|
||
// tearingObj["older"] = QString::fromStdString(result.older);
|
||
tearingObj["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
|
||
|
||
tearingArray.append(tearingObj);
|
||
}
|
||
|
||
// 转换为JSON字符串
|
||
QJsonDocument doc(tearingArray);
|
||
QByteArray message = doc.toJson(QJsonDocument::Compact);
|
||
|
||
// 创建数据包
|
||
QByteArray package;
|
||
QDataStream stream(&package, QIODevice::WriteOnly);
|
||
stream.setByteOrder(QDataStream::BigEndian);
|
||
stream << quint8(static_cast<quint8>(ByteDataType::Text)) << quint32(message.size());
|
||
stream.writeRawData(message.constData(), message.size());
|
||
package.append("___END___\r\n");
|
||
|
||
// 发送到所有连接的客户端
|
||
bool success = m_tcpServer->SendAllData(package.constData(), package.size());
|
||
if (success) {
|
||
LOG_DEBUG("Sent tearing results (%d bytes) to %d clients\n", package.size(), m_clients.size());
|
||
} else {
|
||
LOG_WARNING("Failed to send tearing results to all clients\n");
|
||
}
|
||
}
|
||
|
||
bool BeltTearingPresenter::startServer(quint16 port)
|
||
{
|
||
if (!m_tcpServer) {
|
||
LOG_ERROR("TCP server not created\n");
|
||
return false;
|
||
}
|
||
|
||
// 先停止现有服务器
|
||
stopServer();
|
||
|
||
m_port = port;
|
||
|
||
// 初始化TCP服务器
|
||
if (!m_tcpServer->Init(port, true)) { // 启用Nagle算法优化
|
||
LOG_ERROR("Failed to initialize TCP server on port %d\n", port);
|
||
return false;
|
||
}
|
||
|
||
// 设置事件回调
|
||
m_tcpServer->SetEventCallback(OnServerEvent);
|
||
|
||
// 启动TCP服务器
|
||
if (!m_tcpServer->Start(OnServerRecv, false)) { // 不使用自定义协议
|
||
LOG_ERROR("Failed to start TCP server on port %d\n", port);
|
||
return false;
|
||
}
|
||
|
||
LOG_INFO("TCP server started on port %d\n", port);
|
||
return true;
|
||
}
|
||
|
||
void BeltTearingPresenter::stopServer()
|
||
{
|
||
if (!m_tcpServer) {
|
||
return;
|
||
}
|
||
|
||
// 清空客户端映射
|
||
m_clients.clear();
|
||
|
||
// 停止TCP服务器
|
||
m_tcpServer->Stop();
|
||
m_tcpServer->Close();
|
||
|
||
m_port = 0;
|
||
LOG_INFO("TCP server stopped\n");
|
||
}
|
||
|
||
// 静态回调函数实现
|
||
void BeltTearingPresenter::OnServerRecv(const TCPClient* pClient, const char* pData, const unsigned int nLen)
|
||
{
|
||
if (s_instance) {
|
||
s_instance->handleServerRecv(pClient, pData, nLen);
|
||
}
|
||
}
|
||
|
||
void BeltTearingPresenter::OnServerEvent(const TCPClient* pClient, TCPServerEventType eventType)
|
||
{
|
||
if (s_instance) {
|
||
s_instance->handleServerEvent(pClient, eventType);
|
||
}
|
||
}
|
||
|
||
// 实例方法实现
|
||
void BeltTearingPresenter::handleServerRecv(const TCPClient* pClient, const char* pData, const unsigned int nLen)
|
||
{
|
||
// 处理接收到的数据 - 这里暂时只记录日志
|
||
QString clientId = generateClientId(pClient);
|
||
LOG_DEBUG("Received %d bytes from client %s\n", nLen, clientId.toStdString().c_str());
|
||
}
|
||
|
||
void BeltTearingPresenter::handleServerEvent(const TCPClient* pClient, TCPServerEventType eventType)
|
||
{
|
||
QString clientId = generateClientId(pClient);
|
||
|
||
switch (eventType) {
|
||
case TCP_EVENT_CLIENT_CONNECTED:
|
||
m_clients[clientId] = pClient;
|
||
LOG_INFO("Client connected: %s\n", clientId.toStdString().c_str());
|
||
break;
|
||
|
||
case TCP_EVENT_CLIENT_DISCONNECTED:
|
||
m_clients.remove(clientId);
|
||
LOG_INFO("Client disconnected: %s\n", clientId.toStdString().c_str());
|
||
break;
|
||
|
||
case TCP_EVENT_CLIENT_EXCEPTION:
|
||
m_clients.remove(clientId);
|
||
LOG_WARNING("Client exception: %s\n", clientId.toStdString().c_str());
|
||
break;
|
||
}
|
||
}
|
||
|
||
QString BeltTearingPresenter::generateClientId(const TCPClient* client)
|
||
{
|
||
return QString("Client_%1").arg(client->m_nFD);
|
||
}
|
||
|
||
QString BeltTearingPresenter::getVersionString()
|
||
{
|
||
return QString(BELT_TEARING_SERVER_VERSION_STRING);
|
||
}
|
||
|
||
QString BeltTearingPresenter::getProductName()
|
||
{
|
||
return QString(BELT_TEARING_SERVER_PRODUCT_NAME);
|
||
}
|
||
|
||
QString BeltTearingPresenter::getBuildInfo()
|
||
{
|
||
return QString("%1 %2 built on %3 %4 for %5")
|
||
.arg(BELT_TEARING_SERVER_BUILD_TYPE)
|
||
.arg(BELT_TEARING_SERVER_VERSION_STRING)
|
||
.arg(BELT_TEARING_SERVER_BUILD_DATE)
|
||
.arg(BELT_TEARING_SERVER_BUILD_TIME)
|
||
.arg(BELT_TEARING_SERVER_PLATFORM);
|
||
}
|
||
|
||
// 激光线队列管理方法实现
|
||
void BeltTearingPresenter::addLaserLineToQueue(const SVzLaserLineData* laserLine)
|
||
{
|
||
if(laserLine == nullptr || laserLine->nPointCount <= 0){
|
||
return;
|
||
}
|
||
|
||
// 创建激光线数据的深拷贝
|
||
SVzLaserLineData copiedLine = *laserLine;
|
||
|
||
|
||
// 深拷贝点云数据
|
||
size_t pointDataSize = laserLine->nPointCount * sizeof(SVzNL3DPosition);
|
||
copiedLine.p3DPoint = static_cast<SVzNL3DPosition*>(malloc(pointDataSize));
|
||
if (copiedLine.p3DPoint) {
|
||
memcpy(copiedLine.p3DPoint, laserLine->p3DPoint, pointDataSize);
|
||
}
|
||
|
||
// 添加数据到队列的锁作用域
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||
// 添加到队列
|
||
m_laserLineQueue.push_back(copiedLine);
|
||
m_lineCounter++;
|
||
|
||
// 管理队列大小
|
||
while (m_laserLineQueue.size() > MAX_QUEUE_SIZE) {
|
||
SVzLaserLineData oldLine = m_laserLineQueue.front();
|
||
m_laserLineQueue.pop_front();
|
||
|
||
// 释放深拷贝的点云数据内存
|
||
if (oldLine.p3DPoint) {
|
||
free(oldLine.p3DPoint);
|
||
oldLine.p3DPoint = nullptr;
|
||
}
|
||
}
|
||
}
|
||
|
||
if(m_imageWorker && m_imageWorker->isProcessing()){
|
||
return;
|
||
}
|
||
|
||
|
||
// 每到图像生成间隔,提交图像生成任务给工作线程
|
||
if (m_lineCounter % IMAGE_GENERATION_INTERVAL == 0) {
|
||
// 准备扫描线数据
|
||
std::vector<std::vector<SVzNL3DPosition>> scanLines;
|
||
|
||
// 在锁保护下复制数据
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||
scanLines.reserve(m_laserLineQueue.size()); // 预分配内存提高效率
|
||
|
||
for (const auto& line : m_laserLineQueue) {
|
||
// 增加安全检查,确保指针有效
|
||
if (line.p3DPoint && line.nPointCount > 0) {
|
||
std::vector<SVzNL3DPosition> linePoints;
|
||
linePoints.reserve(line.nPointCount); // 预分配内存提高效率
|
||
|
||
SVzNL3DPosition* p3DPoints = static_cast<SVzNL3DPosition*>(line.p3DPoint);
|
||
|
||
// 使用更高效的批量复制
|
||
linePoints.assign(p3DPoints, p3DPoints + line.nPointCount);
|
||
scanLines.emplace_back(std::move(linePoints)); // 使用move避免拷贝
|
||
}
|
||
}
|
||
}
|
||
|
||
LOG_DEBUG("addLaserLineToQueue, line counter: %d, scanLines size: %d \n", m_lineCounter, scanLines.size());
|
||
// 只提交图像生成任务给工作线程(不包含算法检测)
|
||
if (m_imageWorker && !scanLines.empty()) {
|
||
m_imageWorker->requestImageGeneration(scanLines);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
void BeltTearingPresenter::sendImageToClients(const QImage& image)
|
||
{
|
||
if (image.isNull() || m_clients.isEmpty() || !m_tcpServer) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 将图像转换为字节数组
|
||
QByteArray imageData;
|
||
QBuffer buffer(&imageData);
|
||
buffer.open(QIODevice::WriteOnly);
|
||
|
||
// 保存为JPEG格式以减少数据大小
|
||
if (!image.save(&buffer, "JPEG", 85)) {
|
||
LOG_ERROR("Failed to convert image to JPEG format\n");
|
||
return;
|
||
}
|
||
|
||
// 构造数据包
|
||
QByteArray packet;
|
||
QDataStream stream(&packet, QIODevice::WriteOnly);
|
||
stream.setByteOrder(QDataStream::BigEndian);
|
||
|
||
// 写入数据类型标识(图像类型)
|
||
stream << static_cast<quint8>(ByteDataType::Image);
|
||
|
||
// 写入图像数据大小
|
||
stream << static_cast<quint32>(imageData.size());
|
||
|
||
// 写入图像数据
|
||
stream.writeRawData(imageData.constData(), imageData.size());
|
||
packet.append("___END___\r\n");
|
||
|
||
|
||
// 发送给所有连接的客户端
|
||
bool success = m_tcpServer->SendAllData(packet.constData(), packet.size());
|
||
if (success) {
|
||
LOG_DEBUG("Sent point cloud image (%d bytes) to %d clients\n", packet.size(), m_clients.size());
|
||
} else {
|
||
LOG_WARNING("Failed to send image data to all clients\n");
|
||
}
|
||
|
||
} catch (const std::exception& e) {
|
||
LOG_ERROR("Error sending image to clients: %s\n", e.what());
|
||
}
|
||
}
|
||
|
||
void BeltTearingPresenter::onImageGenerated(const QImage& image)
|
||
{
|
||
sendImageToClients(image);
|
||
}
|
||
|
||
SSG_beltTearingParam BeltTearingPresenter::configToSDKParam(const BeltTearingConfigResult& config) const
|
||
{
|
||
SSG_beltTearingParam sdkParam;
|
||
|
||
// 基本参数
|
||
sdkParam.scanXScale = config.algorithmParams.beltTearingParam.scanXScale;
|
||
sdkParam.scanYScale = config.algorithmParams.beltTearingParam.scanYScale;
|
||
sdkParam.differnceBinTh = config.algorithmParams.beltTearingParam.differnceBinTh;
|
||
sdkParam.tearingMinLen = config.algorithmParams.beltTearingParam.tearingMinLen;
|
||
sdkParam.tearingMinGap = config.algorithmParams.beltTearingParam.tearingMinGap;
|
||
|
||
// 特征提取参数
|
||
sdkParam.extractPara.sameGapTh = config.algorithmParams.beltTearingParam.sameGapTh;
|
||
sdkParam.extractPara.gapChkWin = config.algorithmParams.beltTearingParam.gapChkWin;
|
||
|
||
return sdkParam;
|
||
}
|
||
|
||
BeltTearingConfigResult BeltTearingPresenter::sdkToConfigParam(const SSG_beltTearingParam& sdkParam) const
|
||
{
|
||
BeltTearingConfigResult config;
|
||
|
||
// 基本参数
|
||
config.algorithmParams.beltTearingParam.scanXScale = sdkParam.scanXScale;
|
||
config.algorithmParams.beltTearingParam.scanYScale = sdkParam.scanYScale;
|
||
config.algorithmParams.beltTearingParam.differnceBinTh = sdkParam.differnceBinTh;
|
||
config.algorithmParams.beltTearingParam.tearingMinLen = sdkParam.tearingMinLen;
|
||
config.algorithmParams.beltTearingParam.tearingMinGap = sdkParam.tearingMinGap;
|
||
|
||
// 特征提取参数
|
||
config.algorithmParams.beltTearingParam.sameGapTh = sdkParam.extractPara.sameGapTh;
|
||
config.algorithmParams.beltTearingParam.gapChkWin = sdkParam.extractPara.gapChkWin;
|
||
|
||
return config;
|
||
}
|