3633 lines
149 KiB
C++
3633 lines
149 KiB
C++
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
#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 "log4cplus/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);
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////实时触发部分
|
||
|
||
//获取实时触发文件
|
||
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 {
|
||
std::vector<JournalRecall> recallParams;
|
||
|
||
// 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) {
|
||
// 获取必需字段
|
||
if (!item.contains("monitorId") ||
|
||
!item.contains("timeInterval") ||
|
||
!item.contains("dataType"))
|
||
{
|
||
std::cout << "json内容解析错误" << std::endl;
|
||
return 10000;
|
||
}
|
||
|
||
// 2.1 解析 dataType
|
||
std::string datatype = item["dataType"].get<std::string>();
|
||
if (!datatype.empty()) {
|
||
stat = (datatype == "0") ? 1 : 0; // 稳态
|
||
voltage = (datatype == "1") ? 1 : 0; // 暂态
|
||
} else {
|
||
stat = voltage = 1; // 全补
|
||
}
|
||
|
||
// 2.2 处理 monitorId 数组
|
||
auto& midArr = item["monitorId"];
|
||
auto& tiArr = item["timeInterval"];
|
||
if (midArr.is_array() && tiArr.is_array() && !midArr.empty()) {
|
||
for (auto& idItem : midArr) {
|
||
std::string monitorId = idItem.get<std::string>();
|
||
|
||
// 判断此监测点是否归属当前进程
|
||
bool mppair = false;
|
||
for (const auto& dev : terminal_devlist) {
|
||
// 只处理本进程对应的终端
|
||
if (std::stoi(dev.processNo) != g_front_seg_index &&
|
||
g_front_seg_index != 0) {
|
||
continue;
|
||
}
|
||
for (const auto& mon : dev.line) {
|
||
if (mon.monitor_id.empty()) continue;
|
||
if (mon.monitor_id == monitorId) {
|
||
mppair = true;
|
||
std::cout << "Matched monitorId " << monitorId
|
||
<< " in terminal " << dev.terminal_id
|
||
<< std::endl;
|
||
break;
|
||
}
|
||
}
|
||
if (mppair) break;
|
||
}
|
||
if (!mppair) continue;
|
||
|
||
// 遍历 timeInterval 数组
|
||
for (auto& timeItem : tiArr) {
|
||
std::string ti = timeItem.get<std::string>();
|
||
auto pos = ti.find('~');
|
||
std::string start = ti.substr(0, pos);
|
||
std::string end = ti.substr(pos + 1);
|
||
|
||
JournalRecall param;
|
||
param.MonitorID = monitorId;
|
||
param.StartTime = start;
|
||
param.EndTime = end;
|
||
param.STEADY = std::to_string(stat);
|
||
param.VOLTAGE = std::to_string(voltage);
|
||
recallParams.push_back(param);
|
||
}
|
||
}
|
||
}
|
||
// 2.3 monitorId 数组存在但为空 -> 补招所有监测点
|
||
else if (midArr.is_array() && midArr.empty()) {
|
||
std::cout << "monitorIdArray is null,补招所有监测点" << std::endl;
|
||
for (const auto& dev : terminal_devlist) {
|
||
if (std::stoi(dev.processNo) != g_front_seg_index &&
|
||
g_front_seg_index != 0) {
|
||
continue;
|
||
}
|
||
for (const auto& mon : dev.line) {
|
||
if (mon.monitor_id.empty()) continue;
|
||
for (auto& timeItem : tiArr) {
|
||
std::string ti = timeItem.get<std::string>();
|
||
auto pos = ti.find('~');
|
||
std::string start = ti.substr(0, pos);
|
||
std::string end = ti.substr(pos + 1);
|
||
|
||
JournalRecall param;
|
||
param.MonitorID = mon.monitor_id;
|
||
param.StartTime = start;
|
||
param.EndTime = end;
|
||
param.STEADY = std::to_string(stat);
|
||
param.VOLTAGE = std::to_string(voltage);
|
||
recallParams.push_back(param);
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
std::cout << "monitorIdArray 不存在或类型不正确" << std::endl;
|
||
}
|
||
}
|
||
|
||
// 3. 生成具体补招记录
|
||
for (auto& rp : recallParams) {
|
||
std::string start_time = rp.StartTime;
|
||
std::string end_time = rp.EndTime;
|
||
|
||
std::cout << "mp_id " << rp.MonitorID
|
||
<< " start_time " << start_time
|
||
<< " end_time " << end_time
|
||
<< " stat " << rp.STEADY
|
||
<< " voltage " << rp.VOLTAGE
|
||
<< std::endl;
|
||
|
||
std::vector<RecallInfo> recallinfo_list_hour;
|
||
Get_Recall_Time_Char(start_time, end_time, recallinfo_list_hour);
|
||
|
||
for (auto& info : recallinfo_list_hour) {
|
||
JournalRecall jr;
|
||
jr.MonitorID = rp.MonitorID;
|
||
|
||
// 转换 starttime
|
||
char buf[20];
|
||
std::tm tm{};
|
||
time_t st = static_cast<time_t>(info.starttime);
|
||
localtime_r(&st, &tm);
|
||
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
|
||
jr.StartTime = buf;
|
||
|
||
// 转换 endtime
|
||
std::tm tm2{};
|
||
time_t et = static_cast<time_t>(info.endtime);
|
||
localtime_r(&et, &tm2);
|
||
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm2);
|
||
jr.EndTime = buf;
|
||
|
||
jr.STEADY = rp.STEADY;
|
||
jr.VOLTAGE = rp.VOLTAGE;
|
||
|
||
std::lock_guard<std::mutex> lk(g_StatisticLackList_list_mutex);
|
||
g_StatisticLackList.push_back(jr);
|
||
}
|
||
}
|
||
}
|
||
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 ¤t_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, //逻辑子设备ID,0-逻辑设备本身,无填-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<float> &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_FIXEDVALUE;isbusy == 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;
|
||
}
|
||
|
||
// 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> lk(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_set_value_reply] device not found: " << dev_id << std::endl;
|
||
return false;
|
||
}
|
||
terminal_dev &dev = *it;
|
||
|
||
// 2) 校验状态:发送“定值读取结果”回复,应处于 READING_INTERFIXEDVALUE;isbusy == 1
|
||
if (dev.isbusy != 1 || dev.busytype != static_cast<int>(DeviceState::READING_INTERFIXEDVALUE)) { //定值读取
|
||
std::cerr << "[send_set_value_reply] device not in READING_INTERFIXEDVALUE state." << 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["DataType"] = 0x0D; //内部定值
|
||
|
||
// DataArray(对象数组):逐个填充,DZ_Value 严格按 set_values 顺序
|
||
nlohmann::json dataArray = nlohmann::json::array();
|
||
|
||
//拼接逻辑
|
||
|
||
// 6) 入队发送
|
||
queue_data_t connect_info;
|
||
connect_info.strTopic = Topic_Reply_Topic;
|
||
connect_info.strText = j.dump(); // 序列化为字符串
|
||
|
||
{
|
||
std::lock_guard<std::mutex> lk(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;
|
||
|
||
// 7) 发送后更新终端状态(按你现有规则)
|
||
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;
|
||
} |