
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>

#include "serial.h"

extern int verbose;

int serial_open(unsigned char *port)
{
	int fd;

	fd = open(port, O_RDWR | O_NONBLOCK);

	if (fd < 0) {
		perror("serial_open: open");
	}

	if (serial_setup(fd, 9600) < 0) {
		close(fd);
		fd = -1;
	}

	serial_drain(fd);

	return fd;
}

void serial_drain(int fd) 
{
	unsigned char buf;

	while (read(fd, &buf, 1) == 1);
}

#define SERIAL_TIMEOUT 100

int serial_send(int fd, unsigned char data)
{
	int k, s;

	for (k = 0; k < SERIAL_TIMEOUT; k++) {
		s = write(fd, &data, 1);

		if ((s == -1) && (errno != EAGAIN)) {
			perror("serial_send: write");
			return -1;
		}

		if (s == 1) {
			break;
		}

		usleep(10000);
	}

	if (k == SERIAL_TIMEOUT) {
		fprintf(stderr, "serial_send: timeout\n");
		return -1;
	}

	if (verbose > 2) {
		printf("send 0x%02x.\n", data);
	}

	return 0;
}

int serial_recv(int fd, unsigned char *buf)
{
	int k, s;

	for (k = 0; k < SERIAL_TIMEOUT; k++) {
		s = read(fd, buf, 1);
		
		if (s == 1) {
			break;
		}

		if ((s == -1) && (errno != EAGAIN)) {
			perror("serial_recv: read");
			return -1;
		}

		usleep(10000);
	}

	if (k == SERIAL_TIMEOUT) {
		fprintf(stderr, "serial_recv: timeout\n");
		return -1;
	}

	if (verbose > 2) {
		printf("recv: 0x%02x.\n", *buf);
	}

	return 0;
}

int serial_write(int fd, unsigned char *buf, int nobytes)
{
	int k;

	for (k = 0; k < nobytes; k++) {
		if (serial_send(fd, buf[k]) < 0) {
			return -1;
		}
	}

	return nobytes;
}

int serial_read(int fd, unsigned char *buf, int nobytes)
{
	int k;

	for (k = 0; k < nobytes; k++) {
		if (serial_recv(fd, &buf[k]) < 0) {
			return -1;
		}
	}

	return nobytes;
}

int serial_setup(int fd, int speed)
{
    struct termios tty;

    if (!isatty(fd)) {
	    fprintf(stderr, "not a tty\n");
	    return -1;
    }

    memset(&tty, 0, sizeof(tty));

    if (tcgetattr(fd, &tty) != 0) {
	perror("tcgetattr");
	return -1;
    }

    switch(speed) {
    case 9600: speed = B9600; break;
    case 19200: speed = B19200; break;
    case 38400: speed = B38400; break;
    case 57600: speed = B57600; break;
    case 115200: speed = B115200; break;
    default:
	    fprintf(stderr, "unsupported bps speed\n");
	    return -1;
    }


    if (cfsetospeed(&tty, speed) != 0) {
	    perror("cfsetospeed");
	    return -1;
    }

    if (cfsetispeed(&tty, speed) != 0) {
	    perror("cfsetispeed");
	    return -1;
    }

    tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;  // 8 data bits

    /* Set into raw, no echo mode */

    tty.c_iflag =  IGNBRK;
    tty.c_lflag = 0;
    tty.c_oflag = 0;
    tty.c_cflag |= CLOCAL | CREAD;

    tty.c_cflag &= ~CRTSCTS; // disable hardware flow control

    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 5;

    tty.c_iflag &= ~(IXON|IXOFF|IXANY);  // no software flow control

    tty.c_cflag &= ~(PARENB | PARODD); // no parity
    tty.c_cflag &= ~CSTOPB;          // one stop bit

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
	perror("tcsetattr");
	return -1;
    }

    return 0;
}

void serial_close(int fd)
{
    close(fd);
}
