/**
 * @file cats.c
 * @brief Main daemon code
 *
 * @author Michel Blanc
 * @date 2009-11-14
 *
 * @version 1.0
 *
 * - Project: cats
 * - Tabsize: 2
 * - Copyright: (c) 2008 ERASME
 * - License: GNU GPL v2
 * - Part of this code (c) Objective Development Gmbh (USB)
 *
 * This program controls the microcontroller via USB, and
 * receive it's orders in OSC. It also sends OSC events
 * when the capacitive button is touched/released.
 *
 * @sa cats.c
 */

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <syslog.h>
#include <fcntl.h> 
#include <termios.h>
#include <sys/types.h>
#include <unistd.h>

#define VERSION 1.0
#define BAUD_DEFAULT B9600

/** 
 * @brief Boolean type 
 *
 */
#ifdef TRUE
#  ifndef boolean_t
#    define boolean_t int
#  endif
#else
#  ifdef boolean_t
#    define FALSE 0
#    define TRUE 1
#  else
     typedef enum {FALSE, TRUE} boolean_t;
#  endif
#endif

boolean_t oDebug = 2;

static void 
debug(int level, const char * template, ...)
{
  char buffer[1024];
  va_list ap;

  va_start (ap, template);

	if (oDebug < level) return;
	vfprintf(stderr, template, ap);
	fprintf(stderr,"\n");
	fflush(stderr);

  va_end (ap);

  return;
}

/**
 * @brief : Initialize the serial port with some tweaks.
 *
 * Open the serial port and set parameters for it.
 * Mainly, it is set at 8 data bits, no parity, 1 stop bit.
 * Port is also set RAW, and VMIN is set to 2 since all data
 * that need to be read has even bytes.
 *
 * @param fd 
 *  The file descriptor to read from
 * @param baudrate 
 *  The baudrate to set
 * @return 
 *  TRUE if happy, FALSE if something went wrong.
 * @warning 
 *  This function should not be used externally
 */

boolean_t 
serialInitPort(int fd, int baudrate) 
{

	// use :   fd = open(port, O_RDWR | O_NOCTTY);

	struct termios options;

  debug(LOG_DEBUG,"Setting port attributes");

	/* Get the current options for the port */
	tcgetattr(fd, &options);

	/* Set the baud rates to BAUDRATE */
  if (cfsetispeed(&options, baudrate) == -1)
    debug(LOG_WARNING,"cfsetispeed error ");

	if (cfsetospeed(&options, baudrate))
    debug(LOG_WARNING,"cfsetospeed error ");

  /* 8N1 */
  options.c_cflag &= ~PARENB;
  options.c_cflag &= ~CSTOPB;
  options.c_cflag &= ~CSIZE;
  options.c_cflag |= CS8;

  /* No hardware flow control */
  options.c_cflag &= ~CRTSCTS;
  
  /* Turn on READ & ignore ctrl lines */
  options.c_cflag |= CREAD | CLOCAL;  

  /* No software flow control */
  options.c_iflag &= ~(IXON | IXOFF | IXANY);
  
  /* Make raw */
  options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
  options.c_oflag &= ~OPOST;
  
  /* see: http://unixwiz.net/techtips/termios-vmin-vtime.html */
  options.c_cc[VMIN]   = 1; 
  options.c_cc[VTIME]  = 0; 
  
	/* Apply the new options for the port */
	tcsetattr(fd, TCSANOW, &options);

  return TRUE;
}

/**
 * @brief : Serial device lookup and opening
 *
 * opens device given in parameters. If device is NULL, 
 * this function will try to autodetect the port (NOT UMPLEMENTED).
 *
 * @param[out] fd The file descriptor to read to/write from is open is successful.
 * @param[in] port The serial rort device to use (e.g. "/dev/ttyUSB1")
 * @param[in] baudrate The communication baud rate (only 9600, 19200 and 57600 are supported)
 *
 * @return 0 on failure, a real fd number otherwise.
 *
 */

int 
serialOpen(const char *port, int baudrate) {
  int baud_sym;

	int fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
	//	fcntl(fd, F_SETFL, FNDELAY);

  debug(LOG_DEBUG,"Opening port %s...", port);

  if (fd == -1) {
		debug(LOG_ALERT,"error: port %s opening failed. Permission problem ?", port);
    exit(EXIT_FAILURE);
  } 

	fcntl(fd, F_SETFL, 0);
  debug(LOG_INFO,"Port %s opened successfuly", port);

  /* Set port attributes (baudrate only) */
	// Check that argv[argc-1] 
	switch (baudrate) {
	case 50: baud_sym = B50; break;
	case 75: baud_sym = B75; break;
	case 110: baud_sym = B110; break;
	case 134: baud_sym = B134; break;
	case 150: baud_sym = B150; break;
	case 200: baud_sym = B200; break;
	case 300: baud_sym = B300; break;
	case 600: baud_sym = B600; break;
	case 1200: baud_sym = B1200; break;
	case 1800: baud_sym = B1800; break;
	case 2400: baud_sym = B2400; break;
	case 4800: baud_sym = B4800; break;
	case 9600: baud_sym = B9600; break;
	case 19200: baud_sym = B19200; break;
	case 38400: baud_sym = B38400; break;
	case 57600: baud_sym = B57600; break;
	case 115200: baud_sym = B115200; break;
	case 230400: baud_sym = B230400; break;
	default: printf("Error : unsupported baud rate"); exit(EXIT_FAILURE);
	}

	serialInitPort(fd, baudrate);

  debug(LOG_INFO,"Baudrate set successfuly to %d", baudrate);

	return fd;
}

/**
 * @brief : Usage of command line switches
 *
 * Usually called when a wrong command line switch is used, or
 * when a mandatory option is missing (but there is not as of now).
 */
void 
usage()
{
  fprintf(stdout, "cats, version %.2f\n\n",VERSION);
  fprintf(stdout, "  Usage : cats [-h] [-g <0-7>] [-b <baud>]<port>\n\n");

  fprintf(stderr, "\t-h : this help\n");
  fprintf(stderr, "\t<port> : serial port (default : none\n");
  fprintf(stderr, "\t-b <rate> : serial baud rate (default : 9600)\n");
  fprintf(stderr, "\t-g <0-7> : debug level (default : no debug, see sys/syslog.h for levels)\n");
  fprintf(stderr, "\n  Supported baud rates :\n");
	fprintf(stderr, "\t\t50 75 110 134 150 200 300 600 1200 1800\n");
  fprintf(stderr, "\t\t2400 4800 9600 19200 38400 57600 115200 230400\n");
  fprintf(stderr, "\n  Example : cats -g7 -b19200 /dev/ttyUSB0\n");

  fprintf(stderr,"\n");
}

/**
 * @brief This never ending loop is called in USB mode
 *
 */
void 
serialLoop(const char *serialPort, int baudRate) {
	int            fd;
	char           data[1024];
	int            n;
	int            nbytes;
	int            max_fd;
	fd_set         input;
	struct timeval timeout;

	fd = serialOpen(serialPort, baudRate);
	
	/* Initialize the input set */
	FD_ZERO(&input);

	while (1) {
		FD_SET(fileno(stdin), &input); // stdin
		FD_SET(fd, &input);
		
		max_fd = fd + 1;
		
		/* Initialize the timeout structure */
		timeout.tv_sec  = 10;
		timeout.tv_usec = 0;
		
		/* Do the select */
		n = select(max_fd, &input, NULL, NULL, &timeout);

		/* See if there was an error */
		if (n < 0) {
			debug(LOG_DEBUG, "select critical");
			perror("select failed");
		}	else if (n == 0) {
			debug(LOG_DEBUG, "select timeout");
		} else {
			/* We have input */
			if (FD_ISSET(fd, &input)) {
				bzero(data,1024);
				if (read(fd, data, 1024)) {
					printf("%s", data);
					fflush(stdout);
				}
			}
			if (FD_ISSET(0, &input)) {
				bzero(data,1024);
				nbytes = read(fileno(stdin), data, 1024);
				write(fd, data, nbytes);
			}
		}
	}
}

/**
 * @brief Well, this is main...
 *
 * Just reading from fds, writing from fds
 *
 * @return Hopefuly not
 */
int 
main(int argc, char **argv)
{
  char c;
	int  oSerialBaudRate = 0;

  /* thread stuff */
  while ((c = getopt(argc, argv, "hg::b:")) != -1 ) {
		debug(LOG_DEBUG, "handling option %c",c);
    switch (c) {
    case 'g':
      oDebug = atoi(optarg);
      break;
    case 'b':
      oSerialBaudRate = atoi(optarg);
      break;
    case '?':
      if (isprint (optopt))
          fprintf (stderr, "Unknown option `-%c'.\n", optopt);
      else
        fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
    case 'h':
    default:
      usage();
      exit(EXIT_SUCCESS);
    }
  }

	if (!oSerialBaudRate) {
	  debug(LOG_NOTICE, "baud rate unspecified, using 9600");
		oSerialBaudRate = 9600;
	}

  debug(LOG_DEBUG, "finished handling options");

	if (!strstr(argv[argc-1],"/dev/tty")) {
		debug(LOG_WARNING, "invalid serial port specified : %s", argv[argc-1]);
	}

	serialLoop(argv[argc-1], oSerialBaudRate);

	return 0;
}


/* Local Variables: */
/* mode:c           */
/* comment-column:0 */
/* compile-command: "gcc cats.c -o cats" */
/* tab-width: 2 */
/* End:             */

