轮胎检测更新算法
This commit is contained in:
parent
4004807396
commit
81a5298e14
@ -165,7 +165,7 @@ else:win32:CONFIG(debug, debug|release): {
|
|||||||
}
|
}
|
||||||
else:unix:!macx: {
|
else:unix:!macx: {
|
||||||
LIBS += -L$$PWD/../../../AppAlgo/workpieceHolePositioning/Arm/aarch64 -lworkpieceHolePositioning -lbaseAlgorithm
|
LIBS += -L$$PWD/../../../AppAlgo/workpieceHolePositioning/Arm/aarch64 -lworkpieceHolePositioning -lbaseAlgorithm
|
||||||
LIBS += -L$$PWD/../../../SDK/OpenCV320/Arm/aarch64 -lopencv_core -lopencv_imgproc -lopencv_highgui
|
LIBS += -L$$PWD/../../../SDK/OpenCV320/Arm/aarch64 -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs
|
||||||
}
|
}
|
||||||
|
|
||||||
# 添加libmodbus依赖
|
# 添加libmodbus依赖
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#define TIREHOLEPOSE_VERSION_H
|
#define TIREHOLEPOSE_VERSION_H
|
||||||
|
|
||||||
#define TIREHOLEPOSE_APP_NAME "轮胎孔定位"
|
#define TIREHOLEPOSE_APP_NAME "轮胎孔定位"
|
||||||
#define TIREHOLEPOSE_VERSION_STRING "1.0.1"
|
#define TIREHOLEPOSE_VERSION_STRING "1.0.2"
|
||||||
#define TIREHOLEPOSE_BUILD_STRING "1"
|
#define TIREHOLEPOSE_BUILD_STRING "1"
|
||||||
#define TIREHOLEPOSE_FULL_VERSION_STRING "V" TIREHOLEPOSE_VERSION_STRING "_" TIREHOLEPOSE_BUILD_STRING
|
#define TIREHOLEPOSE_FULL_VERSION_STRING "V" TIREHOLEPOSE_VERSION_STRING "_" TIREHOLEPOSE_BUILD_STRING
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
# 1.0.1 2026-07-03
|
||||||
|
## build_1
|
||||||
|
1. 更新算法
|
||||||
|
|
||||||
# 1.0.1 2026-06-26
|
# 1.0.1 2026-06-26
|
||||||
## build_1
|
## build_1
|
||||||
1. 更新算法
|
1. 更新算法
|
||||||
|
|||||||
@ -441,9 +441,8 @@ protected:
|
|||||||
*
|
*
|
||||||
* @param debugParam 调试参数
|
* @param debugParam 调试参数
|
||||||
*/
|
*/
|
||||||
void SetDebugParam(const VrDebugParam& debugParam) {
|
void SetDebugParam(const VrDebugParam& debugParam);
|
||||||
m_debugParam = debugParam;
|
void SetDebugDataSaveQueueSize(int maxQueueSize);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 保存点云文件时附加在设备名后的标签(虚函数,子类可选择重写)
|
* @brief 保存点云文件时附加在设备名后的标签(虚函数,子类可选择重写)
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
#ifndef DEBUG_DATA_SAVER_H
|
#ifndef DEBUG_DATA_SAVER_H
|
||||||
#define DEBUG_DATA_SAVER_H
|
#define DEBUG_DATA_SAVER_H
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <thread>
|
|
||||||
#include <mutex>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <queue>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "VZNL_Types.h"
|
#include "VZNL_Types.h"
|
||||||
|
|
||||||
class DebugDataSaver
|
class DebugDataSaver
|
||||||
@ -17,49 +20,56 @@ public:
|
|||||||
explicit DebugDataSaver(int maxThreads = 4);
|
explicit DebugDataSaver(int maxThreads = 4);
|
||||||
~DebugDataSaver();
|
~DebugDataSaver();
|
||||||
|
|
||||||
// 异步保存点云数据(内部深拷贝数据,不阻塞调用线程)
|
|
||||||
// timestamp: 检测时刻的时间戳,格式 YYYYMMDD_HHmmss
|
|
||||||
// 调用方需在持有数据锁时调用,DeepCopyData 会在调用线程完成深拷贝
|
|
||||||
void SaveAsync(const std::string& basePath,
|
void SaveAsync(const std::string& basePath,
|
||||||
const std::string& timestamp,
|
const std::string& timestamp,
|
||||||
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data,
|
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data,
|
||||||
const std::string& deviceName = "");
|
const std::string& deviceName = "");
|
||||||
|
|
||||||
// 停止所有工作线程(等待队列中任务完成后退出)
|
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
// 设置 PointCloud 目录最大总大小(字节)。超过后保存前会循环删除最早的文件。默认 10GB
|
|
||||||
void SetMaxStorageSize(int64_t bytes) { m_maxStorageSize = bytes; }
|
void SetMaxStorageSize(int64_t bytes) { m_maxStorageSize = bytes; }
|
||||||
|
void SetMaxQueueSize(int maxTasks);
|
||||||
|
void PrepareStorageCache(const std::string& basePath);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct SaveTask {
|
struct SaveTask {
|
||||||
|
std::string basePath;
|
||||||
std::string filePath;
|
std::string filePath;
|
||||||
std::string deviceName;
|
std::string deviceName;
|
||||||
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>> data;
|
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>> data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct StorageFileInfo {
|
||||||
|
std::string path;
|
||||||
|
int64_t size = 0;
|
||||||
|
int64_t lastModifiedMSecs = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StorageCache {
|
||||||
|
bool initialized = false;
|
||||||
|
int64_t totalSize = 0;
|
||||||
|
std::vector<StorageFileInfo> files;
|
||||||
|
};
|
||||||
|
|
||||||
void WorkerThread();
|
void WorkerThread();
|
||||||
|
|
||||||
// 深拷贝检测数据(含原始指针数组的完整拷贝)
|
|
||||||
static std::vector<std::pair<EVzResultDataType, SVzLaserLineData>> DeepCopyData(
|
static std::vector<std::pair<EVzResultDataType, SVzLaserLineData>> DeepCopyData(
|
||||||
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& src);
|
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& src);
|
||||||
|
|
||||||
// 释放深拷贝的数据
|
|
||||||
static void FreeData(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data);
|
static void FreeData(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data);
|
||||||
|
|
||||||
// 生成带日期目录的完整保存路径: basePath/YYYYMMDD/pointcloud[_deviceName]_YYYYMMDD_HHmmss_NNN.txt
|
std::string GenerateSavePath(const std::string& basePath,
|
||||||
// timestamp: 检测时刻的时间戳,格式 YYYYMMDD_HHmmss
|
const std::string& timestamp,
|
||||||
// deviceName: 设备名称(为空时不包含在文件名中)
|
const std::string& deviceName = "");
|
||||||
std::string GenerateSavePath(const std::string& basePath, const std::string& timestamp, const std::string& deviceName = "");
|
|
||||||
|
|
||||||
// 确保 basePath 总大小不超过 m_maxStorageSize,超过时循环删除最早的文件
|
|
||||||
// basePath: SaveAsync 传入的根目录(不含日期子目录)
|
|
||||||
void EnsureStorageLimit(const std::string& basePath);
|
void EnsureStorageLimit(const std::string& basePath);
|
||||||
|
void EnsureStorageLimitLocked(const std::string& basePath, StorageCache& cache);
|
||||||
|
void BuildStorageCacheLocked(const std::string& basePath, StorageCache& cache);
|
||||||
|
void AddFileToCacheLocked(StorageCache& cache, const StorageFileInfo& fileInfo);
|
||||||
|
void RecordSavedFile(const std::string& basePath, const std::string& filePath);
|
||||||
|
|
||||||
// 统计 rootPath 下(含子目录)所有 .txt 文件的总大小(字节)
|
static bool RemoveFileFromCache(StorageCache& cache, const std::string& filePath);
|
||||||
|
static bool IsOlderFile(const StorageFileInfo& lhs, const StorageFileInfo& rhs);
|
||||||
static int64_t ComputeDirSize(const std::string& rootPath);
|
static int64_t ComputeDirSize(const std::string& rootPath);
|
||||||
|
|
||||||
// 查找 rootPath 下(含子目录)按修改时间排序最早的 .txt 文件,找不到返回空串
|
|
||||||
static std::string FindOldestFile(const std::string& rootPath);
|
static std::string FindOldestFile(const std::string& rootPath);
|
||||||
|
|
||||||
int m_maxThreads;
|
int m_maxThreads;
|
||||||
@ -68,10 +78,11 @@ private:
|
|||||||
std::mutex m_queueMutex;
|
std::mutex m_queueMutex;
|
||||||
std::condition_variable m_queueCondition;
|
std::condition_variable m_queueCondition;
|
||||||
std::atomic<bool> m_running;
|
std::atomic<bool> m_running;
|
||||||
|
std::atomic<int> m_maxQueueSize{10};
|
||||||
|
|
||||||
// 磁盘空间维护:多个 worker 共享,必须串行
|
|
||||||
std::mutex m_diskSpaceMutex;
|
std::mutex m_diskSpaceMutex;
|
||||||
std::atomic<int64_t> m_maxStorageSize{10LL * 1024 * 1024 * 1024}; // 默认 10GB
|
std::atomic<int64_t> m_maxStorageSize{10LL * 1024 * 1024 * 1024};
|
||||||
|
std::map<std::string, StorageCache> m_storageCaches;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DEBUG_DATA_SAVER_H
|
#endif // DEBUG_DATA_SAVER_H
|
||||||
|
|||||||
@ -10,6 +10,28 @@
|
|||||||
#include "PathManager.h"
|
#include "PathManager.h"
|
||||||
#include "IYModbusTCPServer.h"
|
#include "IYModbusTCPServer.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
QString ResolveDebugOutputPath(const VrDebugParam& debugParam)
|
||||||
|
{
|
||||||
|
if (debugParam.debugOutputPath.empty()) {
|
||||||
|
return QCoreApplication::applicationDirPath() + "/debug";
|
||||||
|
}
|
||||||
|
return QString::fromStdString(debugParam.debugOutputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString EnsureDebugOutputPath(const VrDebugParam& debugParam)
|
||||||
|
{
|
||||||
|
QString outputPath = ResolveDebugOutputPath(debugParam);
|
||||||
|
QDir dir(outputPath);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkpath(".");
|
||||||
|
}
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
BasePresenter::BasePresenter(QObject *parent)
|
BasePresenter::BasePresenter(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_currentCameraIndex(0)
|
, m_currentCameraIndex(0)
|
||||||
@ -366,6 +388,21 @@ void BasePresenter::SetWorkStatus(WorkStatus status)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============ InitCamera 完整实现 ============
|
// ============ InitCamera 完整实现 ============
|
||||||
|
void BasePresenter::SetDebugParam(const VrDebugParam& debugParam)
|
||||||
|
{
|
||||||
|
m_debugParam = debugParam;
|
||||||
|
|
||||||
|
if (m_debugParam.enableDebug && m_debugParam.savePointCloud) {
|
||||||
|
QString outputPath = EnsureDebugOutputPath(m_debugParam);
|
||||||
|
m_debugDataSaver.PrepareStorageCache(outputPath.toStdString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BasePresenter::SetDebugDataSaveQueueSize(int maxQueueSize)
|
||||||
|
{
|
||||||
|
m_debugDataSaver.SetMaxQueueSize(maxQueueSize);
|
||||||
|
}
|
||||||
|
|
||||||
int BasePresenter::InitCamera(std::vector<DeviceInfo>& cameraList, bool bRGB, bool bSwing)
|
int BasePresenter::InitCamera(std::vector<DeviceInfo>& cameraList, bool bRGB, bool bSwing)
|
||||||
{
|
{
|
||||||
LOG_INFO("[BasePresenter] InitCamera\n");
|
LOG_INFO("[BasePresenter] InitCamera\n");
|
||||||
@ -618,19 +655,9 @@ int BasePresenter::DetectTask()
|
|||||||
// 2. 调试模式 - 异步保存点云数据(不持 m_detectionDataMutex 锁,深拷贝在 SaveAsync 内进行)
|
// 2. 调试模式 - 异步保存点云数据(不持 m_detectionDataMutex 锁,深拷贝在 SaveAsync 内进行)
|
||||||
if (debugParam.enableDebug && debugParam.savePointCloud) {
|
if (debugParam.enableDebug && debugParam.savePointCloud) {
|
||||||
// 确定输出路径
|
// 确定输出路径
|
||||||
QString outputPath;
|
QString outputPath = EnsureDebugOutputPath(debugParam);
|
||||||
if (debugParam.debugOutputPath.empty()) {
|
|
||||||
outputPath = QCoreApplication::applicationDirPath() + "/debug";
|
|
||||||
} else {
|
|
||||||
outputPath = QString::fromStdString(debugParam.debugOutputPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保输出根目录存在
|
// 确保输出根目录存在
|
||||||
QDir dir(outputPath);
|
|
||||||
if (!dir.exists()) {
|
|
||||||
dir.mkpath(".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在检测时刻生成时间戳(毫秒精度),文件名将形如 {设备名}_YYYYMMDD_HHmmsszzz.txt
|
// 在检测时刻生成时间戳(毫秒精度),文件名将形如 {设备名}_YYYYMMDD_HHmmsszzz.txt
|
||||||
std::string timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmsszzz").toStdString();
|
std::string timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmsszzz").toStdString();
|
||||||
|
|
||||||
|
|||||||
@ -1,32 +1,48 @@
|
|||||||
#include "DebugDataSaver.h"
|
#include "DebugDataSaver.h"
|
||||||
#include "LaserDataLoader.h"
|
#include "LaserDataLoader.h"
|
||||||
#include "VrLog.h"
|
|
||||||
#include "VrError.h"
|
#include "VrError.h"
|
||||||
|
#include "VrLog.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QDateTime>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QDateTime>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// 获取当前应用名(不含扩展名),用于构造 debug 子目录前缀
|
|
||||||
QString AppName()
|
QString AppName()
|
||||||
{
|
{
|
||||||
QString name = QFileInfo(QCoreApplication::applicationFilePath()).baseName();
|
QString name = QFileInfo(QCoreApplication::applicationFilePath()).baseName();
|
||||||
if (name.isEmpty()) name = QStringLiteral("app");
|
if (name.isEmpty()) {
|
||||||
|
name = QStringLiteral("app");
|
||||||
|
}
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 子目录前缀:{appName}_PointCloud_,仅匹配此前缀的子目录会被统计/清理
|
|
||||||
QString SubDirPrefix()
|
QString SubDirPrefix()
|
||||||
{
|
{
|
||||||
return AppName() + QStringLiteral("_PointCloud_");
|
return AppName() + QStringLiteral("_PointCloud_");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string NormalizeBasePath(const std::string& basePath)
|
||||||
|
{
|
||||||
|
return QDir(QString::fromStdString(basePath)).absolutePath().toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string NormalizeFilePath(const std::string& filePath)
|
||||||
|
{
|
||||||
|
return QFileInfo(QString::fromStdString(filePath)).absoluteFilePath().toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
DebugDataSaver::DebugDataSaver(int maxThreads)
|
DebugDataSaver::DebugDataSaver(int maxThreads)
|
||||||
: m_maxThreads(std::max(1, std::min(maxThreads, 4)))
|
: m_maxThreads(std::max(1, std::min(maxThreads, 4)))
|
||||||
, m_running(true)
|
, m_running(true)
|
||||||
@ -34,7 +50,7 @@ DebugDataSaver::DebugDataSaver(int maxThreads)
|
|||||||
for (int i = 0; i < m_maxThreads; ++i) {
|
for (int i = 0; i < m_maxThreads; ++i) {
|
||||||
m_workers.emplace_back(&DebugDataSaver::WorkerThread, this);
|
m_workers.emplace_back(&DebugDataSaver::WorkerThread, this);
|
||||||
}
|
}
|
||||||
LOG_INFO("[DebugDataSaver] 已启动 %d 个存储线程\n", m_maxThreads);
|
LOG_INFO("[DebugDataSaver] started %d storage threads\n", m_maxThreads);
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugDataSaver::~DebugDataSaver()
|
DebugDataSaver::~DebugDataSaver()
|
||||||
@ -57,29 +73,68 @@ void DebugDataSaver::Stop()
|
|||||||
}
|
}
|
||||||
m_workers.clear();
|
m_workers.clear();
|
||||||
|
|
||||||
// 释放队列中未处理的任务
|
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||||
while (!m_taskQueue.empty()) {
|
while (!m_taskQueue.empty()) {
|
||||||
FreeData(m_taskQueue.front().data);
|
FreeData(m_taskQueue.front().data);
|
||||||
m_taskQueue.pop();
|
m_taskQueue.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO("[DebugDataSaver] 已停止\n");
|
LOG_INFO("[DebugDataSaver] stopped\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugDataSaver::SaveAsync(const std::string& basePath, const std::string& timestamp, const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data, const std::string& deviceName)
|
void DebugDataSaver::SetMaxQueueSize(int maxTasks)
|
||||||
{
|
{
|
||||||
if (data.empty() || !m_running) return;
|
if (maxTasks < 1) {
|
||||||
|
maxTasks = 1;
|
||||||
|
}
|
||||||
|
m_maxQueueSize = maxTasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugDataSaver::PrepareStorageCache(const std::string& basePath)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_diskSpaceMutex);
|
||||||
|
StorageCache& cache = m_storageCaches[NormalizeBasePath(basePath)];
|
||||||
|
if (!cache.initialized) {
|
||||||
|
BuildStorageCacheLocked(basePath, cache);
|
||||||
|
LOG_INFO("[DebugDataSaver] storage cache ready: %s, files=%zu, size=%lld\n",
|
||||||
|
basePath.c_str(), cache.files.size(), static_cast<long long>(cache.totalSize));
|
||||||
|
}
|
||||||
|
EnsureStorageLimitLocked(basePath, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugDataSaver::SaveAsync(const std::string& basePath,
|
||||||
|
const std::string& timestamp,
|
||||||
|
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& data,
|
||||||
|
const std::string& deviceName)
|
||||||
|
{
|
||||||
|
if (data.empty() || !m_running.load()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int maxQueueSize = m_maxQueueSize.load();
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||||
|
if (static_cast<int>(m_taskQueue.size()) >= maxQueueSize) {
|
||||||
|
LOG_WARNING("[DebugDataSaver] save queue full (%zu/%d), skip point cloud save: %s\n",
|
||||||
|
m_taskQueue.size(), maxQueueSize, deviceName.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SaveTask task;
|
SaveTask task;
|
||||||
|
task.basePath = basePath;
|
||||||
task.deviceName = deviceName;
|
task.deviceName = deviceName;
|
||||||
task.filePath = GenerateSavePath(basePath, timestamp, deviceName);
|
task.filePath = GenerateSavePath(basePath, timestamp, deviceName);
|
||||||
task.data = DeepCopyData(data);
|
task.data = DeepCopyData(data);
|
||||||
|
|
||||||
// 在入队前清理超限文件,避免任务堆积时磁盘瞬间打满
|
|
||||||
EnsureStorageLimit(basePath);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(m_queueMutex);
|
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||||
|
if (static_cast<int>(m_taskQueue.size()) >= maxQueueSize) {
|
||||||
|
LOG_WARNING("[DebugDataSaver] save queue full after copy (%zu/%d), skip point cloud save: %s\n",
|
||||||
|
m_taskQueue.size(), maxQueueSize, deviceName.c_str());
|
||||||
|
FreeData(task.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_taskQueue.push(std::move(task));
|
m_taskQueue.push(std::move(task));
|
||||||
}
|
}
|
||||||
m_queueCondition.notify_one();
|
m_queueCondition.notify_one();
|
||||||
@ -94,10 +149,10 @@ void DebugDataSaver::WorkerThread()
|
|||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(m_queueMutex);
|
std::unique_lock<std::mutex> lock(m_queueMutex);
|
||||||
m_queueCondition.wait(lock, [this] {
|
m_queueCondition.wait(lock, [this] {
|
||||||
return !m_taskQueue.empty() || !m_running;
|
return !m_taskQueue.empty() || !m_running.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!m_running && m_taskQueue.empty()) {
|
if (!m_running.load() && m_taskQueue.empty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (m_taskQueue.empty()) {
|
if (m_taskQueue.empty()) {
|
||||||
@ -116,9 +171,10 @@ void DebugDataSaver::WorkerThread()
|
|||||||
int result = dataLoader.SaveLaserScanData(task.filePath, task.data, lineNum, scanSpeed, maxTimeStamp, clockPerSecond);
|
int result = dataLoader.SaveLaserScanData(task.filePath, task.data, lineNum, scanSpeed, maxTimeStamp, clockPerSecond);
|
||||||
|
|
||||||
if (result == SUCCESS) {
|
if (result == SUCCESS) {
|
||||||
LOG_INFO("[DebugDataSaver] 保存成功: %s (%d行)\n", task.filePath.c_str(), lineNum);
|
RecordSavedFile(task.basePath, task.filePath);
|
||||||
|
LOG_INFO("[DebugDataSaver] saved: %s (%d lines)\n", task.filePath.c_str(), lineNum);
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR("[DebugDataSaver] 保存失败: %s, 错误: %s\n", task.filePath.c_str(), dataLoader.GetLastError().c_str());
|
LOG_ERROR("[DebugDataSaver] save failed: %s, error: %s\n", task.filePath.c_str(), dataLoader.GetLastError().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
FreeData(task.data);
|
FreeData(task.data);
|
||||||
@ -180,9 +236,10 @@ void DebugDataSaver::FreeData(std::vector<std::pair<EVzResultDataType, SVzLaserL
|
|||||||
data.clear();
|
data.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string DebugDataSaver::GenerateSavePath(const std::string& basePath, const std::string& timestamp, const std::string& deviceName)
|
std::string DebugDataSaver::GenerateSavePath(const std::string& basePath,
|
||||||
|
const std::string& timestamp,
|
||||||
|
const std::string& deviceName)
|
||||||
{
|
{
|
||||||
// 子目录命名:{appName}_PointCloud_YYYYMMDD(时间戳前 8 位为日期)
|
|
||||||
QString subDir = SubDirPrefix() + QString::fromStdString(timestamp.substr(0, 8));
|
QString subDir = SubDirPrefix() + QString::fromStdString(timestamp.substr(0, 8));
|
||||||
QString fullPath = QString::fromStdString(basePath) + "/" + subDir;
|
QString fullPath = QString::fromStdString(basePath) + "/" + subDir;
|
||||||
|
|
||||||
@ -191,8 +248,6 @@ std::string DebugDataSaver::GenerateSavePath(const std::string& basePath, const
|
|||||||
dir.mkpath(".");
|
dir.mkpath(".");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件名:{设备名}_{日期}_{时间}.txt(timestamp 形如 YYYYMMDD_HHmmsszzz)
|
|
||||||
// 设备名缺省时退化为 pointcloud
|
|
||||||
QString name = deviceName.empty() ? QStringLiteral("pointcloud")
|
QString name = deviceName.empty() ? QStringLiteral("pointcloud")
|
||||||
: QString::fromStdString(deviceName);
|
: QString::fromStdString(deviceName);
|
||||||
QString fileName = QString("%1/%2_%3.txt").arg(fullPath)
|
QString fileName = QString("%1/%2_%3.txt").arg(fullPath)
|
||||||
@ -205,60 +260,157 @@ std::string DebugDataSaver::GenerateSavePath(const std::string& basePath, const
|
|||||||
void DebugDataSaver::EnsureStorageLimit(const std::string& basePath)
|
void DebugDataSaver::EnsureStorageLimit(const std::string& basePath)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(m_diskSpaceMutex);
|
std::lock_guard<std::mutex> lock(m_diskSpaceMutex);
|
||||||
|
StorageCache& cache = m_storageCaches[NormalizeBasePath(basePath)];
|
||||||
|
if (!cache.initialized) {
|
||||||
|
BuildStorageCacheLocked(basePath, cache);
|
||||||
|
}
|
||||||
|
EnsureStorageLimitLocked(basePath, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugDataSaver::EnsureStorageLimitLocked(const std::string& basePath, StorageCache& cache)
|
||||||
|
{
|
||||||
|
Q_UNUSED(basePath);
|
||||||
|
|
||||||
const int64_t limit = m_maxStorageSize.load();
|
const int64_t limit = m_maxStorageSize.load();
|
||||||
if (limit <= 0) return;
|
if (limit <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int64_t totalSize = ComputeDirSize(basePath);
|
|
||||||
const QString prefix = SubDirPrefix();
|
const QString prefix = SubDirPrefix();
|
||||||
|
while (cache.totalSize > limit && !cache.files.empty()) {
|
||||||
|
StorageFileInfo oldest = cache.files.front();
|
||||||
|
cache.files.erase(cache.files.begin());
|
||||||
|
|
||||||
// 循环删除最早的文件,直到目录总大小 <= limit
|
QString filePath = QString::fromStdString(oldest.path);
|
||||||
while (totalSize > limit) {
|
QFileInfo info(filePath);
|
||||||
std::string oldest = FindOldestFile(basePath);
|
|
||||||
if (oldest.empty()) {
|
|
||||||
LOG_WARNING("[DebugDataSaver] 目录大小 %lld 超过上限 %lld,但无可删除文件\n",
|
|
||||||
static_cast<long long>(totalSize), static_cast<long long>(limit));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFileInfo info(QString::fromStdString(oldest));
|
|
||||||
qint64 fileSize = info.size();
|
|
||||||
QString parentPath = info.absolutePath();
|
QString parentPath = info.absolutePath();
|
||||||
|
int64_t fileSize = oldest.size;
|
||||||
|
|
||||||
if (!QFile::remove(QString::fromStdString(oldest))) {
|
if (QFileInfo::exists(filePath) && !QFile::remove(filePath)) {
|
||||||
LOG_ERROR("[DebugDataSaver] 删除失败: %s\n", oldest.c_str());
|
cache.files.insert(cache.files.begin(), oldest);
|
||||||
|
LOG_ERROR("[DebugDataSaver] delete failed: %s\n", oldest.path.c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO("[DebugDataSaver] 磁盘超限,删除最早文件: %s (%lld 字节)\n",
|
cache.totalSize -= fileSize;
|
||||||
oldest.c_str(), static_cast<long long>(fileSize));
|
if (cache.totalSize < 0) {
|
||||||
totalSize -= fileSize;
|
cache.totalSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("[DebugDataSaver] storage limit exceeded, delete oldest file: %s (%lld bytes)\n",
|
||||||
|
oldest.path.c_str(), static_cast<long long>(fileSize));
|
||||||
|
|
||||||
// 父目录若已为空且名字匹配 {appName}_PointCloud_* 前缀,连同目录一起回收
|
|
||||||
QDir parentDir(parentPath);
|
QDir parentDir(parentPath);
|
||||||
const QString parentName = parentDir.dirName();
|
const QString parentName = parentDir.dirName();
|
||||||
if (parentName.startsWith(prefix) && parentDir.isEmpty()) {
|
if (parentName.startsWith(prefix) && parentDir.isEmpty()) {
|
||||||
parentDir.cdUp();
|
parentDir.cdUp();
|
||||||
if (parentDir.rmdir(parentName)) {
|
if (parentDir.rmdir(parentName)) {
|
||||||
LOG_INFO("[DebugDataSaver] 已移除空目录: %s\n", parentPath.toStdString().c_str());
|
LOG_INFO("[DebugDataSaver] removed empty directory: %s\n", parentPath.toStdString().c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DebugDataSaver::BuildStorageCacheLocked(const std::string& basePath, StorageCache& cache)
|
||||||
|
{
|
||||||
|
cache.files.clear();
|
||||||
|
cache.totalSize = 0;
|
||||||
|
|
||||||
|
QDir rootDir(QString::fromStdString(basePath));
|
||||||
|
if (rootDir.exists()) {
|
||||||
|
const QString prefix = SubDirPrefix();
|
||||||
|
const QStringList subDirs = rootDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
for (const QString& name : subDirs) {
|
||||||
|
if (!name.startsWith(prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QString subPath = rootDir.absoluteFilePath(name);
|
||||||
|
QDirIterator it(subPath, QStringList() << "*.txt", QDir::Files, QDirIterator::Subdirectories);
|
||||||
|
while (it.hasNext()) {
|
||||||
|
it.next();
|
||||||
|
QFileInfo info = it.fileInfo();
|
||||||
|
StorageFileInfo fileInfo;
|
||||||
|
fileInfo.path = info.absoluteFilePath().toStdString();
|
||||||
|
fileInfo.size = info.size();
|
||||||
|
fileInfo.lastModifiedMSecs = info.lastModified().toMSecsSinceEpoch();
|
||||||
|
cache.totalSize += fileInfo.size;
|
||||||
|
cache.files.push_back(fileInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(cache.files.begin(), cache.files.end(), DebugDataSaver::IsOlderFile);
|
||||||
|
cache.initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugDataSaver::AddFileToCacheLocked(StorageCache& cache, const StorageFileInfo& fileInfo)
|
||||||
|
{
|
||||||
|
RemoveFileFromCache(cache, fileInfo.path);
|
||||||
|
auto it = std::lower_bound(cache.files.begin(), cache.files.end(), fileInfo, DebugDataSaver::IsOlderFile);
|
||||||
|
cache.files.insert(it, fileInfo);
|
||||||
|
cache.totalSize += fileInfo.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugDataSaver::RecordSavedFile(const std::string& basePath, const std::string& filePath)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_diskSpaceMutex);
|
||||||
|
StorageCache& cache = m_storageCaches[NormalizeBasePath(basePath)];
|
||||||
|
if (!cache.initialized) {
|
||||||
|
BuildStorageCacheLocked(basePath, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string normalizedFilePath = NormalizeFilePath(filePath);
|
||||||
|
QFileInfo info(QString::fromStdString(normalizedFilePath));
|
||||||
|
if (info.exists() && info.isFile()) {
|
||||||
|
StorageFileInfo fileInfo;
|
||||||
|
fileInfo.path = normalizedFilePath;
|
||||||
|
fileInfo.size = info.size();
|
||||||
|
fileInfo.lastModifiedMSecs = info.lastModified().toMSecsSinceEpoch();
|
||||||
|
AddFileToCacheLocked(cache, fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureStorageLimitLocked(basePath, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DebugDataSaver::RemoveFileFromCache(StorageCache& cache, const std::string& filePath)
|
||||||
|
{
|
||||||
|
auto it = std::find_if(cache.files.begin(), cache.files.end(), [&filePath](const StorageFileInfo& item) {
|
||||||
|
return item.path == filePath;
|
||||||
|
});
|
||||||
|
if (it == cache.files.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.totalSize -= it->size;
|
||||||
|
if (cache.totalSize < 0) {
|
||||||
|
cache.totalSize = 0;
|
||||||
|
}
|
||||||
|
cache.files.erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DebugDataSaver::IsOlderFile(const StorageFileInfo& lhs, const StorageFileInfo& rhs)
|
||||||
|
{
|
||||||
|
if (lhs.lastModifiedMSecs == rhs.lastModifiedMSecs) {
|
||||||
|
return lhs.path < rhs.path;
|
||||||
|
}
|
||||||
|
return lhs.lastModifiedMSecs < rhs.lastModifiedMSecs;
|
||||||
|
}
|
||||||
|
|
||||||
int64_t DebugDataSaver::ComputeDirSize(const std::string& rootPath)
|
int64_t DebugDataSaver::ComputeDirSize(const std::string& rootPath)
|
||||||
{
|
{
|
||||||
QString root = QString::fromStdString(rootPath);
|
QDir rootDir(QString::fromStdString(rootPath));
|
||||||
QDir rootDir(root);
|
if (!rootDir.exists()) {
|
||||||
if (!rootDir.exists()) return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const QString prefix = SubDirPrefix();
|
const QString prefix = SubDirPrefix();
|
||||||
int64_t total = 0;
|
int64_t total = 0;
|
||||||
|
|
||||||
// 只统计匹配 {appName}_debug_* 模式的子目录,避免误判 basePath 下其他内容
|
|
||||||
const QStringList subDirs = rootDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
const QStringList subDirs = rootDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
for (const QString& name : subDirs) {
|
for (const QString& name : subDirs) {
|
||||||
if (!name.startsWith(prefix)) continue;
|
if (!name.startsWith(prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
QString subPath = rootDir.absoluteFilePath(name);
|
QString subPath = rootDir.absoluteFilePath(name);
|
||||||
QDirIterator it(subPath, QStringList() << "*.txt", QDir::Files, QDirIterator::Subdirectories);
|
QDirIterator it(subPath, QStringList() << "*.txt", QDir::Files, QDirIterator::Subdirectories);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
@ -271,9 +423,10 @@ int64_t DebugDataSaver::ComputeDirSize(const std::string& rootPath)
|
|||||||
|
|
||||||
std::string DebugDataSaver::FindOldestFile(const std::string& rootPath)
|
std::string DebugDataSaver::FindOldestFile(const std::string& rootPath)
|
||||||
{
|
{
|
||||||
QString root = QString::fromStdString(rootPath);
|
QDir rootDir(QString::fromStdString(rootPath));
|
||||||
QDir rootDir(root);
|
if (!rootDir.exists()) {
|
||||||
if (!rootDir.exists()) return std::string();
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
const QString prefix = SubDirPrefix();
|
const QString prefix = SubDirPrefix();
|
||||||
QString oldestPath;
|
QString oldestPath;
|
||||||
@ -282,7 +435,9 @@ std::string DebugDataSaver::FindOldestFile(const std::string& rootPath)
|
|||||||
|
|
||||||
const QStringList subDirs = rootDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
const QStringList subDirs = rootDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
for (const QString& name : subDirs) {
|
for (const QString& name : subDirs) {
|
||||||
if (!name.startsWith(prefix)) continue;
|
if (!name.startsWith(prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
QString subPath = rootDir.absoluteFilePath(name);
|
QString subPath = rootDir.absoluteFilePath(name);
|
||||||
QDirIterator it(subPath, QStringList() << "*.txt", QDir::Files, QDirIterator::Subdirectories);
|
QDirIterator it(subPath, QStringList() << "*.txt", QDir::Files, QDirIterator::Subdirectories);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user