diff --git a/mms/mms_process.c b/mms/mms_process.c index f98785a..d53d1be 100644 --- a/mms/mms_process.c +++ b/mms/mms_process.c @@ -103,6 +103,7 @@ static void format_time_ms(long long ms, char* buf, size_t buf_len){ } static apr_status_t ChannelCheckPQDIFFiles(chnl_usr_t *chnl_usr); +static apr_status_t ChannelRecallPQDIFFiles(chnl_usr_t *chnl_usr, LD_info_t *LD_info, long long start_sec, long long end_sec); //lnk20250122start apr_status_t init_rem_dib_table() { @@ -580,21 +581,28 @@ void ChannelCheckIECLogs(chnl_usr_t *chnl_usr) LD_info_t *LD_info; loginfo_t *loginfo = NULL; int cpuno; + int is_pqdif_process; double now; static double last_check_recall_config_time = 0.0; ied = chnl_usr->chnl->ied; ied_usr = GET_IEDEXT_ADDR(ied); + is_pqdif_process = (g_node_id == PQDIF_DATA_BASE_NODE_ID); for(cpuno=0 ; cpunocpucount; cpuno++) { LD_info = &(ied_usr->LD_info[cpuno]); //添加保护,防止台账中测点号不连续导致的崩溃 if (LD_info->cpuno == 0 || - LD_info->LD_name == NULL || - LD_info->logcount <= 0 || - LD_info->loginfo == NULL) + LD_info->LD_name == NULL) + { + continue; + } + + if (!is_pqdif_process && + (LD_info->logcount <= 0 || + LD_info->loginfo == NULL)) { continue; } @@ -620,11 +628,13 @@ void ChannelCheckIECLogs(chnl_usr_t *chnl_usr) continue; }*/ - loginfo = LD_info->loginfo[0] ; + if (!is_pqdif_process) { + loginfo = LD_info->loginfo[0] ; //添加保护,防止台账中测点号不连续导致的崩溃 if (loginfo == NULL || loginfo->LD_info == NULL) continue; + } apr_sleep(apr_time_from_sec(1) / 10); @@ -636,14 +646,17 @@ void ChannelCheckIECLogs(chnl_usr_t *chnl_usr) int failed_count = 0; /////////////////////////////////////////////////////根据配置文件控制下发补招时间为北京时间还是utc时间 + long long utc_or_beijing = 0; + if (!is_pqdif_process) { printf("~~~~~~~this dev type is %s~~~~~~~",ied_usr->dev_type); XmlConfigC cfg1; + memset(&cfg1,0,sizeof(cfg1)); if (get_xml_config_by_dev_type(ied_usr->dev_type, &cfg1)) { printf("ValueOfTimeUnit = %s\n", cfg1.ValueOfTimeUnit); } else { printf("读取失败,未找到 dev_type\n"); } - long long utc_or_beijing; + utc_or_beijing = 0; if(strcmp(cfg1.ValueOfTimeUnit, "utc") == 0){//装置时间是utc还是北京 utc_or_beijing = 28800;//秒 @@ -654,6 +667,11 @@ void ChannelCheckIECLogs(chnl_usr_t *chnl_usr) DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_RECALL,"【WARN】监测点:%s - id:%s开始补招数据,注意!下发补招时间为beijing时间,监测点对应装置型号:%s", LD_info->name,LD_info->mp_id,ied_usr->dev_type); } + } + else { + DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_RECALL,"[PQDIF][RECALL] monitor:%s - id:%s start recall PQDIF files, dev_type:%s", LD_info->name,LD_info->mp_id,ied_usr->dev_type); + } + ////////////////////////////////////////////////////////////// //记录本次补招的时间范围 long long min_start_sec = LLONG_MAX; @@ -674,6 +692,9 @@ void ChannelCheckIECLogs(chnl_usr_t *chnl_usr) LD_info->autorecallflag = 1;//正在补招 //当前不区分稳态和暂态lnk20241030,如果做区分修改:Check_Recall_Config从xml文件获取数据后,赋值给了LD_info + if (is_pqdif_process) + continue; + loginfo->need_steady = LD_info->autorecall[i]->need_steady; loginfo->need_voltage = LD_info->autorecall[i]->need_voltage; loginfo->start_time = apr_time_from_sec(LD_info->autorecall[i]->start - 5);//保证时间开头 @@ -726,6 +747,24 @@ void ChannelCheckIECLogs(chnl_usr_t *chnl_usr) //不管是否成功,这个补招文件必须删除,可能出现一直失败,循环读取文件和循环补招导致程序崩溃202050724lnk //if (failed_count==0) {//成功 + if (is_pqdif_process) { + if (min_start_sec == LLONG_MAX || max_end_sec <= min_start_sec) { + failed_count++; + printf("[PQDIF][RECALL] invalid recall range mp_id=%s min=%lld max=%lld\n", + LD_info->mp_id, min_start_sec, max_end_sec); + } + else { + ret = ChannelRecallPQDIFFiles(chnl_usr, LD_info, min_start_sec, max_end_sec); + if (ret != APR_SUCCESS) + failed_count++; + } + + g_dead_lock_counter = 0; + g_thread_blocked_times = 0; + now = sGetMsTime(); + last_check_recall_config_time = now; + } + Delete_recall_Xml(LD_info->mp_id); // ===== [新增] 组装上送的补招开始/结束时间字符串(本地时区,直接“秒转字符串”)===== @@ -1652,7 +1691,8 @@ void CheckAllConnectedChannel() if ( (g_node_id == SOE_COMTRADE_BASE_NODE_ID) || (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)) ChannelCheckWaveFiles(chnl_usr);//录波文件 - 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)) + 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) + || g_node_id == PQDIF_DATA_BASE_NODE_ID) ChannelCheckIECLogs(chnl_usr);//补招文件 if ( (sGetMsTime() - chnl_usr->m_LastPosRespTime) > 15*1000 ) //wait 15 secs,隔15秒获取一次响应,两次没响应后关闭 @@ -2063,42 +2103,60 @@ static void pqdif_make_time_match(int cpu_no, time_t tm_value, char* out, size_t tm_local.tm_hour); } -static int pqdif_match_recent_interval(const char* filename, int cpu_no) +static int pqdif_use_interval_end_time(const char* dev_type) +{ + return (dev_type != NULL && strcmp(dev_type, "PS_NET_PQDIF") == 0); +} + +static time_t pqdif_align_interval_start(long long sec) { - time_t now_time; time_t hour_floor; - time_t interval_end; - time_t interval_start; - struct tm tm_now; - char start_match[64]; - char end_match[64]; + struct tm tm_local; int hour_mod; + memset(&tm_local, 0, sizeof(tm_local)); + hour_floor = (time_t)sec; + localtime_r(&hour_floor, &tm_local); + tm_local.tm_min = 0; + tm_local.tm_sec = 0; + hour_floor = mktime(&tm_local); + + hour_mod = tm_local.tm_hour % PQDIF_AUTO_INTERVAL_HOURS; + return hour_floor - hour_mod * 3600; +} + +static int pqdif_match_interval(const char* filename, int cpu_no, time_t interval_start, const char* dev_type) +{ + time_t name_time; + char time_match[64]; + if (!pqdif_has_pqd_suffix(filename)) return FALSE; if (cpu_no <= 0) return FALSE; - now_time = time(NULL); - localtime_r(&now_time, &tm_now); - tm_now.tm_min = 0; - tm_now.tm_sec = 0; - hour_floor = mktime(&tm_now); - - /* 最近一个完整周期: - * 例如当前 11:xx、周期 2 小时,则完整周期是 08:00~10:00。 - * 普通设备按周期开始时间命名,PS_NET_PQDIF 振兴设备按周期结束时间命名。 - * 这里同时匹配开始和结束时间,避免需要额外区分厂家。 + /* C# 原逻辑按设备类型决定文件名时间: + * - 普通设备用周期开始时间; + * - PS_NET_PQDIF 用周期结束时间。 + * 这里保持同样规则,避免把普通设备下一周期的开始文件误认为本周期结束文件。 */ - hour_mod = tm_now.tm_hour % PQDIF_AUTO_INTERVAL_HOURS; - interval_end = hour_floor - hour_mod * 3600; - interval_start = interval_end - PQDIF_AUTO_INTERVAL_HOURS * 3600; + name_time = interval_start; + if (pqdif_use_interval_end_time(dev_type)) + name_time += PQDIF_AUTO_INTERVAL_HOURS * 3600; - pqdif_make_time_match(cpu_no, interval_start, start_match, sizeof(start_match)); - pqdif_make_time_match(cpu_no, interval_end, end_match, sizeof(end_match)); + pqdif_make_time_match(cpu_no, name_time, time_match, sizeof(time_match)); + return strstr(filename, time_match) != NULL; +} - return (strstr(filename, start_match) != NULL || strstr(filename, end_match) != NULL); +static int pqdif_match_recent_interval(const char* filename, int cpu_no, const char* dev_type) +{ + time_t now_time; + time_t interval_start; + + now_time = time(NULL); + interval_start = pqdif_align_interval_start((long long)now_time) - PQDIF_AUTO_INTERVAL_HOURS * 3600; + return pqdif_match_interval(filename, cpu_no, interval_start, dev_type); } static int pqdif_ensure_local_dir() @@ -2198,6 +2256,141 @@ static int pqdif_get_file_from_device(chnl_usr_t *chnl_usr, const char *rem_file return SD_SUCCESS; } + +static apr_status_t ChannelRecallPQDIFFiles(chnl_usr_t *chnl_usr, LD_info_t *LD_info, long long start_sec, long long end_sec) +{ + ied_t *ied; + ied_usr_t *ied_usr; + char **filenames = NULL; + int filenum = 0; + int ret; + int file_idx; + int fetched = 0; + int existed = 0; + int missing = 0; + int failed = 0; + time_t interval_start; + time_t interval_step; + + if (chnl_usr == NULL || chnl_usr->chnl == NULL || chnl_usr->chnl->ied == NULL || + chnl_usr->chnl->ied->usr_ext == NULL || chnl_usr->net_info == NULL || + LD_info == NULL) { + printf("[PQDIF][RECALL] invalid context, skip\n"); + return APR_EGENERAL; + } + + if (LD_info->cpuno == 0 || LD_info->LD_name == NULL || LD_info->mp_id[0] == '\0') { + printf("[PQDIF][RECALL] invalid LD_info, skip\n"); + return APR_EGENERAL; + } + + if (end_sec <= start_sec) { + printf("[PQDIF][RECALL] invalid time range mp_id=%s start=%lld end=%lld\n", + LD_info->mp_id, start_sec, end_sec); + return APR_EGENERAL; + } + + ied = chnl_usr->chnl->ied; + ied_usr = (ied_usr_t*)ied->usr_ext; + + if (pqdif_ensure_local_dir() != SD_SUCCESS) { + echo_warn1("[PQDIF][RECALL] create local dir failed: %s\n", PQDIF_LOCAL_DIR); + return APR_EGENERAL; + } + + /* 单点补招与 C# 手动/补招逻辑保持一致: + * 1. 先读取装置 /PQDIF/ 目录; + * 2. 按 PQDIF_AUTO_INTERVAL_HOURS 将补招时间范围切成多个文件周期; + * 3. 普通设备用周期开始时间匹配文件名,PS_NET_PQDIF 用周期结束时间匹配文件名; + * 4. 每个周期只取一份匹配文件,下载接口直接调用 mms_getFile。 + */ + ret = mms_mvla_fdir(chnl_usr->net_info,(ST_CHAR*)PQDIF_REMOTE_DIR, + 3*g_pt61850app->mmsOpTimeout,&filenames,&filenum,g_pt61850app->tmp_pool); + if (ret != SD_SUCCESS) { + echo_warn2("[PQDIF][RECALL] mms_mvla_fdir failed, IED=%d IP=%s\n", ied->id, chnl_usr->ip_str); + return APR_EGENERAL; + } + + if (filenames == NULL || filenum <= 0) { + printf("[PQDIF][RECALL] no file in remote dir %s, mp_id=%s IP=%s\n", + PQDIF_REMOTE_DIR, LD_info->mp_id, chnl_usr->ip_str); + return APR_EGENERAL; + } + + interval_step = PQDIF_AUTO_INTERVAL_HOURS * 3600; + interval_start = pqdif_align_interval_start(start_sec); + + printf("[PQDIF][RECALL] begin mp_id=%s cpuno=%d range=%lld~%lld interval_start=%lld files=%d dev_type=%s\n", + LD_info->mp_id, LD_info->cpuno, start_sec, end_sec, (long long)interval_start, + filenum, ied_usr->dev_type); + + while ((long long)interval_start < end_sec) { + int found_this_interval = FALSE; + + for (file_idx = 0; file_idx < filenum; file_idx++) { + const char *base_name; + char remote_filename[256]; + char only_filename_ret[256]; + int ret_get; + + if (filenames[file_idx] == NULL) + continue; + + base_name = pqdif_basename(filenames[file_idx]); + if (base_name[0] == '\0') + continue; + + if (!pqdif_match_interval(base_name, LD_info->cpuno, interval_start, ied_usr->dev_type)) + continue; + + found_this_interval = TRUE; + if (pqdif_local_file_exists(base_name)) { + existed++; + printf("[PQDIF][RECALL] local file exists, skip mp_id=%s interval=%lld file=%s\n", + LD_info->mp_id, (long long)interval_start, base_name); + break; + } + + memset(remote_filename,0,sizeof(remote_filename)); + memset(only_filename_ret,0,sizeof(only_filename_ret)); + pqdif_build_remote_filename(filenames[file_idx], remote_filename, sizeof(remote_filename)); + if (remote_filename[0] == '\0') { + failed++; + break; + } + + ret_get = pqdif_get_file_from_device(chnl_usr, remote_filename, only_filename_ret, sizeof(only_filename_ret)); + if (ret_get == SD_SUCCESS) { + fetched++; + chnl_usr->m_LastPosRespTime = sGetMsTime(); + printf("[PQDIF][RECALL] fetch success mp_id=%s interval=%lld remote=%s local=%s/%s\n", + LD_info->mp_id, (long long)interval_start, remote_filename, PQDIF_LOCAL_DIR, only_filename_ret); + } + else { + failed++; + echo_warn3("[PQDIF][RECALL] fetch failed mp_id=%s remote=%s IP=%s\n", + LD_info->mp_id, remote_filename, chnl_usr->ip_str); + } + break; + } + + if (!found_this_interval) { + missing++; + printf("[PQDIF][RECALL] no matched file mp_id=%s interval=%lld dev_type=%s\n", + LD_info->mp_id, (long long)interval_start, ied_usr->dev_type); + } + + interval_start += interval_step; + } + + printf("[PQDIF][RECALL] finish mp_id=%s fetched=%d existed=%d missing=%d failed=%d\n", + LD_info->mp_id, fetched, existed, missing, failed); + + if (missing > 0 || failed > 0) + return APR_EGENERAL; + + return APR_SUCCESS; +} int pt61850_write_cn_file(chnl_usr_t *chnl_usr, ied_t *ied, char *rem_filename, char *only_filename_ret) @@ -2338,7 +2531,7 @@ static apr_status_t ChannelCheckPQDIFFiles(chnl_usr_t *chnl_usr) * 这里保持同样的匹配方式,不依赖厂家前缀,也兼容普通设备用 * 周期开始时间命名、振兴类设备用周期结束时间命名的差异。 */ - if (!pqdif_match_recent_interval(base_name, LD_info->cpuno)) + if (!pqdif_match_recent_interval(base_name, LD_info->cpuno, ied_usr->dev_type)) continue; matched++;