添加pqdif处理函数

This commit is contained in:
lnk
2026-07-17 15:58:19 +08:00
parent 0e2ade351c
commit abc5f9e735

View File

@@ -44,7 +44,13 @@ SD_CONST static ST_CHAR *SD_CONST thisFileName = __FILE__;
#ifdef _OS_UNIX_ #ifdef _OS_UNIX_
#include <sys/vfs.h> #include <sys/vfs.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif #endif
#include <ctype.h>
#include <errno.h>
#include <time.h>
extern uint32_t g_dead_lock_counter; extern uint32_t g_dead_lock_counter;
extern uint32_t g_thread_blocked_times; extern uint32_t g_thread_blocked_times;
@@ -95,6 +101,8 @@ static void format_time_ms(long long ms, char* buf, size_t buf_len){
tm_time.tm_sec, tm_time.tm_sec,
milli); milli);
} }
static apr_status_t ChannelCheckPQDIFFiles(chnl_usr_t *chnl_usr);
//lnk20250122start //lnk20250122start
apr_status_t init_rem_dib_table() apr_status_t init_rem_dib_table()
{ {
@@ -1636,7 +1644,7 @@ void CheckAllConnectedChannel()
HandleFileDirReqForChannel(chnl_usr);//文件目录请求 HandleFileDirReqForChannel(chnl_usr);//文件目录请求
} }
if(g_node_id == PQDIF_DATA_BASE_NODE_ID){ if(g_node_id == PQDIF_DATA_BASE_NODE_ID){
//补招PQDIF文件 ChannelCheckPQDIFFiles(chnl_usr);
} }
else{ else{
ChannelCheckIECReports(chnl_usr);//报告 ChannelCheckIECReports(chnl_usr);//报告
@@ -1989,6 +1997,208 @@ static ST_RET Write_Named_Var(LD_info_t *LD_info,chnl_usr_t *chnl_usr,char* VarN
#define TICKS_PER_MIN 60LL*1000LL*1000LL #define TICKS_PER_MIN 60LL*1000LL*1000LL
/* PQDIF 文件召唤参数。
* C# 读取程序的默认周期是 2 小时,文件名形如:
* PQMonitor_PQM1_20190401_1400_02.pqd
* 其中 “1_20190401_1400_” 用于匹配测点序号和时间片。
* 当前进程没有单独的手动时间窗输入,所以这里实现自动模式:
* 1. 每个终端按固定扫描间隔读取一次 /PQDIF/ 目录;
* 2. 只下载最近一个完整周期内、且测点序号匹配的 .pqd 文件;
* 3. 下载前检查 /FeProject/data/pqdif/ 下是否已有同名文件,避免重复下载。
*/
#define PQDIF_REMOTE_DIR "/PQDIF/"
#define PQDIF_LOCAL_DIR "/FeProject/data/pqdif"
#define PQDIF_AUTO_INTERVAL_HOURS 2
#define PQDIF_SCAN_INTERVAL_MS (60 * 1000)
#define PQDIF_MAX_DEVICE_INDEX 4096
#define PQDIF_MAX_FETCH_PER_SCAN 32
static const char* pqdif_basename(const char* filename)
{
const char* base = filename;
const char* p;
if (filename == NULL)
return "";
for (p = filename; *p != '\0'; ++p) {
if (*p == '/' || *p == '\\')
base = p + 1;
}
return base;
}
static int pqdif_has_pqd_suffix(const char* filename)
{
size_t len;
if (filename == NULL)
return FALSE;
len = strlen(filename);
if (len < 4)
return FALSE;
return (filename[len - 4] == '.'
&& tolower((unsigned char)filename[len - 3]) == 'p'
&& tolower((unsigned char)filename[len - 2]) == 'q'
&& tolower((unsigned char)filename[len - 1]) == 'd');
}
static void pqdif_make_time_match(int cpu_no, time_t tm_value, char* out, size_t out_len)
{
struct tm tm_local;
memset(&tm_local, 0, sizeof(tm_local));
localtime_r(&tm_value, &tm_local);
/* C# 目录匹配逻辑使用 “测点号_yyyyMMdd_HH00_” 作为关键片段。
* 例如测点号 1、2026-07-17 10 点,对应 “1_20260717_1000_”。
*/
snprintf(out, out_len, "%d_%04d%02d%02d_%02d00_",
cpu_no % 10,
tm_local.tm_year + 1900,
tm_local.tm_mon + 1,
tm_local.tm_mday,
tm_local.tm_hour);
}
static int pqdif_match_recent_interval(const char* filename, int cpu_no)
{
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];
int hour_mod;
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 振兴设备按周期结束时间命名。
* 这里同时匹配开始和结束时间,避免需要额外区分厂家。
*/
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;
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));
return (strstr(filename, start_match) != NULL || strstr(filename, end_match) != NULL);
}
static int pqdif_ensure_local_dir()
{
#ifdef _OS_UNIX_
if (mkdir("/FeProject/data", 0777) != 0 && errno != EEXIST)
return SD_FAILURE;
if (mkdir(PQDIF_LOCAL_DIR, 0777) != 0 && errno != EEXIST)
return SD_FAILURE;
#endif
return SD_SUCCESS;
}
static int pqdif_local_file_exists(const char* filename)
{
char local_file[512];
const char* base = pqdif_basename(filename);
if (base[0] == '\0')
return FALSE;
snprintf(local_file, sizeof(local_file), "%s/%s", PQDIF_LOCAL_DIR, base);
#ifdef _OS_UNIX_
return access(local_file, F_OK) == 0;
#else
return FALSE;
#endif
}
static void pqdif_build_remote_filename(const char* dir_filename, char* out, size_t out_len)
{
if (dir_filename == NULL || dir_filename[0] == '\0') {
out[0] = '\0';
return;
}
/* mms_mvla_fdir 的返回值有两种可能:
* 1. 带目录:/PQDIF/xxx.pqd
* 2. 仅文件名xxx.pqd
* PQDIF 必须固定从 /PQDIF/ 取,所以仅返回文件名时要补齐远端目录。
*/
if (strchr(dir_filename, '/') != NULL || strchr(dir_filename, '\\') != NULL)
snprintf(out, out_len, "%s", dir_filename);
else
snprintf(out, out_len, "%s%s", PQDIF_REMOTE_DIR, dir_filename);
}
static int pqdif_get_file_from_device(chnl_usr_t *chnl_usr, const char *rem_filename, char *only_filename_ret, size_t only_filename_ret_len)
{
int ret;
char loc_file_fullname[512];
char rem_file_fullname[512];
const char *only_filename;
if (chnl_usr == NULL || chnl_usr->net_info == NULL || rem_filename == NULL || rem_filename[0] == '\0')
return SD_FAILURE;
only_filename = pqdif_basename(rem_filename);
if (only_filename[0] == '\0')
return SD_FAILURE;
if (pqdif_ensure_local_dir() != SD_SUCCESS) {
echo_warn1("[PQDIF] create local dir failed: %s\n", PQDIF_LOCAL_DIR);
return SD_FAILURE;
}
memset(loc_file_fullname,0,sizeof(loc_file_fullname));
memset(rem_file_fullname,0,sizeof(rem_file_fullname));
apr_snprintf(loc_file_fullname,sizeof(loc_file_fullname),"%s/%s",PQDIF_LOCAL_DIR,only_filename);
apr_snprintf(rem_file_fullname,sizeof(rem_file_fullname),"%s",rem_filename);
if (only_filename_ret != NULL && only_filename_ret_len > 0)
apr_snprintf(only_filename_ret,only_filename_ret_len,"%s",only_filename);
printf("[PQDIF] mms_getFile local=%s remote=%s\n", loc_file_fullname, rem_file_fullname);
ret = mms_getFile(chnl_usr->net_info,loc_file_fullname,rem_file_fullname,g_pt61850app->mmsOpTimeout);
if (ret != SD_SUCCESS) {
echo_warn3("[PQDIF] mms_getFile failed IP=%s local=%s remote=%s\n",
chnl_usr->ip_str, loc_file_fullname, rem_file_fullname);
return ret;
}
#ifdef _OS_UNIX_
{
struct stat st;
if (stat(loc_file_fullname, &st) != 0 || st.st_size <= 0) {
unlink(loc_file_fullname);
echo_warn2("[PQDIF] file is empty or invalid: %s from %s\n",
loc_file_fullname, rem_file_fullname);
return SD_FAILURE;
}
}
#endif
return SD_SUCCESS;
}
int pt61850_write_cn_file(chnl_usr_t *chnl_usr, ied_t *ied, char *rem_filename, char *only_filename_ret) int pt61850_write_cn_file(chnl_usr_t *chnl_usr, ied_t *ied, char *rem_filename, char *only_filename_ret)
{ {
@@ -2034,6 +2244,140 @@ int pt61850_write_cn_file(chnl_usr_t *chnl_usr, ied_t *ied, char *rem_filename,
return ret; return ret;
} }
static apr_status_t ChannelCheckPQDIFFiles(chnl_usr_t *chnl_usr)
{
ied_t *ied;
ied_usr_t *ied_usr;
LD_info_t *LD_info;
char **filenames = NULL;
int filenum = 0;
int ret;
int cpuno;
int file_idx;
int fetched = 0;
int matched = 0;
int scan_idx;
double now_ms;
static double s_last_scan_ms[PQDIF_MAX_DEVICE_INDEX] = {0};
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) {
printf("[PQDIF] invalid channel context, skip scan\n");
return APR_EGENERAL;
}
ied = chnl_usr->chnl->ied;
ied_usr = (ied_usr_t*)ied->usr_ext;
/* CheckAllConnectedChannel 会轮询通道;这里按终端做节流,避免每次循环都读一次
* /PQDIF/ 目录。目录读和文件下载都走 MMS同一个终端一分钟扫描一次即可。
*/
scan_idx = ied_usr->dev_idx;
if (scan_idx < 0 || scan_idx >= PQDIF_MAX_DEVICE_INDEX)
scan_idx = (int)chnl_usr->chnl_id;
if (scan_idx < 0 || scan_idx >= PQDIF_MAX_DEVICE_INDEX)
scan_idx = 0;
now_ms = sGetMsTime();
if (s_last_scan_ms[scan_idx] > 0 &&
(now_ms - s_last_scan_ms[scan_idx]) < PQDIF_SCAN_INTERVAL_MS) {
return APR_SUCCESS;
}
s_last_scan_ms[scan_idx] = now_ms;
/* 本函数只做 PQDIF 文件获取:
* 1. 从装置固定目录 /PQDIF/ 取文件列表;
* 2. 用台账里的 LD_info->cpuno 匹配文件名里的 “测点号_yyyyMMdd_HH00_”
* 3. 只下载 .pqd 文件,且本地 /FeProject/data/pqdif/ 已存在同名文件时跳过;
* 4. 真正下载直接调用 mms_getFile不改动原录波文件下载函数。
*/
if (pqdif_ensure_local_dir() != SD_SUCCESS) {
echo_warn1("[PQDIF] create local dir failed: %s\n", PQDIF_LOCAL_DIR);
return APR_EGENERAL;
}
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] 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] no file in remote dir %s, IED=%d IP=%s\n", PQDIF_REMOTE_DIR, ied->id, chnl_usr->ip_str);
return APR_SUCCESS;
}
printf("[PQDIF] scan remote dir %s success, IED=%d IP=%s file_count=%d\n",
PQDIF_REMOTE_DIR, ied->id, chnl_usr->ip_str, filenum);
for (cpuno = 0; cpuno < ied->cpucount; cpuno++) {
LD_info = &(ied_usr->LD_info[cpuno]);
if (LD_info->cpuno == 0 ||
LD_info->LD_name == NULL ||
LD_info->mp_id[0] == '\0') {
continue;
}
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;
/* C# 读取程序的兼容逻辑是读目录后按片段匹配:
* cpuNo%10_yyyyMMdd_HH00_
* 这里保持同样的匹配方式,不依赖厂家前缀,也兼容普通设备用
* 周期开始时间命名、振兴类设备用周期结束时间命名的差异。
*/
if (!pqdif_match_recent_interval(base_name, LD_info->cpuno))
continue;
matched++;
if (pqdif_local_file_exists(base_name)) {
printf("[PQDIF] local file exists, skip mp_id=%s file=%s\n", LD_info->mp_id, base_name);
continue;
}
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')
continue;
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] fetch success mp_id=%s name=%s remote=%s local=%s/%s\n",
LD_info->mp_id, LD_info->name, remote_filename, PQDIF_LOCAL_DIR, only_filename_ret);
}
else {
echo_warn3("[PQDIF] fetch failed mp_id=%s remote=%s IP=%s\n",
LD_info->mp_id, remote_filename, chnl_usr->ip_str);
}
if (fetched >= PQDIF_MAX_FETCH_PER_SCAN) {
printf("[PQDIF] reach fetch limit per scan=%d, IED=%d IP=%s\n",
PQDIF_MAX_FETCH_PER_SCAN, ied->id, chnl_usr->ip_str);
return APR_SUCCESS;
}
}
}
printf("[PQDIF] scan finish IED=%d IP=%s matched=%d fetched=%d\n",
ied->id, chnl_usr->ip_str, matched, fetched);
return APR_SUCCESS;
}
apr_status_t prepare_call_cn_wavelist(LD_info_t *LD_info, int FltNum) apr_status_t prepare_call_cn_wavelist(LD_info_t *LD_info, int FltNum)
{ {
int i; int i;