// VPserIO.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "VPserIO.h"
#include "enumser.h"
#include "Debug.h"
#include <sstream>
#include <thread>
#pragma warning (disable:4200)

#define MYHND ((HANDLE)m_pdevhnd)
#define MYHND_PTR ((HANDLE*)&m_pdevhnd)

using namespace iodbg;

static int g_baudval[]
{
	1200 //VPIO_BR_1200 = 0		// not used...
	, 2400 //VPIO_BR_2400			// not valid for viper
	, 4800 //VPIO_BR_4800			// not valid for viper
	, 9600 //VPIO_BR_9600			// not valid for viper
	, 19200 //VPIO_BR_19200		// not valid for viper
	, 38400 //VPIO_BR_38400
	, 57600 //VPIO_BR_57600
	, 115200 //VPIO_BR_115200
	, 230400       //!< 230,400 baud
	, 460800       //!< 460,800 baud
	, 921600       //!< 921,600 baud
	, 1843200      //!< 1,843,200 baud
	, 3686400      //!< 3,686,400 baud
	, 7372800      //!< 7,372,800 baud

	, -1
};

#define BAUDVAL(x) (g_baudval[CLAMP(x,VPIO_BR_1200,VPIO_BR_7372800)])
#define PARITYVAL(x) (CLAMP(x,VPIO_PARITY_NONE,VPIO_PARITY_EVEN))

CVPserIO::CVPserIO(uint32_t sercfg) :
	m_pdevhnd(0)
	, m_baud(VPSERIO_DEFAULT_BAUD)
	, m_parity(VPSERIO_DEFAULT_PARITY)
	, m_iNextHostIndex(0)
	, m_iHostFC(0)
	, m_iLastBytesIn(0)
	, m_pLastBufIn(0)
	, m_pthCont(0)
	, m_bContReadRunning(false)
	, m_pDataReadyCallback(0)
	, m_pCBInfo(0)
	, m_Mode(E_MODE_VP)
	, m_bAsciiContMode(false)

{
	// initialize here
	SetCfg(sercfg);

//#ifdef _DEBUG
	SetTrace(true, PiDebug::PID_3_NORMALIO);
//#endif	
}

CVPserIO::~CVPserIO()
{
	if (Initialized())
	{
		//ReadContStop(); // The close operation will stop the cont loop.  Inadviseable to do that here.
		Close();

	}
}
void CVPserIO::SetCfg(uint32_t sercfg)
{
	if (sercfg)
	{
		vp_serportcfg cfg(sercfg);
		m_baud = cfg.bf.baudrate;
		m_parity = cfg.bf.parity;
	}

}
void CVPserIO::SetTrace(bool btrace, int ilev)
{
	DOUT(__FUNCTION__ " : btrace=%s , ilev = %d\r\n", (btrace ? "true" : "false"), ilev);
	if (btrace)
	{
		// Don't set regular code trace for SerIO.. too much comes out.  
		//PiDebug::PIDSetLevel(ilev);
		if (ilev > PiDebug::PID_5_CODETRACE)
		{
			PiDebug::PIDSetLevel(ilev);
			DOUT0(__FUNCTION__ " : %d : Setting trace level verbose or higher\r\n", __LINE__);
		}
		else if (ilev < PiDebug::PID_5_CODETRACE)
		{
			PiDebug::PIDSetLevel(ilev);
			DOUT0(__FUNCTION__ " : %d : Setting trace level normal i/o or lower\r\n", __LINE__);
		}
	}
	else
	{
		PiDebug::PIDSetLevel(0);
	}
}

void CVPserIO::SetModeLY()
{
	m_Mode = E_MODE_LY;
}

// in: arrDevInfo[], may be null.  If null, only arrcount is output
// in: arrcount indicates size of arrDevInfo
// out: arrDevInfo[] filled with info about discovered serial ports, up to input arrcount
// out: arrcount indicates number of serial ports found, this may be larger than array size
int CVPserIO::ListSerPorts(vp_serportinfo arrDevInfo[], size_t & arrcount)
{
	CODETRACE;
	int r = 0;
	bool bCountOnly = false;

	if (!arrDevInfo)
		bCountOnly = true;

	CEnumerateSerial::CPortsArray ports;
	CEnumerateSerial::CNamesArray friendlynames;
	CEnumerateSerial::CNamesArray portnames;

	if (CEnumerateSerial::UsingSetupAPI1(ports, portnames, friendlynames))
	{
		if (bCountOnly)
			arrcount = ports.size();

		else
		{
			for (uint32_t i = 0; i < min(ports.size(), arrcount); i++)
			{
				_vp_serportinfo tmp(ports[i], portnames[i], friendlynames[i]);
				arrDevInfo[i] = tmp;
			}

			arrcount = ports.size();
		}
	}
	else
		r = -1;


	CODETRACEEX;
	return r;
}

int CVPserIO::DiscoverPort()
{
	CODETRACE;
	int r = 0;

	CEnumerateSerial::CPortsArray ports;
	CEnumerateSerial::CNamesArray names;
	CEnumerateSerial::CNamesArray fnames;

	if (!(CEnumerateSerial::UsingSetupAPI1(ports, names, fnames)))
	{ 
		r = -1;
	}
	else
	{
		for (uint32_t i = 0; i < ports.size(); i++)
		{
			DOUT1("%s : Trying port %d : %s\r\n ..", __FUNCTION__, ports[i], names[i].c_str());
			if (r = Open(ports[i]))
			{
				//try the next one
			}
			else
			{
				// the rest of the devinfo was set up in Open call.
				m_devinfo.strFriendlyName = fnames[i];// = _vp_serportinfo(ports[i], names[i]);
				m_devinfo.strPortName = names[i];
				m_devinfo.serCfg.bf.baudrate = m_baud;
				m_devinfo.serCfg.bf.parity = m_parity;
				r = 0;
				break;
			}
		}
	}

	CODETRACEEX;
	return r;

}

int CVPserIO::Open(uint32_t port)
{
	CODETRACE;
	int r = 0;
	if (MYHND != 0)
		CloseHandles();

	HANDLE hCnx = 0;
	m_hReadEvent = NULL;
	m_hWriteEvent = NULL;

	TCHAR szPortName[100];
	_stprintf_s(szPortName, 100, _T("\\\\.\\COM%u"), port);

	hCnx = CreateFile(szPortName,		// file name//m_szPortName.c_str(),
		GENERIC_READ | GENERIC_WRITE,	// desired access
		0,								// share mode // exclusive access
		NULL,							// security attrs // no security attrs
		OPEN_EXISTING,					// creation disposition
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // flags and attrs
		NULL);							// template file

	if (hCnx == INVALID_HANDLE_VALUE)
	{
		DWORD dwErr = ::GetLastError();
		DOUT_ERR5(__FUNCTION__ " : CreateFile %s Result INVALID_HANDLE_VALUE Error %d \n", szPortName, dwErr);
		hCnx = NULL;
		r = VPIO_SYSERR;
	}
	else if (hCnx == 0)
	{
		DOUT_ERR5(__FUNCTION__ " : CreateFile Result NULL %d \n", ::GetLastError());
		r = VPIO_SYSERR;
	}
	else
	{
		MYHND = hCnx;
		//DWORD dwModemStat = 0;

		if (r = SetupSerComm(hCnx))
		{ }
		else if (r = InitHandles(hCnx))
		{ }
		/*else if (!(::GetCommModemStatus(hCnx, &dwModemStat)))
		{
			DOUT_ERR5(__FUNCTION__ " : GetCommModemStatus failed\n");
		}
		else if (!(dwModemStat & MS_CTS_ON))
		{
		DOUT5("CPiCmmSer::Connect - no device detected\n");
		CPiError::Set(pResult, PI_DEVICE_NOT_DETECTED);
		}*/
		else if (r = FlushComm())
		{ }
		else
		{
			m_devinfo = vp_serportinfo(port, tstring(szPortName), tstring());
			m_devinfo.serCfg.bf.baudrate = m_baud;
			m_devinfo.serCfg.bf.parity = m_parity;
			DOUT1("COM Port %d : %s Open : BaudRate %d, Parity %d\r\n", port, szPortName, BAUDVAL(m_baud), m_parity);
		}
	}


	if (r)
		CloseHandles();

	CODETRACEEX;
	return r;
}
int CVPserIO::SetupSerComm(HANDLE hCnx)
{
	CODETRACE;
	int r = 0;
	BOOL			bRet = FALSE;
	DCB             dcb;            //device control block
	COMMTIMEOUTS    cTOuts;
	//DWORD			dwErr;
	//TCHAR			szEMsg[200];
	//TCHAR			szMsg[500];


	if (!(::SetCommMask(hCnx, EV_RXCHAR))) // get any early notifications   
	{
		//dwErr = ::GetLastError();
		//_stprintf_s(szMsg, _countof(szMsg), _T("PiCommSer::SetupSerComm - Error in SetCommMask Result %d"), ::GetLastError());
		//__tcserror_s(szEMsg, _countof(szEMsg), szMsg);
		//DOUT5(szEMsg);
	}

	else if (!(::SetupComm(hCnx, 3100, 1024)))  // set buffer sizes    
	{
		//_stprintf_s(szMsg, _countof(szMsg), _T("PiCommSer::SetupSerComm - Error in SetupComm Result %d"), ::GetLastError());
		//__tcserror_s(szEMsg, _countof(szEMsg), szMsg);
		//DOUT5(szEMsg);
	}

	else if (!(::GetCommState(hCnx, &dcb)))    // Set the parameters on the comms
	{
		//_stprintf_s(szMsg, _countof(szMsg), _T("PiCommSer::SetupSerComm - Error in GetCommState Result %d"), ::GetLastError());
		//__tcserror_s(szEMsg, _countof(szEMsg), szMsg);
		//DOUT5(szEMsg);
	}

	else
	{
		DumpDCB(__FUNCTION__ "PRE", dcb);

		dcb.BaudRate = BAUDVAL(m_baud);  //VPSERIO_DEFAULT_BAUD;
		dcb.fOutxCtsFlow = 0;
		dcb.fOutxDsrFlow = 0;
		dcb.fDtrControl = DTR_CONTROL_ENABLE;
		dcb.fDsrSensitivity = 0;
		dcb.fTXContinueOnXoff = 0;
		dcb.fOutX = 0;
		dcb.fInX = 0;
		dcb.fRtsControl = RTS_CONTROL_ENABLE;
		dcb.fAbortOnError = 0;
		dcb.XonLim = 2048;
		dcb.XoffLim = 512;
		dcb.ByteSize = VPSERIO_DEFAULT_DATABITS;
		dcb.Parity = m_parity;
		dcb.StopBits = ONESTOPBIT;

		if (!(::SetCommState(hCnx, &dcb)))
		{
			//_stprintf_s(szMsg, _countof(szMsg), _T("PiCommSer::SetupSerComm - Error in SetCommState dcb Result %d"), ::GetLastError());
			//__tcserror_s(szEMsg, _countof(szEMsg), szMsg);
			//DOUT5(szEMsg);

			DOUT5("\r\n%s : ::SetCommState Error Setting BaudRate to %d, Parity to %d\r\n", __FUNCTION__, BAUDVAL(m_baud), m_parity);

		}

		else if (!(::GetCommState(hCnx, &dcb)))     // Set the parameters on the comms
		{
			DOUT5("\r\n%s : ::GetCommState Error \r\n", __FUNCTION__);
		}

		else
		{
			DumpDCB("PiCommSer::SetupSerComm POST", dcb);


			// setting ReadIntervalTimeout to MAXDWORD works under 95 / 98 but not NT....
			// reasonable values seem to work everywhere.
			//  timeout = bytesToBeRead * multiplier + Constant(milliseconds)
			/*	cTOuts.ReadIntervalTimeout = 100;
			cTOuts.ReadTotalTimeoutMultiplier = 0;
			cTOuts.ReadTotalTimeoutConstant = 100;//1000; // milliseconds*/

			/*
			If an application sets ReadIntervalTimeout and ReadTotalTimeoutMultiplier
			to MAXDWORD and sets ReadTotalTimeoutConstant to a value greater than zero
			and less than MAXDWORD, one of the following occurs when the ReadFile
			function is called:

			If there are any characters in the input buffer,
			ReadFile returns immediately with the characters in the buffer.
			If there are no characters in the input buffer,
			ReadFile waits until a character arrives and then returns immediately.
			If no character arrives within the time specified by ReadTotalTimeoutConstant,
			ReadFile times out.

			*/
			cTOuts.ReadIntervalTimeout = MAXDWORD;
			cTOuts.ReadTotalTimeoutMultiplier = MAXDWORD;
			cTOuts.ReadTotalTimeoutConstant = 100;//1000; // milliseconds

			cTOuts.WriteTotalTimeoutMultiplier = 0;
			cTOuts.WriteTotalTimeoutConstant = 500;//1000;

			if (!(::SetCommTimeouts(hCnx, &cTOuts)))
			{

				DOUT5("\r\n%s : ::SetCommTimeouts Error \r\n", __FUNCTION__);
			}
			else
			{
				bRet = TRUE;
			}
		}
	}

	if (!bRet)
	{
		r = -1;
	}

	CODETRACEEX;

	return r;
}
int CVPserIO::FlushComm()
{
	BOOL	bRet = FALSE;
	if (!(::PurgeComm(MYHND, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR)))
	{
		//DWORD dwError = ::GetLastError();
		DOUT_ERR5(__FUNCTION__ " : PurgeComm Result %d : %s\n", ::GetLastError());
	}
	else
	{
		bRet = TRUE;
	}

	return bRet ? 0 : -1;
}
void CVPserIO::DumpDCB(LPSTR sTitle, DCB & dcb)
{
	char msg[2100];
	int n = 0;

	//if (m_bTraceON)
	{
		sprintf_s(msg, _countof(msg), "%s DCB\n", sTitle);
		DOUT5(msg);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD DCBlength            = %2d;      // sizeof(DCB)                      \n", dcb.DCBlength);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD BaudRate             = %2d;    // Baudrate                           \n", dcb.BaudRate);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fBinary: 1           = %2d;    // Binary Mode (skip EOF check)       \n", dcb.fBinary);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fParity: 1           = %2d     // Enable parity checking             \n", dcb.fParity);
		DOUT5(msg); n = 0;
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fOutxCtsFlow:1       = %2d     // CTS handshaking on output          \n", dcb.fOutxCtsFlow);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fOutxDsrFlow:1       = %2d     // DSR handshaking on output          \n", dcb.fOutxDsrFlow);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fDtrControl:2        = %2d     // DTR Flow control                   \n", dcb.fDtrControl);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fDsrSensitivity:1    = %2d     // DSR Sensitivity                    \n", dcb.fDsrSensitivity);
		DOUT5(msg); n = 0;
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fTXContinueOnXoff: 1 = %2d     // Continue TX when Xoff sent         \n", dcb.fTXContinueOnXoff);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fOutX: 1             = %2d     // Enable output X-ON/X-OFF           \n", dcb.fOutX);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fInX: 1              = %2d     // Enable input X-ON/X-OFF            \n", dcb.fInX);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fErrorChar: 1        = %2d     // Enable Err Replacement             \n", dcb.fErrorChar);
		DOUT5(msg); n = 0;
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fNull: 1             = %2d     // Enable Null stripping              \n", dcb.fNull);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fRtsControl:2        = %2d     // Rts Flow control                   \n", dcb.fRtsControl);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fAbortOnError:1      = %2d     // Abort all reads and writes on Error\n", dcb.fAbortOnError);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tDWORD fDummy2:17           = %2d     // Reserved                           \n", dcb.fDummy2);
		DOUT5(msg); n = 0;
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tWORD wReserved             = %2d     // Not currently used                 \n", dcb.wReserved);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tWORD XonLim                = %2d     // Transmit X-ON threshold            \n", dcb.XonLim);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tWORD XoffLim               = %2d     // Transmit X-OFF threshold           \n", dcb.XoffLim);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tBYTE ByteSize              = %2d     // Number of bits/byte, 4-8           \n", dcb.ByteSize);
		DOUT5(msg); n = 0;
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tBYTE Parity                = %2d     // 0-4=None,Odd,Even,Mark,Space       \n", dcb.Parity);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tBYTE StopBits              = %2d     // 0,1,2 = 1, 1.5, 2                  \n", dcb.StopBits);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tchar XonChar               = %#2x     // Tx and Rx X-ON character           \n", dcb.XonChar);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tchar XoffChar              = %#2x     // Tx and Rx X-OFF character          \n", dcb.XoffChar);
		DOUT5(msg); n = 0;
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tchar ErrorChar             = %#2x     // Error replacement char             \n", dcb.ErrorChar);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tchar EofChar               = %#2x     // End of Input character             \n", dcb.EofChar);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tchar EvtChar               = %#2x     // Received Event character           \n", dcb.EvtChar);
		n += sprintf_s(&msg[n], _countof(msg) - n, "\tWORD wReserved1            = %#2x     // Fill for now.                     \n", dcb.wReserved1);
		DOUT5(msg);
	}
}
int CVPserIO::InitHandles(HANDLE hCnx)
{
	BOOL	bRet = TRUE;

	m_hReadEvent = NULL;
	m_hWriteEvent = NULL;

	MYHND = hCnx;

	m_hReadEvent = CreateEvent(NULL,   // no security attributes 
		TRUE,  // manual reset event 
		FALSE,  // not signaled 
		NULL    // no name 
	);

	if (m_hReadEvent == NULL)
	{
		DOUT_ERR5(__FUNCTION__ " : CreateEvent m_hReadEvent Error ");
		bRet = FALSE;
	}
	else
	{
		m_hWriteEvent = CreateEvent(NULL,   // no security attributes 
			TRUE,  // manual reset event 
			FALSE,  // not signaled 
			NULL    // no name 
		);

		if (m_hWriteEvent == NULL)
		{
			DOUT_ERR5(__FUNCTION__ " : CreateEvent m_hWriteEvent Error ");
			bRet = FALSE;
		}
	}

	return bRet ? 0 : -1;
}


int CVPserIO::Write(uint8_t *pbuf, int & count)
{
	DOUTHEXBUF(pbuf, count, true, __FUNCTION__);
	BOOL        bRet = TRUE;
	DWORD       dwWritten = 0;
	DWORD       dwError;

	OVERLAPPED      m_OWrite;

	memset((PVOID)&m_OWrite, 0, sizeof(m_OWrite));
	m_OWrite.hEvent = m_hWriteEvent;

	if (!(::WriteFile(MYHND, pbuf, count, &dwWritten, &m_OWrite)))
	{
		dwError = ::GetLastError();
		if (dwError != ERROR_IO_PENDING)
		{
			DOUT_ERR7("PiCommBase::WriteCmd - WriteFile Result %d : ", dwError);
			bRet = FALSE;
		}
		// call 
		else if (!(GetOverlappedResult(MYHND, &m_OWrite, &dwWritten, TRUE)))
		{
			dwError = ::GetLastError();
			DOUT5(__FUNCTION__ " : GetOverlappedResult: %x dwError %d dwWritten %d\n", dwError, dwWritten);
			if (dwError != ERROR_IO_INCOMPLETE)
			{
				DOUT_ERR5(__FUNCTION__ " : GetOverlappedResult Error %d : ", dwError);
				bRet = FALSE;
			}
		}

	}

	CODETRACEEX;
	return bRet ? 0 : 1;
}
//PiCommBase::ReadCmd(CPiStreamBuf * pBuf, DWORD &dwSize, CPiError * pResult, DWORD tout/*=200*/)

#define VP_PREAMBLE_MASK 0x00ffffff
#define LY_PREAMBLE_MASK 0x0000ffff
bool PreambleTestM(_VPPREAMBLE pIN, _VPPREAMBLE pCmp, uint32_t mask)
{
	DOUT3("%s : preamble IN = %.8x, CMP = %.8x, mask = %.8x\r\n", __FUNCTION__, pIN, pCmp, mask);
	DOUT3("%s : (pIN & mask) = %.8x  :  (pCmp & mask) = %.8x\r\n", __FUNCTION__, (pIN & mask), (pCmp & mask));
	DOUT3("%s : %.8x ^ %.8x = %.8x\r\n", __FUNCTION__, (pIN & mask), (pCmp & mask), ((pIN & mask) ^ (pCmp & mask)));
	if (((pIN & mask) ^ (pCmp & mask)) == 0)
		return true;

	DOUT0("%s : %d : Expected preamble: %.8x, actual: %.8x\r\n", __FUNCTION__,__LINE__,(pCmp  ), (pIN ));
	return false;
}

#define MODE_VP (m_Mode == E_MODE_VP)
bool CVPserIO::PreambleTest(uint8_t * pIN)
{
	return PreambleTestM(*((_VPPREAMBLE*)pIN), m_devinfo.preamble, MODE_VP ? VP_PREAMBLE_MASK : LY_PREAMBLE_MASK);
}

int CVPserIO::Read(uint8_t *pbuf, int & count, bool bTOisErr/*=false*/)
{
	//CODETRACE;
	DOUT3("%s : %s : %d : pbuf 0x%x, count %d, bTOisErr %s\r\n",
		__FILE__, __FUNCTION__, __LINE__,
		(uint32_t)pbuf, count, bTOisErr ? "true" : "false");

	int r = 0;
	if (pbuf == 0)
		return -1;

	BOOL        bRet = TRUE;
	DWORD       dwTotalRead = 0;
	DWORD       dwRead = 0;

	DWORD       dwBytesToRead = count;// = (count == 0) ? 200 : (DWORD)count;

	DWORD       dwError;

	OVERLAPPED      m_ORead;
	memset((PVOID)&m_ORead, 0, sizeof(m_ORead));
	m_ORead.hEvent = m_hReadEvent;

	//dwBytesToRead = PICOMM_MAX_RESP_BUF_LEN;
	//memset((PVOID)m_ucRspBuf, 0x99, sizeof(m_ucRspBuf));

	BOOL bFirstRead = TRUE;

	uint8_t * p_in = 0;
	while ((dwTotalRead < dwBytesToRead) && bRet)
	{
		//pbuf[dwRead] = 0;
		p_in = &pbuf[dwTotalRead];

		if (!(::ReadFile(MYHND, p_in, dwBytesToRead-dwTotalRead, &dwRead, &m_ORead)))
		{
			dwError = ::GetLastError();
			if (dwError != ERROR_IO_PENDING)
			{
				DOUT_ERR(__FUNCTION__ " : ReadFile Error %d : ", dwError);
				r = VPIO_SYSERR;
				bRet = FALSE;
			}
			else
			{
				::SetLastError(NO_ERROR);
			}
		}

		if (bRet && (!(GetOverlappedResult(MYHND, &m_ORead, &dwRead, TRUE))))
		{
			dwError = ::GetLastError();
			if (dwError != ERROR_IO_INCOMPLETE)
			{
				DOUT_ERR(__FUNCTION__ " : GetOverlappedResult Error %d : ", dwError);
				r = VPIO_SYSERR;
				bRet = FALSE;
			}
			else
			{
				::SetLastError(NO_ERROR);
			}
		}

		if (dwRead == 0)
		{
			// error if response was expected.
			if ((dwTotalRead == 0) && (count != 0))
			{
				// no bytes were read.
				if (bTOisErr)//(ReadCmdTOIsError())
				{ 
					bRet = FALSE;
					r = VPIO_NORESP;
				}
			}
			break;
		}
		else if (bRet)
		{
			dwTotalRead += dwRead;

			//DOUTHEXBUF(p_in, dwRead, true, __FUNCTION__);
		}
		bFirstRead = FALSE;
	}

	DOUT3("%s : Requested %d, Actual %d\r\n", __FUNCTION__, count, dwTotalRead);
	count = dwTotalRead;

	if (count)
		DOUTHEXBUF(pbuf, count, true, __FUNCTION__);

	return bRet ? 0 : r;
}
#define MODE_HDRLEN (MODE_VP ? sizeof(VPSERHDR) : sizeof(LYSERHDR))
#define MODE_BODYLEN(pIN) (MODE_VP ? ((VPSERHDR*)pIN)->size : ((LYSERHDR*)pIN)->size)
#define MODE_PREAMBLELEN (MODE_VP ? sizeof(_VPPREAMBLE)-1 : sizeof(_LYPREAMBLE))
int CVPserIO::Readframe(uint8_t *pbuf, int & count)
{
	CODETRACE;

	int r = 0;
	if (pbuf == 0)
		return  -1;

	BOOL        bRet = TRUE;

	int     iHdrLen = MODE_HDRLEN; // sizeof(VPSERHDR);
	uint8_t *p_in = pbuf;
	DOUT3("%s : iHdrLen = %d\r\n", __FUNCTION__, iHdrLen);

	//VPSERHDR *p_inhdr = (VPSERHDR*)p_in;
	int		iBodyLen;
	DOUT3("%s : Reading Header, Len = %d\r\n", __FUNCTION__, iHdrLen);
	if ( r = Read(p_in, iHdrLen, true))
	{
		if (r == VPIO_NORESP)
		{
			DOUT5("%s : Error Reading Header : %d : %s\r\n", __FUNCTION__, r, ErrorStr(r));
		}
		else
		{
			DOUT0("%s : Error Reading Header : %d : %s\r\n", __FUNCTION__, r, ErrorStr(r));
			DOUTHEXBUF0(p_in, iHdrLen, true, __FUNCTION__ " Error Reading Header");

		}
		bRet = FALSE;
	}
	//else if (!PreambleTest(p_inhdr->pre, FramePreamble()))
	else if (!PreambleTest(p_in))
	{
		DOUTHEXBUF0(p_in, iHdrLen, true, __FUNCTION__ " Error in Preamble" );

		bRet = FALSE;
		r = VPIO_FRAMERR;
	}
	else
	{
		iBodyLen = MODE_BODYLEN(p_in); // p_inhdr->size;
		DOUT3("%s : Reading Body, iBodyLen = %d\r\n", __FUNCTION__, iBodyLen);
		p_in += iHdrLen;

		if (r = Read(p_in, iBodyLen, true))
		{
			DOUT0("%s : Error Reading Body : %d : %s. Returning error %d : %s\r\n", __FUNCTION__, r, ErrorStr(r), VPIO_FRAMERR, ErrorStr(VPIO_FRAMERR));
			r = VPIO_FRAMERR;
			bRet = FALSE;
		}
		else
		{
			count = iHdrLen + iBodyLen;
		}
	}

	CODETRACEEX;
	return bRet ? 0 : r;
}

#define PREAMBLE_MASK 0x00ffffff
#define MASK0 0x000000ff
#define MASK1 0x0000ff00
#define MASK2 0x00ff0000

int CVPserIO::Findframe(uint8_t *pbuf, int & count)
{
	CODETRACE;

	int r = 0;
	if (pbuf == 0)
		return  -1;

	_VPPREAMBLE pre = FramePreamble();
	uint8_t szTag[] = { (uint8_t)(pre & MASK0), (uint8_t)((pre & MASK1)>>8), (uint8_t)((pre & MASK2) >> 16) };

	int iMaxNumToTest = 200;
	int iLeftToTest = 200;
	int iBytesRequested = 32;
	int itested = 0;
	uint8_t sample[32];
	int samplesize = 32; // smallest possible frame size
	int iFFstage = 0;

	uint8_t tmp[VPSER_RAW_READ_BUF_SIZE];
	int samplebyteskept = 0;

	bool bFound = false;

	while (!bFound && (itested < iMaxNumToTest))
	{
		iLeftToTest = iMaxNumToTest - itested;

		if (r = Read(sample, samplesize, true))
		{
			break;
		}
		else if (samplesize == 0) // should not happen
		{
			break;
		}
		else
		{
			int smptested = 0;
			int startindex = 0;

			while (smptested <= samplesize)
			{
				uint8_t testbyte = sample[smptested];
				uint8_t cmpbyte = szTag[iFFstage];
				if (testbyte == cmpbyte)
				//if (sample[smptested++] == szTag[iFFstage])
				{
					iFFstage++;
				}
				else
				{
					iFFstage = 0;
					startindex = smptested+1;
				}
				smptested++;

				if (iFFstage >= (int)MODE_PREAMBLELEN)//sizeof(szTag))
				{
					bFound = true;
					//found it: break out
					//copy from start index to end of sample into next capture buffer.
					//and break out.
					//_VPPREAMBLE *pfoundpre = (_VPPREAMBLE*)(&sample[startindex]);
					uint8_t *pfoundpre = &sample[startindex];
					samplebyteskept = samplesize - startindex;
					memcpy_s(tmp, sizeof(tmp), pfoundpre, samplebyteskept);
					break;
				}
			}
		}
		itested += samplesize;
	}

	if (!bFound)
		return r;

	int tmp_index = samplebyteskept;
	int hdr_size = MODE_HDRLEN;// sizeof(VPSERHDR);
	if (samplebyteskept < hdr_size)
	{
		//read the rest of the header into tmp
		int hdr_bytes_to_read = hdr_size - samplebyteskept;
		if (r = Read(&tmp[tmp_index], hdr_bytes_to_read, true))
		{
			return r;
		}
		tmp_index += hdr_bytes_to_read;
	}

	if (r)
		return r;

	// if we get here, we know the whole header is populated.
	//VPSERHDR *  phdr = (VPSERHDR*)(&tmp[0]);
	
	int frame_size = MODE_BODYLEN(tmp);// phdr->size;
	int frame_bytes = frame_size + hdr_size;
	if (frame_bytes >= VPSER_RAW_READ_BUF_SIZE)
	{
		r = VPIO_FRAMERR;
	}
	else if (tmp_index < (frame_bytes))
	{
		int frame_bytes_to_read = frame_bytes - tmp_index ;
		int tmp_buffer_remaining = _countof(tmp) - tmp_index-1;
		int bytes_to_read = CLAMP(frame_bytes_to_read, 0, tmp_buffer_remaining);
		if (r = Read(&tmp[tmp_index], bytes_to_read, true))
		{
			return r;
		}
		tmp_index += frame_bytes_to_read;
	}

	if (r)
		return r;

	// if we get here, there is a complete frame in tmp[]
	// copy it into pbuf
	memcpy(pbuf, tmp, tmp_index);
	count = tmp_index;

	DOUTHEXBUF(pbuf, count, true, __FUNCTION__);
	return r;
}

int CVPserIO::ReadCont( )
{
	return StartAsyncRead();
}

int CVPserIO::ReadContStop()
{
	StopContThread();
	return 0;
}

int CVPserIO::LastAsyncRead(uint8_t *& pbuf, int & nbytes)
{
	std::lock_guard<std::mutex> lck(m_mtxRxData);

	pbuf = m_pLastBufIn;
	nbytes = m_iLastBytesIn;
	return 0;
}

static TCHAR* g_parityString[]
{
	_T("NONE")
	, _T("ODD")
	, _T("EVEN")
	, _T("undefined")
};

void CVPserIO::DevReport(tstring & s) const
{
	//std::stringstream ss;
	tstring sport; 
	PortReport(vp_serportinfo(m_devinfo), sport);
	tstringstream ss;
	ss.str(s);
	ss << sport.c_str() << std::endl;
	ss << "baud : " << g_baudval[m_devinfo.serCfg.bf.baudrate] << " parity : " << g_parityString[m_devinfo.serCfg.bf.parity];
	ss << std::endl;
	s = ss.str();

}

void CVPserIO::PortReport(vp_serportinfo & info, tstring & s) 
{
	//std::stringstream ss;
	tstringstream ss;
	ss.str(s);
	ss << "port : " << info.serPort << " : " <<info.strPortName;
	ss << " : " << info.strFriendlyName;
	s = ss.str();
}

void CVPserIO::PortListReport(tstring &s)
{
	int r = 0;
	size_t count = 0;
	if (r = ListSerPorts(0, count))
	{
		s = _T("Error listing ports.");
	}
	else if (count)
	{
		vp_serportinfo *list = new vp_serportinfo[++count];

		if (r = ListSerPorts(list, count))
		{
			s = _T("Error listing ports.");
		}
		else
		{
			//tstring s;
			for (int i = 0; i < (int)count; i++)
			{
				tstring tmp;
				PortReport(list[i], tmp);
				s += tmp; s += _T("\r\n");
			}
		}

		delete[] list;
	}
	else
	{
		s = _T("No serial ports detected");
	}
}

static char* g_ErrString[] 
{
	"VPIO_NOERR"
	, "VPIO_TIMEOUT"
	, "VPIO_NORESP"
	, "VPIO_SETUPERR"
	, "VPIO_SYSERR"
	, "VPIO_FRAMERR"
	, "VPIO_GENERAL_ERROR"
};

static wchar_t* g_ErrStringW[]
{
	L"VPIO_NOERR"
	, L"VPIO_TIMEOUT"
	, L"VPIO_NORESP"
	, L"VPIO_SETUPERR"
	, L"VPIO_SYSERR"
	, L"VPIO_FRAMERR"
	, L"VPIO_GENERAL_ERROR"
};

const char * CVPserIO::ErrorStr(int err)
{
	return g_ErrString[CLAMP((uint32_t)err, 0, VPIO_MAXERR)];
}

const wchar_t * CVPserIO::ErrorStrW(int err)
{
	return g_ErrStringW[CLAMP((uint32_t)err, 0, VPIO_MAXERR)];
}

int CVPserIO::StartAsyncRead()
{
	CODETRACE;
	int r = 0;

	if (m_bContReadRunning)
		return -1;

	m_iNextHostIndex = 0;
	m_iHostFC = 0;
	m_iLastBytesIn = 0;
	m_pLastBufIn = 0;

	//start thread
	StartContThread();

	CODETRACEEX;
	return r;
}
void CVPserIO::StartContThread()
{
	CODETRACE;

	if (m_thidCont == std::thread::id())
	{
		std::thread *pth = new std::thread(&CVPserIO::thproc_Cont, this);
		m_pthCont = pth;
		m_thidCont = pth->get_id();

		DOUT0("%s : Thread started id = 0x%x\r\n", __FUNCTION__, m_thidCont);
	}
	else // thread is already running
	{
		DOUT0("%s : Thread id = 0x%x already RUNNING\r\n", __FUNCTION__, m_thidCont);
	}
	CODETRACEEX;
}
bool ThreadRunning(std::thread::id thid, std::thread *pth)
{
	return ((thid != std::thread::id()) && (pth != 0));
}
void CVPserIO::thproc_Cont()
{
	CODETRACE_ALT5;
	m_bContReadRunning = true;

	if (AsciiContMode())
		ContLoopFreeform();
	else
		ContLoop();

	CODETRACEEX_ALT5;
}

void CVPserIO::ContLoop()
{
	CODETRACE_ALT5;
	/*LARGE_INTEGER nPerfFreq;
	LARGE_INTEGER nStartCount;
	LARGE_INTEGER nEndCount;
	LARGE_INTEGER nDeltaCount;
	LARGE_INTEGER nMS;

	nPerfFreq.QuadPart = 0;
	nStartCount.QuadPart = 0;
	nEndCount.QuadPart = 0;
	nDeltaCount.QuadPart = 0;

	if (!(::QueryPerformanceFrequency( &nPerfFreq )))
	{
	int i = 1;
	}

	::QueryPerformanceCounter( &nStartCount );*/

	bool	bSearchHdr = false;
	bool	bFrameReady = false;

	int r = 0;

	do
	{
		bFrameReady = false;
		int iFrameBytes = 0;

		
		/*::QueryPerformanceCounter( &nEndCount );

		nDeltaCount.QuadPart = nEndCount.QuadPart - nStartCount.QuadPart;

		nMS.QuadPart = (nDeltaCount.QuadPart * 1000) / nPerfFreq.QuadPart;
		double dblSeconds = (double) nMS.QuadPart / 1000.0;

		if (dblSeconds > (2.0/60.0))
		{
		//DOUT(_T("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!PiCommBase::ContLoop - read interval too long: %8.3f !!!!!\r\n"), dblSeconds );

		LOGOUT(_T("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!PiCommBase::ContLoop - read interval too long: %8.3f !!!!!\r\n"), dblSeconds );
		}

		nStartCount.QuadPart = nEndCount.QuadPart;*/

		if (bSearchHdr)
		{
			if (r = Findframe(m_data_in, iFrameBytes))
			{
				if (r == VPIO_NORESP)
				{
					//bSearchHdr = false; // stop searching, just wait for next frame to start?
				}
			}
			else
			{
				bSearchHdr = false;
				bFrameReady = true;
			}

		}

		else
		{
			if (r = Readframe(m_data_in, iFrameBytes))
			{
				// post async error allows caller to stop the cont cmd
				if (r != VPIO_NORESP)
				{
					DOUT0("%s : %d : ReadFrame returned error: 0x%x (%d) : %s\n", __FUNCTION__,__LINE__,r,r, ErrorStr(r));

					if (r == VPIO_FRAMERR)
					{
						DOUT0("%s : %d : Frame Error:  Starting search for valid header in input stream. \n", __FUNCTION__, __LINE__);
						bSearchHdr = TRUE;
					}
				}
			}
			else if (iFrameBytes)
				bFrameReady = true;
		}

		if (bFrameReady && m_bContReadRunning)
		{
			DataReady(m_data_in, iFrameBytes);
		}
		
	} while (m_bContReadRunning);

	CODETRACEEX_ALT5;
}

void CVPserIO::ContLoopFreeform()
{
	bool	bFrameReady = false;

	int r = 0;

	do
	{
		bFrameReady = false;
		int iFrameBytes = 200;

		{
			if (r = Read(m_data_in, iFrameBytes))
			{
				// post async error allows caller to stop the cont cmd
				if (r != VPIO_NORESP)
				{
					DOUT_ERR(__FUNCTION__ " : ReadFrame error: %d\n", r);
				}
			}
			else if (iFrameBytes)
				bFrameReady = true;
		}

		if (bFrameReady && m_bContReadRunning)
		{
			DataReady(m_data_in, iFrameBytes);
		}

	} while (m_bContReadRunning);

	CODETRACEEX;
}

void CVPserIO::StopContThread()
{
	//DOUT0("Entering %s \r\n", __FUNCTION__);
	CODETRACE;
	//if ((!m_bContReadRunning) || (m_pthCont == 0))
	if (!(ThreadRunning(m_thidCont, m_pthCont)))
	{
		std::ostringstream abc;
		abc << m_thidCont;
		std::string str = abc.str();

		//DOUT0("%s : m_bContReadRunning = %s, m_pthCont = 0x%s\r\n", __FUNCTION__, (m_bContReadRunning ? "true" : "false"), m_pthCont);
		DOUT0("%s : Thread not running : m_thidCont = %s, m_pthCont = 0x%s\r\n", __FUNCTION__, str.c_str(), m_pthCont);
		DOUT0("Leaving %s \r\n", __FUNCTION__);
		return;
	}

	//trTracef(0, "StopHDiscThread....");

	m_bContReadRunning = false;

	::Sleep(100);
	if (m_pthCont->joinable())
	{
		// join thread and wait for it to terminate
		DOUT0("%s : Thread joinable, joining\r\n", __FUNCTION__);
		m_pthCont->join();
		DOUT0("%s : Thread JOINED\r\n", __FUNCTION__);
	}
	else
	{
		DOUT0("%s : Thread NOT joinable\r\n", __FUNCTION__);

	}
	delete m_pthCont;
	m_pthCont = 0;
	m_thidCont = std::thread::id();
	//trTracef(0, "STOPPED\r\n");
	DOUT0("%s : Thread STOPPED\r\n", __FUNCTION__);

	CODETRACEEX;
	//DOUT0("Leaving %s \r\n", __FUNCTION__);

}

inline bool CVPserIO::DataReady(uint8_t *pdata, int nBytes)
{
	CODETRACE_ALT;

	DOUTHEXBUF_ALT(pdata, nBytes, true, __FUNCTION__);
	if (m_pDataReadyCallback)
	{
		m_pDataReadyCallback(m_pCBInfo, pdata, (uint32_t)nBytes);
	}
	// if no callback was specified, copy data into capture buffer
	else
	{
		DOUT0("%s : no DataReady Callback specified\r\n", __FUNCTION__);

		std::lock_guard<std::mutex> lck(m_mtxRxData);

		if (m_iNextHostIndex + nBytes >= 2048)
			m_iNextHostIndex = 0;

		memcpy(&(m_cap_buf[m_iNextHostIndex]), pdata, nBytes);
		m_iHostFC++;
		m_pLastBufIn = &(m_cap_buf[m_iNextHostIndex]);
		m_iLastBytesIn = nBytes;
		m_iNextHostIndex += nBytes;
	}

	CODETRACEEX_ALT;

	return m_bContReadRunning;
}



int CVPserIO::Close()
{
	int r = -1;

	StopContThread();

	if (!(::CloseHandle(MYHND)))
	{
		DOUT_ERR5(__FUNCTION__, " : CloseHandle MYHND failed\n");
	}
	else if ((m_hReadEvent) && (!(::CloseHandle(m_hReadEvent))))
	{
		DOUT_ERR5(__FUNCTION__, " : closehandle m_hReadEvent failed\n");
	}
	else if ((m_hWriteEvent) && (!(::CloseHandle(m_hWriteEvent))))
	{
		DOUT_ERR5(__FUNCTION__, " : closehandle m_hWriteEvent failed\n");
	}
	else
	{
		r = 0;
	}

	MYHND = NULL;
	m_hReadEvent = NULL;
	m_hWriteEvent = NULL;

	m_devinfo = vp_serportinfo();

	return r;
}


void CVPserIO::CloseHandles()
{
	if (MYHND != NULL)
		CloseHandle(MYHND);

	if (m_hReadEvent != NULL)
		CloseHandle(m_hReadEvent);

	if (m_hWriteEvent != NULL)
		CloseHandle(m_hWriteEvent);

	MYHND = NULL;
	m_hReadEvent = NULL;
	m_hWriteEvent = NULL;

}