/* ======================================================================
**
** H2O  Generic Serial I/O Communications
**	open_serial_port, read_port, write_to_port, close_port
**
** serial.c - SL 12/2000
**
** History:
**       Date       Who      Description
**       ------     ---      --------------------------------------------
**      12/2000     SL       Create from Ryan Eustice serial.c,LLW sio.cpp,
**                           and linux how-to
**      04/2001   SL/JCH     added disable XON/XOFF to no-flow control option
**                           added ICRNL,INLCR,IGNCR,INPCK,ISTRIP for raw-mode
**      07/2001     rme      Changed some DPRINTFs verbose from MSG to DBG1
**      12/2004     SL       Ported to Win2000/XP/Cygwin - elim constant bauds > 115k
** ======================================================================
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>    /* UNIX standard function definitions */
#include <fcntl.h>     /* file control definitions           */
#include <termios.h>   /* POSIX terminal control definitions */

#include "serial_io.h"
#include "debug.h"

//*************************************************************************
//
//  int set_serial_port() --  Configures serial port based on io_cfg params
//                            Note: serial port must be opened first
//                            Returns 1-OK, 0-Error
//
//*************************************************************************
int set_serial_port(io_cfg_t *io)
{
  struct termios tio;
  
  //Get current options for the port
  tcgetattr(io->port_fd, &tio);
  
  // set tio.cflag - Enable the receiver and set local mode
  // CLOCAL - ignore modem control lines like carrier detect 
  // CREAD  - enable uart receiver
  tio.c_cflag |= (CLOCAL | CREAD); 

  // set tio.oflag - raw
  tio.c_oflag &= ~OPOST;

  // set tio.iflag to raw
  tio.c_iflag &= ~(ICRNL | INLCR | IGNCR | INPCK | ISTRIP);

  // ignore incoming BREAK events
  tio.c_iflag |= IGNBRK;

  
  /*----------set in/out baud rate------------------*/
  //define macro to handle variety of baud rates
  #define setbaud( BAUD) case BAUD: cfsetispeed(&tio, B ## BAUD); cfsetospeed(&tio, B ## BAUD); break
  switch (io->baud)
    { setbaud (0);
      setbaud (50);
      setbaud (75);
      setbaud (110);
      setbaud (134);
      setbaud (150);
      setbaud (200);
      setbaud (300);
      setbaud (600);
      setbaud (1200);
      setbaud (1800);
      setbaud (2400);
      setbaud (4800);
      setbaud (9600);
      setbaud (19200);
      setbaud (38400);
      setbaud (57600);
      setbaud (115200);
      //setbaud (230400);
      //setbaud (460800);
      //setbaud (500000);
      //setbaud (576000);
      //setbaud (921600);
      //setbaud (1000000);
      //setbaud (1152000);
      //setbaud (1500000);
      //setbaud (2000000);
      //setbaud (2500000);
      //setbaud (3000000);
      //setbaud (3500000);
      //setbaud (4000000);
    default:
      cfsetispeed(&tio, B9600);
      cfsetospeed(&tio, B9600);
      break;
    }
  DPRINTF(MSG,io->vlevel,printf("Port baud rate set at: %d\t\t\t\t\t[serial.c]\n", 
          io->baud));

  // switch statement to set the character size width in tio.cflag
  tio.c_cflag &= ~CSIZE;
  switch (io->nbits)
    {case  5: tio.c_cflag |= CS5; break;
     case  6: tio.c_cflag |= CS6; break;
     case  7: tio.c_cflag |= CS7; break;
     case  8: tio.c_cflag |= CS8; break;
     default: tio.c_cflag |= CS8; break;
     }

  // set stop bit parametrs in tio.cflag
  switch (io->sbits)
    {case  2: tio.c_cflag |= CSTOPB;  break;
     case  1:
     default: tio.c_cflag &= ~CSTOPB; break;
     }


  /*----------------set parity------------------------------------*/
  if (io->parity == 'N' || io->parity == 'n')      //no parity
    {tio.c_cflag &= ~PARENB;
     }
  else if (io->parity == 'E' || io->parity == 'e') // even parity
    {tio.c_cflag |= PARENB;
     tio.c_cflag &= ~PARODD;
     tio.c_iflag |= (INPCK | ISTRIP);	// enable input parity checking
     }
  else if (io->parity == 'O' || io->parity == 'o') // odd parity
    {tio.c_cflag |= PARENB;
     tio.c_cflag |= PARODD;
     tio.c_iflag |= (INPCK | ISTRIP);	// enable input parity checking
     }
  else
    {//default to noparity
     tio.c_cflag &= ~PARENB;
     }

  /*-----------set hardware flow control-----------------*/
  // set flow control parametrs - flowctl 0,1,2,3
  switch (io->flowctl)
   {case  1: tio.c_iflag |= IXON | IXOFF; // enable software XON/XOFF flow ctl
             break;
    case  2: tio.c_cflag |= CRTSCTS; // enable hardware RTC CTS flow control
             break;
    case  3: //FLOW_CONTROL_XONOFF_RTSCTS:
             tio.c_iflag |= IXON | IXOFF; // enable XON/XOFF
             tio.c_cflag |= CRTSCTS;      // enable hardware RTC CTS flow control
             break;
    case  0: //default to no flow control
    default: tio.c_cflag &= ~CRTSCTS;
             tio.c_iflag &= ~(IXON | IXOFF); // disable XON/XOFF
             break;
    }


  // Canonical or raw mode on input
  if (io->canonical)
    {tio.c_lflag |= (ICANON); 
     tio.c_lflag &= ~(ECHO | ECHOE);  /* no echo */
     tio.c_cc[VEOL] = io->terminator; /* canonical eol character */
     }
  else
    {// set input mode raw (non-canonical, no echo,...) 
     tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
     }

  // If echo flag turned-on, set it (default: off)
  if (io->echo)
    {tio.c_lflag |= (ECHO | ECHOE);
     } 

  //Set char timeout - valid only in non-canonical mode
  tio.c_cc[VTIME] = io->vtime;	/* inter-character timer unused */
  tio.c_cc[VMIN]  = io->vmin;	/* blocking read until at least n chars rcvd */


  // flush the serial port
  tcflush(io->port_fd, TCIFLUSH);

  /*-------send the new configuration to the port------------*/
  tcsetattr(io->port_fd, TCSANOW, &tio);
  /*---------------------------------------------------------*
   * TCSANOW:   make changes occur immediately without       *
   *            waiting for data to complete                 *
   * TCSADRAIN: wait until everything has been transmitted   *
   * TCSAFLUSH: flush input and ouput buffers and make       *
   *            the change                                   *
   *---------------------------------------------------------*/

   return(1);
}



/************************************************************************
 *  int write_to_port() -- Public: Writes a string to desired port      *
 ************************************************************************/
int write_to_port(int fd, char *msg, int msg_length, int vlevel)
{
  int n;
  
  n = write(fd, msg, msg_length);
  if (n > 0)
    {DPRINTF(DBG1,vlevel,printf("## Wrote %d characters to port (fd=%d): ##\t\t\t\t[serial.c]\n%s",
		    msg_length, fd, msg));
     DPRINTF(DBG1,vlevel,printf("## End Write to Port (fd=%d) ##\t\t\t\t\t\t[serial.c]\n",fd));
    }
  else if (n == 0)
    {DPRINTF(ERR,vlevel,printf("## Wrote zero characters to port (fd=%d)##\t\t\t\t[serial.c]\n",fd));
     }
  else if (n == -1)
    {DPRINTF(ERR,vlevel,printf("write_to_port[fd=%d] failed: %s\t\t\t[serial.c]\n",
		    fd,strerror(errno)));
      /*fprintf(stderr, "write_to_port failed: %s\t\t\t[serial.c]\n",strerror(errno)); */
    }
  
  (void) fflush(NULL);
  return n;
}

/************************************************************************
 *  int read_port() -- Public: Reads port buffer into string.           *
 ************************************************************************/
int read_port(int fd, char *buff, int buff_length, int vlevel)
{
  int n;

  DPRINTF(DBG1,vlevel,printf("Waiting to read up to %d chars from fd: %d\n", buff_length,fd));

  n = read(fd, buff, buff_length);
  if (n > 0)
    {
      *(buff + n) = '\0';
      DPRINTF(DBG1,vlevel,printf(("## Read %d characters from port %d: ##"
		     "\t\t\t\t[serial.c]\n"), n, fd));
      DPRINTF(DBG1,vlevel,printf("%s\n", buff));
      DPRINTF(DBG1,vlevel,printf("##  End read of port %d ##\t\t\t\t\t[serial.c]\n", fd));
    }
  else if (n == 0)
    {
      //DPRINTF(WNG,vlevel,printf("## Buffer empty [fd=%d]##\t\t\t\t\t\t[serial.c]\n",
      //      fd));
      *buff = '\0';
    }
  else if (n == -1)
    {
      *buff = '\0';
      //Temporarily make DBG level for non-blocking reads
      DPRINTF(DBG,vlevel,err_print(errno,"***read_port failed"));
    }
  
  (void) fflush(NULL);
  return n;
}

/*************************************************************************
 *  void close_port() -- Public: Closes connection to port.              *
 *************************************************************************/
void close_port(int fd, int vlevel)
{
  if (fd > 0)
    { close(fd);
      DPRINTF(MSG,vlevel,printf("## Port %d closed ##\t\t\t\t\t\t[serial.c]\n", fd));
      (void) fflush(NULL);
    }
  else
    { DPRINTF(ERR,vlevel,printf(("## Can't close port %d: Bad file "
		                 "descriptor ##\t\t\t[serial.c]\n"), fd));
      (void) fflush(NULL);
    }
}

/*************************************************************************
 *  void send_break() -- Public: Sends hard break to port.               *
 *************************************************************************/
void send_break(int fd, int duration)
{
  if (fd > 0)
    {
      tcsendbreak(fd, duration);
      (void) printf("## Port %d sent hard break ##\t\t\t\t\t[serial.c]\n",
		    fd);
      (void) fflush(NULL);
    }
  else
    {
      (void) printf(("## Can't send hard break to port %d: Bad file "
		    "descriptor ##\t[serial.c]\n"), fd);
      (void) fflush(NULL);
    }
}





