#include "com_ttySx.h"

uint32_t com_crc32(uint8_t* buf, int len) 
{
    int i, j;
    uint32_t crc, msk;

    i = 0;
    crc = 0xFFFFFFFF;
    while (i < len) {
        uint32_t b = buf[i];
        crc = crc ^ b;
        for (j = 7; j >= 0; j--) {
            msk = -(crc & 1);
            crc = (crc >> 1) ^ (0xEDB88320 & msk);
        }
        i = i + 1;
    }
    return crc;
}

int comm_ttySx_reset(com_ttySx_t* dev)
{
    if (dev)
    {
        if (dev->fd > 0)
        {
            tcflush(dev->fd, TCIOFLUSH);
            close(dev->fd);
            dev->fd = -1;
        }
        dev->fd = open(dev->devname, O_RDWR | O_NOCTTY | O_SYNC);//| O_NOCTTY | O_NDELAY
        if (dev->fd > 0) {
            if (tcsetattr(dev->fd, TCSANOW, &dev->curr_termios) == 0) 
            {
                return 0;
            }
            printe("tty:%s\n",dev->devname);
            return -3;
        }
        printe("tty:%s\n",dev->devname);
        return -2;
    }
    return -1;
}

int com_ttySx_init(com_ttySx_t* dev, const char* devname, int baudrate, int databits, int parbits, int stopbits)
{
    printi("devname:%s, B:%d, %d, %c, %d\n", devname, baudrate, databits, parbits, stopbits);
    sprintf(dev->devname, "%s", devname);
    dev->debug = 0;
    dev->fd = open(devname, O_RDWR | O_NOCTTY | O_SYNC);//| O_NOCTTY | O_NDELAY
    if (dev->fd < 0) {
        printe("tty:%s\n",dev->devname);
        return errno;
    }
    memset(&dev->save_termios, 0, sizeof(dev->save_termios));
    if (tcgetattr(dev->fd, &dev->save_termios) != 0) 
    {
        printe("tty:%s\n",dev->devname);
        return errno;
    }
    // dev->curr_termios = dev->save_termios;
    memset(&dev->curr_termios, 0, sizeof(dev->curr_termios));

    int baud = 0;
    switch (baudrate) {
        case 1500000: baud = B1500000; break;
        case 1152000: baud = B1152000; break;
        case 1000000: baud = B1000000; break;
        case 921600:  baud = B921600; break;
        case 576000:  baud = B576000; break;
        case 500000:  baud = B500000; break;
        case 460800:  baud = B460800; break;
        case 230400:  baud = B230400; break;
        case 115200:  baud = B115200; break;
        case 57600:   baud = B57600;  break;
        case 38400:   baud = B38400;  break;
        case 19200:   baud = B19200;  break;
        case 9600:    baud = B9600;   break;
        default: baud = B4800;
    }
    
    cfsetospeed(&dev->curr_termios, baud);
    cfsetispeed(&dev->curr_termios, baud);
    
    dev->curr_termios.c_cflag |= CLOCAL | CREAD; /* ignore modem control lines and enable receiver */
    dev->curr_termios.c_cflag &= ~CSIZE;
    switch(databits)
    {
        case 5: dev->curr_termios.c_cflag |= CS5; break;
        case 6: dev->curr_termios.c_cflag |= CS6; break;
        case 7: dev->curr_termios.c_cflag |= CS7; break;
        default: dev->curr_termios.c_cflag |= CS8;
    }
    
    switch(parbits)
    {
        case 'o':
        case 'O': 
            dev->curr_termios.c_cflag |= PARENB; 
            dev->curr_termios.c_cflag |= PARODD; 
            dev->curr_termios.c_iflag |= INPCK; 
            dev->curr_termios.c_iflag &=~ISTRIP; 
            break;
        case 'e':
        case 'E': 
            dev->curr_termios.c_cflag |= PARENB; 
            dev->curr_termios.c_cflag &=~PARODD; 
            dev->curr_termios.c_iflag |= INPCK; 
            dev->curr_termios.c_iflag &=~ISTRIP; 
            break;
        case 'n':
        case 'N':
        default:  
            dev->curr_termios.c_cflag &=~PARENB; 
            dev->curr_termios.c_cflag &=~PARODD;
    }
    dev->curr_termios.c_lflag &=~ICANON;//原始模式 
    //dev->curr_termios.c_lflag |=ICANON; //标准模式
    dev->curr_termios.c_cflag &= ~CRTSCTS;
    switch(stopbits)
    {
        case 1:dev->curr_termios.c_cflag &= ~CSTOPB;break;
        default:dev->curr_termios.c_cflag |= CSTOPB;break;
    }

	/* set timeout in deciseconds for non-canonical read */
	dev->curr_termios.c_cc[VTIME] = 0;
	/* set minimum number of characters for non-canonical read */
	dev->curr_termios.c_cc[VMIN] = 0;
	/* flushes data received but not read */
	tcflush(dev->fd, TCIOFLUSH);

    if (tcsetattr(dev->fd, TCSANOW, &dev->curr_termios) != 0) 
    {
        printe("tty:%s\n",dev->devname);
        return errno;
    }

    return 0;
}

int com_ttySx_send(com_ttySx_t* dev, char* txbuff, int txlen)
{
    if (dev)
    {
        if (dev->fd > 0)
        {
            if (dev->debug) printline(txbuff, txlen, "%s->send", dev->devname);
            int ret = write(dev->fd, txbuff, txlen);
            if (ret < 0)
            {
                close(dev->fd);
                dev->fd = -1;
            }
            return ret;
        }
        return -2;
    }
    return -1;
}

int com_ttySx_recv(com_ttySx_t* dev, char* rxbuff, int rxsize)
{
    if (dev)
    {
        if (dev->fd > 0)
        {
            int ret = read(dev->fd, rxbuff, rxsize);
            if (dev->debug) printline(rxbuff, ret, "%s->recv", dev->devname);
            if (ret < 0)
            {
                close(dev->fd);
                dev->fd = -1;
            }
            return ret;
        }
        return -2;
    }
    return -1;
}

int com_ttySx_flush(com_ttySx_t* dev)
{
    if (dev)
    {
        if (dev->fd > 0)
        {
            return tcflush(dev->fd, TCIOFLUSH);
        }
        return -2;
    }
    return -1;
}

int com_ttySx_recv_with_timeout(com_ttySx_t* dev, char* rxbuff, int max_bytes, int timeout)
{
    if (dev)
    {
        if (dev->fd >0)
        {
            fd_set rset;
            struct timeval tv;
            FD_ZERO(&rset);
            FD_SET(dev->fd, &rset);
            tv.tv_sec = timeout/1000;
            tv.tv_usec = (timeout%1000) * 1000;
            int ret = select(dev->fd + 1, &rset, NULL, NULL, &tv);
            if (ret > 0)
            {
                if(FD_ISSET(dev->fd, &rset))
                {
                    int read_bytes = 0;
                    while(read_bytes < max_bytes)
                    {
                        int readb = read(dev->fd, &rxbuff[read_bytes], (max_bytes - read_bytes));
                        if (readb>=0)
                        {
                            read_bytes += readb;
                        }else{
                            printe("tty:%s, read:%d, errno:%d\n", dev->devname, readb, errno);
                            break;
                        }
                    }
                    if (dev->debug) printline(rxbuff, read_bytes, "%s->recv", dev->devname);
                    return read_bytes;
                }
            }
            return ret;
        }
        return -2;
    }
    return -1;
}

int com_ttySx_rs485_enable(com_ttySx_t* dev, int enable)
{
    if (dev)
    {
        if (dev->fd >0)
        {
            struct serial_rs485 rs485conf;

            /* Get configure from device */
            int ret = ioctl(dev->fd, TIOCGRS485, &rs485conf);
            if (ret < 0) {
                printe("tty:%s\n",dev->devname);
                return errno;
            }

            /* Set enable/disable to configure */
            if (enable == 1) {   // Enable rs485 mode
                rs485conf.flags |=  SER_RS485_ENABLED;
            }
            if (enable == 0){    // Disable rs485 mode
                rs485conf.flags &= ~SER_RS485_ENABLED;
            }

            rs485conf.flags |=  SER_RS485_RTS_ON_SEND;
            rs485conf.flags &=  ~SER_RS485_RTS_AFTER_SEND;

            rs485conf.delay_rts_before_send = 0x00000008;
            rs485conf.delay_rts_after_send = 0x00000000;
            /* Set configure to device */
            ret = ioctl(dev->fd, TIOCSRS485, &rs485conf);
            if (ret < 0) {
                printe("tty:%s\n",dev->devname);
                return errno;
            }

            return ret;
        }
        return -2;
    }
    return -1;
}

int com_ttySx_rs485_control(com_ttySx_t* dev, int send)
{
    return 0;
    if (dev)
    {
        if (dev->fd >0)
        {
            struct serial_rs485 rs485conf;

            /* Get configure from device */
            int ret = ioctl(dev->fd, TIOCGRS485, &rs485conf);
            if (ret < 0) {
                printe("tty:%s\n",dev->devname);
                return errno;
            }

            /*rs485 send - recv*/
            if(send == 1)
                rs485conf.flags |=  SER_RS485_RTS_AFTER_SEND;
            if(send == 0)
                rs485conf.flags &= ~SER_RS485_RTS_AFTER_SEND;

            /* Set configure to device */
            ret = ioctl(dev->fd, TIOCSRS485, &rs485conf);
            if (ret < 0) {
                printe("tty:%s\n",dev->devname);
                return errno;
            }
            
            return ret;
        }
        return -2;
    }
    return -1;
}

void com_ttySx_exit(com_ttySx_t* dev)
{
    if (dev)
    {
        if (dev->fd >= 0)
        {
            if (tcsetattr(dev->fd, TCSANOW, &dev->save_termios) != 0)
            {
                printe("tty:%s\n",dev->devname);
            }
            close(dev->fd);
            dev->fd = -1;
        }
    }
}