/**
* @file:    $RCSfile: mms_process.c,v $
* @brief:   $PROFIBUS 与SSRTDB交互
*
* @version: $Revision: 1.28 $
* @date:    $Date: 2022/11/28 07:13:13 $
* @author:  $Author: lizhongming $
* @state:   $State: Exp $
*
* @latest:  $Id: mms_process.c,v 1.28 2022/11/28 07:13:13 lizhongming Exp $
*/
#include <string.h>
#include "rdb_client.h"
#include <stdlib.h>
#include "db_interface.h"
#include "node.h"
#include "ied.h"
#include "../json/mms_json_inter.h" 
#include "../cfg_parse/custom_printf.h"//lnk20250225

#include "../log4cplus/log4.h"//lnk添加log4

void clear_rpt_counter_by_trigger(trigger_t *trigger);

//lnk20241031
extern void SOEFileWeb(char* localpath,char* cloudpath,char* wavepath);
//lnk 2024-11-4 添加时间转换函数
char* convertMsToDateTimeString(int64_t msTime);
//lnk20250115
extern pthread_mutex_t mtx;
extern apr_pool_t* g_cfg_pool;
extern apr_pool_t* g_init_pool;

extern int	g_DevFlag;	//日志配置中读取的参数，暂无特定使用lnk20250121
extern bool DEBUGOPEN;//调试开关，控制是否输出调试日志，默认关闭
extern int IED_COUNT;
extern int RECALL_ONLY_FLAG; //lnk20260309添加一个全局变量，标志是否只运行补招程序

//lnk20250115end

#ifdef DEBUG_SISCO
SD_CONST static ST_CHAR *SD_CONST thisFileName = __FILE__;
#endif

#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;

extern uint16_t			g_client_id;

extern RPT_TYPEIDS g_rpt_typeids;
//extern rdb_t			*g_rdb  ;
extern node_t			*g_node ;
extern char 			g_my_conf_fname[256];
extern apr_pool_t		*g_init_pool;
extern apr_pool_t		*g_run_pool;
extern pt61850app_t		*g_pt61850app;
//extern char				*g_sysfile_filedir;
extern char				g_onlyIP[255];			//直连某个IP，仅仅为方便测试
//extern int				g_sysfile_appid;        //add by rzx
//extern apr_time_t		g_file_valid_time;
extern uint32_t			g_min_free_size;
/////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
extern uint32_t			g_node_id; 

extern int		three_secs_enabled;
extern int		auto_register_report_enabled;
extern int FRONT_MP_NUM;
///////////////////////////////////////////////////////////////////////////////
extern int FILE_FLAG;
  
extern int recall_len;
extern int recall_sta;
extern int recall_daily;
extern char* UDS_UPLOAD_URL;

///////////////////////////////////////////////////////////////////
static void format_time_ms(long long ms, char* buf, size_t buf_len){
	    time_t sec = ms / 1000;
    int milli = ms % 1000;

    struct tm tm_time;
    localtime_r(&sec, &tm_time);   // 线程安全

    snprintf(buf, buf_len,
             "%04d-%02d-%02d %02d:%02d:%02d.%03d",
             tm_time.tm_year + 1900,
             tm_time.tm_mon + 1,
             tm_time.tm_mday,
             tm_time.tm_hour,
             tm_time.tm_min,
             tm_time.tm_sec,
             milli);
}

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()
{
	int pos = 0;
	int			iedno,chnl_no;
	ied_t		*ied;
	struct in_addr	ip;
	chnl_usr_t	*chnl_usr;

	if(IED_COUNT < g_pt61850app->chnl_counts){
		set_rem_dib_table_size( g_pt61850app->chnl_counts );//按照最大的终端数来申请
		g_pt61850app->chnl_usr = apr_pcalloc( g_init_pool,g_pt61850app->chnl_counts*sizeof(chnl_usr_t*) );
		printf( "set_rem_dib_table_size  %d \n",g_pt61850app->chnl_counts );
	}
	else{
		set_rem_dib_table_size( IED_COUNT );//按照最大的终端数来申请
		g_pt61850app->chnl_usr = apr_pcalloc( g_init_pool,IED_COUNT*sizeof(chnl_usr_t*) );
		printf( "set_rem_dib_table_size  %d \n",IED_COUNT );
	}
	
	for(iedno=0 ; iedno<g_node->n_clients; iedno++) {
		ied = g_node->clients[iedno];		
		for(chnl_no=0 ; chnl_no<ied->chncount; chnl_no++) {	
			chnl_usr = ied->channel[chnl_no].connect;
			g_pt61850app->chnl_usr[pos] = chnl_usr;
			ip.s_addr = htonl(ied->channel[chnl_no].addr);
			strcpy(chnl_usr->ip_str,inet_ntoa(ip));
			printf( " add_rem_dib_table  %s:%d \n",chnl_usr->ip_str ,ied->channel[chnl_no].port );
			add_rem_dib_table (pos++,chnl_usr->ip_str,ied->channel[chnl_no].port );
			{
				char comm_str[256]; 
				memset(comm_str,0,256);
				apr_snprintf(comm_str,sizeof(comm_str),"%16s:%d\t\tinited",chnl_usr->ip_str,ied->channel[chnl_no].port);
				add_comm_log(comm_str);
			}
		}
	}
	return APR_SUCCESS;
}
//lnk20250122end

void CloseIECReports(chnl_usr_t *chnl_usr)
{
	ied_t		*ied;
	ied_usr_t  *ied_usr;
	LD_info_t	*LD_info;
	rptinfo_t	*rptinfo = NULL;
	int			cpuno,rpt_no;
	ST_RET		ret;

	ied = chnl_usr->chnl->ied;
	ied_usr = GET_IEDEXT_ADDR(ied);
	for(cpuno=0 ; cpuno<ied->cpucount; cpuno++) {
		LD_info = &(ied_usr->LD_info[cpuno]);

		//添加保护lnk20260602
		if (LD_info->cpuno == 0 ||
		    LD_info->LD_name == NULL ||
		    LD_info->rptcount <= 0 ||
		    LD_info->rptinfo == NULL)
		{
		    continue;
		}

		for(rpt_no=0 ; rpt_no<LD_info->rptcount; rpt_no++) {
			char rpt_inst_name[65];
			rptinfo = LD_info->rptinfo[rpt_no];

			//添加保护lnk20260602
			if (rptinfo == NULL)
        		continue;

			if ( !rptinfo->rpt_registered )
				continue;
			if ( rptinfo->chnl_id != chnl_usr->chnl_id)
				continue;

			rptinfo->rpt_registered = FALSE;	
			//注销报告后，取消10分钟 再注册一次的限制，可立即注册
			rptinfo->m_LastRegisterFailedTime = sGetMsTime() -10*60*1000;
			rptinfo->m_rcb_info = NULL;
		}
	}	
}

void closeChannel(chnl_usr_t *chnl_usr)
{

	char comm_str[256]; 
	memset(comm_str,0,256);
	apr_snprintf(comm_str,sizeof(comm_str),"%16s:%d\t\tdisconnected !!!",chnl_usr->ip_str,chnl_usr->chnl->port);
	add_comm_log(comm_str);
	FRONT_MP_NUM--;
	echo_warn1("Close Channel   IP:  %s",chnl_usr->ip_str );	

	CloseIECReports(chnl_usr);	
	echo_warn1("-------Close Channel   IP:  %s success!!!!!!!!!", chnl_usr->ip_str);

	if (chnl_usr->net_info) {
		ALL_RCB_INFO *all_rcb_info;
		RCB_INFO *rcb_info;	
		ST_RET ret;
		if(chnl_usr->net_info->user_info)
		{
			all_rcb_info = (ALL_RCB_INFO *)chnl_usr->net_info->user_info;
			while((rcb_info = (RCB_INFO *)list_get_first(&all_rcb_info->rcb_info_list)) != NULL)
				rcb_info_destroy (rcb_info);
			chk_free(all_rcb_info);
			chnl_usr->net_info->user_info=NULL;
		}
		chnl_usr->net_info->rem_vmd = NULL;							
		echo_warn("---------start disconnectFromServer!\n");
		ret = mms_disconnectFromServer(chnl_usr->net_info,&chnl_usr->m_reqCtrl);
		echo_warn("---------end disconnectFromServer!\n");

		DIY_WARNLOG_CODE(((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,1,LOG_CODE_COMM,"【WARN】前置与终端%s - ip端口%s:%d 断开连接", ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,chnl_usr->ip_str,chnl_usr->chnl->port);

		if (ret != SD_SUCCESS){
			echo_warn("---------disconnectFromServer success!\n");
			
			echo_warn2( "CHANNEL %s,NetInfo= %x  mms_disconnectFromServer failed ,Disconnect it roughly! \n",chnl_usr->ip_str,chnl_usr->net_info);	
			
			mvl_free_req_ctrl(chnl_usr->m_reqCtrl);

			chnl_usr->net_info->user_ext = NULL;
			chnl_usr->net_info = NULL;

			
			chnl_usr->m_reqCtrl = NULL;
			chnl_usr->net_info = NULL;
			chnl_usr->m_state = CHANNEL_DISCONNECTED;
			chnl_usr->m_ClosedMsTime = sGetMsTime();

			chnl_usr->chnl->status = STATUS_BREAKOFF;			
		}
		else{
			chnl_usr->m_state = CHANNEL_DISCONNECTING;
			chnl_usr->m_StartDisconnectingTime = sGetMsTime();	
		}			 		
	}else	{
		chnl_usr->m_state = CHANNEL_DISCONNECTED;
		chnl_usr->m_ClosedMsTime = sGetMsTime();
		
		chnl_usr->chnl->status = STATUS_BREAKOFF;			
	}
}

ST_VOID Callback_channel_disconnect_ind(MVL_NET_INFO * NetInfo, ST_INT discType)
{
	chnl_usr_t	*chnl_usr;

	chnl_usr = (chnl_usr_t*)NetInfo->user_ext;
	
	if ( chnl_usr )	{		 	 
		if(chnl_usr->m_state == CHANNEL_CONNECTING)
		{	//do nothing;
			printf("Do nothing,m_state == CHANNEL_CONNECTING ,NetInfo = %x",NetInfo);
		}else if ( chnl_usr->m_state == CHANNEL_CONNECTED )
		{
			closeChannel(chnl_usr);
		}else if ( chnl_usr->m_state == CHANNEL_DISCONNECTING )
		{	//do nothing;
			printf("Do nothing,m_state == CHANNEL_DISCONNECTING ,NetInfo = %x",NetInfo);
		}else if ( chnl_usr->m_state == CHANNEL_DISCONNECTED )
		{	//do nothing;
			printf("Do nothing,m_state == CHANNEL_DISCONNECTED ,NetInfo = %x",NetInfo);
		}				

		
		chnl_usr->net_info = NULL;
		NetInfo->user_ext = NULL;

	}
	printf(" Callback_channel_disconnect_ind ,NetInfo = %x",NetInfo);
		
	//zw修改 2023 - 8 - 17 通讯中断回调函数 PG库记录当日中断次数
	ied_usr_t* ied_usr = (ied_usr_t*)(chnl_usr->chnl[0].ied->usr_ext);
}


void IECReport_tryGI(chnl_usr_t *chnl_usr,rptinfo_t	*rptinfo)
{
	char varName[64] = "";
	ST_BOOLEAN GI = TRUE;		/* call GI	*/

	if ( (sGetMsTime() -rptinfo->m_LastGITime) < g_pt61850app->giTime*1000 )
		return;
	rptinfo->m_LastGITime = sGetMsTime();
	get_rpt_inst_name(rptinfo,varName);
	strcat(varName, "$GI");	
	mms_named_var_write (chnl_usr->net_info, varName, DOM_SPEC,  rptinfo->LD_info->LD_name, 
		g_rpt_typeids.mmsbool, (ST_CHAR *) &GI, g_pt61850app->mmsOpTimeout);	
}

//增加处理根据稳态，或暂态功能等，决定 报告是否需要注册、取消注册或 不做任何处理
int judge_rpt_next_should_do(rptinfo_t *rptinfo)
{

	if (rptinfo == NULL) {
        printf("[RPT][ERROR] rptinfo is NULL\n");
        return SHOULD_DO_NOTHING;
    }

    if (rptinfo->LD_info == NULL) {
        printf("[RPT][ERROR] rptinfo->LD_info is NULL, rptID=%s\n",
               rptinfo->rptID ? rptinfo->rptID : "NULL");
        return SHOULD_DO_NOTHING;
    }

	int should_register_state = 1; //各功能默认注册
	int	is_real_report = (rptinfo->report_PQ_type & REPORT_TYPE_REAL);//报告控制中包含的类型
	int is_soe_report = (rptinfo->report_PQ_type & REPORT_TYPE_SOE);
	 
	if (three_secs_enabled) {
		should_register_state = 0; //3秒功能模块，默认不注册
		if (is_real_report)  {//映射中包含控制块且触发
			should_register_state = rptinfo->LD_info->real_data;
		}
		if (is_soe_report)  {
			should_register_state = rptinfo->LD_info->soe_data;
		}
	}

	if (should_register_state==rptinfo->rpt_registered)//已经触发/没有触发，没有变动
		return SHOULD_DO_NOTHING;
	else if (should_register_state)//有变动且要触发
		return SHOULD_REGISTER;
	else
		return SHOULD_UNREGISTER;//有变动，不触发
}

void ChannelCheckIECReports(chnl_usr_t *chnl_usr)
{
	ied_t		*ied;
	ied_usr_t  *ied_usr;
	LD_info_t	*LD_info;
	rptinfo_t	*rptinfo = NULL;
	channel_t	*channel = NULL;
	int			cpuno,rpt_no;
	char		rpt_inst_name[65];
	ST_RET		ret;

	ied = chnl_usr->chnl->ied;
	ied_usr = GET_IEDEXT_ADDR(ied);
	channel = chnl_usr->chnl;

	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->rptcount <= 0 ||
		    LD_info->rptinfo == NULL)
		{
		    printf("[RPT][SKIP] invalid LD_info cpu=%d cpuno=%d LD_name=%p rptcount=%d rptinfo=%p\n",
		           cpuno,
		           LD_info->cpuno,
		           LD_info->LD_name,
		           LD_info->rptcount,
		           LD_info->rptinfo);
		    continue;
		}

		//未初始化的测点告警没有意义，直接访问id还可能崩溃
		/*if (LD_info->cpuno==0){
			// 仅在还没达到5次上限时打印
    		if (!LD_info->has_logged_regist) {
    		    LD_info->registcount++;
			
    		    if (LD_info->registcount <= 5) {
    		        DIY_ERRORLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT,"【ERROR】监测点:%s - id:%s报告触发失败,监测点逻辑标识号为0,请检查装置对应的台账信息是否正确", LD_info->name,LD_info->mp_id);
    		    }
			
    		    // 到5次就不再打印，并标记
    		    if (LD_info->registcount > 5) {
    		        LD_info->has_logged_regist = true;
    		        DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT,"【WARN】监测点:%s - id:%s监测点逻辑标识号错误日志已达本次记录上限，不再输出，请检查装置对应的台账信息是否正确",
    		                    LD_info->name, LD_info->mp_id);
    		    }
    		}
			continue;
		}*/
			

		for(rpt_no=0 ; rpt_no<LD_info->rptcount; rpt_no++) {	//遍历报告（映射文件中读取的报告控制）

			rptinfo = LD_info->rptinfo[rpt_no] ;

			//检查是否需要注册或取消注册报告，或不做任何处理
			if(DEBUGOPEN)printf("[RPT][CHECK] ip=%s cpu=%d rpt_no=%d rptcount=%d LD_name=%s rptinfo=%p\n",
			       chnl_usr->ip_str,
			       cpuno,
			       rpt_no,
			       LD_info->rptcount,
			       LD_info->LD_name ? LD_info->LD_name : "NULL",
			       rptinfo);

			if (rptinfo == NULL) {
			    printf("[RPT][ERROR] rptinfo NULL, skip\n");
			    continue;
			}

			if (rptinfo->LD_info == NULL) {
			    printf("[RPT][ERROR] rptinfo->LD_info NULL, rptID=%s, skip\n",
			           rptinfo->rptID ? rptinfo->rptID : "NULL");
			    continue;
			}

			if (rptinfo->rptID == NULL || rptinfo->rptID[0] == '\0') {
			    printf("[RPT][ERROR] rptID empty, skip\n");
			    continue;
			}
			//添加保护

			if (judge_rpt_next_should_do(rptinfo)==SHOULD_DO_NOTHING)//检查是否触发
			{
				//DIY_DEBUGLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT,"【DEBUG】监测点:%s - id:%s不注册报告", LD_info->name,LD_info->mp_id);
				continue;
			}

			if(rptinfo->m_curRptSuffix==-1)
				rptinfo->m_curRptSuffix = g_pt61850app->rptSuffix[g_client_id][0] ;	

			get_rpt_inst_name(rptinfo,rpt_inst_name);//获取报告名

			if ( ! rptinfo->rpt_registered )	{
				if ( (sGetMsTime() -rptinfo->m_LastRegisterFailedTime) > 20*1000 )	{
					//注册失败后间隔	20秒 再注册一次					
					RCB_INFO	*rcb_info;					
					printf("start  mms_register_iec_rpt................................\n");

					//mq日志
					DIY_INFOLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT,"【NORMAL】监测点:%s - id:%s开始注册报告,报告名:%s", LD_info->name,LD_info->mp_id,rpt_inst_name);

					if ( strstr(rptinfo->rptID,"LLN0$BR$brcbFlickerData") )
						rptinfo->IntgPd = 600; //10分钟

					/////////////////////////WW 2023-08-30 增加设备类型与报告绑定
					rcb_info = mms_register_iec_rpt (chnl_usr->net_info, &g_rpt_typeids,
						LD_info->LD_name,rpt_inst_name,g_pt61850app->mmsOpTimeout, rptinfo->IntgPd,rptinfo->TrgOpt,
						(ST_UINT8*)rptinfo->m_EntryID ,(ST_UINT8*)rptinfo->OptFlds);
					//WW end
					///////////////////////////
					if( !rcb_info )
					{
						if ( ++rptinfo->m_curRptSuffix > g_pt61850app->rptSuffix[g_client_id][1] )
							rptinfo->m_curRptSuffix = g_pt61850app->rptSuffix[g_client_id][0] ;

						rptinfo->m_LastRegisterFailedTime = sGetMsTime() ;

						echo_err9("\n注册报告失败！Rregister iec_rpt failed !!! IED_ID=%d ,CPU=%d , domain: %s ,rpt_inst_name: %s ,ip: %s:%d,chnl_id: %d  ,IntgPd=%d ,TrgOpt=0x%x  \n",
							APR_EGENERAL, LD_info->ied->id,LD_info->cpuno,LD_info->LD_name,rpt_inst_name,chnl_usr->ip_str,chnl_usr->chnl->port,
							chnl_usr->chnl_id, rptinfo->IntgPd,rptinfo->TrgOpt  );

						//mq日志
						// 仅在还没达到5次上限时打印
    					if (!LD_info->has_logged_regist) {
    					    LD_info->registcount++;
						
    					    if (LD_info->registcount <= 5) {
    					        DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT, "【WARN】监测点:%s - id:%s注册报告失败,报告名:%s",
    					                    LD_info->name, LD_info->mp_id, rpt_inst_name);
    					    }
						
    					    // 到5次就不再打印，并标记
    					    if (LD_info->registcount > 5) {
    					        LD_info->has_logged_regist = true;
    					        DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT,"【WARN】监测点:%s - id:%s注册报告失败日志已达本次注册上限，不再输出，请检查装置icd和映射文件是否匹配或者装置存在过多连接",
    					                    LD_info->name, LD_info->mp_id);
    					    }
    					}

					}
					else
					{

						double GIoffset;
						rptinfo->rpt_registered = TRUE;	
						rptinfo->m_rcb_info = rcb_info;	
						rptinfo->chnl_id = chnl_usr->chnl_id;
						chnl_usr->m_NegRespTimes = 0;
						chnl_usr->m_LastPosRespTime = sGetMsTime();
						
						echo_msg11("\nRegister iec_rpt succeed, IED_ID=%d ,CPU=%d ,domain: %s ,rpt_inst_name: %s ,ip: %s:%d,chnl_id: %d ,IntgPd=%d ,TrgOpt=0x%x ,OptFlds=0x%x%x   \n",
							LD_info->ied->id,LD_info->cpuno,LD_info->LD_name,rpt_inst_name,chnl_usr->ip_str,chnl_usr->chnl->port,chnl_usr->chnl_id,
							rptinfo->IntgPd,rptinfo->TrgOpt,rptinfo->OptFlds[0],rptinfo->OptFlds[1] );
						
						//mq日志
						LD_info->has_logged_regist = FALSE;
						LD_info->registcount = 0;
						DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT,"【WARN】监测点:%s - id:%s注册报告成功,报告名:%s", LD_info->name,LD_info->mp_id,rpt_inst_name);

						//   add here to GI not the same time 	
						GIoffset = 0.5 * g_pt61850app->giTime;
						rptinfo->m_LastGITime = sGetMsTime() - GIoffset*1000;								
					}	
					printf("end  mms_register_iec_rpt................................\n");
					//mq日志
					DIY_INFOLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT,"【NORMAL】监测点:%s - id:%s注册报告结束,报告名:%s", LD_info->name,LD_info->mp_id,rpt_inst_name);
				}
			}
			else { //rpt_registered ==TRUE
				if ( (sGetMsTime() -rptinfo->m_LastUnRegisterFailedTime) > 20*1000 )	{
					//取消注册失败后间隔	20秒 再取消注册一次		 				
					printf("start  mms_unregister_iec_rpt................................\n"); 

					//mq日志
					DIY_INFOLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT,"【NORMAL】监测点:%s - id:%s开始注销报告,报告名:%s", LD_info->name,LD_info->mp_id,rpt_inst_name);

					ret = mms_unregister_iec_rpt (chnl_usr->net_info, &g_rpt_typeids,
						LD_info->LD_name,rpt_inst_name,g_pt61850app->mmsOpTimeout);

					if( ret == SD_FAILURE )		{						 
						rptinfo->m_LastUnRegisterFailedTime = sGetMsTime() ;
						echo_err6("\n取消注册报告失败！UnRregister iec_rpt failed !!! IED_ID=%d ,CPU=%d , domain: %s ,rpt_inst_name: %s ,ip: %s,chnl_id: %d \n",
							APR_EGENERAL, LD_info->ied->id,LD_info->cpuno,LD_info->LD_name,rpt_inst_name,chnl_usr->ip_str,chnl_usr->chnl_id);

						//mq日志
						DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT,"【WARN】监测点:%s - id:%s注销报告失败,报告名:%s", LD_info->name,LD_info->mp_id,rpt_inst_name);

					}
					else	{ 
						rptinfo->rpt_registered = FALSE;	 
						echo_msg7("\nUnRegister iec_rpt succeed, IED_ID=%d ,CPU=%d ,domain: %s ,rpt_inst_name: %s ,ip: %s:%d,chnl_id: %d  \n",
							LD_info->ied->id,LD_info->cpuno,LD_info->LD_name,rpt_inst_name,chnl_usr->ip_str,chnl_usr->chnl->port,chnl_usr->chnl_id ); 	
							
						//mq日志
						DIY_INFOLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT,"【NORMAL】监测点:%s - id:%s注销报告成功,报告名:%s", LD_info->name,LD_info->mp_id,rpt_inst_name);
					}	
					printf("end  mms_unregister_iec_rpt................................\n");
					//mq日志
					DIY_INFOLOG_CODE(LD_info->mp_id,2,LOG_CODE_REPORT,"【NORMAL】监测点:%s - id:%s注销报告结束,报告名:%s", LD_info->name,LD_info->mp_id,rpt_inst_name);
				}

			} 
		}
	}	
}

void ChannelCheckWaveFiles(chnl_usr_t *chnl_usr)
{ 
	ied_t		*ied;
	ied_usr_t  *ied_usr;
	LD_info_t	*LD_info; 
	int			cpuno;

	ied = chnl_usr->chnl->ied;
	ied_usr = GET_IEDEXT_ADDR(ied);
	for(cpuno=0 ; cpuno<ied->cpucount; cpuno++) {
		LD_info = &(ied_usr->LD_info[cpuno]);
		if (LD_info->line_id<=0) {
			continue;
		}
		call_cn_wavelist(LD_info); //try to call wave file		 
	}	
}

//lnk20250821时间转换：秒转本地时间
static void sec_to_timestr(long long sec, char *out, size_t out_sz)
{
    time_t t = (time_t)sec;
    struct tm tmv;
    localtime_r(&t, &tmv);  // 本地时间
    strftime(out, out_sz, "%Y-%m-%d %H:%M:%S", &tmv);
}

void ChannelCheckIECLogs(chnl_usr_t *chnl_usr)
{
	ST_RET		ret;
	ied_t		*ied;
	ied_usr_t  *ied_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 ; cpuno<ied->cpucount; cpuno++) {
		LD_info = &(ied_usr->LD_info[cpuno]);

		//添加保护，防止台账中测点号不连续导致的崩溃
		if (LD_info->cpuno == 0 ||
		    LD_info->LD_name == NULL)
		{
		    continue;
		}

		if (!is_pqdif_process &&
		    (LD_info->logcount <= 0 ||
		     LD_info->loginfo == NULL))
		{
		    continue;
		}

		//日志控制块缺失
		/*if (LD_info->logcount<=0){
			// 仅在还没达到5次上限时打印
    		if (!LD_info->has_logged_regist) {
    		    LD_info->registcount++;
			
    		    if (LD_info->registcount <= 5) {
    		        DIY_ERRORLOG_CODE(LD_info->mp_id,2,LOG_CODE_RECALL,"【ERROR】监测点:%s - id:%s补招数据失败,监测点缺少日志控制块,请检查装置对应的装置类型是否有配对的icd模型", LD_info->name,LD_info->mp_id);
    		    }
			
    		    // 到5次就不再打印，并标记
    		    if (LD_info->registcount > 5) {
    		        LD_info->has_logged_regist = true;
    		        DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_RECALL,"【WARN】监测点:%s - id:%s缺少日志控制块日志已达本次记录上限，不再输出，请检查装置对应的装置类型是否有配对的icd模型",
    		                    LD_info->name, LD_info->mp_id);
    		    }
    		}
			
			continue;
		}*/
			
		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);		

		Check_Recall_Config(LD_info->mp_id);//尝试获取xml结构

		//补招数量不为0且不是正在补招
		if (LD_info->autorecallcount != 0 && LD_info->autorecallflag != 1) {
			int i;
			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");
			}
			utc_or_beijing = 0;
		
			if(strcmp(cfg1.ValueOfTimeUnit, "utc") == 0){//装置时间是utc还是北京
				utc_or_beijing = 28800;//秒
				DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_RECALL,"【WARN】监测点:%s - id:%s开始补招数据,注意!下发补招时间为utc时间,监测点对应装置型号:%s", LD_info->name,LD_info->mp_id,ied_usr->dev_type);
			}
			else{
				utc_or_beijing = 0;
				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;
    		long long max_end_sec   = 0;
			/////////////////////////////////////////////////////////////

			for (i = 0; i < LD_info->autorecallcount; i++) {

				// ===== [新增] 累计全局最小/最大区间（直接用原始 start/end）=====
        		if (LD_info->autorecall[i]->start < min_start_sec) {
        		    min_start_sec = LD_info->autorecall[i]->start;
        		}
        		if (LD_info->autorecall[i]->end > max_end_sec) {
        		    max_end_sec = LD_info->autorecall[i]->end;
        		}
        		// ===== [新增结束] =====

				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);//保证时间开头
				loginfo->end_time = apr_time_from_sec(LD_info->autorecall[i]->end - 5);//不保证时间结尾

				/////////////////////////////////////////////////////根据配置文件控制下发补招时间为北京时间还是utc时间,上送的数据61850库会转换成北京时间？				
				loginfo->start_time = loginfo->start_time - utc_or_beijing * APR_USEC_PER_SEC;//下发utc时间需要减去8小时-秒
				loginfo->end_time = loginfo->end_time - utc_or_beijing * APR_USEC_PER_SEC;
				/////////////////////////////////////////////////////

				if (loginfo->need_steady == 0 && loginfo->need_voltage == 0)
					continue;
				if (loginfo->end_time <= loginfo->start_time)
					continue;

				printf("start  mms_jread................................\n");

				echo_msg6("\n mms_jread IED_ID=%d ,CPU=%d ,domain: %s ,logName: %s ,ip: %s,chnl_id: %d \n",
					LD_info->ied->id, LD_info->cpuno, LD_info->LD_name, loginfo->logName, chnl_usr->ip_str, chnl_usr->chnl_id);

				//mq日志
				//DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_RECALL,"【WARN】监测点:%s - id:%s开始补招数据", LD_info->name,LD_info->mp_id);

				ret = mms_jread(loginfo, chnl_usr->net_info, loginfo->LD_info->LD_name, loginfo->logName,
					loginfo->start_time, loginfo->end_time, g_pt61850app->mmsOpTimeout, chnl_usr->ip_str);
				if (ret != SD_SUCCESS) {
					echo_warn6("\n mms_jread Failed! IED_ID=%d ,CPU=%d ,domain: %s ,logName: %s ,ip: %s,chnl_id: %d \n",
						LD_info->ied->id, LD_info->cpuno, LD_info->LD_name, loginfo->logName, chnl_usr->ip_str, chnl_usr->chnl_id);

					//mq日志
					DIY_ERRORLOG_CODE(LD_info->mp_id,2,LOG_CODE_RECALL,"【ERROR】监测点:%s - id:%s补招数据失败 - 失败时间点:%lld 至 %lld", LD_info->name,LD_info->mp_id,loginfo->start_time,loginfo->end_time);

					failed_count++;
				}

				del_mvl_type_ctrl();

				loginfo->need_steady = 0;
				loginfo->need_voltage = 0;
				loginfo->start_time = loginfo->end_time;

				g_dead_lock_counter = 0;
				g_thread_blocked_times = 0;		//防止补招时间过长导致进程退出

				now = sGetMsTime();
				last_check_recall_config_time = now;
				printf("end ==============%.2f================\n", last_check_recall_config_time);
				printf("end  mms_jread................................\n");
			}

			//不管是否成功，这个补招文件必须删除，可能出现一直失败，循环读取文件和循环补招导致程序崩溃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);

				// ===== [新增] 组装上送的补招开始/结束时间字符串（本地时区，直接“秒转字符串”）=====
    			char recallStartDate[32] = {0};
    			char recallEndDate[32]   = {0};

    			if (min_start_sec == LLONG_MAX) {
    			    // 理论上不会进来：有 autorecallcount != 0。兜底写成当前时间
    			    time_t nowt = time(NULL);
    			    sec_to_timestr((long long)nowt, recallStartDate, sizeof(recallStartDate));
    			    sec_to_timestr((long long)nowt, recallEndDate,   sizeof(recallEndDate));
    			} else {
    			    sec_to_timestr(min_start_sec, recallStartDate, sizeof(recallStartDate));
    			    sec_to_timestr(max_end_sec,   recallEndDate,   sizeof(recallEndDate));
    			}
			
    			//拼接result
				const char *ld_name_safe = (LD_info->name && LD_info->name[0])
                           ? LD_info->name : "-";

				char result_buf[256];
				snprintf(result_buf, sizeof(result_buf),
				         "监测点：%s 补招时间最小值和最大值：%s ~ %s 本次补招执行结束",
				         ld_name_safe, recallStartDate, recallEndDate);

				// guid 固定 "12345"，step 固定 "2"
				//send_reply_to_kafka_c("12345", "2", result_buf);
				send_reply_to_kafka_recall_c("12345", "2", result_buf,LD_info->mp_id,recallStartDate,recallEndDate);
    			
    			// ===== [新增结束] =====

				DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_RECALL,"【WARN】监测点:%s - id:%s结束补招数据", LD_info->name,LD_info->mp_id);
			//}
		}

	}	
}


void process_3s_config(trigger_3s_xml_t *trigger_3s_xml)
{
	int			i,j;
	trigger_t	*trigger;
	trigger_t	*trigger_work;
	int			trigger_num;
	ied_t		*ied;
	LD_info_t	*LD_info;

	int			need_write_file;
	int			new_in_work_found;

	printf("start process_3s_config\n");

	need_write_file = FALSE;
	trigger = trigger_3s_xml->new_triggers;			//3s配置文件中的newtrigger数组，如果没有newtrigger这里就是0
	trigger_num = trigger_3s_xml->new_trigger_num;
	for (i=0; i<trigger_num; i++){ 		
		new_in_work_found = FALSE;
		for (j=0; j<trigger_3s_xml->work_trigger_num; j++){ 
			trigger_work = &trigger_3s_xml->work_triggers[j];
			if (trigger_work->dev_idx==trigger[i].dev_idx  && trigger_work->line_id==trigger[i].line_id  ) {

				if (trigger[i].real_data>=0)
					trigger_work->real_data = trigger[i].real_data;//更新rtdata标志
				if (trigger[i].soe_data>=0)
					trigger_work->soe_data = trigger[i].soe_data;	//更新soe标志
				trigger_work->limit = trigger[i].limit;

				clear_rpt_counter_by_trigger(trigger_work);//清理报告
				new_in_work_found = TRUE;
			}
		}
		if (!new_in_work_found)  {	//newtrigger为0，这是全新的触发，不是正在工作的触发
			clear_rpt_counter_by_trigger(&trigger[i]);
			if (trigger[i].real_data<0)
				trigger[i].real_data = 0;
			if (trigger[i].soe_data<0)
				trigger[i].soe_data = 0;
			trigger_3s_xml->work_triggers[trigger_3s_xml->work_trigger_num++] = trigger[i];
		}
		need_write_file = TRUE;//需要将新增的点写到文件中
	} 

	trigger = trigger_3s_xml->delete_triggers;			//如果没有deletetrigger这里就是0
	trigger_num = trigger_3s_xml->delete_trigger_num;
	for (i=0; i<trigger_num; i++){ 
		//for (j=0; j<trigger_3s_xml->work_trigger_num; j++){ 
			//trigger_work = &trigger_3s_xml->work_triggers[j];
			//if (trigger_work->dev_idx==trigger[i].dev_idx  && trigger_work->line_id==trigger[i].line_id  ) {
				clear_one_LD_real_soe_report_shoud_register(trigger[i].dev_idx,trigger[i].line_id);
				//clear_rpt_counter_by_trigger(trigger_work);
				clear_rpt_counter_by_trigger(trigger);
				trigger->dev_idx = INVALID_DEV_IDX;	
			//}
		//}
		need_write_file = TRUE;
	} 

	/*trigger = trigger_3s_xml->modify_triggers;		//如果没有modifytrigger这里就是0
	trigger_num = trigger_3s_xml->modify_trigger_num;
	for (i=0; i<trigger_num; i++){ 
		for (j=0; j<trigger_3s_xml->work_trigger_num; j++){ 
			trigger_work = &trigger_3s_xml->work_triggers[j];
			if (trigger_work->dev_idx==trigger[i].dev_idx  && trigger_work->line_id==trigger[i].line_id  ) {
				*trigger_work = trigger[i];
				clear_rpt_counter_by_trigger(trigger_work);
			}
		}
		need_write_file = TRUE;
	}  */

	//clear_all_LD_real_soe_report_shoud_register();		//清空所有需要注册的报告，根据实时配置文件来。这里使LD_info->real_data = 0;LD_info->soe_data = 0;就不会触发报告

	trigger = trigger_3s_xml->work_triggers;		//文件的work块
	trigger_num = trigger_3s_xml->work_trigger_num;

	for (i=0; i<trigger_num; i++){ 		//遍历work块中所有触发点
		int real_report_count = 0;		//实时报告数量

		 if (trigger[i].dev_idx==INVALID_DEV_IDX){//跳过错误的终端号
			printf("dev_idx incaild\n");
			continue;
		 }
		 	
		 ied = find_ied_from_dev_idx(trigger[i].dev_idx);	//通过文件中的dev号找到配置的设备，找不到说明不在这个进程里跳过（多进程的实现是否有必要？如果要实现就得每个进程一个操作目录）
		 if (!ied){
			printf("can't find ied by dev_idx\n");
			continue; 
		 }
			
		 LD_info = find_LD_info_from_line_id(ied,trigger[i].line_id);		//用文件中的检测点号找到配置的监测点
		 if (!LD_info){
			printf("can't find line by line_idx\n");
			continue;
		 }
			
		 real_report_count = get_real_report_count(LD_info);	//获取监测点的实时报告数量LD_info->rptinfo[rpt_no]->count

		//调试用
		printf("terminal:%s  -  mp:%s  real_report_count %d\n",  ((ied_usr_t*)(ied->usr_ext))->terminal_id, LD_info->mp_id, real_report_count);

		 trigger[i].count = real_report_count;	//记录此时这个监测点的实时报告数量
		 if (trigger[i].real_data && trigger[i].limit && (real_report_count>trigger[i].limit) ) {//监测点的实时报告数量大于当前文件中的限制，这个监测点的数据清零不再触发
			trigger[i].real_data = 0;
			trigger[i].limit = 0;
			trigger[i].count = 0;
			need_write_file = TRUE;
		 }
		 LD_info->real_data = trigger[i].real_data;		//根据文件中的配置来设置，然后在报告触发中触发实时报告
		 LD_info->soe_data = trigger[i].soe_data;
		 LD_info->limit = trigger[i].limit;
		 LD_info->count = trigger[i].count;
		 if (LD_info->limit > 0)	//限制数更新记录
			need_write_file = TRUE;
		 if ( trigger[i].real_data==0 &&  trigger[i].soe_data==0 )//不触发条件记录
			 need_write_file = TRUE;
	}

	if (need_write_file)
		create_3s_xml(trigger_3s_xml);		//写入文件的work块
}

/*void del_process_recall_config(recall_xml_t* recall_xml) 
{
	int			i,j;
	recall_t	*recall;
	recall_t	*recall_work;
	int			recall_num;
	ied_t		*ied;
	LD_info_t	*LD_info;
	loginfo_t	*loginfo = NULL;
	int			need_write_file;

	need_write_file = FALSE;
	recall = recall_xml->new_recalls;
	recall_num = recall_xml->new_recall_num;
	for (i=0; i<recall_num; i++){  
		recall_xml->work_recalls[recall_xml->work_recall_num++] = recall[i];
		need_write_file = TRUE;
	}

	recall = recall_xml->work_recalls;
	recall_num = recall_xml->work_recall_num;
	for (i=0; i<recall_num; i++){ 	 		   
		 LD_info = find_LD_info_only_from_line_id(recall[i].line_id);
		 if (!LD_info)
			 continue; 
		 if (LD_info->logcount<=0)
			continue;
		loginfo = LD_info->loginfo[0] ;	

		loginfo->need_steady = recall[i].need_steady;
		loginfo->need_voltage = recall[i].need_voltage;

		loginfo->start_time = recall[i].start_time;
		loginfo->end_time = recall[i].end_time;
 
	}

}*/

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 ( fabs(now - last_check_3s_config_time) < 3*1000 ) //wait 3secs  //当前进程任务执行时查看当前时间和上次执行时间间隔，小于3秒不执行，大于等于3秒往下执行
		return;

	//调试用
	//printf("begin 3s config...\n");

	last_check_3s_config_time = now;	//记录本次运行时间
	while (APR_SUCCESS==parse_3s_xml(&trigger_3s_xml)){	//处理3秒文件，一次处理一个
		//处理实时触发加台账锁lnk20250114
		//pthread_mutex_lock(&mtx);	printf("3s hold lock !!!!!!!!!!!");
		process_3s_config(&trigger_3s_xml);		 	//根据文件处理数据
		//pthread_mutex_unlock(&mtx);	printf("3s free lock !!!!!!!!!!!");
	}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//lnk20250114参照实时数据方法处理台账信息
int isValidModelId(const char* model_id) {
	size_t i;
	
    if (model_id == NULL) 
	{
		printf("!!!model_id null!!!\n");
		return 0;
	}  // NULL 无效

    size_t len = strlen(model_id);
    if (len < 4) {
		printf("!!!model_id length < 4!!!\n");
		return 0;  // 长度 < 4 无效
	}

    // 检查是否全是空格
    for (i = 0; i < len; i++) {
        if (!isspace((unsigned char)model_id[i])) {
            return 1;  // 只要包含非空格字符，就是合法的
        }
    }
	printf("!!!model_id only space!!!\n");
    return 0;  // 仅包含空格，无效
}

void process_ledger_update(trigger_update_xml_t *ledger_update_xml)
{
	int			i,j;
	terminal	*update;
	terminal	*update_work;
	int			update_num;
	ied_t		*ied;
	LD_info_t	*LD_info;
	int			new_in_work_found;

	ied_usr_t* ied_usr;
	int			chnl_no;
	chnl_usr_t	*chnl_usr = NULL;

	printf("!!!start update ledger!!!\n");

	update = ledger_update_xml->new_updates;			//处理新增台账部分
	update_num = ledger_update_xml->new_update_num;
	printf("add ledger num:%d\n",update_num);
	for (i=0; i<update_num; i++){						//查看所有需要新增的台账
		new_in_work_found = FALSE;
		if(update[i].terminal_id != NULL){
			printf("add ledger of %s\n",update[i].terminal_id);
			ied = find_ied_from_terminal_id(update[i].terminal_id);	//通过文件中的终端号找到配置的设备
			if (ied){
				printf("find ied by terminal_id, terminal already exsist\n");
				new_in_work_found = TRUE;				//当前运行的台账中找到已有台账，变为更新

				// 将当前终端从 new_updates 数组中移到 modify_updates 数组
        		if (ledger_update_xml->modify_update_num < MAX_UPDATEA_NUM) {
        		    // 添加到 modify_updates 数组
        		    ledger_update_xml->modify_updates[ledger_update_xml->modify_update_num] = update[i];
        		    ledger_update_xml->modify_update_num++;  // 更新 modify 数组的计数器

        		    // 删除该终端（从 new_updates 数组中移除）
        		    for (j = i; j < ledger_update_xml->new_update_num - 1; j++) {
        		        ledger_update_xml->new_updates[j] = ledger_update_xml->new_updates[j + 1];  // 向前移动元素
        		    }

        		    ledger_update_xml->new_update_num--;  // 更新 new_update_num，减少一个元素
        		    continue;
        		} else {
        		    fprintf(stderr, "Exceeded MAX_UPDATEA_NUM limit for modify_updates!\n");
        		}
			}
		}		

		if (!new_in_work_found)  {	//这是全新的台账，在台账数组中没有包含这个设备

			int terminal_index;
			int ied_take = 0;	//当前台账数组里有不使用的ied空间？0没有，1有
			//进行台账添加和初始化操作
			//1-申请新的内存空间//////////////////////////////
			//新的台账数量 (n_clients) 和原有的台账数量 (原数量为 g_node->n_clients)
    		int new_client_count = g_node->n_clients + 1;  // 增加一个新的台账

			//判断新增进程后是否超过原有的台账数组，如果超过了就从不使能的ied空间中找可用空间，如果还是没有那就提示不能荷载更多终端
			if(new_client_count > IED_COUNT){	//不更新台账数量
				ied_t		*ied_unused = NULL;
				ied_usr_t* ied_usr_unused = NULL;
				ied_unused = find_ied_unused();//遍历g_node找到第一个不使用的ied，这个ied之前初始化时分配过空间，直接占用
				if(ied_unused != NULL){
					ied_usr_unused = (ied_usr_t*)ied_unused->usr_ext;
					ied = ied_unused;	//新增的ied指向已有的未使用的ied空间
					terminal_index = ied_usr_unused->dev_idx;	//记录这个ied的编号，即g_node的下标

					//打印提示
					printf("!!!!!!!!ied index:%d ,origin terminal_id:%s has been taken!!!!!!!!!!\n",ied_usr_unused->dev_idx,ied_usr_unused->terminal_id);

					//终端初始化，不需要再申请空间
					ied_usr = (ied_usr_t*)ied->usr_ext;
					ied_usr->last_call_wavelist_time = sGetMsTime() + g_pt61850app->giTime * 1000;

					ied_usr->dev_flag = ENABLE;//终端有效

					ied->chncount = 1;	//通信端口数
					//不再分配通道空间
					ied->channel[0].ied = ied;	//通道的所属设备
					ied->channel[0].status = STATUS_BREAKOFF;//初始化为通信中断
					ied->cpucount = 0;	//监测点数初始为0，读取监测点台账时写入

					ied_take = 1;//ied之前存在

				}
				else{	//在已有的全部台账中都没找到不使用的ied，说明这个进程可挂的台账满了，不再处理
					printf("!!!!!!!!!!ledger array is full!!!!!!\n");

					//添加mq响应台账添加失败：台账挂满
					//update[i].guid
					char msg[256];
					sprintf(msg, "终端 id: %s 台账更新失败, 这个进程的台账空间已满，达到了配置台账数量的最大值", update[i].terminal_id);
					send_reply_to_kafka_c(update[i].guid, "2", msg);

					return;
				}
					
			}//新台账数量小于等于设定的台账数量
			else{

				//调试用
				printf("!!!!!!!!!!gnodeindex:%d!!!!!!\n",new_client_count - 1);

				// 新增的台账在已有的台账数组中记录
    			g_node->clients[new_client_count - 1] = (ied_t*)apr_pcalloc(g_cfg_pool, sizeof(ied_t));

    			// 更新台账数量
    			g_node->n_clients = new_client_count;  // 更新台账数量
				//1-申请新的内存空间//////////////////////////////

				//2-处理终端台账///////////////////////////////////
				ied = g_node->clients[new_client_count - 1];//终端台账指针定向到ied数组当前位置的后一位

				terminal_index = new_client_count;//新的台账终端
				
				ied_usr = (ied_usr_t*)apr_pcalloc(g_init_pool, sizeof(ied_usr_t));
				ied->usr_ext = ied_usr;//内存挂到ied上
				if (ied_usr == NULL){
					char msg[256];
					snprintf(msg, sizeof(msg), "终端 id: %s 台账更新失败,没有找到台账的终端空间", update[i].terminal_id);
					send_reply_to_kafka_c(update[i].guid, "2", msg);
					return ;
				}
					

				ied_usr->last_call_wavelist_time = sGetMsTime() + g_pt61850app->giTime * 1000;//从FeProject/子功能目录/etc/pt61850netd_pqfe.xml中读取的总查询时间
				
				ied_usr->LD_info = (LD_info_t*)apr_pcalloc(g_init_pool, MAX_CPUNO * sizeof(LD_info_t));//内存挂到ied上
				if (ied_usr->LD_info == NULL){
					char msg[256];
					snprintf(msg, sizeof(msg), "终端 id: %s 台账更新失败,没有找到台账的监测点空间", update[i].terminal_id);
					send_reply_to_kafka_c(update[i].guid, "2", msg);
					return ;
				}
					

				ied_usr->dev_flag = ENABLE;//终端有效

				ied->chncount = 1;	//通信端口数,一个终端一般只有一个
				ied->channel = (channel_t*)apr_pcalloc(g_cfg_pool, sizeof(channel_t) * ied->chncount);//内存挂到ied上
				ied->channel[0].ied = ied;	//通道的所属设备
				ied->channel[0].status = STATUS_BREAKOFF;//初始化为通信中断
				ied->cpucount = 0;	//监测点数初始为0，读取监测点台账时写入
				//2-处理终端台账///////////////////////////////////
			}

			//3-写入台账内容//////////////////////////////
			int ret = update_one_terminal_ledger(update,i,ied,terminal_index,ied_take);
			if(ret){
				printf("ledger can not be update!!!!!quit process!!!!!\n");	
				char msg[256];
				snprintf(msg, sizeof(msg), "终端 id: %s 台账更新失败,无法写入台账", update[i].terminal_id);
				send_reply_to_kafka_c(update[i].guid, "2", msg);

				clearIed(ied);//添加失败清空整个ied

				return ;
			}
			//3-写入台账内容///////////////////////////////////

			//4-配置映射文件//////////////////////////////
			if (g_node_id != PQDIF_DATA_BASE_NODE_ID) {
				char model[64] = {0};
    		// 获取模型ID，检查是否返回 NULL
    		parse_model_cfg_web_one(ied,&model);//存储在/FeProject/dat/

    		if (isValidModelId(model)) { //lnk20250313防止拿不到映射文件
    		    // 安全拷贝字符串到 model 数组
    		    strncpy(model, model, sizeof(model) - 1);
    		    model[sizeof(model) - 1] = '\0';  // 确保以 null 结尾
    		    printf("ledger Model ID: %s\n", model);
    		} else {
    		    printf("ledger No model ID found.quit\n");
				char msg[256];
				snprintf(msg, sizeof(msg), "终端 id: %s 台账更新失败,没有找到装置型号", update[i].terminal_id);
				send_reply_to_kafka_c(update[i].guid, "2", msg);

				clearIed(ied);//添加失败清空整个ied

				return ;
    		}
			char full_path[128];
        	snprintf(full_path, sizeof(full_path), "/FeProject/dat/%s.xml", model);  // 拼接路径

        	// 打印模型路径
        	printf("ledger icd config file full path: %s\n", full_path);

			//映射文件解析
			Set_xml_nodeinfo_one(ied_usr->dev_type);
			//4-配置映射文件///////////////////////////////////

			//5-报告块初始化//////////////////////////////
			parse_rpt_log_ini_one(ied);
			//5-报告块初始化///////////////////////////////////

			//调试
			}
			else {
				printf("[PQDIF] skip model xml and report init terminal=%s\n", ied_usr->terminal_id);
			}

			printf("ledger id: %s\n", ((ied_usr_t*)ied->channel[0].ied->usr_ext)->terminal_id);

			//6-init_rem_dib_table//////////////////////////////
			init_rem_dib_table_one(ied);
			//6-init_rem_dib_table///////////////////////////////////

			//7启动终端日志
			init_loggers_bydevid(((ied_usr_t*)ied->channel[0].ied->usr_ext)->terminal_id);

			//8响应添加成功
			//update[i].guid
			char msg[256];
			snprintf(msg, sizeof(msg), "终端 id: %s 台账添加成功", update[i].terminal_id);
			send_reply_to_kafka_c(update[i].guid, "2", msg);
			
		}
	} 

//////////////////////////////////////////////////////////////////////////////modify
	update = ledger_update_xml->modify_updates;			//处理修改台账部分
	update_num = ledger_update_xml->modify_update_num;
	printf("modify ledger num:%d\n",update_num);
	for (i=0; i<update_num; i++){						//查看所有需要新增的台账
		new_in_work_found = FALSE;
		if(update[i].terminal_id != NULL){
			printf("modify ledger of %s\n",update[i].terminal_id);
			ied = find_ied_from_terminal_id(update[i].terminal_id);	//通过文件中的终端号找到配置的设备
			if (ied){
				printf("find ied by terminal_id, terminal already exsist\n");
				new_in_work_found = TRUE;				//当前运行的台账中找到已有台账，更新它

				//关闭这个终端的所有连接//////////////////////////////////////////////////////////////////////
				for(chnl_no=0 ; chnl_no<ied->chncount; chnl_no++) {
					chnl_usr = (chnl_usr_t*)ied->channel[chnl_no].connect;
					/*if (chnl_usr->m_state!=CHANNEL_CONNECTED){//跳过未连接的通道
						continue;		
					}*///20260416任何状态都关闭连接
					closeChannel(chnl_usr);//关闭更新台账后，任务会自动连接
				}

				//更新数据/////////////////////////////////////////////////////////////////////////////////
				//3-写入台账内容//////////////////////////////
				ied_usr = ied->usr_ext;

				//清空logger
				remove_loggers_by_terminal_id(update[i].terminal_id);

				//写入前先清空已有数据
				clearIedUsr(ied_usr);//不会清空dev_idx

				//ied_usr->last_call_wavelist_time = sGetMsTime() + g_pt61850app->giTime * 1000;//从FeProject/子功能目录/etc/pt61850netd_pqfe.xml中读取的总查询时间

				ied_usr->dev_flag = ENABLE;//终端有效

				int ret = update_one_terminal_ledger(update,i,ied,ied_usr->dev_idx,1);//1：更新已有的ied
				if(ret){
					printf("ledger can not be update!!!!!quit process!!!!!\n");	

					char msg[256];
					snprintf(msg, sizeof(msg), "终端 id: %s 台账更新失败,台账无法写入", update[i].terminal_id);
					send_reply_to_kafka_c(update[i].guid, "2", msg);

					clearIed(ied);//添加失败清空整个ied

					return ;
				}
				//3-写入台账内容////////////////////////////////////////////

				//4-配置映射文件///////////////////////////////////////////
				if (g_node_id != PQDIF_DATA_BASE_NODE_ID) {
					char model[64] = {0};
    			// 获取模型ID，检查是否返回 NULL
    			parse_model_cfg_web_one(ied,&model);//存储在/FeProject/dat/

    			if (isValidModelId(model)) {
    			    // 安全拷贝字符串到 model 数组
    			    strncpy(model, model, sizeof(model) - 1);
    			    model[sizeof(model) - 1] = '\0';  // 确保以 null 结尾
    			    printf("ledger Model ID: %s\n", model);
    			} else {
    			    printf("ledger No model ID found.\n");

					char msg[256];
					snprintf(msg, sizeof(msg), "终端 id: %s 台账更新失败,没有找到装置型号", update[i].terminal_id);
					send_reply_to_kafka_c(update[i].guid, "2", msg);

					clearIed(ied);//添加失败清空整个ied

					return ;
    			}
				char full_path[128];
        		snprintf(full_path, sizeof(full_path), "/FeProject/dat/%s.xml", model);  // 拼接路径

        		// 打印模型路径
        		printf("ledger icd config file full path: %s\n", full_path);

				//映射文件解析
				Set_xml_nodeinfo_one(ied_usr->dev_type);
				//4-配置映射文件///////////////////////////////////

				//5-报告块初始化//////////////////////////////
				parse_rpt_log_ini_one(ied);
				//5-报告块初始化///////////////////////////////////

				}
				else {
					printf("[PQDIF] skip model xml and report init terminal=%s\n", ied_usr->terminal_id);
				}

				//6-init_rem_dib_table//////////////////////////////
				init_rem_dib_table_one(ied);
				//6-init_rem_dib_table///////////////////////////////////
				//更新数据//////////////////////////////////////////////////////////////////////

				//7启动终端日志
				init_loggers_bydevid(((ied_usr_t*)ied->channel[0].ied->usr_ext)->terminal_id);
				
				//8响应添加成功
				//update[i].guid
				char msg[256];
				snprintf(msg, sizeof(msg), "终端 id: %s 台账修改成功", update[i].terminal_id);
				send_reply_to_kafka_c(update[i].guid, "2", msg);
			}
		}		

		if (!new_in_work_found){	
			printf("modify_updates can not find ied!!!!!!\n");

			//添加mq响应台账添加失败：台账找不到
			//update[i].guid
			char msg[256];
			sprintf(msg, "终端 id: %s 台账修改失败, 无法找到这个终端", update[i].terminal_id);
			send_reply_to_kafka_c(update[i].guid, "2", msg);
		}		
	}
///////////////////////////////////////////////////////////////////////////////delete
	update = ledger_update_xml->delete_updates;			//处理删除台账部分
	update_num = ledger_update_xml->delete_update_num;	//删除的数量
	printf("delete ledger num:%d\n",update_num);

	for (i=0; i<update_num; i++){						//查看所有需要删除的台账
		new_in_work_found = FALSE;						//找到现存台账的标志
		if(update[i].terminal_id != NULL){				//处理非空台账
			printf("delete ledger of %s\n",update[i].terminal_id);

			ied = find_ied_from_terminal_id(update[i].terminal_id);	//通过文件中的终端号找到配置的设备，没找到就不处理
			if (ied){
				printf("find ied by terminal_id, terminal already exsist\n");
				new_in_work_found = TRUE;				//当前运行的台账中找到已有台账，更新它

				//关闭这个终端的所有连接//////////////////////////////////////////////////////////////////////
				for(chnl_no=0 ; chnl_no<ied->chncount; chnl_no++) {
					chnl_usr = (chnl_usr_t*)ied->channel[chnl_no].connect;
					/*if (chnl_usr->m_state!=CHANNEL_CONNECTED){	//跳过没连接的通道，一般一个终端只有一个
						continue;		
					}*///20260416任何状态都关闭连接
					closeChannel(chnl_usr);//关闭更新台账后，如果是删除则不会再连接，关闭连接时这个ied的报告会被清除，报告控制会被注销
				}

				//更新数据//////////////////////////////////////////////////////////////////////
				//3-删除台账内容//////////////////////////////

//调试用
				// 假设要删除 g_node->clients 中的某个 ied，index 为删除的索引
				int index_to_remove = 0; 
				ied_t* ied_find = NULL;
				int			iedno;
				ied_usr_t* ied_usr_find = NULL;		//遍历的ied
				ied_usr = (ied_usr_t*)ied->usr_ext;//要删除的ied
				for (iedno = 0; iedno < g_node->n_clients; iedno++) {
					ied_find = g_node->clients[iedno];
					ied_usr_find = (ied_usr_t*)ied_find->usr_ext;
					if (ied_usr_find && strcmp(ied_usr_find->terminal_id, ied_usr->terminal_id) == 0) {
						index_to_remove = iedno;//找到要删除的ied在g_node中的位置
						break;	//找到退出
					}
				}
								
				// 先清理要删除的 ied 的内容
				ied_t* ied_to_remove = g_node->clients[index_to_remove];//指向g_node对应的内存区

				if(ied_to_remove == ied){//通过终端id找到的ied应该也指向g_node对应的内存区
					printf("this ied is ied_to_remove\n");
				}
//调试用

				//清空logger
				remove_loggers_by_terminal_id(update[i].terminal_id);

				//清空ied的内容
				clearIed(ied_to_remove);

				//3-删除台账内容///////////////////////////////////

				//4-配置映射文件//////////////////////////////
				//映射文件保留，如果再次添加这个ied，如果是相同的映射文件则可以直接加载
				//4-配置映射文件///////////////////////////////////

				//5-报告块//////////////////////////////
				//关闭连接时这个ied的报告已清空。报告控制块保留，因为报告控制块是根据设备类型设置的，如果删除会影响其他同类型的ied。这个ied对应的控制块在清空内容时已清空
				//5-报告块///////////////////////////////////

				//6-init_rem_dib_table//////////////////////////////
				//rem_dib_table的内容将会被保留，如果有新的台账使用这个位置，将会直接覆盖
				//6-init_rem_dib_table///////////////////////////////////
				//更新数据//////////////////////////////////////////////////////////////////////

				//7响应添加成功
				//update[i].guid
				char msg[256];
				snprintf(msg, sizeof(msg), "终端 id: %s 台账删除成功", update[i].terminal_id);
				send_reply_to_kafka_c(update[i].guid, "2", msg);
			}
		}		

		if (!new_in_work_found)  {	
			printf("modify_updates can not find ied!!!!!!\n");

			//添加mq响应台账添加失败：台账找不到
			//update[i].guid
			
			char msg[256];
			sprintf(msg, "终端 id: %s 台账删除失败, 无法找到这个终端", update[i].terminal_id);
			send_reply_to_kafka_c(update[i].guid, "2", msg);

		}		
	}
//////////////////////////////////////////////////////////////////////////////
	if (ledger_update_xml->modify_update_num || ledger_update_xml->new_update_num || ledger_update_xml->delete_update_num){
		create_ledger_log(ledger_update_xml);		//写入文件
		if (g_node_id == PQDIF_DATA_BASE_NODE_ID) {
			if (pqdif_rebuild_config_from_current_ledger() != APR_SUCCESS) {
				printf("[PQDIF][CONFIG] rebuild pqdif-config.xml failed after ledger update\n");
			}
		}
	}

}
//台账更新调试打印函数
// 检查字符串是否为空
// 检查字符串是否为空
int is_empty(const char* str) {
    return (str == NULL || str[0] == '\0');
}

// 打印 monitor 结构体信息
void print_monitor(const monitor* mon) {
    printf("Monitor ID: %s\n", is_empty(mon->monitor_id) ? "N/A" : mon->monitor_id);
    printf("Terminal Code: %s\n", is_empty(mon->terminal_code) ? "N/A" : mon->terminal_code);
    printf("Monitor Name: %s\n", is_empty(mon->monitor_name) ? "N/A" : mon->monitor_name);
    printf("Logical Device Sequence: %s\n", is_empty(mon->logical_device_seq) ? "N/A" : mon->logical_device_seq);
    printf("Voltage Level: %s\n", is_empty(mon->voltage_level) ? "N/A" : mon->voltage_level);
    printf("Terminal Connect: %s\n", is_empty(mon->terminal_connect) ? "N/A" : mon->terminal_connect);
    printf("Timestamp: %s\n", is_empty(mon->timestamp) ? "N/A" : mon->timestamp);
    printf("Status: %s\n", is_empty(mon->status) ? "N/A" : mon->status);
    printf("Log Level: %d\n", mon->log_level);
}

// 打印 terminal 结构体信息
void print_terminal(const terminal* tmnl) {
	printf("GUID: %s\n", is_empty(tmnl->guid) ? "N/A" : tmnl->guid);//lnk20250506
    printf("Terminal ID: %s\n", is_empty(tmnl->terminal_id) ? "N/A" : tmnl->terminal_id);
    printf("Terminal Code: %s\n", is_empty(tmnl->terminal_code) ? "N/A" : tmnl->terminal_code);
    printf("Organization Name: %s\n", is_empty(tmnl->org_name) ? "N/A" : tmnl->org_name);
    printf("Maintenance Name: %s\n", is_empty(tmnl->maint_name) ? "N/A" : tmnl->maint_name);
    printf("Station Name: %s\n", is_empty(tmnl->station_name) ? "N/A" : tmnl->station_name);
    printf("Factory Name: %s\n", is_empty(tmnl->tmnl_factory) ? "N/A" : tmnl->tmnl_factory);
    printf("Terminal Status: %s\n", is_empty(tmnl->tmnl_status) ? "N/A" : tmnl->tmnl_status);
    printf("Device Type: %s\n", is_empty(tmnl->dev_type) ? "N/A" : tmnl->dev_type);
    printf("Device Key: %s\n", is_empty(tmnl->dev_key) ? "N/A" : tmnl->dev_key);
    printf("Device Series: %s\n", is_empty(tmnl->dev_series) ? "N/A" : tmnl->dev_series);
    printf("Com Type: %s\n", is_empty(tmnl->com_type) ? "MMS" : tmnl->com_type);
    printf("Address: %s\n", is_empty(tmnl->addr_str) ? "N/A" : tmnl->addr_str);
    printf("Port: %s\n", is_empty(tmnl->port) ? "N/A" : tmnl->port);
    printf("Timestamp: %s\n", is_empty(tmnl->timestamp) ? "N/A" : tmnl->timestamp);
	printf("Log Level: %d\n", tmnl->log_level);

    // 打印监测点信息，如果监测点字段为空，则打印 N/A
    int i;
    for (i = 0; i < 10 && !is_empty(tmnl->line[i].monitor_id); ++i) {
        printf("  Monitor %d:\n", i + 1);
        print_monitor(&tmnl->line[i]);
    }
}

// 打印 trigger_update_xml_t 结构体信息
void print_trigger_update_xml(const trigger_update_xml_t* trigger_update) {
    printf("Work Updates Count: %d\n", trigger_update->work_update_num);
    printf("New Updates Count: %d\n", trigger_update->new_update_num);
    printf("Delete Updates Count: %d\n", trigger_update->delete_update_num);
    printf("Modify Updates Count: %d\n", trigger_update->modify_update_num);

    printf("\nWork Updates:\n");
    int i;
    for (i = 0; i < trigger_update->work_update_num; ++i) {
        printf("Work Update %d:\n", i + 1);
        print_terminal(&trigger_update->work_updates[i]);
    }

    printf("\nNew Updates:\n");
    for (i = 0; i < trigger_update->new_update_num; ++i) {
        printf("New Update %d:\n", i + 1);
        print_terminal(&trigger_update->new_updates[i]);
    }

    printf("\nDelete Updates:\n");
    for (i = 0; i < trigger_update->delete_update_num; ++i) {
        printf("Delete Update %d:\n", i + 1);
        print_terminal(&trigger_update->delete_updates[i]);
    }

    printf("\nModify Updates:\n");
    for (i = 0; i < trigger_update->modify_update_num; ++i) {
        printf("Modify Update %d:\n", i + 1);
        print_terminal(&trigger_update->modify_updates[i]);
    }
}

void check_ledger_update()//lnk20250113
{
	double now;
	static double last_check_3s_config_time = 0.0;//初始化时间

	now = sGetMsTime();			//当前时间
	if ( fabs(now - last_check_3s_config_time) < 3*1000 ) //wait 3secs  //当前进程任务执行时查看当前时间和上次执行时间间隔，小于3秒不执行，大于等于3秒往下执行
		return;

	// 动态分配内存给 trigger_ledger_update_xml 结构体
    trigger_update_xml_t* trigger_ledger_update_xml = (trigger_update_xml_t*)malloc(sizeof(trigger_update_xml_t));
    if (trigger_ledger_update_xml == NULL) {
        printf("Memory allocation failed!\n");
        return;  // 如果内存分配失败，返回
    }

	//每次都初始化防止重复
	memset(trigger_ledger_update_xml, 0, sizeof(trigger_update_xml_t));

	//printf("check ledger update...trigger_ledger_update_xml:%d\n",trigger_ledger_update_xml->modify_update_num);//减少不必要的打印

	last_check_3s_config_time = now;	//记录本次运行时间

	//判断是否满足执行条件，一个文件就是一个终端，读取文件后删除文件
	if (APR_SUCCESS==parse_ledger_update_xml(trigger_ledger_update_xml)){	//处理台账更新文件,如果有可以更新或者添加台账的
		//调试用
		print_trigger_update_xml(trigger_ledger_update_xml);

		//处理台账更新添加控制标志
		if (RECALL_ONLY_FLAG != 1 || (g_node_id != STAT_DATA_BASE_NODE_ID)) {
			process_ledger_update(trigger_ledger_update_xml);		 	//台账更新	
		}
		else{
			printf("only process recall config, skip ledger update\n");
			DIY_WARNLOG_CODE("process",0,LOG_CODE_SPACE_ALARM,"【WARN】当前配置为仅日志模式,统计数据进程跳过台账更新");
		}
	}

	 // 使用完后释放动态分配的内存
	 free(trigger_ledger_update_xml);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _OS_UNIX_
uint32_t get_freedisk_MB(uint32_t* totalSizeMB) 
{
	struct statfs diskInfo;
	unsigned long long totalBlocks = 0;
	unsigned long long totalSize = 0;
	size_t mbTotalsize = 0;
	size_t freeDisk=0;
	size_t mbFreeDisk = 0;

	statfs("/home/pq", &diskInfo);
	totalBlocks = diskInfo.f_bsize;		
	totalSize = totalBlocks * diskInfo.f_blocks;
	mbTotalsize = totalSize >> 20;
	freeDisk = diskInfo.f_bfree*totalBlocks;		
	mbFreeDisk = freeDisk >> 20;
	printf("/home/pq total=%dMB, free=%dMB \n", mbTotalsize, mbFreeDisk);
	if (totalSizeMB)
		*totalSizeMB = mbTotalsize;
	return mbFreeDisk;
}
#else
uint32_t get_freedisk_MB(uint32_t* totalSizeMB) {
	if (totalSizeMB)
		*totalSizeMB = 1000;
	return 5;
}
#endif

void check_disk_quota()
{
	double now;
	static double last_check_time = -10000000.0; 
	uint32_t freeSizeMB,totalSizeMB;

	if(g_node_id != STAT_DATA_BASE_NODE_ID || g_front_seg_index != 1)//lnk20250527只有稳态进程1监测磁盘
		return;
	now = sGetMsTime();
	if ( fabs(now - last_check_time) < 15*1000 ) //wait 15 secs
		return;

	last_check_time = now; 

	freeSizeMB = get_freedisk_MB(&totalSizeMB);

	//printf("Current user disk free size: %dMB ,total size: %dMB  \n",freeSizeMB,totalSizeMB);
	if (freeSizeMB<g_min_free_size){
		echo_warn2("Current user disk free size: %dMB < %dMB, please check!\n",freeSizeMB,g_min_free_size);
		DIY_WARNLOG_CODE("process",0,LOG_CODE_SPACE_ALARM,"【WARN】前置磁盘检测： 当前磁盘的可用空间为%dMB,小于最小可用空间%dMB,请检查磁盘",freeSizeMB,g_min_free_size);
	}
		
	if ((freeSizeMB/(totalSizeMB/100+1) )<10){
		echo_warn2("Current user disk free size percent < 10%%, free size: %dMB ,total size: %dMB ,please check!\n",
			freeSizeMB,totalSizeMB);
		DIY_WARNLOG_CODE("process",0,LOG_CODE_SPACE_ALARM,"【WARN】前置磁盘检测： 当前磁盘的可用空间的百分比小于10%%,可用空间为%dMB,总空间为%dMB,请检查磁盘",freeSizeMB,totalSizeMB);
	}
		
}

void create_recall_xml() 
{
	//生成待补招xml文件
	if (g_node_id == HIS_DATA_BASE_NODE_ID || g_node_id == NEW_HIS_DATA_BASE_NODE_ID || g_node_id == RECALL_HIS_DATA_BASE_NODE_ID || (g_node_id == RECALL_ALL_DATA_BASE_NODE_ID)) {
		DeletcRecallXml();
		CreateRecallXml();
	}
}

void Delete_recall_Xml(char* 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)) {
		delete_recall_xml(id);

	}
}

void Check_Recall_Config(char *id) //检查补招配置文件Recall.xml
{
	if (g_node_id == HIS_DATA_BASE_NODE_ID || g_node_id == NEW_HIS_DATA_BASE_NODE_ID || g_node_id == RECALL_HIS_DATA_BASE_NODE_ID || (g_node_id == RECALL_ALL_DATA_BASE_NODE_ID)) {

		recall_xml_t recall_xml;
		//memset((char*)&recall_xml, 0, sizeof(recall_xml_t));

		recall_xml_init(&recall_xml); 

		parse_recall_xml(&recall_xml,id);	//解析补招文件，目录下所有属于这个监测点的文件都读取
		process_recall_config(&recall_xml);	//解析的补招数据赋值到全局变量

		recall_xml_free(&recall_xml);
	}
}

void CheckAllConnectedChannel()	
{
	chnl_usr_t			*chnl_usr;	
	static uint32_t		chnl_sequence_no = 0;


	//lnk20250514如果进程启动没有台账则不往下执行，等待台账更新
	if(g_pt61850app->chnl_counts == 0)return;
	//lnk20250514如果进程启动没有台账则不往下执行，等待台账更新


	//一次访问一个终端
	do {
		chnl_usr = g_pt61850app->chnl_usr[chnl_sequence_no];
		chnl_sequence_no = (chnl_sequence_no+1) % g_pt61850app->chnl_counts;

		if (chnl_usr == NULL) continue; // 安全防护lnk20250701

	} while ( (g_onlyIP[0]!=0) && (strcmp(g_onlyIP,chnl_usr->ip_str)!=0) );

	if (chnl_usr == NULL || chnl_usr->chnl == NULL || chnl_usr->chnl->ied == NULL || chnl_usr->chnl->ied->usr_ext == NULL) {
    	printf("chnl_usr or nested member is NULL, skip...\n");
    	return;
	}

	if(chnl_usr->m_state == CHANNEL_CONNECTED)
	{
		if(g_node_id == THREE_SECS_DATA_BASE_NODE_ID) {
			InitLedrsOperTypeForChannel(chnl_usr);//写特殊控制的初始化
			if(DEBUGOPEN)printf("[FILEDIR] enter HandleFileDirReqForChannel");
			HandleFileDirReqForChannel(chnl_usr);//文件目录请求
		}
		if(g_node_id == PQDIF_DATA_BASE_NODE_ID){
			ChannelCheckPQDIFFiles(chnl_usr);
		}
		else{
			ChannelCheckIECReports(chnl_usr);//报告
		}
			
		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)
			|| g_node_id == PQDIF_DATA_BASE_NODE_ID)
			ChannelCheckIECLogs(chnl_usr);//补招文件

		if ( (sGetMsTime() - chnl_usr->m_LastPosRespTime) > 15*1000 ) //wait 15 secs，隔15秒获取一次响应，两次没响应后关闭
		{	
			char** varnames ;
			int varnum;
			
			ST_RET ret = mms_mvla_getnam(chnl_usr->net_info, VMD_SPEC, NULL,MMS_CLASS_DOM,g_pt61850app->mmsOpTimeout,
				&varnames,&varnum,g_pt61850app->tmp_pool);

			chnl_usr->m_LastPosRespTime = sGetMsTime();
			if (ret == SD_SUCCESS) 	{
				
				chnl_usr->m_NegRespTimes = 0;
			}else {
				
				printf("%d--------------\n", chnl_usr->m_NegRespTimes);
				chnl_usr->m_NegRespTimes++;
			}
		}
		if ( chnl_usr->m_NegRespTimes >=2 ) {
			
			closeChannel(chnl_usr);//???特殊情况是否需要 mms_release_connection
			chnl_usr->m_NegRespTimes = 0;
			chnl_usr->m_LastPosRespTime = sGetMsTime();
		}
	}
}

void CheckNextNotConnectedChannel()
{
	static uint32_t		chnl_total_no = 0;
	chnl_usr_t			*chnl_usr;

	//lnk20250514如果进程启动没有台账则不往下执行，等待台账更新
	if(g_pt61850app->chnl_counts == 0)return;
	//lnk20250514如果进程启动没有台账则不往下执行，等待台账更新

	do {
		chnl_usr = g_pt61850app->chnl_usr[chnl_total_no];
		chnl_total_no = (chnl_total_no+1) % g_pt61850app->chnl_counts;

		if (chnl_usr == NULL) continue; // 安全防护lnk20250701

	}   while  ( (g_onlyIP[0]!=0) && (strcmp(g_onlyIP,chnl_usr->ip_str)!=0) );

	if (chnl_usr == NULL || chnl_usr->chnl == NULL || chnl_usr->chnl->ied == NULL || chnl_usr->chnl->ied->usr_ext == NULL) {
    	printf("chnl_usr or nested member is NULL, skip...\n");
    	return;
	}

	//10-11-01 22:03 beijing
	if( ( (chnl_total_no+1)==g_pt61850app->chnl_counts) || (g_onlyIP[0]!=0) ){
		if(g_pt61850app->initNum<255)
			g_pt61850app->initNum++;
	}

	if(chnl_usr->m_state == CHANNEL_CONNECTING)//正在连接
	{

		MVL_REQ_PEND* reqCtrl= chnl_usr->m_reqCtrl ;	
		
		if(  reqCtrl->done == SD_TRUE)
		{

			if(reqCtrl->result == SD_SUCCESS)
			{

				ALL_RCB_INFO *all_rcb_info;

				echo_warn4("\nCHANNEL_CONNECTED %s:%d ,NetInfo= %x chnl_usr= %x \n",
					chnl_usr->ip_str,chnl_usr->chnl->port,chnl_usr->net_info,chnl_usr);

				//mq日志
				((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->lastconnectstat = true;
				((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->has_logged_disconnect  = false;
				DIY_WARNLOG_CODE(((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,1,LOG_CODE_COMM,"【WARN】终端%s - ip端口:%s:%d连接成功", ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,chnl_usr->ip_str,chnl_usr->chnl->port);

				mvl_free_req_ctrl(chnl_usr->m_reqCtrl);
				chnl_usr->m_reqCtrl = NULL;

				chnl_usr->m_state = CHANNEL_CONNECTED;
				chnl_usr->net_info->user_ext = chnl_usr;

				all_rcb_info = (ALL_RCB_INFO *)chk_calloc(1, sizeof (ALL_RCB_INFO));
				all_rcb_info->rpt_typeids = &g_rpt_typeids;

				if (chnl_usr->net_info->user_info != NULL) {
					echo_warn("chnl_usr->net_info->user_info is not NULL\n");
				}
	
				chnl_usr->net_info->user_info = all_rcb_info;
				chnl_usr->chnl->ied->status = STATUS_NORMAL;
				chnl_usr->chnl->status = STATUS_NORMAL;	

				{
					char comm_str[256]; 
					memset(comm_str,0,256);
					apr_snprintf(comm_str,sizeof(comm_str),"%16s:%d\t\tconnected",chnl_usr->ip_str,chnl_usr->chnl->port);
					add_comm_log(comm_str);
					FRONT_MP_NUM++;
				}

				//lnk202411-1添加连接成功的记录
				ied_usr_t* ied_usr = (ied_usr_t*)chnl_usr->chnl->ied->usr_ext;
				apr_time_t t_now = apr_time_now();
				printf("msTime:%ld",t_now);
				connectlog_pgsql(ied_usr->terminal_id,convertMsToDateTimeString(t_now),1);//1成功
				
			}
			else
			{// solaris 9  上 224秒
				
				int secsSince = (int)(sGetMsTime() - chnl_usr->m_StartConnectingTime)/1000 ;
				
				ied_usr_t* ied_usr = (ied_usr_t*)chnl_usr->chnl->ied->usr_ext;

				printf( "reqCtrl->result == FAIL, Since StartConnecting %i sec ,channel IP %s:%d \n",secsSince,chnl_usr->ip_str,chnl_usr->chnl->port);		
				
				//mq日志
				if(true == ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->lastconnectstat){
					((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->lastconnectstat = false;
					((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->has_logged_disconnect = true;
					DIY_WARNLOG_CODE(((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,1,LOG_CODE_COMM,"【WARN】终端%s - ip/端口:%s:%d,从开始连接到目前已经%i秒,连接失败，断开连接！", ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,chnl_usr->ip_str,chnl_usr->chnl->port,secsSince);
				}
				else if(false == ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->lastconnectstat && false == ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->has_logged_disconnect){
					((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->has_logged_disconnect = true;
					DIY_WARNLOG_CODE(((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,1,LOG_CODE_COMM,"【WARN】终端%s - ip/端口:%s:%d,从开始连接到目前已经%i秒,连接失败，断开连接！", ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,chnl_usr->ip_str,chnl_usr->chnl->port,secsSince);
				}

				mvl_free_req_ctrl(chnl_usr->m_reqCtrl);
				chnl_usr->m_reqCtrl = NULL;
				chnl_usr->net_info->rem_vmd = NULL;
				chnl_usr->m_state = CHANNEL_DISCONNECTED;
				chnl_usr->m_ClosedMsTime = sGetMsTime();	
				
				//断联成功
				
				apr_time_t t_now = apr_time_now();
				connectlog_pgsql(ied_usr->terminal_id,convertMsToDateTimeString(t_now),0);
				//更新状态
				chnl_usr->chnl->ied->status = STATUS_OVERTIME;
				chnl_usr->chnl->status = STATUS_OVERTIME;	
			}
		}
		else 
		{

			if ( (sGetMsTime() - chnl_usr->m_StartConnectingTime) > 300*1000 ) //300*1000 ) //wait 300 secs
			{
				ied_usr_t* ied_usr = (ied_usr_t*)chnl_usr->chnl->ied->usr_ext;
	
				echo_warn2( "reqCtrl->done未完成,but time over 300 secs, close channel IP %s,NetInfo= %x ",chnl_usr->ip_str,chnl_usr->net_info);	
				
				//mq日志
				if(true == ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->lastconnectstat){
					((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->lastconnectstat = false;
					((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->has_logged_disconnect = true;
					DIY_WARNLOG_CODE(((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,1,LOG_CODE_COMM,"【WARN】终端%s - ip端口:%s:%d,从开始连接到目前已经300秒,未能获取连接响应，断开连接！", ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,chnl_usr->ip_str,chnl_usr->chnl->port);
				}
				else if(false == ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->lastconnectstat && false == ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->has_logged_disconnect){
					((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->has_logged_disconnect = true;
					DIY_WARNLOG_CODE(((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,1,LOG_CODE_COMM,"【WARN】终端%s - ip端口:%s:%d,从开始连接到目前已经300秒,未能获取连接响应，断开连接！", ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,chnl_usr->ip_str,chnl_usr->chnl->port);
				}
				

				if (chnl_usr->net_info->req_pend_list) {
					echo_warn("reqCtrl->done未完成,but time over 300 secs!!!!!!!!\n");
					mvl_free_req_ctrl(chnl_usr->m_reqCtrl);
					chnl_usr->m_reqCtrl = NULL;
				}
				mms_release_connection(chnl_usr->net_info);					
				
				chnl_usr->net_info->rem_vmd = NULL;
				chnl_usr->m_state = CHANNEL_DISCONNECTED;
				chnl_usr->m_ClosedMsTime = sGetMsTime();

				//断联
				
				apr_time_t t_now = apr_time_now();
				connectlog_pgsql(ied_usr->terminal_id,convertMsToDateTimeString(t_now),0);
				//更新状态
				chnl_usr->chnl->ied->status = STATUS_OVERTIME;
				chnl_usr->chnl->status = STATUS_OVERTIME;

			}			
		}
	} 

	else if(chnl_usr->m_state == CHANNEL_DISCONNECTED)
	{

		if ( (sGetMsTime() - chnl_usr->m_ClosedMsTime) > NEXT_CONNECT_TIME ) //wait 10 secs
		{

			ST_RET ret;
			ST_CHAR serverARName[32];
			ied_usr_t *ied_usr = (ied_usr_t*)chnl_usr->chnl->ied->usr_ext;
			apr_snprintf(serverARName,sizeof(serverARName),"%s:%d",chnl_usr->ip_str,chnl_usr->chnl->port);
			if (chnl_usr->chnl->ied->cpucount != NULL && chnl_usr->chnl->ied->cpucount > 0 && ied_usr->dev_flag == ENABLE) {//2023-09-26 czy 如果line count<0 不需要连接//lnk20250121如果终端无效则不连接
				
				//mq日志
				//DIY_WARNLOG_CODE(((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,1,LOG_CODE_COMM,"【WARN】重新连接终端%s - ip端口:%s:%d", ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,chnl_usr->ip_str,chnl_usr->chnl->port);
				
				ret = mms_connectToServer(ied_usr->dev_key, ied_usr->dev_series, serverARName, &(chnl_usr->net_info), &(chnl_usr->m_reqCtrl));

				if (ret == SD_SUCCESS)
				{

					echo_msg3("mms_connectToServer IP %s:%d  ,NetInfo= %x \n", chnl_usr->ip_str, chnl_usr->chnl->port, chnl_usr->net_info);
					chnl_usr->m_state = CHANNEL_CONNECTING;
					chnl_usr->m_StartConnectingTime = sGetMsTime();

					//mq日志
					//DIY_WARNLOG_CODE(((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,1,LOG_CODE_COMM,"【WARN】正在重新连接终端%s - ip端口:%s:%d - 识别码/秘钥:%s/%s", ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,chnl_usr->ip_str,chnl_usr->chnl->port,((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->dev_series,((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->dev_key);


				}
				else
				{

					chnl_usr->m_ClosedMsTime = sGetMsTime();

					echo_warn3("FAILED:  mms_connectToServer IP %s:%d  ,NetInfo= %x \n", chnl_usr->ip_str, chnl_usr->chnl->port, chnl_usr->net_info);

					//mq日志
					//DIY_WARNLOG_CODE(((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,1,LOG_CODE_COMM,"【WARN】重新连接终端%s - ip端口:%s:%d 失败！", ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,chnl_usr->ip_str,chnl_usr->chnl->port);
				}
			}
			
		}			
	}
	else if(chnl_usr->m_state == CHANNEL_DISCONNECTING) //need check timeout?会不会永远停留在这里
	{

		MVL_REQ_PEND* reqCtrl= chnl_usr->m_reqCtrl ;				
		if(  reqCtrl->done == SD_TRUE)
		{				

			echo_warn3( "CHANNEL_DISCONNECTING done %s:%d,NetInfo= %x ",chnl_usr->ip_str,chnl_usr->chnl->port,chnl_usr->net_info);	
			mvl_free_req_ctrl(chnl_usr->m_reqCtrl);
			if(chnl_usr->net_info) 
				chnl_usr->net_info->user_ext = NULL;

			chnl_usr->m_reqCtrl = NULL;
			chnl_usr->net_info = NULL;
			chnl_usr->m_state = CHANNEL_DISCONNECTED;
			chnl_usr->m_ClosedMsTime = sGetMsTime();
			//断联成功
			ied_usr_t* ied_usr = (ied_usr_t*)chnl_usr->chnl->ied->usr_ext;
			apr_time_t t_now = apr_time_now();
			connectlog_pgsql(ied_usr->terminal_id,convertMsToDateTimeString(t_now),0);
			//更新状态
			chnl_usr->chnl->ied->status = STATUS_BREAKOFF;
			chnl_usr->chnl->status = STATUS_BREAKOFF;

			//mq日志
			DIY_WARNLOG_CODE(((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,1,LOG_CODE_COMM,"【WARN】终端%s - ip端口:%s:%d 断连完成，关闭连接通道", ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,chnl_usr->ip_str,chnl_usr->chnl->port);

		}	
		else 
		{

			echo_warn2( "CHANNEL_DISCONNECTING waiting ... %s,NetInfo= %x ",chnl_usr->ip_str,chnl_usr->net_info);	

			if ( (sGetMsTime() - chnl_usr->m_StartDisconnectingTime) > 30*1000 ) // //wait 30 secs  ?????
			{
	
				echo_warn2( "CHANNEL_DISCONNECTING reqCtrl->done未完成,but time over 180 secs, close channel IP %s,NetInfo= %x ",chnl_usr->ip_str,chnl_usr->net_info);								
				mvl_free_req_ctrl(chnl_usr->m_reqCtrl);	
				chnl_usr->net_info->user_ext = NULL;

				mms_release_connection(chnl_usr->net_info);	
				chnl_usr->net_info->rem_vmd = NULL;
				chnl_usr->m_reqCtrl = NULL;
				chnl_usr->net_info = NULL;
				chnl_usr->m_state = CHANNEL_DISCONNECTED;
				chnl_usr->m_ClosedMsTime = sGetMsTime();
				//超时断联成功
				ied_usr_t* ied_usr = (ied_usr_t*)chnl_usr->chnl->ied->usr_ext;
				apr_time_t t_now = apr_time_now();
				connectlog_pgsql(ied_usr->terminal_id,convertMsToDateTimeString(t_now),0);
				//更新状态
				chnl_usr->chnl->ied->status = STATUS_BREAKOFF;
				chnl_usr->chnl->status = STATUS_BREAKOFF;

				//mq日志
				DIY_WARNLOG_CODE(((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,1,LOG_CODE_COMM,"【WARN】终端%s - ip端口:%s:%d 断连未完成,但是已经超时180秒,关闭连接通道", ((ied_usr_t*)(chnl_usr->chnl->ied->usr_ext))->terminal_id,chnl_usr->ip_str,chnl_usr->chnl->port);

			}			
		}						
	}
}

//////////////////////*********相关控制操作**********////////////////////////////////////////////////
static ST_RET Read_Named_Var(LD_info_t	*LD_info,chnl_usr_t *chnl_usr,char* VarName,ST_INT type_id, ST_VOID *dataDest, ST_INT timeOut)
{
	ST_RET ret= SD_FAILURE;
	ST_CHAR *domName = LD_info->LD_name;
	int Need_Destroy_Typeid = FALSE;
	assert(chnl_usr && LD_info);
	if (type_id==-1) {
		Need_Destroy_Typeid = TRUE;
		type_id= mms_var_type_id_create (chnl_usr->net_info,DOM_SPEC, domName, VarName,timeOut);		  
		if (type_id==-1) ret = SD_FAILURE; else ret = SD_SUCCESS; 
		if (ret == SD_FAILURE) return SD_FAILURE;	
	} 		
	ret=mms_named_var_read (chnl_usr->net_info,VarName,DOM_SPEC, domName,type_id, dataDest, timeOut);	
	if (Need_Destroy_Typeid)
		if (type_id!=-1) 	mvl_type_id_destroy(type_id);	
	return ret;
}

static ST_RET Write_Named_Var(LD_info_t	*LD_info,chnl_usr_t *chnl_usr,char* VarName,ST_INT type_id, ST_VOID *dataDest, ST_INT timeOut)
{
	ST_RET ret= SD_FAILURE;
	ST_CHAR *domName = LD_info->LD_name;
	int Need_Destroy_Typeid = FALSE;
	assert(chnl_usr && LD_info);
	if (type_id==-1) {
		Need_Destroy_Typeid = TRUE;
		type_id= mms_var_type_id_create (chnl_usr->net_info,DOM_SPEC, domName, VarName,timeOut);		  
		if (type_id==-1) ret = SD_FAILURE; else ret = SD_SUCCESS; 
		if (ret == SD_FAILURE) return SD_FAILURE;	
	} 		
	ret=mms_named_var_write(chnl_usr->net_info,VarName,DOM_SPEC, domName,type_id, dataDest, timeOut);	
	if (Need_Destroy_Typeid)
		if (type_id!=-1) 	mvl_type_id_destroy(type_id);	
	return ret;
}

/*
<EnumType id="ctlModelsEnum">
<EnumVal ord="0">status-only</EnumVal>
<EnumVal ord="1">direct-with-normal-security</EnumVal>
<EnumVal ord="2">sbo-with-normal-security</EnumVal>
<EnumVal ord="3">direct-with-enhanced-security</EnumVal>
<EnumVal ord="4">sbo-with-enhanced-security</EnumVal>
</EnumType>
*/	
  

#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/ 下是否已有同名文件，避免重复下载。
 */
/* Current rule:
 * - read /PQDIF/ periodically per terminal;
 * - parse the .pqd file's own yyyyMMdd_HHmm_period-hours tokens;
 * - save valid files into /FeProject/data/pqdif/<ip>/;
 * - prevent repeated downloads with /FeProject/data/pqdif/.state/<ip>.downloaded.
 */
#define PQDIF_REMOTE_DIR             "/PQDIF/"
#define PQDIF_LOCAL_DIR              "/FeProject/data/pqdif"
#define PQDIF_TMP_DIR                "/FeProject/data/pqdif/.tmp"
#define PQDIF_STATE_DIR              "/FeProject/data/pqdif/.state"
#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

typedef struct {
	int		cpu_digit;
	int		period_hours;
	time_t	start_time;
	time_t	end_time;
} pqdif_file_interval_t;

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_use_interval_end_time(const char* dev_type)
{
	return (dev_type != NULL && strcmp(dev_type, "PS_NET_PQDIF") == 0);
}

/* Parse names such as xxx_LD1_20260720_0000_24.pqd.
 * The token before yyyyMMdd supplies the CPU digit; the last numeric token is
 * the file period in hours, so 1h/2h/24h files share the same matcher.
 */
static int pqdif_is_digit_token(const char* str, int expect_len)
{
	int i;

	if (str == NULL)
		return FALSE;

	for (i = 0; str[i] != '\0'; ++i) {
		if (!isdigit((unsigned char)str[i]))
			return FALSE;
	}

	return (i == expect_len);
}

static int pqdif_parse_datetime(const char* date_token, const char* time_token, time_t* out_time)
{
	struct tm tm_value;
	int year;
	int month;
	int day;
	int hour;
	int min;

	if (!pqdif_is_digit_token(date_token, 8) || !pqdif_is_digit_token(time_token, 4) || out_time == NULL)
		return FALSE;

	year = (date_token[0] - '0') * 1000 + (date_token[1] - '0') * 100 +
		(date_token[2] - '0') * 10 + (date_token[3] - '0');
	month = (date_token[4] - '0') * 10 + (date_token[5] - '0');
	day = (date_token[6] - '0') * 10 + (date_token[7] - '0');
	hour = (time_token[0] - '0') * 10 + (time_token[1] - '0');
	min = (time_token[2] - '0') * 10 + (time_token[3] - '0');

	if (month < 1 || month > 12 || day < 1 || day > 31 || hour < 0 || hour > 23 || min < 0 || min > 59)
		return FALSE;

	memset(&tm_value, 0, sizeof(tm_value));
	tm_value.tm_year = year - 1900;
	tm_value.tm_mon = month - 1;
	tm_value.tm_mday = day;
	tm_value.tm_hour = hour;
	tm_value.tm_min = min;
	tm_value.tm_sec = 0;
	tm_value.tm_isdst = -1;

	*out_time = mktime(&tm_value);
	return (*out_time != (time_t)-1);
}

static int pqdif_parse_file_interval(const char* filename, const char* dev_type, pqdif_file_interval_t* out_info)
{
	char name_buf[256];
	char* tokens[32];
	char* token;
	const char* base;
	size_t len;
	int token_count = 0;
	int i;

	if (out_info == NULL)
		return FALSE;

	memset(out_info, 0, sizeof(*out_info));

	if (!pqdif_has_pqd_suffix(filename))
		return FALSE;

	base = pqdif_basename(filename);
	len = strlen(base);
	if (len <= 4 || len >= sizeof(name_buf))
		return FALSE;

	snprintf(name_buf, sizeof(name_buf), "%s", base);
	name_buf[len - 4] = '\0';

	token = strtok(name_buf, "_");
	while (token != NULL && token_count < (int)(sizeof(tokens) / sizeof(tokens[0]))) {
		tokens[token_count++] = token;
		token = strtok(NULL, "_");
	}

	for (i = 1; i + 2 < token_count; ++i) {
		const char* cpu_token = tokens[i - 1];
		const char* date_token = tokens[i];
		const char* time_token = tokens[i + 1];
		const char* period_token = tokens[i + 2];
		size_t cpu_len = strlen(cpu_token);
		time_t file_time;
		int period_hours;

		if (!pqdif_is_digit_token(date_token, 8) || !pqdif_is_digit_token(time_token, 4))
			continue;

		if (cpu_len == 0 || !isdigit((unsigned char)cpu_token[cpu_len - 1]))
			continue;

		for (len = 0; period_token[len] != '\0'; ++len) {
			if (!isdigit((unsigned char)period_token[len]))
				break;
		}
		if (period_token[len] != '\0')
			continue;

		period_hours = atoi(period_token);
		if (period_hours <= 0 || period_hours > 24 * 366)
			continue;

		if (!pqdif_parse_datetime(date_token, time_token, &file_time))
			continue;

		out_info->cpu_digit = cpu_token[cpu_len - 1] - '0';
		out_info->period_hours = period_hours;

		if (pqdif_use_interval_end_time(dev_type)) {
			out_info->end_time = file_time;
			out_info->start_time = file_time - period_hours * 3600;
		}
		else {
			out_info->start_time = file_time;
			out_info->end_time = file_time + period_hours * 3600;
		}

		return TRUE;
	}

	return FALSE;
}

static int pqdif_cpu_matches(int file_cpu_digit, int cpu_no)
{
	if (cpu_no <= 0)
		return FALSE;

	return (file_cpu_digit == (cpu_no % 10));
}

static time_t pqdif_align_interval_start(long long sec)
{
	time_t hour_floor;
	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)
{
	pqdif_file_interval_t file_info;

	if (!pqdif_parse_file_interval(filename, dev_type, &file_info))
		return FALSE;

	if (!pqdif_cpu_matches(file_info.cpu_digit, cpu_no))
		return FALSE;

	return (file_info.start_time <= interval_start && interval_start < file_info.end_time);
#if 0

	if (!pqdif_has_pqd_suffix(filename))
		return FALSE;

	if (cpu_no <= 0)
		return FALSE;

	/* C# 原逻辑按设备类型决定文件名时间：
	 *   - 普通设备用周期开始时间；
	 *   - PS_NET_PQDIF 用周期结束时间。
	 * 这里保持同样规则，避免把普通设备下一周期的开始文件误认为本周期结束文件。
	 */
	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, name_time, time_match, sizeof(time_match));
	return strstr(filename, time_match) != NULL;
#endif
}

static int pqdif_match_recent_interval(const char* filename, int cpu_no, const char* dev_type)
{
	time_t now_time;
	pqdif_file_interval_t file_info;

	now_time = time(NULL);
	if (!pqdif_parse_file_interval(filename, dev_type, &file_info))
		return FALSE;

	if (!pqdif_cpu_matches(file_info.cpu_digit, cpu_no))
		return FALSE;

	return (file_info.end_time <= now_time);
}

static int pqdif_match_auto_candidate(const char* filename, int cpu_no, const char* dev_type,
	time_t now_time, pqdif_file_interval_t* out_info)
{
	pqdif_file_interval_t file_info;

	if (!pqdif_parse_file_interval(filename, dev_type, &file_info))
		return FALSE;

	if (!pqdif_cpu_matches(file_info.cpu_digit, cpu_no))
		return FALSE;

	if (file_info.end_time > now_time)
		return FALSE;

	if (out_info != NULL)
		*out_info = file_info;

	return TRUE;
}

static int pqdif_is_latest_auto_file(char **filenames, int filenum, const char* candidate_name,
	int cpu_no, const char* dev_type, time_t now_time)
{
	pqdif_file_interval_t best_info;
	const char *best_name = NULL;
	int have_best = FALSE;
	int i;

	if (!pqdif_match_auto_candidate(candidate_name, cpu_no, dev_type, now_time, NULL))
		return FALSE;

	memset(&best_info, 0, sizeof(best_info));
	for (i = 0; i < filenum; i++) {
		const char *base_name;
		pqdif_file_interval_t file_info;

		if (filenames[i] == NULL)
			continue;

		base_name = pqdif_basename(filenames[i]);
		if (base_name[0] == '\0')
			continue;

		if (!pqdif_match_auto_candidate(base_name, cpu_no, dev_type, now_time, &file_info))
			continue;

		if (!have_best ||
			file_info.end_time > best_info.end_time ||
			(file_info.end_time == best_info.end_time && file_info.start_time > best_info.start_time) ||
			(file_info.end_time == best_info.end_time && file_info.start_time == best_info.start_time &&
				best_name != NULL && strcmp(base_name, best_name) > 0)) {
			have_best = TRUE;
			best_info = file_info;
			best_name = base_name;
		}
	}

	return (have_best && best_name != NULL && strcmp(candidate_name, best_name) == 0);
}

static int pqdif_match_recall_range(const char* filename, int cpu_no, long long start_sec, long long end_sec,
	const char* dev_type, pqdif_file_interval_t* out_info)
{
	pqdif_file_interval_t file_info;

	if (!pqdif_parse_file_interval(filename, dev_type, &file_info))
		return FALSE;

	if (!pqdif_cpu_matches(file_info.cpu_digit, cpu_no))
		return FALSE;

	if ((long long)file_info.end_time <= start_sec || (long long)file_info.start_time >= end_sec)
		return FALSE;

	if (out_info != NULL)
		*out_info = file_info;

	return TRUE;
}

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;

	if (mkdir(PQDIF_TMP_DIR, 0777) != 0 && errno != EEXIST)
		return SD_FAILURE;

	if (mkdir(PQDIF_STATE_DIR, 0777) != 0 && errno != EEXIST)
		return SD_FAILURE;
#endif
	return SD_SUCCESS;
}

static void pqdif_make_ip_dir_name(const char* ip_str, char* out, size_t out_len)
{
	size_t i;
	size_t j = 0;

	if (out == NULL || out_len == 0)
		return;

	if (ip_str == NULL || ip_str[0] == '\0') {
		snprintf(out, out_len, "unknown");
		return;
	}

	for (i = 0; ip_str[i] != '\0' && j + 1 < out_len; ++i) {
		unsigned char ch = (unsigned char)ip_str[i];

		if (isalnum(ch) || ch == '.' || ch == '-' || ch == '_')
			out[j++] = (char)ch;
		else
			out[j++] = '_';
	}
	out[j] = '\0';

	if (out[0] == '\0')
		snprintf(out, out_len, "unknown");
}

static void pqdif_build_device_dirs(chnl_usr_t *chnl_usr, char* local_dir, size_t local_dir_len, char* tmp_dir, size_t tmp_dir_len)
{
	char ip_dir[128];

	pqdif_make_ip_dir_name(chnl_usr ? chnl_usr->ip_str : NULL, ip_dir, sizeof(ip_dir));

	if (local_dir != NULL && local_dir_len > 0)
		snprintf(local_dir, local_dir_len, "%s/%s", PQDIF_LOCAL_DIR, ip_dir);

	if (tmp_dir != NULL && tmp_dir_len > 0)
		snprintf(tmp_dir, tmp_dir_len, "%s/%s", PQDIF_TMP_DIR, ip_dir);
}

static int pqdif_ensure_device_dirs(chnl_usr_t *chnl_usr, char* local_dir, size_t local_dir_len, char* tmp_dir, size_t tmp_dir_len)
{
	pqdif_build_device_dirs(chnl_usr, local_dir, local_dir_len, tmp_dir, tmp_dir_len);

	if (pqdif_ensure_local_dir() != SD_SUCCESS)
		return SD_FAILURE;

#ifdef _OS_UNIX_
	if (local_dir == NULL || tmp_dir == NULL)
		return SD_FAILURE;

	if (mkdir(local_dir, 0777) != 0 && errno != EEXIST)
		return SD_FAILURE;

	if (mkdir(tmp_dir, 0777) != 0 && errno != EEXIST)
		return SD_FAILURE;
#endif
	return SD_SUCCESS;
}

static int pqdif_copy_file(const char* src_file, const char* dst_file)
{
	FILE *src;
	FILE *dst;
	char buf[8192];
	size_t nread;
	int ret = SD_SUCCESS;

	if (src_file == NULL || dst_file == NULL || src_file[0] == '\0' || dst_file[0] == '\0')
		return SD_FAILURE;

	src = fopen(src_file, "rb");
	if (src == NULL)
		return SD_FAILURE;

	dst = fopen(dst_file, "wb");
	if (dst == NULL) {
		fclose(src);
		return SD_FAILURE;
	}

	while ((nread = fread(buf, 1, sizeof(buf), src)) > 0) {
		if (fwrite(buf, 1, nread, dst) != nread) {
			ret = SD_FAILURE;
			break;
		}
	}

	if (ferror(src))
		ret = SD_FAILURE;

	if (fclose(dst) != 0)
		ret = SD_FAILURE;

	fclose(src);

	if (ret != SD_SUCCESS)
		remove(dst_file);

	return ret;
}

static long pqdif_get_local_file_size(const char* filename)
{
	FILE *fp;
	long size;

	if (filename == NULL || filename[0] == '\0')
		return -1;

	fp = fopen(filename, "rb");
	if (fp == NULL)
		return -1;

	if (fseek(fp, 0, SEEK_END) != 0) {
		fclose(fp);
		return -1;
	}

	size = ftell(fp);
	fclose(fp);
	return size;
}

/* Successful downloads are tracked outside the final data directory.  The PQDIF
 * reader may delete /FeProject/data/pqdif/<ip>/ files after processing, so the
 * final directory must not be used as the duplicate-download ledger.
 */
static void pqdif_build_state_file(chnl_usr_t *chnl_usr, char* state_file, size_t state_file_len)
{
	char ip_dir[128];

	if (state_file == NULL || state_file_len == 0)
		return;

	pqdif_make_ip_dir_name(chnl_usr ? chnl_usr->ip_str : NULL, ip_dir, sizeof(ip_dir));
	snprintf(state_file, state_file_len, "%s/%s.downloaded", PQDIF_STATE_DIR, ip_dir);
}

static void pqdif_strip_line_end(char* line)
{
	size_t len;

	if (line == NULL)
		return;

	len = strlen(line);
	while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n' ||
		line[len - 1] == ' ' || line[len - 1] == '\t')) {
		line[len - 1] = '\0';
		len--;
	}
}

static int pqdif_download_record_exists(chnl_usr_t *chnl_usr, const char* filename)
{
	char state_file[512];
	char line[512];
	FILE *fp;
	const char* base = pqdif_basename(filename);

	if (base[0] == '\0')
		return FALSE;

	pqdif_build_state_file(chnl_usr, state_file, sizeof(state_file));
	fp = fopen(state_file, "r");
	if (fp == NULL)
		return FALSE;

	while (fgets(line, sizeof(line), fp) != NULL) {
		pqdif_strip_line_end(line);
		if (strcmp(line, base) == 0) {
			fclose(fp);
			return TRUE;
		}
	}

	fclose(fp);
	return FALSE;
}

static int pqdif_record_download_success(chnl_usr_t *chnl_usr, const char* filename)
{
	char state_file[512];
	FILE *fp;
	const char* base = pqdif_basename(filename);
	int ret = SD_SUCCESS;

	if (base[0] == '\0')
		return SD_FAILURE;

	if (pqdif_download_record_exists(chnl_usr, base))
		return SD_SUCCESS;

	if (pqdif_ensure_local_dir() != SD_SUCCESS)
		return SD_FAILURE;

	pqdif_build_state_file(chnl_usr, state_file, sizeof(state_file));
	fp = fopen(state_file, "a");
	if (fp == NULL)
		return SD_FAILURE;

	if (fprintf(fp, "%s\n", base) < 0)
		ret = SD_FAILURE;

	if (fflush(fp) != 0)
		ret = SD_FAILURE;

	if (fclose(fp) != 0)
		ret = SD_FAILURE;

	return ret;
}

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 tmp_file_fullname[512];
	char final_file_fullname[512];
	char local_dir[512];
	char tmp_dir[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_device_dirs(chnl_usr, local_dir, sizeof(local_dir), tmp_dir, sizeof(tmp_dir)) != SD_SUCCESS) {
		echo_warn2("[PQDIF] create local dir failed: local=%s tmp=%s\n", local_dir, tmp_dir);
		return SD_FAILURE;
	}

	memset(tmp_file_fullname,0,sizeof(tmp_file_fullname));
	memset(final_file_fullname,0,sizeof(final_file_fullname));
	memset(rem_file_fullname,0,sizeof(rem_file_fullname));
	apr_snprintf(tmp_file_fullname,sizeof(tmp_file_fullname),"%s/%s",tmp_dir,only_filename);
	apr_snprintf(final_file_fullname,sizeof(final_file_fullname),"%s/%s",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);

	/* Keep the same safety flow as the C# reader:
	 * download to a temporary backup path first, remove failed/empty files,
	 * copy to the final PQDIF directory only after validation, then remove temp.
	 */
	remove(tmp_file_fullname);

	printf("[PQDIF] mms_getFile tmp=%s final=%s remote=%s\n",
		tmp_file_fullname, final_file_fullname, rem_file_fullname);

	ret = mms_getFile(chnl_usr->net_info,tmp_file_fullname,rem_file_fullname,g_pt61850app->mmsOpTimeout);
	if (ret != SD_SUCCESS) {
		remove(tmp_file_fullname);
		echo_warn3("[PQDIF] mms_getFile failed IP=%s tmp=%s remote=%s\n",
			chnl_usr->ip_str, tmp_file_fullname, rem_file_fullname);
		return ret;
	}

	if (pqdif_get_local_file_size(tmp_file_fullname) <= 0) {
		remove(tmp_file_fullname);
		echo_warn2("[PQDIF] file is empty or invalid: %s from %s\n",
			tmp_file_fullname, rem_file_fullname);
		return SD_FAILURE;
	}

	if (pqdif_copy_file(tmp_file_fullname, final_file_fullname) != SD_SUCCESS) {
		remove(tmp_file_fullname);
		echo_warn2("[PQDIF] copy validated file failed: %s -> %s\n",
			tmp_file_fullname, final_file_fullname);
		return SD_FAILURE;
	}

	if (pqdif_record_download_success(chnl_usr, only_filename) != SD_SUCCESS) {
		remove(tmp_file_fullname);
		remove(final_file_fullname);
		echo_warn2("[PQDIF] write download record failed, remove copied file: IP=%s file=%s\n",
			chnl_usr->ip_str, only_filename);
		return SD_FAILURE;
	}

	remove(tmp_file_fullname);

	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;
	int			matched = 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 ||
		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;
	}

	printf("[PQDIF][RECALL] begin mp_id=%s cpuno=%d range=%lld~%lld files=%d dev_type=%s\n",
		LD_info->mp_id, LD_info->cpuno, start_sec, end_sec, filenum, ied_usr->dev_type);

	for (file_idx = 0; file_idx < filenum; file_idx++) {
		const char *base_name;
		char remote_filename[256];
		char only_filename_ret[256];
		pqdif_file_interval_t file_info;
		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_recall_range(base_name, LD_info->cpuno, start_sec, end_sec,
			ied_usr->dev_type, &file_info))
			continue;

		matched++;
		if (pqdif_download_record_exists(chnl_usr, base_name)) {
			existed++;
			printf("[PQDIF][RECALL] download record exists, skip mp_id=%s file=%s file_range=%lld~%lld period=%d\n",
				LD_info->mp_id, base_name, (long long)file_info.start_time,
				(long long)file_info.end_time, file_info.period_hours);
			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') {
			failed++;
			continue;
		}

		ret_get = pqdif_get_file_from_device(chnl_usr, remote_filename, only_filename_ret, sizeof(only_filename_ret));
		if (ret_get == SD_SUCCESS) {
			char local_dir[512];

			fetched++;
			chnl_usr->m_LastPosRespTime = sGetMsTime();
			pqdif_build_device_dirs(chnl_usr, local_dir, sizeof(local_dir), NULL, 0);
			printf("[PQDIF][RECALL] fetch success mp_id=%s file_range=%lld~%lld period=%d remote=%s local=%s/%s\n",
				LD_info->mp_id, (long long)file_info.start_time, (long long)file_info.end_time,
				file_info.period_hours, remote_filename, 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);
		}
	}

	if (matched == 0) {
		missing++;
		printf("[PQDIF][RECALL] no matched file mp_id=%s range=%lld~%lld dev_type=%s\n",
			LD_info->mp_id, start_sec, end_sec, ied_usr->dev_type);
	}

	printf("[PQDIF][RECALL] finish mp_id=%s matched=%d fetched=%d existed=%d missing=%d failed=%d\n",
		LD_info->mp_id, matched, fetched, existed, missing, failed);

	if (matched == 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)
{
	int			ret ;

	char		loc_filename[128], loc_file_fullname[256], rem_file_fullname[256],only_filename_str[256];

	char		*only_filename,*the_full_file;
  
	memset(loc_filename,0,sizeof(loc_filename));
	memset(only_filename_str,0,sizeof(only_filename_str) );
  
	strcat(only_filename_str,rem_filename);
	only_filename = only_filename_str;
	the_full_file = only_filename_str;
	while ( *the_full_file != 0 )	{
		if (*the_full_file == '/')
			only_filename=the_full_file+1;
		the_full_file++;
	}

	memset(rem_file_fullname,0,sizeof(rem_file_fullname) );
	if (strstr(rem_filename,"/")!= NULL) {
		strcat(rem_file_fullname,rem_filename); 
	}
	else {
		strcat(rem_file_fullname,g_pt61850app->accPath);
		strcat(rem_file_fullname,rem_filename); 
	}

	memset(loc_file_fullname,0,sizeof(loc_file_fullname) );
	apr_snprintf(loc_file_fullname,sizeof(loc_file_fullname),"../comtrade/%s",only_filename);
	apr_snprintf(only_filename_ret,256,"%s",only_filename);
	printf("\n >>>>>>>>>****** Get wave file :	%s  from	%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)	{ 		
		 
	}
	else {
		echo_warn3(">>>>>**** Error!!!  %s mms_getFile wave file :	%s  from	%s\n",chnl_usr->ip_str ,loc_file_fullname,rem_file_fullname);
	}
	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;
	time_t			now_time;
	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;
	now_time = time(NULL);

	/* 本函数只做 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_is_latest_auto_file(filenames, filenum, base_name,
				LD_info->cpuno, ied_usr->dev_type, now_time))
				continue;

			matched++;

			if (pqdif_download_record_exists(chnl_usr, base_name)) {
				printf("[PQDIF] latest file already downloaded, skip mp_id=%s file=%s\n",
					LD_info->mp_id, 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')
				continue;

			ret_get = pqdif_get_file_from_device(chnl_usr, remote_filename, only_filename_ret, sizeof(only_filename_ret));
			if (ret_get == SD_SUCCESS) {
				char local_dir[512];

				fetched++;
				chnl_usr->m_LastPosRespTime = sGetMsTime();
				pqdif_build_device_dirs(chnl_usr, local_dir, sizeof(local_dir), NULL, 0);
				printf("[PQDIF] fetch success mp_id=%s name=%s remote=%s local=%s/%s\n",
					LD_info->mp_id, LD_info->name, remote_filename, 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;
			}
			break;
		}
	}

	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)
{
	int			i; 

	for (i=0;i<256;i++) {
		if (LD_info->FltNum[i]<=0) {
			LD_info->FltNum[i] = FltNum;
			break;
		}
	}
	return APR_SUCCESS;
}

//判断暂态记录非空，lnk20250818
int ld_has_qvvr_nonempty(const LD_info_t* info)
{
    if (!info) return 0;

    /* 规则1：索引计数 > 0 直接认为非空 */
    if (info->qvvr_idx > 0) return 1;

    /* 规则2：扫描数组，任意一项出现被使用/有时间/有名称/有数值 即认为非空 */
    int i;
    for (i = 0; i < QVVR_NUM; ++i) {
        const QVVR_t* it = &info->qvvr[i];

        if (it->used_status != 0 && it->QVVR_time != 0 && it->QVVR_PerTime != 0)
        {
            return 1;
        }
    }

    /* 都没有则为空 */
    return 0;
}

apr_status_t call_cn_wavelist(LD_info_t  *LD_info )
{
	int			ret ;
	int			chnl_no;
	chnl_usr_t	*chnl_usr = NULL;
	char		**filenames;
	int			filenum ;
	int			i;
	char		file_match_str[65];
	char		*ldstr;
	ied_t		*ied = LD_info->ied;
	ied_usr_t  *ied_usr = GET_IEDEXT_ADDR(ied);
	int			have_new_files = FALSE;

	for (i=0;i<256;i++) {
		if (LD_info->FltNum[i]<=0)
			continue;

		//LD名称规范四字节：PQMn,  或者PQLDn ,有5个字节， 但也只取4个字符进行匹配
		ldstr = LD_info->LD_name+strlen(LD_info->LD_name)-4;
		apr_snprintf(file_match_str,sizeof(file_match_str),"%s_%06d",ldstr,LD_info->FltNum[i]);

		printf(">>>>>>>>  IED [%d]:	%s  is calling cn wavefile, !!!!!!!! file_match_str=%s \n",ied->id,ied->name,file_match_str);

		//mq日志
		DIY_INFOLOG_CODE(LD_info->mp_id,2,LOG_CODE_COMTRADE_FILE,"【NORMAL】监测点:%s - id:%s开始召唤录波文件", LD_info->name,LD_info->mp_id);

		ret = SD_FAILURE;
		filenum = 0;
		assert(ied->chncount);
		for(chnl_no=0 ; chnl_no<ied->chncount; chnl_no++) {
			chnl_usr = (chnl_usr_t*)ied->channel[chnl_no].connect;
			if (chnl_usr->m_state!=CHANNEL_CONNECTED)
				continue;		
			ret = mms_mvla_fdir(chnl_usr->net_info,g_pt61850app->accPath,3*g_pt61850app->mmsOpTimeout,&filenames,&filenum,g_pt61850app->tmp_pool);
			if (ret==SD_SUCCESS)	
				break;	
		}	
		if (ret==SD_SUCCESS)	{
			int cfg_idx,dat_idx;
			char file_base_name[128];
			char file_yyyymm[128];
			char cfg_only_filename_ret[256];
			char dat_only_filename_ret[256];
			int ret2,ret3;

			//mq日志
			DIY_INFOLOG_CODE(LD_info->mp_id,2,LOG_CODE_COMTRADE_FILE,"【NORMAL】监测点:%s - id:%s获取录波文件列表成功,开始匹配录波文件", LD_info->name,LD_info->mp_id);

			//WW 2023-11-01将录波段号由字符串匹配修改为int的fltnum匹配
			ret2 = parse_file_names_by_fltnum(LD_info->FltNum[i], ldstr, filenames, filenum, &cfg_idx, &dat_idx, file_base_name, file_yyyymm);
			//WW 2023-11-01 end
			if (ret2 !=APR_SUCCESS){
				//mq日志
				//DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_COMTRADE_FILE,"【WARN】监测点:%s - id:%s前置记录的录波事件上传的录波号段%d与从装置获取的录波文件列表匹配失败,装置没有对应的号段的录波文件，清除该记录", LD_info->name,LD_info->mp_id,LD_info->FltNum[i]);
				//lnk20250819装置没有对应的文件时清除录波号段
				printf("监测点:%s - id:%s前置记录的录波事件上传的录波号段%d与从装置获取的录波文件列表匹配失败,装置没有对应的号段的录波文件，清除该记录", LD_info->name,LD_info->mp_id,LD_info->FltNum[i]);
				LD_info->FltNum[i] = -1;
				return ret2;
			}

			//完成录波文件，记录在文件名dat/cfg_only_filename_ret中，两个后缀不一样的录波文件，要上传两次
			 ret2 = pt61850_write_cn_file(chnl_usr,ied,filenames[cfg_idx],cfg_only_filename_ret);	
			 ret3 = pt61850_write_cn_file(chnl_usr,ied,filenames[dat_idx],dat_only_filename_ret);
			 have_new_files = TRUE;

/////////////////////////////////////////////////////////////////////////////////////////////上传文件
			 if (ret2==SD_SUCCESS && ret3==SD_SUCCESS )	{ //两个文件都写好了

				//mq日志
				DIY_INFOLOG_CODE(LD_info->mp_id,2,LOG_CODE_COMTRADE_FILE,"【NORMAL】监测点:%s - id:%s从终端获取录波文件成功", LD_info->name,LD_info->mp_id);

				 QVVR_t *qvvr;	//暂态事件
				 long long start_tm,trig_tm,end_tm;//北京时间

				 ret2 = extract_timestamp_from_cfg_file(filenames[cfg_idx],&start_tm,&trig_tm);//提取文件的开始时间和触发时间

				//添加录波文件时间戳校准20260409
				{
					XmlConfigC cfg;
					long long utc_or_beijing = 0;
					if (get_xml_config_by_dev_type(ied_usr->dev_type, &cfg)) {
						if(strcmp(cfg.WaveTimeFlag, "utc") == 0){//装置时间是utc还是北京
							utc_or_beijing = 28800000;//八小时相差毫秒数
						}
						else{
							utc_or_beijing = 0;
						}
						trig_tm = trig_tm + utc_or_beijing;//如果是utc时间则转换为北京时间
						start_tm = start_tm + utc_or_beijing;//接口上送没用到
					} 
					else {
					    printf("读取失败，未找到 dev_type\n");
					}
				}

				 printf(">>>>>>>>  extract_timestamp_from_cfg_file end \n");
				 if (ret2 ==APR_SUCCESS) {
					DIY_INFOLOG_CODE(LD_info->mp_id,2,LOG_CODE_COMTRADE_FILE,"【NORMAL】监测点:%s - id:%s提取录波文件时间成功", LD_info->name,LD_info->mp_id);
					 //to find the paired qvvr by the time of trig_tm
					 printf(">>>>>>>>  extract_timestamp_from_cfg_file success \n");
					 qvvr = find_qvvr_by_trig_tm(LD_info,trig_tm);		//根据文件的触发时间查找检测点记录的匹配上的暂态事件
					 if (qvvr) 	 {
						DIY_INFOLOG_CODE(LD_info->mp_id,2,LOG_CODE_COMTRADE_FILE,"【NORMAL】监测点:%s - id:%s波形文件匹配暂态事件成功", LD_info->name,LD_info->mp_id);

						 char* uuid_cfg = (char*)malloc(65 * sizeof(char));//上传文件后获取到的路径
						 char* uuid_dat = (char*)malloc(65 * sizeof(char));
						 char* filename_cfg = (char*)malloc(100 * sizeof(char));//上传文件后获取到的文件名
						 char* filename_dat = (char*)malloc(100 * sizeof(char));

						 //lnk202411-5 记录web返回的路径+文件名
						 char* wavepath_cfg = (char*)malloc(256 * sizeof(char));
						 char* wavepath_dat = (char*)malloc(256 * sizeof(char));
/*上传.cfg和.dat两个文件*/
						 char linux_cmd[256] = {0}; 
						 printf(">>>>>>>>  qvvr ok end \n");
						 apr_snprintf(linux_cmd,sizeof(linux_cmd),"./sftp_upload %s %s/%04d",cfg_only_filename_ret,file_yyyymm,LD_info->line_id);//没有使用

						 char loc_file_fullname_cfg[256];//本地文件名
						 memset(loc_file_fullname_cfg, 0, sizeof(loc_file_fullname_cfg));
						 apr_snprintf(loc_file_fullname_cfg, sizeof(loc_file_fullname_cfg), "/FeProject/comtrade/%s", cfg_only_filename_ret);
						 char oss_file_fullname_cfg[256];//远端文件名
						 memset(oss_file_fullname_cfg, 0, sizeof(oss_file_fullname_cfg));
						 apr_snprintf(oss_file_fullname_cfg, sizeof(oss_file_fullname_cfg), "comtrade/wave/%s/%s", LD_info->mp_id, cfg_only_filename_ret);
						 if (FILE_FLAG == 1) {
							 //PutOSS(oss_file_fullname_cfg, loc_file_fullname_cfg);//使用buffer推送文件
						 }
						 else if (FILE_FLAG == 2) {
							 //OBSFile(loc_file_fullname_cfg, oss_file_fullname_cfg, "putObject");//这里并没有上传文件流
						 }
						 else if(FILE_FLAG==3){
							 //WebAPI_Uds_Upload(UDS_UPLOAD_URL, loc_file_fullname_cfg, uuid_cfg, filename_cfg);//通过form-data上传文件
						 }
						 //LNK20241031使用JSON编码文件上传-具体的远端路径可以用原本代码的硬编码或者在配置文件中获取
						 else if (FILE_FLAG == 4) {
							char mq_file_fullname_cfg[256]; // 远端文件名
							memset(mq_file_fullname_cfg, 0, sizeof(mq_file_fullname_cfg));
						
							// 使用修改后的文件名传入 apr_snprintf
							apr_snprintf(mq_file_fullname_cfg, sizeof(mq_file_fullname_cfg), "comtrade/%s/",
										 LD_info->ied->channel[0].addr_str);
						
							SOEFileWeb(loc_file_fullname_cfg, mq_file_fullname_cfg, wavepath_cfg);

							printf("\n>>>>>>!! %s %s...... \n", mq_file_fullname_cfg, loc_file_fullname_cfg);
						 }

						 if (FILE_FLAG == 4) {
							
						 }
						 else{
						 	printf("\n>>>>>>!! %s %s...... \n", oss_file_fullname_cfg, loc_file_fullname_cfg);
						 }

						 apr_snprintf(linux_cmd,sizeof(linux_cmd),"./sftp_upload %s %s/%04d",dat_only_filename_ret,file_yyyymm,LD_info->line_id);//先通过sftp上传才发json过去

						 char loc_file_fullname_dat[256];
						 memset(loc_file_fullname_dat, 0, sizeof(loc_file_fullname_dat));
						 apr_snprintf(loc_file_fullname_dat, sizeof(loc_file_fullname_dat), "/FeProject/comtrade/%s", dat_only_filename_ret);
						 char oss_file_fullname_dat[256];
						 memset(oss_file_fullname_dat, 0, sizeof(oss_file_fullname_dat));
						 apr_snprintf(oss_file_fullname_dat, sizeof(oss_file_fullname_dat), "comtrade/wave/%s/%s", LD_info->mp_id, dat_only_filename_ret);
						 if (FILE_FLAG == 1) {
							 //PutOSS(oss_file_fullname_dat, loc_file_fullname_dat);//使用buffer推送文件
						 }
						 else if (FILE_FLAG == 2) {
							 //OBSFile(loc_file_fullname_dat, oss_file_fullname_dat, "putObject");//这里并没有上传文件流
						 }
						 else if(FILE_FLAG==3){
							 //WebAPI_Uds_Upload(UDS_UPLOAD_URL, loc_file_fullname_dat, uuid_dat, filename_dat);//通过form-data上传文件
						 }
						 //LNK20241031使用JSON编码文件上传-具体的远端路径可以用原本代码的硬编码或者在配置文件中获取
						 else if (FILE_FLAG == 4) {
							char mq_file_fullname_dat[256]; // 远端文件名
							memset(mq_file_fullname_dat, 0, sizeof(mq_file_fullname_dat));
						
							// 使用修改后的文件名传入 apr_snprintf
							apr_snprintf(mq_file_fullname_dat, sizeof(mq_file_fullname_dat), "comtrade/%s/",
										 LD_info->ied->channel[0].addr_str);
						
							SOEFileWeb(loc_file_fullname_dat, mq_file_fullname_dat, wavepath_dat);

							printf("\n>>>>>>!! %s %s...... \n", mq_file_fullname_dat, loc_file_fullname_dat);
						 }

						 if (FILE_FLAG == 4) {
							
						 }
						 else{
							printf("\n>>>>>>!! %s %s...... \n", oss_file_fullname_dat, loc_file_fullname_dat);
						 }
						 
/*上传.cfg和.dat两个文件*/
/*上传消息*/
						/////////////////////////////////////////////////////lnk20250520
						//根据配置文件控制上传的暂态事件时间为北京时间的ms数
						 printf("~~~~~~~this dev type is %s~~~~~~~",ied_usr->dev_type);
						XmlConfigC cfg1;
						if (get_xml_config_by_dev_type(ied_usr->dev_type, &cfg1)) {
						    printf("UnitOfTimeUnit = %s\n", cfg1.UnitOfTimeUnit);
						    printf("ValueOfTimeUnit = %s\n", cfg1.ValueOfTimeUnit);
						} else {
						    printf("读取失败，未找到 dev_type\n");
						}
						double s_or_ms;
						long long utc_or_beijing;
						if(strcmp(cfg1.UnitOfTimeUnit, "1") == 0){//持续时间上送的是秒1还是毫秒0
							s_or_ms = 0.001;
						}
						else{
							s_or_ms = 1.0;
						}
					
						if(strcmp(cfg1.ValueOfTimeUnit, "utc") == 0){//上送的是utc还是北京
							utc_or_beijing = 0;//61850库已经转换过
						}
						else{
							utc_or_beijing = 0;
						}
						/////////////////////////////////////////////////////

						 //to send json of this qvvr and rdre
						 end_tm = (long long)(qvvr->QVVR_PerTime*1000) + trig_tm;			//结束时间是持续时间加触发时间			 
						 if (FILE_FLAG == 3) {
							 size_t cfg_len = strlen(uuid_cfg) + strlen(filename_cfg) + 3; // 额外的3个字符用于"-"和结束符'\0'  
							 char* cfg_result = (char*)malloc(cfg_len * sizeof(char)); // 动态分配足够的内存空间  
							 size_t dat_len = strlen(uuid_dat) + strlen(filename_dat) + 3; // 额外的3个字符用于"-"和结束符'\0'  
							 char* dat_result = (char*)malloc(dat_len * sizeof(char)); // 动态分配足够的内存空间 
							 if (cfg_result != NULL && dat_result != NULL) {
								 snprintf(cfg_result, cfg_len, "%s-%s", uuid_cfg, filename_cfg); // 拼接字符串并确保不会溢出目标缓冲区  
								 snprintf(dat_result, dat_len, "%s-%s", uuid_dat, filename_dat); // 拼接字符串并确保不会溢出目标缓冲区  
								 ret3 = transfer_json_qvvr_data(g_node_id, 
									LD_info->line_id, 
									(double)qvvr->QVVR_Amg, 
									(double)qvvr->QVVR_PerTime/s_or_ms, //lnk20250520上送的暂态持续时间为毫秒
									start_tm + utc_or_beijing, 			//lnk20250520上送的暂态时间为北京时间  
									end_tm + utc_or_beijing, 			//lnk20250520上送的暂态时间为北京时间  
									qvvr->QVVR_type, 
									cfg_result, 
									dat_result, 
									LD_info->mp_id, 
									qvvr->QVVR_Rptname, 
									ied_usr->dev_type);
							 }
							 free(cfg_result); // 使用完毕后释放动态分配的内存空间 
							 free(dat_result); // 使用完毕后释放动态分配的内存空间 
						 }
						 else if(FILE_FLAG==4)//lnk20241031发送暂态web消息
						 {
							//内存分配部分不修改
							 size_t cfg_len = strlen(wavepath_cfg) + 1;
							 char* cfg_result = (char*)malloc(cfg_len * sizeof(char)); // 动态分配足够的内存空间  
							 size_t dat_len = strlen(wavepath_dat) + 1; 
							 char* dat_result = (char*)malloc(dat_len * sizeof(char)); // 动态分配足够的内存空间 

							 if (cfg_result != NULL && dat_result != NULL) {
								 snprintf(cfg_result, cfg_len, wavepath_cfg); // 拼接字符串并确保不会溢出目标缓冲区  
								 snprintf(dat_result, dat_len, wavepath_dat); // 拼接字符串并确保不会溢出目标缓冲区  
								 ret3 = transfer_json_qvvr_data(g_node_id, 	//这个参数没有使用
								 LD_info->line_id, 							//监测点序号
								 (double)qvvr->QVVR_Amg, 
								 (double)qvvr->QVVR_PerTime/s_or_ms, //lnk20250520上送的暂态持续时间为毫秒 
								 qvvr->QVVR_time + utc_or_beijing, 			//lnk20250520上送的暂态时间为北京时间  //这里不使用文件中的开始时间start_tm，因为会影响写库，使用QVVR的时间QVVR_time//lnk20250311
								 end_tm + utc_or_beijing, 			//lnk20250520上送的暂态时间为北京时间 
								 qvvr->QVVR_type, //伏值、持续时间、开始时间、结束时间、暂态类型
								 cfg_result, 
								 dat_result, //两个文件路径
								 LD_info->mp_id, 
								 qvvr->QVVR_Rptname, 
								 ied_usr->dev_type);//监测点号，文件和监测点暂态事件匹配上的暂态报告名，终端类型
							 }
							 free(cfg_result); // 使用完毕后释放动态分配的内存空间 
							 free(dat_result); // 使用完毕后释放动态分配的内存空间 
						 }
						 else if (FILE_FLAG == 1 || FILE_FLAG == 2)
						 {
							 size_t cfg_len = strlen(oss_file_fullname_cfg) + strlen(cfg_only_filename_ret) + 3; // 额外的3个字符用于"-"和结束符'\0'  
							 char* cfg_result = (char*)malloc(cfg_len * sizeof(char)); // 动态分配足够的内存空间  
							 size_t dat_len = strlen(oss_file_fullname_dat) + strlen(dat_only_filename_ret) + 3; // 额外的3个字符用于"-"和结束符'\0'  
							 char* dat_result = (char*)malloc(dat_len * sizeof(char)); // 动态分配足够的内存空间 
							 if (cfg_result != NULL && dat_result != NULL) {
								 snprintf(cfg_result, cfg_len, "%s-%s", oss_file_fullname_cfg, cfg_only_filename_ret); // 拼接字符串并确保不会溢出目标缓冲区  
								 snprintf(dat_result, dat_len, "%s-%s", oss_file_fullname_dat, dat_only_filename_ret); // 拼接字符串并确保不会溢出目标缓冲区  
								 ret3 = transfer_json_qvvr_data(g_node_id, 
									LD_info->line_id, 
									(double)qvvr->QVVR_Amg,
									(double)qvvr->QVVR_PerTime/s_or_ms, //lnk20250520上送的暂态持续时间为毫秒 
									start_tm + utc_or_beijing, 			//lnk20250520上送的暂态时间为北京时间 
									end_tm + utc_or_beijing, 			//lnk20250520上送的暂态时间为北京时间 
									qvvr->QVVR_type, 
									cfg_result, 
									dat_result, 
									LD_info->mp_id, 
									qvvr->QVVR_Rptname, 
									ied_usr->dev_type);
							 }
							 free(cfg_result); // 使用完毕后释放动态分配的内存空间 
							 free(dat_result); // 使用完毕后释放动态分配的内存空间 
						 }
/*上传消息*/
						 qvvr->used_status = QVVR_DATA_NOT_USED;//上传信息后这个qvvr的状态置为未使用
						 free(uuid_cfg);
						 free(uuid_dat);
						 free(filename_cfg);
						 free(filename_dat);

						//lnk 202411-5
						 free(wavepath_cfg);
						 free(wavepath_dat);

					 }
					 
					 else if(ld_has_qvvr_nonempty(LD_info))//防止手动录波日志还一直上送
					 {
						//获取时间类型lnk20250520
						XmlConfigC cfg;
						if (get_xml_config_by_dev_type(ied_usr->dev_type, &cfg)) {

							char trig_time_str[64] = {0};
							format_time_ms(trig_tm, trig_time_str, sizeof(trig_time_str));

							if(strcmp(cfg.WaveTimeFlag, "utc") == 0) {
						        DIY_ERRORLOG_CODE(
						            LD_info->mp_id,
						            2,
						            LOG_CODE_COMTRADE_FILE,
						            "【ERROR】监测点:%s - id:%s 匹配录波文件失败，录波号段：%d，录波触发时间戳:%lld，录波触发时间:%s，映射配置的暂态持续时间单位是s",
						            LD_info->name,
						            LD_info->mp_id,
						            LD_info->FltNum[i],
						            trig_tm,
						            trig_time_str);
								
						    } else {
							
						        DIY_ERRORLOG_CODE(
						            LD_info->mp_id,
						            2,
						            LOG_CODE_COMTRADE_FILE,
						            "【ERROR】监测点:%s - id:%s 匹配录波文件失败，录波号段：%d，录波触发时间戳:%lld，录波触发时间:%s，映射配置的暂态持续时间单位是ms",
						            LD_info->name,
						            LD_info->mp_id,
						            LD_info->FltNum[i],
						            trig_tm,
						            trig_time_str);
						    }
						} 
						else {
						    printf("读取失败，未找到 dev_type\n");
							DIY_ERRORLOG_CODE(LD_info->mp_id,2,LOG_CODE_COMTRADE_FILE,"【ERROR】监测点:%s - id:%s 匹配录波文件失败，录波号段：%d,录波文件的开始时间:%lld，触发时间:%lld，映射配置的录波文件时间单位未配置", LD_info->name,LD_info->mp_id,LD_info->FltNum[i],start_tm,trig_tm);
						}						
						
					 }
					 else {
						DIY_INFOLOG_CODE(LD_info->mp_id,2,LOG_CODE_COMTRADE_FILE,"【INFO】监测点:%s - id:%s的录波文件没有任何暂态事件可以匹配，录波号段：%d", LD_info->name,LD_info->mp_id,LD_info->FltNum[i]);
					 }
				}
				 
			}
			else{
				DIY_ERRORLOG_CODE(LD_info->mp_id,2,LOG_CODE_COMTRADE_FILE,"【ERROR】监测点:%s - id:%s 下载录波文件%s和%s失败，录波号段：%d", LD_info->name,LD_info->mp_id,filenames[cfg_idx],filenames[dat_idx],LD_info->FltNum[i]);
			}
						 
		}		
		else {
			if (ied && chnl_usr){
				echo_warn2("mms_mvla_fdir Failed:  IED [%d]  %s \n", ied->id , chnl_usr->ip_str) ;
				//mq日志
				DIY_WARNLOG_CODE(LD_info->mp_id,2,LOG_CODE_COMTRADE_FILE,"【WARN】监测点:%s - id:%s召唤录波文件列表失败,放弃这个号段", LD_info->name,LD_info->mp_id);
				LD_info->FltNum[i] = -1;
			}
				
			return APR_EAGAIN;
		}
		LD_info->FltNum[i]= -1;
	}

	if (have_new_files)
		clear_old_comtrade_files();

	return APR_SUCCESS;
}

//lnk 2024-11-4 添加时间转换函数
char* convertMsToDateTimeString(int64_t usTime) {
    // 确保时间戳是有效的
    if (usTime < 0) {
        return "Invalid timestamp";
    }

    // 将 `apr_time_t` 微秒（us）转换为 `time_t` 秒（s）
    time_t seconds = usTime / 1000000;

    // 用 `struct tm` 变量存储时间
    struct tm timeInfo;
    // **使用 `localtime_r()` 考虑系统时区**
    localtime_r(&seconds, &timeInfo);

    // 静态分配的字符数组存储转换后的字符串
    static char buffer[30];
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeInfo);

    return buffer;
}
