/* --------------------------------------------------------------------
 * $Id: bla_serial_terminal.c,v 1.3 2009/01/24 15:50:00 akurz Exp $
 * simple serial line terminal
 *
 * Copyright 2006 by Alexander Kurz
 *
 * This is free software.
 * You may copy and redistibute this software according to the
 * GNU public Licence 2 or any later verson.
 *
 * ----------------------------------------------------------------- */
#include<sys/types.h>	// stat
#include<sys/stat.h>	// stat
#include<termios.h>	// serial line stuff
#include<fcntl.h>	// O_NONBLOCK flags
#include<unistd.h>	// read()

#include<stdio.h>
#include<errno.h>
#include<string.h>

#define MAX_STRLEN                4096
/* ------------ global ------------ */
struct termios oldtio_l, oldtio_c;

/*-------- print usage-information ------------------------*/
void usage(char* prog){
  fprintf(stderr,
    "Usage: %s [option(s)]\n"
    "without options, connect 9600n81 to /dev/ttyS0\n"
    "type ^X to exit\n"
    "  -h,  --help     print this help-information and exit\n"
    "  -v,  --version  print version and exit\n\n",
     prog);
}

/*-------- check TTY speed --------------------------------*/
speed_t check_termios_speed(const int rate) {
  switch(rate) {
    case 50:    return B50;
    case 75:    return B75;
    case 150:   return B150;
    case 300:   return B300;
    case 600:   return B600;
    case 1200:  return B1200;
    case 2400:  return B2400;
    case 4800:  return B4800;
    case 9600:  return B9600;
    case 19200: return B19200;
    case 38400: return B38400;
    default:	fprintf(stderr,"unsupported TTY speed");
  }
  return B0;
}

/*-------- check and open TTY device ----------------------*/
int check_open_tty(const char* ttyport) {
  int fd;
  /* --------- check tty port */
  struct stat buf;
  if (stat(ttyport,&buf)!=0) {
    fprintf(stderr,"%s: %s\n",ttyport,strerror(errno));
    return -1;
  }
  if (!S_ISCHR(buf.st_mode)) {
    fprintf(stderr,"%s is not a char device\n",ttyport);
    return -1;
  }

  /* --------- open tty port */
  fd = open(ttyport, O_RDWR|O_NONBLOCK|O_NOCTTY);
  if (fd<0) {
    fprintf(stderr,"%s: %s\n",ttyport,strerror(errno));
  }
  return fd;
}

/*-------- save old and set new TTY settings --------------*/
int set_termios(int fd, struct termios* oldtio_p, const speed_t termios_speed) {
  struct termios newtio;
  memset(&newtio, 0, sizeof(newtio));

  /* --------- save old and set new tty parameters */
  if( tcgetattr(fd,oldtio_p) == -1 ){
    perror("failed to save old tty state");
    return 1;
  }

  /* --- control flags */
  /* 8 bits/byte, ignore modem control lines */
  newtio.c_cflag |= CS8 | CLOCAL | CREAD;

  /* 8 bits/byte, ignore modem control lines, hardware flow control */
  /*  newtio.c_cflag |= CS8 | CLOCAL | CREAD | CRTSCTS;	*/

  /* --- output flags */
  /* baud rate */
  cfsetospeed(&newtio,termios_speed);
  /* output processing off */
  newtio.c_oflag &= ~(OPOST);

  /* --- input flags */
  /* input speed same as output speed */
  cfsetispeed(&newtio,0);
  /* dont ignore input parity errors */
  /*  newtio.c_iflag &= ~IGNPAR;*/
  /* enable software flow control */
  /*  newtio.c_iflag |= (IXON | IXOFF);	*/
  /* disable software flow control */
  newtio.c_iflag &= ~(IXON | IXOFF);
  /* no SIGINT on BREAK, CR to NL off, input pariry check off, dont strip 8th bit */
  newtio.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON );

  /* --- local flags */
  /* set echo off, input non-canonical mode, no extended input processing, no signal chars */
  newtio.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);

  /* --- control chars */
  /* inter-character timer unused, blocking read until 1 chars received */
  newtio.c_cc[VTIME] = 0;
  newtio.c_cc[VMIN] = 1;

  /* --- flush old bits out of the input buffer */
  if( tcflush(fd, TCIFLUSH) == -1 ){
    perror("flush tty");
    return 1;
  }

  /* --- write new port settings */
  if( tcsetattr(fd,TCSANOW,&newtio) == -1 ){
    perror("write new tty settings");
    return 1;
  }
  return 0;
}

int main(int argc, char** argv) {
  int fd;
  int baudrate=9600;
  const char ttyport[]	= "/dev/ttyS0";
  speed_t termios_speed;

  fprintf(stderr,"type ^X to exit\n");

  if ((termios_speed = check_termios_speed(baudrate))==B0)	return 1;
  if ((fd = check_open_tty(ttyport))<0)				return 1;
  if (set_termios(fd, &oldtio_l, termios_speed)==-1)		return 1;
  if (set_termios(0, &oldtio_c, termios_speed)==-1)		return 1;

  /* --------- port is ready now */
  if(1) {
    struct timeval tv;
    fd_set readfds;
    int responsetime=3*1000;
    unsigned int size=MAX_STRLEN;

    int num	= 0;		/* number of new bytes read */
    int eoc	= 0;	/* end of communication, nothing new read */
    int quit	= 0;
    char buf[MAX_STRLEN];

    while(!quit) {
      eoc	= 0;
      tv.tv_sec	= (int)(responsetime/1000);
      tv.tv_usec	= 1000*(responsetime-1000*(int)(responsetime/1000));
      while(!eoc){
	FD_ZERO(&readfds);
	FD_SET(fd,&readfds);	/* tty-Port */
	FD_SET(0,&readfds);	/* stdin */
	if (select(fd+1,&readfds,NULL,NULL,&tv)==-1){
	  fprintf(stderr,"error: ttyport %s: %s\n",ttyport,strerror(errno));
	  return 1;
	}
	if (!(FD_ISSET(fd, &readfds)|FD_ISSET(0, &readfds))){      // select timeout
	  eoc		= 1;
	}else{
	  if (FD_ISSET(fd, &readfds)){
	    num = read(fd,buf,size);
	    if (num<0){
	      fprintf(stderr,"error: ttyport %s: %s\n",ttyport,strerror(errno));
	      return 1;
	    }else if (num==0){
	      /* fd is set, but nothing was there to read ---> finish */
	      eoc		= 1;
	    }else{
	      tv.tv_sec	= (int)(responsetime/1000);
	      tv.tv_usec	= 1000*(responsetime-1000*(int)(responsetime/1000));
	      if (write(1,buf,num) != num) {
	        fprintf(stderr,"error: partial/no write %i\n",num);
	        return 1;
	      }
	    }
	  }
	  if (FD_ISSET(0, &readfds)){
	    num = read(0,buf,size);
	    if (num<0){
	      fprintf(stderr,"error: stdin: %s\n",strerror(errno));
	      return 1;
	    }else if (num==0){
	      /* fd is set, but nothing was there to read ---> finish */
	      eoc		= 1;
	    }else{
	      tv.tv_sec	= (int)(responsetime/1000);
	      tv.tv_usec	= 1000*(responsetime-1000*(int)(responsetime/1000));
	      buf[num]	= '\0';
	      if (buf[0] == '\30') { quit=1; break; }
	      if (write(fd,buf,num) != num) {
	        fprintf(stderr,"error: partial/no write %i\n",num);
	        return 1;
	      }
	      tcdrain(fd);	/* wait until all data is written */
	    }
	  }
        }
      }
    }
  }

  /* --------- restore old tty parameters, cleanup */
  tcsetattr(fd,TCSANOW,&oldtio_l);
  tcsetattr(0,TCSANOW,&oldtio_c);
  close(fd);

  return 0;
}

