Files
front_linux/LFtid1056/cloudfront/code/cfg_parser.cpp
2025-10-11 10:19:04 +08:00

5118 lines
225 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/////////////////////////////////////////////////////////////////////////////////////////////////
#include <ctime>
#include <unistd.h>
#include <string>
#include <iostream>
#include <list>
#include <sstream> //流解析
#include <set> //写去重的设备类型
#include <fstream> //打开文件
#include <algorithm>
#include <cctype>
#include <dirent.h>
#include <vector>
#include <array>
#include <map>
#include <mutex>
#include <thread>
#include <atomic>
#include <cstdio>
#include <sys/stat.h>
#include <fnmatch.h>
#include <memory>
/////////////////////////////////////////////////////////////////////////////////////////////////
#include "nlohmann/json.hpp"
#include "curl/curl.h"
#include "log4.h" //关键上送日志
#include "interface.h" //台账结构
#include "tinyxml2.h"
#include "rocketmq.h"
/////////////////////////////////////////////////////////////////////////////////////////////////
using namespace std;
/////////////////////////////////////////////////////////////////////////////////////////////////
//进程标识
extern std::string subdir;
extern int g_front_seg_index;
extern int g_front_seg_num;
extern unsigned int g_node_id; //前置程序类型(100-600)
//初始化完成标识
extern int INITFLAG;
//线程阻塞计数
extern uint32_t g_ontime_blocked_times;
//台账锁
extern std::mutex ledgermtx;
//队列
extern std::mutex queue_data_list_mutex; //queue发送数据锁
extern std::list<queue_data_t> queue_data_list; //queue发送数据链表
extern int three_secs_enabled;
extern std::map<std::string, Xmldata*> xmlinfo_list;//保存所有型号对应的icd映射文件解析数据
extern XmlConfig xmlcfg;//星形接线xml节点解析的数据-默认映射文件解析数据
extern std::list<CTopic *> topicList; //队列发送主题链表
extern XmlConfig xmlcfg2;//角型接线xml节点解析的数据-默认映射文件解析数据
extern std::list<CTopic*> topicList2; //角型接线发送主题链表
extern std::map<std::string, Xmldata*> xmlinfo_list2;//保存所有型号角形接线对应的icd映射文件解析数据
////////////////////////////////////////////////////////////////////////////////////////////////////
static std::mutex g_filemenu_cache_mtx;
std::map<std::string, std::vector<tag_dir_info>> g_filemenu_cache;
//补招
std::list<JournalRecall> g_StatisticLackList; //日志补招结构类链表
std::mutex g_StatisticLackList_list_mutex; //补招队列数据锁
std::string DEFAULT_CONFIG_FN = "Default_Config.xml"; //默认映射文件
std::string LEDGER_UPDATE_FN = "LedgerUpdate.log"; //台账更新本地记录日志
//一个终端最大检测点数
const int MAX_CPUNO = 10;
//角型接线标志,0不存在角形接线1存在角形接线
int isdelta_flag = 0;
//多前置flag:1为开启,0为关闭
int MULTIPLE_NODE_FLAG = 1;
//终端台账数量配置
int IED_COUNT = 300; //默认300
//前置标志
std::string FRONT_INST = "";
std::string FRONT_IP = "";
//终端和监测点的状态筛选
std::string TERMINAL_STATUS = "";
std::string MONITOR_STATUS = "";
std::string ICD_FLAG = "";
//web接口
std::string WEB_DEVICE = "";
std::string WEB_ICD = "";
std::string WEB_EVENT = "";
std::string WEB_FILEUPLOAD = "";
std::string WEB_FILEDOWNLOAD = "";
////////////////////////////////////////////////////////////////////////////mq配置
//备用
std::string BROKER_LIST = "";
//通用主题
std::string TOPIC_STAT = "";
std::string TOPIC_PST = "";
std::string TOPIC_PLT = "";
std::string TOPIC_EVENT = "";
std::string TOPIC_ALARM = "";
std::string TOPIC_SNG = "";
std::string TOPIC_RTDATA = "";
//通用tagkey
std::string G_ROCKETMQ_TAG = "";//tag
std::string G_ROCKETMQ_KEY = "";//key
//实时数据tagkey
std::string G_RT_TAG = "";//tag
std::string G_RT_KEY = "";//key
//生产者
std::string G_ROCKETMQ_PRODUCER = ""; //rocketmq producer
std::string G_MQPRODUCER_IPPORT = ""; //rocketmq ip+port
std::string G_MQPRODUCER_ACCESSKEY = ""; //rocketmq 认证
std::string G_MQPRODUCER_SECRETKEY = ""; //rocketmq 秘钥
//日志
std::string G_LOG_TOPIC = "";//topie
std::string G_LOG_TAG = "";//tag
std::string G_LOG_KEY = "";//key
//终端连接
std::string G_CONNECT_TOPIC = "";//consumer topie
std::string G_CONNECT_TAG = "";//consumer tag
std::string G_CONNECT_KEY = "";//consumer key
//心跳
std::string Heart_Beat_Topic = "";
std::string Heart_Beat_Tag = "";
std::string Heart_Beat_Key = "";
//消息响应
std::string Topic_Reply_Topic = "";
std::string Topic_Reply_Tag = "";
std::string Topic_Reply_Key = "";
//消费者
std::string G_ROCKETMQ_CONSUMER = "";//rocketmq consumer
std::string G_MQCONSUMER_IPPORT = "";//consumer ip+port
std::string G_MQCONSUMER_ACCESSKEY = "";
std::string G_MQCONSUMER_SECRETKEY = "";
std::string G_MQCONSUMER_CHANNEL = "";
//实时数据请求
std::string G_MQCONSUMER_TOPIC_RT = "";//consumer topie
std::string G_MQCONSUMER_TAG_RT = "";//consumer tag
std::string G_MQCONSUMER_KEY_RT = "";//consumer key
//台账更新请求
std::string G_MQCONSUMER_TOPIC_UD = "";//consumer topie
std::string G_MQCONSUMER_TAG_UD = "";//consumer tag
std::string G_MQCONSUMER_KEY_UD = "";//consumer key
//补招数据请求
std::string G_MQCONSUMER_TOPIC_RC = "";//consumer topie
std::string G_MQCONSUMER_TAG_RC = "";//consumer tag
std::string G_MQCONSUMER_KEY_RC = "";//consumer key
//进程控制请求
std::string G_MQCONSUMER_TOPIC_SET = "";//consumer topie
std::string G_MQCONSUMER_TAG_SET = "";//consumer tag
std::string G_MQCONSUMER_KEY_SET = "";//consumer key
//日志数据请求
std::string G_MQCONSUMER_TOPIC_LOG = "";//consumer topie
std::string G_MQCONSUMER_TAG_LOG = "";//consumer tag
std::string G_MQCONSUMER_KEY_LOG = "";//consumer key
std::string G_MQCONSUMER_TOPIC_CLOUD = "";//consumer topie
std::string G_MQCONSUMER_TAG_CLOUD = "";//consumer tag
std::string G_MQCONSUMER_KEY_CLOUD = "";//consumer key
//测试用的主题
std::string G_ROCKETMQ_TOPIC_TEST = "";//topie
std::string G_ROCKETMQ_TAG_TEST = "";//tag
std::string G_ROCKETMQ_KEY_TEST = "";//key
//测试相关配置
int G_TEST_FLAG = 0;
int G_TEST_NUM = 0;
int G_TEST_TYPE = 0;
int TEST_PORT = 11000; //用于当前进程登录测试shell的端口
std::string G_TEST_LIST = ""; //测试用的发送实际数据的终端列表
std::vector<std::string> TESTARRAY; //解析的列表数组
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////其他文件定义的函数引用声明
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////当前文件函数声明
void create_ledger_log(trigger_update_xml_t* ledger_update_xml);
void print_trigger_update_xml(const trigger_update_xml_t& trigger_update);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////辅助函数
//去除字符串前后空格
static void trim(std::string& s) {
auto not_space = [](int ch) { return !std::isspace(ch); };
s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space));
s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end());
}
//判断空格
bool is_blank(const std::string& str)
{
for (std::string::const_iterator it = str.begin(); it != str.end(); ++it)
{
if (!std::isspace(*it)) {
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////配置文件读取
//将 G_TEST_LIST 按逗号分割,填充 TESTARRAY
static void parseTestList() {
TESTARRAY.clear();
std::istringstream iss(G_TEST_LIST);
std::string token;
while (std::getline(iss, token, ',')) {
trim(token);
if (!token.empty()) {
TESTARRAY.push_back(token);
}
}
}
//简易映射方式解析配置文件文件
void loadConfig(const std::string& filename) {
// 1. 构造“节.键 → 变量地址”映射表
std::unordered_map<std::string, std::string*> strMap;
std::unordered_map<std::string, int*> intMap;
// [Flag]
strMap["Flag.FrontInst"] = &FRONT_INST;
strMap["Flag.FrontIP"] = &FRONT_IP;
// [Ledger]
intMap["Ledger.IedCount"] = &IED_COUNT;
strMap["Ledger.TerminalStatus"]= &TERMINAL_STATUS;
strMap["Ledger.MonitorStatus"] = &MONITOR_STATUS;
strMap["Ledger.IcdFlag"] = &ICD_FLAG;
// [Http]
strMap["Http.WebDevice"] = &WEB_DEVICE;
strMap["Http.WebIcd"] = &WEB_ICD;
strMap["Http.WebEvent"] = &WEB_EVENT;
strMap["Http.WebFileupload"] = &WEB_FILEUPLOAD;
strMap["Http.WebFiledownload"]= &WEB_FILEDOWNLOAD;
// [Queue]
strMap["Queue.BrokerList"] = &BROKER_LIST;
strMap["Queue.RTDataTopic"] = &TOPIC_RTDATA;
strMap["Queue.HisTopic"] = &TOPIC_STAT;
strMap["Queue.PSTTopic"] = &TOPIC_PST;
strMap["Queue.PLTTopic"] = &TOPIC_PLT;
strMap["Queue.EventTopic"] = &TOPIC_EVENT;
strMap["Queue.AlmTopic"] = &TOPIC_ALARM;
strMap["Queue.SngTopic"] = &TOPIC_SNG;
strMap["Queue.QUEUE_TAG"] = &G_ROCKETMQ_TAG;
strMap["Queue.QUEUE_KEY"] = &G_ROCKETMQ_KEY;
//添加rt的tagkey
strMap["Queue.RT_TAG"] = &G_RT_TAG;
strMap["Queue.RT_KEY"] = &G_RT_KEY;
// [RocketMq] —— 生产者
strMap["RocketMq.producer"] = &G_ROCKETMQ_PRODUCER;
strMap["RocketMq.Ipport"] = &G_MQPRODUCER_IPPORT;
strMap["RocketMq.AccessKey"] = &G_MQPRODUCER_ACCESSKEY;
strMap["RocketMq.SecretKey"] = &G_MQPRODUCER_SECRETKEY;
strMap["RocketMq.LOGTopic"] = &G_LOG_TOPIC;
strMap["RocketMq.LOGTag"] = &G_LOG_TAG;
strMap["RocketMq.LOGKey"] = &G_LOG_KEY;
strMap["RocketMq.CONNECTTopic"] = &G_CONNECT_TOPIC;
strMap["RocketMq.CONNECTTag"] = &G_CONNECT_TAG;
strMap["RocketMq.CONNECTKey"] = &G_CONNECT_KEY;
strMap["RocketMq.Heart_Beat_Topic"] = &Heart_Beat_Topic;
strMap["RocketMq.Heart_Beat_Tag"] = &Heart_Beat_Tag;
strMap["RocketMq.Heart_Beat_Key"] = &Heart_Beat_Key;
strMap["RocketMq.Topic_Reply_Topic"] = &Topic_Reply_Topic;
strMap["RocketMq.Topic_Reply_Tag"] = &Topic_Reply_Tag;
strMap["RocketMq.Topic_Reply_Key"] = &Topic_Reply_Key;
// [RocketMq] —— 消费者
strMap["RocketMq.consumer"] = &G_ROCKETMQ_CONSUMER;
strMap["RocketMq.ConsumerIpport"] = &G_MQCONSUMER_IPPORT;
strMap["RocketMq.ConsumerAccessKey"] = &G_MQCONSUMER_ACCESSKEY;
strMap["RocketMq.ConsumerSecretKey"] = &G_MQCONSUMER_SECRETKEY;
strMap["RocketMq.ConsumerChannel"] = &G_MQCONSUMER_CHANNEL;
strMap["RocketMq.ConsumerTopicRT"] = &G_MQCONSUMER_TOPIC_RT;
strMap["RocketMq.ConsumerTagRT"] = &G_MQCONSUMER_TAG_RT;
strMap["RocketMq.ConsumerKeyRT"] = &G_MQCONSUMER_KEY_RT;
strMap["RocketMq.ConsumerTopicUD"] = &G_MQCONSUMER_TOPIC_UD;
strMap["RocketMq.ConsumerTagUD"] = &G_MQCONSUMER_TAG_UD;
strMap["RocketMq.ConsumerKeyUD"] = &G_MQCONSUMER_KEY_UD;
strMap["RocketMq.ConsumerTopicRC"] = &G_MQCONSUMER_TOPIC_RC;
strMap["RocketMq.ConsumerTagRC"] = &G_MQCONSUMER_TAG_RC;
strMap["RocketMq.ConsumerKeyRC"] = &G_MQCONSUMER_KEY_RC;
strMap["RocketMq.ConsumerTopicSET"] = &G_MQCONSUMER_TOPIC_SET;
strMap["RocketMq.ConsumerTagSET"] = &G_MQCONSUMER_TAG_SET;
strMap["RocketMq.ConsumerKeySET"] = &G_MQCONSUMER_KEY_SET;
strMap["RocketMq.ConsumerTopicLOG"] = &G_MQCONSUMER_TOPIC_LOG;
strMap["RocketMq.ConsumerTagLOG"] = &G_MQCONSUMER_TAG_LOG;
strMap["RocketMq.ConsumerKeyLOG"] = &G_MQCONSUMER_KEY_LOG;
strMap["RocketMq.ConsumerTopicCLOUD"] = &G_MQCONSUMER_TOPIC_CLOUD;
strMap["RocketMq.ConsumerTagCLOUD"] = &G_MQCONSUMER_TAG_CLOUD;
strMap["RocketMq.ConsumerKeyCLOUD"] = &G_MQCONSUMER_KEY_CLOUD;
strMap["RocketMq.Topic_Test"] = &G_ROCKETMQ_TOPIC_TEST;
strMap["RocketMq.Tag_Test"] = &G_ROCKETMQ_TAG_TEST;
strMap["RocketMq.Key_Test"] = &G_ROCKETMQ_KEY_TEST;
intMap["RocketMq.Testflag"] = &G_TEST_FLAG;
intMap["RocketMq.Testnum"] = &G_TEST_NUM;
intMap["RocketMq.Testtype"] = &G_TEST_TYPE;
intMap["RocketMq.TestPort"] = &TEST_PORT;
strMap["RocketMq.TestList"] = &G_TEST_LIST;
// 2. 打开并逐行解析 INI 文件
std::ifstream fin(filename);
if (!fin.is_open()) {
std::cerr << "无法打开配置文件: " << filename << "\n";
return;
}
std::string line;
std::string currentSection;
while (std::getline(fin, line)) {
trim(line);
if (line.empty() || line[0] == ';' || line[0] == '#') {
continue; // 跳过空白或注释
}
if (line.front() == '[' && line.back() == ']') {
currentSection = line.substr(1, line.size() - 2);
trim(currentSection);
continue;
}
auto pos = line.find('=');
if (pos == std::string::npos) {
continue;
}
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
trim(key);
trim(value);
// 去掉值两端双引号
if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
value = value.substr(1, value.size() - 2);
}
// 拼出 "节.键"
std::string fullKey = currentSection + "." + key;
// 如果在字符串映射表里,就写入对应的全局 std::string
auto sit = strMap.find(fullKey);
if (sit != strMap.end()) {
*(sit->second) = value;
continue;
}
// 如果在整型映射表里,就 stoi 后写入对应的全局 int
auto iit = intMap.find(fullKey);
if (iit != intMap.end()) {
try {
*(iit->second) = std::stoi(value);
} catch (...) {
*(iit->second) = 0;
}
}
}
fin.close();
// 3. 将 G_TEST_LIST 拆分到 TESTARRAY
parseTestList();
}
//打印所有全局变量,名称对齐
void printConfig() {
const int nameWidth = 30; // 变量名区域宽度
std::cout << "------- Loaded Configuration -------\n";
// 辅助 lambda 方便打印
auto printStr = [&](const std::string& name, const std::string& val) {
std::cout << std::left << std::setw(nameWidth) << name
<< " = " << val << "\n";
};
auto printInt = [&](const std::string& name, int val) {
std::cout << std::left << std::setw(nameWidth) << name
<< " = " << val << "\n";
};
std::cout << "\n// 前置区分 —— 通用\n";
printInt("IED_COUNT", IED_COUNT);
printStr("FRONT_INST", FRONT_INST);
printStr("FRONT_IP", FRONT_IP);
std::cout << "\n// 消息队列 —— 通用\n";
printStr("BROKER_LIST", BROKER_LIST);
printStr("TOPIC_STAT", TOPIC_STAT);
printStr("TOPIC_PST", TOPIC_PST);
printStr("TOPIC_PLT", TOPIC_PLT);
printStr("TOPIC_EVENT", TOPIC_EVENT);
printStr("TOPIC_ALARM", TOPIC_ALARM);
printStr("TOPIC_SNG", TOPIC_SNG);
printStr("TOPIC_RTDATA", TOPIC_RTDATA);
std::cout << "\n// MQ —— 生产者\n";
printStr("G_ROCKETMQ_PRODUCER", G_ROCKETMQ_PRODUCER);
printStr("G_MQPRODUCER_IPPORT", G_MQPRODUCER_IPPORT);
printStr("G_MQPRODUCER_ACCESSKEY", G_MQPRODUCER_ACCESSKEY);
printStr("G_MQPRODUCER_SECRETKEY", G_MQPRODUCER_SECRETKEY);
printStr("G_LOG_TOPIC", G_LOG_TOPIC);
printStr("G_LOG_TAG", G_LOG_TAG);
printStr("G_LOG_KEY", G_LOG_KEY);
printStr("G_CONNECT_TOPIC", G_CONNECT_TOPIC);
printStr("G_CONNECT_TAG", G_CONNECT_TAG);
printStr("G_CONNECT_KEY", G_CONNECT_KEY);
std::cout << "\n// MQ —— 心跳 & 响应\n";
printStr("Heart_Beat_Topic", Heart_Beat_Topic);
printStr("Heart_Beat_Tag", Heart_Beat_Tag);
printStr("Heart_Beat_Key", Heart_Beat_Key);
printStr("Topic_Reply_Topic", Topic_Reply_Topic);
printStr("Topic_Reply_Tag", Topic_Reply_Tag);
printStr("Topic_Reply_Key", Topic_Reply_Key);
std::cout << "\n// MQ —— 消费者\n";
printStr("G_ROCKETMQ_CONSUMER", G_ROCKETMQ_CONSUMER);
printStr("G_MQCONSUMER_IPPORT", G_MQCONSUMER_IPPORT);
printStr("G_MQCONSUMER_ACCESSKEY", G_MQCONSUMER_ACCESSKEY);
printStr("G_MQCONSUMER_SECRETKEY", G_MQCONSUMER_SECRETKEY);
printStr("G_MQCONSUMER_CHANNEL", G_MQCONSUMER_CHANNEL);
std::cout << "\n// MQ —— 主题细分类\n";
printStr("G_MQCONSUMER_TOPIC_RT", G_MQCONSUMER_TOPIC_RT);
printStr("G_MQCONSUMER_TAG_RT", G_MQCONSUMER_TAG_RT);
printStr("G_MQCONSUMER_KEY_RT", G_MQCONSUMER_KEY_RT);
printStr("G_MQCONSUMER_TOPIC_UD", G_MQCONSUMER_TOPIC_UD);
printStr("G_MQCONSUMER_TAG_UD", G_MQCONSUMER_TAG_UD);
printStr("G_MQCONSUMER_KEY_UD", G_MQCONSUMER_KEY_UD);
printStr("G_MQCONSUMER_TOPIC_RC", G_MQCONSUMER_TOPIC_RC);
printStr("G_MQCONSUMER_TAG_RC", G_MQCONSUMER_TAG_RC);
printStr("G_MQCONSUMER_KEY_RC", G_MQCONSUMER_KEY_RC);
printStr("G_MQCONSUMER_TOPIC_SET", G_MQCONSUMER_TOPIC_SET);
printStr("G_MQCONSUMER_TAG_SET", G_MQCONSUMER_TAG_SET);
printStr("G_MQCONSUMER_KEY_SET", G_MQCONSUMER_KEY_SET);
printStr("G_MQCONSUMER_TOPIC_LOG", G_MQCONSUMER_TOPIC_LOG);
printStr("G_MQCONSUMER_TAG_LOG", G_MQCONSUMER_TAG_LOG);
printStr("G_MQCONSUMER_KEY_LOG", G_MQCONSUMER_KEY_LOG);
std::cout << "\n// MQ —— 测试用主题 & 参数\n";
printStr("G_ROCKETMQ_TOPIC_TEST", G_ROCKETMQ_TOPIC_TEST);
printStr("G_ROCKETMQ_TAG_TEST", G_ROCKETMQ_TAG_TEST);
printStr("G_ROCKETMQ_KEY_TEST", G_ROCKETMQ_KEY_TEST);
printInt("G_TEST_FLAG", G_TEST_FLAG);
printInt("G_TEST_NUM", G_TEST_NUM);
printInt("G_TEST_TYPE", G_TEST_TYPE);
printInt("TEST_PORT", TEST_PORT);
printStr("G_TEST_LIST", G_TEST_LIST);
// 打印解析后的 TESTARRAY
std::cout << std::left << std::setw(nameWidth) << "TESTARRAY" << " = [";
for (size_t i = 0; i < TESTARRAY.size(); ++i) {
std::cout << TESTARRAY[i];
if (i + 1 < TESTARRAY.size()) {
std::cout << ", ";
}
}
std::cout << "]\n";
std::cout << "\n// 终端 & 监测点状态筛选\n";
printStr("TERMINAL_STATUS", TERMINAL_STATUS);
printStr("MONITOR_STATUS", MONITOR_STATUS);
printStr("ICD_FLAG", ICD_FLAG);
std::cout << "\n// Web 接口\n";
printStr("WEB_DEVICE", WEB_DEVICE);
printStr("WEB_ICD", WEB_ICD);
printStr("WEB_EVENT", WEB_EVENT);
printStr("WEB_FILEUPLOAD", WEB_FILEUPLOAD);
printStr("WEB_FILEDOWNLOAD", WEB_FILEDOWNLOAD);
std::cout << "-------------------------------------\n";
}
//初始化配置
void init_config() {
loadConfig(FRONT_PATH + "/config/front.cfg");
printConfig();
//多前置处理
if (g_front_seg_index > 0 && g_front_seg_num > 0) {
MULTIPLE_NODE_FLAG = 1;
std::cout << "this is multiple process of index:" << g_front_seg_index << std::endl;
if(g_front_seg_index > g_front_seg_num){
DIY_ERRORLOG("process","【ERROR】前置当前进程的进程号为:%d,前置的多进程最大进程号为:%d,当前进程的进程号应该为1到最大进程号范围内的整数,退出该进程",g_front_seg_index,g_front_seg_num);
exit(-1039);
}
}
else if(g_front_seg_num == 0 && g_front_seg_index == 0){
MULTIPLE_NODE_FLAG = 0;
std::cout << "this is single process" << std::endl;
}
else{
DIY_ERRORLOG("process","【ERROR】前置当前进程的进程号为:%d,前置的多进程最大进程号为:%d,应该为大于0的整数,退出该进程",g_front_seg_index,g_front_seg_num);
exit(-1039);
}
//测试进程端口
/*if (g_node_id == STAT_DATA_BASE_NODE_ID)//统计采集
TEST_PORT = TEST_PORT + STAT_DATA_BASE_NODE_ID + g_front_seg_index;
else if (g_node_id == RECALL_HIS_DATA_BASE_NODE_ID) {//补召
TEST_PORT = TEST_PORT + RECALL_HIS_DATA_BASE_NODE_ID + g_front_seg_index;
}
else if (g_node_id == THREE_SECS_DATA_BASE_NODE_ID) {//3秒采集
TEST_PORT = TEST_PORT + THREE_SECS_DATA_BASE_NODE_ID + g_front_seg_index;
}
else if (g_node_id == SOE_COMTRADE_BASE_NODE_ID) {//暂态录波
TEST_PORT = TEST_PORT + SOE_COMTRADE_BASE_NODE_ID + g_front_seg_index;
}*/
TEST_PORT = TEST_PORT + g_front_seg_index;
}
////////////////////////////////////////////////////////////////////////////////////////////获取当前时间
// 用于获取当前时间,单位毫秒
double sGetMsTime() {
auto now = std::chrono::system_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()
).count();
return static_cast<double>(ms);
}
//秒时间转为标准时间字符串
// ★新增:将 epoch 秒级时间转成 "YYYY-MM-DD HH:MM:SS"
static std::string epoch_to_datetime_str(long long epoch_sec) {
char buf[20];
std::tm tm{};
time_t t = static_cast<time_t>(epoch_sec);
localtime_r(&t, &tm);
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
return std::string(buf);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////实时触发部分
//获取实时触发文件
std::string get_3s_trig_fn() {
const std::string dirPath = FRONT_PATH + "/etc/trigger3s";
DIR* dp = opendir(dirPath.c_str());
if (!dp) return "";
struct dirent* entry;
std::vector<std::pair<std::string, time_t>> xmlFiles;
while ((entry = readdir(dp)) != nullptr) {
if (strstr(entry->d_name, ".xml")) {
std::string fullPath = dirPath + "/" + entry->d_name;
struct stat st;
if (stat(fullPath.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
xmlFiles.emplace_back(entry->d_name, st.st_mtime);
}
}
}
closedir(dp);
if (xmlFiles.empty()) return "";
std::sort(xmlFiles.begin(), xmlFiles.end(),
[](const std::pair<std::string, time_t>& a, const std::pair<std::string, time_t>& b) {
return a.second > b.second; // 最近时间在前
});
return xmlFiles.front().first;
}
// 打印 trigger_t 结构体的函数
void print_trigger(const trigger_t& trigger) {
std::cout << " dev_idx: " << trigger.dev_idx
<< ", line_id: " << trigger.line_id
<< ", real_data: " << trigger.real_data
<< ", soe_data: " << trigger.soe_data
<< ", limit: " << trigger.limit
<< ", count: " << trigger.count
<< std::endl;
}
// 打印 trigger_3s_xml_t 结构体的函数
void print_trigger_3s_xml(const trigger_3s_xml_t& trigger_3s_xml) {
std::cout << "Work Trigger Count: " << trigger_3s_xml.work_trigger_num << std::endl;
for (int i = 0; i < trigger_3s_xml.work_trigger_num; ++i) {
std::cout << " Work Trigger [" << (i + 1) << "]:" << std::endl;
print_trigger(trigger_3s_xml.work_triggers[i]);
}
std::cout << "New Trigger Count: " << trigger_3s_xml.new_trigger_num << std::endl;
for (int i = 0; i < trigger_3s_xml.new_trigger_num; ++i) {
std::cout << " New Trigger [" << (i + 1) << "]:" << std::endl;
print_trigger(trigger_3s_xml.new_triggers[i]);
}
std::cout << "Delete Trigger Count: " << trigger_3s_xml.delete_trigger_num << std::endl;
for (int i = 0; i < trigger_3s_xml.delete_trigger_num; ++i) {
std::cout << " Delete Trigger [" << (i + 1) << "]:" << std::endl;
print_trigger(trigger_3s_xml.delete_triggers[i]);
}
std::cout << "Modify Trigger Count: " << trigger_3s_xml.modify_trigger_num << std::endl;
for (int i = 0; i < trigger_3s_xml.modify_trigger_num; ++i) {
std::cout << " Modify Trigger [" << (i + 1) << "]:" << std::endl;
print_trigger(trigger_3s_xml.modify_triggers[i]);
}
}
//处理触发标志
int getValueFromElemAttrStr(std::string str)
{
if (str == "true")
return 1;
else if (str == "false")
return 0;
else
return -1;
}
//将文件内容读取到结构中
void parse_3s_trigger(trigger_3s_xml_t* trigger_3s_xml, const std::string& parentTag, tinyxml2::XMLElement* trigger_e)
{
if (!trigger_3s_xml || !trigger_e) return;
trigger_t trigger;
const char* attr = nullptr;
// DevSeries
attr = trigger_e->Attribute("DevSeries");
trigger.dev_idx = attr ? std::atoi(attr) : 0;
// Line
attr = trigger_e->Attribute("Line");
trigger.line_id = attr ? std::atoi(attr) : 0;
// RealData
attr = trigger_e->Attribute("RealData");
std::string realDataStr = attr ? attr : "";
std::transform(realDataStr.begin(), realDataStr.end(), realDataStr.begin(), ::tolower);
trigger.real_data = getValueFromElemAttrStr(realDataStr);
// SOEData
attr = trigger_e->Attribute("SOEData");
std::string soeDataStr = attr ? attr : "";
std::transform(soeDataStr.begin(), soeDataStr.end(), soeDataStr.begin(), ::tolower);
trigger.soe_data = getValueFromElemAttrStr(soeDataStr);
// Limit
attr = trigger_e->Attribute("Limit");
trigger.limit = attr ? std::atoi(attr) : 0;
// Count
attr = trigger_e->Attribute("Count");
trigger.count = attr ? std::atoi(attr) : 0;
// 分类插入
if (parentTag == "Work") {
trigger_3s_xml->work_triggers[trigger_3s_xml->work_trigger_num++] = trigger;
} else if (parentTag == "New") {
trigger_3s_xml->new_triggers[trigger_3s_xml->new_trigger_num++] = trigger;
} else if (parentTag == "Delete") {
trigger_3s_xml->delete_triggers[trigger_3s_xml->delete_trigger_num++] = trigger;
} else if (parentTag == "Modify") {
trigger_3s_xml->modify_triggers[trigger_3s_xml->modify_trigger_num++] = trigger;
}
// 调试用
print_trigger_3s_xml(*trigger_3s_xml);
}
//实时触发文件处理
int load_3s_data_from_xml(trigger_3s_xml_t* trigger_3s_xml, const std::string& xml_fn) {
if (!trigger_3s_xml) return 1;
tinyxml2::XMLDocument doc;
tinyxml2::XMLError result = doc.LoadFile(xml_fn.c_str());
if (result == tinyxml2::XML_ERROR_FILE_NOT_FOUND) {
return 1;
} else if (result != tinyxml2::XML_SUCCESS) {
return 1;
}
tinyxml2::XMLElement* root = doc.RootElement();
if (!root) return 1;
for (tinyxml2::XMLElement* groupElem = root->FirstChildElement(); groupElem != nullptr; groupElem = groupElem->NextSiblingElement()) {
std::string strTag = groupElem->Name();
if (strTag == "Work" || strTag == "New" || strTag == "Delete" || strTag == "Modify") {
for (tinyxml2::XMLElement* triggerElem = groupElem->FirstChildElement("Trigger"); triggerElem != nullptr; triggerElem = triggerElem->NextSiblingElement("Trigger")) {
parse_3s_trigger(trigger_3s_xml, strTag, triggerElem);
}
}
}
return 0;
}
//将内容写入协议
void process_3s_config(trigger_3s_xml_t *trigger_3s_xml)
{
//根据协议补充内容
//根据协议补充内容
}
//实时触发处理
int parse_3s_xml(trigger_3s_xml_t* trigger_3s_xml) {
std::cout << "begin 3s xml..." << std::endl;
std::memset(trigger_3s_xml, 0, sizeof(trigger_3s_xml_t));
std::string THREE_SECS_WEBSERVICE_DIR = FRONT_PATH + "/etc/trigger3s/"; //实时数据读取目录
std::string BAK_WEBSERVICE_3S_TRIG_COMMAND_XML_FN = THREE_SECS_WEBSERVICE_DIR + "bak_3s_trig_command.txt"; //实时触发文件备份文件名
std::string the_webservice_xml_fn = get_3s_trig_fn(); // 获取目录下最新 XML 文件名
if (the_webservice_xml_fn.length() > 4) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 等待 0.1 秒
std::string full_path = std::string(THREE_SECS_WEBSERVICE_DIR) + the_webservice_xml_fn;
if (load_3s_data_from_xml(trigger_3s_xml, full_path) != 0) {
std::cerr << "Failed to load 3s data from XML: " << full_path << std::endl;
return 1;
}
std::remove(BAK_WEBSERVICE_3S_TRIG_COMMAND_XML_FN.c_str()); // 删除旧备份
if (std::rename(full_path.c_str(), BAK_WEBSERVICE_3S_TRIG_COMMAND_XML_FN.c_str()) != 0) {
std::perror("Rename failed");
}
std::cout << "/etc/trigger3s/*.xml success..." << std::endl;
DIY_WARNLOG("process", "【WARN】前置读取实时数据触发文件成功,即将注册实时数据报告");
return 0;
}
std::cout << "3s xml fail..." << std::endl;
return 1;
}
void check_3s_config() {
double now;
static double last_check_3s_config_time = 0.0; // 初始化时间
trigger_3s_xml_t trigger_3s_xml; // 3s触发文件
if (!three_secs_enabled) // 只有cfg_3s_data进程才会开启
return;
now = sGetMsTime(); // 当前时间
if (std::fabs(now - last_check_3s_config_time) < 3 * 1000) // wait 3secs
return; // 当前进程任务执行时查看当前时间和上次执行时间间隔小于3秒不执行大于等于3秒往下执行
last_check_3s_config_time = now; // 记录本次运行时间
while (0 == parse_3s_xml(&trigger_3s_xml)) { // 处理3秒文件一次处理一个
process_3s_config(&trigger_3s_xml); // 根据文件处理数据
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////补招文件部分
//将文件内容读取到结构中
void parse_recall(recall_xml_t* recall_xml, const std::string& parentTag, const std::map<std::string, std::string>& attributes, const std::string& id) {
recall_t recall;
std::memset(&recall, 0, sizeof(recall_t));
// 设置监测点 ID
recall.line_id = id;
// 解析时间字符串
auto parse_time = [](const std::string& time_str) -> std::time_t {
std::tm tm = {};
std::istringstream ss(time_str);
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
if (ss.fail()) return 0;
return std::mktime(&tm);
};
recall.start_time = parse_time(attributes.at("StartTime"));
recall.end_time = parse_time(attributes.at("EndTime"));
recall.need_steady = std::stoi(attributes.at("STEADY"));
recall.need_voltage = std::stoi(attributes.at("VOLTAGE"));
std::cout << parentTag << " -> " << recall.line_id
<< " " << recall.need_steady
<< " " << recall.need_voltage
<< " " << recall.start_time
<< " " << recall.end_time << std::endl;
if (parentTag == "Work" && recall_xml->work_recall_num < MAX_RECALL_NUM) {
recall_xml->work_recalls[recall_xml->work_recall_num++] = recall;
} else if (parentTag == "New" && recall_xml->new_recall_num < MAX_RECALL_NUM) {
recall_xml->new_recalls[recall_xml->new_recall_num++] = recall;
}
}
//读取补招文件
int parse_recall_xml(recall_xml_t* recall_xml, const std::string& id) {
std::string cfg_dir = FRONT_PATH + "/etc/recall";
std::string pattern = subdir + "_" + std::to_string(g_front_seg_index) + "_" + id + "_*_Recall.xml";
DIR* dir = opendir(cfg_dir.c_str());
if (!dir) {
DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 无法解析补招文件补招文件路径FRONT_PATH + /etc/recall/不存在", g_front_seg_index);
return false;
}
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
std::string filename = entry->d_name;
if (fnmatch(pattern.c_str(), filename.c_str(), 0) != 0) continue;
std::string filepath = cfg_dir + "/" + filename;
tinyxml2::XMLDocument doc;
if (doc.LoadFile(filepath.c_str()) != tinyxml2::XML_SUCCESS) {
DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 无法解析补招文件%s,补招内容无效", g_front_seg_index, filepath.c_str());
continue;
}
tinyxml2::XMLElement* root = doc.RootElement();
if (!root) continue;
for (tinyxml2::XMLElement* elem = root->FirstChildElement(); elem != nullptr; elem = elem->NextSiblingElement()) {
std::string tag = elem->Name();
if (tag == "Work" || tag == "New") {
for (tinyxml2::XMLElement* recallElem = elem->FirstChildElement("Recall"); recallElem != nullptr; recallElem = recallElem->NextSiblingElement("Recall")) {
std::map<std::string, std::string> attrs;
const char* start = recallElem->Attribute("StartTime");
const char* end = recallElem->Attribute("EndTime");
const char* steady = recallElem->Attribute("STEADY");
const char* voltage = recallElem->Attribute("VOLTAGE");
if (start && end && steady && voltage) {
attrs["StartTime"] = start;
attrs["EndTime"] = end;
attrs["STEADY"] = steady;
attrs["VOLTAGE"] = voltage;
parse_recall(recall_xml, tag, attrs, id);
}
}
}
}
}
closedir(dir);
return 0;
}
//将读取到的补招文件写入到监测点的运行结构中,后续根据实际补充
void process_recall_config(recall_xml_t* recall_xml)
{
}
//根据监测点id来获取补招数据补招时调用这个
void Check_Recall_Config(const std::string& id) {
/*if (g_node_id == HIS_DATA_BASE_NODE_ID ||
g_node_id == NEW_HIS_DATA_BASE_NODE_ID ||
g_node_id == RECALL_HIS_DATA_BASE_NODE_ID ||
g_node_id == RECALL_ALL_DATA_BASE_NODE_ID) {*/
recall_xml_t recall_xml;
std::memset(&recall_xml, 0, sizeof(recall_xml_t));
// 解析补招文件
parse_recall_xml(&recall_xml, id);
// 将补招数据赋值到全局变量
process_recall_config(&recall_xml);
//}
}
//补招成功后删除补招文件,补招后调用这个
int delete_recall_xml(const std::string& id) {
std::string cfg_dir = FRONT_PATH + "/etc/recall";
std::string pattern = std::string(subdir) + "_" +
std::to_string(g_front_seg_index) + "_" +
id + "_*_Recall.xml";
DIR* dir = opendir(cfg_dir.c_str());
if (!dir) {
std::cerr << "Folder does not exist or cannot be opened: " << cfg_dir << std::endl;
return false;
}
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
std::string filename = entry->d_name;
if (fnmatch(pattern.c_str(), filename.c_str(), 0) == 0) {
std::string fullpath = cfg_dir + "/" + filename;
if (remove(fullpath.c_str()) == 0) {
std::cout << "Deleted: " << fullpath << std::endl;
} else {
std::cerr << "Failed to delete: " << fullpath << std::endl;
}
}
}
closedir(dir);
return 0;
}
//删除过期的xmllnk:多个进程并发删除导致的失败不会影响进程
void DeletcRecallXml() {
std::string cfg_dir = FRONT_PATH + "/etc/recall";
std::string pattern = std::string(subdir) + "_*_Recall.xml";
DIR* dir = opendir(cfg_dir.c_str());
if (!dir) {
std::cerr << "folder does not exist!" << std::endl;
DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 删除旧的补招文件失败,补招文件路径FRONT_PATH + /etc/recall/不存在", g_front_seg_index);
return;
}
// 获取当前时间,计算 2 天前的时间戳
std::time_t now = std::time(nullptr);
std::time_t cutoff = now - 2 * 24 * 60 * 60; // 两天前
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
std::string filename = entry->d_name;
if (fnmatch(pattern.c_str(), filename.c_str(), 0) == 0) {
std::string fullpath = cfg_dir + "/" + filename;
struct stat file_stat;
if (stat(fullpath.c_str(), &file_stat) == 0) {
if (file_stat.st_mtime < cutoff) {
if (remove(fullpath.c_str()) == 0) {
DIY_INFOLOG("process", "【NORMAL】前置的%d号进程 删除超过两天的补招文件", g_front_seg_index);
} else {
std::cerr << "Failed to remove file: " << fullpath << std::endl;
}
}
}
}
}
closedir(dir);
}
//根据补招列表创建补招文件
void CreateRecallXml() {
std::time_t now = std::time(nullptr);
std::tm* tm_now = std::localtime(&now);
char timestamp[32] = {0};
std::strftime(timestamp, sizeof(timestamp), "%Y%m%d%H%M%S", tm_now);
g_StatisticLackList_list_mutex.lock();
if (!g_StatisticLackList.empty()) {
DIY_INFOLOG("process", "【NORMAL】前置的%d号进程 开始写入补招文件", g_front_seg_index);
std::map<std::string, std::list<JournalRecall>> id_map;
for (const auto& jr : g_StatisticLackList) {
id_map[jr.MonitorID].push_back(jr);
}
for (const auto& pair : id_map) {
const std::string& monitor_id = pair.first;
const std::list<JournalRecall>& recalls = pair.second;
std::ostringstream path;
path << FRONT_PATH << "/etc/recall/" << subdir << "_" << g_front_seg_index << "_" << monitor_id << "_" << timestamp << "_Recall.xml";
tinyxml2::XMLDocument doc;
tinyxml2::XMLDeclaration* decl = doc.NewDeclaration();
doc.InsertFirstChild(decl);
tinyxml2::XMLElement* root = doc.NewElement("RecallList");
doc.InsertEndChild(root);
// 空 Work 段
tinyxml2::XMLElement* work = doc.NewElement("Work");
root->InsertEndChild(work);
// New 段
tinyxml2::XMLElement* new_elem = doc.NewElement("New");
for (const auto& jr : recalls) {
tinyxml2::XMLElement* recall = doc.NewElement("Recall");
recall->SetAttribute("MonitorID", jr.MonitorID.c_str());
recall->SetAttribute("StartTime", jr.StartTime.c_str());
recall->SetAttribute("EndTime", jr.EndTime.c_str());
recall->SetAttribute("STEADY", jr.STEADY.c_str());
recall->SetAttribute("VOLTAGE", jr.VOLTAGE.c_str());
new_elem->InsertEndChild(recall);
}
root->InsertEndChild(new_elem);
tinyxml2::XMLError save_result = doc.SaveFile(path.str().c_str());
if (save_result != tinyxml2::XML_SUCCESS) {
DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 无法将补招文件写入路径: %s", g_front_seg_index, path.str().c_str());
continue;
}
}
}
g_StatisticLackList.clear();
g_StatisticLackList_list_mutex.unlock();
}
//生成待补招xml文件
void create_recall_xml()
{
//if (g_node_id == HIS_DATA_BASE_NODE_ID || g_node_id == NEW_HIS_DATA_BASE_NODE_ID || g_node_id == RECALL_HIS_DATA_BASE_NODE_ID || (g_node_id == RECALL_ALL_DATA_BASE_NODE_ID)) {
DeletcRecallXml();
CreateRecallXml();
//}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////补招部分
// 工具函数:将时间字符串转为 time_t秒级
static long long parse_time_to_epoch(const std::string& time_str) {
std::tm tm = {};
std::istringstream ss(time_str);
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
if (ss.fail()) {
return 0;
}
return static_cast<long long>(std::mktime(&tm));
}
// 数据完整性补招判断划分为1小时
void Get_Recall_Time_Char(const std::string& start_time_str,
const std::string& end_time_str,
std::vector<RecallInfo>& recallinfo_list_hour) {
long long starttime = parse_time_to_epoch(start_time_str);
long long endtime = parse_time_to_epoch(end_time_str);
if (starttime == 0 || endtime == 0 || starttime >= endtime) {
return;
}
// 初始区间加入
RecallInfo initial;
initial.starttime = starttime;
initial.endtime = endtime;
std::vector<RecallInfo> recallinfo_list;
recallinfo_list.push_back(initial);
const long long max_interval = 3600; // 1小时
for (const auto& interval : recallinfo_list) {
long long duration = interval.endtime - interval.starttime;
for (long long j = 0; j <= duration; j += max_interval) {
RecallInfo info;
info.starttime = interval.starttime + j;
if (j + max_interval > duration) {
info.endtime = interval.endtime;
} else {
info.endtime = interval.starttime + j + max_interval - 1;
}
recallinfo_list_hour.push_back(info);
}
}
}
//mq调用将补招信息写入补招列表
int recall_json_handle_from_mq(const std::string& body)
{
try {
// ====== 解析外层 JSON ======
nlohmann::json root;
try {
root = nlohmann::json::parse(body);
} catch (const std::exception& e) {
std::cerr << "Error parsing JSON: " << e.what() << std::endl;
// ★与原逻辑等价:无法解析,不再进入 recall_json_handle
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,消息的json结构不正确",
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
return 10004;
}
// 提取 "messageBody"(字符串)
if (!root.contains("messageBody") || !root["messageBody"].is_string()) {
std::cerr << "'messageBody' is missing or is not a string" << std::endl;
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,没有messageBody字段",
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
return 10004;
}
std::string messageBodyStr = root["messageBody"].get<std::string>();
if (messageBodyStr.empty()) {
std::cerr << "'messageBody' is empty" << std::endl;
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,messageBody为空",
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
return 10004;
}
// 解析 messageBody 内层 JSON
nlohmann::json messageBody;
try {
messageBody = nlohmann::json::parse(messageBodyStr);
} catch (const std::exception& e) {
std::cerr << "Failed to parse 'messageBody' JSON: " << e.what() << std::endl;
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,messageBody的json结构不正确",
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
return 10004;
}
// 提取 guid 并立即回执
if (!messageBody.contains("guid") || !messageBody["guid"].is_string()) {
std::cerr << "'guid' is missing or is not a string" << std::endl;
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,没有guid字段",
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
return 10004;
}
std::string guid = messageBody["guid"].get<std::string>();
send_reply_to_queue(guid, "1", "收到补招指令");
// 提取 data 数组
if (!messageBody.contains("data") || !messageBody["data"].is_array()) {
std::cerr << "'data' is missing or is not an array" << std::endl;
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,没有data字段",
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
return 10004;
}
// 仅用于保留你原先的调试输出
std::string data_dump;
try { data_dump = messageBody["data"].dump(); } catch (...) { data_dump.clear(); }
std::cout << "parseJsonMessageRC: " << data_dump << std::endl;
// 不指定稳态/暂态则全部补招
int stat = 0;
int voltage = 0;
// 2. 遍历每个补招项(这里直接用已解析的 messageBody["data"]
for (auto& item : messageBody["data"]) {
// 获取必需字段
// ★修改:强制要求 terminalId
if (!item.contains("terminalId") ||
!item.contains("monitor") ||
!item.contains("timeInterval") ||
!item.contains("dataType"))
{
std::cout << "json内容解析错误" << std::endl;
return 10000;
}
// ★新增:读取 terminalId必填
std::string terminalId = item["terminalId"].get<std::string>();
if (terminalId.empty()) {
std::cout << "terminalId为空" << std::endl;
continue;
}
// 2.1 解析 dataType仅保留稳态/暂态)
std::string datatype = item["dataType"].get<std::string>();
if (!datatype.empty()) {
if (datatype == "0" || datatype == "稳态" || datatype == "steady" || datatype == "STEADY") {
stat = 1; voltage = 0; // 稳态
} else if (datatype == "1" || datatype == "暂态" || datatype == "voltage" || datatype == "VOLTAGE") {
stat = 0; voltage = 1; // 暂态
} else {
stat = voltage = 1; // 其他情况按全补
}
} else {
stat = voltage = 1; // 全补
}
// ★新增:定位并校验该 terminal 是否归属当前进程
std::lock_guard<std::mutex> lock(ledgermtx);
const terminal_dev* targetDev = NULL;
for (std::vector<terminal_dev>::const_iterator it = terminal_devlist.begin();
it != terminal_devlist.end(); ++it)
{
if (it->terminal_id == terminalId) {
targetDev = &(*it);
break;
}
}
if (!targetDev) {
std::cout << "terminalId未匹配当前进程内装置: " << terminalId << std::endl;
continue;
}
// 添加判断装置在线不注册guid异步补招
if (ClientManager::instance().get_dev_status(targetDev->terminal_id) != 1) {
std::cout << "terminalId对应装置不在线: " << targetDev->terminal_id << std::endl;
// 响应 web
std::string msg = std::string("装置:") + targetDev->terminal_name + " 不在线,无法补招";
send_reply_to_kafka_recall("12345", "2", static_cast<int>(ResponseCode::INTERNAL_ERROR), msg, targetDev->terminal_id, "", "", "");
continue;//处理下一个装置的补招记录
}
// ★新增:按新结构解析 monitor 层级
nlohmann::json& monitors = item["monitor"];
if (!monitors.is_array() || monitors.empty()) {
std::cout << "monitor数组为空或非数组类型" << std::endl;
continue;
}
for (auto& mobj : monitors) {
if (!mobj.contains("monitorId") || !mobj.contains("timeInterval")) {
std::cout << "monitor项缺少 monitorId 或 timeInterval" << std::endl;
continue;
}
std::string monitorId = mobj["monitorId"].get<std::string>();
if (monitorId.empty()) continue;
// 不只是判断存在,还要拿到指针 lm 以便后续 push_back
ledger_monitor* lm = NULL;
// 注意:这里需要非常量指针,取 const_cast 后遍历
terminal_dev* dev_nc = const_cast<terminal_dev*>(targetDev);
for (std::vector<ledger_monitor>::iterator itLm = dev_nc->line.begin();
itLm != dev_nc->line.end(); ++itLm)
{
if (!itLm->monitor_id.empty() && itLm->monitor_id == monitorId) {
lm = &(*itLm);
break;
}
}
if (!lm) {
std::cout << "monitorId未在terminal内找到: " << monitorId
<< " @ " << terminalId << std::endl;
continue;
}
nlohmann::json& tiArr = mobj["timeInterval"];
if (!tiArr.is_array() || tiArr.empty()) {
std::cout << "timeInterval为空或非数组类型: monitorId=" << monitorId << std::endl;
continue;
}
// 这里拆分时间段并 push 到 lm->recall_list / lm->recall_list_static
for (auto& timeItem : tiArr) {
std::string ti = timeItem.get<std::string>();
std::string::size_type pos = ti.find('~');
if (pos == std::string::npos) {
std::cout << "timeInterval格式错误: " << ti << std::endl;
continue;
}
std::string start = ti.substr(0, pos);
std::string end = ti.substr(pos + 1);
// 仅对 recall_list事件进行 1 小时拆分recall_list_static稳态不拆分
{
// 公共字段(整体区间,不拆分)用于 recall_list_static
RecallFile rm_all; // ★类型正确:稳态列表的元素
rm_all.recall_status = 0; // 初始状态:未补招
rm_all.StartTime = start; // 直接使用字符串
rm_all.EndTime = end;
rm_all.STEADY = std::to_string(stat);
rm_all.VOLTAGE = std::to_string(voltage);
// 仅当需要事件补招voltage==1才进行 1 小时拆分并压入 recall_list
if (voltage == 1) {
// 拆分时间段为 1 小时一段,并存入 recall_list
std::vector<RecallInfo> recallinfo_list_hour;
Get_Recall_Time_Char(start, end, recallinfo_list_hour);
for (std::size_t i = 0; i < recallinfo_list_hour.size(); ++i) {
const RecallInfo& info = recallinfo_list_hour[i];
RecallMonitor rm; // ★类型正确:事件列表的元素
rm.recall_status = 0; // 初始状态:未补招
rm.StartTime = epoch_to_datetime_str(info.starttime);
rm.EndTime = epoch_to_datetime_str(info.endtime);
rm.STEADY = std::to_string(stat);
rm.VOLTAGE = std::to_string(voltage);
lm->recall_list.push_back(rm);
// 事件补招列表recall_list调试打印
std::cout << "[recall_json_handle] terminal=" << terminalId
<< " monitor=" << monitorId
<< " [recall_list] start=" << lm->recall_list.back().StartTime
<< " end=" << lm->recall_list.back().EndTime
<< " steady="<< lm->recall_list.back().STEADY
<< " voltage="<< lm->recall_list.back().VOLTAGE
<< std::endl;
}
}
// 仅当需要稳态补招stat==1不拆分直接压入 recall_list_static
if (stat == 1) {
lm->recall_list_static.push_back(rm_all); // 不拆分,整体区间
// 稳态补招列表recall_list_static调试打印
std::cout << "[recall_json_handle] terminal=" << terminalId
<< " monitor=" << monitorId
<< " [recall_list_static] start=" << lm->recall_list_static.back().StartTime
<< " end=" << lm->recall_list_static.back().EndTime
<< " steady="<< lm->recall_list_static.back().STEADY
<< " voltage="<< lm->recall_list_static.back().VOLTAGE
<< std::endl;
}
// 非法输入保护(保留你原来的保护与返回码)
if (stat == 0 && voltage == 0) {
std::cout << "[recall_json_handle] skip: stat=0 && voltage=0, monitor=" << monitorId
<< " terminal=" << terminalId
<< " start=" << rm_all.StartTime
<< " end=" << rm_all.EndTime
<< std::endl;
return 10003;
}
}
}
}
}
}
catch (const std::exception& e) {
std::cout << "处理客户端发送的消息错误,原因:" << e.what() << std::endl;
return 10004;
}
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////台账接口打印
// 打印 terminal_dev_map 中所有内容的函数
void printTerminalDevMap(const std::map<std::string, terminal_dev>& terminal_dev_map) {
for (const auto& kv : terminal_dev_map) {
const std::string& key = kv.first;
const terminal_dev& dev = kv.second; // 引用对象
std::cout << "Key: " << key
<< ", Terminal ID: " << dev.terminal_id
<< ", Terminal Code: " << dev.terminal_name
<< ", Organization Name: "<< dev.org_name
<< ", Maintenance Name: " << dev.maint_name
<< ", Station Name: " << dev.station_name
<< ", Factory: " << dev.tmnl_factory
<< ", Status: " << dev.tmnl_status
<< ", Device Type: " << dev.dev_type
<< ", Device Key: " << dev.dev_key
<< ", Device Series: " << dev.dev_series
<< ", ProcessNo: " << dev.processNo
<< ", MaxProcessNum: " << dev.maxProcessNum
<< ", Address: " << dev.addr_str
<< ", Port: " << dev.port
<< ", Timestamp: " << dev.timestamp
<< ", mac: " << dev.mac
<< std::endl;
// 打印监测点信息
for (size_t i = 0; i < dev.line.size(); ++i) {
const auto& m = dev.line[i];
std::cout << " Monitor [" << i << "] "
<< "ID: " << m.monitor_id
<< ", Code: " << m.terminal_id
<< ", Name: " << m.monitor_name
<< ", Seq: " << m.logical_device_seq
<< ", Voltage: "<< m.voltage_level
<< ", Connect: "<< m.terminal_connect
<< ", Timestamp:"<< m.timestamp
<< ", Status: " << m.status
<< ", CT1: " << m.CT1
<< ", CT2: " << m.CT2
<< ", PT1: " << m.PT1
<< ", PT2: " << m.PT2
<< std::endl;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////更新台账
//在台账更新目录查找自己进程要处理的文件
std::list<std::string> find_xml_belong_to_this_process()
{
char prefix[20]; // 假设最多需要20个字符根据实际需要调整
sprintf(prefix, "%d_%d", g_node_id, g_front_seg_index); // 将g_node_id和g_front_seg_index格式化为字符串
std::string LEDGER_UPDATE_DIR = FRONT_PATH + "/etc/ledgerupdate/"; //台账更新读取目录
DIR *dir = opendir(LEDGER_UPDATE_DIR.c_str()); // 打开目录
struct dirent *entry;
std::list<std::string> found_files; // 用于存储找到的所有匹配文件名
if (dir == NULL) {
std::cout << "Failed to open directory: " << LEDGER_UPDATE_DIR << std::endl;
return found_files; // 返回空的list
}
// 遍历目录中的所有文件
while ((entry = readdir(dir)) != NULL) {
std::string filename = entry->d_name;
// 排除 "." 和 ".." 目录
if (filename == "." || filename == "..") {
continue;
}
std::cout << "find" << filename << "in" << LEDGER_UPDATE_DIR << std::endl;
// 判断文件名是否以 prefix 开头且扩展名是 .xml
if (filename.find(prefix) == 0 && filename.substr(filename.find_last_of('.') + 1) == "xml") {
std::string full_path = LEDGER_UPDATE_DIR + filename;
found_files.push_back(full_path); // 将完整路径加入容器
}
}
closedir(dir); // 关闭目录
return found_files; // 返回所有找到的文件名
}
// 根据 str_tag 将 terminal 添加到对应的数组
void add_terminal_to_trigger_update(trigger_update_xml_t& trigger_update_xml,
const std::string& str_tag,
const terminal_dev& work_terminal) {
if (str_tag == "add") {
std::cout << "new ledger!!!!" << std::endl;
trigger_update_xml.new_updates.push_back(work_terminal);
} else if (str_tag == "modify") {
std::cout << "modify ledger!!!" << std::endl;
trigger_update_xml.modify_updates.push_back(work_terminal);
} else {
std::cerr << "Unknown tag: " << str_tag << std::endl;
}
}
// 将添加和修改的文件内容写入结构
void parse_terminal_from_data(trigger_update_xml_t& trigger_update_xml,
const std::string& str_tag,
const std::string& data,
const std::string& guid_value) {
terminal_dev work_terminal;
work_terminal.guid = guid_value;
tinyxml2::XMLDocument doc;
if (doc.Parse(data.c_str()) != tinyxml2::XML_SUCCESS) {
return;
}
auto root = doc.FirstChildElement("terminal");
if (!root) return;
auto get_value = [&](const char* tag) -> std::string {
auto elem = root->FirstChildElement(tag);
return elem && elem->GetText() ? elem->GetText() : "";
};
work_terminal.terminal_id = get_value("id");
work_terminal.terminal_name = get_value("terminalCode");
work_terminal.org_name = get_value("orgName");
work_terminal.maint_name = get_value("maintName");
work_terminal.station_name = get_value("stationName");
work_terminal.tmnl_factory = get_value("manufacturer");
work_terminal.tmnl_status = get_value("status");
work_terminal.dev_type = get_value("devType");
work_terminal.dev_key = get_value("devKey");
work_terminal.dev_series = get_value("series");
work_terminal.processNo = get_value("processNo");
work_terminal.addr_str = get_value("ip");
work_terminal.port = get_value("port");
work_terminal.timestamp = get_value("updateTime");
work_terminal.mac = get_value("mac");
for (tinyxml2::XMLElement* monitor = root->FirstChildElement("monitorData");
monitor;
monitor = monitor->NextSiblingElement("monitorData")) {
ledger_monitor mon;
mon.monitor_id = monitor->FirstChildElement("id") ? monitor->FirstChildElement("id")->GetText() : "N/A";
mon.monitor_name = monitor->FirstChildElement("name") ? monitor->FirstChildElement("name")->GetText() : "N/A";
mon.voltage_level = monitor->FirstChildElement("voltageLevel") ? monitor->FirstChildElement("voltageLevel")->GetText() : "N/A";
mon.terminal_connect = monitor->FirstChildElement("ptType") ? monitor->FirstChildElement("ptType")->GetText() : "N/A";
mon.logical_device_seq = monitor->FirstChildElement("lineNo") ? monitor->FirstChildElement("lineNo")->GetText() : "N/A";
mon.timestamp = monitor->FirstChildElement("timestamp") ? monitor->FirstChildElement("timestamp")->GetText() : "N/A";
mon.terminal_id = monitor->FirstChildElement("terminal_id") ? monitor->FirstChildElement("terminal_name")->GetText() : "N/A";
mon.status = monitor->FirstChildElement("status") ? monitor->FirstChildElement("status")->GetText() : "N/A";
mon.CT1 = monitor->FirstChildElement("CT1") && monitor->FirstChildElement("CT1")->GetText()
? atof(monitor->FirstChildElement("CT1")->GetText()) : 0.0;
mon.CT2 = monitor->FirstChildElement("CT2") && monitor->FirstChildElement("CT2")->GetText()
? atof(monitor->FirstChildElement("CT2")->GetText()) : 0.0;
mon.PT1 = monitor->FirstChildElement("PT1") && monitor->FirstChildElement("PT1")->GetText()
? atof(monitor->FirstChildElement("PT1")->GetText()) : 0.0;
mon.PT2 = monitor->FirstChildElement("PT2") && monitor->FirstChildElement("PT2")->GetText()
? atof(monitor->FirstChildElement("PT2")->GetText()) : 0.0;
work_terminal.line.push_back(mon);
}
add_terminal_to_trigger_update(trigger_update_xml, str_tag, work_terminal);
}
// 统一处理文件内容和结构
void parse_ledger_update(trigger_update_xml_t& trigger_update_xml,
const std::string& strTag,
const std::string& data,
const std::string& guid_value) {
std::cout << "record one xml.." << std::endl;
if (strTag == "add" || strTag == "modify") {
parse_terminal_from_data(trigger_update_xml, strTag, data, guid_value);
} else if (strTag == "delete") {
terminal_dev delete_terminal;
tinyxml2::XMLDocument doc;
if (doc.Parse(data.c_str()) != tinyxml2::XML_SUCCESS) {
std::cerr << "Failed to parse XML for delete tag." << std::endl;
return;
}
tinyxml2::XMLElement* root = doc.FirstChildElement("terminalData");
if (!root) {
std::cerr << "Missing terminalData element in delete tag." << std::endl;
return;
}
tinyxml2::XMLElement* idElem = root->FirstChildElement("id");
if (idElem && idElem->GetText()) {
delete_terminal.terminal_id = idElem->GetText();
} else {
std::cerr << "Missing id element in delete tag." << std::endl;
return;
}
delete_terminal.guid = guid_value;
trigger_update_xml.delete_updates.push_back(delete_terminal);
} else {
std::cerr << "Unsupported strTag: " << strTag << std::endl;
}
}
//读取台账更新文件
int load_ledger_update_from_xml(trigger_update_xml_t& trigger_update_xml, const std::string& xml_fn) {
std::cout << "start to load one xml.." << std::endl;
std::ifstream file(xml_fn);
if (!file.is_open()) {
std::cerr << "Failed to open file: " << xml_fn << std::endl;
return -1;
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();
file.close();
tinyxml2::XMLDocument doc;
if (doc.Parse(content.c_str()) != tinyxml2::XML_SUCCESS) {
std::cerr << "Failed to parse XML content." << std::endl;
return -1;
}
auto* root = doc.FirstChildElement("ledger_update");
if (!root) {
std::cerr << "Missing <ledger_update> root tag." << std::endl;
return -1;
}
std::string guid_value;
auto* guidElem = root->FirstChildElement("guid");
if (guidElem && guidElem->GetText()) {
guid_value = guidElem->GetText();
std::cout << "Found guid: " << guid_value << std::endl;
}
const char* tag_names[] = {"add", "modify", "delete"};
tinyxml2::XMLElement* tagElem = nullptr;
std::string target_tag;
for (const auto& tag : tag_names) {
tagElem = root->FirstChildElement(tag);
if (tagElem) {
target_tag = tag;
break;
}
}
if (!tagElem) {
std::cerr << "No <add>, <modify>, or <delete> tag found!" << std::endl;
return -1;
}
for (auto* termElem = tagElem->FirstChildElement("terminalData");
termElem;
termElem = termElem->NextSiblingElement("terminalData")) {
tinyxml2::XMLPrinter printer;
termElem->Accept(&printer);
std::string data_content = printer.CStr();
std::cout << "ledger data_content is " << data_content << std::endl;
parse_ledger_update(trigger_update_xml, target_tag, data_content, guid_value);
}
std::cout << "load one xml finish" << std::endl;
return 0;
}
//台账更新处理文件
int parse_ledger_update_xml(trigger_update_xml_t& trigger_update_xml)
{
std::list<std::string> result = find_xml_belong_to_this_process();
if (result.empty()) return 1;
for (const auto& filename : result) {
std::cout << "Found XML: " << filename << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (!load_ledger_update_from_xml(trigger_update_xml, filename)) {
DIY_WARNLOG("process", "【WARN】成功读取台账更新文件: %s", filename.c_str());
}
if (std::remove(filename.c_str()) != 0) {
DIY_ERRORLOG("process", "【ERROR】删除台账更新文件失败: %s", filename.c_str());
return 1;
} else {
DIY_INFOLOG("process", "【NORMAL】成功删除台账更新文件: %s", filename.c_str());
}
}
if (!trigger_update_xml.new_updates.empty() ||
!trigger_update_xml.modify_updates.empty() ||
!trigger_update_xml.delete_updates.empty()) {
std::cout << "ledger update xml have data..." << std::endl;
return 0;
}
std::cout << "ledger update xml no data..." << std::endl;
return 1;
}
//更新单个台账
int update_one_terminal_ledger(const terminal_dev& update,terminal_dev& target_dev) {
// 更新基本信息
if (!update.terminal_id.empty()) {
target_dev.terminal_id = update.terminal_id;
std::cout << "terminal_id: " << target_dev.terminal_id << std::endl;
}
if (!update.terminal_name.empty()) {
target_dev.terminal_name = update.terminal_name;
std::cout << "terminal_name: " << target_dev.terminal_name << std::endl;
}
if (!update.tmnl_factory.empty()) {
target_dev.tmnl_factory = update.tmnl_factory;
std::cout << "tmnl_factory: " << target_dev.tmnl_factory << std::endl;
}
if (!update.tmnl_status.empty()) {
target_dev.tmnl_status = update.tmnl_status;
std::cout << "tmnl_status: " << target_dev.tmnl_status << std::endl;
}
if (!update.dev_type.empty()) {
target_dev.dev_type = update.dev_type;
std::cout << "dev_type: " << target_dev.dev_type << std::endl;
}
if (!update.processNo.empty()) {
target_dev.processNo = update.processNo;
std::cout << "processNo: " << target_dev.processNo << std::endl;
}
if (!update.dev_series.empty()) {
target_dev.dev_series = update.dev_series;
std::cout << "dev_series: " << target_dev.dev_series << std::endl;
}
if (!update.dev_key.empty()) {
target_dev.dev_key = update.dev_key;
std::cout << "dev_key: " << target_dev.dev_key << std::endl;
}
if (!update.addr_str.empty()) {
target_dev.addr_str = update.addr_str;
std::cout << "addr_str: " << target_dev.addr_str << std::endl;
}
if (!update.port.empty()) {
target_dev.port = update.port;
std::cout << "port: " << target_dev.port << std::endl;
}
if (!update.mac.empty()) {
target_dev.mac = update.mac;
std::cout << "mac: " << target_dev.mac << std::endl;
}
if (!update.timestamp.empty()) {
struct tm timeinfo = {};
if (sscanf(update.timestamp.c_str(), "%4d-%2d-%2d %2d:%2d:%2d",
&timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday,
&timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec) == 6) {
timeinfo.tm_year -= 1900;
timeinfo.tm_mon -= 1;
timeinfo.tm_isdst = -1;
time_t raw_time = mktime(&timeinfo);
if (raw_time != -1) {
target_dev.timestamp = static_cast<long long>(raw_time);
std::cout << "timestamp (unix): " << target_dev.timestamp << std::endl;
} else {
std::cerr << "Error: mktime failed." << std::endl;
return -1;
}
} else {
std::cerr << "Error: invalid timestamp format." << std::endl;
return -1;
}
}
// 清空旧监测点并重新填充
target_dev.line.clear();
for (const auto& mon : update.line) {
if (mon.monitor_id.empty()) break;
ledger_monitor m;
m.monitor_id = mon.monitor_id;
m.monitor_name = mon.monitor_name;
m.logical_device_seq = mon.logical_device_seq;
m.voltage_level = mon.voltage_level;
m.terminal_connect = mon.terminal_connect;
m.status = mon.status;
m.terminal_id = mon.terminal_id;
m.timestamp = mon.timestamp;
m.CT1 = mon.CT1;
m.CT2 = mon.CT2;
m.PT1 = mon.PT1;
m.PT2 = mon.PT2;
if (m.terminal_connect != "0") {
isdelta_flag = 1;
std::cout << "monitor_id " << m.monitor_id << " uses delta wiring." << std::endl;
}
if (!m.timestamp.empty()) {
struct tm timeinfo = {};
if (sscanf(m.timestamp.c_str(), "%4d-%2d-%2d %2d:%2d:%2d",
&timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday,
&timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec) == 6) {
timeinfo.tm_year -= 1900;
timeinfo.tm_mon -= 1;
timeinfo.tm_isdst = -1;
time_t raw_time = mktime(&timeinfo);
if (raw_time != -1) {
m.timestamp = static_cast<long long>(raw_time);
std::cout << "monitor time (unix): " << m.timestamp << std::endl;
}
}
}
target_dev.line.push_back(m);
}
return 0;
}
//台账更新到台账列表
void process_ledger_update(trigger_update_xml_t& ledger_update_xml)
{
// --- 1. 新增处理 ---
std::cout << "add ledger num: " << ledger_update_xml.new_updates.size() << std::endl;
for (auto it = ledger_update_xml.new_updates.begin(); it != ledger_update_xml.new_updates.end(); ) {
terminal_dev& new_dev = *it;
auto found = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
[&](const terminal_dev& d) { return d.terminal_id == new_dev.terminal_id; });
if (found != terminal_devlist.end()) {
if (ledger_update_xml.modify_updates.size() < MAX_UPDATEA_NUM) {
ledger_update_xml.modify_updates.push_back(new_dev);
} else {
std::cerr << "Exceeded MAX_UPDATEA_NUM limit for modify_updates!" << std::endl;
}
it = ledger_update_xml.new_updates.erase(it); // 删除已处理项
continue;
}
if (terminal_devlist.size() >= static_cast<size_t>(IED_COUNT)) {
send_reply_to_queue(new_dev.guid, "2",
"终端 id: " + new_dev.terminal_id + " 台账更新失败,配置台账数量已满");
++it;
continue;
}
terminal_dev target_dev;
if (update_one_terminal_ledger(new_dev, target_dev) != 0) {
send_reply_to_queue(new_dev.guid, "2",
"终端 id: " + new_dev.terminal_id + " 台账更新失败,无法写入台账");
++it;
continue;
}
if (parse_model_cfg_web_one(target_dev.dev_type).empty()) {
send_reply_to_queue(new_dev.guid, "2",
"终端 id: " + new_dev.terminal_id + " 台账更新失败,未找到装置型号");
++it;
continue;
}
Set_xml_nodeinfo_one(target_dev.dev_type);
init_loggers_bydevid(target_dev.terminal_id);
terminal_devlist.push_back(target_dev);
send_reply_to_queue(new_dev.guid, "2",
"终端 id: " + new_dev.terminal_id + " 台账添加成功");
it = ledger_update_xml.new_updates.erase(it);
}
// --- 2. 修改处理 ---
std::cout << "modify ledger num: " << ledger_update_xml.modify_updates.size() << std::endl;
for (auto& mod_dev : ledger_update_xml.modify_updates) {
auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
[&](const terminal_dev& d) { return d.terminal_id == mod_dev.terminal_id; });
if (it != terminal_devlist.end()) {
remove_loggers_by_terminal_id(mod_dev.terminal_id);
if (update_one_terminal_ledger(mod_dev, *it) != 0) {
send_reply_to_queue(mod_dev.guid, "2",
"终端 id: " + mod_dev.terminal_id + " 台账更新失败,写入失败");
continue;
}
if (parse_model_cfg_web_one(it->dev_type).empty()) {
send_reply_to_queue(mod_dev.guid, "2",
"终端 id: " + mod_dev.terminal_id + " 台账更新失败,未找到装置型号");
continue;
}
Set_xml_nodeinfo_one(it->dev_type);
init_loggers_bydevid(mod_dev.terminal_id);
send_reply_to_queue(mod_dev.guid, "2",
"终端 id: " + mod_dev.terminal_id + " 台账修改成功");
} else {
send_reply_to_queue(mod_dev.guid, "2",
"终端 id: " + mod_dev.terminal_id + " 台账修改失败,未找到终端");
}
}
// --- 3. 删除处理 ---
std::cout << "delete ledger num: " << ledger_update_xml.delete_updates.size() << std::endl;
for (auto& del_dev : ledger_update_xml.delete_updates) {
auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
[&](const terminal_dev& d) { return d.terminal_id == del_dev.terminal_id; });
if (it != terminal_devlist.end()) {
remove_loggers_by_terminal_id(del_dev.terminal_id);
terminal_devlist.erase(it);
send_reply_to_queue(del_dev.guid, "2",
"终端 id: " + del_dev.terminal_id + " 台账删除成功");
} else {
send_reply_to_queue(del_dev.guid, "2",
"终端 id: " + del_dev.terminal_id + " 台账删除失败,未找到终端");
}
}
// --- 4. 日志记录 ---
if (!ledger_update_xml.modify_updates.empty() ||
!ledger_update_xml.new_updates.empty() ||
!ledger_update_xml.delete_updates.empty()) {
create_ledger_log(&ledger_update_xml);
}
}
//台账更新处理函数
void check_ledger_update()
{
static double last_check_time = 0.0;
double now = sGetMsTime();
if (now - last_check_time < 3000.0)
return;
last_check_time = now;
std::unique_ptr<trigger_update_xml_t> trigger_ledger_update_xml(new trigger_update_xml_t());
if (0 == parse_ledger_update_xml(*trigger_ledger_update_xml)) {
print_trigger_update_xml(*trigger_ledger_update_xml);
std::lock_guard<std::mutex> lock(ledgermtx);
process_ledger_update(*trigger_ledger_update_xml);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////台账更新记录日志
// 获取当前时间并格式化为 "YYYY-MM-DD HH:MM:SS"
std::string get_current_time() {
std::time_t t = std::time(NULL);
struct std::tm tm = *std::localtime(&t);
char buffer[80];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
return std::string(buffer);
}
// 写入日志条目
void write_log_entry(std::ofstream &log_file, const std::string &action, const std::string &terminal_id, const std::string &current_time) {
log_file << terminal_id << "\t" << action << "time:" << current_time << "\n";
}
// 创建台账更新日志
void create_ledger_log(trigger_update_xml_t* ledger_update_xml) {
std::cout << "create_ledger_log." << std::endl;
std::string log_filename = FRONT_PATH + "/etc/" + LEDGER_UPDATE_FN;
std::ofstream log_file(log_filename.c_str(), std::ios::app); // 以追加模式打开文件
if (!log_file.is_open()) {
std::cerr << "Failed to open log file: " << log_filename << std::endl;
return;
}
std::vector<std::pair<std::string, std::string>> new_entries;
std::vector<std::pair<std::string, std::string>> modify_entries;
std::vector<std::pair<std::string, std::string>> delete_entries;
std::string current_time = get_current_time(); // 获取当前时间
// new_updates
for (const auto& dev : ledger_update_xml->new_updates) {
new_entries.emplace_back(dev.terminal_id, current_time);
}
// modify_updates
for (const auto& dev : ledger_update_xml->modify_updates) {
modify_entries.emplace_back(dev.terminal_id, current_time);
}
// delete_updates
for (const auto& dev : ledger_update_xml->delete_updates) {
delete_entries.emplace_back(dev.terminal_id, current_time);
}
// 写入日志
if (!new_entries.empty()) {
log_file << "<new>\n";
for (const auto& entry : new_entries) {
write_log_entry(log_file, "add", entry.first, entry.second);
}
log_file << "</new>\n";
}
if (!modify_entries.empty()) {
log_file << "<modify>\n";
for (const auto& entry : modify_entries) {
write_log_entry(log_file, "modify", entry.first, entry.second);
}
log_file << "</modify>\n";
}
if (!delete_entries.empty()) {
log_file << "<delete>\n";
for (const auto& entry : delete_entries) {
write_log_entry(log_file, "delete", entry.first, entry.second);
}
log_file << "</delete>\n";
}
log_file.close();
std::cout << "Ledger log has been updated." << std::endl;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////shell打印日志
// ------------------ 全局日志列表和锁 ------------------
std::list<std::string> errorList;
std::list<std::string> warnList;
std::list<std::string> normalList;
pthread_mutex_t errorListMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t warnListMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t normalListMutex = PTHREAD_MUTEX_INITIALIZER;
// ------------------ 输出开关 ------------------
bool errorOutputEnabled = false; // 是否将 error 级别写入 errorList
bool warnOutputEnabled = false; // 是否将 warn 级别写入 warnList
bool normalOutputEnabled = false; // 是否将 normal 级别写入 normalList
// ------------------ 用于恢复原始缓冲区 ------------------
static std::streambuf* g_originalCoutBuf = NULL;
static std::streambuf* g_originalClogBuf = NULL;
static std::streambuf* g_originalCerrBuf = NULL;
// ------------------ 日志级别枚举C++98 ------------------
enum LogLevel {
LOGERROR,
LOGWARN,
LOGNORMAL
};
// ------------------------------------------------------------------
// TeeStreamBuf: 先写到原始buf(保持终端输出)再拷贝到list里
// ------------------------------------------------------------------
class TeeStreamBuf : public std::streambuf
{
public:
// 默认构造先把指针设为NULL
TeeStreamBuf()
: m_originalBuf(NULL), m_level(LOGNORMAL)
{
pthread_mutex_init(&m_mutex, NULL);
}
// 带参构造:直接初始化
TeeStreamBuf(std::streambuf* originalBuf, LogLevel level)
: m_originalBuf(originalBuf), m_level(level)
{
pthread_mutex_init(&m_mutex, NULL);
}
// 析构函数:销毁互斥锁
virtual ~TeeStreamBuf()
{
pthread_mutex_destroy(&m_mutex);
}
// 自定义 init(...) 函数:在同一个对象上重新设置
void init(std::streambuf* originalBuf, LogLevel level)
{
m_originalBuf = originalBuf;
m_level = level;
pthread_mutex_lock(&m_mutex);
m_buffer.clear();
pthread_mutex_unlock(&m_mutex);
}
protected:
// 当 flush 或 std::endl 时会调用 sync()
virtual int sync()
{
// 先让原始缓冲区执行同步
if (m_originalBuf) {
m_originalBuf->pubsync();
}
// 再将自身的缓存 flush
flushBuffer();
return 0; // 成功
}
// 当写入一个新字符时overflow() 被调用
virtual int_type overflow(int_type ch)
{
if (ch == traits_type::eof()) {
return ch;
}
// 1) 写到原始缓冲区 → 保留终端输出
if (m_originalBuf) {
if (m_originalBuf->sputc(static_cast<char>(ch)) == traits_type::eof()) {
return traits_type::eof();
}
}
// 2) 存到我们的临时缓存,注意加锁保护
pthread_mutex_lock(&m_mutex); //防止多线程推入崩溃lnk20250305
m_buffer.push_back(static_cast<char>(ch));
// 3) 遇到换行就 flushBuffer()
if (ch == '\n') {
flushBuffer_locked();
}
pthread_mutex_unlock(&m_mutex);
return ch;
}
private:
// 内部版本:假定互斥锁已经被加锁
void flushBuffer_locked()
{
if (m_buffer.empty()) {
return;
}
// 根据等级和对应开关,将 m_buffer 写入相应的 list
switch (m_level) {
case LOGERROR:
if (normalOutputEnabled) {
pthread_mutex_lock(&normalListMutex);
normalList.push_back(m_buffer);
pthread_mutex_unlock(&normalListMutex);
}
else if (warnOutputEnabled) {
pthread_mutex_lock(&warnListMutex);
warnList.push_back(m_buffer);
pthread_mutex_unlock(&warnListMutex);
}
else if (errorOutputEnabled) {
pthread_mutex_lock(&errorListMutex);
errorList.push_back(m_buffer);
pthread_mutex_unlock(&errorListMutex);
}
break;
case LOGWARN:
if (normalOutputEnabled) {
pthread_mutex_lock(&normalListMutex);
normalList.push_back(m_buffer);
pthread_mutex_unlock(&normalListMutex);
}
else if (warnOutputEnabled) {
pthread_mutex_lock(&warnListMutex);
warnList.push_back(m_buffer);
pthread_mutex_unlock(&warnListMutex);
}
break;
case LOGNORMAL:
if (normalOutputEnabled) {
pthread_mutex_lock(&normalListMutex);
normalList.push_back(m_buffer);
pthread_mutex_unlock(&normalListMutex);
}
break;
}
m_buffer.clear();
}
// 对外接口,内部对 m_buffer 加锁
void flushBuffer()
{
pthread_mutex_lock(&m_mutex);
flushBuffer_locked();
pthread_mutex_unlock(&m_mutex);
}
private:
// 禁止自动生成的赋值函数
TeeStreamBuf& operator=(const TeeStreamBuf&);
private:
std::streambuf* m_originalBuf;
LogLevel m_level;
std::string m_buffer;
pthread_mutex_t m_mutex;
};
// ------------------ 全局Tee对象避免重复赋值 ------------------
static TeeStreamBuf g_errorTeeBuf;
static TeeStreamBuf g_warnTeeBuf;
static TeeStreamBuf g_normalTeeBuf;
// ------------------ 重定向函数 ------------------
// 只在第一次启用时,用 init(...) 初始化 TeeStreamBuf
// 之后再启用时,不再 new 或 赋值,而是直接用之前构造好的对象。
void redirectErrorOutput(bool enabled)
{
errorOutputEnabled = enabled;
if (enabled) {
if (g_originalCerrBuf == NULL) {
g_originalCerrBuf = std::cerr.rdbuf();
g_errorTeeBuf.init(g_originalCerrBuf, LOGERROR);
}
std::cerr.rdbuf(&g_errorTeeBuf);
} else {
if (g_originalCerrBuf) {
std::cerr.rdbuf(g_originalCerrBuf);
}
}
}
void redirectWarnOutput(bool enabled)
{
warnOutputEnabled = enabled;
if (enabled) {
if (g_originalClogBuf == NULL) {
g_originalClogBuf = std::clog.rdbuf();
g_warnTeeBuf.init(g_originalClogBuf, LOGWARN);
}
std::clog.rdbuf(&g_warnTeeBuf);
} else {
if (g_originalClogBuf) {
std::clog.rdbuf(g_originalClogBuf);
}
}
}
void redirectNormalOutput(bool enabled)
{
normalOutputEnabled = enabled;
if (enabled) {
if (g_originalCoutBuf == NULL) {
g_originalCoutBuf = std::cout.rdbuf();
g_normalTeeBuf.init(g_originalCoutBuf, LOGNORMAL);
}
std::cout.rdbuf(&g_normalTeeBuf);
} else {
if (g_originalCoutBuf) {
std::cout.rdbuf(g_originalCoutBuf);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////脚本控制
void execute_bash(string fun,int process_num,string type)
{
// 为 char 数组分配足够的空间
char p_num_str[20];
// 使用 sprintf 转换
std::sprintf(p_num_str, "%d", process_num);
const char* script = std::string(FRONT_PATH + "/bin/set_process.sh").c_str();//使用setsid防止端口占用
const char* param1 = fun.c_str();
const char* param2 = p_num_str;
const char* param3 = type.c_str();
// 构造完整的命令
char command[256];
snprintf(command, sizeof(command), "%s %s %s %s &", script, param1, param2, param3);
std::cout << "command:" << command <<std::endl;
// 执行命令
system(command);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////打印台账更新
void print_monitor(const ledger_monitor& mon) {
auto safe = [](const std::string& s) { return s.empty() ? "N/A" : s; };
std::cout << "Monitor ID: " << safe(mon.monitor_id) << "\n";
std::cout << "Terminal ID: " << safe(mon.terminal_id) << "\n";
std::cout << "Monitor Name: " << safe(mon.monitor_name) << "\n";
std::cout << "Logical Device Sequence: " << safe(mon.logical_device_seq) << "\n";
std::cout << "Voltage Level: " << safe(mon.voltage_level) << "\n";
std::cout << "Terminal Connect: " << safe(mon.terminal_connect) << "\n";
std::cout << "Timestamp: " << safe(mon.timestamp) << "\n";
std::cout << "Status: " << safe(mon.status) << "\n";
std::cout << "CT1: " << mon.CT1 << "\n";
std::cout << "CT2: " << mon.CT2 << "\n";
std::cout << "PT1: " << mon.PT1 << "\n";
std::cout << "PT2: " << mon.PT2 << "\n";
}
void print_terminal(const terminal_dev& tmnl) {
auto safe = [](const std::string& s) { return s.empty() ? "N/A" : s; };
std::cout << "GUID: " << safe(tmnl.guid) << "\n";
std::cout << "Terminal ID: " << safe(tmnl.terminal_id) << "\n";
std::cout << "Terminal Code: " << safe(tmnl.terminal_name)<< "\n";
std::cout << "Organization Name: "<< safe(tmnl.org_name) << "\n";
std::cout << "Maintenance Name: " << safe(tmnl.maint_name) << "\n";
std::cout << "Station Name: " << safe(tmnl.station_name) << "\n";
std::cout << "Factory Name: " << safe(tmnl.tmnl_factory) << "\n";
std::cout << "Terminal Status: " << safe(tmnl.tmnl_status) << "\n";
std::cout << "Device Type: " << safe(tmnl.dev_type) << "\n";
std::cout << "Device Key: " << safe(tmnl.dev_key) << "\n";
std::cout << "Device Series: " << safe(tmnl.dev_series) << "\n";
std::cout << "Address: " << safe(tmnl.addr_str) << "\n";
std::cout << "Port: " << safe(tmnl.port) << "\n";
std::cout << "Timestamp: " << safe(tmnl.timestamp) << "\n";
std::cout << "mac: " << safe(tmnl.mac) << "\n";
for (size_t i = 0; i < 10 && !tmnl.line[i].monitor_id.empty(); ++i) {
std::cout << " Monitor " << (i + 1) << ":\n";
print_monitor(tmnl.line[i]);
}
}
void print_trigger_update_xml(const trigger_update_xml_t& trigger_update) {
std::cout << "Work Updates Count: " << trigger_update.work_updates.size() << "\n";
std::cout << "New Updates Count: " << trigger_update.new_updates.size() << "\n";
std::cout << "Delete Updates Count: " << trigger_update.delete_updates.size() << "\n";
std::cout << "Modify Updates Count: " << trigger_update.modify_updates.size() << "\n\n";
std::cout << "Work Updates:\n";
for (size_t i = 0; i < trigger_update.work_updates.size(); ++i) {
std::cout << "Work Update " << (i + 1) << ":\n";
print_terminal(trigger_update.work_updates[i]);
}
std::cout << "\nNew Updates:\n";
for (size_t i = 0; i < trigger_update.new_updates.size(); ++i) {
std::cout << "New Update " << (i + 1) << ":\n";
print_terminal(trigger_update.new_updates[i]);
}
std::cout << "\nDelete Updates:\n";
for (size_t i = 0; i < trigger_update.delete_updates.size(); ++i) {
std::cout << "Delete Update " << (i + 1) << ":\n";
print_terminal(trigger_update.delete_updates[i]);
}
std::cout << "\nModify Updates:\n";
for (size_t i = 0; i < trigger_update.modify_updates.size(); ++i) {
std::cout << "Modify Update " << (i + 1) << ":\n";
print_terminal(trigger_update.modify_updates[i]);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////解析模板文件
//解析映射文件
bool ParseXMLConfig2(int xml_flag, XmlConfig *cfg, std::list<CTopic*> *ctopiclist, const std::string& path)
{
// 注释同原代码
const std::string strPhasic[4] = { "A", "B", "C", "T" };
const std::string strLine[4] = { "AB", "BC", "CA", "T" };
int nStart = 1, nEnd = 1;
std::string strValueTemp;
tinyxml2::XMLDocument doc;
std::string xmlFile;
if (path == "not define") {
xmlFile = FRONT_PATH + "/etc/" + DEFAULT_CONFIG_FN; //默认映射文件
std::cout << "[调试] 加载XML路径: " << xmlFile << std::endl;
} else {
xmlFile = FRONT_PATH + "/dat/" + path + ".xml";
std::cout << "[调试] 加载XML路径: " << xmlFile << std::endl;
}
auto result = doc.LoadFile(xmlFile.c_str());
if (result != tinyxml2::XML_SUCCESS) {
std::cout << "[错误] 无法打开XML文件: " << xmlFile << std::endl;
return false;
}
tinyxml2::XMLElement* root = doc.RootElement();
if (!root) {
std::cout << "[错误] XML无根节点" << std::endl;
return false;
}
for (tinyxml2::XMLElement* topicElem = root->FirstChildElement(); topicElem; topicElem = topicElem->NextSiblingElement()) {
std::string strTag = topicElem->Name();
if (strTag == "Topic") {
CTopic* topic = new CTopic();
topic->strTopic = topicElem->Attribute("name") ? topicElem->Attribute("name") : "";
ctopiclist->push_back(topic);
std::cout << "[调试] 解析Topic节点: " << topic->strTopic << std::endl;
// HISDATA、RTDATA
if (topic->strTopic == "HISDATA" || topic->strTopic == "RTDATA") {
for (tinyxml2::XMLElement* dataTypeElem = topicElem->FirstChildElement(); dataTypeElem; dataTypeElem = dataTypeElem->NextSiblingElement()) {
if (std::string(dataTypeElem->Name()) == "DataType") {
CDataType* dt = new CDataType();
dt->iDataType = dataTypeElem->IntAttribute("value", 0);
dt->type = dataTypeElem->Attribute("type") ? dataTypeElem->Attribute("type") : "";
dt->BaseFlag0 = 0;
dt->BaseFlag1 = 0;
topic->DataTypeList.push_back(dt);
// 监测点
for (tinyxml2::XMLElement* monitorElem = dataTypeElem->FirstChildElement(); monitorElem; monitorElem = monitorElem->NextSiblingElement()) {
if (std::string(monitorElem->Name()) == "Monitor") {
CMonitor* mt = new CMonitor();
mt->strMonitor = monitorElem->Attribute("name") ? monitorElem->Attribute("name") : "";
mt->type = monitorElem->Attribute("type") ? monitorElem->Attribute("type") : "";
dt->MonitorList.push_back(mt);
// 数据项
for (tinyxml2::XMLElement* itemElem = monitorElem->FirstChildElement(); itemElem; itemElem = itemElem->NextSiblingElement()) {
if (std::string(itemElem->Name()) == "Item") {
CItem* it = new CItem();
it->strItemName = itemElem->Attribute("name") ? itemElem->Attribute("name") : "";
it->type = itemElem->Attribute("type") ? itemElem->Attribute("type") : "";
if (it->strItemName == "FLAG")
it->strItemValue = itemElem->Attribute("value") ? itemElem->Attribute("value") : "";
mt->ItemList.push_back(it);
// Sequence
for (tinyxml2::XMLElement* seqElem = itemElem->FirstChildElement(); seqElem; seqElem = seqElem->NextSiblingElement()) {
if (std::string(seqElem->Name()) == "Sequence") {
std::string strPhase = seqElem->Attribute("value") ? seqElem->Attribute("value") : "";
// 读取ABC三相
if ((!xml_flag || it->strItemName != "V") && strPhase == "7") {
for (int n = 0; n < 3; ++n) {
CSequence* sq = new CSequence();
sq->strSValue = strPhase;
sq->type = seqElem->Attribute("type") ? seqElem->Attribute("type") : "";
sq->strSeq = strPhasic[n];
it->SequenceList.push_back(sq);
for (tinyxml2::XMLElement* valueElem = seqElem->FirstChildElement(); valueElem; valueElem = valueElem->NextSiblingElement()) {
if (std::string(valueElem->Name()) == "Value") {
std::string strDVName = valueElem->Attribute("name") ? valueElem->Attribute("name") : "";
std::string strDAName = valueElem->Attribute("DA") ? valueElem->Attribute("DA") : "";
// l_phs* → phsAB
if (strDAName.find("l_phs*") != std::string::npos) {
strDAName.replace(strDAName.find("l_phs"), 5, "phs");
size_t starPos = strDAName.find("*");
if (starPos != std::string::npos)
strDAName.replace(starPos, 1, strLine[n]);
} else if (strDAName.find("phs*") != std::string::npos) {
size_t starPos = strDAName.find("*");
if (starPos != std::string::npos)
strDAName.replace(starPos, 1, sq->strSeq);
}
// 谐波数据
if (strDVName.find("%") != std::string::npos && strDAName.find("%-") != std::string::npos) {
size_t firstP = strDVName.find('%'), lastP = strDVName.rfind('%');
if (firstP != std::string::npos && lastP != std::string::npos && firstP != lastP) {
std::string harmRange = strDVName.substr(firstP + 1, lastP - firstP - 1); // 0,49
size_t comma = harmRange.find(',');
if (comma != std::string::npos) {
nStart = std::stoi(harmRange.substr(0, comma));
nEnd = std::stoi(harmRange.substr(comma + 1));
strValueTemp = "%" + harmRange + "%";
}
}
// 取DA的范围
size_t lb = strDAName.find('['), rb = strDAName.find(']');
int strDAoffset = 0;
if (lb != std::string::npos && rb != std::string::npos && lb < rb) {
std::string sub = strDAName.substr(lb + 1, rb - lb - 1);
size_t dash = sub.find('-');
if (dash != std::string::npos)
strDAoffset = std::stoi(sub.substr(dash + 1));
}
int offset = valueElem->IntAttribute("Offset", 0);
for (int i = nStart; i <= nEnd; ++i) {
std::string strDVNameTemp = strDVName;
std::string strDANameTemp = strDAName;
CDataValue* dv1 = new CDataValue();
dv1->type = valueElem->Attribute("type") ? valueElem->Attribute("type") : "";
if (valueElem->Attribute("Coefficient")) {
dv1->strCoefficient = valueElem->Attribute("Coefficient");
dv1->fCoefficient = valueElem->FloatAttribute("Coefficient", 1.0f);
}
dv1->strOffset = valueElem->Attribute("Offset") ? valueElem->Attribute("Offset") : "";
dv1->iOffset = offset;
dv1->DO = valueElem->Attribute("DO") ? valueElem->Attribute("DO") : "";
size_t tmpPos = strDVNameTemp.find(strValueTemp);
if (tmpPos != std::string::npos)
strDVNameTemp.replace(tmpPos, strValueTemp.size(), std::to_string(i + offset));
size_t subPos = strDANameTemp.find('[');
size_t subPos2 = strDANameTemp.find(']');
if (subPos != std::string::npos && subPos2 != std::string::npos)
strDANameTemp.replace(subPos + 1, subPos2 - subPos - 1, std::to_string(i - strDAoffset));
dv1->strName = strDVNameTemp;
dv1->DA = strDANameTemp;
// BaseFlag, LimitUp, LimitDown
if (valueElem->Attribute("BaseFlag")) {
dv1->BaseFlag = valueElem->Attribute("BaseFlag");
if (dv1->BaseFlag == "1") dt->BaseFlag1++; else dt->BaseFlag0++;
} else {
dt->BaseFlag0++;
dv1->BaseFlag = "0";
}
dv1->LimitUp = valueElem->Attribute("LimitUp") ? valueElem->Attribute("LimitUp") : "not define";
dv1->LimitDown = valueElem->Attribute("LimitDown") ? valueElem->Attribute("LimitDown") : "not define";
if (!dv1->DO.empty() && !dv1->DA.empty())
dv1->strFullName = dv1->DO + "$" + dv1->DA;
else
dv1->strFullName = "not define";
sq->DataValueList.push_back(dv1);
// 调试打印谐波展开
std::cout << "[调试] 谐波展开DataValue: " << dv1->strName << " DA=" << dv1->DA << std::endl;
}
} else {
// 非谐波数据
CDataValue* dv2 = new CDataValue();
dv2->strName = strDVName;
dv2->type = valueElem->Attribute("type") ? valueElem->Attribute("type") : "";
if (valueElem->Attribute("Coefficient")) {
dv2->strCoefficient = valueElem->Attribute("Coefficient");
dv2->fCoefficient = valueElem->FloatAttribute("Coefficient", 1.0f);
}
dv2->DO = valueElem->Attribute("DO") ? valueElem->Attribute("DO") : "";
dv2->DA = strDAName;
if (valueElem->Attribute("BaseFlag")) {
dv2->BaseFlag = valueElem->Attribute("BaseFlag");
if (dv2->BaseFlag == "1") dt->BaseFlag1++; else dt->BaseFlag0++;
} else {
dt->BaseFlag0++;
dv2->BaseFlag = "0";
}
dv2->LimitUp = valueElem->Attribute("LimitUp") ? valueElem->Attribute("LimitUp") : "not define";
dv2->LimitDown = valueElem->Attribute("LimitDown") ? valueElem->Attribute("LimitDown") : "not define";
if (valueElem->Attribute("PltFlag") && std::string(valueElem->Attribute("PltFlag")) == "True")
dv2->bPlt = true;
else
dv2->bPlt = false;
if (!dv2->DO.empty() && !dv2->DA.empty())
dv2->strFullName = dv2->DO + "$" + dv2->DA;
else
dv2->strFullName = "not define";
sq->DataValueList.push_back(dv2);
std::cout << "[调试] 普通DataValue: " << dv2->strName << " DA=" << dv2->DA << std::endl;
}
}
}
}
}
// T相
if (strPhase == "8") {
CSequence* sq = new CSequence();
sq->strSValue = strPhase;
sq->type = seqElem->Attribute("type") ? seqElem->Attribute("type") : "";
sq->strSeq = strPhasic[3];
it->SequenceList.push_back(sq);
for (tinyxml2::XMLElement* valueElem = seqElem->FirstChildElement(); valueElem; valueElem = valueElem->NextSiblingElement()) {
if (std::string(valueElem->Name()) == "Value") {
CDataValue* dv2 = new CDataValue();
dv2->strName = valueElem->Attribute("name") ? valueElem->Attribute("name") : "";
dv2->type = valueElem->Attribute("type") ? valueElem->Attribute("type") : "";
if (valueElem->Attribute("Coefficient")) {
dv2->strCoefficient = valueElem->Attribute("Coefficient");
dv2->fCoefficient = valueElem->FloatAttribute("Coefficient", 1.0f);
}
dv2->DO = valueElem->Attribute("DO") ? valueElem->Attribute("DO") : "";
dv2->DA = valueElem->Attribute("DA") ? valueElem->Attribute("DA") : "";
if (valueElem->Attribute("BaseFlag")) {
dv2->BaseFlag = valueElem->Attribute("BaseFlag");
if (dv2->BaseFlag == "1") dt->BaseFlag1++; else dt->BaseFlag0++;
} else {
dt->BaseFlag0++;
dv2->BaseFlag = "0";
}
dv2->LimitUp = valueElem->Attribute("LimitUp") ? valueElem->Attribute("LimitUp") : "not define";
dv2->LimitDown = valueElem->Attribute("LimitDown") ? valueElem->Attribute("LimitDown") : "not define";
if (!dv2->DO.empty() && !dv2->DA.empty())
dv2->strFullName = dv2->DO + "$" + dv2->DA;
else
dv2->strFullName = "not define";
sq->DataValueList.push_back(dv2);
std::cout << "[调试] T相DataValue: " << dv2->strName << std::endl;
}
}
}
}
}
}
}
}
}
}
}
}
// RTDATASOE
else if (topic->strTopic == "RTDATASOE") {
for (tinyxml2::XMLElement* dataTypeElem = topicElem->FirstChildElement(); dataTypeElem; dataTypeElem = dataTypeElem->NextSiblingElement()) {
if (std::string(dataTypeElem->Name()) == "DataType") {
CDataType* dt = new CDataType();
dt->iDataType = dataTypeElem->IntAttribute("value", 0);
dt->type = dataTypeElem->Attribute("type") ? dataTypeElem->Attribute("type") : "";
topic->DataTypeList.push_back(dt);
for (tinyxml2::XMLElement* soeElem = dataTypeElem->FirstChildElement(); soeElem; soeElem = soeElem->NextSiblingElement()) {
if (std::string(soeElem->Name()) == "SOE") {
CEventData* ed = new CEventData();
ed->triggerFlag = soeElem->Attribute("TriggerFlag") ? soeElem->Attribute("TriggerFlag") : "";
ed->DO = soeElem->Attribute("DO") ? soeElem->Attribute("DO") : "";
ed->DA = soeElem->Attribute("DA") ? soeElem->Attribute("DA") : "";
ed->type = soeElem->Attribute("type") ? soeElem->Attribute("type") : "";
if (!ed->DO.empty() && !ed->DA.empty())
ed->strFullName = ed->DO + "$" + ed->DA;
else
ed->strFullName = "not define";
dt->SOEList.push_back(ed);
std::cout << "[调试] SOE事件: " << ed->strFullName << std::endl;
}
}
}
}
}
// SOEDATA
else if (topic->strTopic == "SOEDATA") {
for (tinyxml2::XMLElement* dataTypeElem = topicElem->FirstChildElement(); dataTypeElem; dataTypeElem = dataTypeElem->NextSiblingElement()) {
if (std::string(dataTypeElem->Name()) == "DataType") {
CEventData* ed = new CEventData();
ed->triggerFlag = dataTypeElem->Attribute("name") ? dataTypeElem->Attribute("name") : "";
ed->DO = dataTypeElem->Attribute("DO") ? dataTypeElem->Attribute("DO") : "";
ed->DA = dataTypeElem->Attribute("DA") ? dataTypeElem->Attribute("DA") : "";
ed->type = dataTypeElem->Attribute("type") ? dataTypeElem->Attribute("type") : "";
if (!ed->DO.empty() && !ed->DA.empty())
ed->strFullName = ed->DO + "$" + ed->DA;
else
ed->strFullName = "not define";
cfg->SOEList.push_back(ed);
std::cout << "[调试] SOEDATA事件: " << ed->strFullName << std::endl;
}
}
}
}
// 其他节点
else if (strTag == "WavePhasic") {
const char* flagAttr = topicElem->Attribute("Flag");
if (flagAttr)
cfg->WavePhasicFlag = flagAttr;
if (!cfg->WavePhasicFlag.empty() && cfg->WavePhasicFlag == "1") {
const char* aAttr = topicElem->Attribute("A");
if (aAttr) cfg->WavePhasicA = aAttr;
const char* bAttr = topicElem->Attribute("B");
if (bAttr) cfg->WavePhasicB = bAttr;
const char* cAttr = topicElem->Attribute("C");
if (cAttr) cfg->WavePhasicC = cAttr;
}
std::cout << "[调试] WavePhasic解析" << std::endl;
} else if (strTag == "UnitOfTime") {
const char* unitAttr = topicElem->Attribute("Unit");
if (unitAttr)
cfg->UnitOfTimeUnit = unitAttr;
std::cout << "[调试] UnitOfTime解析" << std::endl;
} else if (strTag == "ValueOfTime") {
const char* unitAttr = topicElem->Attribute("Unit");
if (unitAttr)
cfg->ValueOfTimeUnit = unitAttr;
std::cout << "[调试] ValueOfTime解析" << std::endl;
} else if (strTag == "ComtradeFile") {
const char* waveTimeFlag = topicElem->Attribute("WaveTimeFlag");
if (waveTimeFlag)
cfg->WaveTimeFlag = waveTimeFlag;
std::cout << "[调试] ComtradeFile解析" << std::endl;
} else if (strTag == "IED") {
const char* nameAttr = topicElem->Attribute("name");
if (nameAttr)
cfg->IEDname = nameAttr;
std::cout << "[调试] IED解析" << std::endl;
} else if (strTag == "LDevice") {
const char* prefixAttr = topicElem->Attribute("Prefix");
if (prefixAttr)
cfg->LDevicePrefix = prefixAttr;
std::cout << "[调试] LDevice解析" << std::endl;
}
}
return true;
}
//清理旧的映射配置
void clearXmlConfigAndTopicList(Xmldata* data) {
// 清空 XmlConfig
data->xmlcfg = XmlConfig(); // 通过重新赋值重置 xmlcfg
// 清空 topicList
std::list<CTopic*>::iterator it;
for (it = data->topicList.begin(); it != data->topicList.end(); ++it) {
delete *it; // 释放内存
}
data->topicList.clear(); // 清空链表
}
//配置映射文件单个
void Set_xml_nodeinfo_one(const std::string& dev_type)
{
bool ret = false;
// 假定 xmlinfo_list 是 std::map<std::string, Xmldata*>
if (xmlinfo_list.count(dev_type) && xmlinfo_list[dev_type] != nullptr) { // 已存在这个类型的节点
if (xmlinfo_list[dev_type]->updataflag == true) { // 需要更新
// 删除这个点的 xmlcfg 和 topicList
clearXmlConfigAndTopicList(xmlinfo_list[dev_type]);
ret = ParseXMLConfig2(
0,
&(xmlinfo_list[dev_type]->xmlcfg),
&(xmlinfo_list[dev_type]->topicList),
xmlinfo_list[dev_type]->xmlbase.MODEL_ID
);
if (!ret) {
std::cout << "!!!! this ledger xml config fail!!!!" << std::endl;
}
}
}
else {
std::cout << "xmlinfo_list not contain this devtype" << std::endl;
}
// 处理角形
if (isdelta_flag) {
if (xmlinfo_list2.count(dev_type) && xmlinfo_list2[dev_type] != nullptr) { // 已存在
if (xmlinfo_list2[dev_type]->updataflag == true) { // 需要更新
// 删除这个点的 xmlcfg 和 topicList
clearXmlConfigAndTopicList(xmlinfo_list2[dev_type]);
ret = ParseXMLConfig2(
1,
&(xmlinfo_list2[dev_type]->xmlcfg),
&(xmlinfo_list2[dev_type]->topicList),
xmlinfo_list2[dev_type]->xmlbase.MODEL_ID
);
if (!ret) {
std::cout << "!!!! this ledger xml config fail!!!!" << std::endl;
}
}
}
else {
std::cout << "xmlinfo_list2 not contain this devtype" << std::endl;
}
}
}
//配置映射文件全部
void Set_xml_nodeinfo()
{
// 配置无对应 xml 文件时的默认解析配置
if (!DEFAULT_CONFIG_FN.empty()) { //可改为在配置中读取默认映射文件名
std::string path = "not define";
ParseXMLConfig2(0, &xmlcfg, &topicList, path); // 调用 ParseXMLConfig() 解析 JiangSu_Config.xml 配置文件
if (isdelta_flag) {
ParseXMLConfig2(1, &xmlcfg2, &topicList2, path); // 角型接线
}
}
if (!xmlinfo_list.empty()) {
std::cout << "!!!!!!!!!! xmlinfo_list.size() != 0 !!!!!!!!!!!" << std::endl;
for (auto& pair : xmlinfo_list) {
const std::string& key = pair.first;
Xmldata* value = pair.second;
if (value && value->updataflag) {
ParseXMLConfig2(0, &(value->xmlcfg), &(value->topicList), value->xmlbase.MODEL_ID);
}
}
if (isdelta_flag) {
for (auto& pair : xmlinfo_list2) {
const std::string& key = pair.first;
Xmldata* value = pair.second;
if (value && value->updataflag) {
ParseXMLConfig2(1, &(value->xmlcfg), &(value->topicList), value->xmlbase.MODEL_ID);
}
}
}
} else {
std::cout << "!!!!!!!!!! xmlinfo_list.size() == 0 !!!!!!!!!!!" << std::endl;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////录播波形匹配
// 工具函数:解析 "dd/MM/yyyy,hh:mm:ss.zzz" 格式的字符串为时间戳(毫秒)
static long long parse_datetime_to_epoch_ms(const std::string& dt_str) {
std::tm tm = {};
char dummy;
int ms = 0;
std::istringstream ss(dt_str);
ss >> std::get_time(&tm, "%d/%m/%Y,%H:%M:%S") >> dummy >> ms;
if (ss.fail()) return 0;
std::time_t time_sec = std::mktime(&tm);
if (time_sec < 0) return 0;
return static_cast<long long>(time_sec) * 1000 + ms;
}
// 主函数:从 .cfg 文件中提取起始和触发时间戳(毫秒)
bool extract_timestamp_from_cfg_file(const std::string& cfg_path, long long& start_tm, long long& trig_tm) {
struct stat st;
if (stat(cfg_path.c_str(), &st) != 0) {
std::cerr << "File not found: " << cfg_path << "\n";
return false;
}
std::ifstream infile(cfg_path);
if (!infile) {
std::cerr << "Cannot open file: " << cfg_path << "\n";
return false;
}
std::string line, prev_line, current_line;
while (std::getline(infile, line)) {
// 去除前后空白
line.erase(line.find_last_not_of(" \t\r\n") + 1);
line.erase(0, line.find_first_not_of(" \t\r\n"));
std::string upper = line;
std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
if (upper == "ASCII" || upper == "BINARY") break;
prev_line = current_line;
current_line = line;
}
if (prev_line.length() > 3) prev_line = prev_line.substr(0, prev_line.length() - 3);
if (current_line.length() > 3) current_line = current_line.substr(0, current_line.length() - 3);
start_tm = parse_datetime_to_epoch_ms(prev_line);
trig_tm = parse_datetime_to_epoch_ms(current_line);
return start_tm > 0 && trig_tm > 0;
}
bool compare_qvvr_and_file(const std::string& cfg_path, const std::vector<qvvr_data>& data_list,qvvr_data& matched_data) {
long long start_tm = 0;
long long trig_tm = 0;
// 提取 .cfg 文件中的时间戳
if (!extract_timestamp_from_cfg_file(cfg_path, start_tm, trig_tm)) {
std::cerr << "Failed to extract timestamp from cfg file: " << cfg_path << "\n";
return false;
}
//打印提取到的时间戳
std::cout << "[调试] 提取到的起始时间戳: " << start_tm << ", 触发时间戳: " << trig_tm << "\n";
// 遍历所有暂态事件,查找与 trig_tm 匹配的
for (const auto& data : data_list) {
long long diff = static_cast<long long>(data.QVVR_time) - trig_tm;
if (std::abs(diff) <= 1) {
matched_data = data; // 返回匹配到的事件
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////数据转换函数
// DataArrayItem to_json
void to_json(nlohmann::json& j, const DataArrayItem& d) {
j = nlohmann::json{
{"dataAttr", d.DataAttr},
{"dataTimeSec", d.DataTimeSec},
{"dataTimeUSec", d.DataTimeUSec},
{"dataTag", d.DataTag},
{"data", d.Data}
};
}
// MsgObj to_json
void to_json(nlohmann::json& j, const MsgObj& m) {
j = nlohmann::json{
{"clDid", m.Cldid},
{"dataType", m.DataType},
{"dataAttr", m.DataAttr},
{"dsNameIdx", m.DsNameIdx},
{"dataArray", m.DataArray}
};
}
// FullObj to_json
void to_json(nlohmann::json& j, const FullObj& f) {
j = nlohmann::json{
{"id", f.mac},
{"mid", f.Mid},
{"did", f.Did},
{"pri", f.Pri},
{"type", f.Type},
{"msg", f.Msg}
};
}
std::string generate_json(
const std::string mac,
int Mid, //需应答的报文订阅者收到后需以此ID应答无需应答填入“-1”
int Did, //设备唯一标识Ldid填入0代表Ndid。
int Pri, //报文处理的优先级
int Type, //消息类型
int Cldid, //逻辑子设备ID0-逻辑设备本身,无填-1
int DataType, //数据类型0-表示以数据集方式上送
int DataAttr, //数据属性无“0”、实时“1”、统计“2”等。
int DsNameIdx, //数据集序号(以数据集方式上送),无填-1
const std::vector<DataArrayItem>& dataArray //数据数组。
) {
FullObj fobj;
fobj.mac = mac;
fobj.Mid = Mid;
fobj.Did = Did;
fobj.Pri = Pri;
fobj.Type = Type;
fobj.Msg.Cldid = Cldid;
fobj.Msg.DataType = DataType;
fobj.Msg.DataAttr = DataAttr;
fobj.Msg.DsNameIdx = DsNameIdx;
fobj.Msg.DataArray = dataArray;
nlohmann::json j = fobj;
return j.dump(); // 输出标准 json 字符串
}
void upload_data_test(){
std::vector<DataArrayItem> arr;
arr.push_back({1, 1725477660, 0, 1, "xxxx"}); //数据属性 -1-无, 0-“Rt”,1-“Max”,2-“Min”,3-“Avg”,4-“Cp95”
//数据时标相对1970年的秒无效填入“-1”
//数据时标,微秒钟,无效填入“-1”
//数据标识1-标识数据异常
//数据序列数据集上送时将二进制数据流转换成Base64字符串其他数据为object
arr.push_back({2, 1691741340, 0, 1, "yyyy"});
std::string js = generate_json(
"123",-1, 1, 1, 4866, 1, 0, 2, 1, arr
);
std::cout << js << std::endl;
queue_data_t data;
data.monitor_no = 1;
data.strTopic = TOPIC_ALARM;
data.strText = js;
data.mp_id = "test";
data.tag = G_ROCKETMQ_TAG_TEST;
data.key = G_ROCKETMQ_KEY_TEST;
std::lock_guard<std::mutex> lock(queue_data_list_mutex);
queue_data_list.push_back(data);
}
//////////////////////////////////////////////////////////////////////////////////////////////////台账赋值给通信
std::vector<DeviceInfo> GenerateDeviceInfoFromLedger(const std::vector<terminal_dev>& terminal_devlist) {
std::lock_guard<std::mutex> lock(ledgermtx);
std::vector<DeviceInfo> devices;
for (const auto& terminal : terminal_devlist) {
DeviceInfo device;
device.device_id = terminal.terminal_id;
device.name = terminal.terminal_name;
device.model = terminal.dev_type;
device.mac = terminal.addr_str;
device.status = 1;
for (const auto& monitor : terminal.line) {
PointInfo point;
point.point_id = monitor.monitor_id;
point.name = monitor.monitor_name;
point.device_id = terminal.terminal_id;
point.PT1 = monitor.PT1;
point.PT2 = monitor.PT2;
point.CT1 = monitor.CT1;
point.CT2 = monitor.CT2;
point.strScale = monitor.voltage_level;
point.nCpuNo = std::stoi(monitor.logical_device_seq);
point.nPTType = std::stoi(monitor.terminal_connect);
device.points.push_back(point);
}
devices.push_back(device);
}
return devices;
}
////////////////////////////////////////////////////////////////////////////////////////////////////录波文件通知
bool assign_qvvr_file_list(const std::string& id,
ushort nCpuNo,
const std::vector<std::string>& file_list_raw) {
// ★新增:台账加锁(若上层已有锁,可移除本行)
std::lock_guard<std::mutex> lk(ledgermtx);
std::vector<std::string> file_names;
// 1. 提取文件名部分
for (const auto& full_path_raw : file_list_raw) {
std::string full_path = sanitize(full_path_raw); // ★修改:清洗入参路径
size_t pos = full_path.find_last_of("/\\");
std::string name = (pos != std::string::npos && pos + 1 < full_path.size())
? full_path.substr(pos + 1)
: full_path;
name = sanitize(name); // ★修改:清洗提取的文件名
file_names.push_back(name);
}
// ★可选:去重(如果 file_list_raw 里可能有重复)
std::sort(file_names.begin(), file_names.end());
file_names.erase(std::unique(file_names.begin(), file_names.end()), file_names.end());
// 2. 遍历终端
for (auto& dev : terminal_devlist) {
if (dev.terminal_id == id) { // 根据终端id匹配终端
for (auto& monitor : dev.line) {
try {
// ★修改:清洗 logical_device_seq 再进行转换
std::string seq_str = sanitize(monitor.logical_device_seq);
ushort monitor_seq = static_cast<ushort>(std::stoi(seq_str));
if (monitor_seq == nCpuNo) { // 根据监测点编号匹配监测点
// 构造 qvvr_file
qvvr_file qfile;
qfile.file_name.assign(file_names.begin(), file_names.end()); // 终端文件列表(已清洗)
qfile.is_download = false;
qfile.is_pair = false;
qfile.file_time_count = 0;
qfile.used_status = true;
// 添加到唯一的 qvvrevent
monitor.qvvrevent.qvvrfile.push_back(std::move(qfile)); // 记录暂态文件组
return true;
}
} catch (...) {
continue;
}
}
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////下载成功通知
//提取下载路径的文件名
std::string extract_filename1(const std::string& path) {
size_t pos = path.find_last_of("/\\");
return (pos != std::string::npos) ? path.substr(pos + 1) : path;
}
// ★新增dirname返回“目录/”(保留末尾斜杠;若没有目录则返回空串)
static inline std::string dirname_with_slash(const std::string& path) {
size_t pos = path.find_last_of("/\\");
if (pos == std::string::npos) return std::string{};
return path.substr(0, pos + 1);
}
//发送匹配的所有录波文件
bool SendAllQvvrFiles(qvvr_file& qfile, std::string& out_wavepath) {
std::vector<std::string> wavepaths;
std::string first_wavepath;
bool send_success = true;
for (const auto& file_localpath : qfile.file_download) {
std::string file_cloudpath = "comtrade/" + dirname_with_slash(file_localpath);
std::string wavepath_result;
// 发送本地文件到远端,返回 wavepath
SOEFileWeb(const_cast<std::string&>(file_localpath), file_cloudpath, wavepath_result);
// 如果失败,重发一次
if (wavepath_result.empty()) {
std::cerr << "[SOEFileWeb] Warning: first send failed for file: " << file_localpath << ", retrying...\n";
SOEFileWeb(const_cast<std::string&>(file_localpath), file_cloudpath, wavepath_result);
}
if (wavepath_result.empty()) {
send_success = false;
std::cerr << "[SOEFileWeb] Failed: wavepath empty for file: " << file_localpath << "\n";
break;
}
if (wavepaths.empty()) {
first_wavepath = wavepath_result;
} else if (wavepath_result != first_wavepath) {
send_success = false;
std::cerr << "[SOEFileWeb] Mismatch wavepath: " << wavepath_result
<< " vs " << first_wavepath << "\n";
break;
}
wavepaths.push_back(wavepath_result);
}
// 检查数量是否一致
if (!send_success || wavepaths.size() != qfile.file_download.size()) {
std::cerr << "[SOEFileWeb] Failed to send all qvvr files. "
<< "Sent: " << wavepaths.size()
<< ", Expected: " << qfile.file_download.size() << "\n";
return false;
}
out_wavepath = first_wavepath; // 返回统一的 wavepath
std::cout << "[SOEFileWeb] Success: all files sent for qfile. Wavepath = "
<< first_wavepath << "\n";
return true;
}
//文件下载结束接口
bool update_qvvr_file_download(const std::string& filename_with_mac_in, const std::string& terminal_id) {
// ★ 先把原始入参清洗
std::string filename_with_mac = sanitize(filename_with_mac_in);
std::cout << "[update_qvvr_file_download] raw=" << filename_with_mac_in
<< " | sanitized=" << filename_with_mac
<< " | terminal_id=" << terminal_id << std::endl;
//台账加锁
std::unique_lock<std::mutex> lock(ledgermtx);
// 去除 mac 路径前缀,仅保留文件名
std::string filename = sanitize(extract_filename1(filename_with_mac));
// 提取逻辑序号(如 PQ_PQLD1 → 1
size_t under_pos1 = filename.find('_');
if (under_pos1 == std::string::npos) {
std::cout << "[DEBUG] 未找到 '_'filename=" << filename
<< "under_pos=npos返回 false\n";
return false;
}
size_t under_pos2 = filename.find('_', under_pos1 + 1);
std::string type_part = (under_pos2 == std::string::npos)
? filename.substr(0, under_pos1) // 兜底:只有一个下划线
: filename.substr(0, under_pos2); // 取到第二个下划线(得到 PQ_PQLD1
std::cout << "[DEBUG] type_part=" << type_part
<< " (under_pos1=" << under_pos1
<< ", under_pos2=" << under_pos2 << ")\n";
size_t num_start = type_part.find_last_not_of("0123456789");
if (num_start == std::string::npos || num_start + 1 >= type_part.size()) {
std::cout << "[DEBUG] 数字起始位置异常num_start=" << num_start
<< "type_part.size()=" << type_part.size()
<< "type_part=\"" << type_part << "\",返回 false\n";
return false;
}
std::string seq_str = type_part.substr(num_start + 1);
unsigned short logical_seq = static_cast<unsigned short>(std::stoul(seq_str));
std::cout << "[DEBUG] 解析到 logical_seq=" << logical_seq << "\n";
//找终端
for (auto& dev : terminal_devlist) {
if (dev.terminal_id != terminal_id) {
std::cout << "[cmp-terminal-id][NOT-MATCH]"
<< " dev_id=" << dev.terminal_id
<< " target_id=" << terminal_id
<< std::endl;
continue;
}
//找监测点
for (auto& monitor : dev.line) {
try {
// 将监测点台账中的 logical_device_seq 转换为数字进行匹配
ushort monitor_seq = static_cast<ushort>(std::stoi(monitor.logical_device_seq));
if (monitor_seq != logical_seq) {
// ★新增:不匹配时对比打印
std::cout << "[cmp-monitor-seq][NOT-MATCH]"
<< " monitor_id=" << monitor.monitor_id
// ★ 这里之前打印的是 seq_str容易误导。改为 ledger 的原始串:
<< " seq_in_ledger_raw=\"" << monitor.logical_device_seq << "\""
<< " parsed=" << monitor_seq
<< " target_seq=" << logical_seq
<< std::endl;
continue;
}
else{
std::cout << "[cmp-monitor-seq][MATCH!!!]"
<< " monitor_id=" << monitor.monitor_id
<< " seq_in_ledger_raw=\"" << monitor.logical_device_seq << "\""
<< " parsed=" << monitor_seq
<< " target_seq=" << logical_seq
<< std::endl;
}
} catch (const std::exception& e) {
// ★新增:解析失败详细原因
std::cout << "[cmp-monitor-seq][PARSE-FAIL]"
<< " monitor_id=" << monitor.monitor_id
<< " seq_in_ledger=\"" << monitor.logical_device_seq << "\""
<< " err=" << e.what()
<< std::endl;
continue; // logical_device_seq 非法,跳过
}catch (...) {
// ★新增:未知异常
std::cout << "[cmp-monitor-seq][PARSE-FAIL]"
<< " monitor_id=" << monitor.monitor_id
<< " seq_in_ledger=\"" << monitor.logical_device_seq << "\""
<< " err=<unknown>"
<< std::endl;
continue; // logical_device_seq 非法,跳过
}
// 匹配监测点下 qvvrfile 中的 file_name
for (size_t i = 0; i < monitor.qvvrevent.qvvrfile.size(); ++i) {
auto& qfile = monitor.qvvrevent.qvvrfile[i];
// file_name 中是文件名,需与提取的 filename 比较
auto it = std::find(qfile.file_name.begin(), qfile.file_name.end(), filename);
//找到匹配文件名
if (it != qfile.file_name.end()) {
// 添加到 file_download记录完整路径避免重复
if (std::find(qfile.file_download.begin(), qfile.file_download.end(), filename_with_mac) == qfile.file_download.end()) {
std::cout << "[update_qvvr_file_download] Adding downloaded file: " << filename_with_mac << std::endl;
qfile.file_download.push_back(filename_with_mac);
}
qfile.file_time_count = 0; // 任一录波文件下载后,计时归零
// file_download 中是完整路径,需提取文件名后与 file_name 做集合比较
std::set<std::string> s_name(qfile.file_name.begin(), qfile.file_name.end());
std::set<std::string> s_down;
for (const auto& path : qfile.file_download) {
s_down.insert(sanitize(extract_filename1(path))); // 提取每个路径中的文件名
}
//打印s_name和s_down内容
std::cout << "[update_qvvr_file_download] Expected files (file_name): ";
for (const auto& fn : s_name) std::cout << fn << " ";
std::cout << std::endl;
std::cout << "[update_qvvr_file_download] Downloaded files (file_download): ";
for (const auto& fn : s_down) std::cout << fn << " ";
std::cout << std::endl;
// 检查 file_download 是否与 file_name 完全一致(集合相同)
if (s_name == s_down) {
std::cout << "[update_qvvr_file_download] All files downloaded for qfile in logical_seq=" << logical_seq << std::endl;
qfile.is_download = true; // 全部下载完成
// 找到其中的 .cfg 文件进行匹配
for (const auto& fpath : qfile.file_download) {
std::string fname = extract_filename1(fpath);
if (fname.size() >= 4 && fname.substr(fname.size() - 4) == ".cfg") {
// 提取文件时标和监测点事件的时标匹配
qvvr_data matched;
if (compare_qvvr_and_file(fpath, monitor.qvvrevent.qvvrdata,matched)) {
qfile.is_pair = true; // 文件与事件匹配成功
// ★新增:上传前拷贝“将要上传的文件列表”,避免锁外用容器引用
std::vector<std::string> files_to_send(qfile.file_download.begin(),
qfile.file_download.end());
// ★新增:构造一个临时 qvvr_file仅用于上传不改动原结构
qvvr_file tmp_send;
tmp_send.file_download.assign(files_to_send.begin(), files_to_send.end());
// 发送所有文件(已下载完成)
std::string wavepath;
// ★在解锁前,备份“签名”,用于回锁后定位同一个 qfile
std::set<std::string> sig_names(qfile.file_name.begin(), qfile.file_name.end());
std::set<std::string> sig_downs(qfile.file_download.begin(), qfile.file_download.end());
// ★修改:把上传与上送 JSON 放到“解锁区间”
lock.unlock(); // ★新增:提前解锁
if (SendAllQvvrFiles(qfile, wavepath)) {
//文件发送成功后更新事件
transfer_json_qvvr_data(terminal_id,
logical_seq,
matched.QVVR_Amg,
matched.QVVR_PerTime,
matched.QVVR_time,
matched.QVVR_type,
matched.phase,
wavepath);
// ★新增:上传成功后再加锁,准备修改台账
lock.lock();
// 删除上传成功的文件
for (const auto& uploaded_file : qfile.file_download) {
if (std::remove(uploaded_file.c_str()) != 0) {
std::cerr << "[Cleanup] Failed to delete file: " << uploaded_file << "\n";
} else {
std::cout << "[Cleanup] Deleted uploaded file: " << uploaded_file << "\n";
}
}
// ★替换原来的 i<size 判断为:按签名查找当前容器里的那一条
auto it_qf = std::find_if(monitor.qvvrevent.qvvrfile.begin(),
monitor.qvvrevent.qvvrfile.end(),
[&](const qvvr_file& x){
std::set<std::string> n(x.file_name.begin(), x.file_name.end());
std::set<std::string> d(x.file_download.begin(), x.file_download.end());
return n==sig_names && d==sig_downs;
});
if (it_qf != monitor.qvvrevent.qvvrfile.end()) {
monitor.qvvrevent.qvvrfile.erase(it_qf); // ✔ 删到同一条
} else {
std::cerr << "[Cleanup] qvvrfile changed; target group not found, skip erase\n";
}
//清除暂态事件
auto it = std::find_if(
monitor.qvvrevent.qvvrdata.begin(),
monitor.qvvrevent.qvvrdata.end(),
[&](const qvvr_data& d) {
return d.QVVR_time == matched.QVVR_time;
});
if (it != monitor.qvvrevent.qvvrdata.end()) {
monitor.qvvrevent.qvvrdata.erase(it);
}
}
else {
lock.lock(); // ★新增:失败时补回锁
std::cerr << "[update_qvvr_file_download] Failed to send qvvr files for logical_seq=" << logical_seq << std::endl;
}
}
else {
std::cout << "[update_qvvr_file_download] No matching qvvr_data found for cfg file: " << fpath << std::endl;
}
break; // 只处理第一个 cfg 文件
}
}
}
else{
std::cout << "qvvr file still imcomplete!!!" << std::endl;
}
lock.unlock();
return true; // 当前文件处理成功
}
}
std::cout << "file name doesnt match any file in this monitor!!!" << std::endl;
}
}
lock.unlock();
return false; // 未匹配到终端ID或逻辑序号对应的监测点
}
////////////////////////////////////////////////////////////////////////////////////////提取mac
std::string normalize_mac(const std::string &mac) {
std::string res;
res.reserve(mac.size());
for (char c : mac) {
if (c != '-' && c != ':' && c != ' ')
res.push_back(c);
}
return res;
}
////////////////////////////////////////////////////////////////////////////////////////目录信息发送接口函数
bool send_file_list(terminal_dev* dev, const std::vector<tag_dir_info>& FileList) {
if (!dev) {
std::cerr << "[send_file_list_locked] dev=nullptr\n";
return false;
}
// 判断 isbusy==1 且 busytype==READING_FILEMENU
if (dev->isbusy != 1 || dev->busytype != static_cast<int>(DeviceState::READING_FILEMENU)) {
std::cerr << "[send_file_list] device not in READING_FILEMENU state." << std::endl;
return false;
}
// 构造 JSON 报文
nlohmann::json j;
j["guid"] = dev->guid;
j["FrontIP"] = FRONT_IP; // 这里填你的前置机 IP
j["Node"] = g_front_seg_index; // 节点号
j["Dev_mac"] = normalize_mac(dev->addr_str); // addr_str 存的是 MAC
// 构造 DirInfo 数组
nlohmann::json dirArray = nlohmann::json::array();
for (const auto &f : FileList) {
nlohmann::json item;
item["Name"] = f.name;
item["Type"] = (f.flag == 0) ? "dir" : "file";
item["Size"] = f.size;
dirArray.push_back(item);
}
// 构造 Detail 部分
nlohmann::json detail;
detail["Type"] = 0x2131; // 读取目录
detail["Msg"] = { {"DirInfo", dirArray} };
detail["Code"] = 200; // 请求成功
// 放到顶层
j["Detail"] = detail;
// 打印调试
std::cout << j.dump(4) << std::endl;
// ---- 入队发送 ----
queue_data_t connect_info;
connect_info.strTopic = Topic_Reply_Topic;
connect_info.strText = j.dump(); // 序列化为字符串
connect_info.tag = Topic_Reply_Tag;
connect_info.key = Topic_Reply_Key;
{
std::lock_guard<std::mutex> lock(queue_data_list_mutex);
queue_data_list.push_back(std::move(connect_info));
}
// 调试打印
std::cout << "[send_reply_to_cloud] queued: " << j.dump() << std::endl;
//发送后清除guid和标志
if (dev->isbusy > 0) {
dev->isbusy--;
}
if(dev->isbusy == 0){
dev->guid.clear();
dev->busytype = 0;
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////检查云前置终端的mq业务超时
int get_type_by_state(int state) {
switch (static_cast<DeviceState>(state)) {
case DeviceState::READING_STATS:
case DeviceState::READING_STATS_TIME:
case DeviceState::READING_REALSTAT:
case DeviceState::READING_FIXEDVALUE:
case DeviceState::READING_FIXEDVALUEDES:
case DeviceState::SET_FIXEDVALUE:
case DeviceState::READING_INTERFIXEDVALUE:
case DeviceState::READING_INTERFIXEDVALUEDES:
case DeviceState::READING_CONTROLWORD:
case DeviceState::SET_INTERFIXEDVALUE:
return 0x2106; //读数据
case DeviceState::READING_FILEMENU:
return 0x2131; //读目录
case DeviceState::READING_EVENTFILE:
case DeviceState::READING_FILEDATA:
return 0x2132; //读文件
default:
return 0; // 没有对应的type
}
}
// 定时检查业务超时
void check_device_busy_timeout()
{
std::lock_guard<std::mutex> lock(ledgermtx);
for (auto &dev : terminal_devlist)
{
if (dev.isbusy != 0) // 有业务在进行
{
dev.busytimecount++;
if (dev.busytype == static_cast<int>(DeviceState::READING_FILEDATA)) //下载文件业务
{
if (dev.busytimecount > 30)
{
std::cout << "[Timeout] Device " << dev.terminal_id
<< " busytype=READING_FILEDATA 超时("
<< dev.busytimecount << "s)" << std::endl;
//发送超时响应
send_reply_to_cloud(static_cast<int>(ResponseCode::TIMEOUT),dev.terminal_id,get_type_by_state(dev.busytype),dev.guid,dev.mac);
// 超时清空状态
dev.guid.clear(); // 清空进行中的 guid
dev.busytype = 0; // 复位业务类型
dev.isbusy = 0; // 标记空闲
dev.busytimecount = 0; // 计时清零
}
}
else //其他业务
{
if (dev.busytimecount > 10)
{
std::cout << "[Timeout] Device " << dev.terminal_id
<< " busytype=" << dev.busytype
<< " 超时(" << dev.busytimecount << "s)" << std::endl;
//发送超时响应
send_reply_to_cloud(static_cast<int>(ResponseCode::TIMEOUT),dev.terminal_id,get_type_by_state(dev.busytype),dev.guid,dev.mac);
// 超时清空状态
dev.guid.clear();
dev.busytype = 0;
dev.isbusy = 0;
dev.busytimecount = 0;
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////// 一分钟调用一次:检查 qvvr_file 超时并备份
static bool ensure_dir_exists(const std::string &path)
{
struct stat st;
if (stat(path.c_str(), &st) == 0) {
if (S_ISDIR(st.st_mode)) {
return true; // 已存在
}
return false; // 存在但不是目录
}
// 不存在则创建
if (mkdir(path.c_str(), 0755) == 0) {
return true;
}
return false;
}
void check_and_backup_qvvr_files() {
std::lock_guard<std::mutex> lock(ledgermtx);
const std::string data_dir = FRONT_PATH + "/data";
const std::string backup_dir = data_dir + "/comtrade_bak";
if (!ensure_dir_exists(data_dir) && !ensure_dir_exists(data_dir)) {
std::cerr << "[check_and_backup_qvvr_files] 创建 data 目录失败\n";
return;
}
if (!ensure_dir_exists(backup_dir) && !ensure_dir_exists(backup_dir)) {
std::cerr << "[check_and_backup_qvvr_files] 创建 comtrade_bak 目录失败\n";
return;
}
for (auto &dev : terminal_devlist) {
for (auto &line : dev.line) {
// 用迭代器遍历,允许在循环中 erase 当前元素
for (std::vector<qvvr_file>::iterator qit = line.qvvrevent.qvvrfile.begin();
qit != line.qvvrevent.qvvrfile.end(); /* no ++ here */) {
qvvr_file &qfile = *qit;
if (!qfile.used_status) {
++qit;
continue;
}
++qfile.file_time_count; // 每分钟+1
// 超时阈值:>10 分钟
if (qfile.file_time_count > 10) {
std::cout << "[Qvvr Timeout] dev=" << dev.terminal_id
<< " -> move files to: " << backup_dir << std::endl;
// 移动该记录内的所有文件
for (std::list<std::string>::const_iterator it = qfile.file_download.begin();
it != qfile.file_download.end(); ++it) {
const std::string &src = *it;
const size_t pos = src.find_last_of("/\\");
const std::string base = (pos == std::string::npos) ? src : src.substr(pos + 1);
const std::string dst = backup_dir + "/" + base;
if (rename(src.c_str(), dst.c_str()) != 0) {
std::cerr << " [ERROR] 移动失败: " << src
<< " -> " << dst << " , " << strerror(errno) << std::endl;
} else {
std::cout << " moved: " << src << " -> " << dst << std::endl;
}
}
// ✅ 直接从 qvvrfile 向量中删除这条记录
qit = line.qvvrevent.qvvrfile.erase(qit);
// 注意:不再对 qfile 读写,因为它已被删除
continue; // 不自增,由 erase 返回的新迭代器继续
}
++qit; // 正常前进
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////定值信息发送接口函数
bool save_set_value(const std::string &dev_id, unsigned char mp_index, const std::vector<float> &fabsf) {
std::lock_guard<std::mutex> lock(ledgermtx);
// 1. 找到对应 terminal_dev
auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
[&](const terminal_dev &dev) { return dev.terminal_id == dev_id; });
if (it == terminal_devlist.end()) {
std::cerr << "[send_set_reply] device not found: " << dev_id << std::endl;
return false;
}
terminal_dev &dev = *it;
// 2. 检查状态
if (dev.isbusy != 2 || dev.busytype != static_cast<int>(DeviceState::READING_FIXEDVALUE)) {
std::cerr << "[send_set_reply] device not in READING_FIXEDVALUE state." << std::endl;
return false;
}
// 3. 遍历监测点,找到 logical_device_seq == mp_index 的监测点
bool found = false;
for (auto &mon : dev.line) {
if (std::atoi(mon.logical_device_seq.c_str()) == static_cast<int>(mp_index)) {
// ★ 清理原有的 set_values
mon.set_values.clear();
// ★ 将 fabsf 依次存入该监测点的 set_values
for (const auto &val : fabsf) {
mon.set_values.push_back(val);
}
found = true;
break;
}
}
if (!found) {
std::cerr << "[send_set_reply] monitor with seq=" << (int)mp_index
<< " not found in terminal " << dev_id << std::endl;
return false;
}
// 4. 状态递减
dev.isbusy--;
return true;
}
bool save_internal_value(const std::string &dev_id, const std::vector<ushort> &fabsf) {
// 找到对应 terminal_dev
std::lock_guard<std::mutex> lock(ledgermtx);
auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
[&](const terminal_dev &dev) { return dev.terminal_id == dev_id; });
if (it == terminal_devlist.end()) {
std::cerr << "[send_set_reply] device not found: " << dev_id << std::endl;
return false;
}
terminal_dev &dev = *it;
// 判断 isbusy==3 且 busytype==READING_INTERFIXEDVALUE
if (dev.isbusy != 3 || dev.busytype != static_cast<int>(DeviceState::READING_INTERFIXEDVALUE)) {
std::cerr << "[send_set_reply] device not in READING_INTERFIXEDVALUE state." << std::endl;
return false;
}
// ★ 新增:清理原有的内部定值列表
dev.internal_values.clear();
//将值严格按顺序存入list中
for (const auto &val : fabsf) {
dev.internal_values.push_back(val);
}
dev.isbusy--;
return true;
}
bool save_internal_info(const std::string &dev_id, const std::vector<NameFixValue> &fixValueList) {
// 找到对应 terminal_dev
std::lock_guard<std::mutex> lock(ledgermtx);
auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
[&](const terminal_dev &dev) { return dev.terminal_id == dev_id; });
if (it == terminal_devlist.end()) {
std::cerr << "[send_set_reply] device not found: " << dev_id << std::endl;
return false;
}
terminal_dev &dev = *it;
// 判断 isbusy==2 且 busytype==READING_INTERFIXEDVALUE
if (dev.isbusy != 2 || dev.busytype != static_cast<int>(DeviceState::READING_INTERFIXEDVALUE)) {
std::cerr << "[send_set_reply] device not in READING_INTERFIXEDVALUE state." << std::endl;
return false;
}
// ★ 新增:清理原有的内部定值列表
dev.dz_internal_info_list.clear();
//将值严格按顺序存入list中
for (const auto &info : fixValueList) {
dev.dz_internal_info_list.push_back(info);
}
dev.isbusy--;
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////回复定值读取响应
bool send_set_value_reply(const std::string &dev_id, unsigned char mp_index, const std::vector<DZ_TAB_STRUCT> &dz_info) {
std::lock_guard<std::mutex> lock(ledgermtx);
// 1) 找终端
auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
[&](const terminal_dev &d) { return d.terminal_id == dev_id; });
if (it == terminal_devlist.end()) {
std::cerr << "[send_set_value_reply] device not found: " << dev_id << std::endl;
return false;
}
terminal_dev &dev = *it;
// 2) 校验状态:发送“定值读取结果”回复,应处于 READING_FIXEDVALUEisbusy == 1
if (dev.isbusy != 1 || dev.busytype != static_cast<int>(DeviceState::READING_FIXEDVALUE)) { //定值读取
std::cerr << "[send_set_value_reply] device not in READING_FIXEDVALUE state." << std::endl;
return false;
}
// 3) 定位监测点mp_index ∈ [1,6]),与 logical_device_seq 比对
ledger_monitor *pMon = nullptr;
for (auto &mon : dev.line) {
if (std::atoi(mon.logical_device_seq.c_str()) == static_cast<int>(mp_index)) {
pMon = &mon;
break;
}
}
if (!pMon) {
std::cerr << "[send_set_value_reply] monitor with seq=" << (int)mp_index
<< " not found in terminal " << dev_id << std::endl;
return false;
}
//将dz_info存入监测点
pMon->dz_info_list.clear();
for (const auto &dz : dz_info) {
pMon->dz_info_list.push_back(dz);
}
// 4) 取该监测点的 set_values严格按顺序用于 DZ_Value
std::vector<float> ordered_vals;
ordered_vals.reserve(pMon->set_values.size());
for (float v : pMon->set_values) ordered_vals.push_back(v);
if (ordered_vals.empty()) {
std::cerr << "[send_set_value_reply] monitor seq=" << (int)mp_index
<< " has empty set_values." << std::endl;
return false;
}
// 5) 生成 JSON结构严格贴合你给的样例
nlohmann::json j;
// 顶层
j["guid"] = dev.guid;
j["FrontIP"] = FRONT_IP; // 你的前置机 IP项目已有常量/变量)
j["Node"] = g_front_seg_index; // 节点号(项目已有变量)
j["Dev_mac"] = normalize_mac(dev.addr_str);
// Detail
nlohmann::json detail;
detail["Type"] = 0x2106; // 设备数据
// Msg
nlohmann::json msg;
msg["Cldid"] = mp_index; //测点序号
msg["DataType"] = 0x0C; //定值
// DataArray对象数组逐个填充DZ_Value 严格按 set_values 顺序
nlohmann::json dataArray = nlohmann::json::array();
const size_t n_meta = dz_info.size();
const size_t n_vals = ordered_vals.size();
const size_t n = std::min(n_meta, n_vals); // 以两者较短长度为准
if (n_meta != n_vals) {
std::cerr << "[send_set_value_reply] warn: dz_info size(" << n_meta
<< ") != set_values size(" << n_vals << "), will emit " << n << " items.\n";
return false; // 或者继续发送,视需求而定
}
for (size_t i = 0; i < n; ++i) {
const DZ_TAB_STRUCT &dz = dz_info[i];
nlohmann::json item;
item["LN_Num"] = dz.LN_Num;
item["DZ_Num"] = dz.DZ_Num;
item["DZ_Name"] = dz.DZ_Name;
item["DZ_Value"] = ordered_vals[i]; // ★ 严格按顺序
item["DZ_Type"] = dz.DZ_Type;
item["DZ_Min"] = dz.DZ_Min;
item["DZ_Max"] = dz.DZ_Max;
item["DZ_Default"]= dz.DZ_Default;
item["DZ_UNIT"] = dz.DZ_UNIT;
dataArray.push_back(std::move(item));
}
msg["DataArray"] = std::move(dataArray);
detail["Msg"] = std::move(msg);
detail["Code"] = 200;
j["Detail"] = std::move(detail);
// 6) 入队发送
queue_data_t connect_info;
connect_info.strTopic = Topic_Reply_Topic;
connect_info.strText = j.dump(); // 序列化为字符串
connect_info.tag = Topic_Reply_Tag;
connect_info.key = Topic_Reply_Key;
{
std::lock_guard<std::mutex> lock(queue_data_list_mutex);
queue_data_list.push_back(std::move(connect_info));
}
// 调试打印
std::cout << "[send_set_value_reply] queued JSON:\n" << j.dump(4) << std::endl;
// 7) 发送后更新终端状态(按你现有规则)
if (dev.isbusy > 0) {
dev.isbusy--;
}
if (dev.isbusy == 0) {
dev.guid.clear();
dev.busytype = 0;
if (pMon) {
pMon->set_values.clear();//清理本次定值记录
}
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////回复内部定值响应读取
bool send_internal_value_reply(const std::string &dev_id, const std::vector<DZ_kzz_bit> &control_words)
{
std::lock_guard<std::mutex> lock(ledgermtx);
// 1) 找终端
auto it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
[&](const terminal_dev &d) { return d.terminal_id == dev_id; });
if (it == terminal_devlist.end()) {
std::cerr << "[send_internal_value_reply] device not found: " << dev_id << std::endl;
return false;
}
terminal_dev &dev = *it;
// 2) 校验状态:发送“内部定值读取结果”回复,应处于 READING_INTERFIXEDVALUEisbusy == 1
if (dev.isbusy != 1 || dev.busytype != static_cast<int>(DeviceState::READING_INTERFIXEDVALUE)) {
std::cerr << "[send_internal_value_reply] device not in READING_INTERFIXEDVALUE state." << std::endl;
return false;
}
//将control_words存入dev
dev.control_words.clear();
for (const auto &cw : control_words) {
dev.control_words.push_back(cw);
}
// -------------------- [新增] 建立 internal_values 与 dz_internal_info_list 的一一对应 --------------------
// 说明:按索引次序一一对应(第 i 个 NameFixValue 对应 internal_values 的第 i 个)
// 若数量不同,按 min 对齐,忽略多出来的一边并告警。
std::vector<float> internal_vals; // [新增]
internal_vals.reserve(dev.internal_values.size()); // [新增]
for (float v : dev.internal_values) internal_vals.push_back(v); // [新增]
const size_t n_dz = dev.dz_internal_info_list.size(); // [新增]
const size_t n_val = internal_vals.size(); // [新增]
const size_t n_use = std::min(n_dz, n_val); // [新增]
if (n_dz != n_val) { // [新增]
std::cerr << "[send_internal_value_reply] WARN: dz_internal_info_list size("
<< n_dz << ") != internal_values size(" << n_val
<< "), will use min(" << n_use << ")." << std::endl;
}
// ------------------------------------------------------------------------------------------------------
// 3) 组包顶层
nlohmann::json j;
j["guid"] = dev.guid;
j["FrontIP"] = FRONT_IP;
j["Node"] = g_front_seg_index;
j["Dev_mac"] = normalize_mac(dev.addr_str);
nlohmann::json detail;
detail["Type"] = 0x2106; // 设备数据
nlohmann::json msg;
msg["DataType"] = 0x0D; // 内部定值
// 4) === 将 C# 的拼接逻辑移植为 DataArray ===
// C# 变量对应关系:
// DevInfo.nDevIndex -> 这里用 1
// DevInfo.strGuId -> 这里用 装置id
// DevInfo.controlwordlist -> 这里用参数 control_wordsDZ_kzz_bit 含 kzz_bit/bit_enable
//
// NameFixValue 列表:使用 dev.dz_internal_info_list
//
// 关键逻辑:
// - 遍历每个 NameFixValuek 从 1 递增nStep 每个定值递增 1
// - 若 DataType == 1将 Max/Min/Default 都 /100并 property 输出一个空对象 [{}](保持与 C# 一致)
// - 否则:为该定值构建 property 位数组,范围 [nStep*16, (nStep+1)*16)
// 名称为空则提前结束本定值的 propertyflag = (DefaultValue >> j) & 0x01
//
nlohmann::json dataArray = nlohmann::json::array(); // [新增]
int nStep = 0; // [新增] 每个 NameFixValue 递增
int kSort = 1; // [新增] 排序号,从 1 开始
// 保护dz_internal_info_list 是引用成员,确保不会因并发被改动
//for (const auto& nf : dev.dz_internal_info_list) { // [新增]
for (size_t idxNF = 0; idxNF < n_use; ++idxNF) { // [修改] 使用 idxNF 控制索引
const auto& nf = dev.dz_internal_info_list[idxNF];
// 取字段
const uint16_t dataType = nf.DataType;
const uint16_t minRaw = nf.MinValue;
const uint16_t maxRaw = nf.MaxValue;
const uint16_t defaultRaw = nf.DefaultValue;
const std::string unit = trim_cstr(nf.sDimension, sizeof(nf.sDimension));
const std::string name = trim_cstr(nf.sFixValueName, sizeof(nf.sFixValueName));
// 取对应内部值
const float internal_v_raw = internal_vals[idxNF]; // [新增]
const double internal_v_out = static_cast<double>(internal_v_raw); // [新增] 直接转 double 输出,不缩放
// 构造一条记录
nlohmann::json one;
one["cpu_no"] = 1; // [新增] C#: DevInfo.nDevIndex 填设备号固定为1
one["dev_type"] = dev_id; // [新增] C#: DevInfo.strGuId 填装置id
one["type"] = 90; // [新增] 固定 "90"
one["unit"] = unit; // [新增]
one["describe"] = name; // [新增]
one["sort"] = kSort; // [新增]
one["Internal_Value"] = internal_v_out; // [新增] 精确对应 internal_values 的值(含必要缩放)
// 数值DataType == 1 时缩放 /100
if (dataType == 1) { // [新增] 缩放分支
int ChangeMaxValue = static_cast<int>(maxRaw) / 100;
int ChangeMinValue = static_cast<int>(minRaw) / 100;
int ChangeDefaultValue = static_cast<int>(defaultRaw) / 100;
one["maxvalue"] = ChangeMaxValue;
one["minvalue"] = ChangeMinValue;
one["defaultvalue"] = ChangeDefaultValue;
one["value"] = ChangeDefaultValue;
// C# 在该分支 property 写成 [{ }](一个空对象的数组)
nlohmann::json prop = nlohmann::json::array();
prop.push_back(nlohmann::json::object()); // [{}]
one["property"] = std::move(prop);
} else { // [新增] 未缩放分支 + property 位描述
one["maxvalue"] = static_cast<int>(maxRaw);
one["minvalue"] = static_cast<int>(minRaw);
one["defaultvalue"] = static_cast<int>(defaultRaw);
one["value"] = static_cast<int>(defaultRaw);
// 构建 property16 位窗口,从 nStep*16 到 (nStep+1)*16 - 1
nlohmann::json prop = nlohmann::json::array();
bool hasAny = false;
const int begin = nStep * 16;
const int end = (nStep + 1) * 16; // 不含 end
for (int idx = begin, jbit = 0; idx < end; ++idx, ++jbit) {
if (idx < 0 || static_cast<size_t>(idx) >= control_words.size()) break;
// 名称空则提前退出(仿 C#temp=="" break
const std::string cw_name = trim_cstr(control_words[idx].kzz_bit, sizeof(control_words[idx].kzz_bit));
if (cw_name.empty()) {
// 注意C# 如果 j==0 则设置了 flag2=1仅用于逗号处理这里不需要
break;
}
int flag = (defaultRaw >> jbit) & 0x01; // 取该位默认值
nlohmann::json bitItem;
bitItem["type_num"] = jbit;
bitItem["bit0"] = ""; // 保持与 C# 一致
bitItem["bit1"] = "";
bitItem["describe"] = cw_name;
bitItem["flag"] = (flag ? "1" : "0"); // C# 用字符串
prop.push_back(std::move(bitItem));
hasAny = true;
}
if (!hasAny) {
// 与 C# 对齐:如果一个都没有,就给 [{}] 以避免 "property":[] 的结构差异
prop.push_back(nlohmann::json::object());
}
one["property"] = std::move(prop);
}
dataArray.push_back(std::move(one)); // [新增]
++nStep; // [新增] 进入下一个 16 位窗口
++kSort; // [新增]
}
msg["DataArray"] = std::move(dataArray); // [新增]
detail["Msg"] = std::move(msg);
j["Detail"] = std::move(detail);
// 5) 入队发送(保持你的队列逻辑)
queue_data_t connect_info;
connect_info.strTopic = Topic_Reply_Topic;
connect_info.strText = j.dump(); // 序列化为字符串
connect_info.tag = Topic_Reply_Tag;
connect_info.key = Topic_Reply_Key;
{
std::lock_guard<std::mutex> lock(queue_data_list_mutex);
queue_data_list.push_back(std::move(connect_info));
}
// 调试打印
std::cout << "[send_internal_value_reply] queued JSON:\n" << j.dump(4) << std::endl;
// 6) 发送后更新终端状态(保持你现有规则)
if (dev.isbusy > 0) {
dev.isbusy--;
}
if (dev.isbusy == 0) {
dev.guid.clear();
dev.busytype = 0;
dev.internal_values.clear(); // 清理本次定值记录
dev.dz_internal_info_list.clear(); // 清理本次定值描述记录(注意:如果这是引用成员,确保其实际容器存在)
}
return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////处理补招逻辑
//发送补招响应给web
void send_reply_to_kafka_recall(const std::string& guid, const std::string& step,int code, const std::string& result,const std::string& terminalId,const std::string& lineIndex,const std::string& recallStartDate,const std::string& recallEndDate){
// 构造 JSON 字符串
std::ostringstream oss;
oss << "{"
<< "\"guid\":\"" << guid << "\","
<< "\"step\":\"" << step << "\","
<< "\"code\":" << code << ","
<< "\"result\":\"" << result << "\","
<< "\"terminalId\":\"" << terminalId << "\","
<< "\"lineIndex\":\"" << lineIndex << "\","
<< "\"recallStartDate\":\"" << recallStartDate << "\","
<< "\"recallEndDate\":\"" << recallEndDate << "\","
<< "\"processNo\":\"" << g_front_seg_index << "\","
<< "\"nodeId\":\"" << FRONT_INST << "\""
<< "}";
std::string jsonString = oss.str();
// 封装 Kafka 消息
queue_data_t connect_info;
connect_info.strTopic = Topic_Reply_Topic;
connect_info.strText = jsonString;
connect_info.tag = Topic_Reply_Tag;
connect_info.key = Topic_Reply_Key;
// 加入发送队列(带互斥锁保护)
queue_data_list_mutex.lock();
queue_data_list.push_back(connect_info);
queue_data_list_mutex.unlock();
}
// ===== 一次遍历可下发“多个终端的一条” =====
void check_recall_event() {
std::vector<RecallTask> tasks; // 本轮要发送的“每终端一条”
{ //锁作用域
std::lock_guard<std::mutex> lock(ledgermtx);
// 遍历所有 terminal —— 每个 terminal 只挑一条,先不判断运行状态,因为正在处理其他事务的装置也可以记录待补招信息
for (auto& dev : terminal_devlist) {
//如果该终端不是正在补招或者idle则直接跳过节省运行时间
if(dev.busytype != static_cast<int>(DeviceState::READING_EVENTLOG) && dev.busytype != static_cast<int>(DeviceState::IDLE)){
continue;
}
// 对正在补招或idle终端的所有监测点的待补招列表进行处理
// 1) 先弹掉首条为 DONE/FAILED 的记录(所有 monitor 都要处理首条)
bool any_non_empty = false;
for (auto& lm : dev.line) {
while (!lm.recall_list.empty()) {
const RecallMonitor& front = lm.recall_list.front();
if (front.recall_status == static_cast<int>(RecallStatus::DONE)) {
std::cout << "[check_recall_event] DONE dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
//调用reply接口通知web端该时间段补招完成
std::string msg = std::string("监测点:") + lm.monitor_name
+ " 补招时间范围:" + front.StartTime
+ " ~ " + front.EndTime
+ " 补招执行完成";
send_reply_to_kafka_recall("12345","2",static_cast<int>(ResponseCode::OK),msg,dev.terminal_id,lm.logical_device_seq,front.StartTime,front.EndTime);
lm.recall_list.pop_front(); // 弹掉首条
} else if (front.recall_status == static_cast<int>(RecallStatus::FAILED)) {
std::cout << "[check_recall_event] FAILED dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
//调用reply接口通知web端该时间段补招失败
std::string msg = std::string("监测点:") + lm.monitor_name
+ " 补招时间范围:" + front.StartTime
+ " ~ " + front.EndTime
+ " 补招执行失败";
send_reply_to_kafka_recall("12345","2",static_cast<int>(ResponseCode::BAD_REQUEST),msg,dev.terminal_id,lm.logical_device_seq,front.StartTime,front.EndTime);
lm.recall_list.pop_front(); // 弹掉首条
} else {
break; // 首条不是 2/3如果是正在处理其他业务或者idle的装置写入了待补招列表应该都是0如果是正在补招的装置新增的部分不会影响原有顺序
}
}
if (!lm.recall_list.empty()) any_non_empty = true; // 处理了成功和失败的以后只要有一条非空就标记,可能是待处理或者正在处理的补招
}
if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::READING_EVENTLOG)) {
// 该终端本轮已无任何补招条目,且处于补招暂态事件的状态清空运行态
//通知补招全部完成
dev.guid.clear(); // 清空 guid
dev.busytype = 0; // 业务类型归零
dev.isbusy = 0; // 清空业务标志
dev.busytimecount = 0; // 计时归零
continue;
}
//如果是idle又没有待补招任务了应该跳过
else if(!any_non_empty && dev.busytype == static_cast<int>(DeviceState::IDLE)){
continue;
}
else{//有待补招任务且处于补招状态或者idle状态
// 继续补招处理
}
// 2) 若任一 monitor 的首条为 RUNNING则该终端正在补招中 -> 跳过该终端,不会下发新的补招请求
bool has_running = false;
for (auto& lm : dev.line) {
if (!lm.recall_list.empty() &&
lm.recall_list.front().recall_status == static_cast<int>(RecallStatus::RUNNING)) {
has_running = true;
break;
}
}
if (has_running) continue;
// 3) 选择该终端的“第一条 NOT_STARTED(0)”作为本终端本轮任务
bool picked = false;
for (auto& lm : dev.line) {
if (lm.recall_list.empty()) continue; //跳过空的监测点
RecallMonitor& front = lm.recall_list.front(); //取非空测点的列表的第一条
if (front.recall_status == static_cast<int>(RecallStatus::NOT_STARTED)) {
// 标记为 RUNNING并设置终端忙状态
front.recall_status = static_cast<int>(RecallStatus::RUNNING);//该补招记录刷新为补招中
dev.isbusy = 1; //标记为忙
dev.busytype = static_cast<int>(DeviceState::READING_EVENTLOG);//装置状态正在补招和idle的都刷新为正在补招
dev.busytimecount = 0; //刷新业务超时计数
// 记录任务(每终端只取这一条)
tasks.push_back(RecallTask{
dev.terminal_id,
front.StartTime,
front.EndTime,
lm.monitor_id
});
picked = true; //该装置已取
break;
}
}
// 若该终端没有 NOT_STARTED 的首条(可能剩下的都是 RUNNING 或内部已经被清空),
// 就留待下一轮;不要清空运行态,直到所有条目被处理为止。
}
} // 解锁
// 对于本轮挑选到的任务,逐终端下发一条,这些终端的状态都已设为补招
for (const auto& t : tasks) {
//处理入参
std::tm tm1{}, tm2{};
if (!parse_datetime_tm(t.start_time, tm1) || !parse_datetime_tm(t.end_time, tm2)) {
std::cout << "[check_recall_event] parse time fail: "
<< t.start_time << " ~ " << t.end_time << std::endl;
continue;
}
uint8_t mp = 1; // 默认
try {
mp = static_cast<uint8_t>(std::stoi(t.monitor_index));
} catch (...) {
std::cout << "[check_recall_event] parse mpid fail: "
<< t.monitor_index << std::endl;
continue;
}
// 下发补招请求action=2
ClientManager::instance().read_eventlog_action_to_device(
t.dev_id, tm1, tm2, 2, mp);//2是暂态事件
std::cout << "[check_recall_event] SEND dev=" << t.dev_id
<< " monitor=" << mp
<< " start=" << t.start_time
<< " end=" << t.end_time
<< " action(2)" << std::endl;
}
// 重要:本函数不把 RUNNING 改成 DONE/FAILED
// 应由设备回调/结果处理逻辑在完成后调用通知函数,把对应 monitor.front().recall_status 从1置为 2/3
// 下一轮本函数会弹掉 2/3并在该终端所有 recall_list 全部清空后重置装置状态。
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////处理补招逻辑统计数据
// ====== 从文件名中提取“第二段下划线分隔字段”并转换为 epoch 秒 ======
static bool extract_epoch_from_filename(const std::string& name,
long long& out_epoch,
int logical_device_seq)
{
// 拆分
std::vector<std::string> parts;
parts.reserve(8);
size_t start = 0, pos;
while ((pos = name.find('_', start)) != std::string::npos) {
parts.emplace_back(name.substr(start, pos - start));
start = pos + 1;
}
parts.emplace_back(name.substr(start)); // 最后一段(含扩展名)
if (parts.size() < 4) return false;
// 第二段序号是倒数第4段
const std::string& seq_str = parts[parts.size() - 4];
// 允许前导 0把字符串转 int 后比较
for (char c : seq_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
int seq_val = 0;
try {
seq_val = std::stoi(seq_str);
} catch (...) {
return false;
}
if (seq_val != logical_device_seq) return false;
// 其余与上面相同
const std::string& date_str = parts[parts.size() - 3];
const std::string& time_str = parts[parts.size() - 2];
std::string ms_str = parts.back();
size_t dot = ms_str.find('.');
if (dot != std::string::npos) {
ms_str.erase(dot);
}
if (date_str.size() != 8 || time_str.size() != 6) return false;
for (char c : date_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
for (char c : time_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
for (char c : ms_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
int year = std::stoi(date_str.substr(0, 4));
int month = std::stoi(date_str.substr(4, 2));
int day = std::stoi(date_str.substr(6, 2));
int hour = std::stoi(time_str.substr(0, 2));
int min = std::stoi(time_str.substr(2, 2));
int sec = std::stoi(time_str.substr(4, 2));
// int msec = std::stoi(ms_str);
std::tm tm{}; tm.tm_isdst = -1;
tm.tm_year = year - 1900;
tm.tm_mon = month - 1;
tm.tm_mday = day;
tm.tm_hour = hour;
tm.tm_min = min;
tm.tm_sec = sec;
time_t t = timegm(&tm);
if (t < 0) return false;
out_epoch = static_cast<long long>(t); // 秒级
return true;
}
// ====== ★修改check_recall_stat —— 加入“两步法”状态机 ======
void check_recall_file() {
std::vector<RecallTask> tasks; // 本轮要发送的“每终端一条”(目录请求 或 文件下载 请求)
{ //锁作用域
std::lock_guard<std::mutex> lock(ledgermtx);
for (auto& dev : terminal_devlist) {
// 仅处理“正在补招/空闲”终端,与你原逻辑一致
if (dev.busytype != static_cast<int>(DeviceState::READING_STATSFILE) &&
dev.busytype != static_cast<int>(DeviceState::IDLE)) {
continue;
}
// 1) 清理首条 DONE/FAILED
bool any_non_empty = false;
for (auto& lm : dev.line) {
while (!lm.recall_list_static.empty()) {
const RecallFile& front = lm.recall_list_static.front();
if (front.recall_status == static_cast<int>(RecallStatus::DONE)) {
std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
lm.recall_list_static.pop_front();
} else if (front.recall_status == static_cast<int>(RecallStatus::FAILED)) {
std::cout << "[check_recall_stat] FAILED dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
lm.recall_list_static.pop_front();
} else {
break;
}
}
if (!lm.recall_list_static.empty()) any_non_empty = true;
}
// 无条目时的装置态收尾
if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::READING_EVENTLOG)) {
// (保持你原注释)处于“暂态补招”的状态且无条目 -> 清空运行态
dev.guid.clear();
dev.busytype = 0;
dev.isbusy = 0;
dev.busytimecount = 0;
continue;
} else if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::IDLE)) {
continue;
}
// 2) 若任一 monitor 的首条为 RUNNING则该终端正在补招中 -> 不下发新的任务(但需要推进状态机!)
bool has_running = false;
for (auto& lm : dev.line) {
if (!lm.recall_list_static.empty() &&
lm.recall_list_static.front().recall_status == static_cast<int>(RecallStatus::RUNNING)) {
has_running = true;
break;
}
}
// ★新增:当存在 RUNNING 时,推进“该终端的首条补招记录”的两步状态机
if (has_running) {
for (auto& lm : dev.line) {
if (lm.recall_list_static.empty()) continue;
RecallFile& front = lm.recall_list_static.front();
if (front.recall_status != static_cast<int>(RecallStatus::RUNNING)) continue;
// 初始化阶段:从 NOT_STARTED->RUNNING 已完成,此处保证 phase 就绪
if (front.phase == RecallPhase::IDLE) {
front.phase = RecallPhase::LISTING;
front.cur_dir_index = 0;
front.cur_dir.clear();
front.list_result = ActionResult::PENDING;
front.download_result = ActionResult::PENDING;
front.download_queue.clear();
// 立即发起第一个目录请求
if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
front.cur_dir = front.dir_candidates[front.cur_dir_index];
// ★★ 只发一个目录请求,并等待外部线程回写结果与文件名列表
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
std::cout << "[check_recall_stat] LIST req dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " dir=" << front.cur_dir << std::endl;
} else {
// 无目录可查
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::cout << "[check_recall_stat] no dir candidates, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
}
// 一个终端只推进首条,跳出
break;
}
// LISTING 阶段:等待其他线程回写 list_result + dir_files[cur_dir]
if (front.phase == RecallPhase::LISTING) {
if (front.list_result == ActionResult::PENDING) {
// 还在等目录回执,本轮不再发任何请求
// 外部线程拿到目录文件列表后应写入front.dir_files[front.cur_dir] = {a,b,c...} 并置 list_result=OK/FAIL
continue;
}
if (front.list_result == ActionResult::FAIL) {
// 尝试下一个目录
front.cur_dir_index++;
front.list_result = ActionResult::PENDING;
front.download_queue.clear();
if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
front.cur_dir = front.dir_candidates[front.cur_dir_index];
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
std::cout << "[check_recall_stat] LIST retry dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " dir=" << front.cur_dir << std::endl;
} else {
// 所有目录都失败
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::cout << "[check_recall_stat] all dir failed, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
}
continue;
}
// OK根据起止时间筛选文件
{
long long beg = parse_time_to_epoch(front.StartTime);
long long end = parse_time_to_epoch(front.EndTime);
if (beg < 0 || end < 0 || beg > end) {
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::cout << "[check_recall_stat] time parse ERR, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " start=" << front.StartTime
<< " end=" << front.EndTime << std::endl;
continue;
}
auto it = front.dir_files.find(front.cur_dir);
if (it != front.dir_files.end()) {
if (front.direct_mode) {
// ★新增:直下文件(精确匹配文件名)
for (const auto& ent : it->second) {
if (ent.flag != 1) continue; // 只要文件
// 安全从 char[64] 转成 std::string
size_t n = ::strnlen(ent.name, sizeof(ent.name));
std::string fname(ent.name, n);
if (fname == front.target_filename) {
front.download_queue.push_back(front.cur_dir + "/" + fname);
break; // 找到一个就足够
}
}
} else {
// ☆原有:按时间窗筛选
long long beg = parse_time_to_epoch(front.StartTime);
long long end = parse_time_to_epoch(front.EndTime);
for (const auto& ent : it->second) {
if (ent.flag != 1) continue; // 只要文件
// 文件名
size_t n = ::strnlen(ent.name, sizeof(ent.name));
std::string fname(ent.name, n);
long long fe = -1;
int seq = 0;
try { seq = std::stoi(lm.logical_device_seq); } catch (...) { seq = 0; }
if (!extract_epoch_from_filename(fname, fe, seq)) continue;
if (fe >= beg && fe <= end) {
front.download_queue.push_back(front.cur_dir + "/" + fname);
}
}
}
}
}
if (front.download_queue.empty()) {
// 当前目录无匹配文件 -> 试下一个目录
front.cur_dir_index++;
front.list_result = ActionResult::PENDING;
if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
front.cur_dir = front.dir_candidates[front.cur_dir_index];
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
std::cout << "[check_recall_stat] LIST next dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " dir=" << front.cur_dir << std::endl;
} else {
// 所有目录都“无匹配文件”
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::cout << "[check_recall_stat] no matched files in ALL dirs, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
}
continue;
} else {
// 进入下载阶段
front.phase = RecallPhase::DOWNLOADING;
front.download_result = ActionResult::PENDING;
front.downloading_file.clear();
std::cout << "[check_recall_stat] enter DOWNLOADING dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " count=" << front.download_queue.size() << std::endl;
}
}
// DOWNLOADING 阶段:一次只下一个文件,等待外部线程填充 download_result
if (front.phase == RecallPhase::DOWNLOADING) {
if (front.downloading_file.empty()) {
if (front.download_queue.empty()) {
// 所有文件下载完毕
front.recall_status = static_cast<int>(RecallStatus::DONE);
std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
continue;
}
// 发起下一文件下载
front.downloading_file = front.download_queue.front();
front.download_queue.pop_front();
front.download_result = ActionResult::PENDING;
// ★★ 只发一个下载请求,等待外部线程回写结果
ClientManager::instance().add_file_download_action_to_device(dev.terminal_id, front.downloading_file);
std::cout << "[check_recall_stat] DL req dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " file=" << front.downloading_file << std::endl;
continue;
} else {
// 等待 download_result
if (front.download_result == ActionResult::PENDING) {
continue; // 仍在等待
}
if (front.download_result == ActionResult::OK) {
// 记录成功文件
front.file_paths.push_back(front.downloading_file);
std::cout << "[check_recall_stat] DL ok dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " file=" << front.downloading_file << std::endl;
// 清空当前文件标志,进入下一轮取队首
front.downloading_file.clear();
continue;
} else {
// 失败:直接尝试下一个文件(不中断整条补招)
std::cout << "[check_recall_stat] DL fail dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " file=" << front.downloading_file << std::endl;
front.downloading_file.clear();
continue;
}
}
}
// 一个终端只推进“一个监测点的首条”状态机,避免并行
break;
}
// 本终端已有 RUNNING 项,且已推进;不再挑选新的 NOT_STARTED
continue;
}
// 3) 没有 RUNNING挑选第一条 NOT_STARTED并发起“首个目录”的请求
for (auto& lm : dev.line) {
if (lm.recall_list_static.empty()) continue;
RecallFile& front = lm.recall_list_static.front();
if (front.recall_status == static_cast<int>(RecallStatus::NOT_STARTED)) {
// 标记为 RUNNING并设置终端忙状态
front.recall_status = static_cast<int>(RecallStatus::RUNNING);
dev.isbusy = 1;
dev.busytype = static_cast<int>(DeviceState::READING_STATSFILE);
dev.busytimecount = 0;
// 初始化状态机并发出第一个目录请求
front.reset_runtime(true);//保留直下文件信息
front.phase = RecallPhase::LISTING;
if (!front.dir_candidates.empty()) {
front.cur_dir_index = 0;
front.cur_dir = front.dir_candidates[0];
front.list_result = ActionResult::PENDING;
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
std::cout << "[check_recall_stat] LIST start dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " dir=" << front.cur_dir
<< " start=" << front.StartTime
<< " end=" << front.EndTime << std::endl;
} else {
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::cout << "[check_recall_stat] empty dir_candidates, FAIL dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id << std::endl;
}
// 每终端本轮仅取一条
break;
}
}
} // end for dev
} // 解锁
// ★说明:本函数不主动把 RUNNING 改 DONE/FAILED
// 由“其他线程”在目录/下载结果返回后,找到对应 dev/monitor 的 lm.recall_list_static.front()
// 写入:
// - 目录请求front.dir_files[front.cur_dir] = {...}; front.list_result = OK/FAIL;
// - 文件下载front.download_result = OK/FAIL;
// 下一轮本函数会推进状态机(继续本目录的文件或切到下一个目录)。
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////根据下发的指令直接补招文件
// ★新增:直下文件的任务入队(终端、测点、文件名)
// 约定:外部线程的回调,照旧把目录返回值写到 lm.recall_list_static.front().dir_files[dir]
// 并置 front.list_result=OK/FAIL下载回执置 front.download_result=OK/FAIL。
// 处理指令部分将文件名拼接出来调用这个函数
bool enqueue_direct_download(const std::string& dev_id,
const std::string& monitor_id,
const std::string& filename,
const std::vector<std::string>& dir_candidates)
{
std::lock_guard<std::mutex> lk(ledgermtx);
// 找终端
auto dev_it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
[&](const terminal_dev& d){ return d.terminal_id == dev_id; });
if (dev_it == terminal_devlist.end()) return false;
// 找监测点
auto lm_it = std::find_if(dev_it->line.begin(), dev_it->line.end(),
[&](const ledger_monitor& lm){ return lm.monitor_id == monitor_id; });
if (lm_it == dev_it->line.end()) return false;
// 组装一条 RecallFile
RecallFile rf;
rf.recall_status = static_cast<int>(RecallStatus::NOT_STARTED);
rf.StartTime = "1970-01-01 00:00:00"; // 仅占位,直下文件不会用到时间窗
rf.EndTime = "1970-01-01 00:00:01";
rf.dir_candidates = dir_candidates; // 传入要检索的目录列表
rf.direct_mode = true; // ★关键:直下文件
rf.target_filename = filename; // ★关键:匹配的目标文件名
lm_it->recall_list_static.push_back(std::move(rf));
// 若设备空闲,可直接置忙(可选,视你的流程而定)
if (dev_it->busytype == static_cast<int>(DeviceState::IDLE)) {
dev_it->isbusy = 1;
dev_it->busytype = static_cast<int>(DeviceState::READING_STATSFILE); // 或 READING_EVENTFILE
dev_it->busytimecount = 0;
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////通讯响应处理
void filemenu_cache_put(const std::string& dev_id,
std::vector<tag_dir_info> FileList)
{
std::lock_guard<std::mutex> lk(g_filemenu_cache_mtx);
g_filemenu_cache[dev_id] = std::move(FileList); // 直接存 vector<tag_dir_info>
}
bool filemenu_cache_take(const std::string& dev_id, std::vector<tag_dir_info>& out)
{
std::lock_guard<std::mutex> lk(g_filemenu_cache_mtx);
auto it = g_filemenu_cache.find(dev_id);
if (it == g_filemenu_cache.end()) return false;
out = std::move(it->second); // 转移 vector<tag_dir_info>
g_filemenu_cache.erase(it); // 删除缓存
return true;
}
// 提取文件名列表(仅 flag==1
static inline void build_file_name_list(const std::vector<tag_dir_info>& in,
std::list<std::string>& out)
{
out.clear();
for (const auto& e : in) {
if (e.flag == 1) {
// 安全地从固定长度 char[64] 转成 std::string
size_t n = ::strnlen(e.name, sizeof(e.name));
out.emplace_back(std::string(e.name, n));
}
}
}
void on_device_response_minimal(int response_code,
const std::string& id,
unsigned char cid,
int device_state_int)
{
const bool ok = is_ok(response_code);
DeviceState state = static_cast<DeviceState>(device_state_int);
switch (state) {
// ================= 特殊:补招事件日志 =================
case DeviceState::READING_EVENTLOG: {
std::lock_guard<std::mutex> lk(ledgermtx);
terminal_dev* dev = nullptr;
for (auto& d : terminal_devlist) {
if (d.terminal_id == id) { dev = &d; break; }
}
if (!dev) {
std::cout << "[RESP][EVENTLOG] dev not found, id=" << id
<< " rc=" << response_code << std::endl;
return;
}
// 找到“首条 RUNNING”的补招条目按 rc==200 成功/失败标记
bool updated = false;
// 1) 找到 logical_device_seq 与 cid 匹配的 monitor
ledger_monitor* matched_monitor = nullptr;
// 将 cid 转成十进制字符串用于直接比较
const std::string cid_str = std::to_string(static_cast<int>(cid));
for (auto& lm : dev->line) {
// 直接字符串相等
if (lm.logical_device_seq == cid_str) {
matched_monitor = &lm;
break;
}
}
if (!matched_monitor) { //没有匹配的监测点
std::cout << "[RESP][EVENTLOG][WARN] dev=" << id
<< " cannot find monitor by cid=" << static_cast<int>(cid)
<< " (no logical_device_seq match)" << std::endl;
}
else {
// 2) 仅更新该监测点 recall_list 的首条,且要求处于 RUNNING
if (!matched_monitor->recall_list.empty()) {
RecallMonitor& front = matched_monitor->recall_list.front(); //取出首条
if (front.recall_status == static_cast<int>(RecallStatus::RUNNING)) { //正在补招
if (ok) { //补招成功
front.recall_status = static_cast<int>(RecallStatus::DONE); //标记为完成,补招任务会执行下一条
std::cout << "[RESP][EVENTLOG][OK] dev=" << id
<< " mon=" << matched_monitor->monitor_id
<< " " << front.StartTime << "~" << front.EndTime
<< std::endl;
} else { //补招失败
front.recall_status = static_cast<int>(RecallStatus::FAILED);
std::cout << "[RESP][EVENTLOG][FAIL] dev=" << id
<< " mon=" << matched_monitor->monitor_id
<< " " << front.StartTime << "~" << front.EndTime
<< " rc=" << response_code << std::endl; //错误响应码
//记录日志
DIY_ERRORLOG_CODE(matched_monitor->monitor_id.c_str(),2,static_cast<int>(LogCode::LOG_CODE_RECALL),"【ERROR】监测点:%s 补招数据失败 - 失败时间点:%lld 至 %lld",matched_monitor->monitor_id.c_str(),front.StartTime,front.EndTime);
}
updated = true;
} else { //首条不是 RUNNING 状态,不应该收到这条响应
std::cout << "[RESP][EVENTLOG][WARN] dev=" << id
<< " mon=" << matched_monitor->monitor_id
<< " first item not RUNNING (status="
<< front.recall_status << ")" << std::endl; //打印调试
}
} else { //补招列表为空,不应该收到这条响应
std::cout << "[RESP][EVENTLOG][WARN] dev=" << id
<< " mon=" << matched_monitor->monitor_id
<< " recall_list empty" << std::endl;
}
}
if (!updated) {//不更新补招列表
std::cout << "[RESP][EVENTLOG][WARN] no RUNNING item, dev=" << id
<< " rc=" << response_code << std::endl;
}
break;
}
// ================= 特殊:读取文件目录 =================
case DeviceState::READING_FILEMENU: {
std::lock_guard<std::mutex> lk(ledgermtx);
// 1) 找到对应终端
terminal_dev* dev = nullptr;
for (auto& d : terminal_devlist) {
if (d.terminal_id == id) { dev = &d; break; }
}
if (!dev) {
std::cout << "[RESP][FILEMENU] dev not found, id=" << id
<< " rc=" << response_code << std::endl;
break;
}
// 2) 按该终端当前 busytype 分支处理
const int bt = dev->busytype;
if (bt == static_cast<int>(DeviceState::READING_FILEMENU)) {
// ====== 分支 A当前业务就是“读取文件目录” ======
if (ok) {
std::vector<tag_dir_info> names;
if (filemenu_cache_take(id, names)) {
//发送目录
send_file_list(dev,names);
} else {
// 失败响应web并复位为空闲
send_reply_to_cloud(static_cast<int>(ResponseCode::BAD_REQUEST), id, static_cast<int>(DeviceState::READING_FILEMENU),dev->guid,dev->mac);
std::cout << "[RESP][FILEMENU->FILEMENU][WARN] dev=" << id
<< " names missing in cache" << std::endl;
}
// 成功:复位
dev->guid.clear();
dev->isbusy = 0;
dev->busytype = 0;
dev->busytimecount = 0;
std::cout << "[RESP][FILEMENU->FILEMENU][OK] dev=" << id << std::endl;
} else {
// 失败响应web并复位为空闲
send_reply_to_cloud(response_code, id, static_cast<int>(DeviceState::READING_FILEMENU),dev->guid,dev->mac);
dev->guid.clear();
dev->isbusy = 0;
dev->busytype = 0;
dev->busytimecount = 0;
std::cout << "[RESP][FILEMENU->FILEMENU][FAIL] dev=" << id
<< " rc=" << response_code << std::endl;
}
}
else if (
bt == static_cast<int>(DeviceState::READING_EVENTFILE)
|| bt == static_cast<int>(DeviceState::READING_STATSFILE)
) {
// ====== 分支 B当前业务为“下载事件文件/统计文件”(两者处理相同) ======
// 一个装置同一时刻只会有一个监测点在下载
ledger_monitor* running_monitor = nullptr;
RecallFile* running_front = nullptr;
// 在该终端下,找到“首条 recall_list_static.front() 正在运行且处于 DOWNLOADING 的监测点”
for (auto& lm : dev->line) {
if (lm.recall_list_static.empty()) continue;
RecallFile& f = lm.recall_list_static.front();
if (f.recall_status == static_cast<int>(RecallStatus::RUNNING)
&& f.phase == RecallPhase::DOWNLOADING) {
running_monitor = &lm;
running_front = &f;
break;
}
}
if (!running_monitor || !running_front) {
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
<< " no RUNNING/DOWNLOADING recall on this device, ignore resp"
<< " rc=" << response_code << std::endl;
break;
}
// 根据回执结果,回写下载结果;状态机会在下一轮推进到下一个文件/结束
if (ok) {
running_front->download_result = ActionResult::OK;
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][OK] dev=" << id
<< " monitor=" << running_monitor->monitor_id
<< " file=" << running_front->downloading_file << std::endl;
} else {
running_front->download_result = ActionResult::FAIL;
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][FAIL] dev=" << id
<< " monitor=" << running_monitor->monitor_id
<< " file=" << running_front->downloading_file
<< " rc=" << response_code << std::endl;
}
break;
}
else {
// ====== 分支 C其他业务场景下收到 FILEMENU 响应,统一处理 打印提示======
if (ok) {
// 成功:
std::cout << "[unknown][RESP][FILEMENU->OTHER][OK] dev=" << id
<< " keep busytype=" << bt << std::endl;
} else {
// 失败:
std::cout << "[unknown][RESP][FILEMENU->OTHER][FAIL] dev=" << id
<< " rc=" << response_code << " prev busytype=" << bt << std::endl;
}
}
break;
}
// ================= 特殊:下载文件 =================
case DeviceState::READING_FILEDATA: {
std::lock_guard<std::mutex> lk(ledgermtx);
// 1) 找到对应终端
terminal_dev* dev = nullptr;
for (auto& d : terminal_devlist) {
if (d.terminal_id == id) { dev = &d; break; }
}
if (!dev) {
std::cout << "[RESP][FILEDATA] dev not found, id=" << id
<< " rc=" << response_code << std::endl;
break;
}
// 2) 按该终端当前 busytype 分支处理
const int bt = dev->busytype;
if (bt == static_cast<int>(DeviceState::READING_FILEDATA)) {
// ====== 分支 A当前业务就是“读取文件数据” ======
if (ok) {
// 成功:复位
send_reply_to_cloud(static_cast<int>(ResponseCode::OK), id, static_cast<int>(DeviceState::READING_FILEDATA),dev->guid,dev->mac);
dev->guid.clear();
dev->isbusy = 0;
dev->busytype = 0;
dev->busytimecount = 0;
std::cout << "[RESP][FILEDATA->FILEDATA][OK] dev=" << id << std::endl;
} else {
// 失败响应web并复位
send_reply_to_cloud(response_code, id, static_cast<int>(DeviceState::READING_FILEDATA),dev->guid,dev->mac);
dev->guid.clear();
dev->isbusy = 0;
dev->busytype = 0;
dev->busytimecount = 0;
std::cout << "[RESP][FILEDATA->FILEDATA][FAIL] dev=" << id
<< " rc=" << response_code << std::endl;
}
}
else if (
bt == static_cast<int>(DeviceState::READING_EVENTFILE)
|| bt == static_cast<int>(DeviceState::READING_STATSFILE)
) {
// ====== 分支 B当前业务为“下载事件文件/统计文件”(两者处理相同) ======
// ★新增:通过 cid 精确找到监测点logical_device_seq == cid
ledger_monitor* matched_monitor = nullptr;
{
const std::string cid_str = std::to_string(static_cast<int>(cid));
for (auto& lm : dev->line) {
if (lm.logical_device_seq == cid_str) { matched_monitor = &lm; break; }
}
}
if (!matched_monitor || matched_monitor->recall_list_static.empty()) {
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
<< " no matched monitor or empty recall list, cid="
<< static_cast<int>(cid) << std::endl;
break;
}
RecallFile& front = matched_monitor->recall_list_static.front();
if (front.recall_status != static_cast<int>(RecallStatus::RUNNING)
|| front.phase != RecallPhase::DOWNLOADING) {
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
<< " monitor=" << matched_monitor->monitor_id
<< " ignore resp: status=" << front.recall_status
<< " phase=" << static_cast<int>(front.phase) << std::endl;
break;
}
if (ok) {
// ★新增:下载成功 -> 通知状态机推进到下一个文件(真正入账 file_paths 在 check_recall_stat 里做)
front.download_result = ActionResult::OK;
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][OK] dev=" << id
<< " file=" << front.downloading_file << std::endl;
} else {
// ★新增:下载失败 -> 让状态机尝试下一个文件
front.download_result = ActionResult::FAIL;
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][FAIL] dev=" << id
<< " file=" << front.downloading_file
<< " rc=" << response_code << std::endl;
}
break;
}
else {
// ====== 分支 C其他业务场景下收到 FILEDATA 响应,统一处理 打印提示======
if (ok) {
// 成功:
std::cout << "[unknown][RESP][FILEDATA->OTHER][OK] dev=" << id
<< " keep busytype=" << bt << std::endl;
} else {
// 失败:
std::cout << "[unknown][RESP][FILEDATA->OTHER][FAIL] dev=" << id
<< " rc=" << response_code << " prev busytype=" << bt << std::endl;
}
}
break;
}
// ================= 其它状态统一处理 =================
default: {
std::lock_guard<std::mutex> lk(ledgermtx);
terminal_dev* dev = nullptr;
for (auto& d : terminal_devlist) {
if (d.terminal_id == id) { dev = &d; break; }
}
if (dev) {
//直接根据输入响应mq
send_reply_to_cloud(response_code, id, device_state_int, dev->guid, dev->mac);
//其他的错误和成功都会结束业务
dev->guid.clear(); // 清空 guid
dev->busytype = 0; // 业务类型归零
dev->isbusy = 0; // 清空业务标志
dev->busytimecount = 0; // 计时归零
std::cout << "[clear_terminal_runtime_state] Cleared runtime state for terminal_id="
<< id << std::endl;
}
break;
}
} // end switch
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////记录暂态事件到本地
// 将一条暂态数据更新/写入到指定终端ID与逻辑序号的监测点
// 返回 true 表示已写入更新或追加false 表示未找到对应终端/监测点。
bool append_qvvr_event(const std::string& terminal_id,
int logical_seq, // 监测点序号(如 1
int nType, // 事件类型
double fPersisstime_sec, // 持续时间(秒)
double fMagnitude_pu, // 幅值pu
uint64_t triggerTimeMs, // 触发时间(毫秒)
int phase) // 相别
{
std::cout << "[append_qvvr_event] enter"
<< " tid=" << std::this_thread::get_id()
<< " terminal_id=" << terminal_id
<< " logical_seq=" << logical_seq
<< " type=" << nType
<< " per_s=" << fPersisstime_sec
<< " mag_pu=" << fMagnitude_pu
<< " time_ms=" << static_cast<unsigned long long>(triggerTimeMs)
<< " phase=" << phase
<< std::endl;
std::lock_guard<std::mutex> lk(ledgermtx);
std::cout << "[append_qvvr_event] lock acquired. terminal_devlist.size="
<< terminal_devlist.size() << std::endl;
// 1) 找终端
auto dev_it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
[&](const terminal_dev& d){ return d.terminal_id == terminal_id; });
if (dev_it == terminal_devlist.end()) {
std::cout << "[append_qvvr_event][MISS] terminal not found: "
<< terminal_id << std::endl;
return false;
}
std::cout << "[append_qvvr_event][HIT] terminal_id=" << terminal_id
<< " monitors(line).size=" << dev_it->line.size()
<< std::endl;
// 2) 找监测点(按逻辑序号匹配:字符串等于 或 数值等于)
ledger_monitor* pMon = nullptr;
for (size_t i = 0; i < dev_it->line.size(); ++i) {
auto& m = dev_it->line[i];
bool eq_str = (m.logical_device_seq == std::to_string(logical_seq));
bool eq_num = false;
try {
if (!m.logical_device_seq.empty())
eq_num = (std::stoi(m.logical_device_seq) == logical_seq);
} catch (...) {
// 仅调试提示,不改变原逻辑
std::cout << "[append_qvvr_event][monitor #" << i
<< "] stoi fail, logical_device_seq=\""
<< m.logical_device_seq << "\"" << std::endl;
}
std::cout << "[append_qvvr_event][probe monitor #" << i
<< "] monitor_id=" << m.monitor_id
<< " logical_device_seq=\"" << m.logical_device_seq << "\""
<< " eq_str=" << eq_str << " eq_num=" << eq_num << std::endl;
if (eq_str || eq_num) { pMon = &m; break; }
}
if (!pMon) {
std::cout << "[append_qvvr_event][MISS] monitor not found by seq="
<< logical_seq << " in terminal_id=" << terminal_id << std::endl;
return false;
}
std::cout << "[append_qvvr_event][HIT] monitor_id=" << pMon->monitor_id
<< " logical_device_seq=" << pMon->logical_device_seq
<< " qvvrdata.size=" << pMon->qvvrevent.qvvrdata.size()
<< std::endl;
qvvr_event& qe = pMon->qvvrevent;
// 3) 先尝试“就地更新”(同类型 + 同时间戳 视为同一事件)
for (size_t i = 0; i < qe.qvvrdata.size(); ++i) {
auto& q = qe.qvvrdata[i];
if (q.QVVR_type == nType && q.QVVR_time == triggerTimeMs) {
std::cout << "[append_qvvr_event][UPDATE match idx=" << i << "]"
<< " old{used=" << q.used_status
<< ", per=" << q.QVVR_PerTime
<< ", mag=" << q.QVVR_Amg
<< ", phase=" << q.phase
<< "} -> new{used=true"
<< ", per=" << fPersisstime_sec
<< ", mag=" << fMagnitude_pu
<< ", phase=" << phase
<< "}" << std::endl;
q.used_status = true;
q.QVVR_PerTime = fPersisstime_sec;
q.QVVR_Amg = fMagnitude_pu;
q.phase = phase;
std::cout << "[append_qvvr_event] done(update)."
<< std::endl;
return true;
}
}
// 4) 复用空槽used_status=false
for (size_t i = 0; i < qe.qvvrdata.size(); ++i) {
auto& q = qe.qvvrdata[i];
if (!q.used_status) {
std::cout << "[append_qvvr_event][REUSE idx=" << i << "]"
<< " set{type=" << nType
<< ", time_ms=" << static_cast<unsigned long long>(triggerTimeMs)
<< ", per=" << fPersisstime_sec
<< ", mag=" << fMagnitude_pu
<< ", phase=" << phase
<< "}" << std::endl;
q.used_status = true;
q.QVVR_type = nType;
q.QVVR_time = triggerTimeMs;
q.QVVR_PerTime = fPersisstime_sec;
q.QVVR_Amg = fMagnitude_pu;
q.phase = phase;
std::cout << "[append_qvvr_event] done(reuse)."
<< std::endl;
return true;
}
}
// 5) 直接追加
qvvr_data q{};
q.used_status = true;
q.QVVR_type = nType;
q.QVVR_time = triggerTimeMs; // ms
q.QVVR_PerTime = fPersisstime_sec; // s
q.QVVR_Amg = fMagnitude_pu;
q.phase = phase;
qe.qvvrdata.push_back(q);
std::cout << "[append_qvvr_event][PUSH_BACK]"
<< " new_size=" << qe.qvvrdata.size()
<< " last_idx=" << (qe.qvvrdata.empty() ? -1 : (int)qe.qvvrdata.size()-1)
<< " {type=" << nType
<< ", time_ms=" << static_cast<unsigned long long>(triggerTimeMs)
<< ", per=" << fPersisstime_sec
<< ", mag=" << fMagnitude_pu
<< ", phase=" << phase
<< "}" << std::endl;
std::cout << "[append_qvvr_event] done(push_back)."
<< std::endl;
return true;
}