添加pqdif处理函数
This commit is contained in:
@@ -44,7 +44,13 @@ SD_CONST static ST_CHAR *SD_CONST thisFileName = __FILE__;
|
||||
|
||||
#ifdef _OS_UNIX_
|
||||
#include <sys/vfs.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
extern uint32_t g_dead_lock_counter;
|
||||
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,
|
||||
milli);
|
||||
}
|
||||
|
||||
static apr_status_t ChannelCheckPQDIFFiles(chnl_usr_t *chnl_usr);
|
||||
//lnk20250122start
|
||||
apr_status_t init_rem_dib_table()
|
||||
{
|
||||
@@ -1636,7 +1644,7 @@ void CheckAllConnectedChannel()
|
||||
HandleFileDirReqForChannel(chnl_usr);//文件目录请求
|
||||
}
|
||||
if(g_node_id == PQDIF_DATA_BASE_NODE_ID){
|
||||
//补招PQDIF文件
|
||||
ChannelCheckPQDIFFiles(chnl_usr);
|
||||
}
|
||||
else{
|
||||
ChannelCheckIECReports(chnl_usr);//报告
|
||||
@@ -1988,6 +1996,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
|
||||
|
||||
/* 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)
|
||||
@@ -2033,6 +2243,140 @@ int pt61850_write_cn_file(chnl_usr_t *chnl_usr, ied_t *ied, char *rem_filename,
|
||||
}
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user