2025-06-24 17:55:34 +08:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
#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"
|
2025-09-10 16:59:50 +08:00
|
|
|
|
#include "log4.h" //关键上送日志
|
2025-06-24 17:55:34 +08:00
|
|
|
|
#include "interface.h" //台账结构
|
|
|
|
|
|
#include "tinyxml2.h"
|
|
|
|
|
|
#include "rocketmq.h"
|
|
|
|
|
|
|
2025-06-26 16:44:21 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
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映射文件解析数据
|
|
|
|
|
|
|
2025-09-24 15:58:50 +08:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
extern time_t ConvertToTimestamp(const tagTime& time);
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
2025-09-30 16:17:04 +08:00
|
|
|
|
//实时数据时间记录
|
|
|
|
|
|
std::mutex g_last_ts_mtx;
|
|
|
|
|
|
std::unordered_map<std::string, time_t> g_last_ts_by_devid;
|
|
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
static std::mutex g_filemenu_cache_mtx;
|
|
|
|
|
|
std::map<std::string, std::vector<tag_dir_info>> g_filemenu_cache;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
//补招
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2025-09-22 13:26:52 +08:00
|
|
|
|
//实时数据tagkey
|
|
|
|
|
|
std::string G_RT_TAG = "";//tag
|
|
|
|
|
|
std::string G_RT_KEY = "";//key
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
//生产者
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2025-08-08 16:35:36 +08:00
|
|
|
|
std::string G_MQCONSUMER_TOPIC_CLOUD = "";//consumer topie
|
|
|
|
|
|
std::string G_MQCONSUMER_TAG_CLOUD = "";//consumer tag
|
|
|
|
|
|
std::string G_MQCONSUMER_KEY_CLOUD = "";//consumer key
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
//测试用的主题
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2025-09-22 13:26:52 +08:00
|
|
|
|
//添加rt的tagkey
|
|
|
|
|
|
strMap["Queue.RT_TAG"] = &G_RT_TAG;
|
|
|
|
|
|
strMap["Queue.RT_KEY"] = &G_RT_KEY;
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
// [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;
|
|
|
|
|
|
|
2025-08-08 16:35:36 +08:00
|
|
|
|
strMap["RocketMq.ConsumerTopicCLOUD"] = &G_MQCONSUMER_TOPIC_CLOUD;
|
|
|
|
|
|
strMap["RocketMq.ConsumerTagCLOUD"] = &G_MQCONSUMER_TAG_CLOUD;
|
|
|
|
|
|
strMap["RocketMq.ConsumerKeyCLOUD"] = &G_MQCONSUMER_KEY_CLOUD;
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//测试进程端口
|
2025-06-27 16:33:41 +08:00
|
|
|
|
/*if (g_node_id == STAT_DATA_BASE_NODE_ID)//统计采集
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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;
|
2025-06-27 16:33:41 +08:00
|
|
|
|
}*/
|
|
|
|
|
|
TEST_PORT = TEST_PORT + g_front_seg_index;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////获取当前时间
|
|
|
|
|
|
|
|
|
|
|
|
// 用于获取当前时间,单位毫秒
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
//秒时间转为标准时间字符串
|
|
|
|
|
|
// ★新增:将 epoch 秒级时间转成 "YYYY-MM-DD HH:MM:SS"
|
|
|
|
|
|
static std::string epoch_to_datetime_str(long long epoch_sec) {
|
|
|
|
|
|
char buf[20];
|
|
|
|
|
|
std::tm tm{};
|
|
|
|
|
|
time_t t = static_cast<time_t>(epoch_sec);
|
|
|
|
|
|
localtime_r(&t, &tm);
|
|
|
|
|
|
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
|
|
|
|
|
|
return std::string(buf);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////实时触发部分
|
|
|
|
|
|
|
|
|
|
|
|
//获取实时触发文件
|
|
|
|
|
|
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); // 根据文件处理数据
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////补招文件部分
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
//将文件内容读取到结构中
|
|
|
|
|
|
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) {
|
2025-06-27 16:33:41 +08:00
|
|
|
|
DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 无法解析补招文件,补招文件路径FRONT_PATH + /etc/recall/不存在", g_front_seg_index);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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) {
|
2025-06-27 16:33:41 +08:00
|
|
|
|
DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 无法解析补招文件%s,补招内容无效", g_front_seg_index, filepath.c_str());
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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) {
|
2025-06-27 16:33:41 +08:00
|
|
|
|
/*if (g_node_id == HIS_DATA_BASE_NODE_ID ||
|
2025-06-24 17:55:34 +08:00
|
|
|
|
g_node_id == NEW_HIS_DATA_BASE_NODE_ID ||
|
|
|
|
|
|
g_node_id == RECALL_HIS_DATA_BASE_NODE_ID ||
|
2025-06-27 16:33:41 +08:00
|
|
|
|
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);
|
|
|
|
|
|
//}
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//补招成功后删除补招文件,补招后调用这个
|
|
|
|
|
|
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;
|
2025-06-27 16:33:41 +08:00
|
|
|
|
DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 删除旧的补招文件失败,补招文件路径FRONT_PATH + /etc/recall/不存在", g_front_seg_index);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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) {
|
2025-06-27 16:33:41 +08:00
|
|
|
|
DIY_INFOLOG("process", "【NORMAL】前置的%d号进程 删除超过两天的补招文件", g_front_seg_index);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
} 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()) {
|
2025-06-27 16:33:41 +08:00
|
|
|
|
DIY_INFOLOG("process", "【NORMAL】前置的%d号进程 开始写入补招文件", g_front_seg_index);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
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) {
|
2025-06-27 16:33:41 +08:00
|
|
|
|
DIY_ERRORLOG("process", "【ERROR】前置的%d号进程 无法将补招文件写入路径: %s", g_front_seg_index, path.str().c_str());
|
2025-06-24 17:55:34 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
g_StatisticLackList.clear();
|
|
|
|
|
|
g_StatisticLackList_list_mutex.unlock();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//生成待补招xml文件
|
|
|
|
|
|
void create_recall_xml()
|
|
|
|
|
|
{
|
2025-06-27 16:33:41 +08:00
|
|
|
|
//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();
|
|
|
|
|
|
//}
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
2025-09-10 16:59:50 +08:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////补招部分
|
2025-06-24 17:55:34 +08:00
|
|
|
|
// 工具函数:将时间字符串转为 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调用将补招信息写入补招列表
|
2025-09-15 16:36:21 +08:00
|
|
|
|
int recall_json_handle_from_mq(const std::string& body)
|
|
|
|
|
|
{
|
2025-06-24 17:55:34 +08:00
|
|
|
|
try {
|
2025-09-15 16:36:21 +08:00
|
|
|
|
// ====== 解析外层 JSON ======
|
|
|
|
|
|
nlohmann::json root;
|
|
|
|
|
|
try {
|
|
|
|
|
|
root = nlohmann::json::parse(body);
|
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
|
std::cerr << "Error parsing JSON: " << e.what() << std::endl;
|
|
|
|
|
|
// ★与原逻辑等价:无法解析,不再进入 recall_json_handle
|
|
|
|
|
|
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,消息的json结构不正确",
|
|
|
|
|
|
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
|
|
|
|
|
|
return 10004;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取 "messageBody"(字符串)
|
|
|
|
|
|
if (!root.contains("messageBody") || !root["messageBody"].is_string()) {
|
|
|
|
|
|
std::cerr << "'messageBody' is missing or is not a string" << std::endl;
|
|
|
|
|
|
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,没有messageBody字段",
|
|
|
|
|
|
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
|
|
|
|
|
|
return 10004;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::string messageBodyStr = root["messageBody"].get<std::string>();
|
|
|
|
|
|
if (messageBodyStr.empty()) {
|
|
|
|
|
|
std::cerr << "'messageBody' is empty" << std::endl;
|
|
|
|
|
|
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,messageBody为空",
|
|
|
|
|
|
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
|
|
|
|
|
|
return 10004;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析 messageBody 内层 JSON
|
|
|
|
|
|
nlohmann::json messageBody;
|
|
|
|
|
|
try {
|
|
|
|
|
|
messageBody = nlohmann::json::parse(messageBodyStr);
|
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
|
std::cerr << "Failed to parse 'messageBody' JSON: " << e.what() << std::endl;
|
|
|
|
|
|
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,messageBody的json结构不正确",
|
|
|
|
|
|
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
|
|
|
|
|
|
return 10004;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取 guid 并立即回执
|
|
|
|
|
|
if (!messageBody.contains("guid") || !messageBody["guid"].is_string()) {
|
|
|
|
|
|
std::cerr << "'guid' is missing or is not a string" << std::endl;
|
|
|
|
|
|
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,没有guid字段",
|
|
|
|
|
|
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
|
|
|
|
|
|
return 10004;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::string guid = messageBody["guid"].get<std::string>();
|
2025-10-11 09:08:43 +08:00
|
|
|
|
//send_reply_to_queue(guid, static_cast<int>(ResponseCode::OK), "收到补招指令");
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 提取 data 数组
|
|
|
|
|
|
if (!messageBody.contains("data") || !messageBody["data"].is_array()) {
|
|
|
|
|
|
std::cerr << "'data' is missing or is not an array" << std::endl;
|
|
|
|
|
|
DIY_ERRORLOG("process","【ERROR】前置的%d号进程处理topic:%s_%s的补招触发消息失败,没有data字段",
|
|
|
|
|
|
g_front_seg_index, FRONT_INST.c_str(), G_MQCONSUMER_TOPIC_RC.c_str());
|
|
|
|
|
|
return 10004;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 仅用于保留你原先的调试输出
|
|
|
|
|
|
std::string data_dump;
|
|
|
|
|
|
try { data_dump = messageBody["data"].dump(); } catch (...) { data_dump.clear(); }
|
|
|
|
|
|
std::cout << "parseJsonMessageRC: " << data_dump << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
// 不指定稳态/暂态则全部补招
|
|
|
|
|
|
int stat = 0;
|
|
|
|
|
|
int voltage = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 遍历每个补招项(这里直接用已解析的 messageBody["data"])
|
|
|
|
|
|
for (auto& item : messageBody["data"]) {
|
2025-06-24 17:55:34 +08:00
|
|
|
|
// 获取必需字段
|
2025-09-10 16:59:50 +08:00
|
|
|
|
// ★修改:强制要求 terminalId;
|
|
|
|
|
|
if (!item.contains("terminalId") ||
|
2025-09-15 16:36:21 +08:00
|
|
|
|
!item.contains("monitor") ||
|
2025-06-24 17:55:34 +08:00
|
|
|
|
!item.contains("timeInterval") ||
|
|
|
|
|
|
!item.contains("dataType"))
|
|
|
|
|
|
{
|
|
|
|
|
|
std::cout << "json内容解析错误" << std::endl;
|
|
|
|
|
|
return 10000;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
// ★新增:读取 terminalId(必填)
|
|
|
|
|
|
std::string terminalId = item["terminalId"].get<std::string>();
|
|
|
|
|
|
if (terminalId.empty()) {
|
|
|
|
|
|
std::cout << "terminalId为空" << std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-15 16:36:21 +08:00
|
|
|
|
// 2.1 解析 dataType(仅保留稳态/暂态)
|
2025-06-24 17:55:34 +08:00
|
|
|
|
std::string datatype = item["dataType"].get<std::string>();
|
|
|
|
|
|
if (!datatype.empty()) {
|
2025-09-10 16:59:50 +08:00
|
|
|
|
if (datatype == "0" || datatype == "稳态" || datatype == "steady" || datatype == "STEADY") {
|
|
|
|
|
|
stat = 1; voltage = 0; // 稳态
|
|
|
|
|
|
} else if (datatype == "1" || datatype == "暂态" || datatype == "voltage" || datatype == "VOLTAGE") {
|
|
|
|
|
|
stat = 0; voltage = 1; // 暂态
|
|
|
|
|
|
} else {
|
|
|
|
|
|
stat = voltage = 1; // 其他情况按全补
|
|
|
|
|
|
}
|
2025-06-24 17:55:34 +08:00
|
|
|
|
} else {
|
2025-09-10 16:59:50 +08:00
|
|
|
|
stat = voltage = 1; // 全补
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
// ★新增:定位并校验该 terminal 是否归属当前进程
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(ledgermtx);
|
|
|
|
|
|
|
2025-09-15 16:36:21 +08:00
|
|
|
|
const terminal_dev* targetDev = NULL;
|
|
|
|
|
|
for (std::vector<terminal_dev>::const_iterator it = terminal_devlist.begin();
|
|
|
|
|
|
it != terminal_devlist.end(); ++it)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (it->terminal_id == terminalId) {
|
|
|
|
|
|
targetDev = &(*it);
|
2025-09-10 16:59:50 +08:00
|
|
|
|
break;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-10 16:59:50 +08:00
|
|
|
|
if (!targetDev) {
|
|
|
|
|
|
std::cout << "terminalId未匹配当前进程内装置: " << terminalId << std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-15 16:36:21 +08:00
|
|
|
|
// 添加判断装置在线,不注册guid,异步补招
|
|
|
|
|
|
if (ClientManager::instance().get_dev_status(targetDev->terminal_id) != 1) {
|
|
|
|
|
|
std::cout << "terminalId对应装置不在线: " << targetDev->terminal_id << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
// 响应 web
|
|
|
|
|
|
std::string msg = std::string("装置:") + targetDev->terminal_name + " 不在线,无法补招";
|
|
|
|
|
|
send_reply_to_kafka_recall("12345", "2", static_cast<int>(ResponseCode::INTERNAL_ERROR), msg, targetDev->terminal_id, "", "", "");
|
|
|
|
|
|
continue;//处理下一个装置的补招记录
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
// ★新增:按新结构解析 monitor 层级
|
2025-09-15 16:36:21 +08:00
|
|
|
|
nlohmann::json& monitors = item["monitor"];
|
2025-09-10 16:59:50 +08:00
|
|
|
|
if (!monitors.is_array() || monitors.empty()) {
|
|
|
|
|
|
std::cout << "monitor数组为空或非数组类型" << std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (auto& mobj : monitors) {
|
|
|
|
|
|
if (!mobj.contains("monitorId") || !mobj.contains("timeInterval")) {
|
|
|
|
|
|
std::cout << "monitor项缺少 monitorId 或 timeInterval" << std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
std::string monitorId = mobj["monitorId"].get<std::string>();
|
|
|
|
|
|
if (monitorId.empty()) continue;
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
// 不只是判断存在,还要拿到指针 lm 以便后续 push_back
|
2025-09-15 16:36:21 +08:00
|
|
|
|
ledger_monitor* lm = NULL;
|
|
|
|
|
|
// 注意:这里需要非常量指针,取 const_cast 后遍历
|
|
|
|
|
|
terminal_dev* dev_nc = const_cast<terminal_dev*>(targetDev);
|
|
|
|
|
|
for (std::vector<ledger_monitor>::iterator itLm = dev_nc->line.begin();
|
|
|
|
|
|
itLm != dev_nc->line.end(); ++itLm)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!itLm->monitor_id.empty() && itLm->monitor_id == monitorId) {
|
|
|
|
|
|
lm = &(*itLm);
|
2025-09-10 16:59:50 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!lm) {
|
|
|
|
|
|
std::cout << "monitorId未在terminal内找到: " << monitorId
|
|
|
|
|
|
<< " @ " << terminalId << std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
|
|
|
|
|
nlohmann::json& tiArr = mobj["timeInterval"];
|
2025-09-10 16:59:50 +08:00
|
|
|
|
if (!tiArr.is_array() || tiArr.empty()) {
|
|
|
|
|
|
std::cout << "timeInterval为空或非数组类型: monitorId=" << monitorId << std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 这里拆分时间段并 push 到 lm->recall_list / lm->recall_list_static
|
2025-09-10 16:59:50 +08:00
|
|
|
|
for (auto& timeItem : tiArr) {
|
|
|
|
|
|
std::string ti = timeItem.get<std::string>();
|
2025-09-15 16:36:21 +08:00
|
|
|
|
std::string::size_type pos = ti.find('~');
|
2025-09-10 16:59:50 +08:00
|
|
|
|
if (pos == std::string::npos) {
|
|
|
|
|
|
std::cout << "timeInterval格式错误: " << ti << std::endl;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2025-09-10 16:59:50 +08:00
|
|
|
|
std::string start = ti.substr(0, pos);
|
|
|
|
|
|
std::string end = ti.substr(pos + 1);
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 仅对 recall_list(事件)进行 1 小时拆分;recall_list_static(稳态)不拆分
|
2025-09-12 17:08:25 +08:00
|
|
|
|
{
|
2025-09-15 16:36:21 +08:00
|
|
|
|
// 公共字段(整体区间,不拆分)用于 recall_list_static
|
|
|
|
|
|
RecallFile rm_all; // ★类型正确:稳态列表的元素
|
|
|
|
|
|
rm_all.recall_status = 0; // 初始状态:未补招
|
|
|
|
|
|
rm_all.StartTime = start; // 直接使用字符串
|
|
|
|
|
|
rm_all.EndTime = end;
|
|
|
|
|
|
rm_all.STEADY = std::to_string(stat);
|
|
|
|
|
|
rm_all.VOLTAGE = std::to_string(voltage);
|
|
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
// 仅当需要事件补招(voltage==1)时,才进行 1 小时拆分并压入 recall_list
|
|
|
|
|
|
if (voltage == 1) {
|
|
|
|
|
|
// 拆分时间段为 1 小时一段,并存入 recall_list
|
|
|
|
|
|
std::vector<RecallInfo> recallinfo_list_hour;
|
|
|
|
|
|
Get_Recall_Time_Char(start, end, recallinfo_list_hour);
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
|
|
|
|
|
for (std::size_t i = 0; i < recallinfo_list_hour.size(); ++i) {
|
|
|
|
|
|
const RecallInfo& info = recallinfo_list_hour[i];
|
|
|
|
|
|
|
|
|
|
|
|
RecallMonitor rm; // ★类型正确:事件列表的元素
|
|
|
|
|
|
rm.recall_status = 0; // 初始状态:未补招
|
|
|
|
|
|
rm.StartTime = epoch_to_datetime_str(info.starttime);
|
|
|
|
|
|
rm.EndTime = epoch_to_datetime_str(info.endtime);
|
|
|
|
|
|
rm.STEADY = std::to_string(stat);
|
|
|
|
|
|
rm.VOLTAGE = std::to_string(voltage);
|
|
|
|
|
|
|
|
|
|
|
|
lm->recall_list.push_back(rm);
|
|
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
// 事件补招列表(recall_list)调试打印
|
|
|
|
|
|
std::cout << "[recall_json_handle] terminal=" << terminalId
|
|
|
|
|
|
<< " monitor=" << monitorId
|
|
|
|
|
|
<< " [recall_list] start=" << lm->recall_list.back().StartTime
|
|
|
|
|
|
<< " end=" << lm->recall_list.back().EndTime
|
|
|
|
|
|
<< " steady="<< lm->recall_list.back().STEADY
|
|
|
|
|
|
<< " voltage="<< lm->recall_list.back().VOLTAGE
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
// 仅当需要稳态补招(stat==1)时,不拆分,直接压入 recall_list_static
|
|
|
|
|
|
if (stat == 1) {
|
|
|
|
|
|
lm->recall_list_static.push_back(rm_all); // 不拆分,整体区间
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 稳态补招列表(recall_list_static)调试打印
|
2025-09-12 17:08:25 +08:00
|
|
|
|
std::cout << "[recall_json_handle] terminal=" << terminalId
|
|
|
|
|
|
<< " monitor=" << monitorId
|
|
|
|
|
|
<< " [recall_list_static] start=" << lm->recall_list_static.back().StartTime
|
|
|
|
|
|
<< " end=" << lm->recall_list_static.back().EndTime
|
|
|
|
|
|
<< " steady="<< lm->recall_list_static.back().STEADY
|
|
|
|
|
|
<< " voltage="<< lm->recall_list_static.back().VOLTAGE
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
}
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 非法输入保护(保留你原来的保护与返回码)
|
2025-09-12 17:08:25 +08:00
|
|
|
|
if (stat == 0 && voltage == 0) {
|
|
|
|
|
|
std::cout << "[recall_json_handle] skip: stat=0 && voltage=0, monitor=" << monitorId
|
|
|
|
|
|
<< " terminal=" << terminalId
|
|
|
|
|
|
<< " start=" << rm_all.StartTime
|
|
|
|
|
|
<< " end=" << rm_all.EndTime
|
|
|
|
|
|
<< std::endl;
|
2025-09-15 16:36:21 +08:00
|
|
|
|
return 10003;
|
2025-09-12 17:08:25 +08:00
|
|
|
|
}
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
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
|
2025-06-26 14:39:34 +08:00
|
|
|
|
<< ", Terminal Code: " << dev.terminal_name
|
2025-06-24 17:55:34 +08:00
|
|
|
|
<< ", 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
|
2025-06-26 14:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
<< ", mac: " << dev.mac
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
<< 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
|
2025-06-26 14:39:34 +08:00
|
|
|
|
<< ", Code: " << m.terminal_id
|
2025-06-24 17:55:34 +08:00
|
|
|
|
<< ", Name: " << m.monitor_name
|
|
|
|
|
|
<< ", Seq: " << m.logical_device_seq
|
|
|
|
|
|
<< ", Voltage: "<< m.voltage_level
|
|
|
|
|
|
<< ", Connect: "<< m.terminal_connect
|
|
|
|
|
|
<< ", Timestamp:"<< m.timestamp
|
|
|
|
|
|
<< ", Status: " << m.status
|
2025-06-26 14:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
<< ", CT1: " << m.CT1
|
|
|
|
|
|
<< ", CT2: " << m.CT2
|
|
|
|
|
|
<< ", PT1: " << m.PT1
|
|
|
|
|
|
<< ", PT2: " << m.PT2
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
<< 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,
|
2025-09-25 16:36:04 +08:00
|
|
|
|
const update_dev& work_terminal) {
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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) {
|
2025-09-25 16:36:04 +08:00
|
|
|
|
update_dev work_terminal;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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");
|
2025-06-26 14:39:34 +08:00
|
|
|
|
work_terminal.terminal_name = get_value("terminalCode");
|
2025-09-25 16:36:04 +08:00
|
|
|
|
//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");
|
2025-06-24 17:55:34 +08:00
|
|
|
|
work_terminal.tmnl_status = get_value("status");
|
|
|
|
|
|
work_terminal.dev_type = get_value("devType");
|
2025-09-25 16:36:04 +08:00
|
|
|
|
//work_terminal.dev_key = get_value("devKey");
|
|
|
|
|
|
//work_terminal.dev_series = get_value("series");
|
2025-06-24 17:55:34 +08:00
|
|
|
|
work_terminal.processNo = get_value("processNo");
|
2025-09-25 16:36:04 +08:00
|
|
|
|
//work_terminal.addr_str = get_value("ip");
|
|
|
|
|
|
//work_terminal.port = get_value("port");
|
|
|
|
|
|
//work_terminal.timestamp = get_value("updateTime");
|
|
|
|
|
|
work_terminal.Righttime = get_value("Righttime");
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2025-06-26 14:39:34 +08:00
|
|
|
|
work_terminal.mac = get_value("mac");
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
for (tinyxml2::XMLElement* monitor = root->FirstChildElement("monitorData");
|
|
|
|
|
|
monitor;
|
|
|
|
|
|
monitor = monitor->NextSiblingElement("monitorData")) {
|
2025-09-25 16:36:04 +08:00
|
|
|
|
update_monitor mon;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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";
|
2025-09-25 16:36:04 +08:00
|
|
|
|
//mon.timestamp = monitor->FirstChildElement("timestamp") ? monitor->FirstChildElement("timestamp")->GetText() : "N/A";
|
2025-06-26 14:39:34 +08:00
|
|
|
|
mon.terminal_id = monitor->FirstChildElement("terminal_id") ? monitor->FirstChildElement("terminal_name")->GetText() : "N/A";
|
2025-06-24 17:55:34 +08:00
|
|
|
|
mon.status = monitor->FirstChildElement("status") ? monitor->FirstChildElement("status")->GetText() : "N/A";
|
|
|
|
|
|
|
2025-06-26 14:39:34 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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") {
|
2025-09-25 16:36:04 +08:00
|
|
|
|
update_dev delete_terminal;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//更新单个台账
|
2025-09-25 16:36:04 +08:00
|
|
|
|
int update_one_terminal_ledger(const update_dev& update,terminal_dev& target_dev) {
|
2025-06-24 17:55:34 +08:00
|
|
|
|
// 更新基本信息
|
|
|
|
|
|
if (!update.terminal_id.empty()) {
|
|
|
|
|
|
target_dev.terminal_id = update.terminal_id;
|
|
|
|
|
|
std::cout << "terminal_id: " << target_dev.terminal_id << std::endl;
|
|
|
|
|
|
}
|
2025-06-26 14:39:34 +08:00
|
|
|
|
if (!update.terminal_name.empty()) {
|
|
|
|
|
|
target_dev.terminal_name = update.terminal_name;
|
|
|
|
|
|
std::cout << "terminal_name: " << target_dev.terminal_name << std::endl;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
2025-09-25 16:36:04 +08:00
|
|
|
|
/*if (!update.tmnl_factory.empty()) {
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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;
|
2025-09-25 16:36:04 +08:00
|
|
|
|
}*/
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-09-25 16:36:04 +08:00
|
|
|
|
/*if (!update.dev_series.empty()) {
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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;
|
2025-09-25 16:36:04 +08:00
|
|
|
|
}*/
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
if (!update.addr_str.empty()) {
|
|
|
|
|
|
target_dev.addr_str = update.addr_str;
|
|
|
|
|
|
std::cout << "addr_str: " << target_dev.addr_str << std::endl;
|
|
|
|
|
|
}
|
2025-09-25 16:36:04 +08:00
|
|
|
|
/*if (!update.port.empty()) {
|
2025-06-24 17:55:34 +08:00
|
|
|
|
target_dev.port = update.port;
|
|
|
|
|
|
std::cout << "port: " << target_dev.port << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-26 14:39:34 +08:00
|
|
|
|
if (!update.mac.empty()) {
|
|
|
|
|
|
target_dev.mac = update.mac;
|
|
|
|
|
|
std::cout << "mac: " << target_dev.mac << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-09-25 16:36:04 +08:00
|
|
|
|
}*/
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 清空旧监测点并重新填充
|
|
|
|
|
|
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;
|
2025-06-26 14:39:34 +08:00
|
|
|
|
m.terminal_id = mon.terminal_id;
|
2025-09-25 16:36:04 +08:00
|
|
|
|
//m.timestamp = mon.timestamp;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2025-06-26 14:39:34 +08:00
|
|
|
|
m.CT1 = mon.CT1;
|
|
|
|
|
|
m.CT2 = mon.CT2;
|
|
|
|
|
|
m.PT1 = mon.PT1;
|
|
|
|
|
|
m.PT2 = mon.PT2;
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
if (m.terminal_connect != "0") {
|
|
|
|
|
|
isdelta_flag = 1;
|
|
|
|
|
|
std::cout << "monitor_id " << m.monitor_id << " uses delta wiring." << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 16:36:04 +08:00
|
|
|
|
/*if (!m.timestamp.empty()) {
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-25 16:36:04 +08:00
|
|
|
|
}*/
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
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(); ) {
|
2025-09-25 16:36:04 +08:00
|
|
|
|
update_dev& new_dev = *it;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
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)) {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
send_reply_to_queue(new_dev.guid, static_cast<int>(ResponseCode::BAD_REQUEST),
|
2025-06-24 17:55:34 +08:00
|
|
|
|
"终端 id: " + new_dev.terminal_id + " 台账更新失败,配置台账数量已满");
|
|
|
|
|
|
++it;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
terminal_dev target_dev;
|
|
|
|
|
|
if (update_one_terminal_ledger(new_dev, target_dev) != 0) {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
send_reply_to_queue(new_dev.guid, static_cast<int>(ResponseCode::BAD_REQUEST),
|
2025-06-24 17:55:34 +08:00
|
|
|
|
"终端 id: " + new_dev.terminal_id + " 台账更新失败,无法写入台账");
|
|
|
|
|
|
++it;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
init_loggers_bydevid(target_dev.terminal_id);
|
|
|
|
|
|
terminal_devlist.push_back(target_dev);
|
|
|
|
|
|
|
2025-09-25 16:36:04 +08:00
|
|
|
|
DeviceInfo device = make_device_from_terminal(target_dev);
|
|
|
|
|
|
ClientManager::instance().add_device(device);
|
|
|
|
|
|
|
2025-09-30 16:17:04 +08:00
|
|
|
|
send_reply_to_queue(new_dev.guid, static_cast<int>(ResponseCode::OK),
|
2025-06-24 17:55:34 +08:00
|
|
|
|
"终端 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()) {
|
2025-09-25 16:36:04 +08:00
|
|
|
|
erase_one_terminals_by_id(mod_dev.terminal_id);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
if (update_one_terminal_ledger(mod_dev, *it) != 0) {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
send_reply_to_queue(mod_dev.guid, static_cast<int>(ResponseCode::BAD_REQUEST),
|
2025-06-24 17:55:34 +08:00
|
|
|
|
"终端 id: " + mod_dev.terminal_id + " 台账更新失败,写入失败");
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
init_loggers_bydevid(mod_dev.terminal_id);
|
2025-09-25 16:36:04 +08:00
|
|
|
|
|
2025-09-30 16:17:04 +08:00
|
|
|
|
DeviceInfo device = make_device_from_terminal(*it);
|
2025-09-25 16:36:04 +08:00
|
|
|
|
ClientManager::instance().add_device(device);
|
|
|
|
|
|
|
2025-09-30 16:17:04 +08:00
|
|
|
|
send_reply_to_queue(mod_dev.guid, static_cast<int>(ResponseCode::OK),
|
2025-06-24 17:55:34 +08:00
|
|
|
|
"终端 id: " + mod_dev.terminal_id + " 台账修改成功");
|
|
|
|
|
|
} else {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
send_reply_to_queue(mod_dev.guid, static_cast<int>(ResponseCode::NOT_FOUND),
|
2025-06-24 17:55:34 +08:00
|
|
|
|
"终端 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()) {
|
2025-09-25 16:36:04 +08:00
|
|
|
|
erase_one_terminals_by_id(del_dev.terminal_id);
|
|
|
|
|
|
ClientManager::instance().remove_device(del_dev.terminal_id);
|
2025-09-30 16:17:04 +08:00
|
|
|
|
send_reply_to_queue(del_dev.guid, static_cast<int>(ResponseCode::OK),
|
2025-06-24 17:55:34 +08:00
|
|
|
|
"终端 id: " + del_dev.terminal_id + " 台账删除成功");
|
|
|
|
|
|
} else {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
send_reply_to_queue(del_dev.guid, static_cast<int>(ResponseCode::NOT_FOUND),
|
2025-06-24 17:55:34 +08:00
|
|
|
|
"终端 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);
|
2025-10-11 09:08:43 +08:00
|
|
|
|
std::string script = FRONT_PATH + "/bin/set_process.sh";//使用setsid防止端口占用
|
2025-06-24 17:55:34 +08:00
|
|
|
|
const char* param1 = fun.c_str();
|
|
|
|
|
|
const char* param2 = p_num_str;
|
|
|
|
|
|
const char* param3 = type.c_str();
|
|
|
|
|
|
|
|
|
|
|
|
// 构造完整的命令
|
|
|
|
|
|
char command[256];
|
2025-10-11 09:08:43 +08:00
|
|
|
|
snprintf(command, sizeof(command), "%s %s %s %s &", script.c_str(), param1, param2, param3);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
std::cout << "command:" << command <<std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
// 执行命令
|
|
|
|
|
|
system(command);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////打印台账更新
|
|
|
|
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-26 14:39:34 +08:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////解析模板文件
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
//解析映射文件
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-31 13:38:55 +08:00
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////录播波形匹配
|
|
|
|
|
|
// 工具函数:解析 "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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 18:59:11 +08:00
|
|
|
|
bool compare_qvvr_and_file(const std::string& cfg_path, const std::vector<qvvr_data>& data_list,qvvr_data& matched_data) {
|
2025-08-05 20:00:20 +08:00
|
|
|
|
long long start_tm = 0;
|
|
|
|
|
|
long long trig_tm = 0;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2025-08-05 20:00:20 +08:00
|
|
|
|
// 提取 .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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 11:07:10 +08:00
|
|
|
|
//打印提取到的时间戳
|
|
|
|
|
|
std::cout << "[调试] 提取到的起始时间戳: " << start_tm << ", 触发时间戳: " << trig_tm << "\n";
|
|
|
|
|
|
|
2025-08-05 20:00:20 +08:00
|
|
|
|
// 遍历所有暂态事件,查找与 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) {
|
2025-08-07 18:59:11 +08:00
|
|
|
|
matched_data = data; // 返回匹配到的事件
|
2025-08-05 20:00:20 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-08-08 16:35:36 +08:00
|
|
|
|
|
2025-06-26 14:39:34 +08:00
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////数据转换函数
|
|
|
|
|
|
// DataArrayItem to_json
|
|
|
|
|
|
void to_json(nlohmann::json& j, const DataArrayItem& d) {
|
|
|
|
|
|
j = nlohmann::json{
|
2025-09-05 16:28:26 +08:00
|
|
|
|
{"dataAttr", d.DataAttr},
|
|
|
|
|
|
{"dataTimeSec", d.DataTimeSec},
|
|
|
|
|
|
{"dataTimeUSec", d.DataTimeUSec},
|
|
|
|
|
|
{"dataTag", d.DataTag},
|
|
|
|
|
|
{"data", d.Data}
|
2025-06-26 14:39:34 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MsgObj to_json
|
|
|
|
|
|
void to_json(nlohmann::json& j, const MsgObj& m) {
|
|
|
|
|
|
j = nlohmann::json{
|
2025-09-05 16:28:26 +08:00
|
|
|
|
{"clDid", m.Cldid},
|
|
|
|
|
|
{"dataType", m.DataType},
|
|
|
|
|
|
{"dataAttr", m.DataAttr},
|
|
|
|
|
|
{"dsNameIdx", m.DsNameIdx},
|
|
|
|
|
|
{"dataArray", m.DataArray}
|
2025-06-26 14:39:34 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// FullObj to_json
|
|
|
|
|
|
void to_json(nlohmann::json& j, const FullObj& f) {
|
|
|
|
|
|
j = nlohmann::json{
|
2025-09-05 16:28:26 +08:00
|
|
|
|
{"id", f.mac},
|
|
|
|
|
|
{"mid", f.Mid},
|
|
|
|
|
|
{"did", f.Did},
|
|
|
|
|
|
{"pri", f.Pri},
|
|
|
|
|
|
{"type", f.Type},
|
|
|
|
|
|
{"msg", f.Msg}
|
2025-06-26 14:39:34 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
std::string generate_json(
|
2025-08-08 11:16:38 +08:00
|
|
|
|
const std::string mac,
|
2025-06-26 16:44:21 +08:00
|
|
|
|
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 //数据数组。
|
2025-06-26 14:39:34 +08:00
|
|
|
|
) {
|
|
|
|
|
|
FullObj fobj;
|
2025-08-08 11:16:38 +08:00
|
|
|
|
fobj.mac = mac;
|
2025-06-26 14:39:34 +08:00
|
|
|
|
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;
|
2025-06-26 16:44:21 +08:00
|
|
|
|
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)
|
2025-06-26 14:39:34 +08:00
|
|
|
|
arr.push_back({2, 1691741340, 0, 1, "yyyy"});
|
|
|
|
|
|
|
|
|
|
|
|
std::string js = generate_json(
|
2025-08-08 11:16:38 +08:00
|
|
|
|
"123",-1, 1, 1, 4866, 1, 0, 2, 1, arr
|
2025-06-26 14:39:34 +08:00
|
|
|
|
);
|
|
|
|
|
|
std::cout << js << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
queue_data_t data;
|
|
|
|
|
|
data.monitor_no = 1;
|
|
|
|
|
|
data.strTopic = TOPIC_ALARM;
|
|
|
|
|
|
data.strText = js;
|
|
|
|
|
|
data.mp_id = "test";
|
2025-09-22 13:26:52 +08:00
|
|
|
|
data.tag = G_ROCKETMQ_TAG_TEST;
|
|
|
|
|
|
data.key = G_ROCKETMQ_KEY_TEST;
|
2025-06-26 14:39:34 +08:00
|
|
|
|
std::lock_guard<std::mutex> lock(queue_data_list_mutex);
|
|
|
|
|
|
queue_data_list.push_back(data);
|
|
|
|
|
|
}
|
2025-06-26 16:44:21 +08:00
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////台账赋值给通信
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<DeviceInfo> GenerateDeviceInfoFromLedger(const std::vector<terminal_dev>& terminal_devlist) {
|
2025-09-04 20:59:17 +08:00
|
|
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(ledgermtx);
|
|
|
|
|
|
|
2025-06-26 16:44:21 +08:00
|
|
|
|
std::vector<DeviceInfo> devices;
|
|
|
|
|
|
|
|
|
|
|
|
for (const auto& terminal : terminal_devlist) {
|
|
|
|
|
|
DeviceInfo device;
|
|
|
|
|
|
device.device_id = terminal.terminal_id;
|
|
|
|
|
|
device.name = terminal.terminal_name;
|
2025-08-08 11:16:38 +08:00
|
|
|
|
device.model = terminal.dev_type;
|
|
|
|
|
|
device.mac = terminal.addr_str;
|
|
|
|
|
|
device.status = 1;
|
2025-06-26 16:44:21 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
2025-08-08 11:16:38 +08:00
|
|
|
|
point.strScale = monitor.voltage_level;
|
|
|
|
|
|
point.nCpuNo = std::stoi(monitor.logical_device_seq);
|
|
|
|
|
|
point.nPTType = std::stoi(monitor.terminal_connect);
|
2025-06-26 16:44:21 +08:00
|
|
|
|
|
|
|
|
|
|
device.points.push_back(point);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
devices.push_back(device);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return devices;
|
2025-07-31 13:38:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////录波文件通知
|
|
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
bool assign_qvvr_file_list(const std::string& id,
|
|
|
|
|
|
ushort nCpuNo,
|
|
|
|
|
|
const std::vector<std::string>& file_list_raw) {
|
|
|
|
|
|
// ★新增:台账加锁(若上层已有锁,可移除本行)
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(ledgermtx);
|
|
|
|
|
|
|
2025-07-31 13:38:55 +08:00
|
|
|
|
std::vector<std::string> file_names;
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 提取文件名部分
|
2025-09-22 16:46:33 +08:00
|
|
|
|
for (const auto& full_path_raw : file_list_raw) {
|
|
|
|
|
|
std::string full_path = sanitize(full_path_raw); // ★修改:清洗入参路径
|
2025-07-31 13:38:55 +08:00
|
|
|
|
size_t pos = full_path.find_last_of("/\\");
|
2025-09-22 16:46:33 +08:00
|
|
|
|
std::string name = (pos != std::string::npos && pos + 1 < full_path.size())
|
|
|
|
|
|
? full_path.substr(pos + 1)
|
|
|
|
|
|
: full_path;
|
|
|
|
|
|
name = sanitize(name); // ★修改:清洗提取的文件名
|
|
|
|
|
|
file_names.push_back(name);
|
2025-07-31 13:38:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
// ★可选:去重(如果 file_list_raw 里可能有重复)
|
|
|
|
|
|
std::sort(file_names.begin(), file_names.end());
|
|
|
|
|
|
file_names.erase(std::unique(file_names.begin(), file_names.end()), file_names.end());
|
|
|
|
|
|
|
2025-07-31 13:38:55 +08:00
|
|
|
|
// 2. 遍历终端
|
|
|
|
|
|
for (auto& dev : terminal_devlist) {
|
2025-09-22 16:46:33 +08:00
|
|
|
|
if (dev.terminal_id == id) { // 根据终端id匹配终端
|
2025-07-31 13:38:55 +08:00
|
|
|
|
for (auto& monitor : dev.line) {
|
|
|
|
|
|
try {
|
2025-09-22 16:46:33 +08:00
|
|
|
|
// ★修改:清洗 logical_device_seq 再进行转换
|
|
|
|
|
|
std::string seq_str = sanitize(monitor.logical_device_seq);
|
|
|
|
|
|
ushort monitor_seq = static_cast<ushort>(std::stoi(seq_str));
|
|
|
|
|
|
if (monitor_seq == nCpuNo) { // 根据监测点编号匹配监测点
|
2025-07-31 13:38:55 +08:00
|
|
|
|
// 构造 qvvr_file
|
|
|
|
|
|
qvvr_file qfile;
|
2025-09-22 16:46:33 +08:00
|
|
|
|
qfile.file_name.assign(file_names.begin(), file_names.end()); // 终端文件列表(已清洗)
|
|
|
|
|
|
qfile.is_download = false;
|
|
|
|
|
|
qfile.is_pair = false;
|
2025-08-04 13:28:51 +08:00
|
|
|
|
qfile.file_time_count = 0;
|
2025-09-22 16:46:33 +08:00
|
|
|
|
qfile.used_status = true;
|
2025-07-31 13:38:55 +08:00
|
|
|
|
|
|
|
|
|
|
// 添加到唯一的 qvvrevent
|
2025-09-22 16:46:33 +08:00
|
|
|
|
monitor.qvvrevent.qvvrfile.push_back(std::move(qfile)); // 记录暂态文件组
|
2025-07-31 13:38:55 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (...) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////下载成功通知
|
2025-08-05 20:00:20 +08:00
|
|
|
|
//提取下载路径的文件名
|
2025-08-08 11:16:38 +08:00
|
|
|
|
std::string extract_filename1(const std::string& path) {
|
2025-08-05 20:00:20 +08:00
|
|
|
|
size_t pos = path.find_last_of("/\\");
|
|
|
|
|
|
return (pos != std::string::npos) ? path.substr(pos + 1) : path;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-23 21:00:24 +08:00
|
|
|
|
// ★新增:dirname,返回“目录/”(保留末尾斜杠;若没有目录则返回空串)
|
|
|
|
|
|
static inline std::string dirname_with_slash(const std::string& path) {
|
|
|
|
|
|
size_t pos = path.find_last_of("/\\");
|
|
|
|
|
|
if (pos == std::string::npos) return std::string{};
|
|
|
|
|
|
return path.substr(0, pos + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-05 20:00:20 +08:00
|
|
|
|
//发送匹配的所有录波文件
|
|
|
|
|
|
bool SendAllQvvrFiles(qvvr_file& qfile, std::string& out_wavepath) {
|
|
|
|
|
|
std::vector<std::string> wavepaths;
|
|
|
|
|
|
std::string first_wavepath;
|
|
|
|
|
|
bool send_success = true;
|
2025-07-31 13:38:55 +08:00
|
|
|
|
|
2025-08-05 20:00:20 +08:00
|
|
|
|
for (const auto& file_localpath : qfile.file_download) {
|
2025-09-23 21:00:24 +08:00
|
|
|
|
std::string file_cloudpath = "comtrade/" + dirname_with_slash(file_localpath);
|
2025-08-05 20:00:20 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//文件下载结束接口
|
2025-08-07 18:59:11 +08:00
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
bool update_qvvr_file_download(const std::string& filename_with_mac_in, const std::string& terminal_id) {
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 先把原始入参清洗
|
|
|
|
|
|
std::string filename_with_mac = sanitize(filename_with_mac_in);
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[update_qvvr_file_download] raw=" << filename_with_mac_in
|
|
|
|
|
|
<< " | sanitized=" << filename_with_mac
|
|
|
|
|
|
<< " | terminal_id=" << terminal_id << std::endl;
|
2025-09-16 11:07:10 +08:00
|
|
|
|
|
2025-08-07 18:59:11 +08:00
|
|
|
|
//台账加锁
|
2025-09-23 21:00:24 +08:00
|
|
|
|
std::unique_lock<std::mutex> lock(ledgermtx);
|
2025-08-07 18:59:11 +08:00
|
|
|
|
|
2025-08-05 20:00:20 +08:00
|
|
|
|
// 去除 mac 路径前缀,仅保留文件名
|
2025-09-22 16:46:33 +08:00
|
|
|
|
std::string filename = sanitize(extract_filename1(filename_with_mac));
|
2025-07-31 13:38:55 +08:00
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
// 提取逻辑序号(如 PQ_PQLD1 → 1)
|
|
|
|
|
|
size_t under_pos1 = filename.find('_');
|
|
|
|
|
|
if (under_pos1 == std::string::npos) {
|
|
|
|
|
|
std::cout << "[DEBUG] 未找到 '_',filename=" << filename
|
|
|
|
|
|
<< ",under_pos=npos,返回 false\n";
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
size_t under_pos2 = filename.find('_', under_pos1 + 1);
|
|
|
|
|
|
std::string type_part = (under_pos2 == std::string::npos)
|
|
|
|
|
|
? filename.substr(0, under_pos1) // 兜底:只有一个下划线
|
|
|
|
|
|
: filename.substr(0, under_pos2); // 取到第二个下划线(得到 PQ_PQLD1)
|
|
|
|
|
|
std::cout << "[DEBUG] type_part=" << type_part
|
|
|
|
|
|
<< " (under_pos1=" << under_pos1
|
|
|
|
|
|
<< ", under_pos2=" << under_pos2 << ")\n";
|
2025-07-31 13:38:55 +08:00
|
|
|
|
|
|
|
|
|
|
size_t num_start = type_part.find_last_not_of("0123456789");
|
2025-09-22 16:46:33 +08:00
|
|
|
|
if (num_start == std::string::npos || num_start + 1 >= type_part.size()) {
|
|
|
|
|
|
std::cout << "[DEBUG] 数字起始位置异常:num_start=" << num_start
|
|
|
|
|
|
<< ",type_part.size()=" << type_part.size()
|
|
|
|
|
|
<< ",type_part=\"" << type_part << "\",返回 false\n";
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-07-31 13:38:55 +08:00
|
|
|
|
std::string seq_str = type_part.substr(num_start + 1);
|
2025-09-22 16:46:33 +08:00
|
|
|
|
unsigned short logical_seq = static_cast<unsigned short>(std::stoul(seq_str));
|
|
|
|
|
|
std::cout << "[DEBUG] 解析到 logical_seq=" << logical_seq << "\n";
|
2025-07-31 13:38:55 +08:00
|
|
|
|
|
2025-08-07 18:59:11 +08:00
|
|
|
|
//找终端
|
2025-07-31 13:38:55 +08:00
|
|
|
|
for (auto& dev : terminal_devlist) {
|
2025-09-16 16:42:15 +08:00
|
|
|
|
if (dev.terminal_id != terminal_id) {
|
|
|
|
|
|
std::cout << "[cmp-terminal-id][NOT-MATCH]"
|
|
|
|
|
|
<< " dev_id=" << dev.terminal_id
|
|
|
|
|
|
<< " target_id=" << terminal_id
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2025-08-07 18:59:11 +08:00
|
|
|
|
//找监测点
|
2025-07-31 13:38:55 +08:00
|
|
|
|
for (auto& monitor : dev.line) {
|
|
|
|
|
|
try {
|
2025-08-05 20:00:20 +08:00
|
|
|
|
// 将监测点台账中的 logical_device_seq 转换为数字进行匹配
|
2025-07-31 13:38:55 +08:00
|
|
|
|
ushort monitor_seq = static_cast<ushort>(std::stoi(monitor.logical_device_seq));
|
2025-09-16 11:07:10 +08:00
|
|
|
|
if (monitor_seq != logical_seq) {
|
|
|
|
|
|
// ★新增:不匹配时对比打印
|
|
|
|
|
|
std::cout << "[cmp-monitor-seq][NOT-MATCH]"
|
|
|
|
|
|
<< " monitor_id=" << monitor.monitor_id
|
2025-09-22 16:46:33 +08:00
|
|
|
|
// ★ 这里之前打印的是 seq_str,容易误导。改为 ledger 的原始串:
|
|
|
|
|
|
<< " seq_in_ledger_raw=\"" << monitor.logical_device_seq << "\""
|
2025-09-16 11:07:10 +08:00
|
|
|
|
<< " parsed=" << monitor_seq
|
|
|
|
|
|
<< " target_seq=" << logical_seq
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2025-09-22 16:46:33 +08:00
|
|
|
|
else{
|
|
|
|
|
|
std::cout << "[cmp-monitor-seq][MATCH!!!]"
|
|
|
|
|
|
<< " monitor_id=" << monitor.monitor_id
|
|
|
|
|
|
<< " seq_in_ledger_raw=\"" << monitor.logical_device_seq << "\""
|
|
|
|
|
|
<< " parsed=" << monitor_seq
|
|
|
|
|
|
<< " target_seq=" << logical_seq
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
}
|
2025-09-16 11:07:10 +08:00
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
|
// ★新增:解析失败详细原因
|
|
|
|
|
|
std::cout << "[cmp-monitor-seq][PARSE-FAIL]"
|
|
|
|
|
|
<< " monitor_id=" << monitor.monitor_id
|
|
|
|
|
|
<< " seq_in_ledger=\"" << monitor.logical_device_seq << "\""
|
|
|
|
|
|
<< " err=" << e.what()
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
continue; // logical_device_seq 非法,跳过
|
|
|
|
|
|
}catch (...) {
|
|
|
|
|
|
// ★新增:未知异常
|
|
|
|
|
|
std::cout << "[cmp-monitor-seq][PARSE-FAIL]"
|
|
|
|
|
|
<< " monitor_id=" << monitor.monitor_id
|
|
|
|
|
|
<< " seq_in_ledger=\"" << monitor.logical_device_seq << "\""
|
|
|
|
|
|
<< " err=<unknown>"
|
|
|
|
|
|
<< std::endl;
|
2025-08-05 20:00:20 +08:00
|
|
|
|
continue; // logical_device_seq 非法,跳过
|
2025-07-31 13:38:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 匹配监测点下 qvvrfile 中的 file_name
|
2025-08-07 18:59:11 +08:00
|
|
|
|
for (size_t i = 0; i < monitor.qvvrevent.qvvrfile.size(); ++i) {
|
|
|
|
|
|
auto& qfile = monitor.qvvrevent.qvvrfile[i];
|
2025-08-05 20:00:20 +08:00
|
|
|
|
// file_name 中是文件名,需与提取的 filename 比较
|
2025-07-31 13:38:55 +08:00
|
|
|
|
auto it = std::find(qfile.file_name.begin(), qfile.file_name.end(), filename);
|
2025-08-07 18:59:11 +08:00
|
|
|
|
|
|
|
|
|
|
//找到匹配文件名
|
2025-07-31 13:38:55 +08:00
|
|
|
|
if (it != qfile.file_name.end()) {
|
2025-08-05 20:00:20 +08:00
|
|
|
|
// 添加到 file_download(记录完整路径,避免重复)
|
|
|
|
|
|
if (std::find(qfile.file_download.begin(), qfile.file_download.end(), filename_with_mac) == qfile.file_download.end()) {
|
2025-09-16 11:07:10 +08:00
|
|
|
|
std::cout << "[update_qvvr_file_download] Adding downloaded file: " << filename_with_mac << std::endl;
|
2025-08-05 20:00:20 +08:00
|
|
|
|
qfile.file_download.push_back(filename_with_mac);
|
2025-07-31 13:38:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 18:59:11 +08:00
|
|
|
|
qfile.file_time_count = 0; // 任一录波文件下载后,计时归零
|
2025-07-31 13:38:55 +08:00
|
|
|
|
|
2025-08-05 20:00:20 +08:00
|
|
|
|
// file_download 中是完整路径,需提取文件名后与 file_name 做集合比较
|
2025-07-31 13:38:55 +08:00
|
|
|
|
std::set<std::string> s_name(qfile.file_name.begin(), qfile.file_name.end());
|
2025-08-05 20:00:20 +08:00
|
|
|
|
std::set<std::string> s_down;
|
|
|
|
|
|
for (const auto& path : qfile.file_download) {
|
2025-09-22 16:46:33 +08:00
|
|
|
|
s_down.insert(sanitize(extract_filename1(path))); // 提取每个路径中的文件名
|
2025-08-05 20:00:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 11:07:10 +08:00
|
|
|
|
//打印s_name和s_down内容
|
|
|
|
|
|
std::cout << "[update_qvvr_file_download] Expected files (file_name): ";
|
|
|
|
|
|
for (const auto& fn : s_name) std::cout << fn << " ";
|
|
|
|
|
|
std::cout << std::endl;
|
|
|
|
|
|
std::cout << "[update_qvvr_file_download] Downloaded files (file_download): ";
|
|
|
|
|
|
for (const auto& fn : s_down) std::cout << fn << " ";
|
2025-09-23 21:00:24 +08:00
|
|
|
|
std::cout << std::endl;
|
2025-09-16 11:07:10 +08:00
|
|
|
|
|
2025-08-05 20:00:20 +08:00
|
|
|
|
// 检查 file_download 是否与 file_name 完全一致(集合相同)
|
2025-07-31 13:38:55 +08:00
|
|
|
|
if (s_name == s_down) {
|
2025-09-16 11:07:10 +08:00
|
|
|
|
std::cout << "[update_qvvr_file_download] All files downloaded for qfile in logical_seq=" << logical_seq << std::endl;
|
2025-08-05 20:00:20 +08:00
|
|
|
|
qfile.is_download = true; // 全部下载完成
|
2025-07-31 13:38:55 +08:00
|
|
|
|
|
|
|
|
|
|
// 找到其中的 .cfg 文件进行匹配
|
2025-08-05 20:00:20 +08:00
|
|
|
|
for (const auto& fpath : qfile.file_download) {
|
2025-08-08 11:16:38 +08:00
|
|
|
|
std::string fname = extract_filename1(fpath);
|
2025-08-05 20:00:20 +08:00
|
|
|
|
if (fname.size() >= 4 && fname.substr(fname.size() - 4) == ".cfg") {
|
|
|
|
|
|
// 提取文件时标和监测点事件的时标匹配
|
2025-08-07 18:59:11 +08:00
|
|
|
|
qvvr_data matched;
|
|
|
|
|
|
if (compare_qvvr_and_file(fpath, monitor.qvvrevent.qvvrdata,matched)) {
|
2025-08-05 20:00:20 +08:00
|
|
|
|
qfile.is_pair = true; // 文件与事件匹配成功
|
|
|
|
|
|
|
2025-09-23 21:00:24 +08:00
|
|
|
|
// ★新增:上传前拷贝“将要上传的文件列表”,避免锁外用容器引用
|
|
|
|
|
|
std::vector<std::string> files_to_send(qfile.file_download.begin(),
|
|
|
|
|
|
qfile.file_download.end());
|
|
|
|
|
|
|
|
|
|
|
|
// ★新增:构造一个临时 qvvr_file,仅用于上传(不改动原结构)
|
|
|
|
|
|
qvvr_file tmp_send;
|
|
|
|
|
|
tmp_send.file_download.assign(files_to_send.begin(), files_to_send.end());
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-08-05 20:00:20 +08:00
|
|
|
|
// 发送所有文件(已下载完成)
|
|
|
|
|
|
std::string wavepath;
|
2025-09-23 21:00:24 +08:00
|
|
|
|
|
|
|
|
|
|
// ★在解锁前,备份“签名”,用于回锁后定位同一个 qfile
|
|
|
|
|
|
std::set<std::string> sig_names(qfile.file_name.begin(), qfile.file_name.end());
|
|
|
|
|
|
std::set<std::string> sig_downs(qfile.file_download.begin(), qfile.file_download.end());
|
|
|
|
|
|
|
|
|
|
|
|
// ★修改:把上传与上送 JSON 放到“解锁区间”
|
|
|
|
|
|
lock.unlock(); // ★新增:提前解锁
|
|
|
|
|
|
|
2025-08-05 20:00:20 +08:00
|
|
|
|
if (SendAllQvvrFiles(qfile, wavepath)) {
|
|
|
|
|
|
//文件发送成功后更新事件
|
2025-09-23 21:00:24 +08:00
|
|
|
|
|
2025-08-05 20:00:20 +08:00
|
|
|
|
transfer_json_qvvr_data(terminal_id,
|
|
|
|
|
|
logical_seq,
|
2025-08-07 18:59:11 +08:00
|
|
|
|
matched.QVVR_Amg,
|
|
|
|
|
|
matched.QVVR_PerTime,
|
|
|
|
|
|
matched.QVVR_time,
|
|
|
|
|
|
matched.QVVR_type,
|
|
|
|
|
|
matched.phase,
|
2025-08-05 20:00:20 +08:00
|
|
|
|
wavepath);
|
2025-09-23 21:00:24 +08:00
|
|
|
|
|
|
|
|
|
|
// ★新增:上传成功后再加锁,准备修改台账
|
|
|
|
|
|
lock.lock();
|
|
|
|
|
|
|
2025-08-05 20:00:20 +08:00
|
|
|
|
// 删除上传成功的文件
|
|
|
|
|
|
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";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-07 18:59:11 +08:00
|
|
|
|
|
2025-09-23 21:00:24 +08:00
|
|
|
|
// ★替换原来的 i<size 判断为:按签名查找当前容器里的那一条
|
|
|
|
|
|
auto it_qf = std::find_if(monitor.qvvrevent.qvvrfile.begin(),
|
|
|
|
|
|
monitor.qvvrevent.qvvrfile.end(),
|
|
|
|
|
|
[&](const qvvr_file& x){
|
|
|
|
|
|
std::set<std::string> n(x.file_name.begin(), x.file_name.end());
|
|
|
|
|
|
std::set<std::string> d(x.file_download.begin(), x.file_download.end());
|
|
|
|
|
|
return n==sig_names && d==sig_downs;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (it_qf != monitor.qvvrevent.qvvrfile.end()) {
|
|
|
|
|
|
monitor.qvvrevent.qvvrfile.erase(it_qf); // ✔ 删到同一条
|
|
|
|
|
|
} else {
|
|
|
|
|
|
std::cerr << "[Cleanup] qvvrfile changed; target group not found, skip erase\n";
|
|
|
|
|
|
}
|
2025-08-07 18:59:11 +08:00
|
|
|
|
|
|
|
|
|
|
//清除暂态事件
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
2025-09-16 11:07:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
else {
|
2025-09-23 21:00:24 +08:00
|
|
|
|
lock.lock(); // ★新增:失败时补回锁
|
2025-09-16 11:07:10 +08:00
|
|
|
|
std::cerr << "[update_qvvr_file_download] Failed to send qvvr files for logical_seq=" << logical_seq << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
std::cout << "[update_qvvr_file_download] No matching qvvr_data found for cfg file: " << fpath << std::endl;
|
2025-07-31 13:38:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
break; // 只处理第一个 cfg 文件
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-07 18:59:11 +08:00
|
|
|
|
else{
|
|
|
|
|
|
std::cout << "qvvr file still imcomplete!!!" << std::endl;
|
|
|
|
|
|
}
|
2025-09-23 21:00:24 +08:00
|
|
|
|
lock.unlock();
|
2025-08-05 20:00:20 +08:00
|
|
|
|
return true; // 当前文件处理成功
|
2025-08-07 18:59:11 +08:00
|
|
|
|
}
|
2025-07-31 13:38:55 +08:00
|
|
|
|
}
|
2025-08-07 18:59:11 +08:00
|
|
|
|
|
|
|
|
|
|
std::cout << "file name doesnt match any file in this monitor!!!" << std::endl;
|
|
|
|
|
|
|
2025-07-31 13:38:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-23 21:00:24 +08:00
|
|
|
|
lock.unlock();
|
2025-08-05 20:00:20 +08:00
|
|
|
|
return false; // 未匹配到终端ID或逻辑序号对应的监测点
|
2025-07-31 13:38:55 +08:00
|
|
|
|
}
|
2025-08-08 11:16:38 +08:00
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////提取mac
|
2025-09-02 14:58:19 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-23 21:00:24 +08:00
|
|
|
|
|
2025-09-02 14:58:19 +08:00
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////目录信息发送接口函数
|
|
|
|
|
|
|
2025-09-23 21:00:24 +08:00
|
|
|
|
bool send_file_list(terminal_dev* dev, const std::vector<tag_dir_info>& FileList) {
|
2025-09-02 14:58:19 +08:00
|
|
|
|
|
2025-09-23 21:00:24 +08:00
|
|
|
|
if (!dev) {
|
|
|
|
|
|
std::cerr << "[send_file_list_locked] dev=nullptr\n";
|
2025-09-02 14:58:19 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断 isbusy==1 且 busytype==READING_FILEMENU
|
2025-09-23 21:00:24 +08:00
|
|
|
|
if (dev->isbusy != 1 || dev->busytype != static_cast<int>(DeviceState::READING_FILEMENU)) {
|
2025-09-02 14:58:19 +08:00
|
|
|
|
std::cerr << "[send_file_list] device not in READING_FILEMENU state." << std::endl;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构造 JSON 报文
|
|
|
|
|
|
nlohmann::json j;
|
2025-09-23 21:00:24 +08:00
|
|
|
|
j["guid"] = dev->guid;
|
2025-09-24 09:44:52 +08:00
|
|
|
|
j["FrontId"] = FRONT_INST; // 这里填你的前置机id
|
2025-09-02 14:58:19 +08:00
|
|
|
|
j["Node"] = g_front_seg_index; // 节点号
|
2025-09-23 21:00:24 +08:00
|
|
|
|
j["Dev_mac"] = normalize_mac(dev->addr_str); // addr_str 存的是 MAC
|
2025-09-02 14:58:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 构造 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(); // 序列化为字符串
|
2025-09-22 13:26:52 +08:00
|
|
|
|
connect_info.tag = Topic_Reply_Tag;
|
|
|
|
|
|
connect_info.key = Topic_Reply_Key;
|
2025-09-02 14:58:19 +08:00
|
|
|
|
{
|
|
|
|
|
|
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和标志
|
2025-09-23 21:00:24 +08:00
|
|
|
|
if (dev->isbusy > 0) {
|
|
|
|
|
|
dev->isbusy--;
|
2025-09-02 14:58:19 +08:00
|
|
|
|
}
|
2025-09-23 21:00:24 +08:00
|
|
|
|
if(dev->isbusy == 0){
|
|
|
|
|
|
dev->guid.clear();
|
|
|
|
|
|
dev->busytype = 0;
|
2025-09-02 14:58:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
2025-09-15 16:36:21 +08:00
|
|
|
|
return 0x2106; //读数据
|
2025-09-02 14:58:19 +08:00
|
|
|
|
|
|
|
|
|
|
case DeviceState::READING_FILEMENU:
|
2025-09-15 16:36:21 +08:00
|
|
|
|
return 0x2131; //读目录
|
2025-09-02 14:58:19 +08:00
|
|
|
|
|
|
|
|
|
|
case DeviceState::READING_EVENTFILE:
|
|
|
|
|
|
case DeviceState::READING_FILEDATA:
|
2025-09-15 16:36:21 +08:00
|
|
|
|
return 0x2132; //读文件
|
2025-09-02 14:58:19 +08:00
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
return 0; // 没有对应的type
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 定时检查业务超时
|
|
|
|
|
|
void check_device_busy_timeout()
|
|
|
|
|
|
{
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(ledgermtx);
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
2025-09-02 14:58:19 +08:00
|
|
|
|
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
|
2025-09-04 20:59:17 +08:00
|
|
|
|
<< " busytype=READING_FILEDATA 超时("
|
2025-09-02 14:58:19 +08:00
|
|
|
|
<< dev.busytimecount << "s)" << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
//发送超时响应
|
2025-09-23 21:00:24 +08:00
|
|
|
|
send_reply_to_cloud(static_cast<int>(ResponseCode::TIMEOUT),dev.terminal_id,get_type_by_state(dev.busytype),dev.guid,dev.mac);
|
2025-09-02 14:58:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 超时清空状态
|
|
|
|
|
|
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;
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
|
|
|
|
|
//发送超时响应
|
2025-09-23 21:00:24 +08:00
|
|
|
|
send_reply_to_cloud(static_cast<int>(ResponseCode::TIMEOUT),dev.terminal_id,get_type_by_state(dev.busytype),dev.guid,dev.mac);
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
2025-09-02 14:58:19 +08:00
|
|
|
|
// 超时清空状态
|
|
|
|
|
|
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; // 正常前进
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-08 16:35:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-02 14:58:19 +08:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////定值信息发送接口函数
|
|
|
|
|
|
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;
|
2025-08-08 16:35:36 +08:00
|
|
|
|
|
2025-09-02 14:58:19 +08:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
2025-08-08 16:35:36 +08:00
|
|
|
|
|
2025-09-02 14:58:19 +08:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-08 16:35:36 +08:00
|
|
|
|
|
2025-09-02 14:58:19 +08:00
|
|
|
|
if (!found) {
|
|
|
|
|
|
std::cerr << "[send_set_reply] monitor with seq=" << (int)mp_index
|
|
|
|
|
|
<< " not found in terminal " << dev_id << std::endl;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-08-08 16:35:36 +08:00
|
|
|
|
|
2025-09-02 14:58:19 +08:00
|
|
|
|
// 4. 状态递减
|
|
|
|
|
|
dev.isbusy--;
|
2025-08-08 16:35:36 +08:00
|
|
|
|
|
2025-09-02 14:58:19 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-05 16:28:26 +08:00
|
|
|
|
bool save_internal_value(const std::string &dev_id, const std::vector<ushort> &fabsf) {
|
2025-09-02 14:58:19 +08:00
|
|
|
|
// 找到对应 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-04 20:59:17 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-02 14:58:19 +08:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////回复定值读取响应
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-05 16:28:26 +08:00
|
|
|
|
//将dz_info存入监测点
|
|
|
|
|
|
pMon->dz_info_list.clear();
|
|
|
|
|
|
for (const auto &dz : dz_info) {
|
|
|
|
|
|
pMon->dz_info_list.push_back(dz);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-02 14:58:19 +08:00
|
|
|
|
// 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;
|
2025-09-24 09:44:52 +08:00
|
|
|
|
j["FrontId"] = FRONT_INST; // 你的前置机 IP(项目已有常量/变量)
|
2025-09-02 14:58:19 +08:00
|
|
|
|
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(); // 序列化为字符串
|
2025-09-22 13:26:52 +08:00
|
|
|
|
connect_info.tag = Topic_Reply_Tag;
|
|
|
|
|
|
connect_info.key = Topic_Reply_Key;
|
2025-09-02 14:58:19 +08:00
|
|
|
|
|
|
|
|
|
|
{
|
2025-09-08 16:06:53 +08:00
|
|
|
|
std::lock_guard<std::mutex> lock(queue_data_list_mutex);
|
2025-09-02 14:58:19 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-09-04 20:59:17 +08:00
|
|
|
|
|
2025-09-05 16:28:26 +08:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////回复内部定值响应读取
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool send_internal_value_reply(const std::string &dev_id, const std::vector<DZ_kzz_bit> &control_words)
|
|
|
|
|
|
{
|
2025-09-04 20:59:17 +08:00
|
|
|
|
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()) {
|
2025-09-05 16:28:26 +08:00
|
|
|
|
std::cerr << "[send_internal_value_reply] device not found: " << dev_id << std::endl;
|
2025-09-04 20:59:17 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
terminal_dev &dev = *it;
|
|
|
|
|
|
|
2025-09-05 16:28:26 +08:00
|
|
|
|
// 2) 校验状态:发送“内部定值读取结果”回复,应处于 READING_INTERFIXEDVALUE;isbusy == 1
|
|
|
|
|
|
if (dev.isbusy != 1 || dev.busytype != static_cast<int>(DeviceState::READING_INTERFIXEDVALUE)) {
|
|
|
|
|
|
std::cerr << "[send_internal_value_reply] device not in READING_INTERFIXEDVALUE state." << std::endl;
|
2025-09-04 20:59:17 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-05 16:28:26 +08:00
|
|
|
|
//将control_words存入dev
|
|
|
|
|
|
dev.control_words.clear();
|
|
|
|
|
|
for (const auto &cw : control_words) {
|
|
|
|
|
|
dev.control_words.push_back(cw);
|
|
|
|
|
|
}
|
2025-09-04 20:59:17 +08:00
|
|
|
|
|
2025-09-05 16:28:26 +08:00
|
|
|
|
// -------------------- [新增] 建立 internal_values 与 dz_internal_info_list 的一一对应 --------------------
|
|
|
|
|
|
// 说明:按索引次序一一对应(第 i 个 NameFixValue 对应 internal_values 的第 i 个)
|
|
|
|
|
|
// 若数量不同,按 min 对齐,忽略多出来的一边并告警。
|
|
|
|
|
|
std::vector<float> internal_vals; // [新增]
|
|
|
|
|
|
internal_vals.reserve(dev.internal_values.size()); // [新增]
|
|
|
|
|
|
for (float v : dev.internal_values) internal_vals.push_back(v); // [新增]
|
|
|
|
|
|
|
|
|
|
|
|
const size_t n_dz = dev.dz_internal_info_list.size(); // [新增]
|
|
|
|
|
|
const size_t n_val = internal_vals.size(); // [新增]
|
|
|
|
|
|
const size_t n_use = std::min(n_dz, n_val); // [新增]
|
|
|
|
|
|
if (n_dz != n_val) { // [新增]
|
|
|
|
|
|
std::cerr << "[send_internal_value_reply] WARN: dz_internal_info_list size("
|
|
|
|
|
|
<< n_dz << ") != internal_values size(" << n_val
|
|
|
|
|
|
<< "), will use min(" << n_use << ")." << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
// 3) 组包顶层
|
|
|
|
|
|
nlohmann::json j;
|
2025-09-04 20:59:17 +08:00
|
|
|
|
j["guid"] = dev.guid;
|
2025-09-24 09:44:52 +08:00
|
|
|
|
j["FrontId"] = FRONT_INST;
|
2025-09-05 16:28:26 +08:00
|
|
|
|
j["Node"] = g_front_seg_index;
|
2025-09-04 20:59:17 +08:00
|
|
|
|
j["Dev_mac"] = normalize_mac(dev.addr_str);
|
|
|
|
|
|
|
|
|
|
|
|
nlohmann::json detail;
|
|
|
|
|
|
detail["Type"] = 0x2106; // 设备数据
|
|
|
|
|
|
nlohmann::json msg;
|
2025-09-05 16:28:26 +08:00
|
|
|
|
msg["DataType"] = 0x0D; // 内部定值
|
|
|
|
|
|
|
|
|
|
|
|
// 4) === 将 C# 的拼接逻辑移植为 DataArray ===
|
|
|
|
|
|
// C# 变量对应关系:
|
|
|
|
|
|
// DevInfo.nDevIndex -> 这里用 1
|
|
|
|
|
|
// DevInfo.strGuId -> 这里用 装置id
|
|
|
|
|
|
// DevInfo.controlwordlist -> 这里用参数 control_words(DZ_kzz_bit 含 kzz_bit/bit_enable)
|
|
|
|
|
|
//
|
|
|
|
|
|
// NameFixValue 列表:使用 dev.dz_internal_info_list
|
|
|
|
|
|
//
|
|
|
|
|
|
// 关键逻辑:
|
|
|
|
|
|
// - 遍历每个 NameFixValue,k 从 1 递增,nStep 每个定值递增 1
|
|
|
|
|
|
// - 若 DataType == 1:将 Max/Min/Default 都 /100,并 property 输出一个空对象 [{}](保持与 C# 一致)
|
|
|
|
|
|
// - 否则:为该定值构建 property 位数组,范围 [nStep*16, (nStep+1)*16),
|
|
|
|
|
|
// 名称为空则提前结束本定值的 property;flag = (DefaultValue >> j) & 0x01
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
nlohmann::json dataArray = nlohmann::json::array(); // [新增]
|
|
|
|
|
|
|
|
|
|
|
|
int nStep = 0; // [新增] 每个 NameFixValue 递增
|
|
|
|
|
|
int kSort = 1; // [新增] 排序号,从 1 开始
|
|
|
|
|
|
|
2025-09-23 21:00:24 +08:00
|
|
|
|
// 保护:dz_internal_info_list 是引用成员,确保不会因并发被改动
|
2025-09-05 16:28:26 +08:00
|
|
|
|
//for (const auto& nf : dev.dz_internal_info_list) { // [新增]
|
|
|
|
|
|
for (size_t idxNF = 0; idxNF < n_use; ++idxNF) { // [修改] 使用 idxNF 控制索引
|
|
|
|
|
|
const auto& nf = dev.dz_internal_info_list[idxNF];
|
|
|
|
|
|
// 取字段
|
|
|
|
|
|
const uint16_t dataType = nf.DataType;
|
|
|
|
|
|
const uint16_t minRaw = nf.MinValue;
|
|
|
|
|
|
const uint16_t maxRaw = nf.MaxValue;
|
|
|
|
|
|
const uint16_t defaultRaw = nf.DefaultValue;
|
|
|
|
|
|
const std::string unit = trim_cstr(nf.sDimension, sizeof(nf.sDimension));
|
|
|
|
|
|
const std::string name = trim_cstr(nf.sFixValueName, sizeof(nf.sFixValueName));
|
|
|
|
|
|
|
|
|
|
|
|
// 取对应内部值
|
|
|
|
|
|
const float internal_v_raw = internal_vals[idxNF]; // [新增]
|
|
|
|
|
|
const double internal_v_out = static_cast<double>(internal_v_raw); // [新增] 直接转 double 输出,不缩放
|
|
|
|
|
|
|
|
|
|
|
|
// 构造一条记录
|
|
|
|
|
|
nlohmann::json one;
|
|
|
|
|
|
one["cpu_no"] = 1; // [新增] C#: DevInfo.nDevIndex 填设备号,固定为1
|
|
|
|
|
|
one["dev_type"] = dev_id; // [新增] C#: DevInfo.strGuId 填装置id
|
|
|
|
|
|
one["type"] = 90; // [新增] 固定 "90"
|
|
|
|
|
|
one["unit"] = unit; // [新增]
|
|
|
|
|
|
one["describe"] = name; // [新增]
|
|
|
|
|
|
one["sort"] = kSort; // [新增]
|
|
|
|
|
|
one["Internal_Value"] = internal_v_out; // [新增] 精确对应 internal_values 的值(含必要缩放)
|
|
|
|
|
|
|
|
|
|
|
|
// 数值:DataType == 1 时缩放 /100
|
|
|
|
|
|
if (dataType == 1) { // [新增] 缩放分支
|
|
|
|
|
|
int ChangeMaxValue = static_cast<int>(maxRaw) / 100;
|
|
|
|
|
|
int ChangeMinValue = static_cast<int>(minRaw) / 100;
|
|
|
|
|
|
int ChangeDefaultValue = static_cast<int>(defaultRaw) / 100;
|
|
|
|
|
|
|
|
|
|
|
|
one["maxvalue"] = ChangeMaxValue;
|
|
|
|
|
|
one["minvalue"] = ChangeMinValue;
|
|
|
|
|
|
one["defaultvalue"] = ChangeDefaultValue;
|
|
|
|
|
|
one["value"] = ChangeDefaultValue;
|
|
|
|
|
|
|
|
|
|
|
|
// C# 在该分支 property 写成 [{ }](一个空对象的数组)
|
|
|
|
|
|
nlohmann::json prop = nlohmann::json::array();
|
|
|
|
|
|
prop.push_back(nlohmann::json::object()); // [{}]
|
|
|
|
|
|
one["property"] = std::move(prop);
|
|
|
|
|
|
} else { // [新增] 未缩放分支 + property 位描述
|
|
|
|
|
|
one["maxvalue"] = static_cast<int>(maxRaw);
|
|
|
|
|
|
one["minvalue"] = static_cast<int>(minRaw);
|
|
|
|
|
|
one["defaultvalue"] = static_cast<int>(defaultRaw);
|
|
|
|
|
|
one["value"] = static_cast<int>(defaultRaw);
|
|
|
|
|
|
|
|
|
|
|
|
// 构建 property:16 位窗口,从 nStep*16 到 (nStep+1)*16 - 1
|
|
|
|
|
|
nlohmann::json prop = nlohmann::json::array();
|
|
|
|
|
|
bool hasAny = false;
|
|
|
|
|
|
|
|
|
|
|
|
const int begin = nStep * 16;
|
|
|
|
|
|
const int end = (nStep + 1) * 16; // 不含 end
|
|
|
|
|
|
|
|
|
|
|
|
for (int idx = begin, jbit = 0; idx < end; ++idx, ++jbit) {
|
|
|
|
|
|
if (idx < 0 || static_cast<size_t>(idx) >= control_words.size()) break;
|
|
|
|
|
|
|
|
|
|
|
|
// 名称空则提前退出(仿 C#:temp=="" break)
|
|
|
|
|
|
const std::string cw_name = trim_cstr(control_words[idx].kzz_bit, sizeof(control_words[idx].kzz_bit));
|
|
|
|
|
|
if (cw_name.empty()) {
|
|
|
|
|
|
// 注意:C# 如果 j==0 则设置了 flag2=1,仅用于逗号处理,这里不需要
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2025-09-04 20:59:17 +08:00
|
|
|
|
|
2025-09-05 16:28:26 +08:00
|
|
|
|
int flag = (defaultRaw >> jbit) & 0x01; // 取该位默认值
|
|
|
|
|
|
nlohmann::json bitItem;
|
|
|
|
|
|
bitItem["type_num"] = jbit;
|
|
|
|
|
|
bitItem["bit0"] = ""; // 保持与 C# 一致
|
|
|
|
|
|
bitItem["bit1"] = "";
|
|
|
|
|
|
bitItem["describe"] = cw_name;
|
|
|
|
|
|
bitItem["flag"] = (flag ? "1" : "0"); // C# 用字符串
|
|
|
|
|
|
prop.push_back(std::move(bitItem));
|
|
|
|
|
|
hasAny = true;
|
|
|
|
|
|
}
|
2025-09-04 20:59:17 +08:00
|
|
|
|
|
2025-09-05 16:28:26 +08:00
|
|
|
|
if (!hasAny) {
|
|
|
|
|
|
// 与 C# 对齐:如果一个都没有,就给 [{}] 以避免 "property":[] 的结构差异
|
|
|
|
|
|
prop.push_back(nlohmann::json::object());
|
|
|
|
|
|
}
|
|
|
|
|
|
one["property"] = std::move(prop);
|
|
|
|
|
|
}
|
2025-09-04 20:59:17 +08:00
|
|
|
|
|
2025-09-05 16:28:26 +08:00
|
|
|
|
dataArray.push_back(std::move(one)); // [新增]
|
|
|
|
|
|
++nStep; // [新增] 进入下一个 16 位窗口
|
|
|
|
|
|
++kSort; // [新增]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
msg["DataArray"] = std::move(dataArray); // [新增]
|
|
|
|
|
|
detail["Msg"] = std::move(msg);
|
|
|
|
|
|
j["Detail"] = std::move(detail);
|
|
|
|
|
|
|
|
|
|
|
|
// 5) 入队发送(保持你的队列逻辑)
|
2025-09-04 20:59:17 +08:00
|
|
|
|
queue_data_t connect_info;
|
|
|
|
|
|
connect_info.strTopic = Topic_Reply_Topic;
|
|
|
|
|
|
connect_info.strText = j.dump(); // 序列化为字符串
|
2025-09-22 13:26:52 +08:00
|
|
|
|
connect_info.tag = Topic_Reply_Tag;
|
|
|
|
|
|
connect_info.key = Topic_Reply_Key;
|
2025-09-04 20:59:17 +08:00
|
|
|
|
|
|
|
|
|
|
{
|
2025-09-08 16:06:53 +08:00
|
|
|
|
std::lock_guard<std::mutex> lock(queue_data_list_mutex);
|
2025-09-04 20:59:17 +08:00
|
|
|
|
queue_data_list.push_back(std::move(connect_info));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调试打印
|
|
|
|
|
|
std::cout << "[send_internal_value_reply] queued JSON:\n" << j.dump(4) << std::endl;
|
|
|
|
|
|
|
2025-09-05 16:28:26 +08:00
|
|
|
|
// 6) 发送后更新终端状态(保持你现有规则)
|
2025-09-04 20:59:17 +08:00
|
|
|
|
if (dev.isbusy > 0) {
|
|
|
|
|
|
dev.isbusy--;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (dev.isbusy == 0) {
|
|
|
|
|
|
dev.guid.clear();
|
|
|
|
|
|
dev.busytype = 0;
|
2025-09-05 16:28:26 +08:00
|
|
|
|
dev.internal_values.clear(); // 清理本次定值记录
|
|
|
|
|
|
dev.dz_internal_info_list.clear(); // 清理本次定值描述记录(注意:如果这是引用成员,确保其实际容器存在)
|
2025-09-04 20:59:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
2025-09-10 16:59:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////处理补招逻辑
|
2025-09-15 16:36:21 +08:00
|
|
|
|
//发送补招响应给web
|
|
|
|
|
|
void send_reply_to_kafka_recall(const std::string& guid, const std::string& step,int code, const std::string& result,const std::string& terminalId,const std::string& lineIndex,const std::string& recallStartDate,const std::string& recallEndDate){
|
|
|
|
|
|
// 构造 JSON 字符串
|
|
|
|
|
|
std::ostringstream oss;
|
|
|
|
|
|
oss << "{"
|
|
|
|
|
|
<< "\"guid\":\"" << guid << "\","
|
|
|
|
|
|
<< "\"step\":\"" << step << "\","
|
|
|
|
|
|
<< "\"code\":" << code << ","
|
|
|
|
|
|
<< "\"result\":\"" << result << "\","
|
|
|
|
|
|
<< "\"terminalId\":\"" << terminalId << "\","
|
|
|
|
|
|
<< "\"lineIndex\":\"" << lineIndex << "\","
|
|
|
|
|
|
<< "\"recallStartDate\":\"" << recallStartDate << "\","
|
|
|
|
|
|
<< "\"recallEndDate\":\"" << recallEndDate << "\","
|
|
|
|
|
|
<< "\"processNo\":\"" << g_front_seg_index << "\","
|
|
|
|
|
|
<< "\"nodeId\":\"" << FRONT_INST << "\""
|
|
|
|
|
|
<< "}";
|
|
|
|
|
|
|
|
|
|
|
|
std::string jsonString = oss.str();
|
|
|
|
|
|
|
|
|
|
|
|
// 封装 Kafka 消息
|
|
|
|
|
|
queue_data_t connect_info;
|
|
|
|
|
|
connect_info.strTopic = Topic_Reply_Topic;
|
|
|
|
|
|
connect_info.strText = jsonString;
|
2025-09-22 13:26:52 +08:00
|
|
|
|
connect_info.tag = Topic_Reply_Tag;
|
|
|
|
|
|
connect_info.key = Topic_Reply_Key;
|
2025-09-15 16:36:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 加入发送队列(带互斥锁保护)
|
|
|
|
|
|
queue_data_list_mutex.lock();
|
|
|
|
|
|
queue_data_list.push_back(connect_info);
|
|
|
|
|
|
queue_data_list_mutex.unlock();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
// ===== 一次遍历可下发“多个终端的一条” =====
|
|
|
|
|
|
void check_recall_event() {
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<RecallTask> tasks; // 本轮要发送的“每终端一条”
|
|
|
|
|
|
|
|
|
|
|
|
{ //锁作用域
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(ledgermtx);
|
|
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
// 遍历所有 terminal —— 每个 terminal 只挑一条,先不判断运行状态,因为正在处理其他事务的装置也可以记录待补招信息
|
2025-09-10 16:59:50 +08:00
|
|
|
|
for (auto& dev : terminal_devlist) {
|
2025-09-12 17:08:25 +08:00
|
|
|
|
//如果该终端不是正在补招或者idle则直接跳过,节省运行时间
|
|
|
|
|
|
if(dev.busytype != static_cast<int>(DeviceState::READING_EVENTLOG) && dev.busytype != static_cast<int>(DeviceState::IDLE)){
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 对正在补招或idle终端的所有监测点的待补招列表进行处理
|
2025-09-10 16:59:50 +08:00
|
|
|
|
// 1) 先弹掉首条为 DONE/FAILED 的记录(所有 monitor 都要处理首条)
|
|
|
|
|
|
bool any_non_empty = false;
|
|
|
|
|
|
for (auto& lm : dev.line) {
|
|
|
|
|
|
while (!lm.recall_list.empty()) {
|
|
|
|
|
|
const RecallMonitor& front = lm.recall_list.front();
|
|
|
|
|
|
if (front.recall_status == static_cast<int>(RecallStatus::DONE)) {
|
2025-09-12 17:08:25 +08:00
|
|
|
|
std::cout << "[check_recall_event] DONE dev=" << dev.terminal_id
|
2025-09-10 16:59:50 +08:00
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
|
|
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
//调用reply接口通知web端该时间段补招完成
|
2025-09-15 16:36:21 +08:00
|
|
|
|
std::string msg = std::string("监测点:") + lm.monitor_name
|
|
|
|
|
|
+ " 补招时间范围:" + front.StartTime
|
|
|
|
|
|
+ " ~ " + front.EndTime
|
|
|
|
|
|
+ " 补招执行完成";
|
|
|
|
|
|
send_reply_to_kafka_recall("12345","2",static_cast<int>(ResponseCode::OK),msg,dev.terminal_id,lm.logical_device_seq,front.StartTime,front.EndTime);
|
2025-09-10 16:59:50 +08:00
|
|
|
|
|
|
|
|
|
|
lm.recall_list.pop_front(); // 弹掉首条
|
|
|
|
|
|
} else if (front.recall_status == static_cast<int>(RecallStatus::FAILED)) {
|
2025-09-12 17:08:25 +08:00
|
|
|
|
std::cout << "[check_recall_event] FAILED dev=" << dev.terminal_id
|
2025-09-10 16:59:50 +08:00
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
|
|
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
//调用reply接口通知web端该时间段补招失败
|
2025-09-15 16:36:21 +08:00
|
|
|
|
std::string msg = std::string("监测点:") + lm.monitor_name
|
|
|
|
|
|
+ " 补招时间范围:" + front.StartTime
|
|
|
|
|
|
+ " ~ " + front.EndTime
|
|
|
|
|
|
+ " 补招执行失败";
|
|
|
|
|
|
send_reply_to_kafka_recall("12345","2",static_cast<int>(ResponseCode::BAD_REQUEST),msg,dev.terminal_id,lm.logical_device_seq,front.StartTime,front.EndTime);
|
2025-09-10 16:59:50 +08:00
|
|
|
|
|
|
|
|
|
|
lm.recall_list.pop_front(); // 弹掉首条
|
|
|
|
|
|
} else {
|
2025-09-12 17:08:25 +08:00
|
|
|
|
break; // 首条不是 2/3,停,如果是正在处理其他业务或者idle的装置写入了待补招列表,应该都是0;如果是正在补招的装置,新增的部分不会影响原有顺序
|
2025-09-10 16:59:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!lm.recall_list.empty()) any_non_empty = true; // 处理了成功和失败的以后只要有一条非空就标记,可能是待处理或者正在处理的补招
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::READING_EVENTLOG)) {
|
|
|
|
|
|
// 该终端本轮已无任何补招条目,且处于补招暂态事件的状态清空运行态
|
|
|
|
|
|
//通知补招全部完成
|
|
|
|
|
|
|
|
|
|
|
|
dev.guid.clear(); // 清空 guid
|
|
|
|
|
|
dev.busytype = 0; // 业务类型归零
|
|
|
|
|
|
dev.isbusy = 0; // 清空业务标志
|
|
|
|
|
|
dev.busytimecount = 0; // 计时归零
|
2025-09-10 16:59:50 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2025-09-12 17:08:25 +08:00
|
|
|
|
//如果是idle又没有待补招任务了,应该跳过
|
|
|
|
|
|
else if(!any_non_empty && dev.busytype == static_cast<int>(DeviceState::IDLE)){
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
else{//有待补招任务且处于补招状态或者idle状态
|
|
|
|
|
|
// 继续补招处理
|
|
|
|
|
|
}
|
2025-09-10 16:59:50 +08:00
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
// 2) 若任一 monitor 的首条为 RUNNING,则该终端正在补招中 -> 跳过该终端,不会下发新的补招请求
|
2025-09-10 16:59:50 +08:00
|
|
|
|
bool has_running = false;
|
|
|
|
|
|
for (auto& lm : dev.line) {
|
|
|
|
|
|
if (!lm.recall_list.empty() &&
|
|
|
|
|
|
lm.recall_list.front().recall_status == static_cast<int>(RecallStatus::RUNNING)) {
|
|
|
|
|
|
has_running = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (has_running) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 3) 选择该终端的“第一条 NOT_STARTED(0)”作为本终端本轮任务
|
|
|
|
|
|
bool picked = false;
|
|
|
|
|
|
for (auto& lm : dev.line) {
|
2025-09-12 17:08:25 +08:00
|
|
|
|
if (lm.recall_list.empty()) continue; //跳过空的监测点
|
2025-09-10 16:59:50 +08:00
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
RecallMonitor& front = lm.recall_list.front(); //取非空测点的列表的第一条
|
2025-09-10 16:59:50 +08:00
|
|
|
|
if (front.recall_status == static_cast<int>(RecallStatus::NOT_STARTED)) {
|
|
|
|
|
|
// 标记为 RUNNING,并设置终端忙状态
|
2025-09-12 17:08:25 +08:00
|
|
|
|
front.recall_status = static_cast<int>(RecallStatus::RUNNING);//该补招记录刷新为补招中
|
2025-09-10 16:59:50 +08:00
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
dev.isbusy = 1; //标记为忙
|
|
|
|
|
|
dev.busytype = static_cast<int>(DeviceState::READING_EVENTLOG);//装置状态正在补招和idle的都刷新为正在补招
|
|
|
|
|
|
dev.busytimecount = 0; //刷新业务超时计数
|
|
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
// 记录任务(每终端只取这一条)
|
|
|
|
|
|
tasks.push_back(RecallTask{
|
|
|
|
|
|
dev.terminal_id,
|
|
|
|
|
|
front.StartTime,
|
|
|
|
|
|
front.EndTime,
|
|
|
|
|
|
lm.monitor_id
|
|
|
|
|
|
});
|
2025-09-12 17:08:25 +08:00
|
|
|
|
picked = true; //该装置已取
|
2025-09-10 16:59:50 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 若该终端没有 NOT_STARTED 的首条(可能剩下的都是 RUNNING 或内部已经被清空),
|
|
|
|
|
|
// 就留待下一轮;不要清空运行态,直到所有条目被处理为止。
|
|
|
|
|
|
}
|
|
|
|
|
|
} // 解锁
|
|
|
|
|
|
|
|
|
|
|
|
// 对于本轮挑选到的任务,逐终端下发一条,这些终端的状态都已设为补招
|
|
|
|
|
|
for (const auto& t : tasks) {
|
2025-09-12 17:08:25 +08:00
|
|
|
|
//处理入参
|
2025-09-10 16:59:50 +08:00
|
|
|
|
std::tm tm1{}, tm2{};
|
|
|
|
|
|
if (!parse_datetime_tm(t.start_time, tm1) || !parse_datetime_tm(t.end_time, tm2)) {
|
|
|
|
|
|
std::cout << "[check_recall_event] parse time fail: "
|
|
|
|
|
|
<< t.start_time << " ~ " << t.end_time << std::endl;
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t mp = 1; // 默认
|
|
|
|
|
|
try {
|
|
|
|
|
|
mp = static_cast<uint8_t>(std::stoi(t.monitor_index));
|
2025-09-12 17:08:25 +08:00
|
|
|
|
} catch (...) {
|
|
|
|
|
|
std::cout << "[check_recall_event] parse mpid fail: "
|
|
|
|
|
|
<< t.monitor_index << std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2025-09-10 16:59:50 +08:00
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
// 下发补招请求,action=2
|
2025-09-10 16:59:50 +08:00
|
|
|
|
ClientManager::instance().read_eventlog_action_to_device(
|
|
|
|
|
|
t.dev_id, tm1, tm2, 2, mp);//2是暂态事件
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[check_recall_event] SEND dev=" << t.dev_id
|
|
|
|
|
|
<< " monitor=" << mp
|
|
|
|
|
|
<< " start=" << t.start_time
|
|
|
|
|
|
<< " end=" << t.end_time
|
|
|
|
|
|
<< " action(2)" << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重要:本函数不把 RUNNING 改成 DONE/FAILED;
|
|
|
|
|
|
// 应由设备回调/结果处理逻辑在完成后调用通知函数,把对应 monitor.front().recall_status 从1置为 2/3,
|
2025-09-12 17:08:25 +08:00
|
|
|
|
// 下一轮本函数会弹掉 2/3,并在该终端所有 recall_list 全部清空后重置装置状态。
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////处理补招逻辑统计数据
|
|
|
|
|
|
// ====== 从文件名中提取“第二段下划线分隔字段”并转换为 epoch 秒 ======
|
|
|
|
|
|
static bool extract_epoch_from_filename(const std::string& name,
|
|
|
|
|
|
long long& out_epoch,
|
|
|
|
|
|
int logical_device_seq)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 拆分
|
|
|
|
|
|
std::vector<std::string> parts;
|
|
|
|
|
|
parts.reserve(8);
|
|
|
|
|
|
size_t start = 0, pos;
|
|
|
|
|
|
while ((pos = name.find('_', start)) != std::string::npos) {
|
|
|
|
|
|
parts.emplace_back(name.substr(start, pos - start));
|
|
|
|
|
|
start = pos + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
parts.emplace_back(name.substr(start)); // 最后一段(含扩展名)
|
|
|
|
|
|
|
|
|
|
|
|
if (parts.size() < 4) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 第二段序号是倒数第4段
|
|
|
|
|
|
const std::string& seq_str = parts[parts.size() - 4];
|
|
|
|
|
|
// 允许前导 0:把字符串转 int 后比较
|
|
|
|
|
|
for (char c : seq_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
|
|
|
|
|
|
int seq_val = 0;
|
|
|
|
|
|
try {
|
|
|
|
|
|
seq_val = std::stoi(seq_str);
|
|
|
|
|
|
} catch (...) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (seq_val != logical_device_seq) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 其余与上面相同
|
|
|
|
|
|
const std::string& date_str = parts[parts.size() - 3];
|
|
|
|
|
|
const std::string& time_str = parts[parts.size() - 2];
|
|
|
|
|
|
std::string ms_str = parts.back();
|
|
|
|
|
|
size_t dot = ms_str.find('.');
|
|
|
|
|
|
if (dot != std::string::npos) {
|
|
|
|
|
|
ms_str.erase(dot);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (date_str.size() != 8 || time_str.size() != 6) return false;
|
|
|
|
|
|
for (char c : date_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
|
|
|
|
|
|
for (char c : time_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
|
|
|
|
|
|
for (char c : ms_str) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
|
|
|
|
|
|
|
|
|
|
|
|
int year = std::stoi(date_str.substr(0, 4));
|
|
|
|
|
|
int month = std::stoi(date_str.substr(4, 2));
|
|
|
|
|
|
int day = std::stoi(date_str.substr(6, 2));
|
|
|
|
|
|
int hour = std::stoi(time_str.substr(0, 2));
|
|
|
|
|
|
int min = std::stoi(time_str.substr(2, 2));
|
|
|
|
|
|
int sec = std::stoi(time_str.substr(4, 2));
|
|
|
|
|
|
// int msec = std::stoi(ms_str);
|
|
|
|
|
|
|
|
|
|
|
|
std::tm tm{}; tm.tm_isdst = -1;
|
|
|
|
|
|
tm.tm_year = year - 1900;
|
|
|
|
|
|
tm.tm_mon = month - 1;
|
|
|
|
|
|
tm.tm_mday = day;
|
|
|
|
|
|
tm.tm_hour = hour;
|
|
|
|
|
|
tm.tm_min = min;
|
|
|
|
|
|
tm.tm_sec = sec;
|
|
|
|
|
|
|
|
|
|
|
|
time_t t = timegm(&tm);
|
|
|
|
|
|
if (t < 0) return false;
|
|
|
|
|
|
|
|
|
|
|
|
out_epoch = static_cast<long long>(t); // 秒级
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ====== ★修改:check_recall_stat —— 加入“两步法”状态机 ======
|
|
|
|
|
|
void check_recall_file() {
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<RecallTask> tasks; // 本轮要发送的“每终端一条”(目录请求 或 文件下载 请求)
|
|
|
|
|
|
|
|
|
|
|
|
{ //锁作用域
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(ledgermtx);
|
|
|
|
|
|
|
|
|
|
|
|
for (auto& dev : terminal_devlist) {
|
|
|
|
|
|
// 仅处理“正在补招/空闲”终端,与你原逻辑一致
|
|
|
|
|
|
if (dev.busytype != static_cast<int>(DeviceState::READING_STATSFILE) &&
|
|
|
|
|
|
dev.busytype != static_cast<int>(DeviceState::IDLE)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1) 清理首条 DONE/FAILED
|
|
|
|
|
|
bool any_non_empty = false;
|
2025-09-30 16:17:04 +08:00
|
|
|
|
bool has_running = false;
|
2025-09-12 17:08:25 +08:00
|
|
|
|
for (auto& lm : dev.line) {
|
|
|
|
|
|
while (!lm.recall_list_static.empty()) {
|
|
|
|
|
|
const RecallFile& front = lm.recall_list_static.front();
|
|
|
|
|
|
if (front.recall_status == static_cast<int>(RecallStatus::DONE)) {
|
|
|
|
|
|
std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
|
|
|
|
|
|
lm.recall_list_static.pop_front();
|
|
|
|
|
|
} else if (front.recall_status == static_cast<int>(RecallStatus::FAILED)) {
|
|
|
|
|
|
std::cout << "[check_recall_stat] FAILED dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " " << front.StartTime << " ~ " << front.EndTime << std::endl;
|
|
|
|
|
|
lm.recall_list_static.pop_front();
|
|
|
|
|
|
} else {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
break;//找到第一条不是成功或失败的记录就退出循环
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!lm.recall_list_static.empty()) {
|
|
|
|
|
|
any_non_empty = true;//弹出后是否为空
|
|
|
|
|
|
if (lm.recall_list_static.front().recall_status == static_cast<int>(RecallStatus::RUNNING)) {
|
|
|
|
|
|
has_running = true; //存在测点正在补招
|
2025-09-12 17:08:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 无条目时的装置态收尾
|
2025-09-30 16:17:04 +08:00
|
|
|
|
if (!any_non_empty && dev.busytype == static_cast<int>(DeviceState::READING_STATSFILE)) {
|
|
|
|
|
|
// 处于“文件补招”的状态且无条目 -> 清空运行态
|
2025-09-12 17:08:25 +08:00
|
|
|
|
dev.guid.clear();
|
|
|
|
|
|
dev.busytype = 0;
|
|
|
|
|
|
dev.isbusy = 0;
|
|
|
|
|
|
dev.busytimecount = 0;
|
|
|
|
|
|
continue;
|
2025-09-30 16:17:04 +08:00
|
|
|
|
} else if (!any_non_empty && dev.busytype != static_cast<int>(DeviceState::IDLE)) {//其他运行态不处理,idle往下执行
|
2025-09-12 17:08:25 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2) 若任一 monitor 的首条为 RUNNING,则该终端正在补招中 -> 不下发新的任务(但需要推进状态机!)
|
2025-09-30 16:17:04 +08:00
|
|
|
|
/*bool has_running = false;
|
2025-09-12 17:08:25 +08:00
|
|
|
|
for (auto& lm : dev.line) {
|
|
|
|
|
|
if (!lm.recall_list_static.empty() &&
|
|
|
|
|
|
lm.recall_list_static.front().recall_status == static_cast<int>(RecallStatus::RUNNING)) {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
has_running = true; //存在测点正在补招
|
2025-09-12 17:08:25 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2025-09-30 16:17:04 +08:00
|
|
|
|
}*/
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
|
|
|
|
|
// ★新增:当存在 RUNNING 时,推进“该终端的首条补招记录”的两步状态机
|
|
|
|
|
|
if (has_running) {
|
|
|
|
|
|
for (auto& lm : dev.line) {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
if (lm.recall_list_static.empty()) continue;//跳过没有记录的测点
|
|
|
|
|
|
RecallFile& front = lm.recall_list_static.front();//有记录测点的第一个补招
|
|
|
|
|
|
if (front.recall_status != static_cast<int>(RecallStatus::RUNNING)) continue;//跳过不是正在补招的记录
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
2025-09-30 16:17:04 +08:00
|
|
|
|
// 初始化阶段:补招分为两个阶段,读文件列表和下载文件,如果是刚进入 RUNNING 状态则初始化
|
|
|
|
|
|
if (front.phase == RecallPhase::IDLE) { //暂态补招
|
2025-09-12 17:08:25 +08:00
|
|
|
|
front.phase = RecallPhase::LISTING;
|
|
|
|
|
|
front.cur_dir_index = 0;
|
|
|
|
|
|
front.cur_dir.clear();
|
|
|
|
|
|
front.list_result = ActionResult::PENDING;
|
|
|
|
|
|
front.download_result = ActionResult::PENDING;
|
|
|
|
|
|
front.download_queue.clear();
|
|
|
|
|
|
// 立即发起第一个目录请求
|
|
|
|
|
|
if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
|
|
|
|
|
|
front.cur_dir = front.dir_candidates[front.cur_dir_index];
|
|
|
|
|
|
// ★★ 只发一个目录请求,并等待外部线程回写结果与文件名列表
|
|
|
|
|
|
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
|
|
|
|
|
|
std::cout << "[check_recall_stat] LIST req dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " dir=" << front.cur_dir << std::endl;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 无目录可查
|
|
|
|
|
|
front.recall_status = static_cast<int>(RecallStatus::FAILED);
|
|
|
|
|
|
std::cout << "[check_recall_stat] no dir candidates, FAIL dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 一个终端只推进首条,跳出
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// LISTING 阶段:等待其他线程回写 list_result + dir_files[cur_dir]
|
|
|
|
|
|
if (front.phase == RecallPhase::LISTING) {
|
|
|
|
|
|
if (front.list_result == ActionResult::PENDING) {
|
|
|
|
|
|
// 还在等目录回执,本轮不再发任何请求
|
|
|
|
|
|
// (外部线程拿到目录文件列表后,应写入:front.dir_files[front.cur_dir] = {a,b,c...} 并置 list_result=OK/FAIL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (front.list_result == ActionResult::FAIL) {
|
|
|
|
|
|
// 尝试下一个目录
|
|
|
|
|
|
front.cur_dir_index++;
|
|
|
|
|
|
front.list_result = ActionResult::PENDING;
|
|
|
|
|
|
front.download_queue.clear();
|
|
|
|
|
|
if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
|
|
|
|
|
|
front.cur_dir = front.dir_candidates[front.cur_dir_index];
|
|
|
|
|
|
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
|
|
|
|
|
|
std::cout << "[check_recall_stat] LIST retry dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " dir=" << front.cur_dir << std::endl;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 所有目录都失败
|
|
|
|
|
|
front.recall_status = static_cast<int>(RecallStatus::FAILED);
|
|
|
|
|
|
std::cout << "[check_recall_stat] all dir failed, FAIL dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// OK:根据起止时间筛选文件
|
|
|
|
|
|
{
|
|
|
|
|
|
long long beg = parse_time_to_epoch(front.StartTime);
|
|
|
|
|
|
long long end = parse_time_to_epoch(front.EndTime);
|
|
|
|
|
|
if (beg < 0 || end < 0 || beg > end) {
|
|
|
|
|
|
front.recall_status = static_cast<int>(RecallStatus::FAILED);
|
|
|
|
|
|
std::cout << "[check_recall_stat] time parse ERR, FAIL dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " start=" << front.StartTime
|
|
|
|
|
|
<< " end=" << front.EndTime << std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto it = front.dir_files.find(front.cur_dir);
|
|
|
|
|
|
if (it != front.dir_files.end()) {
|
|
|
|
|
|
if (front.direct_mode) {
|
|
|
|
|
|
// ★新增:直下文件(精确匹配文件名)
|
|
|
|
|
|
for (const auto& ent : it->second) {
|
|
|
|
|
|
if (ent.flag != 1) continue; // 只要文件
|
|
|
|
|
|
// 安全从 char[64] 转成 std::string
|
|
|
|
|
|
size_t n = ::strnlen(ent.name, sizeof(ent.name));
|
|
|
|
|
|
std::string fname(ent.name, n);
|
|
|
|
|
|
if (fname == front.target_filename) {
|
|
|
|
|
|
front.download_queue.push_back(front.cur_dir + "/" + fname);
|
|
|
|
|
|
break; // 找到一个就足够
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// ☆原有:按时间窗筛选
|
|
|
|
|
|
long long beg = parse_time_to_epoch(front.StartTime);
|
|
|
|
|
|
long long end = parse_time_to_epoch(front.EndTime);
|
|
|
|
|
|
|
|
|
|
|
|
for (const auto& ent : it->second) {
|
|
|
|
|
|
if (ent.flag != 1) continue; // 只要文件
|
|
|
|
|
|
// 文件名
|
|
|
|
|
|
size_t n = ::strnlen(ent.name, sizeof(ent.name));
|
|
|
|
|
|
std::string fname(ent.name, n);
|
|
|
|
|
|
|
|
|
|
|
|
long long fe = -1;
|
|
|
|
|
|
int seq = 0;
|
|
|
|
|
|
try { seq = std::stoi(lm.logical_device_seq); } catch (...) { seq = 0; }
|
|
|
|
|
|
if (!extract_epoch_from_filename(fname, fe, seq)) continue;
|
|
|
|
|
|
if (fe >= beg && fe <= end) {
|
|
|
|
|
|
front.download_queue.push_back(front.cur_dir + "/" + fname);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (front.download_queue.empty()) {
|
|
|
|
|
|
// 当前目录无匹配文件 -> 试下一个目录
|
|
|
|
|
|
front.cur_dir_index++;
|
|
|
|
|
|
front.list_result = ActionResult::PENDING;
|
|
|
|
|
|
if (front.cur_dir_index < static_cast<int>(front.dir_candidates.size())) {
|
|
|
|
|
|
front.cur_dir = front.dir_candidates[front.cur_dir_index];
|
|
|
|
|
|
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);
|
|
|
|
|
|
std::cout << "[check_recall_stat] LIST next dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " dir=" << front.cur_dir << std::endl;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 所有目录都“无匹配文件”
|
|
|
|
|
|
front.recall_status = static_cast<int>(RecallStatus::FAILED);
|
|
|
|
|
|
std::cout << "[check_recall_stat] no matched files in ALL dirs, FAIL dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
continue;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 进入下载阶段
|
|
|
|
|
|
front.phase = RecallPhase::DOWNLOADING;
|
|
|
|
|
|
front.download_result = ActionResult::PENDING;
|
|
|
|
|
|
front.downloading_file.clear();
|
|
|
|
|
|
std::cout << "[check_recall_stat] enter DOWNLOADING dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " count=" << front.download_queue.size() << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DOWNLOADING 阶段:一次只下一个文件,等待外部线程填充 download_result
|
|
|
|
|
|
if (front.phase == RecallPhase::DOWNLOADING) {
|
|
|
|
|
|
if (front.downloading_file.empty()) {
|
|
|
|
|
|
if (front.download_queue.empty()) {
|
|
|
|
|
|
// 所有文件下载完毕
|
|
|
|
|
|
front.recall_status = static_cast<int>(RecallStatus::DONE);
|
|
|
|
|
|
std::cout << "[check_recall_stat] DONE dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id << std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 发起下一文件下载
|
|
|
|
|
|
front.downloading_file = front.download_queue.front();
|
|
|
|
|
|
front.download_queue.pop_front();
|
|
|
|
|
|
front.download_result = ActionResult::PENDING;
|
|
|
|
|
|
|
|
|
|
|
|
// ★★ 只发一个下载请求,等待外部线程回写结果
|
|
|
|
|
|
ClientManager::instance().add_file_download_action_to_device(dev.terminal_id, front.downloading_file);
|
|
|
|
|
|
std::cout << "[check_recall_stat] DL req dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " file=" << front.downloading_file << std::endl;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 等待 download_result
|
|
|
|
|
|
if (front.download_result == ActionResult::PENDING) {
|
|
|
|
|
|
continue; // 仍在等待
|
|
|
|
|
|
}
|
|
|
|
|
|
if (front.download_result == ActionResult::OK) {
|
|
|
|
|
|
// 记录成功文件
|
|
|
|
|
|
front.file_paths.push_back(front.downloading_file);
|
|
|
|
|
|
std::cout << "[check_recall_stat] DL ok dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " file=" << front.downloading_file << std::endl;
|
|
|
|
|
|
// 清空当前文件标志,进入下一轮取队首
|
|
|
|
|
|
front.downloading_file.clear();
|
|
|
|
|
|
continue;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 失败:直接尝试下一个文件(不中断整条补招)
|
|
|
|
|
|
std::cout << "[check_recall_stat] DL fail dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " file=" << front.downloading_file << std::endl;
|
|
|
|
|
|
front.downloading_file.clear();
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 一个终端只推进“一个监测点的首条”状态机,避免并行
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 本终端已有 RUNNING 项,且已推进;不再挑选新的 NOT_STARTED
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3) 没有 RUNNING:挑选第一条 NOT_STARTED,并发起“首个目录”的请求
|
|
|
|
|
|
for (auto& lm : dev.line) {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
if (lm.recall_list_static.empty()) continue; //跳过补招列表为空的测点
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
2025-09-30 16:17:04 +08:00
|
|
|
|
RecallFile& front = lm.recall_list_static.front(); //取测点第一条记录
|
|
|
|
|
|
if (front.recall_status == static_cast<int>(RecallStatus::NOT_STARTED)) { //补招未开始
|
2025-09-12 17:08:25 +08:00
|
|
|
|
// 标记为 RUNNING,并设置终端忙状态
|
2025-09-30 16:17:04 +08:00
|
|
|
|
front.recall_status = static_cast<int>(RecallStatus::RUNNING); //该补招记录刷新为补招中
|
|
|
|
|
|
dev.isbusy = 1; //装置由idle标记为忙
|
|
|
|
|
|
dev.busytype = static_cast<int>(DeviceState::READING_STATSFILE);//装置状态刷新为正在补招文件
|
|
|
|
|
|
dev.busytimecount = 0; //清空业务超时计数
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化状态机并发出第一个目录请求
|
|
|
|
|
|
front.reset_runtime(true);//保留直下文件信息
|
2025-09-30 16:17:04 +08:00
|
|
|
|
front.phase = RecallPhase::LISTING; //正在请求并等待“目录文件名列表”
|
|
|
|
|
|
if (!front.dir_candidates.empty()) {//目录列表非空
|
|
|
|
|
|
front.cur_dir_index = 0; //正在尝试的目录下标
|
|
|
|
|
|
front.cur_dir = front.dir_candidates[0]; //第一个目录
|
|
|
|
|
|
front.list_result = ActionResult::PENDING; //目录状态:等待返回
|
|
|
|
|
|
ClientManager::instance().add_file_menu_action_to_device(dev.terminal_id, front.cur_dir);//调用目录请求接口
|
2025-09-12 17:08:25 +08:00
|
|
|
|
std::cout << "[check_recall_stat] LIST start dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id
|
|
|
|
|
|
<< " dir=" << front.cur_dir
|
|
|
|
|
|
<< " start=" << front.StartTime
|
|
|
|
|
|
<< " end=" << front.EndTime << std::endl;
|
|
|
|
|
|
} else {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
front.recall_status = static_cast<int>(RecallStatus::FAILED); //目录列表空,失败
|
2025-09-12 17:08:25 +08:00
|
|
|
|
std::cout << "[check_recall_stat] empty dir_candidates, FAIL dev=" << dev.terminal_id
|
|
|
|
|
|
<< " monitor=" << lm.monitor_id << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 每终端本轮仅取一条
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} // end for dev
|
|
|
|
|
|
} // 解锁
|
|
|
|
|
|
|
|
|
|
|
|
// ★说明:本函数不主动把 RUNNING 改 DONE/FAILED;
|
|
|
|
|
|
// 由“其他线程”在目录/下载结果返回后,找到对应 dev/monitor 的 lm.recall_list_static.front(),
|
|
|
|
|
|
// 写入:
|
|
|
|
|
|
// - 目录请求:front.dir_files[front.cur_dir] = {...}; front.list_result = OK/FAIL;
|
|
|
|
|
|
// - 文件下载:front.download_result = OK/FAIL;
|
|
|
|
|
|
// 下一轮本函数会推进状态机(继续本目录的文件或切到下一个目录)。
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////根据下发的指令直接补招文件
|
|
|
|
|
|
// ★新增:直下文件的任务入队(终端、测点、文件名)
|
|
|
|
|
|
// 约定:外部线程的回调,照旧把目录返回值写到 lm.recall_list_static.front().dir_files[dir],
|
|
|
|
|
|
// 并置 front.list_result=OK/FAIL;下载回执置 front.download_result=OK/FAIL。
|
|
|
|
|
|
// 处理指令部分将文件名拼接出来调用这个函数
|
|
|
|
|
|
bool enqueue_direct_download(const std::string& dev_id,
|
|
|
|
|
|
const std::string& monitor_id,
|
|
|
|
|
|
const std::string& filename,
|
|
|
|
|
|
const std::vector<std::string>& dir_candidates)
|
|
|
|
|
|
{
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(ledgermtx);
|
|
|
|
|
|
|
|
|
|
|
|
// 找终端
|
|
|
|
|
|
auto dev_it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
|
|
|
|
|
|
[&](const terminal_dev& d){ return d.terminal_id == dev_id; });
|
|
|
|
|
|
if (dev_it == terminal_devlist.end()) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 找监测点
|
|
|
|
|
|
auto lm_it = std::find_if(dev_it->line.begin(), dev_it->line.end(),
|
|
|
|
|
|
[&](const ledger_monitor& lm){ return lm.monitor_id == monitor_id; });
|
|
|
|
|
|
if (lm_it == dev_it->line.end()) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 组装一条 RecallFile
|
|
|
|
|
|
RecallFile rf;
|
|
|
|
|
|
rf.recall_status = static_cast<int>(RecallStatus::NOT_STARTED);
|
|
|
|
|
|
rf.StartTime = "1970-01-01 00:00:00"; // 仅占位,直下文件不会用到时间窗
|
|
|
|
|
|
rf.EndTime = "1970-01-01 00:00:01";
|
|
|
|
|
|
rf.dir_candidates = dir_candidates; // 传入要检索的目录列表
|
|
|
|
|
|
rf.direct_mode = true; // ★关键:直下文件
|
|
|
|
|
|
rf.target_filename = filename; // ★关键:匹配的目标文件名
|
|
|
|
|
|
|
|
|
|
|
|
lm_it->recall_list_static.push_back(std::move(rf));
|
|
|
|
|
|
|
|
|
|
|
|
// 若设备空闲,可直接置忙(可选,视你的流程而定)
|
|
|
|
|
|
if (dev_it->busytype == static_cast<int>(DeviceState::IDLE)) {
|
|
|
|
|
|
dev_it->isbusy = 1;
|
|
|
|
|
|
dev_it->busytype = static_cast<int>(DeviceState::READING_STATSFILE); // 或 READING_EVENTFILE
|
|
|
|
|
|
dev_it->busytimecount = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
2025-09-10 16:59:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////通讯响应处理
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
|
|
|
|
|
void filemenu_cache_put(const std::string& dev_id,
|
|
|
|
|
|
std::vector<tag_dir_info> FileList)
|
|
|
|
|
|
{
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(g_filemenu_cache_mtx);
|
|
|
|
|
|
g_filemenu_cache[dev_id] = std::move(FileList); // 直接存 vector<tag_dir_info>
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool filemenu_cache_take(const std::string& dev_id, std::vector<tag_dir_info>& out)
|
|
|
|
|
|
{
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(g_filemenu_cache_mtx);
|
|
|
|
|
|
auto it = g_filemenu_cache.find(dev_id);
|
|
|
|
|
|
if (it == g_filemenu_cache.end()) return false;
|
|
|
|
|
|
|
|
|
|
|
|
out = std::move(it->second); // 转移 vector<tag_dir_info>
|
|
|
|
|
|
g_filemenu_cache.erase(it); // 删除缓存
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取文件名列表(仅 flag==1)
|
|
|
|
|
|
static inline void build_file_name_list(const std::vector<tag_dir_info>& in,
|
|
|
|
|
|
std::list<std::string>& out)
|
|
|
|
|
|
{
|
|
|
|
|
|
out.clear();
|
|
|
|
|
|
for (const auto& e : in) {
|
|
|
|
|
|
if (e.flag == 1) {
|
|
|
|
|
|
// 安全地从固定长度 char[64] 转成 std::string
|
|
|
|
|
|
size_t n = ::strnlen(e.name, sizeof(e.name));
|
|
|
|
|
|
out.emplace_back(std::string(e.name, n));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
void on_device_response_minimal(int response_code,
|
|
|
|
|
|
const std::string& id,
|
|
|
|
|
|
unsigned char cid,
|
|
|
|
|
|
int device_state_int)
|
|
|
|
|
|
{
|
|
|
|
|
|
const bool ok = is_ok(response_code);
|
|
|
|
|
|
DeviceState state = static_cast<DeviceState>(device_state_int);
|
|
|
|
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
|
|
// ================= 特殊:补招事件日志 =================
|
|
|
|
|
|
case DeviceState::READING_EVENTLOG: {
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(ledgermtx);
|
|
|
|
|
|
|
|
|
|
|
|
terminal_dev* dev = nullptr;
|
|
|
|
|
|
for (auto& d : terminal_devlist) {
|
|
|
|
|
|
if (d.terminal_id == id) { dev = &d; break; }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!dev) {
|
|
|
|
|
|
std::cout << "[RESP][EVENTLOG] dev not found, id=" << id
|
|
|
|
|
|
<< " rc=" << response_code << std::endl;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 找到“首条 RUNNING”的补招条目,按 rc==200 成功/失败标记
|
|
|
|
|
|
bool updated = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 1) 找到 logical_device_seq 与 cid 匹配的 monitor
|
|
|
|
|
|
ledger_monitor* matched_monitor = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
// 将 cid 转成十进制字符串用于直接比较
|
|
|
|
|
|
const std::string cid_str = std::to_string(static_cast<int>(cid));
|
|
|
|
|
|
|
|
|
|
|
|
for (auto& lm : dev->line) {
|
|
|
|
|
|
// 直接字符串相等
|
|
|
|
|
|
if (lm.logical_device_seq == cid_str) {
|
|
|
|
|
|
matched_monitor = &lm;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!matched_monitor) { //没有匹配的监测点
|
|
|
|
|
|
std::cout << "[RESP][EVENTLOG][WARN] dev=" << id
|
|
|
|
|
|
<< " cannot find monitor by cid=" << static_cast<int>(cid)
|
|
|
|
|
|
<< " (no logical_device_seq match)" << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
|
|
|
|
// 2) 仅更新该监测点 recall_list 的首条,且要求处于 RUNNING
|
|
|
|
|
|
if (!matched_monitor->recall_list.empty()) {
|
|
|
|
|
|
RecallMonitor& front = matched_monitor->recall_list.front(); //取出首条
|
|
|
|
|
|
if (front.recall_status == static_cast<int>(RecallStatus::RUNNING)) { //正在补招
|
|
|
|
|
|
if (ok) { //补招成功
|
|
|
|
|
|
front.recall_status = static_cast<int>(RecallStatus::DONE); //标记为完成,补招任务会执行下一条
|
|
|
|
|
|
std::cout << "[RESP][EVENTLOG][OK] dev=" << id
|
|
|
|
|
|
<< " mon=" << matched_monitor->monitor_id
|
|
|
|
|
|
<< " " << front.StartTime << "~" << front.EndTime
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
} else { //补招失败
|
|
|
|
|
|
front.recall_status = static_cast<int>(RecallStatus::FAILED);
|
|
|
|
|
|
std::cout << "[RESP][EVENTLOG][FAIL] dev=" << id
|
|
|
|
|
|
<< " mon=" << matched_monitor->monitor_id
|
|
|
|
|
|
<< " " << front.StartTime << "~" << front.EndTime
|
|
|
|
|
|
<< " rc=" << response_code << std::endl; //错误响应码
|
|
|
|
|
|
|
|
|
|
|
|
//记录日志
|
2025-09-22 16:46:33 +08:00
|
|
|
|
DIY_ERRORLOG_CODE(matched_monitor->monitor_id.c_str(),2,static_cast<int>(LogCode::LOG_CODE_RECALL),"【ERROR】监测点:%s 补招数据失败 - 失败时间点:%lld 至 %lld",matched_monitor->monitor_id.c_str(),front.StartTime,front.EndTime);
|
2025-09-10 16:59:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
updated = true;
|
|
|
|
|
|
} else { //首条不是 RUNNING 状态,不应该收到这条响应
|
|
|
|
|
|
std::cout << "[RESP][EVENTLOG][WARN] dev=" << id
|
|
|
|
|
|
<< " mon=" << matched_monitor->monitor_id
|
|
|
|
|
|
<< " first item not RUNNING (status="
|
|
|
|
|
|
<< front.recall_status << ")" << std::endl; //打印调试
|
|
|
|
|
|
}
|
|
|
|
|
|
} else { //补招列表为空,不应该收到这条响应
|
|
|
|
|
|
std::cout << "[RESP][EVENTLOG][WARN] dev=" << id
|
|
|
|
|
|
<< " mon=" << matched_monitor->monitor_id
|
|
|
|
|
|
<< " recall_list empty" << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!updated) {//不更新补招列表
|
|
|
|
|
|
std::cout << "[RESP][EVENTLOG][WARN] no RUNNING item, dev=" << id
|
|
|
|
|
|
<< " rc=" << response_code << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================= 特殊:读取文件目录 =================
|
|
|
|
|
|
case DeviceState::READING_FILEMENU: {
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(ledgermtx);
|
|
|
|
|
|
|
|
|
|
|
|
// 1) 找到对应终端
|
|
|
|
|
|
terminal_dev* dev = nullptr;
|
|
|
|
|
|
for (auto& d : terminal_devlist) {
|
|
|
|
|
|
if (d.terminal_id == id) { dev = &d; break; }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!dev) {
|
|
|
|
|
|
std::cout << "[RESP][FILEMENU] dev not found, id=" << id
|
|
|
|
|
|
<< " rc=" << response_code << std::endl;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-30 16:17:04 +08:00
|
|
|
|
std::vector<tag_dir_info> names;
|
2025-09-10 16:59:50 +08:00
|
|
|
|
// 2) 按该终端当前 busytype 分支处理
|
|
|
|
|
|
const int bt = dev->busytype;
|
|
|
|
|
|
if (bt == static_cast<int>(DeviceState::READING_FILEMENU)) {
|
|
|
|
|
|
// ====== 分支 A:当前业务就是“读取文件目录” ======
|
|
|
|
|
|
if (ok) {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
|
2025-09-12 17:08:25 +08:00
|
|
|
|
if (filemenu_cache_take(id, names)) {
|
|
|
|
|
|
|
|
|
|
|
|
//发送目录
|
2025-09-23 21:00:24 +08:00
|
|
|
|
send_file_list(dev,names);
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 失败:响应web并复位为空闲
|
2025-09-23 21:00:24 +08:00
|
|
|
|
send_reply_to_cloud(static_cast<int>(ResponseCode::BAD_REQUEST), id, static_cast<int>(DeviceState::READING_FILEMENU),dev->guid,dev->mac);
|
2025-09-12 17:08:25 +08:00
|
|
|
|
std::cout << "[RESP][FILEMENU->FILEMENU][WARN] dev=" << id
|
|
|
|
|
|
<< " names missing in cache" << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
// 成功:复位
|
|
|
|
|
|
dev->guid.clear();
|
|
|
|
|
|
dev->isbusy = 0;
|
|
|
|
|
|
dev->busytype = 0;
|
|
|
|
|
|
dev->busytimecount = 0;
|
|
|
|
|
|
std::cout << "[RESP][FILEMENU->FILEMENU][OK] dev=" << id << std::endl;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 失败:响应web并复位为空闲
|
2025-09-23 21:00:24 +08:00
|
|
|
|
send_reply_to_cloud(response_code, id, static_cast<int>(DeviceState::READING_FILEMENU),dev->guid,dev->mac);
|
2025-09-10 16:59:50 +08:00
|
|
|
|
|
|
|
|
|
|
dev->guid.clear();
|
|
|
|
|
|
dev->isbusy = 0;
|
|
|
|
|
|
dev->busytype = 0;
|
|
|
|
|
|
dev->busytimecount = 0;
|
|
|
|
|
|
std::cout << "[RESP][FILEMENU->FILEMENU][FAIL] dev=" << id
|
|
|
|
|
|
<< " rc=" << response_code << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (
|
|
|
|
|
|
bt == static_cast<int>(DeviceState::READING_EVENTFILE)
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|| bt == static_cast<int>(DeviceState::READING_STATSFILE)
|
2025-09-10 16:59:50 +08:00
|
|
|
|
) {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
// ====== 分支 B:当前业务为“事件文件/统计文件”(两者处理相同) ======
|
|
|
|
|
|
// 一个装置同一时刻只会有一个监测点在补招
|
2025-09-12 17:08:25 +08:00
|
|
|
|
ledger_monitor* running_monitor = nullptr;
|
|
|
|
|
|
RecallFile* running_front = nullptr;
|
|
|
|
|
|
|
2025-09-30 16:17:04 +08:00
|
|
|
|
// 在该终端下,找到“首条 recall_list_static.front() 正在运行且处于 LISTING 的监测点”
|
2025-09-12 17:08:25 +08:00
|
|
|
|
for (auto& lm : dev->line) {
|
|
|
|
|
|
if (lm.recall_list_static.empty()) continue;
|
|
|
|
|
|
RecallFile& f = lm.recall_list_static.front();
|
2025-09-30 16:17:04 +08:00
|
|
|
|
if (f.recall_status == static_cast<int>(RecallStatus::RUNNING)//补招中
|
|
|
|
|
|
&& f.phase == RecallPhase::LISTING) {//正在请求目录
|
|
|
|
|
|
running_monitor = &lm; //补招的测点
|
|
|
|
|
|
running_front = &f; //补招记录
|
2025-09-12 17:08:25 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-30 16:17:04 +08:00
|
|
|
|
if (!running_monitor || !running_front) { //该装置没有正在补招的测点和补招记录,退出处理
|
|
|
|
|
|
std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][WARN] dev=" << id
|
|
|
|
|
|
<< " no RUNNING/LISTING recall on this device, ignore resp"
|
2025-09-12 17:08:25 +08:00
|
|
|
|
<< " rc=" << response_code << std::endl;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-30 16:17:04 +08:00
|
|
|
|
// 根据回执结果,回写目录结果;状态机会在下一轮推进到下一个目录/结束
|
2025-09-10 16:59:50 +08:00
|
|
|
|
if (ok) {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
running_front->dir_files[running_front->cur_dir] = std::move(names);
|
|
|
|
|
|
running_front->list_result = ActionResult::OK;
|
|
|
|
|
|
std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][OK] dev=" << id
|
2025-09-12 17:08:25 +08:00
|
|
|
|
<< " monitor=" << running_monitor->monitor_id
|
2025-09-30 16:17:04 +08:00
|
|
|
|
<< " dir=" << running_front->cur_dir << std::endl;
|
2025-09-10 16:59:50 +08:00
|
|
|
|
} else {
|
2025-09-30 16:17:04 +08:00
|
|
|
|
running_front->list_result = ActionResult::FAIL;
|
|
|
|
|
|
std::cout << "[RESP][FILEMENU->(EVENT/STATS)FILE][FAIL] dev=" << id
|
2025-09-12 17:08:25 +08:00
|
|
|
|
<< " monitor=" << running_monitor->monitor_id
|
2025-09-30 16:17:04 +08:00
|
|
|
|
<< " dir=" << running_front->cur_dir
|
2025-09-10 16:59:50 +08:00
|
|
|
|
<< " rc=" << response_code << std::endl;
|
|
|
|
|
|
}
|
2025-09-12 17:08:25 +08:00
|
|
|
|
break;
|
2025-09-10 16:59:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
// ====== 分支 C:其他业务场景下收到 FILEMENU 响应,统一处理 打印提示======
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
// 成功:
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[unknown][RESP][FILEMENU->OTHER][OK] dev=" << id
|
|
|
|
|
|
<< " keep busytype=" << bt << std::endl;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 失败:
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[unknown][RESP][FILEMENU->OTHER][FAIL] dev=" << id
|
|
|
|
|
|
<< " rc=" << response_code << " prev busytype=" << bt << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================= 特殊:下载文件 =================
|
|
|
|
|
|
case DeviceState::READING_FILEDATA: {
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(ledgermtx);
|
|
|
|
|
|
|
|
|
|
|
|
// 1) 找到对应终端
|
|
|
|
|
|
terminal_dev* dev = nullptr;
|
|
|
|
|
|
for (auto& d : terminal_devlist) {
|
|
|
|
|
|
if (d.terminal_id == id) { dev = &d; break; }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!dev) {
|
|
|
|
|
|
std::cout << "[RESP][FILEDATA] dev not found, id=" << id
|
|
|
|
|
|
<< " rc=" << response_code << std::endl;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2) 按该终端当前 busytype 分支处理
|
|
|
|
|
|
const int bt = dev->busytype;
|
|
|
|
|
|
if (bt == static_cast<int>(DeviceState::READING_FILEDATA)) {
|
|
|
|
|
|
// ====== 分支 A:当前业务就是“读取文件数据” ======
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
// 成功:复位
|
2025-09-23 21:00:24 +08:00
|
|
|
|
send_reply_to_cloud(static_cast<int>(ResponseCode::OK), id, static_cast<int>(DeviceState::READING_FILEDATA),dev->guid,dev->mac);
|
2025-09-10 16:59:50 +08:00
|
|
|
|
|
|
|
|
|
|
dev->guid.clear();
|
|
|
|
|
|
dev->isbusy = 0;
|
|
|
|
|
|
dev->busytype = 0;
|
|
|
|
|
|
dev->busytimecount = 0;
|
|
|
|
|
|
std::cout << "[RESP][FILEDATA->FILEDATA][OK] dev=" << id << std::endl;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 失败:响应web并复位
|
2025-09-23 21:00:24 +08:00
|
|
|
|
send_reply_to_cloud(response_code, id, static_cast<int>(DeviceState::READING_FILEDATA),dev->guid,dev->mac);
|
2025-09-10 16:59:50 +08:00
|
|
|
|
|
|
|
|
|
|
dev->guid.clear();
|
|
|
|
|
|
dev->isbusy = 0;
|
|
|
|
|
|
dev->busytype = 0;
|
|
|
|
|
|
dev->busytimecount = 0;
|
|
|
|
|
|
std::cout << "[RESP][FILEDATA->FILEDATA][FAIL] dev=" << id
|
|
|
|
|
|
<< " rc=" << response_code << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (
|
|
|
|
|
|
bt == static_cast<int>(DeviceState::READING_EVENTFILE)
|
|
|
|
|
|
|| bt == static_cast<int>(DeviceState::READING_STATSFILE)
|
|
|
|
|
|
) {
|
|
|
|
|
|
// ====== 分支 B:当前业务为“下载事件文件/统计文件”(两者处理相同) ======
|
2025-09-12 17:08:25 +08:00
|
|
|
|
// ★新增:通过 cid 精确找到监测点(logical_device_seq == cid)
|
|
|
|
|
|
ledger_monitor* matched_monitor = nullptr;
|
|
|
|
|
|
{
|
|
|
|
|
|
const std::string cid_str = std::to_string(static_cast<int>(cid));
|
|
|
|
|
|
for (auto& lm : dev->line) {
|
|
|
|
|
|
if (lm.logical_device_seq == cid_str) { matched_monitor = &lm; break; }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!matched_monitor || matched_monitor->recall_list_static.empty()) {
|
|
|
|
|
|
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
|
|
|
|
|
|
<< " no matched monitor or empty recall list, cid="
|
|
|
|
|
|
<< static_cast<int>(cid) << std::endl;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RecallFile& front = matched_monitor->recall_list_static.front();
|
|
|
|
|
|
if (front.recall_status != static_cast<int>(RecallStatus::RUNNING)
|
|
|
|
|
|
|| front.phase != RecallPhase::DOWNLOADING) {
|
|
|
|
|
|
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][WARN] dev=" << id
|
|
|
|
|
|
<< " monitor=" << matched_monitor->monitor_id
|
|
|
|
|
|
<< " ignore resp: status=" << front.recall_status
|
|
|
|
|
|
<< " phase=" << static_cast<int>(front.phase) << std::endl;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
if (ok) {
|
2025-09-12 17:08:25 +08:00
|
|
|
|
// ★新增:下载成功 -> 通知状态机推进到下一个文件(真正入账 file_paths 在 check_recall_stat 里做)
|
|
|
|
|
|
front.download_result = ActionResult::OK;
|
|
|
|
|
|
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][OK] dev=" << id
|
|
|
|
|
|
<< " file=" << front.downloading_file << std::endl;
|
2025-09-10 16:59:50 +08:00
|
|
|
|
} else {
|
2025-09-12 17:08:25 +08:00
|
|
|
|
// ★新增:下载失败 -> 让状态机尝试下一个文件
|
|
|
|
|
|
front.download_result = ActionResult::FAIL;
|
2025-09-10 16:59:50 +08:00
|
|
|
|
std::cout << "[RESP][FILEDATA->(EVENT/STATS)FILE][FAIL] dev=" << id
|
2025-09-12 17:08:25 +08:00
|
|
|
|
<< " file=" << front.downloading_file
|
2025-09-10 16:59:50 +08:00
|
|
|
|
<< " rc=" << response_code << std::endl;
|
|
|
|
|
|
}
|
2025-09-12 17:08:25 +08:00
|
|
|
|
break;
|
2025-09-10 16:59:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
// ====== 分支 C:其他业务场景下收到 FILEDATA 响应,统一处理 打印提示======
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
// 成功:
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[unknown][RESP][FILEDATA->OTHER][OK] dev=" << id
|
|
|
|
|
|
<< " keep busytype=" << bt << std::endl;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 失败:
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[unknown][RESP][FILEDATA->OTHER][FAIL] dev=" << id
|
|
|
|
|
|
<< " rc=" << response_code << " prev busytype=" << bt << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================= 其它状态统一处理 =================
|
|
|
|
|
|
default: {
|
|
|
|
|
|
|
2025-09-23 21:00:24 +08:00
|
|
|
|
std::lock_guard<std::mutex> lk(ledgermtx);
|
|
|
|
|
|
terminal_dev* dev = nullptr;
|
|
|
|
|
|
for (auto& d : terminal_devlist) {
|
|
|
|
|
|
if (d.terminal_id == id) { dev = &d; break; }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (dev) {
|
|
|
|
|
|
//直接根据输入响应mq
|
|
|
|
|
|
send_reply_to_cloud(response_code, id, device_state_int, dev->guid, dev->mac);
|
|
|
|
|
|
//其他的错误和成功都会结束业务
|
|
|
|
|
|
dev->guid.clear(); // 清空 guid
|
|
|
|
|
|
dev->busytype = 0; // 业务类型归零
|
|
|
|
|
|
dev->isbusy = 0; // 清空业务标志
|
|
|
|
|
|
dev->busytimecount = 0; // 计时归零
|
|
|
|
|
|
std::cout << "[clear_terminal_runtime_state] Cleared runtime state for terminal_id="
|
|
|
|
|
|
<< id << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 16:59:50 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2025-09-23 21:00:24 +08:00
|
|
|
|
} // end switch
|
2025-09-12 17:08:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////记录暂态事件到本地
|
|
|
|
|
|
// 将一条暂态数据更新/写入到指定终端ID与逻辑序号的监测点
|
|
|
|
|
|
// 返回 true 表示已写入(更新或追加),false 表示未找到对应终端/监测点。
|
|
|
|
|
|
|
|
|
|
|
|
bool append_qvvr_event(const std::string& terminal_id,
|
|
|
|
|
|
int logical_seq, // 监测点序号(如 1)
|
|
|
|
|
|
int nType, // 事件类型
|
|
|
|
|
|
double fPersisstime_sec, // 持续时间(秒)
|
|
|
|
|
|
double fMagnitude_pu, // 幅值(pu)
|
|
|
|
|
|
uint64_t triggerTimeMs, // 触发时间(毫秒)
|
|
|
|
|
|
int phase) // 相别
|
|
|
|
|
|
{
|
|
|
|
|
|
std::cout << "[append_qvvr_event] enter"
|
|
|
|
|
|
<< " tid=" << std::this_thread::get_id()
|
|
|
|
|
|
<< " terminal_id=" << terminal_id
|
|
|
|
|
|
<< " logical_seq=" << logical_seq
|
|
|
|
|
|
<< " type=" << nType
|
|
|
|
|
|
<< " per_s=" << fPersisstime_sec
|
|
|
|
|
|
<< " mag_pu=" << fMagnitude_pu
|
|
|
|
|
|
<< " time_ms=" << static_cast<unsigned long long>(triggerTimeMs)
|
|
|
|
|
|
<< " phase=" << phase
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(ledgermtx);
|
|
|
|
|
|
std::cout << "[append_qvvr_event] lock acquired. terminal_devlist.size="
|
|
|
|
|
|
<< terminal_devlist.size() << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
// 1) 找终端
|
|
|
|
|
|
auto dev_it = std::find_if(terminal_devlist.begin(), terminal_devlist.end(),
|
|
|
|
|
|
[&](const terminal_dev& d){ return d.terminal_id == terminal_id; });
|
|
|
|
|
|
|
|
|
|
|
|
if (dev_it == terminal_devlist.end()) {
|
|
|
|
|
|
std::cout << "[append_qvvr_event][MISS] terminal not found: "
|
|
|
|
|
|
<< terminal_id << std::endl;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::cout << "[append_qvvr_event][HIT] terminal_id=" << terminal_id
|
|
|
|
|
|
<< " monitors(line).size=" << dev_it->line.size()
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
// 2) 找监测点(按逻辑序号匹配:字符串等于 或 数值等于)
|
|
|
|
|
|
ledger_monitor* pMon = nullptr;
|
|
|
|
|
|
for (size_t i = 0; i < dev_it->line.size(); ++i) {
|
|
|
|
|
|
auto& m = dev_it->line[i];
|
|
|
|
|
|
bool eq_str = (m.logical_device_seq == std::to_string(logical_seq));
|
|
|
|
|
|
bool eq_num = false;
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!m.logical_device_seq.empty())
|
|
|
|
|
|
eq_num = (std::stoi(m.logical_device_seq) == logical_seq);
|
|
|
|
|
|
} catch (...) {
|
|
|
|
|
|
// 仅调试提示,不改变原逻辑
|
|
|
|
|
|
std::cout << "[append_qvvr_event][monitor #" << i
|
|
|
|
|
|
<< "] stoi fail, logical_device_seq=\""
|
|
|
|
|
|
<< m.logical_device_seq << "\"" << std::endl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[append_qvvr_event][probe monitor #" << i
|
|
|
|
|
|
<< "] monitor_id=" << m.monitor_id
|
|
|
|
|
|
<< " logical_device_seq=\"" << m.logical_device_seq << "\""
|
|
|
|
|
|
<< " eq_str=" << eq_str << " eq_num=" << eq_num << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
if (eq_str || eq_num) { pMon = &m; break; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!pMon) {
|
|
|
|
|
|
std::cout << "[append_qvvr_event][MISS] monitor not found by seq="
|
|
|
|
|
|
<< logical_seq << " in terminal_id=" << terminal_id << std::endl;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::cout << "[append_qvvr_event][HIT] monitor_id=" << pMon->monitor_id
|
|
|
|
|
|
<< " logical_device_seq=" << pMon->logical_device_seq
|
|
|
|
|
|
<< " qvvrdata.size=" << pMon->qvvrevent.qvvrdata.size()
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
qvvr_event& qe = pMon->qvvrevent;
|
|
|
|
|
|
|
|
|
|
|
|
// 3) 先尝试“就地更新”(同类型 + 同时间戳 视为同一事件)
|
|
|
|
|
|
for (size_t i = 0; i < qe.qvvrdata.size(); ++i) {
|
|
|
|
|
|
auto& q = qe.qvvrdata[i];
|
|
|
|
|
|
if (q.QVVR_type == nType && q.QVVR_time == triggerTimeMs) {
|
|
|
|
|
|
std::cout << "[append_qvvr_event][UPDATE match idx=" << i << "]"
|
|
|
|
|
|
<< " old{used=" << q.used_status
|
|
|
|
|
|
<< ", per=" << q.QVVR_PerTime
|
|
|
|
|
|
<< ", mag=" << q.QVVR_Amg
|
|
|
|
|
|
<< ", phase=" << q.phase
|
|
|
|
|
|
<< "} -> new{used=true"
|
|
|
|
|
|
<< ", per=" << fPersisstime_sec
|
|
|
|
|
|
<< ", mag=" << fMagnitude_pu
|
|
|
|
|
|
<< ", phase=" << phase
|
|
|
|
|
|
<< "}" << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
q.used_status = true;
|
|
|
|
|
|
q.QVVR_PerTime = fPersisstime_sec;
|
|
|
|
|
|
q.QVVR_Amg = fMagnitude_pu;
|
|
|
|
|
|
q.phase = phase;
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[append_qvvr_event] done(update)."
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4) 复用空槽(used_status=false)
|
|
|
|
|
|
for (size_t i = 0; i < qe.qvvrdata.size(); ++i) {
|
|
|
|
|
|
auto& q = qe.qvvrdata[i];
|
|
|
|
|
|
if (!q.used_status) {
|
|
|
|
|
|
std::cout << "[append_qvvr_event][REUSE idx=" << i << "]"
|
|
|
|
|
|
<< " set{type=" << nType
|
|
|
|
|
|
<< ", time_ms=" << static_cast<unsigned long long>(triggerTimeMs)
|
|
|
|
|
|
<< ", per=" << fPersisstime_sec
|
|
|
|
|
|
<< ", mag=" << fMagnitude_pu
|
|
|
|
|
|
<< ", phase=" << phase
|
|
|
|
|
|
<< "}" << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
q.used_status = true;
|
|
|
|
|
|
q.QVVR_type = nType;
|
|
|
|
|
|
q.QVVR_time = triggerTimeMs;
|
|
|
|
|
|
q.QVVR_PerTime = fPersisstime_sec;
|
|
|
|
|
|
q.QVVR_Amg = fMagnitude_pu;
|
|
|
|
|
|
q.phase = phase;
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[append_qvvr_event] done(reuse)."
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5) 直接追加
|
|
|
|
|
|
qvvr_data q{};
|
|
|
|
|
|
q.used_status = true;
|
|
|
|
|
|
q.QVVR_type = nType;
|
|
|
|
|
|
q.QVVR_time = triggerTimeMs; // ms
|
|
|
|
|
|
q.QVVR_PerTime = fPersisstime_sec; // s
|
|
|
|
|
|
q.QVVR_Amg = fMagnitude_pu;
|
|
|
|
|
|
q.phase = phase;
|
|
|
|
|
|
qe.qvvrdata.push_back(q);
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[append_qvvr_event][PUSH_BACK]"
|
|
|
|
|
|
<< " new_size=" << qe.qvvrdata.size()
|
|
|
|
|
|
<< " last_idx=" << (qe.qvvrdata.empty() ? -1 : (int)qe.qvvrdata.size()-1)
|
|
|
|
|
|
<< " {type=" << nType
|
|
|
|
|
|
<< ", time_ms=" << static_cast<unsigned long long>(triggerTimeMs)
|
|
|
|
|
|
<< ", per=" << fPersisstime_sec
|
|
|
|
|
|
<< ", mag=" << fMagnitude_pu
|
|
|
|
|
|
<< ", phase=" << phase
|
|
|
|
|
|
<< "}" << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[append_qvvr_event] done(push_back)."
|
|
|
|
|
|
<< std::endl;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
2025-09-24 15:58:50 +08:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////实时数据封装发送
|
|
|
|
|
|
void enqueue_realtime_pq(const RealtagPqDate_float& realdata,
|
|
|
|
|
|
int nPTType,
|
|
|
|
|
|
unsigned char cid,
|
|
|
|
|
|
const std::string& mac,
|
|
|
|
|
|
const std::string& devid)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 先根据 devIdxMap 的配置决定编码分支:
|
|
|
|
|
|
// 2: 基础数据 3: 谐波电压含有率 4: 谐波电流有效值 5: 间谐波电压含有率
|
|
|
|
|
|
int idx = 0;
|
|
|
|
|
|
std::string base64;
|
|
|
|
|
|
|
|
|
|
|
|
// 这里尝试用 mac 作为 key 获取 idx;如果项目里 devIdxMap 的 key 不是 mac,
|
|
|
|
|
|
// 你可以把这里改成对应的设备 id(devid)。未命中则再尝试用规范化后的 mac。
|
|
|
|
|
|
if (devidx_get(devid, idx)) {
|
|
|
|
|
|
switch (idx) {
|
|
|
|
|
|
case 2: // 基础数据(根据接线方式选择转换方法 数据全集解析)
|
|
|
|
|
|
base64 = realdata.ConvertToBase64(nPTType);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 3: // 谐波电压含有率
|
|
|
|
|
|
base64 = realdata.ConvertToBase64_RtHarmV(nPTType);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 4: // 谐波电流有效值(幅值)
|
|
|
|
|
|
base64 = realdata.ConvertToBase64_RtHarmI();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 5: // 间谐波电压含有率
|
|
|
|
|
|
base64 = realdata.ConvertToBase64_RtInHarmV();
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
// 未知 idx,回退到基础数据
|
|
|
|
|
|
base64 = realdata.ConvertToBase64(nPTType);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 未配置 idx,回退到基础数据
|
|
|
|
|
|
base64 = realdata.ConvertToBase64(nPTType);
|
|
|
|
|
|
}
|
|
|
|
|
|
//std::cout << base64 << std::endl;
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
2025-09-24 15:58:50 +08:00
|
|
|
|
//lnk实时数据使用接口发送20250711
|
|
|
|
|
|
time_t data_time = ConvertToTimestamp(realdata.time);
|
2025-09-30 16:17:04 +08:00
|
|
|
|
{
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(g_last_ts_mtx);
|
|
|
|
|
|
auto it = g_last_ts_by_devid.find(devid);
|
|
|
|
|
|
if (it != g_last_ts_by_devid.end() && it->second == data_time) {
|
2025-10-11 15:57:22 +08:00
|
|
|
|
|
|
|
|
|
|
std::cout << "[enqueue_realtime_pq] duplicate timestamp, devid="
|
|
|
|
|
|
<< devid << " time=" << data_time << std::endl;
|
2025-09-30 16:17:04 +08:00
|
|
|
|
// 同一设备与上次时间相同 → 丢弃本次
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 记录本次时间
|
|
|
|
|
|
g_last_ts_by_devid[devid] = data_time;
|
2025-10-11 15:57:22 +08:00
|
|
|
|
|
|
|
|
|
|
std::cout << "[enqueue_realtime_pq] record timestamp, devid="
|
|
|
|
|
|
<< devid << " time=" << data_time << std::endl;
|
2025-09-30 16:17:04 +08:00
|
|
|
|
}
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
2025-09-24 15:58:50 +08:00
|
|
|
|
std::vector<DataArrayItem> arr;
|
|
|
|
|
|
arr.push_back({1, //数据属性 -1-无, 0-“Rt”,1-“Max”,2-“Min”,3-“Avg”,4-“Cp95”
|
|
|
|
|
|
data_time, //数据转换出来的时间,数据时标,相对1970年的秒,无效填入“-1”
|
|
|
|
|
|
0, //数据时标,微秒钟,无效填入“-1”
|
|
|
|
|
|
0, //数据标识,1-标识数据异常
|
|
|
|
|
|
base64});
|
|
|
|
|
|
std::string js = generate_json(
|
|
|
|
|
|
normalize_mac(mac),
|
|
|
|
|
|
-1, //需应答的报文订阅者收到后需以此ID应答,无需应答填入“-1”
|
|
|
|
|
|
1, //设备唯一标识Ldid,填入0代表Ndid,后续根据商议决定填id还是数字
|
|
|
|
|
|
1, //报文处理的优先级:1 I类紧急请求/响应 2 II类紧急请求/响应 3 普通请求/响应 4 广播报文
|
|
|
|
|
|
0x1302, //设备数据主动上送的数据类型
|
|
|
|
|
|
cid, //逻辑子设备ID,0-逻辑设备本身,无填-1
|
|
|
|
|
|
0x04, //数据类型固定为电能质量数据
|
|
|
|
|
|
1, //数据属性:无“0”、实时“1”、统计“2”等
|
|
|
|
|
|
idx, //数据集序号(以数据集方式上送),无填-1
|
|
|
|
|
|
arr //数据数组
|
|
|
|
|
|
);
|
|
|
|
|
|
//std::cout << js << std::en
|
|
|
|
|
|
queue_data_t data;
|
|
|
|
|
|
data.monitor_no = 1; //上送的实时数据没有测点序号,统一填1
|
|
|
|
|
|
data.strTopic = TOPIC_RTDATA; //实时topic
|
|
|
|
|
|
data.strText = js;
|
|
|
|
|
|
data.mp_id = ""; //监测点id,暂时不需要
|
|
|
|
|
|
data.tag = G_RT_TAG; //实时tag
|
|
|
|
|
|
data.key = G_RT_KEY; //实时key
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(queue_data_list_mutex);
|
|
|
|
|
|
queue_data_list.push_back(data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////统计数据打包发送
|
|
|
|
|
|
|
|
|
|
|
|
// 封装:组装统计数据并入队发送
|
|
|
|
|
|
void enqueue_stat_pq(const std::string& max_base64Str,
|
|
|
|
|
|
const std::string& min_base64Str,
|
|
|
|
|
|
const std::string& avg_base64Str,
|
|
|
|
|
|
const std::string& cp95_base64Str,
|
|
|
|
|
|
time_t data_time,
|
|
|
|
|
|
const std::string& mac,
|
|
|
|
|
|
short cid)
|
|
|
|
|
|
{
|
|
|
|
|
|
std::vector<DataArrayItem> arr;
|
|
|
|
|
|
arr.push_back({1, //数据属性 -1-无, 0-“Rt”,1-“Max”,2-“Min”,3-“Avg”,4-“Cp95”
|
|
|
|
|
|
data_time, //数据转换出来的时间,数据时标,相对1970年的秒,无效填入“-1”
|
|
|
|
|
|
0, //数据时标,微秒钟,无效填入“-1”
|
|
|
|
|
|
0, //数据标识,1-标识数据异常
|
|
|
|
|
|
max_base64Str});
|
|
|
|
|
|
arr.push_back({2, data_time, 0, 0, min_base64Str});
|
|
|
|
|
|
arr.push_back({3, data_time, 0, 0, avg_base64Str});
|
|
|
|
|
|
arr.push_back({4, data_time, 0, 0, cp95_base64Str});
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
2025-09-24 15:58:50 +08:00
|
|
|
|
std::string js = generate_json(
|
|
|
|
|
|
normalize_mac(mac),
|
|
|
|
|
|
-1, //需应答的报文订阅者收到后需以此ID应答,无需应答填入“-1”
|
|
|
|
|
|
1, //设备唯一标识Ldid,填入0代表Ndid,后续根据商议决定填id还是数字
|
|
|
|
|
|
1, //报文处理的优先级:1 I类紧急请求/响应 2 II类紧急请求/响应 3 普通请求/响应 4 广播报文
|
|
|
|
|
|
0x1302, //设备数据主动上送的数据类型
|
|
|
|
|
|
cid, //逻辑子设备ID,0-逻辑设备本身,无填-1(原:avg_data.name)
|
|
|
|
|
|
0x04, //数据类型固定为电能质量
|
|
|
|
|
|
2, //数据属性:无“0”、实时“1”、统计“2”等
|
|
|
|
|
|
1, //数据集序号(以数据集方式上送),无填-1
|
|
|
|
|
|
arr //数据数组
|
|
|
|
|
|
);
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
2025-09-24 15:58:50 +08:00
|
|
|
|
//std::cout << js << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
queue_data_t data;
|
|
|
|
|
|
data.monitor_no = cid; //监测点序号(原:avg_data.name)
|
|
|
|
|
|
data.strTopic = TOPIC_STAT;//统计topic
|
|
|
|
|
|
data.strText = js;
|
|
|
|
|
|
data.mp_id = ""; //监测点id,暂时不需要
|
|
|
|
|
|
data.tag = G_ROCKETMQ_TAG; //统计tag
|
|
|
|
|
|
data.key = G_ROCKETMQ_KEY; //统计key
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(queue_data_list_mutex);
|
|
|
|
|
|
queue_data_list.push_back(data);
|
2025-09-12 17:08:25 +08:00
|
|
|
|
|
2025-09-24 15:58:50 +08:00
|
|
|
|
std::cout << "Successfully assembled tagPqData for line: "
|
|
|
|
|
|
<< cid << std::endl;
|
2025-09-25 16:36:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////清空一个装置运行数据
|
|
|
|
|
|
size_t erase_one_terminals_by_id(const std::string& terminal_id) {
|
|
|
|
|
|
|
|
|
|
|
|
// 先对所有匹配项做日志清理
|
|
|
|
|
|
for (const auto& d : terminal_devlist) {
|
|
|
|
|
|
if (d.terminal_id == terminal_id) {
|
|
|
|
|
|
remove_loggers_by_terminal_id(terminal_id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 再统一擦除
|
|
|
|
|
|
const auto old_size = terminal_devlist.size();
|
|
|
|
|
|
terminal_devlist.erase(
|
|
|
|
|
|
std::remove_if(terminal_devlist.begin(), terminal_devlist.end(),
|
|
|
|
|
|
[&](const terminal_dev& d){ return d.terminal_id == terminal_id; }),
|
|
|
|
|
|
terminal_devlist.end());
|
|
|
|
|
|
return old_size - terminal_devlist.size();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
2025-09-30 16:17:04 +08:00
|
|
|
|
DeviceInfo make_device_from_terminal(const terminal_dev& t) {
|
2025-09-25 16:36:04 +08:00
|
|
|
|
DeviceInfo d;
|
|
|
|
|
|
|
|
|
|
|
|
// 基本信息
|
|
|
|
|
|
d.device_id = t.terminal_id;
|
|
|
|
|
|
d.name = t.terminal_name;
|
|
|
|
|
|
d.model = t.dev_type;
|
|
|
|
|
|
d.mac = t.mac;
|
|
|
|
|
|
|
|
|
|
|
|
// status:优先按数字解析,其次按 online/true/yes/on 识别;默认 0
|
|
|
|
|
|
int status = 0;
|
|
|
|
|
|
if (!t.tmnl_status.empty()) {
|
|
|
|
|
|
bool parsed_num = false;
|
|
|
|
|
|
// 尝试解析为整数
|
|
|
|
|
|
char* endp = nullptr;
|
|
|
|
|
|
long v = std::strtol(t.tmnl_status.c_str(), &endp, 10);
|
|
|
|
|
|
if (endp && *endp == '\0') { parsed_num = true; status = (v != 0) ? 1 : 0; }
|
|
|
|
|
|
|
|
|
|
|
|
if (!parsed_num) {
|
|
|
|
|
|
std::string s = t.tmnl_status;
|
|
|
|
|
|
for (char& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
|
|
|
|
status = (s == "online" || s == "true" || s == "yes" || s == "on" || s == "1") ? 1 : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
d.status = status;
|
|
|
|
|
|
|
|
|
|
|
|
// righttime:支持 "1/true/yes/on";默认 false
|
|
|
|
|
|
bool rt = false;
|
|
|
|
|
|
if (!t.Righttime.empty()) {
|
|
|
|
|
|
std::string s = t.Righttime;
|
|
|
|
|
|
for (char& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
|
|
|
|
rt = (s == "1" || s == "true" || s == "yes" || s == "on");
|
|
|
|
|
|
}
|
|
|
|
|
|
d.righttime = rt;
|
|
|
|
|
|
|
|
|
|
|
|
// points
|
|
|
|
|
|
d.points.clear();
|
|
|
|
|
|
d.points.reserve(t.line.size());
|
|
|
|
|
|
for (const auto& m : t.line) {
|
|
|
|
|
|
PointInfo p;
|
|
|
|
|
|
p.point_id = m.monitor_id;
|
|
|
|
|
|
p.name = m.monitor_name;
|
|
|
|
|
|
p.device_id = t.terminal_id;
|
|
|
|
|
|
|
|
|
|
|
|
// nCpuNo:从 logical_device_seq 转 ushort,非法则置 0
|
|
|
|
|
|
unsigned short cpu_no = 0;
|
|
|
|
|
|
if (!m.logical_device_seq.empty()) {
|
|
|
|
|
|
char* endp = nullptr;
|
|
|
|
|
|
long v = std::strtol(m.logical_device_seq.c_str(), &endp, 10);
|
|
|
|
|
|
if (endp && *endp == '\0' && v >= 0) {
|
|
|
|
|
|
if (v > 0xFFFF) v = 0xFFFF;
|
|
|
|
|
|
cpu_no = static_cast<unsigned short>(v);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
p.nCpuNo = cpu_no;
|
|
|
|
|
|
|
|
|
|
|
|
// 变比与电压等级
|
|
|
|
|
|
p.PT1 = m.PT1;
|
|
|
|
|
|
p.PT2 = m.PT2;
|
|
|
|
|
|
p.CT1 = m.CT1;
|
|
|
|
|
|
p.CT2 = m.CT2;
|
|
|
|
|
|
p.strScale = m.voltage_level;
|
|
|
|
|
|
|
|
|
|
|
|
// 接线方式:0-星型 1-角型;支持 "0/1"、包含“角/三角/delta/Δ”
|
|
|
|
|
|
int pttype = 0; // 默认星型
|
|
|
|
|
|
if (!m.terminal_connect.empty()) {
|
|
|
|
|
|
std::string s = m.terminal_connect;
|
|
|
|
|
|
for (char& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
|
|
|
|
if (s == "1" || s.find("角") != std::string::npos || s.find("三角") != std::string::npos
|
|
|
|
|
|
|| s.find("delta") != std::string::npos || s.find("△") != std::string::npos || s.find("Δ") != std::string::npos) {
|
|
|
|
|
|
pttype = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
p.nPTType = pttype;
|
|
|
|
|
|
|
|
|
|
|
|
d.points.push_back(std::move(p));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return d;
|
2025-09-24 15:58:50 +08:00
|
|
|
|
}
|