/* Classic Ladder Project */
/* Copyright (C) 2001-2005 Marc Le Douarain */
/* http://www.multimania.com/mavati/classicladder */
/* http://www.sourceforge.net/projects/classicladder */
/* August 2005 */
/* ------------------------------------ */
/* Serial low-level functions for Linux */
/* ------------------------------------ */

/* This library is free software; you can redistribute it and/or */
/* modify it under the terms of the GNU Lesser General Public */
/* License as published by the Free Software Foundation; either */
/* version 2.1 of the License, or (at your option) any later version. */

/* This library is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU */
/* Lesser General Public License for more details. */

/* You should have received a copy of the GNU Lesser General Public */
/* License along with this library; if not, write to the Free Software */
/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include <locale.h>
#include <libintl.h>
#define _(x) gettext(x)
#include <termios.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h> 
#include <sys/ioctl.h>
#include <errno.h>

#include "serial_common.h"
extern int ModbusSerialUseRtsToSend;
extern int ModbusDebugLevel;
extern int ModbusSerialDataBits;   // Number of data bits (7, 6, 7, 8)
extern int ModbusSerialStopBits;   // Number of stop bits (1 or 2)
extern int ModbusSerialParity;      // Parity (00 = NONE, 01 = Odd, 02 = Even, 03 = Mark, 04 = Space)

int SerialSpeed[ ] = { 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 0 };
int SerialCorres[ ] = { B300, B600, B1200, B2400, B4800, B9600, B19200, B38400, B57600, B115200, 0 };

char PortIsOpened = 0;
int fd;
long DATABITS;
long STOPBITS;
long PARITYON;
long PARITY;

struct termios oldtio;
struct termios newtio;

char SerialOpen( char * SerialPortName, int Speed )
{
	/* if port already opened => close it before */
	if ( PortIsOpened )
		SerialClose( );

	/* open the device to be non-blocking (read will return immediately) */
	fd = open( SerialPortName, O_RDWR | O_NOCTTY | O_NDELAY/*don't wait DTR*/ );
        //fd = open( SerialPortName, O_RDWR | O_NOCTTY | O_NONBLOCK);
	if (fd >=0)
	{
		int BaudRate = -1;
		int ScanBaudRate = 0;
		fcntl(fd, F_SETFL, O_RDWR | O_NOCTTY ); /* perform blocking reads */
		for(ScanBaudRate = 0; SerialSpeed[ScanBaudRate]; ScanBaudRate++)
		{
			if ( SerialSpeed[ ScanBaudRate ]==Speed )
			{
				BaudRate = SerialCorres[ ScanBaudRate ];
				break;
			}
		}
switch (ModbusSerialDataBits)
      {
         case 8:
         default:
            DATABITS = CS8;
            break;
         case 7:
            DATABITS = CS7;
            break;
         case 6:
            DATABITS = CS6;
            break;
         case 5:
            DATABITS = CS5;
            break;
      }  //end of switch data_bits
      switch (ModbusSerialStopBits)
      {
         case 1:
         default:
            STOPBITS = 0;
            break;
         case 2:
            STOPBITS = CSTOPB;
            break;
      }  //end of switch stop bits
      switch (ModbusSerialParity)
      {
         case 0:
         default:                       //none
            PARITYON = 0;
            PARITY = 0;
            break;
         case 1:                        //odd
            PARITYON = PARENB;
            PARITY = PARODD;
            break;
         case 2:                        //even
            PARITYON = PARENB;
            PARITY = 0;
            break;
      }  //end of switch parity
		if ( BaudRate!=-1 )
		{        
			tcgetattr(fd,&oldtio); /* save current port settings */
			/* set new port settings */
			bzero(&newtio, sizeof(newtio));
			//newtio.c_cflag = BaudRate | /*CRTSCTS |*/ CS8 | CLOCAL | CREAD;
                        newtio.c_cflag = BaudRate | DATABITS | STOPBITS | PARITYON | PARITY | CLOCAL | CREAD;
			//newtio.c_cflag |= PARENB
			newtio.c_iflag = IGNPAR    | IGNBRK; // | ICRNL;
			newtio.c_oflag = 0;
			newtio.c_lflag = 0;
			newtio.c_cc[VMIN]=0; //1;
			newtio.c_cc[VTIME]=0;
			tcsetattr(fd,TCSANOW,&newtio);
			PortIsOpened = 1;
		}
		else
		{
			printf(_("Speed value not found for serial\n"));
		}
	}
	else
	{
		printf( _("Failed to open serial port %s...\n"), SerialPortName );
	}
	return PortIsOpened;
}

void SerialClose( )
{
	if ( PortIsOpened )
	{
		PortIsOpened = 0;
		/* restore old port settings */
		tcsetattr(fd,TCSANOW,&oldtio);
		close(fd);
	}
}

void SerialSetRTS( int State )
{
     int status;
	if ( PortIsOpened )
	{
		ioctl(fd, TIOCMGET, &status);
		if ( State )
			status |= TIOCM_RTS;
		else
			status &= ~TIOCM_RTS;
		if ( ModbusDebugLevel>=3 )
			printf( _("Set RTS=%d\n"), State );
		ioctl(fd, TIOCMSET, &status);
	}
}

void SerialSend( char *Buff, int BuffLength )
{
	if ( PortIsOpened )
	{
		if ( ModbusSerialUseRtsToSend )
		{
			SerialSetRTS( 1 );
		}
		if ( ModbusDebugLevel>=2 )
			printf(_("Serial writing...\n"));
		while(BuffLength > 0) {
			int r = write(fd,Buff,BuffLength);
			if(r < 0) {
				if ( ModbusDebugLevel>=1 )
					printf(_("SerialSend failed: %s!\n"), strerror(errno));
				break;
			}
			Buff += r;
			BuffLength -= r;
		}
		if ( ModbusDebugLevel>=2 )
			printf(_("Writing done!\n"));
		// wait until everything has been transmitted
		tcdrain( fd );
		if ( ModbusSerialUseRtsToSend )
		{
			SerialSetRTS( 0 );
		}
	}
}

void SerialSetResponseSize( int Size, int TimeOutResp )
{
	if ( PortIsOpened )
	{
	        newtio.c_cc[VMIN] = Size; //Nbr chars we should receive;
		newtio.c_cc[VTIME] = 1;
//		tcflush(fd, TCIFLUSH);
		if ( ModbusDebugLevel>=2 )
			printf(_("Serial config...\n"));
		tcsetattr(fd,TCSANOW,&newtio);
	}
}

int SerialReceive( char * Buff, int MaxBuffLength, int TimeOutResp )
{
	int NbrCarsReceived = 0;
	if ( PortIsOpened )
	{

                // the select is used it no char at all is received (else read() block...)
                int recep_descrip;
                fd_set myset;
                struct timeval tv;
                FD_ZERO( &myset);
                // add descrip to survey and set time-out wanted !
                FD_SET( fd, &myset );
                tv.tv_sec = TimeOutResp / 1000; //seconds
                tv.tv_usec = (TimeOutResp % 1000) * 1000; //micro-seconds
                if ( ModbusDebugLevel>=3 )
                        printf(_("select() for serial reading...\n"));
                recep_descrip = select( 16, &myset, NULL, NULL, &tv );
                if ( recep_descrip>0 )
                {
                        if ( ModbusDebugLevel>=2 )
                                printf(_("Serial reading...\n"));
                        NbrCarsReceived = read(fd,Buff,MaxBuffLength);
                        if ( ModbusDebugLevel>=2 )
                                printf(_("%d chars found\n"), NbrCarsReceived);
                }
	}
	return NbrCarsReceived;
}

void SerialFlush( void )
{
	if ( PortIsOpened )
	{
		if ( ModbusDebugLevel>=2 )
			printf(_("Serial flush all!\n"));
		tcflush( fd, TCIOFLUSH );
		usleep( 250*1000 );
		tcflush( fd, TCIOFLUSH );
	}
}

