/******************************************************************************
版权所有：	云谷技术（珠海）有限公司
文件名称：	master_slave_equ_sync.c
文件版本：	01.00
创建作者：	sunxi
创建日期：	2018-04-23
功能说明：	级联装置中，主装置和从装置的参数和录波文件同步。
其它说明：	程序名称为master_slave_equ_sync，放置在装置主板的 /app 目录下面，需要在系统启动后进行启动，程序启动后为后台程序
修改记录：
*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>

#include <pthread.h>
#include <sys/prctl.h>

// #define __FOR_DEBUG_AND_TEST__

/*****************************************************/

enum EQUIPMENT_FALG
{
	EQUIPMENT_MASTER,
	EQUIPMENT_SLAVE,
};


#define E2PROM_UPDATE_PORT			7899
#define	MAX_TCP_CLIENT				1

#define	CMD_TCP_KEEP_HEART			0x50	// 1字节命令头+2字节长度（6）+4字节计数+2字节crc，共9字节
#define CMD_EQUIP_PARAM_UPD			0X51	// 1字节命令头+2字节长度（114）+112字节数据+2字节crc
#define CMD_FIXED_VALUE_UPD			0x52	// 1字节命令头+2字节长度（6826）+6824字节数据+2字节crc

#define	EQUIP_HEART_CNT_SIZE		8


#ifdef __FOR_DEBUG_AND_TEST__ // temp for test
#define	EEPROM_FILENAME_TMP			"/tmp/e2prom.bin"
#endif

#define	EEPROM_FILENAME				"/app/data/e2prom.bin"
#define SET_DATA_FILENAME			"/tmp/set_data.bin"

/**************eeprom空间分配*******************************
每个保存在eeprom中的信息都有装置类型及crc校验 10个字节

密码4字节+8 =12
通道系数 8*8+8=72
压板 4+8=12
装置参数  40*4+10=168
测控参数  40*4+10=168
内部定值  40*4+10=168
装置配置  1*4+10=12
定值区信息 10+10=18
每个定值区占240个字节+8个字节校验码 6个区共1488 (2K)

SOE 512个，
操作信息 512个
每个SOE占28个字节，共计28*512=14k
*/

/*
 * 最终从主装置发送过来的装置参数和公共定值，开关定值的最新数据都要更新到从装置的 /app/data/e2prom.bin 里面去
 * e2prom.bin 文件结构，总共64K字节
 * 装置参数 ---------> 文件偏移：128，	存储大小：376+10（部分更新，更新的数据偏移量66*4=264，大小28*4=112）
 * 内部定值 ---------> 文件偏移：1024，	存储大小：240+10（内部定值不需要更新）
 * 公共定值 + 开关定值  --> 文件偏移：2048，	存储大小：6824+10（全部更新）
 *
 * e2prom.bin文件更新后，如何通知到主程序去？使用信号方式，需要在主程序那边捕获信号，在app_main内核线程的循环里面添加以下代码
 *
		if (signal_pending(current))
		{
			spin_lock_irq(&current->sighand->siglock);

			// 判断是否是用户信号
			if(sigismember(&current->pending.signal, SIGUSR1)
					|| sigismember(&current->signal->shared_pending.signal, SIGUSR1))
			{
				// 处理信号
				printk("get usr's process SIGUSR1 signal!\n");
			}
			// 清空等待的signal
			spin_unlock_irq(&current->sighand->siglock);
			flush_signals(current);
		}
 */

typedef struct EQUIP_INF
{
	unsigned short wType;       	// 保护类型码
	unsigned short wVerH;       	// 软件版本号
	unsigned short wVerL;       	// 软件的发布号及特殊版本号，高字节为发布号，低字节为特殊版本号
	unsigned short wCrc;         	// 保存信息的校验码
	unsigned short wdescCrc;		// set_desc.bin文件的CRC
}EQUIP_INF;

#if 1 // 需要跟工程的 set.h 里面定义的一样
// 线路定值
enum
{
	SET_BH_TT=0,			// 保护投退
	//过流保护
	SET_LABLE_GL,
	SET_GLZERO_TZ,			// 过流停电跳闸投退
	SET_GL1_GJ,				// 过流1告警
	SET_GL1_TZ,			// 过流1跳闸
	SET_GL1_I,				//
	SET_GL1_T,				//
	SET_GL2_GJ,				// 过流1告警
	SET_GL2_TZ,			// 过流1跳闸
	SET_GL2_I,				// 过电流定值
	SET_GL2_T,				// 过电流时间

	SET_GL3_GJ,				// 过流1告警
	SET_GL3_TZ,			// 过流1跳闸
	SET_GL3_I,				// 过电流定值
	SET_GL3_T,				// 过电流时间

	SET_BSTZ_TT,			// 闭锁分闸电流，在负荷开关情况下使用
	SET_BSTZ_I,				// 闭锁分闸电流，在负荷开关情况下使用

	//零序保护
	SET_LABLE_LX,
	SET_XDLGJ_TT,			// 小电流接地告警
	SET_XDLTZ_TT,			// 小电流接地跳闸
	SET_XDLQD_TT,			// 小电流启动类型
	SET_XDL_I,				// 小电流暂态电流

	SET_LX1_GJ,				// 零流I段告警
	SET_LX1_TZ,				// 零流I段跳闸
	SET_LX1_I,				// 零流I段电流
	SET_LX1_T,

	SET_LX2_GJ,				// 零流2段告警
	SET_LX2_TZ,				// 零流2段跳闸
	SET_LX2_I,				// 零流II段电流
	SET_LX2_T,

	//后加速
	SET_LABLE_HJS,
	SET_GLJS_TT,			// 过流加速投退
	SET_GLJS_I,				// 过电流加速定值
	SET_GLJS_T,				// 过电流加速时间

	SET_LXJS_TT,			// 零序投退
	SET_LXJS_I,				// 零序电流加速定值
	SET_LXJS_T,				// 零序电流加速时间

	//重合闸
	SET_LABLE_CHZ,
	SET_CH_TT,				// 重合闸投退
	SET_CH2_TT,			// 多次重合闸投退
	SET_CH_TN,              		// 非一次重合闸时间
	SET_CH_T1,              		// 一次重合闸时间
	SET_CH_T2,              		// 二次重合闸时间
	SET_CH_T3,              		// 3次重合闸时间
	SET_CHBS_T,			// 重合闸闭锁时间
	SET_CHOK_T,			// 重合闸确认时间
	SET_AR_TIMES,			// 重合闸次数
	SET_TTCH_TT,			// 偷跳启动重合闸
	SET_CH_DLBS_TT,		// 大电流闭锁重投
	SET_CH_DLBS_I,		      // 大电流闭锁重投定值


	//就地馈线自动化
	SET_LABLE_FA,
	SET_FA_TT,		//就地馈线自动化选型
	SET_FA_TYPE,		//就地馈线自动化选型
	SET_SL_F,			// 分段、联络选择
	SET_S_L,			// 分段、联络选择
	SET_FTU_TT,
	SET_AUTOGL_TT,
	SET_AUTOLX_TT,
	SET_X_T,
	SET_Y_T,
	SET_C_T,
	SET_S_T,
	SET_XXTZ_T,
	SET_XXCH_T,
	SET_LOST_TT,			// 失压投退
	SET_LOST_T,			// 失压时间
	SET_LOST_TIMES,		//失压次数
	SET_TT_U0TZ,			// 零压分闸投退
	SET_U0TZ_T,				// 零压时间


	SET_LABEL_GDY,			// 过电压投退
	SET_GY_TT,			// 过电压投退
	SET_GY_U,				//过电压定值
	SET_GY_T,				// 过电压时间


	//合环功能
	SET_LABLE_TQ,
	SET_TT_TQ,				// 合环投退
	SET_TQ_T,				// 合环时间
	SET_TQ_U,				// 合环压差

	//电流越限
	SET_LABLE_OVI,
	SET_OVLOAD_TT,				// 电流重载
	SET_OV_I,				//
	SET_OV_T,				//
	SET_DLGZ_TT,				// 电流过载载
	SET_GZ_I,				//
	SET_GZ_T,				//

	SET_DLYX_TT,				// 电流过载载
	SET_DLYX_I,				//
	SET_DLYX_T,				//

	SET_I0YX_TT,				// 零序电流越限
	SET_I0YX_I,				//
	SET_I0YX_T,				//


	//CT变比
	SET_LABLE_CT,
	SET_CT_ONE,				//相CT一次值
	SET_CT_TWO,				// 相CT二次值
	SET_I0CT_ONE,				//零序CT一次值
	SET_I0CT_TWO,				// 零序CT二次值

	SET_LABLE_OTHER,			// 其他
	SET_CURRENT_INV_TT ,	// 电流取反投退，涉及功率计算和小电流接地判断
	SET_POWER_V2_TT	,		//功率计算是否选择第二路电压
	SET_TT_TWCN,			// 弹簧未储能投退
	SET_TT_KZHL,			// 控制回路
	SET_QYD_BS_TT,			// 低气压闭锁投退
	SET_YB_TT,				// 软压板投退
	SET_PT1_VOL,				// 电源侧电压
	SET_PT2_VOL,				// 负荷侧电压
	SET_U0_VOL,				// 零序电压选择
	SET_NUM
};


// 通用定值
enum
{

	//电压参数
	SET_LABLE_VOL=0,
	SET_POWER_U,			// 线路有压定值， 电源有压无压、上电合闸、失压分闸定值均使用此定值
	SET_U0_U,				// 零序过压定值    零压闭锁零流及合后零压判断均采用本电压

	//电压越限
	SET_LABLE_OVU,
	SET_GDY_TT,		//过电压判断
	SET_GDY_U,
	SET_GDY_T,
	SET_DDY_TT,			// 低电压判断
	SET_DDY_U,
	SET_DDY_T,
	//电源判断
	SET_LABLE_DY,
	SET_YY_TT,				// 有压鉴别投退
	SET_YY_T,			// PT有压延时
	SET_WY_TT,				// 无压鉴别投退
	SET_WY_T,			// PT无压延时
	SET_DY1_VOL,
	SET_DY2_VOL,
	//零压定值
	SET_LABLE_U0GJ,
	SET_U0GJ_TT,			// 零序过压告警投退
	SET_U0GJ_T,				// 零序过压时间
	// 其他
	SET_TT_OTHER_PUB,
	SET_PTCHECK_TT,			// PT断线检查
	SET_ZYB_TT,				// 软压板投退
	SET_YC_JG_TT,			//遥测定点间隔时间
	SET_DD_JG_TT,			//电度定点间隔时间

	SET_JDTYPE,				// 系统接地方式
	SET_PUB_NUM
};

// 装置参数
enum
{
	//规约参数组
	PARA_LABEL_PROTOCAL=0,
	PARA_ADDR,	        	// 装置地址
	PARA_YXTYPE,			// 遥信类型
	PARA_YKTYPE,			// 遥控类型
	PARA_YCTYPE,			// 遥测类型
	PARA_YCTIME,			// 遥测发送间隔
	PARA_YCAUTO,			// 遥测自动上送
	PARA_COSAUTO,			// COS自动上传
	PARA_EVPARA,			// COS自动上传
	PARA_YK_TIME,			// 遥控超时时间

	PARA_101LINKADDR,		// 101链路地址
	PARA_101APPADDR,		// 101应用地址
	PARA_101COT,			// 101传送原因

	PARA_104_Client,			// 104端口	是服务器还是客户端
	PARA_104_PORT,			// 104端口
	PARA_104_ACK_TIME,		// 104确认超时
	PARA_104_TEST_TIME,		// 104测试超时
	PARA_104_K,				// 104 K值
	PARA_104_W,				// 104 W值
	PARA_IP_104MASTER1,		// 104主站IP1
	PARA_IP_104MASTER2,		// 104主站IP2
	PARA_IP_104MASTER3,		// 104主站IP3
	PARA_IP_104MASTER4,		// 104主站IP4

	PARA_LABEL_LINK,
	PARA_IP_104LINK1,		// 104级联IP1
	PARA_IP_104LINK2,		// 104级联IP2
	//串行口通道组
	PARA_LABEL_SERIAL,
	PARA_PROTOCOL_S1,		// 规约类型
	PARA_BAUD_S1,			// 波特率
	PARA_PARITY_S1,			// 校验方式
	PARA_PROTOCOL_S2,		// 规约类型
	PARA_BAUD_S2,			// 波特率
	PARA_PARITY_S2,			// 校验方式
	PARA_PROTOCOL_S3,		// 规约类型
	PARA_BAUD_S3,			// 波特率
	PARA_PARITY_S3,			// 校验方式
	PARA_PROTOCOL_S4,		// 规约类型
	PARA_BAUD_S4,			// 波特率
	PARA_PARITY_S4,			// 校验方式
	PARA_PROTOCOL_S5,		// 规约类型
	PARA_BAUD_S5,			// 波特率
	PARA_PARITY_S5,			// 校验方式
	PARA_PROTOCOL_S6_GPS,	// 规约类型
	PARA_BAUD_S6_GPS,		// 波特率
	PARA_PARITY_S6_GPS,		// 校验方式
	PARA_PROTOCOL_GPRS_IN,	// 规约类型 内置GPRS

	//以太网通道组
	PARA_LABEL_ETHERNET,
	PARA_NET1,
	PARA_IP_NET1,
	PARA_MA_NET1,
	PARA_ROUTE_NET1,   //网段
	PARA_ROUTE_NETMASK1,   //掩码
	PARA_ROUTE_GATE1,   //网关
	PARA_NET2,
	PARA_IP_NET2,
	PARA_MA_NET2,
	PARA_ROUTE_NET2,   //网段
	PARA_ROUTE_NETMASK2,   //掩码
	PARA_ROUTE_GATE2,   //网关
	PARA_NET3,
	PARA_IP_NET3,
	PARA_MA_NET3,
	PARA_ROUTE_NET3,   //网段
	PARA_ROUTE_NETMASK3,   //掩码
	PARA_ROUTE_GATE3,   //网关
	PARA_GATE_NET,			// 网关

	//对时组
	PARA_LABEL_TIME,
	PARA_TIMETYPE,			// 对时方式
	PARA_IP_SNTP,			// SNTP服务器IP
	PARA_SNTP_SYNC_TM,		// SNTP对时间隔

	//电池活化组
	PARA_LABEL_BATTERY,
	PARA_BATBEGIN_YMD,		// 电池活化启动时间
	PARA_BATBEGIN_HMS,		// 电池活化启动时刻
	PARA_BAT_INTERVAL,		// 电池活化间隔
	PARA_BAT_ALARM,			// 电池告警阀值

	//远动参数
	PARA_LABLE_RMT,
	PARA_DEAD_I,                // "电流死区",
	PARA_DEAD_U,			//"交流电压死区",
	PARA_DEAD_ZL,			//"直流电压死区",
	PARA_DEAD_P,			//"功率死区",
	PARA_DEAD_F,			//"频率死区",
	PARA_DEAD_COS,		//"功率因数死区",
	PARA_PT_ONE,			//"PT一次额定",
	PARA_PT_TWO,			//"PT二次额定",
	PARA_YXFD,				//"遥信防抖时间",
	PARA_FZPULSE,			//"分闸脉冲",
	PARA_HZPULSE,			//"合闸脉冲",
	PARA_U_ZERO,			//"电压零漂
	PARA_I_ZERO,			//"电流零漂

	//其它组
	PARA_LABEL_OTHERS,
	PARA_RST_TT,			// 自动复归时间
	PARA_RSTTIME,			// 自动复归时间
	RARA_EVTRSTTIME,		//故障遥信复归时间
	PARA_LHTTIME,			// 液晶背光时间
	PARA_NUM
};


#define EEP_EQUIP_PARA_ADDR			128      //装置参数在e2prom.bin的地址偏移量
#define EEP_SET_ADDR				(2*1024) //公共定值和开关定值在e2prom.bin的地址偏移量

#define PARA_104LINK1IP_OFFSET		(PARA_IP_104LINK1*4)
#define FIXED_VALUE_SIZE			(((SET_PUB_NUM+(16*SET_NUM))*4)+10)
#define EQUIP_PARAM_SIZE			((PARA_NUM*4)+10)

#define EQUIP_PARAM_UPDATE_OFFSET	(PARA_LABEL_TIME*4)
#define EQUIP_PARAM_UPDATE_SIZE		((PARA_NUM-PARA_LABEL_TIME)*4)

#define FIXED_VALUE_RW_SIZE			(((SET_PUB_NUM+(16*SET_NUM))*4)+4)
#define EQUIP_PARAM_RW_SIZE			(((PARA_NUM-PARA_LABEL_TIME)*4)+4)

#define EQUIP_INFO_SIZE				sizeof(EQUIP_INF)

#else // 放在工程一起编译


#define EEP_EQUIP_PARA_ADDR			128      //装置参数在e2prom.bin的地址偏移量
#define EEP_SET_ADDR				(2*1024) //公共定值和开关定值在e2prom.bin的地址偏移量

#define PARA_104LINK1IP_OFFSET		96

#define FIXED_VALUE_SIZE			6834
#define EQUIP_PARAM_SIZE			386

#define EQUIP_PARAM_UPDATE_OFFSET	264
#define EQUIP_PARAM_UPDATE_SIZE		112

#define FIXED_VALUE_RW_SIZE			6828
#define EQUIP_PARAM_RW_SIZE			116

#define EQUIP_INFO_SIZE				10

#endif // 放在工程一起编译


#ifdef __FOR_DEBUG_AND_TEST__ // temp for test
static unsigned int g_update_cnt = 0;
static unsigned int g_send_cnt = 0;
#endif


/****************************************************************************/
/*
 * 级联的两台装置，一台为主，一台为从，通过网口2进行连接，
 * 1. e2prom.bin 参数主装置有更新时，把包括“对时”参数以下的参数进行覆盖到从装置上。
 * 2. 当判断到从装置的录波目录有新的录波文件时，拷贝到主装置的录波目录上。
 *
 * 解决方法，绑定网口建立两个socket，一个服务端，一个客户端，服务端作为从装置使用，客户端作为主装置使用。
 * 1. 装置判断 e2prom.bin数据文件的“从装置级联”参数中，如果当前值大于0，则该装置为主装置，如果为0的时候，
 * 则作为从装置，从装置开启服务端，主装置根据“从装置级联”参数中的IP1地址或者IP2地址（需要对应到网口2），
 * 与从装置建立连接，连接建立成功后，每隔1秒钟（根据调试调整）判断 e2prom.bin 文件，如果有更新，
 * 则把数据文件发给从装置，然后从装置把需要更新的参数更新到 e2prom.bin 文件 去。
 *
 * 2. 从装置判断录波文件目录，如果有文件增加，则把文件数据发送到主装置。（判断有新增文件的算法需要思考）
 */

#define RCD_FILE_PATH			"/app/COMTRADE/" // 录波文件保存目录
#define RCD_LAST_FTP_FLAG_FILE	"/app/data/rcd_ftp_flag" // 录波文件传输到主设备标识文件

static pthread_t	g_thread_for_rcd = 0;
static unsigned char g_connect_flag = 0;
static char master_equipment_ip[16] = {0x00};
/*
 * 主装置和从装置tcp通信数据包格式
 * 命令头，1字节
 * 数据长度，2字节，包括crc校验
 * 数据包
 * crc校验，2字节，数据包的校验，后面写文件时可用
 */

//CRC-16 表
const unsigned short crc16_table[256]=
{
		0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241,
		0xC601,0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440,
		0xCC01,0x0CC0,0x0D80,0xCD41,0x0F00,0xCFC1,0xCE81,0x0E40,
		0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,0xC841,
		0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40,
		0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41,
		0x1400,0xD4C1,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641,
		0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040,
		0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240,
		0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441,
		0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41,
		0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840,
		0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41,
		0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40,
		0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640,
		0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041,
		0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240,
		0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441,
		0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41,
		0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840,
		0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41,
		0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40,
		0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640,
		0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041,
		0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241,
		0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440,
		0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40,
		0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841,
		0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40,
		0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41,
		0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641,
		0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x4040
};
/**************************************************************************
函数名称：crc16_cal
函数版本：1.00
作者：
创建日期：2005.4.13
函数功能说明：计算1个字节数组的CRC-16
输入：	p    =数组的开始地址
			len  =数组长度
返回值:	crc  =计算后的CRC-16结果
更新信息：
更新日志1：
日期：
修改者：
修改内容：
修改原因：
***************************************************************************/
unsigned short crc16_cal(unsigned char *p, int len)
{
	unsigned short crc = 0;
	unsigned char tmp = 1;

	while(len-- > 0)
	{
		if(*p != 0)
			tmp = 0;
		crc = crc16_table[(crc&0xff)^(*p++)]^(crc>>8);
	}
	if(tmp)
		crc = 0x5678; //若全为0，crc返回就不要为零

	return crc;
}

static void talk_app_main_thread_handle(void)
{
    int app_main_pid = -1;
    char buffer[16] = {0x00};

    FILE *fp = popen("pidof app_main", "r");
    if(fp != NULL)
    {
    	if(fread(buffer, 1, 16, fp) > 0)
		{
    		app_main_pid = atoi(buffer);
    		if(app_main_pid > 0)
    			kill(app_main_pid, SIGUSR1);
		}
		pclose(fp);
    }
}

/*
 * 1. 启动后，先获取装置的实时时间，再获取到传输标识文件(/app/data/rcd_ftp_flag)最后修改时间，
 * 		该文件主要用途是在两台主从装置连接之前从装置没有同步的录波文件同步（上传）到主装置去；
 * 2. 根据最新实时时间，计算与/app/data/rcd_ftp_flag传输标识文件修改时间相差多少分钟 MINS；如果没有该文件，把MINS设置为1440000（1000天的时间）
 * 3. 利用find "/app/COMTRADE/" -type f -mmin -MINS 查找这段时间内生成有多少个录波文件，然后利用脚本命令把文件ftp到主装置去，
 * 4. 进入循环，每隔2秒时间，当还没有新的录波文件生成时，使用 find /app/COMTRADE/ -type f -mmin -1 查找有没有新的文件生成，有的话等待4秒钟稳定后进行传输文件，然后重写传输标识文件；
 * 		如果已经有最新的录波文件生成，则使用 find /app/COMTRADE/ -type f -newer 最新录波文件名称 ，查找最新的录波文件，有的话等待4秒钟稳定后进行传输文件，然后重写传输标识；
 *
 * 说明：发生故障时才会生成录波文件，正常运行时半个月一个月的时间难得出现一次，但是测试的时候，可能十几秒可以生成一次。
 * 可以两秒钟判断一次，还有上传到主装置的实时性要求不高，因此4秒钟后等稳定再上传是可以的。
 */

static void thread_for_ftp_rcd_file(void)
{
	struct stat statbuf;
	char cmd_buf[256] = {0x00};
	char newest_name[64] = {0x00};	// 最新的文件
	char last_newer_file[64] = {0x00};	// 次新文件
	int ret, read_len = 0;
	int stable_cnt = 0;	// 稳定计数
	unsigned int mmin = 1440000; // 1000天的分钟数，就是三年多的时间里面生成的录波文件需要进行上传
	FILE *fp;
	unsigned short ftp_port = 21;

	struct timeval tv, tv1;

    signal(SIGPIPE, SIG_IGN);

	gettimeofday(&tv, NULL);

	if(!stat(RCD_LAST_FTP_FLAG_FILE, &statbuf)) // 成功获取文件状态
	{
		mmin = (tv.tv_sec - statbuf.st_mtim.tv_sec) / 60;
		if((tv.tv_sec - statbuf.st_mtim.tv_sec) % 60)
			mmin ++;
	}
	system("echo 123 > /tmp/123");
	sprintf(cmd_buf, "ftpput -u root -p hhrhl123@ %s -P %d /tmp/123 /tmp/123 > /dev/null",
			master_equipment_ip, ftp_port);
	if((ret = system(cmd_buf)))
	{
		ftp_port = 6001;
		printf("use ftp_port = %d", ftp_port);
	}
	// 查找最新生成的录波文件并上传到主装置；每上传一个文件后，写ftp传输标识文件
#ifdef __FOR_DEBUG_AND_TEST__ // 临时测试用
	system("mkdir -p /tmp/app");
	system("mkdir -p /tmp/app/COMTRADE");
	sprintf(cmd_buf, "find %s -type f -mmin -%d -exec ftpput -u root -p hhrhl123@ -P %d %s /tmp{} {} \\",
			RCD_FILE_PATH, mmin, ftp_port, master_equipment_ip);
	printf("cmd_buf: %s\n", cmd_buf);
#else
	sprintf(cmd_buf, "find %s -type f -mmin -%d -exec ftpput -u root -p hhrhl123@ -P %d %s {} {} \\;",
			RCD_FILE_PATH, mmin, ftp_port, master_equipment_ip);
#endif
	system(cmd_buf);

	// 以下代码防止在上面同步大量文件的时候花的时间比较多时，中间又生成了比较多的文件。
	gettimeofday(&tv1, NULL);
	mmin = (tv1.tv_sec - tv.tv_sec) / 60;
	if((tv1.tv_sec - tv.tv_sec) % 60)
		mmin ++;
	sprintf(cmd_buf, "find %s -type f -mmin -%d -exec ftpput -u root -p hhrhl123@ -P %d %s {} {} \\; -exec date > %s \\;",
			RCD_FILE_PATH, mmin, ftp_port, master_equipment_ip, RCD_LAST_FTP_FLAG_FILE);
	system(cmd_buf);

	prctl(PR_SET_NAME, "ftp_rcd_file");
	while(g_connect_flag)
	{
		memset(cmd_buf, 0x00, 256);
		if(last_newer_file[0] && !access(last_newer_file, F_OK))
			sprintf(cmd_buf, "find %s -type f -newer %s", RCD_FILE_PATH, last_newer_file);
		else
			sprintf(cmd_buf, "find %s -type f -mmin -1", RCD_FILE_PATH);// 在没有新的录波文件出来之前，搜索启动1分钟内生成的录波文件

		fp = popen(cmd_buf, "r");
		if(fp != NULL)
		{
			memset(newest_name, 0x00, 64);
			read_len = fread(newest_name, 1, 64, fp);
			if(read_len > 0)
			{
				newest_name[read_len - 1] = 0x00;
				if(stable_cnt >= 2) // 暂时等待4秒钟稳定，待确定写入的录波文件大小为多少字节再进行调整。
				{
					stable_cnt = 0;
					memcpy(last_newer_file, newest_name, 64);
					memset(cmd_buf, 0x00, 256);
#ifdef __FOR_DEBUG_AND_TEST__ // 临时测试用
					char tmp_name[64] = {0x00};
					strcpy(tmp_name, "/tmp/");
					strcpy(&tmp_name[5], &newest_name[14]);
					printf("newest_name:%s, tmp_name:%s\n", newest_name, tmp_name);
					sprintf(cmd_buf, "ftpput -u root -p hhrhl123@ -P 6001 %s %s %s; date > %s",
							master_equipment_ip, tmp_name, newest_name, RCD_LAST_FTP_FLAG_FILE);
					printf("cmd_buf: %s\n", cmd_buf);
#else
					sprintf(cmd_buf, "ftpput -u root -p hhrhl123@ %s -P 6001 %s %s; date > %s",
							master_equipment_ip, newest_name, newest_name, RCD_LAST_FTP_FLAG_FILE);
#endif
					system(cmd_buf);
				}
				stable_cnt ++;
			}
			pclose(fp);
		}
	    sleep(2);
	}
}

// 从装置与主装置tcp连接成功后的数据处理
static void slave_equipment_data_handle(int sockfd)
{
    int fd;
    int flag;
    fd_set set;
    struct timeval tv;
    unsigned char cmd;
    int recv_len;
	int recv_len_tmp;
    short int frame_len;
    unsigned short frame_crc, cal_crc;
    int keep_connect_recv_cnt = 0;

    EQUIP_INF equip_inf;
    unsigned char connect_flag = 1;
	unsigned int heart_cnt;

    unsigned char heart_count_buf[EQUIP_HEART_CNT_SIZE+1] = {0x00};	// 心跳计数缓冲区6字节
	unsigned char equip_param_buf[EQUIP_PARAM_SIZE] = {0x00}; // 装置参数缓冲区，94 * 4 + 10=386字节，整个装置参数区需要重新计算crc校验，因此需要全部读出来
    unsigned char equip_param_recv_buf[EQUIP_PARAM_RW_SIZE+1] = {0x00};// 需要更新的装置参数接收缓冲区：2字节长度（114） + 28 * 4 + 2 = 116
    unsigned char fixed_value_buf[FIXED_VALUE_RW_SIZE+1] = {0x00}; // 公共定值和开关定值接收缓冲区：2字节长度 + (26 + 105 * 16) * 4 + 2 = 6828

	g_connect_flag = 1;

    // 是否生成一个线程判断录波文件的更新？
    if(pthread_create(&g_thread_for_rcd, NULL, (void *)thread_for_ftp_rcd_file, NULL) != 0)
        printf("thread for ftp rcd file create failed!\n");
    else
        printf("thread for ftp rcd file create successful.\n");

    flag = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flag & ~O_NONBLOCK);

    while(connect_flag)
    {
    	keep_connect_recv_cnt ++;
    	if(!(keep_connect_recv_cnt % 40)) // 一点半秒钟接收一个字节
    	{
            flag = fcntl(sockfd, F_GETFL, 0);
            fcntl(sockfd, F_SETFL, flag | O_NONBLOCK);
    		recv_len = read(sockfd, &cmd, 1);
    		if(recv_len <= 0)
    		{
    			printf("connector was closed. recv_len = %d\n", recv_len);
    			break;
    		}
    	}
        FD_ZERO(&set);
        FD_SET(sockfd, &set);
        ///注意select之后sel_time的值都会被修改，因此每次都进行重置
        tv.tv_sec = 0;
        tv.tv_usec = 50000;
        if(select(sockfd + 1,&set, NULL, NULL, &tv) < 0)
            continue;

        if(FD_ISSET(sockfd, &set))
        {
        	keep_connect_recv_cnt = 0;
        	cmd = 0x00;
        	recv_len = 0;
        	if((recv_len = read(sockfd, &cmd, 1)) <= 0)
        	{
        		connect_flag = 0;
        		printf("read cmd failed. sockfd = %d\n", sockfd);
        		break;
        	}
        	switch(cmd)
        	{
        	case CMD_TCP_KEEP_HEART:
        		heart_count_buf[0] = cmd;
        		recv_len = read(sockfd, &heart_count_buf[1], EQUIP_HEART_CNT_SIZE);	// 不用处理
        		memcpy(&heart_cnt, &heart_count_buf[3], 4);
				swap32(&heart_cnt);//added by sunxi: 大小端问题
        		//if(!(heart_cnt%30))
        		//	printf("recv_len = %d, heart_cnt = %d\n", recv_len, heart_cnt);
            	break;
        	case CMD_EQUIP_PARAM_UPD:
        		equip_param_recv_buf[0] = cmd;
        		equip_param_recv_buf[1] = 0x00;
        		equip_param_recv_buf[2] = 0x00;
        		recv_len = read(sockfd, &equip_param_recv_buf[1], EQUIP_PARAM_RW_SIZE);
        		memcpy(&frame_len, &equip_param_recv_buf[1], 2);
				swap16(&frame_len);//added by sunxi: 大小端问题
        		if(recv_len != (frame_len + 2))
        			break;
        		memcpy(&frame_crc, &equip_param_recv_buf[1+frame_len], 2);
				swap16(&frame_crc);//added by sunxi: 大小端问题
        		cal_crc = crc16_cal(&equip_param_recv_buf[3], frame_len-2);
        		if(cal_crc != frame_crc)
        			break;

#ifdef __FOR_DEBUG_AND_TEST__ // 临时测试用
        		fd = open(EEPROM_FILENAME_TMP, O_RDWR);
        		if(fd < 0)
        		{
        			printf("open %s failed!\n", EEPROM_FILENAME_TMP);
        			break;
        		}
#else
        		fd = open(EEPROM_FILENAME, O_RDWR);
        		if(fd < 0)
        		{
        			printf("open %s failed!\n", EEPROM_FILENAME);
        			break;
        		}
#endif
        		lseek(fd, EEP_EQUIP_PARA_ADDR, SEEK_SET);
        		read(fd, equip_param_buf, EQUIP_PARAM_SIZE-EQUIP_INFO_SIZE);//把装置参数区的数据全部读出
        		read(fd, &equip_inf, EQUIP_INFO_SIZE);//得到装置信息数据

        		memcpy(equip_param_buf+EQUIP_PARAM_UPDATE_OFFSET, &equip_param_recv_buf[3], EQUIP_PARAM_UPDATE_SIZE);//更新接收到的参数信息

        		equip_inf.wCrc = crc16_cal(equip_param_buf, EQUIP_PARAM_SIZE-EQUIP_INFO_SIZE);//重新计算参数数据的crc

        		lseek(fd, EEP_EQUIP_PARA_ADDR+EQUIP_PARAM_UPDATE_OFFSET, SEEK_SET);//把文件指针定位到需要更新的参数缓冲区位置
        		write(fd, equip_param_buf+EQUIP_PARAM_UPDATE_OFFSET, EQUIP_PARAM_UPDATE_SIZE);//把数据写到文件
        		write(fd, &equip_inf, EQUIP_INFO_SIZE);//把数据写到文件
        		fsync(fd);//更新到nandflash
        		close(fd);
#ifdef __FOR_DEBUG_AND_TEST__	// 临时测试用
				g_update_cnt ++;
        		printf("%s equipment param update successful. g_update_cnt = %d\n", EEPROM_FILENAME_TMP, g_update_cnt);
#else
        		printf("%s equipment param update successful.\n", EEPROM_FILENAME);
#endif
        		// 通知主程序参数已经更新
        		talk_app_main_thread_handle();

        		break;
        	case CMD_FIXED_VALUE_UPD:
        		equip_param_recv_buf[0] = cmd;
        		equip_param_recv_buf[1] = 0x00;
        		equip_param_recv_buf[2] = 0x00;

        		recv_len = read(sockfd, &fixed_value_buf[1], 2);
        		memcpy(&frame_len, &fixed_value_buf[1], 2);
				swap16(&frame_len);//added by sunxi: 大小端问题
        		if(frame_len >= FIXED_VALUE_RW_SIZE)
        			frame_len = FIXED_VALUE_RW_SIZE;
        		recv_len_tmp = 0;
        		recv_len = 0;
        		while(recv_len < frame_len)
        		{
        			recv_len_tmp = read(sockfd, &fixed_value_buf[3 + recv_len], frame_len);
        			if(recv_len_tmp <= 0)
        				break;
        			recv_len += recv_len_tmp;
        		}

        		if(recv_len != frame_len)
        		{
        			printf("recv_len = %d, frame_len = %d\n", recv_len, frame_len);
        			break;
        		}
        		memcpy(&frame_crc, &fixed_value_buf[1+frame_len], 2);
				swap16(&frame_crc);//added by sunxi: 大小端问题
        		cal_crc = crc16_cal(&fixed_value_buf[3], frame_len-2);
        		if(cal_crc != frame_crc)
        			break;
#ifdef __FOR_DEBUG_AND_TEST__ // 临时测试用
        		fd = open(EEPROM_FILENAME_TMP, O_RDWR);
        		if(fd < 0)
        		{
        			printf("open %s failed!\n", EEPROM_FILENAME_TMP);
        			break;
        		}
#else
        		fd = open(EEPROM_FILENAME, O_RDWR);
        		if(fd < 0)
        		{
        			printf("open %s failed!\n", EEPROM_FILENAME);
        			break;
        		}
#endif
        		lseek(fd, EEP_SET_ADDR+frame_len-2, SEEK_SET);
        		read(fd, &equip_inf, EQUIP_INFO_SIZE);
        		equip_inf.wCrc = cal_crc;
        		memcpy(fixed_value_buf + 1 + frame_len, &equip_inf, EQUIP_INFO_SIZE);
        		lseek(fd, EEP_SET_ADDR, SEEK_SET);
        		write(fd, fixed_value_buf+3, frame_len-2 + EQUIP_INFO_SIZE);
        		fsync(fd);
        		close(fd);
#ifdef __FOR_DEBUG_AND_TEST__
				g_update_cnt ++;
        		printf("%s fixed value update successful. g_update_cnt = %d\n", EEPROM_FILENAME_TMP, g_update_cnt);
#else
        		printf("%s fixed value update successful.\n", EEPROM_FILENAME);
#endif
        		// 通知主程序参数已经更新
        		talk_app_main_thread_handle();

        		break;
        	default:
        		break;
        	}
        }
    }
    g_connect_flag = 0;

    if(g_thread_for_rcd != 0)
    {
        // pthread_cancel(g_thread_for_rcd);
        pthread_join(g_thread_for_rcd, NULL);
        g_thread_for_rcd = 0;
    }
	printf("close connect sockfd = %d\n", sockfd);
}

/*
 * set_data.bin 结构，每个项共有多少字节，一共7842个字节
 * 文件头 ----------> 144
 *
 * 公共定值 ---------> 104
 * 开关定值 ---------> 6720
 * 空白保留区 --------> 216
 * 内部定值 ---------> 240
 * 空白保留区 --------> 16
 *
 * 装置参数 ---------> 376
 * 空白保留区 --------> 24
 * crc校验 ---------> 2
 */
#define SETDATA_PUB_SWITCH_OFFSET		144
#define SETDATA_PUB_SWITCH_SIZE			6824
#define SETDATA_EQUIP_PARAM_OFFSET		7440

#define HRART_SEND_FREQ					10	// 心跳发送频率，tcp连接保持用
#define FILE_UPDATE_STABLE_CNT			10	// 主装置的e2prom.bin更新后，等待500毫秒后更新到从装置

// 主装置与从装置tcp连接成功后的数据处理
static void master_equipment_data_handle(int sockfd)
{
	/*
	 * 每隔两百毫秒获取一下/tmp/set_data.bin 的状态，如果文件修改时间有发生变化，则判断
	 */
	int fd;
	struct stat statbuf;
	unsigned int stable_cnt = 0;			// 文件修改抖动处理，最起码需要10秒钟
	unsigned char file_modify_flag = 0;		// 文件修改标志

	unsigned char connect_flag = 1;

    unsigned short frame_crc;
    short int frame_len;

	static struct timespec last_mtime = {0, 0};

	int time_to_send_heart = -1;
	static unsigned int heart_cnt = 0;
    unsigned char heart_count_buf[EQUIP_HEART_CNT_SIZE+1] = {0x00};	// 心跳计数缓冲区6字节

	unsigned char equip_param_last_buf[EQUIP_PARAM_RW_SIZE-4] = {0x00}; // 上次需要更新的装置参数缓冲区，一共112字节
    unsigned char equip_param_send_buf[EQUIP_PARAM_RW_SIZE+1] = {0x00};// 需要更新的装置参数发送缓冲区：1字节命令字 + 2字节长度（114） + 28 * 4 + 2 = 117
	unsigned char fixed_value_last_buf[FIXED_VALUE_RW_SIZE-4] = {0x00}; // 需要更新的公共定值和开关定值缓冲区，一共6824字节
    unsigned char fixed_value_send_buf[FIXED_VALUE_RW_SIZE+1] = {0x00}; // 公共定值和开关定值发送缓冲区：1字节命令字 + 2字节长度 + (26 + 105 * 16) * 4 + 2 = 6829

    heart_count_buf[0] = CMD_TCP_KEEP_HEART;
    frame_len = 2;
    memcpy(&heart_count_buf[1], &frame_len, 2);
	swap16(&heart_count_buf[1]);//added by sunxi: 大小端问题

	while(connect_flag)
	{
		if(!stat(SET_DATA_FILENAME, &statbuf)) // 成功获取文件状态
		{
			if((statbuf.st_mtim.tv_sec != last_mtime.tv_sec)
					|| (statbuf.st_mtim.tv_nsec != last_mtime.tv_nsec))
			{
				file_modify_flag = 1;
				stable_cnt = 0;
				last_mtime.tv_sec = statbuf.st_mtim.tv_sec;
				last_mtime.tv_nsec = statbuf.st_mtim.tv_nsec;
				printf("file_modify_flag = %d\n", file_modify_flag);
			}
			else if(file_modify_flag)
				stable_cnt ++;
		}

		while(stable_cnt >= FILE_UPDATE_STABLE_CNT) // 使用while，方便里面发生错误时break出来
		{
			// 当得到文件发生改变，等待一秒钟后稳定再获取
			stable_cnt = 0;
			file_modify_flag = 0;

			fd = open(SET_DATA_FILENAME, O_RDONLY);
			if(fd < 0)
			{
    			printf("open %s failed!\n", SET_DATA_FILENAME);
				break;
			}

			// 先判断装置参数公共区的数据有没有变化，没变化的则不用处理
			lseek(fd, SETDATA_EQUIP_PARAM_OFFSET + EQUIP_PARAM_UPDATE_OFFSET, SEEK_SET);
			if(read(fd, &equip_param_send_buf[3], EQUIP_PARAM_UPDATE_SIZE) < 0)
			{
    			printf("read %s equipment param data failed!\n", SET_DATA_FILENAME);
				close(fd);
				break;
			}
			// 刚启动的时候，需要进行更新一次
			if(memcmp(equip_param_last_buf, &equip_param_send_buf[3], EQUIP_PARAM_UPDATE_SIZE)) // 参数有发生变化
			{
				memcpy(equip_param_last_buf, &equip_param_send_buf[3], EQUIP_PARAM_UPDATE_SIZE);
				equip_param_send_buf[0] = CMD_EQUIP_PARAM_UPD;
				frame_len = EQUIP_PARAM_RW_SIZE - 2;
				memcpy(&equip_param_send_buf[1], &frame_len, 2);
				swap16(&equip_param_send_buf[1]);//added by sunxi: 大小端问题
				frame_crc = crc16_cal(equip_param_last_buf, EQUIP_PARAM_UPDATE_SIZE);
				memcpy(&equip_param_send_buf[3+EQUIP_PARAM_UPDATE_SIZE], &frame_crc, 2);
				swap16(&equip_param_send_buf[3+EQUIP_PARAM_UPDATE_SIZE]);//added by sunxi: 大小端问题
				if(write(sockfd, equip_param_send_buf, EQUIP_PARAM_RW_SIZE+1) < 0)
				{
	    			printf("send data to slave equipment failed, reconnect!\n");
					close(fd);
					connect_flag = 0;
					break;
				}
#ifdef __FOR_DEBUG_AND_TEST__ // temp for test
				g_send_cnt ++;
				printf("master equipment EquipmentParam update! g_send_cnt = %d\n", g_send_cnt);
#endif
			}

			// 再判断公共定值和开关定值的数据有没有变化（是不是可以只判断crc校验不同就可以？后面做个简单程序来试试）
			lseek(fd, SETDATA_PUB_SWITCH_OFFSET, SEEK_SET);
			if(read(fd, &fixed_value_send_buf[3], SETDATA_PUB_SWITCH_SIZE) < 0)
			{
    			printf("read %s fixed value data failed!\n", SET_DATA_FILENAME);
				close(fd);
				break;
			}
			if(memcmp(fixed_value_last_buf, &fixed_value_send_buf[3], SETDATA_PUB_SWITCH_SIZE)) // 参数有发生变化
			{
				memcpy(fixed_value_last_buf, &fixed_value_send_buf[3], SETDATA_PUB_SWITCH_SIZE);
				fixed_value_send_buf[0] = CMD_FIXED_VALUE_UPD;
				frame_len = FIXED_VALUE_RW_SIZE - 2;
				memcpy(&fixed_value_send_buf[1], &frame_len, 2);
				swap16(&fixed_value_send_buf[1]);//added by sunxi: 大小端问题
				frame_crc = crc16_cal(fixed_value_last_buf, SETDATA_PUB_SWITCH_SIZE);
				memcpy(&fixed_value_send_buf[3+SETDATA_PUB_SWITCH_SIZE], &frame_crc, 2);
				swap16(&fixed_value_send_buf[3+SETDATA_PUB_SWITCH_SIZE]);//added by sunxi: 大小端问题
				if(write(sockfd, fixed_value_send_buf, FIXED_VALUE_RW_SIZE+1) < 0)
				{
	    			printf("send data to slave equipment failed, reconnect!\n");
					close(fd);
					connect_flag = 0;
					break;
				}
#ifdef __FOR_DEBUG_AND_TEST__ // temp for test
				g_send_cnt ++;
				printf("master equipment fixed value update! g_send_cnt = %d\n", g_send_cnt);
#endif

			}
			close(fd);
		}

		// 定时发送心跳信息进行tcp连接保持
		time_to_send_heart ++;
		if(time_to_send_heart >= HRART_SEND_FREQ)
		{
			// 不用那么频繁发送心跳，一秒钟一个就可以了
			time_to_send_heart = 0;
			heart_cnt ++;
			memcpy(&heart_count_buf[3], &heart_cnt, 4);
			swap32(&heart_count_buf[3]);//added by sunxi: 大小端问题
			frame_crc = crc16_cal(&heart_count_buf[3], 4);
			memcpy(&heart_count_buf[7], &frame_crc, 2);
			swap16(&heart_count_buf[7]);//added by sunxi: 大小端问题

			// printf("heart_cnt = %d\n", heart_cnt);
			if(write(sockfd, heart_count_buf, EQUIP_HEART_CNT_SIZE+1) < 0)
			{
				printf("send data failed, reconnect!\n");
				break;
			}
			system("pidof inetd > /dev/null; if [ $? -gt 0 ]; then inetd -q 128; fi");
		}

		usleep(100000);
		// sleep(2);
	}
}


int main(int argc, char *argv[])
{
	int fd = 0;
	int read_len = 0;
	enum EQUIPMENT_FALG equipment_flag = EQUIPMENT_MASTER; // 从级联IP地址来判断，IP有值并且为有效值，作为主装置，否则作为从装置
	unsigned int link_IP1 = 0;

	int sock_fd = 0;
    int connect_fd = 0;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    socklen_t sockaddr_size = sizeof(struct sockaddr_in);
    socklen_t opt = 1;

    pid_t pid;

	if((pid = fork()) > 0)
		exit(0);
	else if(pid < 0)
		exit(1);

	//第一子进程，后台继续执行
	setsid();           //第一子进程成为新的会话组长和进程组长

	//并与控制终端分离
	if((pid = fork()) > 0)
		exit(0);        //是第一子进程，结束第一子进程
	else if(pid < 0)
		exit(1);

	// 接下来第二子进程开始执行，成为后台守护进程

    // 当tcp连接断开以后，某一端进行发送数据的时候，会产生一个SIGPIPE信号，该信号默认处理是终止当前进程，因此程序里面需要忽略该信号。
    signal(SIGPIPE, SIG_IGN);

#ifdef __FOR_DEBUG_AND_TEST__ // temp for test
    g_update_cnt = 0;
    g_send_cnt = 0;

	if(argc == 2) // for client, master equipment
	{
		if(!strcmp(argv[1], "slave"))
			fd = open(EEPROM_FILENAME, O_RDONLY);
		else if(!strcmp(argv[1], "master"))
			fd = open(EEPROM_FILENAME_TMP, O_RDONLY);
		else
		{
			printf("temp Usage: %s slave\nor\n%s master\n", argv[0], argv[0]);
			return -1;
		}

		if(fd < 0)
		{
			if(!strcmp(argv[1], "slave"))
				printf("open %s failed!\n", EEPROM_FILENAME);
			else if(!strcmp(argv[1], "master"))
				printf("open %s failed!\n", EEPROM_FILENAME_TMP);
			return 1;
		}
	}
	else
	{
		printf("temp Usage: %s slave\nor\n%s master\n", argv[0], argv[0]);
		return -2;
	}
#else
	fd = open(EEPROM_FILENAME, O_RDONLY);
	if(fd < 0)
	{
		printf("open %s failed!\n", EEPROM_FILENAME);
		return 1;
	}
#endif

	lseek(fd, EEP_EQUIP_PARA_ADDR+PARA_104LINK1IP_OFFSET, SEEK_SET); // 装置参数的偏移地址

	read_len = read(fd, &link_IP1, sizeof(unsigned int));
	if(read_len != sizeof(unsigned int))
	{
		printf("read %s equipment link ip failed!\n", EEPROM_FILENAME);
		return 2;
	}

	if(link_IP1)
		equipment_flag = EQUIPMENT_MASTER;	// 主装置
	else
		equipment_flag = EQUIPMENT_SLAVE;	// 从装置

    close(fd);

    bzero ((char *) &server_addr, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(E2PROM_UPDATE_PORT);

    /*
     * 从装置作为服务端等待主装置的数据，当主装置成功连接到从装置时，等待主装置的参数数据，
     * 并判断有没有生成的录波文件，如果有，通过ftpput命令发送给主装置。
     */
    if(equipment_flag == EQUIPMENT_SLAVE)
    {

		printf("slave equipment ---> tcp server\n");

    	if((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            printf("create socket failed!\n") ;
            return 3;
        }

    	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(socklen_t)) < 0)
        {
            printf("setsockopt SO_REUSEADDR failed!\n") ;
        }

        if(bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
        {
        	printf("server bind socket failed!\n") ;
            close(sock_fd);
            return 4;
        }

        if(listen(sock_fd, MAX_TCP_CLIENT) < 0)
        {
        	printf("server listen socket failed!\n") ;
            close(sock_fd);
            return 5;
        }

        while(1)
        {
			// 等待客户端连接，阻塞模式
			connect_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sockaddr_size);
			if(connect_fd < 0)
			{
				printf("server accept error!\n") ;
				continue;
			}
			memset(master_equipment_ip, 0x00, 16);
			strcpy(master_equipment_ip, inet_ntoa(client_addr.sin_addr));
            printf("get a client connect, IP: %s\n", master_equipment_ip);

			slave_equipment_data_handle(connect_fd);

		    close(connect_fd);
        }
    }
    else
    {
    	/*
    	 * 主装置连接从装置的服务器，连接成功后，定时检查 /tmp/set_data.bin 文件，
    	 * 只要文件有更新，更新后持续1秒钟稳定，就把更新的数据发送给从装置，然后定时给从装置发送心跳信息。
    	 */
    	int ftp_service_start_flag = 0;
		printf("master equipment ---> tcp client\n");

    	while(1)
    	{

        	if((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
            {
                printf("create socket fd failed!\n") ;
                return 3;
            }

            server_addr.sin_addr.s_addr = htonl(link_IP1);
            // printf("connect server: %s\n", inet_ntoa(server_addr.sin_addr));

			if(connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
			{
				usleep(200000);
				continue;
			}
			if(!ftp_service_start_flag)
			{
				ftp_service_start_flag = 1;
				system("/usr/sbin/inetd -q 128");
			}
            printf("connect server successful.\n");

			master_equipment_data_handle(sock_fd);

    		close(sock_fd);

			usleep(100000);
    	}
    }


	return 0;
}
