Files
front_linux/LFtid1056/cloudfront/code/cfg_parser.cpp
2025-09-10 16:59:50 +08:00

4159 lines
174 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映射文件解析数据
////////////////////////////////////////////////////////////////////////////////////////////////////
//补招
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
//生产者
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;
// [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(const std::string& jstr) {
// 不指定稳态/暂态则全部补招
int stat = 0;
int voltage = 0;
try {
// 1. 解析 JSON 数组
auto json_root = nlohmann::json::parse(jstr);
if (!json_root.is_array()) {
std::cout << "json root解析错误" << std::endl;
return 10000;
}
// 2. 遍历每个补招项
for (auto& item : json_root) {
// 获取必需字段
// ★修改:强制要求 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 = nullptr;
for (const auto& dev : terminal_devlist) {
// 只处理本进程对应的终端
if (dev.terminal_id == terminalId) {
targetDev = &dev;
break;
}
}
if (!targetDev) {
std::cout << "terminalId未匹配当前进程内装置: " << terminalId << std::endl;
continue;
}
// ★新增:按新结构解析 monitor 层级
auto& 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 = nullptr;
for (auto& mon : const_cast<terminal_dev*>(targetDev)->line) {
if (!mon.monitor_id.empty() && mon.monitor_id == monitorId) {
lm = &mon;
break;
}
}
if (!lm) {
std::cout << "monitorId未在terminal内找到: " << monitorId
<< " @ " << terminalId << std::endl;
continue;
}
auto& tiArr = mobj["timeInterval"];
if (!tiArr.is_array() || tiArr.empty()) {
std::cout << "timeInterval为空或非数组类型: monitorId=" << monitorId << std::endl;
continue;
}
// 这里拆分时间段并 push 到 lm->recall_list
for (auto& timeItem : tiArr) {
std::string ti = timeItem.get<std::string>();
auto 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);
// 拆分时间段为 1 小时一段,并存入 recall_list
std::vector<RecallInfo> recallinfo_list_hour;
Get_Recall_Time_Char(start, end, recallinfo_list_hour);
for (auto& info : recallinfo_list_hour) {
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(std::move(rm)); // ★逐一压入
// 可选:调试打印
std::cout << "[recall_json_handle] terminal=" << terminalId
<< " monitor=" << monitorId
<< " 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;
}
}
}
}
}
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;
}
// 遍历所有暂态事件,查找与 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";
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::vector<std::string> file_names;
// 1. 提取文件名部分
for (const auto& full_path : file_list_raw) {
size_t pos = full_path.find_last_of("/\\");
if (pos != std::string::npos && pos + 1 < full_path.size()) {
file_names.push_back(full_path.substr(pos + 1));
} else {
file_names.push_back(full_path);
}
}
// 2. 遍历终端
for (auto& dev : terminal_devlist) {
if (dev.terminal_id == id) { //根据终端id匹配终端
for (auto& monitor : dev.line) {
try {
ushort monitor_seq = static_cast<ushort>(std::stoi(monitor.logical_device_seq));
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;
}
//发送匹配的所有录波文件
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/" + 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, const std::string& terminal_id) {
//台账加锁
std::lock_guard<std::mutex> lock(ledgermtx);
// 去除 mac 路径前缀,仅保留文件名
std::string filename = extract_filename1(filename_with_mac);
// 提取逻辑序号(如 PQM1 → 1
size_t under_pos = filename.find('_');
if (under_pos == std::string::npos) return false;
std::string type_part = filename.substr(0, under_pos); // PQMonitor_PQM1
size_t num_start = type_part.find_last_not_of("0123456789");
if (num_start == std::string::npos || num_start + 1 >= type_part.size()) return false;
std::string seq_str = type_part.substr(num_start + 1);
ushort logical_seq = static_cast<ushort>(std::stoi(seq_str)); // 逻辑序号
//找终端
for (auto& dev : terminal_devlist) {
if (dev.terminal_id != terminal_id) 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) continue;
} catch (...) {
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()) {
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(extract_filename1(path)); // 提取每个路径中的文件名
}
// 检查 file_download 是否与 file_name 完全一致(集合相同)
if (s_name == s_down) {
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::string wavepath;
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);
// 删除上传成功的文件
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";
}
}
// 清除已发送的暂态文件
monitor.qvvrevent.qvvrfile.erase(monitor.qvvrevent.qvvrfile.begin() + i);
//清除暂态事件
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);
}
}
}
break; // 只处理第一个 cfg 文件
}
}
}
else{
std::cout << "qvvr file still imcomplete!!!" << std::endl;
}
return true; // 当前文件处理成功
}
}
std::cout << "file name doesnt match any file in this monitor!!!" << std::endl;
}
}
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;
}
//找到dev的mac
std::string get_mac_by_devid(const std::string &devid) {
std::lock_guard<std::mutex> lock(ledgermtx);
for (auto &dev : terminal_devlist) {
if (dev.terminal_id == devid) {
return normalize_mac(dev.addr_str); // 规范化后返回
}
}
return {}; // 没找到返回空串
}
////////////////////////////////////////////////////////////////////////////////////////目录信息发送接口函数
bool send_file_list(const std::string &dev_id, const std::vector<tag_dir_info> &FileList) {
// 找到对应 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_file_list] device not found: " << dev_id << std::endl;
return false;
}
terminal_dev &dev = *it;
// 判断 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(); // 序列化为字符串
{
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::BAD_REQUEST),dev.terminal_id,get_type_by_state(dev.busytype));
// 超时清空状态
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;
// 超时清空状态
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(); // 序列化为字符串
{
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 是引用成员,确保不会因并发被改动(当前已在 ledgermtx 下)
//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(); // 序列化为字符串
{
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;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////清空运行状态
// 清理指定 terminal_id 的运行时状态
void clear_terminal_runtime_state(const std::string& id) {
std::lock_guard<std::mutex> lock(ledgermtx); // 加锁保证线程安全
for (auto& dev : terminal_devlist) {
if (dev.terminal_id == id) {
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; // 找到后跳出
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////处理补招逻辑
// ===== 一次遍历可下发“多个终端的一条” =====
void check_recall_event() {
std::vector<RecallTask> tasks; // 本轮要发送的“每终端一条”
std::vector<std::string> empty_devs_to_clear; // 本轮发现已无任务的终端,解锁后统一清理
{ //锁作用域
std::lock_guard<std::mutex> lock(ledgermtx);
// 遍历所有 terminal —— 每个 terminal 只挑一条
for (auto& dev : terminal_devlist) {
// 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_work] DONE dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
//调用reply接口通知web端补招完成
lm.recall_list.pop_front(); // 弹掉首条
} else if (front.recall_status == static_cast<int>(RecallStatus::FAILED)) {
std::cout << "[check_recall_work] FAILED dev=" << dev.terminal_id
<< " monitor=" << lm.monitor_id
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
//调用reply接口通知web端补招失败
lm.recall_list.pop_front(); // 弹掉首条
} else {
break; // 首条不是 2/3
}
}
if (!lm.recall_list.empty()) any_non_empty = true; // 处理了成功和失败的以后只要有一条非空就标记,可能是待处理或者正在处理的补招
}
if (!any_non_empty) {
// 该终端本轮已无任何补招条目,等解锁后清空运行态
empty_devs_to_clear.push_back(dev.terminal_id);
continue;
}
// 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);
dev.busytimecount = 0;
// 如需 guid可在此生成 dev.guid = new_guid();
// 记录任务(每终端只取这一条)
tasks.push_back(RecallTask{
dev.terminal_id,
front.StartTime,
front.EndTime,
lm.monitor_id
});
picked = true;
break;
}
}
// 若该终端没有 NOT_STARTED 的首条(可能剩下的都是 RUNNING 或内部已经被清空),
// 就留待下一轮;不要清空运行态,直到所有条目被处理为止。
}
} // 解锁
// 先处理“已空终端”的运行态清理,如果在上面循环中直接清理,可能会与正在处理的补招冲突。而且就算上面解锁后写了新的补招,也会在下一轮被执行。
for (const auto& id : empty_devs_to_clear) {
clear_terminal_runtime_state(id);
}
// 对于本轮挑选到的任务,逐终端下发一条,这些终端的状态都已设为补招
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 (...) { /* 保持默认1或根据需要处理 */ }
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 全部清空后调用 clear_terminal_runtime_state()。
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////通讯响应处理
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 {
// 准备日志用的 key
std::ostringstream mon_key_c, mon_key_d;
mon_key_c << "monitor." << matched_monitor->terminal_id << "." << matched_monitor->logical_device_seq << ".COM";
mon_key_d << "monitor." << matched_monitor->terminal_id << "." << matched_monitor->logical_device_seq << ".DATA";
// 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(mon_key_d.str().c_str(),static_cast<int>(LogCode::LOG_CODE_RECALL),"【ERROR】监测点:%s 补招数据失败 - 失败时间点:%lld 至 %lld",mon_key_d.str().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) {
// 成功:复位
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.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当前业务为“下载事件文件/统计文件”(两者处理相同) ======
if (ok) {
// 成功:通常紧接着进入读取文件数据阶段
std::cout << "[RESP][FILEMENU->FILEDATA][OK] dev=" << id << std::endl;
} else {
// 失败:尝试下一个目录
std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][FAIL] dev=" << id
<< " rc=" << response_code << std::endl;
}
}
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.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.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当前业务为“下载事件文件/统计文件”(两者处理相同) ======
if (ok) {
// 成功:通常紧接着进入读取文件数据阶段
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][OK] dev=" << id << std::endl;
} else {
// 失败:尝试下一个目录
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][FAIL] dev=" << id
<< " rc=" << response_code << std::endl;
}
}
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: {
//直接根据输入响应mq
send_reply_to_cloud(response_code, id, device_state_int);
//其他的错误和成功都会结束业务
clear_terminal_runtime_state(id);
break;
}
}
}