/**    
    @file     uart.c
    @date     2009-03-19
    @author   오재경 freefrug@falinux.com
    @brief    uart 를 사용한 통신을 담당한다.

    @modify   
              2010-08-18 (장길석) mingw와 함께 사용할 수 있는 코드 추가
    @todo    
    @bug     
    @remark   
    
    @warning 
*/
//
//  저작권    에프에이리눅스(주)
//            외부공개 금지
//
//----------------------------------------------------------------------------
#define EMBEDDED_LINUX                                          // 이렇게 처리하지 않으면 EClipse에서 C 영역이 회색 바탕이 됨

#ifdef MS_WIN32
    #undef EMBEDDED_LINUX
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

#ifdef EMBEDDED_LINUX

	#include <sys/poll.h>
	#include <sys/un.h>
	#include <termios.h>

#else

	#include <windows.h>

#endif

#include <pollmng.h>
#include <uart.h>

char desc_uart[] = "falinux uart ver 0.2.1";


/// uart 개별 구조체
typedef struct {

	// 버퍼관리
	char  recv_fifo[UART_RECV_FIFO_MAX];	
	int   fifo_rcnt;

	char  port[256];
	
} uart_priv_t;

//------------------------------------------------------------------------------
/** @brief    uart 을 open 한다.
    @param    fname  문자열 파일이름
    @return   poll_obj_t 형태의 포인터
*///----------------------------------------------------------------------------
poll_obj_t  *uart_open( char *dev_name, int baud, char parity, int stop_bit)
{
	poll_obj_t  *obj;
	fd_t		fd;
	uart_priv_t *uart;

#ifdef EMBEDDED_LINUX

	struct termios  newtio;

	// 시리얼포트를 연다.
	// parity 'T' 이면 터미널형식으로 연다.
	if ( 'T' == parity ) fd = open( dev_name, O_RDWR  );
	else  
		fd = open( dev_name, O_RDWR | O_NOCTTY );
	
	if ( fd < 0 ) 
	{
		// 화일 열기 실패
		printf( "device open fail %s : ", dev_name );
		perror("");
		return NULL;
	}
    	
	// 시리얼 포트 환경을 설정한다.
	memset(&newtio, 0, sizeof(newtio) );
	
	// data 8bit
	newtio.c_cflag = CS8 | CLOCAL | CREAD;	// NO-rts/cts

	if ( 2 == stop_bit){
		newtio.c_cflag |= CSTOPB;   // CSTOPB: 2stop bit
    }
	
	// baud
	switch( baud )
	{
	case 2400   : newtio.c_cflag |= B2400  ; break;
	case 4800   : newtio.c_cflag |= B4800  ; break;
	case 9600   : newtio.c_cflag |= B9600  ; break;
	case 19200  : newtio.c_cflag |= B19200 ; break;
	case 38400  : newtio.c_cflag |= B38400 ; break;
	case 57600  : newtio.c_cflag |= B57600 ; break;
	default     : newtio.c_cflag |= B115200; break;
	}

	// parity	
	switch( parity | 0x20 ) // 소문자 처리
	{
	case 'o'  : newtio.c_cflag  |= (PARENB |PARODD );  break;
	case 'e'  : newtio.c_cflag  |= PARENB           ;  break;
	default   :                                     ;  break;
	}

	newtio.c_iflag      = 0;	
	newtio.c_oflag      = 0;
	newtio.c_lflag      = 0;
	newtio.c_cc[VTIME]  = 0;  
	newtio.c_cc[VMIN]   = 0;  
	
	tcflush  ( fd, TCIFLUSH );
	tcsetattr( fd, TCSANOW, &newtio );
	    
#else

	char 	str_rs_config[1024];

	sprintf( str_rs_config, "baud=%d data=8 parity=%c stop=1", baud, parity);

	fd	= CreateFile( dev_name,
			GENERIC_READ|GENERIC_WRITE,
			0, /* no share  */
			NULL, /* no security */
			OPEN_EXISTING,
			0, /* no threads */
			NULL); /* no templates */

	if ( INVALID_HANDLE_VALUE == fd){
		printf("unable to open %s port\n", dev_name);
		return NULL;
	}

	DCB dcb;

	memset(&dcb, 0, sizeof(dcb)); /* clear the new struct  */
	dcb.DCBlength = sizeof(dcb);

//	if( !BuildCommDCBA( str_rs_config, &dcb))
//	{
//		printf("unable to set comport dcb settings\n");
//		CloseHandle( fd);
//		return NULL;
//	}

	dcb.BaudRate        =  baud;
    dcb.ByteSize        =  8;
    switch( parity | 0x20 ){
    case 'o'  : dcb.Parity  =  ODDPARITY    ;  break;
    case 'e'  : dcb.Parity  =  EVENPARITY   ;  break;
    default   : dcb.Parity  =  NOPARITY     ;  break;
    }
    dcb.StopBits        =  ONESTOPBIT;
    dcb.fBinary         =  TRUE;
    dcb.fOutxCtsFlow    =  FALSE;
    dcb.fOutxDsrFlow    =  FALSE;
    dcb.fDtrControl     =  DTR_CONTROL_DISABLE;
    dcb.fDsrSensitivity =  FALSE;
    dcb.fOutX           =  FALSE;
    dcb.fInX            =  FALSE;
    dcb.fErrorChar      =  FALSE;
    dcb.fNull           =  FALSE;
    dcb.fRtsControl     =  RTS_CONTROL_DISABLE;

	if(!SetCommState( fd, &dcb))
	{
		printf("unable to set comport cfg settings\n");
		CloseHandle( fd);
		return NULL;
	}

	COMMTIMEOUTS cto;

    cto.ReadIntervalTimeout         = MAXDWORD;             // 이 값을 주어야  1 개 바이트가 들어 와도 이벤트 발생
    cto.ReadTotalTimeoutMultiplier  = 0;
    cto.ReadTotalTimeoutConstant    = 0;
    cto.WriteTotalTimeoutConstant   = 0;
    cto.WriteTotalTimeoutMultiplier = 0;

	if( !SetCommTimeouts( fd, &cto))
	{
		printf("unable to set comport time-out settings\n");
		CloseHandle( fd);
		return NULL;
	}

    // 버퍼 비우기
    PurgeComm( fd,PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);


    // 이벤트 설정
    SetCommMask( fd,EV_RXCHAR);

#endif
	
	// uart 만의 정보를 설정한다.
	uart = (uart_priv_t *)malloc( sizeof(uart_priv_t) );
	memset( (void *)uart, 0, sizeof(uart_priv_t) );
	strcpy( uart->port, dev_name );

	obj = poll_add( fd );
	obj->type = STYP_UART;
	obj->priv = (void *)uart;

#ifdef MS_WIN32

	obj->is_serial  = TRUE;

#endif

	return obj;
}
//------------------------------------------------------------------------------
/** @brief    uart 를 모뎀 제어용으로 open 한다.
    @param    fname  문자열 파일이름
    @param    fname  문자열 파일이름
    @param    fname  문자열 파일이름
    @return   poll_obj_t 형태의 포인터
*///----------------------------------------------------------------------------
poll_obj_t  *uart_open_modem( char *dev_name, int baud, char parity )
{
	poll_obj_t  *obj	= NULL;

#ifdef EMBEDDED_LINUX

	int			 fd;
	uart_priv_t *uart;
	struct termios  newtio;

	printf( "[%s:%d] \n",__FILE__,__LINE__);
	// 시리얼포트를 연다.
	fd = open( dev_name, O_RDWR | O_NOCTTY );
	if ( fd < 0 ) 
	{
		// 화일 열기 실패
		printf( "device open fail %s : ", dev_name );
		perror("");
		return NULL;
	}
    	
	printf( "[%s:%d] \n",__FILE__,__LINE__);
	// 시리얼 포트 환경을 설정한다.
	memset(&newtio, 0, sizeof(newtio) );
	
	// data 8bit
	// newtio.c_cflag = CS8 | CLOCAL | CREAD | CRTSCTS  ;
	newtio.c_cflag = CRTSCTS | CS8 | CLOCAL |  CREAD ;
	// newtio.c_cflag = CRTSCTS | CS8 | CREAD ;
	
	// baud
	switch( baud )
	{
	case 2400   : newtio.c_cflag |= B2400  ; break;
	case 4800   : newtio.c_cflag |= B4800  ; break;
	case 9600   : newtio.c_cflag |= B9600  ; break;
	case 19200  : newtio.c_cflag |= B19200 ; break;
	case 38400  : newtio.c_cflag |= B38400 ; break;
	case 57600  : newtio.c_cflag |= B57600 ; break;
	default     : newtio.c_cflag |= B115200; break;
	}

	// parity	
	switch( parity | 0x20 ) // 소문자 처리
	{
	case 'o'  : newtio.c_cflag  |= (PARENB |PARODD );  break;
	case 'e'  : newtio.c_cflag  |= PARENB           ;  break;
	default   :                                     ;  break;
	}

	printf( "[%s:%d] \n",__FILE__,__LINE__);
	// newtio.c_iflag      = 0;	
	newtio.c_iflag      = IGNPAR | ICRNL;	
	newtio.c_oflag      = 0;
	// newtio.c_lflag      = 0;
	newtio.c_lflag      = ICANON;
	newtio.c_cc[VTIME]  = 0;  
	newtio.c_cc[VMIN]   = 0;  
	
	printf( "[%s:%d] \n",__FILE__,__LINE__);
	tcflush  ( fd, TCIFLUSH );
	tcsetattr( fd, TCSANOW, &newtio );
	    
	// uart modem 만의 정보를 설정한다.
	uart = (uart_priv_t *)malloc( sizeof(uart_priv_t) );
	memset( (void *)uart, 0, sizeof(uart_priv_t) );
	strcpy( uart->port, dev_name );

	printf( "[%s:%d] \n",__FILE__,__LINE__);
	obj = poll_add( fd );
	obj->type = STYP_UART;
	obj->priv = (void *)uart;

#else

#endif

	return obj;
}


//------------------------------------------------------------------------------
/** @brief    uart 를 close 한다.
    @param    obj  폴객체 포인터
*///----------------------------------------------------------------------------
void uart_close( poll_obj_t *obj )
{
#ifdef EMBEDDED_LINUX

	close( obj->fd );
	
#else

	CloseHandle( obj->fd);

#endif

	if ( obj->priv )
	{
		free( obj->priv );
	}
	
	poll_delete( obj );
}
//------------------------------------------------------------------------------
/** @brief    uart 폴객체를 파일이름으로 찾는다.
    @param    fname 문자열 파일이름
    @return   obj  폴객체 포인터
*///----------------------------------------------------------------------------
poll_obj_t *uart_get_byport( char *fname )
{
	poll_obj_t *obj;
	uart_priv_t *uart;
	int  idx, count;
	
	count = poll_count();
	
	for(idx=0; idx<count; idx++)
	{
		obj = poll_get_obj( idx );
		if ( obj->type == STYP_UART )
		{
			uart = (uart_priv_t *)obj->priv;  
			if ( uart )
			{
				if ( 0 == strcmp( uart->port, fname ) )
				{
					return obj;
				}
			}
		}
	}
	
	return NULL;
}
//------------------------------------------------------------------------------
/** @brief    uart 를 통해 데이타를 전송한다.
    @param    obj  폴객체 포인터
    @param    buf  전송버퍼
    @param    len  버퍼의 길이
    @return   전송한 데이타 개수
*///----------------------------------------------------------------------------
int  uart_write( poll_obj_t *obj, char *buf, int len )
{
	int  wrcnt;
	
	// 전송한다.
#ifdef EMBEDDED_LINUX

	wrcnt = write( obj->fd, buf, len );

#else

	WriteFile( obj->fd, buf, len, (LPDWORD)((void *)&wrcnt), NULL);

#endif

	if ( 0 > wrcnt )
	{
		perror( "uart send error:" );	
	}
	
	return wrcnt;
}
//------------------------------------------------------------------------------
/** @brief    uart 를 통해 데이타를 읽는다.
    @param    obj  폴객체 포인터
    @param    buf  일기버퍼
    @param    len  버퍼의 길이
    @return   읽은 데이타 개수
*///----------------------------------------------------------------------------
int  uart_read( poll_obj_t *obj, char *buf, int len )
{
	int  rdcnt;

	// 데이타를 읽는다.
#ifdef EMBEDDED_LINUX

	rdcnt = read( obj->fd, buf, len );
	
#else

	ReadFile( obj->fd, buf, len, (LPDWORD)((void *)&rdcnt), NULL);

#endif

	if ( 0 > rdcnt )
	{
		perror( "uart recv error:" );
	}
	
	return rdcnt;
}
//------------------------------------------------------------------------------
/** @brief    uart 내부 수신버퍼에 데이타를 저장한다.
    @param    obj  폴객체 포인터
    @return   내부수신버퍼의 총 데이타 길이
*///----------------------------------------------------------------------------
int  uart_read_into_fifo( poll_obj_t *obj )
{
	uart_priv_t *uart;
	char *buf;
	int   rdcnt, len;
	
	uart = (uart_priv_t *)obj->priv;  
	buf  = uart->recv_fifo + uart->fifo_rcnt;
	len  = UART_RECV_FIFO_MAX - uart->fifo_rcnt;
	if ( 0 >= len )
	{
		printf( "uart recv buffer full\n" );
	}
	else
	{
#ifdef EMBEDDED_LINUX

		rdcnt = read( obj->fd, buf, len );

#else

		ReadFile( obj->fd, buf, len, (LPDWORD)((void *)&rdcnt), NULL);

#endif
		if ( 0 < rdcnt )
		{
			uart->fifo_rcnt += rdcnt;
		}
	}
	
	return uart->fifo_rcnt;
}

//------------------------------------------------------------------------------
/** @brief    uart 내부 수신버퍼에서 데이타를 읽어온다.
    @param    obj  폴객체 포인터
    @param    buf  데이타를 담아올 버퍼
    @param    len  버퍼의 길이
    @return   복사된 데이타 길이
    @remark   데이타를 복사한 후 복사한 크기만큼 버퍼의 앞쪽으로 데이타를 이동한다.
*///----------------------------------------------------------------------------
int  uart_copy_recv_fifo( poll_obj_t *obj, char *buf, int len )
{
	uart_priv_t *uart;

	uart = (uart_priv_t *)obj->priv;  
	
	if ( len > uart->fifo_rcnt ) len = uart->fifo_rcnt;
	
	memcpy( buf, uart->recv_fifo, len );
	uart->fifo_rcnt -= len;
	
	if ( 0 < uart->fifo_rcnt )
	{
		memmove( uart->recv_fifo, uart->recv_fifo+len, uart->fifo_rcnt );
	}
	
	return len;
}
//------------------------------------------------------------------------------
/** @brief    uart 내부 수신버퍼의 포인터를 얻는다.
    @param    obj  폴객체 포인터
    @return   uart 내부 수신버퍼의 포인터
*///----------------------------------------------------------------------------
char *uart_get_recv_fifo( poll_obj_t *obj )
{
	uart_priv_t *uart;
	uart = (uart_priv_t *)obj->priv;  

	return uart->recv_fifo;
}
//------------------------------------------------------------------------------
/** @brief    uart 내부 수신버퍼에 저장된 데이타의 갯수를 얻는다.
    @param    obj  폴객체 포인터
    @return   수신버퍼에 저장된 데이타의 갯수
*///----------------------------------------------------------------------------
int  uart_get_recv_fifo_count( poll_obj_t *obj )
{
	uart_priv_t *uart;
	uart = (uart_priv_t *)obj->priv;  

	return uart->fifo_rcnt;
}
//------------------------------------------------------------------------------
/** @brief    uart 내부 수신버퍼에서 앞쪽의 데이타를 제거한 후 뒤쪽의 데이타를 이동한다.
    @param    obj   폴객체 포인터
    @param    len   제거될 데이타 개수
    @return   수신버퍼에 남아있는 데이타의 갯수
*///----------------------------------------------------------------------------
int  uart_checkout_recv_fifo( poll_obj_t *obj, int len )
{
	uart_priv_t *uart;
	uart = (uart_priv_t *)obj->priv;  

	uart->fifo_rcnt -= len;
	if ( 0 > uart->fifo_rcnt ) uart->fifo_rcnt = 0;
	
	if ( 0 < uart->fifo_rcnt )
	{
		memmove( uart->recv_fifo, uart->recv_fifo+len, uart->fifo_rcnt );
	}
	
	return uart->fifo_rcnt;
}
//------------------------------------------------------------------------------
/** @brief    uart 내부 수신버퍼를 비운다.
    @param    obj   폴객체 포인터
*///----------------------------------------------------------------------------
void  uart_clear_recv_fifo( poll_obj_t *obj )
{
	uart_checkout_recv_fifo( obj, uart_get_recv_fifo_count( obj ) );
}
//------------------------------------------------------------------------------
/** @brief    uart에서 비교데이타가 수신될때까지 감시한다.
    @param    obj         폴객체 포인터
    @param    match       비교할 버퍼
    @param    match_len   비교할 버퍼의 크기
    @param    tmout_msec  타임아웃 시간
    @return   성공 0, 실패 -1
*///----------------------------------------------------------------------------
int  uart_recv_wait_fifo( poll_obj_t *obj, unsigned char *match, int match_len, int tmout_msec )
{
	int    rdcnt, poll_ret, deep_wait;
	char  *rbuf;

	deep_wait = 0;

	// 과거의 데이타를 모두 없앤다.
	// 2011-05-25 제거
	// uart_checkout_recv_fifo( obj, uart_get_recv_fifo_count( obj ) );
	
	while( 0 < tmout_msec )
	{
		// 수신데이타를 감시한다.
		poll_ret = poll_do_one( obj->fd, POLLIN, 100 );   
		
		// 이벤트가 없었다면 시간을 감소시킨다.
		if ( poll_ret != POLL_EVENTED  )
		{
			tmout_msec -= 100;
			
			// 1초 동안 데이타가 없었다면 수신버퍼  clear
			deep_wait ++;
			if ( 10 < deep_wait )
			{
				deep_wait = 0;
				uart_checkout_recv_fifo( obj, uart_get_recv_fifo_count( obj ) );\
			}
			continue;
		}
		
		// 통신이 들어왔다면 1mse 만 감소시킨다.
		tmout_msec --;
		deep_wait = 0;

		// 데이타를 버퍼에 넣는다.
		rdcnt = uart_read_into_fifo( obj );
		rbuf  = uart_get_recv_fifo ( obj );

		while( match_len <= rdcnt )
		{
			// 아래 문장은 아직 테스트를  못했다.
			//
			if ( 0 == memcmp( rbuf, match, match_len ) )
			{
				uart_checkout_recv_fifo( obj, uart_get_recv_fifo_count(obj)-rdcnt );
				return 0;			
			}
			
			rbuf  ++;
			rdcnt --;
		}

		if ( 0 < ( uart_get_recv_fifo_count(obj)-match_len ) )
		{
			uart_checkout_recv_fifo( obj, uart_get_recv_fifo_count(obj)-match_len );
		}
	}
	
	return -1;
}