feat: AiAnalysis - 16通道AI数据UDP实时采集与分析

功能:
- UDP端口监听,实时接收数据
- 解析16通道逗号分隔的ADC数据
- 实时波形绘制(支持滚轮缩放、右键拖拽、双击恢复)
- 数据自动保存到 data_端口号/ 目录
- 支持打开历史CSV文件回放查看
- 16通道独立颜色,可切换显示/隐藏
- 深色主题界面
This commit is contained in:
iorebuild
2026-04-30 12:46:49 +08:00
commit 7e3593c898
11 changed files with 1232 additions and 0 deletions

127
datamanager.cpp Normal file
View File

@@ -0,0 +1,127 @@
#include "datamanager.h"
#include <QDir>
#include <QDebug>
DataManager::DataManager(QObject *parent)
: QObject(parent)
, m_recording(false)
, m_headerWritten(false)
{
}
DataManager::~DataManager()
{
stopRecording();
}
bool DataManager::startRecording(const QString &dirPath)
{
stopRecording();
QDir dir(dirPath);
if (!dir.exists()) {
if (!dir.mkpath(".")) {
qWarning() << "无法创建目录:" << dirPath;
return false;
}
}
QString fileName = QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm-ss") + ".csv";
m_filePath = dir.filePath(fileName);
m_file.setFileName(m_filePath);
if (!m_file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "无法创建文件:" << m_filePath;
return false;
}
m_stream.setDevice(&m_file);
m_headerWritten = false;
m_recording = true;
qDebug() << "开始记录数据到:" << m_filePath;
return true;
}
void DataManager::stopRecording()
{
if (m_recording) {
m_stream.flush();
m_file.close();
m_recording = false;
qDebug() << "停止记录, 文件:" << m_filePath;
}
}
bool DataManager::isRecording() const
{
return m_recording;
}
QString DataManager::currentFilePath() const
{
return m_filePath;
}
void DataManager::appendDataPoint(const QVector<int> &values)
{
if (!m_recording) return;
if (!m_headerWritten) {
// 写入表头
QStringList headers;
for (int i = 0; i < 16; ++i) {
headers << QString("ch%1").arg(i);
}
m_stream << headers.join(",") << "\n";
m_headerWritten = true;
}
QStringList parts;
for (int v : values) {
parts << QString::number(v);
}
m_stream << parts.join(",") << "\n";
}
QVector<QVector<double>> DataManager::loadFile(const QString &filePath, QString &error)
{
QVector<QVector<double>> result(16);
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
error = QString("无法打开文件: %1").arg(filePath);
return result;
}
QTextStream stream(&file);
bool firstLine = true;
while (!stream.atEnd()) {
QString line = stream.readLine().trimmed();
if (line.isEmpty()) continue;
// 跳过表头
if (firstLine) {
firstLine = false;
// 检查是否是表头包含非数字字符如ch
if (line.contains("ch", Qt::CaseInsensitive) && !line.at(0).isDigit()) {
continue;
}
}
QStringList parts = line.split(",");
if (parts.size() < 16) continue;
for (int i = 0; i < 16; ++i) {
bool ok;
double val = parts[i].toDouble(&ok);
if (ok) {
result[i].append(val);
}
}
}
file.close();
return result;
}