/******************************************************************************
版权所有：	
文件名称：	plc.c
文件版本：	01.01
创建作者:	sunxi
创建日期：	2015-11-02
功能说明：	可编程逻辑
其它说明：
	1、只有实点可以合并成双点,且一个实点只能参加一个双点合并。双点合并在转发点表中
	   第一个点必须为合位，第二个点必须为分位。
修改记录：
*/
/*------------------------------- 头文件 --------------------------------------
*/
#include "head.h"
 
/*------------------------------- 宏定义 --------------------------------------
*/
// 逻辑点定义
// 逻辑点由16位构成
// 高3位:	类型	0保留，其它为真正类型
// 次高5位:	开关号，0为公共，其它为开关序号
// 低8位:	点号,	0为无效点号，取值返回为0.
#define LP_TYPE			0XE000 // 逻辑点类型
#define LP_SW			0X1F00 // 开关号
#define LP_INDEX		0X00FF // 点号

#define LP_TYPE_YX		0X2000 // 遥信点
#define LP_TYPE_YK		0X4000 // 遥控点
#define LP_TYPE_YC		0X6000 // 遥测点
//#define LP_TYPE_EV		0X8000 // 事件点
#define LP_TYPE_PLC		0XA000 // PLC 点
#define LP_TYPE_PLC_CST	0XC000 // PLC 常量
#define LP_TYPE_PLC_OPT	0XE000 // PLC 操作符


#define LP_SW_PUB		0
#define LP_SW_BEGIN		1
#define LP_SW_END		31

#define PLC_LOGIC_NOT	(LP_TYPE_PLC_OPT | 0X0001)
#define PLC_LOGIC_AND	(LP_TYPE_PLC_OPT | 0X0002)
#define PLC_LOGIC_OR	(LP_TYPE_PLC_OPT | 0X0003)
#define PLC_LOGIC_XOR	(LP_TYPE_PLC_OPT | 0X0004)



/*------------------------------ 类型结构 -------------------------------------
*/
//可编程逻辑文件头
struct plc_config
{
	// 配置文件头
	struct cfg_file_head cfh;		

	//工程文件名
	s8	prj_name[64];

	// 表达式
	u32	exp_addr;	// 地址
	u32	exp_num;	// 个数
	u32 exp_len;	// 表达式长度

	// 保留
	u32	re_addr;	//地址
	u32	re_num;		//个数

	//对应描述文件
	u32	rsc_version;	// 版本
	u32	rsc_signature;	// 签名
	u32	rsc_length;		// 长度
	u16	rsc_crc;		// CRC
	u16 reserved0;
};

struct di_db
{
	// 初始化值
	u32	timeout;	// 双点滤波超时值
	u16 ev_code[2]; // 两个单点的遥信事件代码，由slot和no组合而成，从转发点表来

	// 双点采集用
	u32	us0;		// 超时原点
	u8	bChange; 	// 标识双点变位动作
	u8 	value0;		// 双点结果，滤波前，由遥信处理程序直接生成
	u8 	value1;		// 双点结果,滤波后，且根据转发点表完成取反操作
	
	struct timespec  ts; // 动作时刻
};



/*------------------------------ 全局变量 -------------------------------------
*/
// 逻辑点数组
struct lp 	g_lp[LP_NUM];

// PLC处理数据
u32			g_plc_num;
struct plc 	g_plc[PLC_EXP_NUM+1];
u32			g_plc_merge_index; //  合并点在PLC表中的位置


// 双点遥信结构
struct di_db g_di_db[MAX_DPI_NUMBER];	


/*------------------------------ 函数声明 -------------------------------------
*/
void plc_db_soe(EVENT_STRUCT *ev);


/*------------------------------ 外部函数 -------------------------------------
外部函数供其它实体文件引用，必须仔细检查传入参数的合法性.
*/
int plc_yx2index(u8 sw, u8 type)
{
	if(sw>SWITCH_NUM_MAX)
	{
		return -1;
	}
	if(type == 0)
	{
		return -1;
	}
	type--;
	
	if(sw == 0)
	{
		return type;
	}
	else
	{
		sw--;
		return PUB_DI_NUM_ALL+(SW_DI_NUM_ALL*sw) + type;
	}

	return -2;
}

SOE_DEF *plc_yx2soe(u8 sw, u8 type)
{
	if(sw>SWITCH_NUM_MAX)
	{
		return NULL;
	}
	if(type == 0)
	{
		return NULL;
	}
	type--;
	
	if(sw == 0)
	{
		if(type >= PUB_DI_NUM && type < PUB_DI_NUM_ALL)
		{
			return (SOE_DEF *)&tAllSOE[type - PUB_DI_NUM + EV_SW_NUM];
		}
	}
	else
	{
		if(type >= SW_DI_NUM && type < SW_DI_NUM_ALL)
		{
			return (SOE_DEF *)&tAllSOE[type - SW_DI_NUM];
		}
	}

	return NULL;
}

s32 plc_get_input(u16 ld)
{
	u16 type,sw,no;
	s32 r;

	type = (ld & LP_TYPE);
	sw	 = (ld & LP_SW)>>8 ;
	no   = (ld & LP_INDEX);

	r = 0;
	switch(type)
	{
	case LP_TYPE_YX:
		r = GetRmtYxVal(sw,no);
		r = (r == SW_DI_TYPE_ON);
	break;
	
	case LP_TYPE_YC:
		r = GetRmtMeaVal(sw,no,NULL);
	break;
	
	case LP_TYPE_PLC:
		r = g_plc[no].result;
	break;
	
	case LP_TYPE_PLC_CST:
	break;
	}

	return r;
}


int plc_init_iec_table(void)
{
	u16 i,j,ep;
	u16 v;
	int index;
	SOE_DEF * soe;

	// 处理单点，如果一个逻辑点对应多个通讯点，将除第一个通讯点外的其它通讯点放入PLC逻辑中
	for(i=0; i<g_table_head->di_num; i++)
	{
		index = plc_yx2index(g_di_table[i].owner,g_di_table[i].indexno);
		if(index < 0)
		{
			continue;
		}

		// 将第一个通讯点滤除
		if(g_lp[index].cp == g_di_table[i].cp)
		{
			continue;
		}

		// 没有通讯点，不需要
		if(g_di_table[i].cp == 0)
		{
			continue;
		}

		// 除第一个通讯点外的其它通讯点,放入PLC逻辑中
		// 检查空间
		if(g_plc_num >= PLC_EXP_NUM)
		{
			rt_printf("g_plc_num单点越限(g_plc_num=%d,i=%d).\r\n",g_plc_num,i);
			return -1;
		}

		// 通讯点
		g_plc[g_plc_num].cp = g_di_table[i].cp;

		// 标志,目前只有故障记录标志
		soe = plc_yx2soe(g_di_table[i].owner,g_di_table[i].indexno);
		if(soe)
		{
			if(soe->soe_def&0x80)
			{
				g_plc[g_plc_num].flag =  (PLC_FLAG_FAULT|(g_di_table[i].owner<<16));
			}
		}
			
		// 插入操作数
		ep = 0;
		v = LP_TYPE_YX;
		v |=  (g_di_table[i].owner<<8) | g_di_table[i].indexno;
		g_plc[g_plc_num].exp[ep++] = v;

		//单点取反
		if(g_di_table[i].is_inverse)
		{
			g_plc[g_plc_num].exp[ep++] = PLC_LOGIC_NOT;
		}

		// 结束符
		g_plc[g_plc_num].exp[ep++] = 0;

		g_plc_num++;
		
	}
		

	// 处理合并点
	g_plc_merge_index = g_plc_num;
	for(i=0;i<g_table_head->dimerge_num;i++) 
	{
		if(g_plc_num >= PLC_EXP_NUM)
		{
			rt_printf("g_plc_num合并点越限(g_plc_num=%d,i=%d).\r\n",g_plc_num,i);
			return -2;
		}
	
		g_plc[g_plc_num].cp = g_di_merge_table[i].cp;
		ep = 0;
		for(j=0;j<MIX_YX_NUMBER;j++)
		{
			u8 no=g_di_merge_table[i].no[j]&0xff;
			u8 owner=(g_di_merge_table[i].no[j]>>8)&0xff;	

			// 组合点号为空代表结束，退出。
			if(g_di_merge_table[i].no[j] == 0)
			{
				break;
			}

			// 标志,目前只有故障记录标志
			soe = plc_yx2soe(owner,no);
			if(soe)
			{
				if(soe->soe_def&0x80)
				{
					g_plc[g_plc_num].flag = (PLC_FLAG_FAULT|(owner<<16));

				}
			}

			// 插入操作数
			v =  LP_TYPE_YX | (owner<<8) | no;
			g_plc[g_plc_num].exp[ep++] = v;

			//单点取反
			if(g_di_merge_table[i].is_inv[j])
			{
				g_plc[g_plc_num].exp[ep++] = PLC_LOGIC_NOT;
			}

			// 插入或运算符
			if(j)
			{
				g_plc[g_plc_num].exp[ep++] = PLC_LOGIC_OR;
			}
		}

		// 合并点取反
		if(g_di_merge_table[i].is_inv_all)
		{
			g_plc[g_plc_num].exp[ep++] = PLC_LOGIC_NOT;
		}

		// 结束符
		g_plc[g_plc_num].exp[ep++] = 0;

		// 检查长度是否越界
		if(ep > PLC_EXP_LEN)
		{
			rt_printf("plc_iec_table: 表达式太长(i=%d,j=%d,ep=%d).\r\n",i,j,ep);
			return -3;
		}

		g_plc_num++;
	}

	//g_plc[2].do_owner = 0;
	//g_plc[2].do_index = 1;
	
	return 0;
}

int plc_init(void)
{
	int i, j, k,index,ret;
	u8 sw, no;
	u32 timeout[2] = {0};
	u32 timeout_add;
	
	// 初值
	for(i=0; i< LP_NUM; i++)
	{
		g_lp[i].cp =0;
		g_lp[i].db_index = -1;
	}
	if(equ_config_null())
	{
		return -1;
	}	
	// 单点
	// 保存第一个单点通讯点号
	for(i=0; i<g_table_head->di_num; i++)
	{
		index = plc_yx2index(g_di_table[i].owner,g_di_table[i].indexno);
		if(index < 0)
		{
			// 跳过备用点
			continue;
		}

		if(g_lp[index].cp == 0)
		{
			g_lp[index].cp = g_di_table[i].cp;
		}
	}

	// 双点
	if(g_table_head->di_db_num >MAX_DPI_NUMBER)
	{
		return -2;
	}
	for(i=0; i<g_table_head->di_db_num; i++) 
	{
		for (k=0; k<2; k++)
		{
			sw = (u8)(g_di_db_table[i].no[k]>>8);
			no = (u8)(g_di_db_table[i].no[k]);

			// 得到检查索引
			index = plc_yx2index(sw,no);
			if(index< 0)
			{
				return -3;
			}

			// 一个逻辑点只能对应一个双点
			if(g_lp[index].db_index >= 0)
			{
				return -4;
			}
			g_lp[index].db_index = i;

			// 得到滤波时间
			for (j=0; j<g_equ_config->di_num; j++)
			{
				if ((g_equ_config_di[j].owner == sw)
					&& (g_equ_config_di[j].type == no))
				{
					g_di_db[i].ev_code[k] = (g_equ_config_di[j].slot<<8)|g_equ_config_di[j].index;
					timeout[k] = g_equ_config_di[j].filter_time*USTIMER_MS;
					break;
				}
			}

			// 初始化双点值
			if(j != g_equ_config->di_num)
			{
				// 双点标准定义中，分位在bit0，合位在bit1.在转发点表中合位在前，分位在后
				// 所以移位必须取反一下。
				int sft = !k; 
				if(dido_di_is_on(g_equ_config_di[j].slot,g_equ_config_di[j].index))
				{
					g_di_db[i].value0 |= 1<<sft;
				}
				else
				{
					g_di_db[i].value0 &= ~(1<<sft);
				}

				g_di_db[i].value1 = g_di_db[i].value0;
				if(g_di_db_table[i].is_inv[k])
				{
					g_di_db[i].value1 ^= 1<<sft;
				}
			}
			else
			{
				rt_printf("双点需要实际通道对应(i=%d)\r\n",i);
				return -5;
			}
		}
		// 双点确认延时(两点滤波时间差)，转化为us，再增加20ms，以确保双点变位成功
		// 2017-09-05:在电科院测试，遥信10ms滤波，双点没有变位信号。针对此情况修改如下：
		// 1、附加延时由固定的20ms改为最小滤波时间的1/4，这样10ms的遥信滤波，将有2.5ms的附加延时。
		//	  初步评估2.5ms的延时相对于5ms的中断间隔是个合适的值，
		// 2、如果附加延时大于20ms则限制为20ms，防止延时过长。
		if(timeout[0] >= timeout[1])
		{

			g_di_db[i].timeout = (timeout[0] - timeout[1]);
			timeout_add = timeout[1]/4;
		}
		else
		{
			g_di_db[i].timeout = (timeout[1] - timeout[0]);
			timeout_add = timeout[0]/4;
		}

		if(timeout_add >20*USTIMER_MS)
		{
			timeout_add = 20*USTIMER_MS;
		}

		g_di_db[i].timeout += timeout_add;
	}

	// 解析PLC文件

	// 将转发点表中的多余单点和合并点转入PLC模块
	ret = plc_init_iec_table();
	if(ret < 0)
	{
		ret -= 10;
	}

	plc_scan(0);

	return ret;
}

int plc_exit(void)
{
	return 0;
}

u8 plc_db_value(int index)
{
	return g_di_db[index].value1;
}

void plc_db_init_value(u8 slot,u8 no,u8 v)
{
	int index,db_index;
	u16 ev_code;
	
	ev_code = (slot<<8) | no;
	
	// 得到检查逻辑点索引
	index = plc_yx2index(g_di_st[slot][no].owner,g_di_st[slot][no].type);
	if(index < 0)
	{
		return;
	}

	// 检查双点索引
	db_index = g_lp[index].db_index;
	if(db_index < 0)
	{
		return;
	}

#if 1
	// 取消正在进行的双点处理过程
	g_di_db[db_index].bChange = 0;
#else
	// 双点第一次变位，记录时标
	if (g_di_db[db_index].bChange == 0)
	{
		g_di_db[db_index].ts = ev->ts;
		g_di_db[db_index].bChange = 1;
	}
#endif

	// 每次变位都重新获取超时起点和双点的值
	g_di_db[db_index].us0 = ustimer_get_origin(); 
	if(ev_code == g_di_db[db_index].ev_code[0])
	{
		index = 1;
	}
	else if(ev_code == g_di_db[db_index].ev_code[1])
	{
		index = 0;
	}
	else
	{
		rt_printf("plc_db_soe:ev_code err!\r\n");
		return;
	}

	// 滤波前的值
	if(v)
	{
		g_di_db[db_index].value0 |= 1<<index;
	}
	else
	{
		g_di_db[db_index].value0 &= ~(1<<index);
	}

	// 滤波后的值
	// 双点标准定义中，分位在bit0，合位在bit1.在转发点表中合位在前，分位在后
	// 所以移位必须取反一下。
	if(g_di_db_table[db_index].is_inv[!index])
	{
		v = !v;
	}
	if(v)
	{
		g_di_db[db_index].value1 |= (1<<index);
	}
	else
	{
		g_di_db[db_index].value1 &= ~(1<<index);
	}

#if 0
	rt_printf("db_init:db=%d,index=%d,is_inv=%d,v0=%d,v1=%d\r\n",
				db_index,index,
				g_di_db_table[db_index].is_inv[!index],
				g_di_db[db_index].value0,
				g_di_db[db_index].value1);
#endif

	return;
}

void plc_db_soe(EVENT_STRUCT *ev)
{
	int index,db_index;
	u8 slot,no;

	slot = (ev->ev_code>>8);
	no = ev->ev_code&0xff;
	
	// 得到检查逻辑点索引
	index = plc_yx2index(g_di_st[slot][no].owner,g_di_st[slot][no].type);
	if(index < 0)
	{
		return;
	}

	// 检查双点索引
	db_index = g_lp[index].db_index;
	if(db_index < 0)
	{
		return;
	}
	
	// 双点第一次变位，记录时标
	if (g_di_db[db_index].bChange == 0)
	{
		g_di_db[db_index].ts = ev->ts;
		g_di_db[db_index].bChange = 1;
	}

	// 每次变位都重新获取超时起点和双点的值
	g_di_db[db_index].us0 = ustimer_get_origin(); 
	if(ev->ev_code == g_di_db[db_index].ev_code[0])
	{
		index = 1;
	}
	else if(ev->ev_code == g_di_db[db_index].ev_code[1])
	{
		index = 0;
	}
	else
	{
		rt_printf("plc_db_soe:ev_code err!\r\n");
		return;
	}
	
	if(ev->ev_value)
	{
		g_di_db[db_index].value0 |= 1<<index;
	}
	else
	{
		g_di_db[db_index].value0 &= ~(1<<index);
	}

	return;
}


// 双点滤波处理，得到滤波后的值
static void plc_db_scan(void)
{
	int i,value;
	uint32_t  flags;
	
	for(i=0; i<g_table_head->di_db_num; i++)
	{
		if (g_di_db[i].bChange)
		{
			// 屏蔽中断的情况下检查超时，防止超时和value不对应。
			value = -1;
			rt_irq_save(flags);
			if(ustimer_get_duration(g_di_db[i].us0) >= g_di_db[i].timeout)
			{

				value = g_di_db[i].value0;
			}
			rt_irq_restore(flags);	

			if(value!= -1)
			{
				g_di_db[i].bChange = 0;
				
				if(g_di_db_table[i].is_inv[0])
				{
					value ^= 2;
				}
				if(g_di_db_table[i].is_inv[1])
				{
					value ^= 1;
				}

				// 如果和旧值相等，说明信号已返回，不应送SOE。
				if(value != g_di_db[i].value1)
				{
					soe_record_dpi(i,value,&g_di_db[i].ts);
					g_di_db[i].value1 = value;
				}
			}
		}
	}

	return;
}


 int plc_scan(int is_rec)
{
	static s32 r[64];
	u16 *p,i,rp;

	// 双点扫描
	plc_db_scan();

	// PLC扫描
	for(i=0;i<g_plc_num;i++)
	{
		p = g_plc[i].exp;
		rp = 0;
		while(*p)
		{
			// 操作数，直接压栈
			if(((*p)&LP_TYPE) != LP_TYPE_PLC_OPT)
			{
				r[rp++] = plc_get_input(*p);
			}
			// 运算符,出栈计算，结果压栈
			else
			{
				switch(*p)
				{
				case PLC_LOGIC_NOT:
					r[rp-1] = (~r[rp-1]) & 0x01;
				break;
				
				case PLC_LOGIC_AND:
					r[rp-2] = r[rp-1] & r[rp-2];
					rp--;
				break;
				
				case PLC_LOGIC_OR:
					r[rp-2] = r[rp-1] | r[rp-2];
					rp--;
				break;
				
				case PLC_LOGIC_XOR:
					r[rp-2] = r[rp-1] ^ r[rp-2];
					rp--;
				break;

				default:
					return -1;
				}	
			}

			p++;
		}

		// 全部计算完毕，rp应该等1.
		if(rp != 1)
		{
			rt_err_set(ERR_CODE_PLC,0);
			rt_printf("plc_scan:i=%d,rp=%d\r\n",i,rp);
		}

		if(g_plc[i].result != r[rp -1])
		{
			g_plc[i].result = r[rp -1];

			// 开出
			if(g_plc[i].do_index)
			{
				int type;
				if(g_plc[i].result)
				{
					type = SW_DO_TYPE_ON;
					soe_record_opt(EV_PLC_HZ,((g_plc[i].do_owner<<8)|g_plc[i].do_index));
				}
				else
				{
					type = SW_DO_TYPE_OFF;
					soe_record_opt(EV_PLC_TZ,((g_plc[i].do_owner<<8)|g_plc[i].do_index));
				}
				
				if(g_plc[i].do_owner == 0)
				{
					sw_do_pub(g_plc[i].do_index-1,type);
				}
				else
				{
					sw_do(g_plc[i].do_owner-1,g_plc[i].do_index-1,type);
				}
 			}

			// 需要送主站
			if(g_plc[i].cp && is_rec)
			{
				u8 value = g_plc[i].result ? 1:0;
				soe_record_plc(i,value,g_plc[i].flag);
			}
		}
		
	}

	return 0;
}


/*------------------------------ 内部函数 -------------------------------------
内部函数以下划线‘_’开头，不需要检查参数的合法性.
*/

/*------------------------------ 测试函数 -------------------------------------
一个实体文件必须带一个本模块的测试函数来进行单元测试，如果的确不方便在本模块中
进行单元测试，必须在此注明实际的测试位置（例如在哪个实体文件中使用哪个测试函数）.
*/
int plc_lp_printf(void)
{
	int i,begin[5];

	begin[0] = 0;
	begin[1] = PUB_DI_NUM;
	begin[2] = PUB_DI_NUM_ALL;
	begin[3] = PUB_DI_NUM_ALL + SW_DI_NUM*SWITCH_NUM_MAX;
	begin[4] = PUB_DI_NUM_ALL + SW_DI_NUM*SWITCH_NUM_MAX + EV_SW_NUM*SWITCH_NUM_MAX;
	
	rt_printf("\r\n");
	for (i=0; i<LP_NUM; i++)
	{

		if(g_lp[i].cp == 0 && g_lp[i].db_index == -1)
		{
			continue;
		}
		
		rt_printf("%04d:通讯点:0x%04x, 双点索引:%02d, ", i,g_lp[i].cp,g_lp[i].db_index);
		
		// 公共实点
		if (i>= begin[0] && i<begin[1])
		{
			rt_printf("资源点:%s\r\n", g_pub_di_name[i]);
		}
		// 公共虚点
		else if (i>= begin[1] && i<begin[2]) 
		{
			rt_printf("资源点:%s\r\n", tAllSOE[(i-begin[1])+EV_SW_NUM].strName);
		}
		// 开关实点搜集
		else if (i>= begin[2] && i<begin[3]) 
		{
			rt_printf("资源点:开关%02d%s\r\n",(i-begin[2])/SW_DI_NUM,g_sw_di_name[(i-begin[2])%SW_DI_NUM]);
		}
		// 开关虚点搜集
		else if (i>= begin[3] && i<begin[4]) 
		{
			rt_printf("资源点:开关%02d%s\r\n", (i-begin[3])/EV_SW_NUM,tAllSOE[(i-begin[3])%EV_SW_NUM].strName);
		}
	}
 	return 0;
}


int plc_info_printf(void)
{
	u16 *p,i; 
	
	rt_printf("\r\n可编程逻辑表(num=%d):\r\n",g_plc_num);

	for(i=0;i<g_plc_num;i++)
	{
		p = g_plc[i].exp;

		rt_printf("PLC%02d:地址=0x%04x,结果=%d,开出归属=%d,开出索引=%d.\r\n",
			i,g_plc[i].cp,g_plc[i].result,g_plc[i].do_owner,g_plc[i].do_index);
		rt_printf("EXP%02d:",i);
		while(*p)
		{
			// 操作数
			if((*p&LP_TYPE) != LP_TYPE_PLC_OPT)
			{
				rt_printf("%04x,",*p);
			}
			// 运算符
			else
			{
				switch(*p)
				{
				case PLC_LOGIC_NOT:
					rt_printf("~,");
				break;
				
				case PLC_LOGIC_AND:
					rt_printf("&,");
				break;
				
				case PLC_LOGIC_OR:
					rt_printf("|,");
				break;
				
				case PLC_LOGIC_XOR:
					rt_printf("^,");
				break;

				default:
					return -1;
				}	
			}

			p++;
		} 	

		rt_printf("\r\n\r\n");
	}

	// 双点信息
	rt_printf("\r\n双点信息(num=%d):\r\n",g_table_head->di_db_num);
	rt_printf("timeout\tevcode0\tevcode1\tbChange\tv0\tv1\r\n");
	for(i=0;i<g_table_head->di_db_num;i++)
	{
		struct di_db *db;
		db = & g_di_db[i];

		rt_printf("%d\t%04x\t%04x\t%d\t%d\t%d\r\n",db->timeout,db->ev_code[0],db->ev_code[1],db->bChange,db->value0,db->value1);
	}


	return 0;
}

/*------------------------------ 文件结束 -------------------------------------
*/


