#include "stdafx.h"
#include "Serial.h"


DCB *CBaseSerial::GetState()
{
    return IsOpen() && ::GetCommState(_hCommHandle, &_DCB) == TRUE ? &_DCB : NULL;
}

bool CBaseSerial::SetState(DCB *pdcb /*= NULL*/)
{
    return IsOpen() ? ::SetCommState(_hCommHandle, pdcb == NULL ? &_DCB : pdcb) == TRUE : false;
}

bool CBaseSerial::SetState(TCHAR *lpszSetStr /* = _T("baud=115200 parity=N data=8 stop=1") */)
{
    if (lpszSetStr && lpszSetStr[0] != '\0' && IsOpen())
    {
        if (::GetCommState(_hCommHandle, &_DCB) != TRUE)
            return false;
        // COMx[:][baud=b][parity=p][data=d][stop=s][to={on|off}][xon={on|off}][odsr={on|off}][octs={on|off}][dtr={on|off|hs}][rts={on|off|hs|tg}][idsr={on|off}]
        if (::BuildCommDCB(lpszSetStr, &_DCB) != TRUE)
            return false;
        return ::SetCommState(_hCommHandle, &_DCB) == TRUE;
    }

    return false;
}

bool CBaseSerial::SetState(DWORD dwBaudRate, DWORD dwByteSize /* = 8 */, DWORD dwParity /* = NOPARITY */, DWORD dwStopBits /* = ONESTOPBIT */)
{
    if (!IsOpen()) return false;

    if (::GetCommState(_hCommHandle, &_DCB) != TRUE)
        return false;

    _DCB.BaudRate = dwBaudRate;
    _DCB.ByteSize = (BYTE)dwByteSize;
    _DCB.Parity = (BYTE)dwParity;
    _DCB.StopBits = (BYTE)dwStopBits;
    return ::SetCommState(_hCommHandle, &_DCB) == TRUE;
}

LPCOMMTIMEOUTS CBaseSerial::GetTimeouts()
{
    return IsOpen() && ::GetCommTimeouts(_hCommHandle, &_CO) == TRUE ? &_CO : NULL;
}

bool CBaseSerial::SetTimeouts(DWORD ReadIntervalTimeout /* = 0 */, DWORD ReadTotalTimeoutMultiplier /* = 10 */, DWORD ReadTotalTimeoutConstant /* = 1500 */, DWORD WriteTotalTimeoutMultiplier /* = 10 */, DWORD WriteTotalTimeoutConstant /* = 1500 */)
{
    COMMTIMEOUTS CO = {
        ReadIntervalTimeout, 
        ReadTotalTimeoutMultiplier, 
        ReadTotalTimeoutConstant, 
        WriteTotalTimeoutMultiplier, 
        WriteTotalTimeoutConstant
    };
    return IsOpen() ? ::SetCommTimeouts(_hCommHandle, &CO) == TRUE : false;
}

bool CBaseSerial::SetTimeouts(LPCOMMTIMEOUTS lpCO)
{
    return IsOpen() ? ::SetCommTimeouts(_hCommHandle, lpCO) == TRUE : false;
}

bool CBaseSerial::SetBufferSize(DWORD dwInputSize, DWORD dwOutputSize)
{
    // SetupComm�������������������С;
    return IsOpen() ? ::SetupComm(_hCommHandle, dwInputSize, dwOutputSize) == TRUE : false;
}

void CBaseSerial::SetMaskEvent(DWORD dwEvent /* = DEFAULT_COM_MASK_EVENT */)
{
    _dwMaskEvent = dwEvent;
}

int CBaseSerial::GetInputSize()
{
    COMSTAT Stat;
    DWORD dwError;
    return ::ClearCommError(_hCommHandle, &dwError, &Stat) == TRUE ? Stat.cbInQue : (DWORD)-1L;
}

bool CBaseSerial::Open(DWORD dwPort)
{
    return Open(dwPort, 19200);
}

bool CBaseSerial::Open(DWORD dwPort, DWORD dwBaudRate)
{
    if (dwPort < 1 || dwPort > 1024)
        return false;

    BindCommPort(dwPort);
    if (!OpenCommPort())
        return false;
    if (!SetupPort())
        return false;

    return SetState(dwBaudRate);
}

bool CBaseSerial::Open(DWORD dwPort, TCHAR *lpszSetStr /* = _T("baud/* =115200 parity/* =N data/* =8 stop/* =1") */ )
{
    if (dwPort < 1 || dwPort > 1024)
        return false;

    BindCommPort(dwPort);
    if (!OpenCommPort())
        return false;
    if (!SetupPort())
        return false;

    return SetState(lpszSetStr);
}

DWORD CBaseSerial::Read(LPVOID Buffer, DWORD dwBufferLength, DWORD dwWaitTime /* = 100 */)
{
    if (!IsOpen())
        return 0;

    COMSTAT Stat;
    DWORD dwError;
    if (::ClearCommError(_hCommHandle, &dwError, &Stat) && dwError > 0) {
        ::PurgeComm(_hCommHandle, PURGE_RXABORT | PURGE_RXCLEAR);    // ������뻺������PURGE_RXCLEAR��
        return 0;
    }

#if 0 
    // ��ʱ������Ҫ�ӻ��������������Щ��Ӧ������������ʱ��������;
    if (!Stat.cbInQue)
        return 0; // ������������
#endif
    utils::_dprintf("��������������%ld", Stat.cbInQue);

    unsigned long uReadLength = 0;
    // dwBufferLength = dwBufferLength > Stat.cbInQue ? Stat.cbInQue : dwBufferLength;//��Խ��;
    if (!::ReadFile(_hCommHandle, Buffer, dwBufferLength, &uReadLength, &_ReadOverlapped))  { // ����Buffer������,װ����;
        WaitForSingleObject(_ReadOverlapped.hEvent, dwWaitTime); // �ȴ��첽�������;
        if (::GetLastError() == ERROR_IO_PENDING) {
            while (!::GetOverlappedResult(_hCommHandle, &_ReadOverlapped, &uReadLength, false)) {
                dwError = ::GetLastError();
                if ( dwError == ERROR_IO_PENDING ) {
                    Sleep(50);
                    utils::_dprintf("���ȴ���%ld", dwError);
                } else if ( dwError == ERROR_SUCCESS || dwError == ERROR_IO_INCOMPLETE ) {
                    utils::_dprintf("����ɣ�%ld,%ld,%ld", dwError, uReadLength,_ReadOverlapped.InternalHigh);
                    // ���⣺_ReadOverlapped���������ϳ�����,�ڳ�ʱ��Ż�ˢ�³���(��֪�Ƿ���Winƽ̨�й�);
                    if ( _ReadOverlapped.InternalHigh )
                        uReadLength = _ReadOverlapped.InternalHigh;
                    else
                        uReadLength = Stat.cbInQue;
                    break;
                } else {
                    utils::_dprintf("������%ld", dwError);
                    uReadLength = 0;
                    break;
                }
            }
        }
    }

    return uReadLength;
}

char* CBaseSerial::ReadString(char *szBuffer, DWORD dwBufferLength, DWORD dwWaitTime /* = 20 */)
{
    unsigned long uReadLength = Read(szBuffer, dwBufferLength - 1, dwWaitTime);
    szBuffer[uReadLength] = '\0';
    return szBuffer;
}

DWORD CBaseSerial::Write(LPVOID Buffer, DWORD dwBufferLength)
{
    if (!IsOpen())
        return 0;

    DWORD dwError;
    if (::ClearCommError(_hCommHandle, &dwError, NULL) && dwError > 0)
        ::PurgeComm(_hCommHandle, PURGE_TXABORT | PURGE_TXCLEAR);    // ��������������PURGE_TXCLEAR��
    
    unsigned long uWriteLength = 0;
    if (!::WriteFile(_hCommHandle, Buffer, dwBufferLength, &uWriteLength, &_WriteOverlapped)) {
        if (ERROR_IO_PENDING == GetLastError()) {
            while (!GetOverlappedResult(_hCommHandle, &_WriteOverlapped, &uWriteLength, FALSE)) {
                dwError = GetLastError();
                if (ERROR_IO_PENDING == dwError) {
                    utils::_dprintf("д�ȴ�");
                    continue;
                } else if (dwError == ERROR_SUCCESS || dwError == ERROR_IO_INCOMPLETE){
                    uWriteLength = _WriteOverlapped.InternalHigh;
                    utils::_dprintf("��:%ld", dwError);
                    break;
                } else {
                    utils::_dprintf("���:%ld",dwError);
                    uWriteLength = 0;
                    ClearCommError(_hCommHandle, &dwError, NULL);
                    break;
                }
            }
        }
    }

    return uWriteLength;
}

DWORD CBaseSerial::Write(const TCHAR *szBuffer)
{
    assert(szBuffer);
    return Write((void *)szBuffer, _tclen(szBuffer));
}

DWORD CBaseSerial::ReadSync(LPVOID Buffer, DWORD dwBufferLength)
{
    if (!IsOpen())
        return 0;
    
    COMSTAT Stat;
    DWORD dwError;

    DWORD dwLastLen = 0;
    ULONGLONG ulTick = GetTickCount();    
    // ֱ��������Ϊֹ����ʱ3��;
    while (true) {// cbInQue��ʾ���뻺�������ֽ���; 
        if (GetTickCount() - ulTick > 3000) {
            utils::_dprintf("������: ����3�������");
            break;
        }

        if ( ::ClearCommError(_hCommHandle, &dwError, &Stat) && dwError > 0 ) {
            ::PurgeComm(_hCommHandle, PURGE_RXABORT | PURGE_RXCLEAR);    // ������뻺������PURGE_RXCLEAR��
            utils::_dprintf("������: ClearCommError����");
            break;
        }

        // ��ֹ��һ��û���꣬�ٶ�һ��;���γ���һ����ʾ����;
        if (Stat.cbInQue != 0 && dwLastLen == Stat.cbInQue)
            break;
        dwLastLen = Stat.cbInQue;
        Sleep(100);
    }

    if (Stat.cbInQue == 0) {
        // ���ڳ�ʱ�������ݷ���;
        return 0;
    }

    if (dwBufferLength < Stat.cbInQue) {
        utils::_dprintf("������: �������ݹ��� %ld > %ld", dwBufferLength, Stat.cbInQue);
    }

    DWORD uReadLength = 0;
    if ( !::ReadFile(_hCommHandle, Buffer, dwBufferLength, &uReadLength, NULL) )
    {
        DWORD dwError = GetLastError();
        utils::_dprintf("��������%ld", dwError);
    }

    return uReadLength;
}

DWORD CBaseSerial::WriteSync(LPVOID Buffer, DWORD dwBufferLength)
{
    if (!IsOpen())
        return 0;
    
    DWORD dwError;
    if (::ClearCommError(_hCommHandle, &dwError, NULL) && dwError > 0)
        ::PurgeComm(_hCommHandle, PURGE_TXABORT | PURGE_TXCLEAR);    // ��������������PURGE_TXCLEAR��
    
    unsigned long uWriteLength = 0;
    if ( !::WriteFile(_hCommHandle, Buffer, dwBufferLength, &uWriteLength, NULL) )
    {
        DWORD dwError = GetLastError();
        utils::_dprintf("�����%ld", dwError);
    }

    return uWriteLength;
}

DWORD CBaseSerial::Write(TCHAR *szBuffer, DWORD dwBufferLength, TCHAR *szFormat, ...)
{
    if (!IsOpen())
        return 0;
    
    va_list va;
    va_start(va, szFormat);
#if _MSC_VER < 1500
    _vsnprintf(szBuffer, dwBufferLength, szFormat, va);
#elif _MSC_VER >=1500 // VC9.0���ϰ汾;
    //_vsntprintf(szBuffer, dwBufferLength, szFormat, va);
    _vsntprintf_s(szBuffer, dwBufferLength, _TRUNCATE, szFormat, va);
#endif
    va_end(va);

    return Write(szBuffer);
}

DWORD CBaseSerial::Write(TCHAR *szBuffer, TCHAR *szFormat, ...)
{
    if (!IsOpen()) 
        return 0;
    
    va_list va;
    va_start(va, szFormat);
#if _MSC_VER < 1500       
    vsprintf(szBuffer, szFormat, va);
#elif _MSC_VER >=1500 // VC9.0���ϰ汾;    
    _vstprintf(szBuffer, szFormat, va);
#endif
    va_end(va);

    return Write(szBuffer);
}

void CBaseSerial::Close()
{
    if (!IsOpen()) return;

    PurgeComm(_hCommHandle, PURGE_TXABORT | PURGE_TXCLEAR);    // ��������������PURGE_TXCLEAR��
    ::CloseHandle(_hCommHandle);
    _hCommHandle = INVALID_HANDLE_VALUE;
}

bool CBaseSerial::SetDTR(bool OnOrOff)
{
    return IsOpen() ? EscapeCommFunction(_hCommHandle, OnOrOff ? SETDTR : CLRDTR) : false;
}

bool CBaseSerial::SetRTS(bool OnOrOff)
{
    return IsOpen() ? EscapeCommFunction(_hCommHandle, OnOrOff ? SETRTS : CLRRTS) : false;
}

bool CBaseSerial::SetBreak(bool OnOrOff)
{
    return IsOpen() ? EscapeCommFunction(_hCommHandle, OnOrOff ? SETBREAK : CLRBREAK) : false;
}

void CBaseSerial::Init()
{
    memset(_szCommStr, 0, 20*sizeof(TCHAR));
    memset(&_DCB, 0, sizeof(_DCB));
    _DCB.DCBlength = sizeof(_DCB);
    _hCommHandle = INVALID_HANDLE_VALUE;

    memset(&_ReadOverlapped, 0, sizeof(_ReadOverlapped));
    memset(&_WriteOverlapped, 0, sizeof(_WriteOverlapped));
    _ReadOverlapped.hEvent = ::CreateEvent(NULL, true, false, NULL);
    assert(_ReadOverlapped.hEvent != INVALID_HANDLE_VALUE);
    _WriteOverlapped.hEvent = ::CreateEvent(NULL, true, false, NULL);
    assert(_WriteOverlapped.hEvent != INVALID_HANDLE_VALUE);

    _hNotifyWnd = NULL;
    _dwNotifyNum = 0;
    _dwMaskEvent = DEFAULT_COM_MASK_EVENT;

    memset(&_WaitOverlapped, 0, sizeof(_WaitOverlapped));
    _WaitOverlapped.hEvent = ::CreateEvent(NULL, true, false, NULL);
    assert(_WaitOverlapped.hEvent != INVALID_HANDLE_VALUE);
}

void CBaseSerial::UnInit()
{
    if (_ReadOverlapped.hEvent != INVALID_HANDLE_VALUE)
        CloseHandle(_ReadOverlapped.hEvent);
    if (_WriteOverlapped.hEvent != INVALID_HANDLE_VALUE)
        CloseHandle(_WriteOverlapped.hEvent);
    if (_WaitOverlapped.hEvent != INVALID_HANDLE_VALUE)
        CloseHandle(_WaitOverlapped.hEvent);
}

void CBaseSerial::BindCommPort(DWORD dwPort)
{
    assert(dwPort >= 1 && dwPort <= 1024);
    TCHAR p[5] = {0};
    _dwPort = dwPort;
#if _MSC_VER >= 1200 && _MSC_VER < 1500
    strcpy(_szCommStr, _T("\\\\.\\COM"));
    ltoa(_dwPort, p, 10);
    strcat(_szCommStr, p);
#elif _MSC_VER >= 1500
    _tcscpy_s(_szCommStr, _T("\\\\.\\COM"));
    _ltot_s(_dwPort, p, 10);
    _tcscat_s(_szCommStr, p);
#endif
}

bool CBaseSerial::OpenCommPort()
{
    if (IsOpen())
        Close();
    _hCommHandle = ::CreateFile(_szCommStr, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | _dwIOMode, NULL);
    return IsOpen();
}

bool CBaseSerial::SetupPort()
{
    if (!IsOpen())
        return false;
    // SetupComm�������������������С;
    if (!::SetupComm(_hCommHandle, 8192, 8192))
        return false;
    if (!::GetCommTimeouts(_hCommHandle, &_CO))
        return false;

    _CO.ReadIntervalTimeout = 0;
    _CO.ReadTotalTimeoutMultiplier = 10;
    _CO.ReadTotalTimeoutConstant = 1500;
    _CO.WriteTotalTimeoutMultiplier = 10;
    _CO.WriteTotalTimeoutConstant = 1500;
    if (!::SetCommTimeouts(_hCommHandle, &_CO))
        return false;

    // ����������������;
    if (!::PurgeComm(_hCommHandle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR))
        return false;

    return true;
}