//=================================================================
//
//        eCosTestSerial.cpp
//
//        Serial test class
//
//=================================================================
//####COPYRIGHTBEGIN####
//
// -------------------------------------------
// The contents of this file are subject to the Cygnus eCos Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://sourceware.cygnus.com/ecos
// 
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
// License for the specific language governing rights and limitations under
// the License.
// 
// The Original Code is eCos - Embedded Cygnus Operating System, released
// September 30, 1998.
// 
// The Initial Developer of the Original Code is Cygnus.  Portions created
// by Cygnus are Copyright (C) 1998, 1999 Cygnus Solutions.
// All Rights Reserved.
// -------------------------------------------
//
//####COPYRIGHTEND####
//=================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):     sdf
// Contributors:  sdf
// Date:          1999-04-01
// Description:   This class abstracts the serial port for use in the 
//                testing infrastructure
// Usage:
//
// To Do:
//  o Unix body
//
//####DESCRIPTIONEND####

#include "stdafx.h"
#include "eCosTestUtils.h"
#include "eCosTestSerial.h"
#include "eCosTestSocket.h"

CeCosTestSerial::CeCosTestSerial():
    m_pHandle(0),
    m_nBaud(0),
    m_bParity(false),
    m_nDataBits(8),
    m_nStopBits(ONE),
    m_nTotalReadTimeout(10*1000),
    m_nTotalWriteTimeout(10*1000),
    m_nInterCharReadTimeout(500),
    m_nInterCharWriteTimeout(500) 
{
}

CeCosTestSerial::~CeCosTestSerial()
{
    Close();
}

bool CeCosTestSerial::Close()
{
    bool rc=m_pHandle && (-1!=close((int)m_pHandle));
    m_pHandle=0;
    return rc;
}

CeCosTestSerial::CeCosTestSerial(const char *pszPort,int nBaud):
    m_pHandle(0),
    m_bParity(false),
    m_nDataBits(8),
    m_nStopBits(ONE),
    m_nTotalReadTimeout(10*1000),
    m_nTotalWriteTimeout(10*1000),
    m_nInterCharReadTimeout(500),
    m_nInterCharWriteTimeout(500),
    m_bBlockingReads(true)
{
    Open(pszPort,nBaud);
}

bool CeCosTestSerial::Open(const char *pszPort,int nBaud)
{
	bool rc=false;
    m_nBaud=nBaud,
    m_strPort=pszPort;
	int fd = open(pszPort,O_RDWR|O_NONBLOCK);
	if (-1==fd) { 
		TRACE("Failed to open port %s\n",pszPort);
                return false;
	} else {
            m_pHandle=(void *)fd;
            if(ApplySettings()){
                rc=true;
            } else {
                Close();
                TRACE("Failed to apply settings.\n");
                return false;
            }
	}
    return rc;
}

bool CeCosTestSerial::ApplySettings()
{
    struct termios buf;
    int rate;

    const char *arpszStopbits[3]={"1","1.5","2"};
    TRACE("Applysettings baud=%d bParity=%d stopbits=%s databits=%d\n",
        m_nBaud, 
        m_bParity, 
        arpszStopbits[m_nStopBits],
        m_nDataBits);

    switch(m_nBaud) {
    case 110:
        rate = B110;
        break;
    case 150:
        rate = B150;
        break;
    case 300:
        rate = B300;
        break;
    case 600:
        rate = B600;
        break;
    case 1200:
        rate = B1200;
        break;
    case 2400:
        rate = B2400;
        break;
    case 4800:
        rate = B4800;
        break;
    case 9600:
        rate = B9600;
        break;
    case 19200:
        rate = B19200;
        break;
    case 38400:
        rate = B38400;
        break;
    case 57600:
        rate = B57600;
        break;
    case 115200:
        rate = B115200;
        break;
    default:
        return false;
    };

    TRACE("Changing configuration...\n");

    // Get current settings.
    if (tcgetattr((int) m_pHandle, &buf)) {
	fprintf(stderr, "Error: tcgetattr\n");
	return false;
    }

    // Reset to raw.
    buf.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
                     |INLCR|IGNCR|ICRNL|IXON);
    buf.c_oflag &= ~OPOST;
    buf.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
    buf.c_cflag &= ~(CSIZE|PARENB);
    buf.c_cflag |= CS8;

    // Set baud rate.
    cfsetispeed(&buf, rate);
    cfsetospeed(&buf, rate);

    // Set data bits.
    {
        int data_bits[9] = {0, 0, 0, 0, 0, CS5, CS6, CS7, CS8};

        buf.c_cflag &= ~CSIZE;
        buf.c_cflag |= data_bits[m_nDataBits];
    }

    // Set stop bits.
    {
        buf.c_cflag &= ~CSTOPB;
        if (ONE != m_nStopBits)
            buf.c_cflag |= CSTOPB;
    }

    // Set parity.
    {
        buf.c_cflag &= ~(PARENB | PARODD); // no parity.
        if (m_bParity)                  // even parity.
            buf.c_cflag |= PARENB;
    }
    
    tcdrain((int) m_pHandle);
    usleep(1000000/10*2);

    // Set the new settings
    if (tcsetattr((int) m_pHandle, TCSADRAIN, &buf)) {
	fprintf(stderr, "Error: tcsetattr\n");
	return false;
    }

    tcdrain((int) m_pHandle);
    usleep(1000000/10*2);

    TRACE("Done.\n");

    return true;
}

bool CeCosTestSerial::Flush (void)
{
    return 0==tcflush((int) m_pHandle, TCIOFLUSH);
}

bool CeCosTestSerial::Read (CeCosTestUtils::String &str)
{
    char *c=str.GetBuffer();
    unsigned int nRead=0;
    bool rc=Read(c,str.GetLength(),nRead);
    c[nRead]='\0';
    str.ReleaseBuffer();
    return rc;
}

bool CeCosTestSerial::Write(const CeCosTestUtils::String &str)
{
    unsigned int nWritten=0;
    return Write((void *)(const char *)str,str.GetLength(),nWritten) && nWritten==str.GetLength();
}

bool CeCosTestSerial::Read (void *pBuf,unsigned int nSize,unsigned int &nRead)
{

    if (!m_bBlockingReads) {
        nRead = 0;
        int n = read((int)m_pHandle, pBuf, nSize);
        if (-1 == n) {
            if (EAGAIN == errno)
                return true;
            TRACE("Read failed: %d\n", errno);
            return false;
        }
        nRead = n;
        return true;
    }

    // Blocking reads: emulate the Windows semantics:
    //  If m_nTotalReadTimeout elapses before we see the first char, 
    //   return.
    //  If m_nInterCharReadTimeout elapses after reading any
    //   subsequent char, return.

    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET((int)m_pHandle, &rfds);

    // Start with total timeout.
    struct timeval tv;
    tv.tv_sec = m_nTotalReadTimeout / 1000;
    tv.tv_usec = (m_nTotalReadTimeout % 1000) * 1000;

    unsigned char* pData = (unsigned char*) pBuf;
    nRead = 0;
    while (nSize) {
        switch(select((int)m_pHandle + 1, &rfds, NULL, NULL, &tv)) {
        case 1:
        {
            int n = read((int)m_pHandle, pData, nSize);
            if (-1 == n && EAGAIN != errno) {
                TRACE("Read failed: %d\n", errno);
                return false;           // FAILED
            }
            nRead += n;
            pData += n;
            nSize -= n;

            // Now use inter-char timeout.
            tv.tv_sec = m_nInterCharReadTimeout / 1000;
            tv.tv_usec = (m_nInterCharReadTimeout % 1000) * 1000;
        }
        break;
        case 0:
            return true;                // Timeout 
        case -1:
            TRACE("Select failed: %d\n", errno);
            return false;
        }
    }

    return true;
}

bool CeCosTestSerial::Write(void *pBuf,unsigned int nSize,unsigned int &nWritten)
{
    bool rc;
    int n=write((int)m_pHandle,pBuf,nSize);
    if(-1==n){
        nWritten=0;
        if (errno == EAGAIN)
            rc = true;
        else
            rc=false;
    } else {
        nWritten=n;
        rc=true;
    }
    return rc;
}

bool CeCosTestSerial::ClearError()
{
    return false;
}

bool CeCosTestSerial:: SetBaud(unsigned int nBaud,bool bApplySettingsNow/*=true*/)
{
    m_nBaud=nBaud;
    return 0==m_pHandle || !bApplySettingsNow || ApplySettings();
}

bool CeCosTestSerial:: SetParity(bool bParityOn,bool bApplySettingsNow/*=true*/)
{
    m_bParity=bParityOn;
    return 0==m_pHandle || !bApplySettingsNow || ApplySettings();
}

bool CeCosTestSerial:: SetDataBits(int n,bool bApplySettingsNow/*=true*/)
{
    m_nDataBits=n;
    return 0==m_pHandle || !bApplySettingsNow || ApplySettings();
}

bool CeCosTestSerial:: SetStopBits(StopBitsType n,bool bApplySettingsNow/*=true*/)
{
    m_nStopBits=n;
    return 0==m_pHandle || !bApplySettingsNow || ApplySettings();
}

bool CeCosTestSerial:: SetReadTimeOuts(int nTotal,int nBetweenChars,bool bApplySettingsNow/*=true*/) // mSec
{
    m_nTotalReadTimeout=nTotal;
    m_nInterCharReadTimeout=nBetweenChars;
    return 0==m_pHandle || !bApplySettingsNow || ApplySettings();
}

bool CeCosTestSerial:: SetWriteTimeOuts(int nTotal,int nBetweenChars,bool bApplySettingsNow/*=true*/) // mSec
{
    m_nTotalWriteTimeout=nTotal;
    m_nInterCharWriteTimeout=nBetweenChars;
    return 0==m_pHandle || !bApplySettingsNow || ApplySettings();
}

bool CeCosTestSerial::SetBlockingReads(bool b,bool bApplySettingsNow/*=true*/)
{
    m_bBlockingReads=b;
    return 0==m_pHandle || !bApplySettingsNow || ApplySettings();
}

