#include "GvfToCsv.h" #include #include #include #include #include #include #include #include #include "GlobalDefine.h" GvfToCsv::GvfToCsv(QObject *parent) : QObject(parent) { connect(m_sqliteWorker.get(), &SQLiteReadWrite::operationCompleted, this, &GvfToCsv::onSqliteOperationCompleted); connect(m_sqliteWorker.get(), &SQLiteReadWrite::progressUpdated, this, &GvfToCsv::conversionProgress); connect(m_sqliteWorker.get(), &SQLiteReadWrite::logMessage, this, [](const QString &msg) { qDebug() << "[GVF转换日志]" << msg; }); } GvfToCsv::~GvfToCsv() { cleanUp(); if (m_syncEventLoop) { delete m_syncEventLoop; } } QList GvfToCsv::parseParticleFrames(const QByteArray &data) { QList particles; const int minDataSize = GvfConst::HEADER_SIZE + GvfConst::PARTICLE_BLOCK_SIZE; if (data.size() < minDataSize) { qDebug() << "粒子数据长度不足,跳过解析"; return particles; } const quint8* rawData = reinterpret_cast(data.constData()); if (rawData[0] != GvfConst::HEADER_BYTE1 || rawData[1] != GvfConst::HEADER_BYTE2) { qDebug() << "粒子数据头部校验失败,跳过解析"; return particles; } const quint8* ptr = rawData + GvfConst::HEADER_SIZE; int remaining = data.size() - GvfConst::HEADER_SIZE; particles.reserve(remaining / GvfConst::PARTICLE_BLOCK_SIZE); while (remaining >= GvfConst::PARTICLE_BLOCK_SIZE) { quint16 alignWord = qFromLittleEndian(static_cast(ptr)); quint8 lowByte = alignWord & 0xFF; quint8 highByte = (alignWord >> 8) & 0xFF; if (highByte != GvfConst::ALIGN_HIGH_BYTE) { ptr += GvfConst::PARTICLE_BLOCK_SIZE; remaining -= GvfConst::PARTICLE_BLOCK_SIZE; continue; } const quint8 board = (lowByte >> 4) & 0x0F; const quint8 channel = lowByte & 0x0F; const int channelIndex = board * 4 + channel; if (channelIndex >= GvfConst::MAX_CHANNEL_COUNT) { ptr += GvfConst::PARTICLE_BLOCK_SIZE; remaining -= GvfConst::PARTICLE_BLOCK_SIZE; continue; } quint64 timestamp = 0; const quint8* timestampPtr = ptr + 2; for (int i = 0; i < 6; ++i) { timestamp |= static_cast(timestampPtr[i]) << (8 * i); } const quint16 amplitude = qFromLittleEndian(static_cast(ptr + 8)); const quint32 icr = qFromLittleEndian(static_cast(ptr + 10)); const quint16 riseTime = qFromLittleEndian(static_cast(ptr + 14)); const quint16 fallTime = qFromLittleEndian(static_cast(ptr + 16)); const int address = static_cast( static_cast(amplitude) / GvfConst::AMPLITUDE_MAX * GvfConst::MAX_ADDRESS ); ParticleData p( board, channel, timestamp, timestamp * GvfConst::TIME_PER_COUNT, amplitude, address, icr, riseTime, fallTime ); particles.append(p); ptr += GvfConst::PARTICLE_BLOCK_SIZE; remaining -= GvfConst::PARTICLE_BLOCK_SIZE; } return particles; } void GvfToCsv::cleanUp() { if (m_sqliteWorker) { // 先断开所有信号连接 disconnect(m_sqliteWorker.get(), nullptr, this, nullptr); // 停止操作 m_sqliteWorker->stopOperation(); // 等待操作完成 QEventLoop loop; QTimer timeoutTimer; timeoutTimer.setSingleShot(true); connect(m_sqliteWorker.get(), &SQLiteReadWrite::operationCompleted, &loop, &QEventLoop::quit); connect(&timeoutTimer, &QTimer::timeout, &loop, &QEventLoop::quit); timeoutTimer.start(3000); loop.exec(QEventLoop::ExcludeUserInputEvents); m_sqliteWorker.reset(); } m_gvfPath.clear(); m_csvPath.clear(); m_lastError.clear(); } bool GvfToCsv::convertGVF2CSVSync(const QString &gvfPath, const QString &csvPath) { if (!m_syncEventLoop) { m_syncEventLoop = new QEventLoop(); } bool result = false; QObject signalReceiver; connect(this, &GvfToCsv::conversionFinished, &signalReceiver, [&](bool success) { result = success; m_syncEventLoop->quit(); }); connect(this, &GvfToCsv::errorOccurred, &signalReceiver, [](const QString &msg) { LOG_ERROR(QStringLiteral(u"GVF转换错误: %1").arg(msg).toUtf8().constData()); }); convertGVF2CSVAsync(gvfPath, csvPath); m_syncEventLoop->exec(); return result; } void GvfToCsv::convertGVF2CSVAsync(const QString &gvfPath, const QString &csvPath) { m_lastError.clear(); cleanUp(); if (gvfPath.isEmpty() || csvPath.isEmpty()) { setLastError("GVF/CSV路径不能为空"); emit conversionFinished(false); return; } QFileInfo gvfFileInfo(gvfPath); if (!gvfFileInfo.exists() || !gvfFileInfo.isFile()) { setLastError(QString("GVF文件不存在: %1").arg(gvfPath)); emit conversionFinished(false); return; } QFileInfo csvFileInfo(csvPath); QDir csvDir = csvFileInfo.absoluteDir(); if (!csvDir.exists() && !csvDir.mkpath(".")) { setLastError(QString("无法创建CSV目录: %1").arg(csvDir.path())); emit conversionFinished(false); return; } m_gvfPath = gvfPath; m_csvPath = csvPath; m_sqliteWorker = std::make_unique(this); connect(m_sqliteWorker.get(), &SQLiteReadWrite::operationCompleted, this, &GvfToCsv::onSqliteOperationCompleted, Qt::QueuedConnection); connect(m_sqliteWorker.get(), &SQLiteReadWrite::progressUpdated, this, &GvfToCsv::conversionProgress, Qt::QueuedConnection); if (!m_sqliteWorker->openDatabase(m_gvfPath)) { setLastError(QString("打开GVF数据库失败: %1").arg(m_gvfPath)); emit conversionFinished(false); return; } m_sqliteWorker->startReadWriteOperation(); } void GvfToCsv::onSqliteOperationCompleted(bool success, const QString &msg) { if (!m_sqliteWorker) { setLastError("SQLite 工作对象已被销毁"); emit conversionFinished(false); return; } if (!success) { setLastError(QString("GVF数据读取失败: %1").arg(msg)); cleanUp(); emit conversionFinished(false); return; } try { processDBData(); emit conversionFinished(true); } catch (const std::exception &e) { setLastError(QString("CSV写入异常: %1").arg(e.what())); emit conversionFinished(false); } catch (...) { setLastError("CSV写入未知异常"); emit conversionFinished(false); } cleanUp(); } void GvfToCsv::processDBData() { if (!m_sqliteWorker) { throw std::runtime_error("SQLite 工作对象已销毁,无法读取数据"); } QVector dbList = m_sqliteWorker->DataBaseList(); if (dbList.isEmpty()) { throw std::runtime_error("GVF数据库中无任何记录"); return; } // 从lmstatisticinfov2读取软件记录的测量启动时间,精度最高 QString measureStartAbsTime = m_sqliteWorker->getMeasureStartTime(); QFile outFile(m_csvPath); if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { throw std::runtime_error(QString("无法创建CSV文件: %1,错误: %2") .arg(m_csvPath) .arg(outFile.errorString()) .toStdString()); } QTextStream out(&outFile); out.setCodec("UTF-8"); out << QStringLiteral(u"板卡号,通道号,道址,时间计数\n"); QString csvBuffer; csvBuffer.reserve(4 * 1024 * 1024); // 4MB缓冲区 quint64 totalParticles = 0; quint64 totalIcrSum = 0; // 所有粒子ICR输入触发总计数 Cicr quint64 tsMin = ULLONG_MAX; // 全部粒子最小时间戳计数(用于计算总时长) quint64 tsMax = 0; // 全部粒子最大时间戳计数 int emptyFrameCount = 0; int processedFrames = 0; const int totalFrames = dbList.size(); for (const auto &data : dbList) { QList particles = parseParticleFrames(data.data); if (particles.isEmpty()) { emptyFrameCount++; } else { totalParticles += particles.size(); for (const auto &p : particles) { csvBuffer += QString("%1,%2,%3,%4\n") .arg(p.boardId) .arg(p.channelId) .arg(p.address) .arg(p.timestampCount); //硬件输入触发ICR总和累加 totalIcrSum += p.icr; //更新全局最小时间戳 if(p.timestampCount < tsMin) tsMin = p.timestampCount; //更新全局最大时间戳 if(p.timestampCount > tsMax) tsMax = p.timestampCount; } } if (csvBuffer.size() >= 4 * 1024 * 1024) { out << csvBuffer; csvBuffer.clear(); } processedFrames++; if (processedFrames % 1000 == 0) { int remainingFrames = totalFrames - processedFrames; int progress = (processedFrames * 100) / totalFrames; emit conversionProgress(progress); } } if (!csvBuffer.isEmpty()) { out << csvBuffer; } double realTime = 0.0; // 实时间RealTime Tr:物理测量总时长(单位s) double liveTime = 0.0; // 活时间LiveTime Tl:探测器有效采集时长(单位s) double deadTime = 0.0; // 死时间DeadTime Td:硬件阻塞无法采集时长(单位s) // 存在有效粒子且时间戳有差值,才具备计算时间的条件 if(totalParticles > 0 && tsMax > tsMin) { // 时间戳差值 = 最大计数 - 最小计数 quint64 tsDelta = tsMax - tsMin; // 计算公式:实时间 = 总计数差 / 时钟频率(200MHz) realTime = static_cast(tsDelta) / (GvfConst::CLOCK_FREQ_MHZ * 1e6); // 防止ICR总和为0除零崩溃 if(totalIcrSum > 0) { // 有效粒子占输入触发总计数比例 double countRatio = static_cast(totalParticles) / totalIcrSum; // 活时间 = 实时间 × 有效粒子占比 liveTime = realTime * countRatio; // 死时间 = 实时间 - 活时间 deadTime = realTime - liveTime; } } QFileInfo csvFileInfo(m_csvPath); QString jsonFilePath = csvFileInfo.absoluteDir().filePath("report_params.json"); QFile jsonFile(jsonFilePath); QJsonObject rootObj; rootObj["real_time_s"] = realTime; // 真时间,单位秒 rootObj["live_time_s"] = liveTime; // 活时间,单位秒 rootObj["dead_time_s"] = deadTime; // 死时间,单位秒 rootObj["start_absolute_time"] = measureStartAbsTime; QFileInfo gvfInfo(m_gvfPath); rootObj["gvf_file_name"] = gvfInfo.fileName(); // 生成带缩进格式化JSON文本,便于阅读 QJsonDocument jsonDoc(rootObj); QByteArray jsonContent = jsonDoc.toJson(QJsonDocument::Indented); // 写入JSON文件,覆盖原有文件 if (!jsonFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { throw std::runtime_error(QString("Generate report_params.json failed, path:%1 error:%2") .arg(jsonFilePath) .arg(jsonFile.errorString()) .toStdString()); } jsonFile.write(jsonContent); jsonFile.flush(); jsonFile.close(); out.flush(); outFile.close(); if (outFile.error() != QFile::NoError) { throw std::runtime_error(QString("CSV文件写入失败: %1,错误: %2") .arg(m_csvPath) .arg(outFile.errorString()) .toStdString()); } if (totalParticles == 0) { QFile::remove(m_csvPath); throw std::runtime_error(QString("GVF文件中未解析到任何有效粒子数据,空帧数: %1") .arg(emptyFrameCount) .toStdString()); } emit conversionProgress(100); }