V0.22.2.250616_alpha:增加数据采集页面的CSV数据采集

This commit is contained in:
jinchao 2025-06-16 16:19:12 +08:00
parent fc788edcb5
commit 746bba9583
19 changed files with 899 additions and 61 deletions

Binary file not shown.

View File

@ -280,6 +280,12 @@ extern "C" std::string XNCORE_EXPORT getFileNameWithoutExt(const std::string &pa
extern "C" int XNCORE_EXPORT safe_stoi(const std::string &str, int defaultValue = 0);
extern "C" int XNCORE_EXPORT safe_stol(const std::string &str, int defaultValue = 0);
extern "C" double XNCORE_EXPORT safe_stod(const std::string &str, double defaultValue = 0);
extern "C" long long XNCORE_EXPORT safe_stoll(const std::string &str, long long defaultValue = 0);
inline std::string getStringFromSqlite3(sqlite3_stmt *stmt, int column)
{
const char *text = reinterpret_cast<const char *>(sqlite3_column_text(stmt, column));

View File

@ -175,7 +175,7 @@ protected:
if (data) {
return std::to_string(data.value());
} else {
return "Unknown";
return "0";
}
} else if constexpr (is_std_array_v<T>) {
if (data) {
@ -201,9 +201,9 @@ protected:
{
if constexpr (std::is_arithmetic_v<T>) {
if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
data = std::stod(value);
data = XNSim::safe_stod(value);
} else {
data = std::stoll(value);
data = XNSim::safe_stoll(value);
}
} else if constexpr (is_std_array_v<T>) {
// 解析输入字符串
@ -238,7 +238,13 @@ protected:
if (i > 0)
ss << ",";
if constexpr (std::is_arithmetic_v<T>) {
ss << data[i];
if constexpr (std::is_same_v<T, char>) {
ss << static_cast<int>(data[i]);
} else if constexpr (std::is_same_v<T, unsigned char>) {
ss << static_cast<unsigned int>(data[i]);
} else {
ss << data[i];
}
} else if constexpr (is_std_array_v<T>) {
ss << getStringFromStdArray(data[i]);
} else {
@ -267,9 +273,9 @@ protected:
if constexpr (std::is_arithmetic_v<T>) {
// 对于基本类型,直接转换
if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
data[i] = static_cast<T>(std::stod(value[start_pos + i]));
data[i] = static_cast<T>(XNSim::safe_stod(value[start_pos + i]));
} else {
data[i] = static_cast<T>(std::stoll(value[start_pos + i]));
data[i] = static_cast<T>(XNSim::safe_stoll(value[start_pos + i]));
}
} else if constexpr (is_std_array_v<T>) {
// 对于嵌套数组,递归处理

View File

@ -55,4 +55,41 @@ int safe_stoi(const std::string &str, int defaultValue)
return defaultValue;
}
}
int safe_stol(const std::string &str, int defaultValue)
{
if (str.empty()) {
return defaultValue;
}
try {
return std::stol(str);
} catch (const std::exception &) {
return defaultValue;
}
}
double safe_stod(const std::string &str, double defaultValue)
{
if (str.empty()) {
return defaultValue;
}
try {
return std::stod(str);
} catch (const std::exception &) {
return defaultValue;
}
}
long long safe_stoll(const std::string &str, long long defaultValue)
{
if (str.empty()) {
return defaultValue;
}
try {
return std::stoll(str);
} catch (const std::exception &) {
return defaultValue;
}
}
} // namespace XNSim

View File

@ -280,6 +280,12 @@ extern "C" std::string XNCORE_EXPORT getFileNameWithoutExt(const std::string &pa
extern "C" int XNCORE_EXPORT safe_stoi(const std::string &str, int defaultValue = 0);
extern "C" int XNCORE_EXPORT safe_stol(const std::string &str, int defaultValue = 0);
extern "C" double XNCORE_EXPORT safe_stod(const std::string &str, double defaultValue = 0);
extern "C" long long XNCORE_EXPORT safe_stoll(const std::string &str, long long defaultValue = 0);
inline std::string getStringFromSqlite3(sqlite3_stmt *stmt, int column)
{
const char *text = reinterpret_cast<const char *>(sqlite3_column_text(stmt, column));

View File

@ -175,7 +175,7 @@ protected:
if (data) {
return std::to_string(data.value());
} else {
return "Unknown";
return "0";
}
} else if constexpr (is_std_array_v<T>) {
if (data) {
@ -201,9 +201,9 @@ protected:
{
if constexpr (std::is_arithmetic_v<T>) {
if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
data = std::stod(value);
data = XNSim::safe_stod(value);
} else {
data = std::stoll(value);
data = XNSim::safe_stoll(value);
}
} else if constexpr (is_std_array_v<T>) {
// 解析输入字符串
@ -238,7 +238,13 @@ protected:
if (i > 0)
ss << ",";
if constexpr (std::is_arithmetic_v<T>) {
ss << data[i];
if constexpr (std::is_same_v<T, char>) {
ss << static_cast<int>(data[i]);
} else if constexpr (std::is_same_v<T, unsigned char>) {
ss << static_cast<unsigned int>(data[i]);
} else {
ss << data[i];
}
} else if constexpr (is_std_array_v<T>) {
ss << getStringFromStdArray(data[i]);
} else {
@ -267,9 +273,9 @@ protected:
if constexpr (std::is_arithmetic_v<T>) {
// 对于基本类型,直接转换
if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
data[i] = static_cast<T>(std::stod(value[start_pos + i]));
data[i] = static_cast<T>(XNSim::safe_stod(value[start_pos + i]));
} else {
data[i] = static_cast<T>(std::stoll(value[start_pos + i]));
data[i] = static_cast<T>(XNSim::safe_stoll(value[start_pos + i]));
}
} else if constexpr (is_std_array_v<T>) {
// 对于嵌套数组,递归处理

View File

@ -48,6 +48,8 @@ add_library(XNMonitorServer SHARED
DataInjectThread.cpp
CSVDataInjectThread.h
CSVDataInjectThread.cpp
DataCollect.h
DataCollect.cpp
)
#

View File

@ -11,7 +11,7 @@ CSVDataInjectThread::~CSVDataInjectThread()
stop();
}
bool CSVDataInjectThread::Initialize(std::vector<InjectDataInfo> injectDataInfos)
bool CSVDataInjectThread::Initialize(std::vector<MonitorDataInfo> injectDataInfos)
{
m_csvFile.open(m_csvFilePath);
if (!m_csvFile.is_open()) {
@ -49,6 +49,7 @@ bool CSVDataInjectThread::Initialize(std::vector<InjectDataInfo> injectDataInfos
if (dataMonitor->isInitialized()) {
m_alreadyStartedMonitors[injectDataInfo.structName] = dataMonitor;
} else {
dataMonitor->Initialize(nullptr, 0, 0);
m_notStartedMonitors[injectDataInfo.structName] = dataMonitor;
}
}
@ -96,9 +97,9 @@ void CSVDataInjectThread::parseHeaderField(const std::string &headerField)
if (injectDataInfo.interfaceNames[i] == csvHeaderField.fieldName) {
csvHeaderField.structName = injectDataInfo.structName;
if (injectDataInfo.arraySizes[i].second > 1) {
csvHeaderField.arraySize1 = injectDataInfo.arraySizes[i].first;
csvHeaderField.arraySize2 = injectDataInfo.arraySizes[i].second;
} else {
csvHeaderField.arraySize1 = 0;
csvHeaderField.arraySize2 = 0;
}
break;
}
@ -161,7 +162,13 @@ void CSVDataInjectThread::updateData()
std::stringstream ss(line);
std::string field;
std::getline(ss, field, ',');
double timeStamp = std::stod(field);
double timeStamp = 0;
try {
timeStamp = std::stod(field);
} catch (const std::exception &e) {
std::cout << "field: " << field << " is not a number" << std::endl;
return;
}
m_nextExecuteTime = static_cast<int64_t>(timeStamp * 1000); // 转换为毫秒
// 为每个结构体初始化数据
@ -197,7 +204,7 @@ void CSVDataInjectThread::updateData()
[m_headerFields[i].arrayIndex1] = field;
} else if (m_headerFields[i].arrayDim == 2) {
tempDataMap[m_headerFields[i].structName][m_headerFields[i].fieldName]
[m_headerFields[i].arrayIndex1 * m_headerFields[i].arraySize1
[m_headerFields[i].arrayIndex1 * m_headerFields[i].arraySize2
+ m_headerFields[i].arrayIndex2] = field;
}
}
@ -248,9 +255,17 @@ void CSVDataInjectThread::threadFunc()
}
// 执行数据注入
for (const auto &[structName, dataMonitor] : m_alreadyStartedMonitors) {
if (dataMonitor && m_data.find(structName) != m_data.end()) {
dataMonitor->setDataByString(m_data[structName]);
for (const auto &monitorDataInfo : m_injectDataInfos) {
DataMonitorBasePtr dataMonitor;
if (m_notStartedMonitors.find(monitorDataInfo.structName)
!= m_notStartedMonitors.end()) {
dataMonitor = m_notStartedMonitors[monitorDataInfo.structName];
} else if (m_alreadyStartedMonitors.find(monitorDataInfo.structName)
!= m_alreadyStartedMonitors.end()) {
dataMonitor = m_alreadyStartedMonitors[monitorDataInfo.structName];
}
if (dataMonitor && m_data.find(monitorDataInfo.structName) != m_data.end()) {
dataMonitor->setDataByString(m_data[monitorDataInfo.structName]);
}
}

View File

@ -1,11 +1,6 @@
#pragma once
#include "DataMonitor.h"
#include <thread>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <fstream>
/**
* @brief 线
@ -24,7 +19,7 @@ public:
*/
~CSVDataInjectThread();
bool Initialize(std::vector<InjectDataInfo> injectDataInfos);
bool Initialize(std::vector<MonitorDataInfo> injectDataInfos);
/**
* @brief 线
@ -55,7 +50,7 @@ private:
private:
std::string m_csvFilePath;
std::ifstream m_csvFile;
std::vector<InjectDataInfo> m_injectDataInfos;
std::vector<MonitorDataInfo> m_injectDataInfos;
std::vector<CSVHeaderField> m_headerFields;
std::thread m_thread; ///< 数据注入线程
std::atomic<bool> m_running; ///< 线程运行标志

View File

@ -0,0 +1,326 @@
#include "DataCollect.h"
#include "DataMonitorFactory.h"
DataCollect::DataCollect()
: m_running(false), m_nextExecuteTime(0), m_collectDuration(0), m_collectFrequency(0)
{
}
DataCollect::~DataCollect()
{
stop();
}
bool DataCollect::Initialize(std::vector<MonitorDataInfo> collectDataInfos, std::string dcsFilePath)
{
m_collectDataInfos = collectDataInfos;
// 打开并读取 dcs 文件
std::ifstream dcsFile(dcsFilePath);
if (!dcsFile.is_open()) {
return false;
}
std::string line;
bool foundCollectList = false;
std::string collectListContent;
// 查找 define collect_list 部分
while (std::getline(dcsFile, line)) {
// 跳过注释行
if (line.empty() || line[0] == '!') {
continue;
}
if (line.find("define collect_list") != std::string::npos) {
foundCollectList = true;
continue;
}
if (foundCollectList) {
// 检查是否到达结束引号
if (line.find("\"") != std::string::npos) {
break;
}
collectListContent += line;
}
}
// 继续读取文件,查找采集时长和频率以及输出文件名
while (std::getline(dcsFile, line)) {
// 跳过注释行
if (line.empty() || line[0] == '!') {
continue;
}
// 查找 "for X at Y" 格式的行
if (line.find("for") != std::string::npos && line.find("at") != std::string::npos) {
std::stringstream ss(line);
std::string token;
ss >> token; // 读取 "for"
ss >> m_collectDuration; // 读取时长
ss >> token; // 读取 "at"
ss >> m_collectFrequency; // 读取频率
}
// 查找输出文件名
if (line.find("put/extend/all result") != std::string::npos) {
std::stringstream ss(line);
std::string token;
ss >> token; // 读取 "put/extend/all"
ss >> token; // 读取 "result"
ss >> m_outputFileName; // 读取文件名
m_outputFileName += ".csv"; // 添加.csv后缀
}
}
// 解析 collect_list 内容
std::stringstream ss(collectListContent);
std::string field;
while (std::getline(ss, field, '-')) {
// 去除字段前后的空白字符
field.erase(0, field.find_first_not_of(" \t\r\n"));
field.erase(field.find_last_not_of(" \t\r\n") + 1);
if (!field.empty()) {
parseHeaderField(field);
}
}
// 关闭 dcs 文件
dcsFile.close();
for (const auto &collectDataInfo : m_collectDataInfos) {
auto dataMonitor = DataMonitorFactory::GetInstance(collectDataInfo.structName);
if (dataMonitor == nullptr) {
return false;
}
if (dataMonitor->isInitialized()) {
m_alreadyStartedMonitors[collectDataInfo.structName] = dataMonitor;
} else {
dataMonitor->Initialize(nullptr, 0, 0);
m_notStartedMonitors[collectDataInfo.structName] = dataMonitor;
}
}
// 获取dcs文件所在目录
std::filesystem::path dcsPath(dcsFilePath);
std::string dcsDir = dcsPath.parent_path().string();
// 将输出文件名设置为dcs文件同目录下的完整路径
m_outputFileName = dcsDir + "/" + m_outputFileName;
if (std::filesystem::exists(m_outputFileName)) {
int suffix = 1;
std::filesystem::path originalPath(m_outputFileName);
std::string stem = originalPath.stem().string();
std::string extension = originalPath.extension().string();
std::string newFileName;
do {
newFileName = stem + "_" + std::to_string(suffix) + extension;
suffix++;
} while (std::filesystem::exists(originalPath.parent_path() / newFileName));
std::filesystem::rename(m_outputFileName,
(originalPath.parent_path() / newFileName).string());
}
m_outputFile.open(m_outputFileName);
if (!m_outputFile.is_open()) {
return false;
}
// 写入表头
m_outputFile << "Time";
for (const auto &headerField : m_headerFields) {
if (headerField.arrayDim == 0) {
m_outputFile << "," << headerField.fieldName;
} else if (headerField.arrayDim == 1) {
m_outputFile << "," << headerField.fieldName << "(" << headerField.arrayIndex1 + 1
<< ")";
} else if (headerField.arrayDim == 2) {
m_outputFile << "," << headerField.fieldName << "(" << headerField.arrayIndex1 + 1
<< "_" << headerField.arrayIndex2 + 1 << ")";
}
}
m_outputFile << std::endl;
return true;
}
void DataCollect::parseHeaderField(const std::string &headerField)
{
CSVHeaderField csvHeaderField;
csvHeaderField.fieldName = headerField.substr(0, headerField.find('('));
csvHeaderField.arrayDim = 0;
csvHeaderField.arrayIndex1 = 0;
csvHeaderField.arrayIndex2 = 0;
if (headerField.find('(') != std::string::npos) {
// 处理一维或二维数组格式为interfaceName(1)或interfaceName(1,2)
size_t firstParenPos = headerField.find('(');
size_t lastParenPos = headerField.find(')');
std::string indexStr =
headerField.substr(firstParenPos + 1, lastParenPos - firstParenPos - 1);
if (indexStr.find('_') != std::string::npos) {
// 二维数组
size_t commaPos = indexStr.find('_');
csvHeaderField.arrayIndex1 = std::stoi(indexStr.substr(0, commaPos)) - 1;
if (csvHeaderField.arrayIndex1 < 0) {
return;
}
csvHeaderField.arrayIndex2 = std::stoi(indexStr.substr(commaPos + 1)) - 1;
if (csvHeaderField.arrayIndex2 < 0) {
return;
}
csvHeaderField.arrayDim = 2;
} else {
// 一维数组
csvHeaderField.arrayIndex1 = std::stoi(indexStr) - 1;
if (csvHeaderField.arrayIndex1 < 0) {
return;
}
csvHeaderField.arrayDim = 1;
}
}
for (const auto &collectDataInfo : m_collectDataInfos) {
for (int i = 0; i < collectDataInfo.interfaceNames.size(); i++) {
if (collectDataInfo.interfaceNames[i] == csvHeaderField.fieldName) {
csvHeaderField.structName = collectDataInfo.structName;
if (collectDataInfo.arraySizes[i].second > 1) {
csvHeaderField.arraySize2 = collectDataInfo.arraySizes[i].second;
} else {
csvHeaderField.arraySize2 = 0;
}
break;
}
}
}
if (csvHeaderField.structName.empty()) {
return;
}
m_headerFields.push_back(csvHeaderField);
}
void DataCollect::start()
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_running) {
m_running = true;
m_thread = std::thread(&DataCollect::threadFunc, this);
}
}
void DataCollect::stop()
{
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_running) {
m_running = false;
m_cv.notify_all();
}
}
if (m_thread.joinable()) {
m_thread.join();
}
// 关闭文件
if (m_outputFile.is_open()) {
m_outputFile.close();
}
// 释放未启动的监控器
for (const auto &[structName, dataMonitor] : m_notStartedMonitors) {
DataMonitorFactory::ReleaseInstance(structName);
}
m_notStartedMonitors.clear();
m_alreadyStartedMonitors.clear();
}
void DataCollect::updateData()
{
for (const auto &headerField : m_headerFields) {
if (m_data.find(headerField.structName) != m_data.end()) {
if (m_data[headerField.structName].find(headerField.fieldName)
!= m_data[headerField.structName].end()) {
if (headerField.arrayDim == 0) {
m_outputFile << "," << m_data[headerField.structName][headerField.fieldName];
} else {
std::string value = m_data[headerField.structName][headerField.fieldName];
std::stringstream ss(value);
std::string item;
std::vector<std::string> values;
while (std::getline(ss, item, ',')) {
values.push_back(item);
}
if (headerField.arrayDim == 1) {
m_outputFile << "," << values[headerField.arrayIndex1];
} else if (headerField.arrayDim == 2) {
m_outputFile << ","
<< values[headerField.arrayIndex1 * headerField.arraySize2
+ headerField.arrayIndex2];
}
}
} else {
m_outputFile << "," << "0";
}
} else {
m_outputFile << "," << "0";
}
}
m_outputFile << std::endl;
}
bool DataCollect::isRunning() const
{
return m_running;
}
void DataCollect::threadFunc()
{
auto startTime = std::chrono::steady_clock::now();
auto step = std::chrono::milliseconds(static_cast<int64_t>(1000 / m_collectFrequency));
auto nextTime = step;
auto endTime =
startTime + std::chrono::milliseconds(static_cast<int64_t>(m_collectDuration * 1000));
while (m_running) {
// 等待直到到达执行时间
auto now = std::chrono::steady_clock::now();
auto targetTime = startTime + nextTime;
if (now < targetTime) {
std::this_thread::sleep_until(targetTime);
}
m_data.clear();
// 执行数据注入
for (const auto &monitorDataInfo : m_collectDataInfos) {
DataMonitorBasePtr dataMonitor;
if (m_notStartedMonitors.find(monitorDataInfo.structName)
!= m_notStartedMonitors.end()) {
dataMonitor = m_notStartedMonitors[monitorDataInfo.structName];
} else if (m_alreadyStartedMonitors.find(monitorDataInfo.structName)
!= m_alreadyStartedMonitors.end()) {
dataMonitor = m_alreadyStartedMonitors[monitorDataInfo.structName];
}
if (dataMonitor && m_data.find(monitorDataInfo.structName) == m_data.end()) {
m_data[monitorDataInfo.structName] =
dataMonitor->getStringData(monitorDataInfo.interfaceNames);
}
}
// 写入一行数据
nextTime += step;
if (targetTime > endTime) {
m_running = false;
break;
}
double timeStamp =
std::chrono::duration_cast<std::chrono::milliseconds>(now - startTime).count() / 1000.0;
m_outputFile << std::fixed << std::setprecision(3) << timeStamp;
updateData();
}
}

View File

@ -0,0 +1,52 @@
#pragma once
#include "DataMonitor.h"
class DataCollect
{
public:
DataCollect();
~DataCollect();
public:
bool Initialize(std::vector<MonitorDataInfo> collectDataInfos, std::string dcsFilePath);
void start();
void stop();
/**
* @brief CSV文件读取下一行数据并更新执行时间
* 线
*/
void updateData();
bool isRunning() const;
private:
/**
* @brief 线
*/
void threadFunc();
void parseHeaderField(const std::string &headerField);
private:
std::string m_outputFileName;
std::ofstream m_outputFile;
std::vector<MonitorDataInfo> m_collectDataInfos;
std::vector<CSVHeaderField> m_headerFields;
std::thread m_thread; ///< 数据采集线程
std::atomic<bool> m_running; ///< 线程运行标志
std::mutex m_mutex; ///< 互斥锁
std::condition_variable m_cv; ///< 条件变量
std::unordered_map<std::string, DataMonitorBasePtr>
m_alreadyStartedMonitors; ///< 已经启动的数据监控器
std::unordered_map<std::string, DataMonitorBasePtr>
m_notStartedMonitors; ///< 未启动的数据监控器
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
m_data; ///< 采集到的数据
std::atomic<int64_t> m_nextExecuteTime; ///< 下一次执行的时间点
int m_collectDuration; ///< 采集时长(秒)
int m_collectFrequency; ///< 采集频率Hz
};

View File

@ -1,10 +1,6 @@
#pragma once
#include "DataMonitor.h"
#include <thread>
#include <atomic>
#include <mutex>
#include <condition_variable>
/**
* @brief 线

View File

@ -12,6 +12,10 @@
#include <iostream>
#include <unordered_map>
#include <typeindex>
#include <thread>
#include <atomic>
#include <condition_variable>
#include <fstream>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
@ -210,7 +214,7 @@ struct ModelDefinition {
std::vector<std::shared_ptr<NamespaceDefinition>> namespaceDefinitions;
};
struct InjectDataInfo {
struct MonitorDataInfo {
/**
* @brief
*/
@ -253,7 +257,7 @@ struct CSVHeaderField {
int arrayIndex2;
/**
* @brief
* @brief
*/
int arraySize1;
int arraySize2;
};

View File

@ -9,6 +9,7 @@
#include "DataMonitorFactory.h"
#include "DataInjectThread.h"
#include "CSVDataInjectThread.h"
#include "DataCollect.h"
// 全局变量
static bool g_initialized = false;
@ -21,6 +22,7 @@ SystemControl *systemControl = nullptr;
bool g_systemControlStarted = false;
CSVDataInjectThread *g_csvDataInjectThread = nullptr;
DataCollect *g_dataCollect = nullptr;
// 初始化函数实现
int XN_Initialize(const char *domainId, int domainIdLen, char *errorMsg, int errorMsgSize)
@ -602,12 +604,12 @@ int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv(const char *injectDataI
delete g_csvDataInjectThread;
g_csvDataInjectThread = nullptr;
}
std::vector<InjectDataInfo> injectDataInfos;
std::vector<MonitorDataInfo> injectDataInfos;
std::string injectDataInfoStr(injectDataInfo, injectDataInfoLen);
try {
nlohmann::json injectDataInfoJson = nlohmann::json::parse(injectDataInfoStr);
for (const auto &[structName, interfaceInfo] : injectDataInfoJson.items()) {
InjectDataInfo info;
MonitorDataInfo info;
info.structName = structName;
for (const auto &[interfaceName, size] : interfaceInfo.items()) {
info.interfaceNames.push_back(interfaceName);
@ -722,12 +724,139 @@ int XN_StopCsvDataInject(char *infoMsg, int infoMsgSize)
return 0;
}
int XNMONITORSERVER_EXPORT XN_StartCollectData(const char *structName, const int structNameLen,
const char *interfaceName,
const int interfaceNameLen, const char *csvFilePath,
const int csvFilePathLen, char *infoMsg,
int infoMsgSize)
int XNMONITORSERVER_EXPORT XN_StartCollectData(const char *CollectDataInfo,
const int CollectDataInfoLen,
const char *dcsFilePath, const int dcsFilePathLen,
char *infoMsg, int infoMsgSize)
{
// TODO: 持续采集数据并保存到csv文件接口
return -1;
if (!g_initialized) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "DDSMonitor Not Initialized", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
if (g_dataCollect != nullptr) {
g_dataCollect->stop();
delete g_dataCollect;
g_dataCollect = nullptr;
}
std::vector<MonitorDataInfo> collectDataInfos;
std::string collectDataInfoStr(CollectDataInfo, CollectDataInfoLen);
try {
nlohmann::json collectDataInfoJson = nlohmann::json::parse(collectDataInfoStr);
for (const auto &[structName, interfaceInfo] : collectDataInfoJson.items()) {
MonitorDataInfo info;
info.structName = structName;
for (const auto &[interfaceName, size] : interfaceInfo.items()) {
info.interfaceNames.push_back(interfaceName);
if (size.is_array()) {
info.arraySizes.push_back({size[0].get<int>(), size[1].get<int>()});
} else {
info.arraySizes.push_back({0, 0});
}
}
collectDataInfos.push_back(info);
}
} catch (const nlohmann::json::parse_error &e) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "Invalid JSON format", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
std::string dcsFilePathStr(dcsFilePath, dcsFilePathLen);
if (dcsFilePathStr.empty()) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "DCS 文件路径为空", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
if (!std::filesystem::exists(dcsFilePathStr)) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "DCS 文件不存在", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
try {
g_dataCollect = new DataCollect();
if (!g_dataCollect->Initialize(collectDataInfos, dcsFilePathStr)) {
delete g_dataCollect;
g_dataCollect = nullptr;
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "数据采集线程初始化失败", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
g_dataCollect->start();
} catch (const std::exception &e) {
if (g_dataCollect) {
delete g_dataCollect;
g_dataCollect = nullptr;
}
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, e.what(), infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
return 0;
}
int XNMONITORSERVER_EXPORT XN_GetCollectDataStatus(char *infoMsg, int infoMsgSize)
{
if (!g_initialized) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "DDSMonitor Not Initialized", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
if (g_dataCollect == nullptr) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "数据采集线程已不存在", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
return g_dataCollect->isRunning() ? 1 : 0;
}
int XNMONITORSERVER_EXPORT XN_StopCollectData(char *infoMsg, int infoMsgSize)
{
if (!g_initialized) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "DDSMonitor Not Initialized", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
if (g_dataCollect == nullptr) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "数据采集线程已不存在", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
try {
g_dataCollect->stop();
delete g_dataCollect;
g_dataCollect = nullptr;
} catch (const std::exception &e) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, e.what(), infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
return 0;
}

View File

@ -224,7 +224,7 @@ extern "C"
* @brief csv数据注入状态
* @param infoMsg
* @param infoMsgSize
* @return 0: , -1:
* @return 0: , -1: 1:
*/
int XNMONITORSERVER_EXPORT XN_GetCsvDataInjectStatus(char *infoMsg, int infoMsgSize);
@ -239,25 +239,37 @@ extern "C"
//******************** csv数据采集 *********************
/**
* @brief csv文件接口
* @param structName
* @param structNameLen
* @param interfaceName JSON数组字符串
* @param interfaceNameLen JSON数组字符串长度
* @param csvFilePath csv文件路径
* @param csvFilePathLen csv文件路径长度
* @param frequency
* @brief dcs采集脚本并将数据保存到csv文件接口
* @param CollectDataInfo JSON数组字符串
* @param CollectDataInfoLen JSON数组字符串长度
* @param dcsFilePath dcs文件路径
* @param dcsFilePathLen dcs文件路径长度
* @param infoMsg
* @param infoMsgSize
* @return 0: , -1:
*/
int XNMONITORSERVER_EXPORT XN_StartCollectData(const char *structName, const int structNameLen,
const char *interfaceName,
const int interfaceNameLen,
const char *csvFilePath,
const int csvFilePathLen, char *infoMsg,
int XNMONITORSERVER_EXPORT XN_StartCollectData(const char *CollectDataInfo,
const int CollectDataInfoLen,
const char *dcsFilePath,
const int dcsFilePathLen, char *infoMsg,
int infoMsgSize);
/**
* @brief csv数据采集状态
* @param infoMsg
* @param infoMsgSize
* @return 0: , -1: 1:
*/
int XNMONITORSERVER_EXPORT XN_GetCollectDataStatus(char *infoMsg, int infoMsgSize);
/**
* @brief
* @param infoMsg
* @param infoMsgSize
* @return 0: , -1:
*/
int XNMONITORSERVER_EXPORT XN_StopCollectData(char *infoMsg, int infoMsgSize);
#ifdef __cplusplus
}
#endif

View File

@ -510,6 +510,20 @@ class DataCollection extends HTMLElement {
background: #d9d9d9;
cursor: not-allowed;
}
/* 卸载脚本按钮样式 */
.action-btn.unload {
background: #ff4d4f;
}
.action-btn.unload:hover {
background: #ff7875;
}
/* 停止采集按钮样式 */
.action-btn.stop {
background: #faad14;
}
.action-btn.stop:hover {
background: #ffc53d;
}
.input-row {
display: flex;
flex-direction: column;
@ -654,8 +668,8 @@ class DataCollection extends HTMLElement {
<div class="left-panel">
<div class="panel-section">
<div class="button-row">
<button class="action-btn" id="loadScriptBtn">${this.scriptFile ? '卸载脚本' : '载入脚本'}</button>
<button class="action-btn" id="startCollectBtn" ${!this.scriptFile ? 'disabled' : ''}>开始采集</button>
<button class="action-btn ${this.scriptFile ? 'unload' : ''}" id="loadScriptBtn">${this.scriptFile ? '卸载脚本' : '载入脚本'}</button>
<button class="action-btn ${this.collectStatus === 2 ? 'stop' : ''}" id="startCollectBtn" ${!this.scriptFile ? 'disabled' : ''}>${this.collectStatus === 2 ? '停止采集' : '开始采集'}</button>
</div>
<div class="input-row">
<div class="input-group">
@ -713,6 +727,78 @@ class DataCollection extends HTMLElement {
this.isActive = true;
this.startStatusTimer();
}
async handleStartCollect() {
// 检查监控状态
if (this.monitorStatus !== 1) {
alert('请先启动监控');
return;
}
// 检查是否已加载脚本
if (!this.scriptFile) {
alert('请先加载脚本');
return;
}
const startCollectBtn = this.shadowRoot.getElementById('startCollectBtn');
// 如果正在采集,则停止采集
if (this.collectStatus === 2) {
try {
const response = await fetch('/api/data-collect/stop', {
method: 'POST'
});
const result = await response.json();
if (result.success) {
// 更新采集状态
this.collectStatus = 1; // 改为已加载脚本状态
// 更新按钮状态
startCollectBtn.textContent = '开始采集';
startCollectBtn.disabled = false;
startCollectBtn.classList.remove('stop');
} else {
throw new Error(result.message);
}
} catch (error) {
console.error('停止采集失败:', error);
alert('停止采集失败: ' + error.message);
}
return;
}
// 开始采集
try {
// 调用后端接口启动采集
const response = await fetch('/api/data-collect/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
collectDataInfo: JSON.stringify(this.structData),
dcsFilePath: this.scriptFile.name
})
});
const result = await response.json();
if (result.success) {
// 更新采集状态
this.collectStatus = 2; // 设置为采集中
// 更新按钮状态
startCollectBtn.textContent = '停止采集';
startCollectBtn.classList.add('stop');
} else {
throw new Error(result.message);
}
} catch (error) {
console.error('启动采集失败:', error);
alert('启动采集失败: ' + error.message);
}
}
}
customElements.define('data-collection', DataCollection);

View File

@ -0,0 +1,94 @@
const express = require('express');
const router = express.Router();
const { startCollectData, getCollectDataStatus, stopCollectData } = require('../utils/xnCoreService');
const path = require('path');
// 启动数据采集
router.post('/start', async (req, res) => {
try {
const { collectDataInfo, dcsFilePath } = req.body;
if (!collectDataInfo || !dcsFilePath) {
return res.status(400).json({
success: false,
message: '缺少必要参数'
});
}
// 构建上传文件的完整路径
const fullPath = path.join(__dirname, '..', 'upload', dcsFilePath);
const result = startCollectData(collectDataInfo, fullPath);
if (result === '启动数据采集成功') {
res.json({
success: true,
message: result
});
} else {
res.status(500).json({
success: false,
message: result
});
}
} catch (error) {
console.error('启动数据采集失败:', error);
res.status(500).json({
success: false,
message: '启动数据采集失败: ' + error.message
});
}
});
// 获取数据采集状态
router.get('/status', async (req, res) => {
try {
const status = getCollectDataStatus();
if (typeof status === 'number') {
res.json({
success: true,
status: status, // 0-成功1-正在采集
message: status === 1 ? '正在采集' : '采集完成'
});
} else {
res.status(500).json({
success: false,
message: status
});
}
} catch (error) {
console.error('获取数据采集状态失败:', error);
res.status(500).json({
success: false,
message: '获取数据采集状态失败: ' + error.message
});
}
});
// 停止数据采集
router.post('/stop', async (req, res) => {
try {
const result = stopCollectData();
if (result === '停止数据采集成功') {
res.json({
success: true,
message: result
});
} else {
res.status(500).json({
success: false,
message: result
});
}
} catch (error) {
console.error('停止数据采集失败:', error);
res.status(500).json({
success: false,
message: '停止数据采集失败: ' + error.message
});
}
});
module.exports = router;

View File

@ -28,6 +28,7 @@ const systemMonitorRoutes = require('./routes/SystemMonitor');
const modelMonitorRoutes = require('./routes/ModelMonitor');
const systemControlRoutes = require('./routes/SystemControl');
const dataMonitorRoutes = require('./routes/DataMonitor');
const dataCollectRoutes = require('./routes/DataCollect');
const app = express();
const PORT = process.env.PORT || 3000;
@ -97,6 +98,7 @@ app.use('/api/system-monitor', systemMonitorRoutes);
app.use('/api/model-monitor', modelMonitorRoutes);
app.use('/api/system-control', systemControlRoutes);
app.use('/api/data-monitor', dataMonitorRoutes);
app.use('/api/data-collect', dataCollectRoutes);
// 接口配置页面路由
app.get('/interface-config', (req, res) => {

View File

@ -62,7 +62,10 @@ try {
'XN_StopInjectContinuous': ['int', [StringType, 'int', StringType, 'int']],
'XN_InjectDataInterfaceFromCsv': ['int', [StringType, 'int', StringType, 'int', StringType, 'int']],
'XN_StopCsvDataInject': ['int', [StringType, 'int']],
'XN_GetCsvDataInjectStatus': ['int', [StringType, 'int']]
'XN_GetCsvDataInjectStatus': ['int', [StringType, 'int']],
'XN_StartCollectData': ['int', [StringType, 'int', StringType, 'int', StringType, 'int']],
'XN_GetCollectDataStatus': ['int', [StringType, 'int']],
'XN_StopCollectData': ['int', [StringType, 'int']]
});
} catch (error) {
console.error(`加载 ${monitorLibName} 失败:`, error);
@ -501,6 +504,64 @@ function getCsvDataInjectStatus() {
}
}
// 启动数据采集
function startCollectData(collectDataInfo, dcsFilePath) {
if (!monitorLib) {
return '监控服务器库未加载';
}
try {
const errorMsg = Buffer.alloc(1024);
const result = monitorLib.XN_StartCollectData(
collectDataInfo, collectDataInfo.length,
dcsFilePath, dcsFilePath.length,
errorMsg, errorMsg.length
);
if (result !== 0) {
return `启动数据采集失败: ${errorMsg.toString('utf8').replace(/\0/g, '')}`;
}
return '启动数据采集成功';
} catch (error) {
return `启动数据采集失败: ${error.message}`;
}
}
// 获取数据采集状态
function getCollectDataStatus() {
if (!monitorLib) {
return '监控服务器库未加载';
}
try {
const infoMsg = Buffer.alloc(1024);
const result = monitorLib.XN_GetCollectDataStatus(infoMsg, infoMsg.length);
if (result === -1) {
return `获取状态失败: ${infoMsg.toString('utf8').replace(/\0/g, '')}`;
}
return result; // 返回状态0-成功1-正在采集
} catch (error) {
return `获取状态失败: ${error.message}`;
}
}
// 停止数据采集
function stopCollectData() {
if (!monitorLib) {
return '监控服务器库未加载';
}
try {
const errorMsg = Buffer.alloc(1024);
const result = monitorLib.XN_StopCollectData(errorMsg, errorMsg.length);
if (result !== 0) {
return `停止数据采集失败: ${errorMsg.toString('utf8').replace(/\0/g, '')}`;
}
return '停止数据采集成功';
} catch (error) {
return `停止数据采集失败: ${error.message}`;
}
}
module.exports = {
loginLib,
monitorLib,
@ -527,5 +588,8 @@ module.exports = {
stopInjectContinuous,
injectDataInterfaceFromCsv,
stopCsvDataInject,
getCsvDataInjectStatus
getCsvDataInjectStatus,
startCollectData,
getCollectDataStatus,
stopCollectData
};