/*
 * File:      wxserial.cpp
 * Purpose:   Serial port class method definitions
 * Author:    George Tasker
 *
 * Created:   30-05-1997
 *
 * Code contributions: Andrew Goldstein
 *
 */

/*
// SYNOPSIS START

   32bit Windows serial port handling classes

   Steps for normal usage of the wxSerial Class

      1) Create an instance of a wxSerial port
      2) Open the port
      3) Set the CallBack function to be used (optional)
      4) Enable the port
      5) Do desired reads/writes
      6) Close port

// SYNOPSIS STOP
*/

/* static const char sccsid[] = "%W% %G%"; */


#ifdef __GNUG__
#pragma implementation
#pragma interface
#endif

// For compilers that support precompilation, includes "wx.h".
#include "wx_prec.h"

#ifdef __BORLANDC__
#  pragma hdrstop
#endif

#ifndef WX_PRECOMP
#  include "wx.h"
#endif

#include <time.h>
#include "wxstring.h"
#include "wxserial.h"

wxSerial_CallBack defaultwxSerialCallBack;
// forward declaration
DWORD FAR PASCAL CommWatchProc( LPSTR lpData );

#if FINAL==0
   // For debug purposes - if an error condition occurs, the ErrorMsg string contains 
   // a descriptive reason of why the requested operation failed.
   wxString ErrorMsg;

   // For debug purposes - Determines whether ErrorMsg is shown immediately upon occurring
   int   ShowErrorMsg = TRUE;
#else
   int   ShowErrorMsg = FALSE;
#endif


char GetParityChar(wxParity parity)
{
   char parityChar;
   switch (parity)
   {
      case wxPARITY_NONE:
         parityChar = 'N';
         break;
      case wxPARITY_ODD:
         parityChar = 'O';
         break;
      case wxPARITY_EVEN:
         parityChar = 'E';
         break;
      case wxPARITY_MARK:
         parityChar = 'M';
         break;
      case wxPARITY_SPACE:
         parityChar = 'S';
         break;
      default:
         parityChar = 'N';
         break;
   }
   return parityChar;
}  // GetParityChar()



////////////////////////////////////////////////////////////////////
//
//  wxSerial Constructor
//                     
////////////////////////////////////////////////////////////////////
wxSerial::wxSerial(char *name)
{
   toggleRTS      =  FALSE;
   info.dcb       =  0;                   
   info.id        =  INVALID_HANDLE_VALUE;
   info.cb        =  0;
   info.portName  =  0;

   info.hPostWnd = NULL;
   info.connected = FALSE;
   info.threadId = 0;
   info.osWrite.Offset = 0;
   info.osWrite.OffsetHigh = 0;
   info.osWrite.hEvent = 0; 
   info.osRead.Offset = 0;
   info.osRead.OffsetHigh = 0;
   info.osRead.hEvent = 0; 
   info.hPostEvent = NULL;
   info.hCommWatchThread = NULL;
   info.softEnable = 0;

   Create (name);
}  // wxSerial::wxSerial constructor



////////////////////////////////////////////////////////////////////
//
//  wxSerial Destructor
//
////////////////////////////////////////////////////////////////////
wxSerial::~wxSerial()
{
   // close port if it has not been explicitly closed
   if (IsOpen())
      Close();

   // Free memory used by the name of the port (if any) and 
   if (info.portName)
      delete [] info.portName;
}  // wxSerial::~wxSerial destructor


////////////////////////////////////////////////////////////////////
//
// Generic function used for setting the error status when something
// fails during serial communication.
//
// If 'ShowErrorMsg' is true, the text of the error message will 
// actually be displayed
//
////////////////////////////////////////////////////////////////////
void wxSerial::setError(int errorCode)
{
   lastErrorCode = errorCode;

#if USING_WXWINDOWS
   if (ShowErrorMsg)
   {
      wxString ErrorMsg = GetErrorText();
      wxMessageBox(ErrorMsg.GetData(), "Error Notification", wxOK);
   }
#endif
}  // wxSerial::setError()



////////////////////////////////////////////////////////////////////
//
// Returns the text version of the last wxSerial error to occur
//
// NOTE: Copy the return char* value for long term use.
// 
////////////////////////////////////////////////////////////////////
const char *wxSerial::GetErrorText()
{
#if USING_WXWINDOWS
   static wxString msg;
#else
   static CString msg;
#endif
   switch (lastErrorCode)
   {
      case 0:
         msg = "";
         break;
      case wxSERIAL_PORT_IS_ALREADY_OPEN:
         msg = "Port is already open.\n\nPort must be closed before being re-created.";
         break;
      case wxSERIAL_PORT_IS_NOT_OPEN:
         msg = "Port has not been opened yet.";
         break;
      case wxSERIAL_PORT_IS_NOT_NAMED:
         msg = "Port has not been named";
         break;
      case wxSERIAL_COULD_NOT_OPEN_PORT:
         msg.sprintf("Could not open serial port: '%s'",info.portName);
         break;
      case wxSERIAL_CANNOT_CREATE_PORT_SETTINGS:
         msg = "Cannot create Device Control Block";
         break;
      case wxSERIAL_CANNOT_CHANGE_PORT_SETTINGS:
         msg = "Cannot create Device Control Block";
         break;
      case wxSERIAL_CANNOT_ALLOCATE_BUFFERS:
         msg = "Cannot allocate serial buffers";
         break;
      case wxSERIAL_READ_ERROR:
         msg.sprintf("Problem reading from serial port: ",info.portName);
         break;

      // Windows specific error messages
      case wxSERIAL_CANNOT_CREATE_READ_EVENT:
         msg = "Cannot create a Read event";
         break;
      case wxSERIAL_CANNOT_CREATE_WRITE_EVENT:
         msg = "Cannot create a Write event";
         break;
      case wxSERIAL_CANNOT_CREATE_POST_EVENT:
         msg = "Cannot create a Post event";
         break;
      case wxSERIAL_CANNOT_INIT_THREAD:
         msg = "Cannot initialize the Thread in Enable()";
         break;
      default:
      {
         msg.sprintf("Unknown error code encountered: %d",lastErrorCode);
         break;
      }
   }

   return msg.Chars();

}  // wxSerial::GetErrorText()


////////////////////////////////////////////////////////////////////
//
// Allows changing of the port settings (baud,parity,wordlenm
// stopbits) without closing and re-opening the port
//
////////////////////////////////////////////////////////////////////
bool wxSerial::ChangeSettings(wxBaud baudRate, wxParity parity, int wordLen, byte stopBits)
{
   wxString dcbStr;
   dcbStr.sprintf("%s: BAUD=%d PARITY=%c DATA=%d STOP=%d",info.portName,baudRate,GetParityChar(parity),wordLen,stopBits); 

   if (!BuildCommDCB(dcbStr.GetData(),info.dcb))
   {
      setError(wxSERIAL_CANNOT_CREATE_PORT_SETTINGS);
      return FALSE;
   }

   if (SetCommState(info.id,info.dcb)==0)
   {
      setError(wxSERIAL_CANNOT_CHANGE_PORT_SETTINGS);
      return FALSE;
   }

   return TRUE;
}  // wxSerial::ChangeSettings()



////////////////////////////////////////////////////////////////////
//
// Returns the current CommSettings last programmed for this port
//
////////////////////////////////////////////////////////////////////
void wxSerial::GetSettings(CommSettings *cs)
{
   cs->baudRate   = info.dcb->BaudRate;
   cs->parity     = info.dcb->Parity;
   cs->wordLen    = info.dcb->ByteSize;
   cs->stopBits   = info.dcb->StopBits;
}  // wxSerial::GetSettings()



////////////////////////////////////////////////////////////////////
//
// This method is called by the constructor for wxSerial and should
// not need to be called directly unless the port is closed and this
// instance of wxSerial is to be re-assigned to a different port.
//
////////////////////////////////////////////////////////////////////
bool wxSerial::Create(char *name)
{
   if (info.id != INVALID_HANDLE_VALUE)
   {
      setError(wxSERIAL_PORT_IS_ALREADY_OPEN);
      return FALSE;
   }

   if (info.portName)
      delete [] info.portName;

   info.portName = new char[strlen(name)+1];
   strcpy(info.portName,name);

   return TRUE;

}  // wxSerial::Create()

   
////////////////////////////////////////////////////////////////////
// This function will flush both buffers of any data, and close the 
// serial port so it may be used by other processes.
//
// NOTE: The serial port object still exists after being closed.  
//       It may be opened for use again using the Open() function.
////////////////////////////////////////////////////////////////////
bool wxSerial::Close()
{
   if (!IsOpen())
   {
      setError(wxSERIAL_PORT_IS_NOT_OPEN);
      return 0;
   }   

   info.connected = FALSE;
   
   // disable event notification
   SetCommMask( info.id, 0 );

   // murder thread
   if (info.hCommWatchThread)
      CloseHandle(info.hCommWatchThread);
// info.threadId != 0;

   // wait for thread to die
// while (info.threadId != 0);

   EscapeCommFunction(info.id, CLRDTR);

   // purge any outstanding reads/writes and close device handle
   PurgeComm( info.id, PURGE_TXABORT | PURGE_RXABORT | 
                           PURGE_TXCLEAR | PURGE_RXCLEAR );

   CloseHandle( info.osRead.hEvent );
   CloseHandle( info.osWrite.hEvent );
	CloseHandle( info.hPostEvent );
   CloseHandle( info.id );

   // clean up event objects
   delete info.dcb;
   info.id = INVALID_HANDLE_VALUE;  

   return TRUE;
}  // wxSerial::Close()



////////////////////////////////////////////////////////////////////
//
// Returns a value indicating whether the serial port has been 
// successfully opened for use
//
////////////////////////////////////////////////////////////////////
bool wxSerial::IsOpen()
{
   return (info.id != INVALID_HANDLE_VALUE);
} // wxSerial::IsOpen()



////////////////////////////////////////////////////////////////////
//
// Handles all the initialization of the serial port parameters.
//
// NOTE: The Create() function must be called before calling this 
//       function
//
////////////////////////////////////////////////////////////////////
bool wxSerial::Open(wxBaud baudRate, wxParity parity, int wordLen, byte stopBits, int inSize, int outSize, int RTSdelay)
{
   if (IsOpen())
   {
      // Nothing needs to be done, since its already open
      return TRUE;
   }
   
   if (!info.portName)
   {
      setError(wxSERIAL_PORT_IS_NOT_NAMED);
      return FALSE;
   }

   info.id = CreateFile(info.portName,
                        GENERIC_READ | GENERIC_WRITE,
                        0, /* exclusive access */
                        NULL, /* no security attrs */
                        OPEN_EXISTING,
                        NULL, /*FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, */
                        NULL);

   if (!IsOpen())
   {
      setError(wxSERIAL_COULD_NOT_OPEN_PORT);
      return FALSE;
   }
     
   info.dcb = new DCB;

   FillMemory(info.dcb, sizeof(DCB), 0);
   info.dcb->DCBlength = sizeof(DCB);

   info.dcb->fBinary          = TRUE;
   info.dcb->fParity          = TRUE;
   info.dcb->fDtrControl      = DTR_CONTROL_HANDSHAKE;
   info.dcb->fOutxCtsFlow     = FALSE;
   info.dcb->fOutxDsrFlow     = FALSE;
   info.dcb->fDsrSensitivity  = FALSE;
   info.dcb->fOutX            = FALSE;
   info.dcb->fInX             = FALSE;
   info.dcb->fRtsControl      = RTS_CONTROL_TOGGLE;

   if (!ChangeSettings(baudRate,parity,wordLen,stopBits))
      return FALSE;
/*
   wxString dcbStr;
   dcbStr.sprintf("%s: BAUD=%d PARITY=%c DATA=%d STOP=%d",info.portName,baudRate,GetParityChar(parity),wordLen,stopBits); 
#if USING_WXWINDOWS
   wxMessageBox(dcbStr.GetData(),"DCB configuration");
#endif
*/
   if (SetupComm( info.id, inSize, outSize))
      info.connected = TRUE;
   else
   {
      setError(wxSERIAL_CANNOT_ALLOCATE_BUFFERS);

      info.connected = FALSE;
      CloseHandle(info.id);
      info.id = INVALID_HANDLE_VALUE;

      return FALSE;
   }

   // create I/O event used for overlapped reads / writes
   info.osRead.hEvent = CreateEvent( NULL,   // no security
                                     TRUE,   // explicit reset req
                                     FALSE,  // initial event reset
                                     NULL);  // no name

   if (info.osRead.hEvent == NULL)
   {
      setError(wxSERIAL_CANNOT_CREATE_READ_EVENT);

      info.connected = FALSE;
      CloseHandle(info.id);
      info.id = INVALID_HANDLE_VALUE;

      return FALSE;
   }

   info.osWrite.hEvent = CreateEvent( NULL,     // no security
                                      TRUE,     // explicit reset req
                                      FALSE,    // initial event reset
                                      NULL );   // no name

   if (NULL == info.osWrite.hEvent)
   {
      setError(wxSERIAL_CANNOT_CREATE_WRITE_EVENT);

      info.connected = FALSE;
      CloseHandle(info.id);
      info.id = INVALID_HANDLE_VALUE;

      return FALSE;
   }

   // create "posted notification" event
   info.hPostEvent = CreateEvent(   NULL,    // no security
                                    TRUE,    // manual reset
                                    TRUE,    // initial event is set
                                    NULL );  // no name

   if (info.hPostEvent == NULL)
   {
      setError(wxSERIAL_CANNOT_CREATE_POST_EVENT);

      CloseHandle( info.osRead.hEvent );
      CloseHandle( info.osWrite.hEvent );
      info.connected = FALSE;
      CloseHandle(info.id);
      info.id = INVALID_HANDLE_VALUE;

      return FALSE;
   }

   delayForRTS = RTSdelay;

   return TRUE;

}  // wxSerial::Open()



////////////////////////////////////////////////////////////////////
//
// Aborts pending data transmissions in any combination of the 
// transmit/receive buffers for the serial port
//
// 'buffer' can be:
//       wxSERIAL_TX                (transmit buffer)
//       wxSERIAL_RX                (receive buffer)
//       wxSERIAL_TX & wxSERIAL_RX  (transmit and receive buffers)
//
////////////////////////////////////////////////////////////////////
bool wxSerial::Abort(int buffer)
{
   bool result=TRUE;

   if (buffer & wxSERIAL_TX) 
      result = result && PurgeComm(info.id, PURGE_TXABORT);
   if (buffer & wxSERIAL_RX)
      result = result && PurgeComm(info.id, PURGE_RXABORT);

   return result;
}  // wxSerial::Abort()



////////////////////////////////////////////////////////////////////
//
// Sends a break signal out the port
//
////////////////////////////////////////////////////////////////////
bool wxSerial::Break(bool setting)
{
   int result;
   if (setting)
      result = EscapeCommFunction(info.id, SETBREAK);
   else
      result = EscapeCommFunction(info.id, CLRBREAK);

   return (result != 0);
}  // wxSerial::Break()



////////////////////////////////////////////////////////////////////
//
// Clears the contents transmit/receive buffers (in any combination)
// for the serial port
//
// 'buffer' can be:
//       wxSERIAL_TX                (transmit buffer)
//       wxSERIAL_RX                (receive buffer)
//       wxSERIAL_TX & wxSERIAL_RX  (transmit and receive buffers)
//
////////////////////////////////////////////////////////////////////
bool wxSerial::Flush(int buffer)
{
   bool result=TRUE;

   if (buffer & wxSERIAL_TX) 
      result = result && PurgeComm(info.id, PURGE_TXABORT | PURGE_TXCLEAR);
   if (buffer & wxSERIAL_RX)
      result = result && PurgeComm(info.id, PURGE_RXABORT | PURGE_RXCLEAR);

   return result;
}  // wxSerial::Flush()



////////////////////////////////////////////////////////////////////
//
// This version of ::Read allows you to specify a maximum number of
// bytes to be read in.  The bytes read are stored in 'data'
// 
// Return value is the number of bytes read and stored in 'data'.
//
// NOTE: There is no NULL character appended at the end of 'data', 
//       as a data stream may contain a NULL as an acceptable
//       character
//
////////////////////////////////////////////////////////////////////
int wxSerial::Read(char *data, int maxLen)
{
   COMSTAT  ComStat;
   bool     fReadStat;
   DWORD    dwErrorFlags, dwLength;

   if ((int) info.id == -1)
      return 0;

   ClearCommError( info.id, &dwErrorFlags, &ComStat );

// dwLength = min( (DWORD) maxLen, ComStat.cbInQue );

   dwLength = maxLen;

   if (dwLength > 0)
   {
      fReadStat = (ReadFile( info.id, data, dwLength, &dwLength, /*&info.osRead overlapped*/ NULL) != 0);

      if (!fReadStat)
      {
         if (GetLastError() == ERROR_IO_PENDING)
         {
            // wait for a second for this transmission to complete
            if (WaitForSingleObject( info.osRead.hEvent, timeoutVal ))
               dwLength = 0;
            else
            {
               GetOverlappedResult( info.id,
                                    &info.osRead,
                                    &dwLength, FALSE );

               info.osRead.Offset += dwLength;
            }
         }
         else
            // some other error occurred
            dwLength = 0;
      }
   }

   return ((int) dwLength);
}  // wxSerial::Read()


////////////////////////////////////////////////////////////////////
//
// Will read either 'maxLen' characters into 'data', or will read
// an unknown number of characters in to 'data' until the 'termChar'
// character is encountered in the data stream.
//
// 'includeTerm' indicates whether the terminator character should be
// returned as the last character of valid 'data'.  If this is TRUE,
// the returned count value from this function will include the 
// terminator character in its count, otherwise it will not include
// the terminator character in the count.
//
// NOTE: Return value is the number of characters read from the port
//       and stored in 'data' (see 'includeTerm' above for additional
//       details).  There is no NULL character appended at the end of
//       'data', as a data stream may contain a NULL as an acceptable
//       character
//
////////////////////////////////////////////////////////////////////
int wxSerial::Read(char *data, int maxLen, int termChar, bool includeTerm)
{
   int   dwLength;
   char  rxStr[2];

   bool done=FALSE,abort=FALSE;

   rxStr[0] = 0;
   rxStr[1] = 0;
   dwLength = 0;

   while (!done && !abort)
   {
      abort = !Read(rxStr,1);

      if (!abort)
      {
         data[dwLength++]=rxStr[0];
         done = (dwLength >= maxLen) || (rxStr[0] == (char)termChar);
      }
   }

   return dwLength;
} // wxSerial::Read()



////////////////////////////////////////////////////////////////////
//
// Writes a single character 'data' out the serial port.  
// 
// This function is called by the second Write() function (below) 
// which is used to send multiple byte messages out the serial port.
// If this byte is to be the last byte of a full length message that
// is being sent out the port, the 'lastByte' parameter should be set
// to TRUE so that the RTS line can be toggled (if set to be toggled).
// 
// Return value will be the number of bytes (0 or 1) that were written
// successfully out to the serial port
//
////////////////////////////////////////////////////////////////////
int wxSerial::Write(char data, bool lastByte, bool calledFromWrite2)
{
   bool     fWriteStat;
   DWORD    dwBytesWritten;

   if (toggleRTS && !calledFromWrite2)
      SetRTS(TRUE);

   if (!IsOpen())
   {
      setError(wxSERIAL_PORT_IS_NOT_OPEN);
      return 0;
   }

   fWriteStat = (WriteFile(info.id, (LPSTR) &data, 1,
                   &dwBytesWritten, /*&info.osWrite overlapped*/ NULL) != 0);

   if (lastByte && toggleRTS && !calledFromWrite2)
   {
      // Need this to allow enough time for the last character to actually be sent out the port
      // before toggling the RTS line
      Sleep(delayForRTS);
      SetRTS(FALSE);
   }

   if (!fWriteStat && (GetLastError() == ERROR_IO_PENDING))
   {
      // wait for a short amount of time for this transmission to complete
      int value;
      if (value = WaitForSingleObject( info.osWrite.hEvent, timeoutVal ))
         dwBytesWritten = 0;
      else
      {
         GetOverlappedResult( info.id,
                              &info.osWrite,
                              &dwBytesWritten, FALSE );
         info.osWrite.Offset += dwBytesWritten;
      }
   }

   return(dwBytesWritten); 
}  // wxSerial::Write()


////////////////////////////////////////////////////////////////////
//
// Used to send multiple byte messages out a serial port
//
// Sends the first "len" bytes of "data" to port "port".  This 
// method will also handle blocks of bytes which contain NULLs, and
// send them out the port
//
// Return value is the number of bytes successfully written to the port.
//
////////////////////////////////////////////////////////////////////
int wxSerial::Write(DWORD len, const char *data)
{
   DWORD dwBytesWritten = 0;

   if (toggleRTS)
      SetRTS(TRUE);

   WriteFile(info.id, data, len, &dwBytesWritten, /*&info.osWrite overlapped*/ NULL);

   if (toggleRTS)
   {
      // Need this to allow enough time for the last character to actually be sent out the port
      // before toggling the RTS line
      Sleep(delayForRTS);
      SetRTS(FALSE);
   }

   return dwBytesWritten;
}  // wxSerial::Write()



////////////////////////////////////////////////////////////////////
//
// Sends an XOFF transmission to the port
//
////////////////////////////////////////////////////////////////////
bool wxSerial::Xoff()
{
   int result;
   result = EscapeCommFunction(info.id, SETXOFF);

   return (result != 0);
}  // wxSerial:Xoff()



////////////////////////////////////////////////////////////////////
//
// Sends an XON transmission to the port
//
////////////////////////////////////////////////////////////////////
bool wxSerial::Xon()
{
   int result;
   result = EscapeCommFunction(info.id, SETXON);

   return (result != 0);
}  // wxSerial:Xon()



////////////////////////////////////////////////////////////////////
//
// Returns the number of bytes queued in the incoming buffer
//
////////////////////////////////////////////////////////////////////
DWORD wxSerial::PendingIn()
{
   DWORD    dwErrorFlags;
   COMSTAT  ComStat;

   ClearCommError( info.id, &dwErrorFlags, &ComStat );

   return(ComStat.cbInQue); 
}  // wxSerial::PendingIn()



////////////////////////////////////////////////////////////////////
//
// Returns the number of bytes queued to be sent out the port
//
////////////////////////////////////////////////////////////////////
DWORD wxSerial::PendingOut()
{
   DWORD    dwErrorFlags;
   COMSTAT  ComStat;

   ClearCommError( info.id, &dwErrorFlags, &ComStat );

   return(ComStat.cbOutQue); 
}  // wxSerial::PendingOut()


////////////////////////////////////////////////////////////////////
//
// Sets the DTR line according to 'setting'
//
////////////////////////////////////////////////////////////////////
bool wxSerial::SetDTR(bool setting)
{
   int result;
   if (setting)
      result = EscapeCommFunction( info.id, SETDTR);
   else
      result = EscapeCommFunction( info.id, CLRDTR);

   return (result != 0);
}  // wxSerial:SetDTR()



////////////////////////////////////////////////////////////////////
//
// Sets the RTS line according to 'setting'
//
////////////////////////////////////////////////////////////////////
bool wxSerial::SetRTS(bool setting)
{
   int result;
   if (setting)
      result = (EscapeCommFunction(info.id, SETRTS));
   else
      result = (EscapeCommFunction(info.id, CLRRTS));

   return (result != 0);
}  // wxSerial:SetRTS()



////////////////////////////////////////////////////////////////////
//
// Sets the delay that will be incurred before auto-toggling the RTS 
// line if 'toggleRTS' is set to TRUE.
//
////////////////////////////////////////////////////////////////////
void wxSerial::SetRTSDelay(int delay)
{
   delayForRTS = delay;
}  // wxSerial::SetRTSDelay()



////////////////////////////////////////////////////////////////////
//
// Sets the timeout period for the comm port for both reads and 
// writes to the port.  A one(1) millisecond gap is provided for
// the read and write of each character, plus the value passed in
// 'timeout'
//
////////////////////////////////////////////////////////////////////
bool wxSerial::SetTimeouts(DWORD timeout)
{
   COMMTIMEOUTS cto;
   GetCommTimeouts( info.id, &cto );
   
   cto.ReadTotalTimeoutConstant     = 0;
   cto.ReadIntervalTimeout          = timeout;
   cto.ReadTotalTimeoutMultiplier   = timeout;
   cto.WriteTotalTimeoutConstant    = 0;
   cto.WriteTotalTimeoutMultiplier  = timeout;

   // If using the port is opened in overlap mode, the above settings
   // are ignored.  This member variable of wxSerial holds the value
   // that is passed in to WaitForSingleObject() for a timeout
   timeoutVal = timeout;

   return (SetCommTimeouts( info.id, &cto ) != 0);
}  // wxSerial::SetTimeouts()



bool wxSerial::SetTimeouts(wxCommTimeouts timeouts)
{
   COMMTIMEOUTS cto;
   GetCommTimeouts( info.id, &cto );
   
   cto.ReadTotalTimeoutConstant     = timeouts.readTimeoutConstant;
   cto.ReadIntervalTimeout          = timeouts.readIntervalTimeout;
   cto.ReadTotalTimeoutMultiplier   = timeouts.readTimeoutMultiplier;
   cto.WriteTotalTimeoutConstant    = timeouts.writeTimeoutConstant;
   cto.WriteTotalTimeoutMultiplier  = timeouts.writeTimeoutMultiplier;

   // If the port is opened in overlap mode, the above settings
   // are ignored.  This member variable of wxSerial holds the value
   // that is passed in to WaitForSingleObject() for a timeout
   timeoutVal = timeouts.overlappedTimeout;

   return (SetCommTimeouts( info.id, &cto ) != 0);
}  // wxSerial::SetTimeouts()



////////////////////////////////////////////////////////////////////
//
// This function provides a way to set the private member variable 
// 'toggleRTS' to indicate whether the RTS line should be 
// automatically raised just before transmitting a telegram, and
// then lowered immediately after sending a telegram. 
//
// Default setting for the port is for 'toggleRTS' to be FALSE
//
////////////////////////////////////////////////////////////////////
void wxSerial::ToggleRTS(bool setting)
{
   toggleRTS = setting;
}  // wxSerial:ToggleRTS()



   
   
/*
 * ==========================================================
 *
 *  Code for multi-threaded interrupt driven communication
 *
 *  This code was part of the original code that Andrew
 *  donated.  I(gt) have little knowledge of how to handle
 *  interrupt driven communication, but it may be a starting
 *  point for someone else
 *
 * ==========================================================
 */

/*
 * Used to create an initial thread for the serial port for interrupt driven
 * communication
 */
bool wxSerial::Enable(HWND hwnd)
{
   DWORD    dwThreadID;

   if (IsOpen())
   {
      setError(wxSERIAL_PORT_IS_NOT_OPEN);
      return FALSE;
   } 

   info.softEnable = 1;
// info.cb = &defaultwxSerialCallBack;
   
   if (NULL == (info.hCommWatchThread =
                     CreateThread(  (LPSECURITY_ATTRIBUTES) NULL,
                                    0, 
                                    (LPTHREAD_START_ROUTINE) CommWatchProc,
                                    (LPVOID) &info,
                                    0, &dwThreadID ))) 
   {
      setError(wxSERIAL_CANNOT_INIT_THREAD);

      info.connected = FALSE;
      CloseHandle(info.id);
      info.id = INVALID_HANDLE_VALUE;

      return FALSE;
   }
   else
   {
      info.hPostWnd = hwnd;
      info.threadId=dwThreadID;

      // Adjust thread priority
      SetThreadPriority( info.hCommWatchThread, THREAD_PRIORITY_BELOW_NORMAL);
      ResumeThread( info.hCommWatchThread );

      // assert DTR
      EscapeCommFunction( info.id, SETDTR );
   }
   
   return TRUE;
}  // wxSerial::Enable()


/*
 * Used to resume the functioning of an already defined thread that has been disabled/suspended
 */
void wxSerial::Enable()
{
   info.softEnable = 1;
   ResumeThread(info.hCommWatchThread);
}  // wxSerial::Enable()


/*
 * Suspends an existing thread
 */
void wxSerial::Disable()
{
   SuspendThread(info.hCommWatchThread);
   info.softEnable = 0;
}  // wxSerial::Disable()



void wxSerial::SetCallBack(wxSerial_CallBack *scb)
{
   info.cb = scb;
}  // wxSerial::SetCallBack()



wxSerial_CallBack::DoCallBack(wxSerial *port)
{
   COMSTAT  ComStat;
   DWORD    dwErrorFlags;

   ClearCommError( port->info.id, &dwErrorFlags, &ComStat );

   int count   =  ComStat.cbInQue;

   while(ComStat.cbInQue)
   {
      if (count)
      { 
         char *data = new char[count+1];
         int nRead = port->Read(data, count);
         if (nRead<0) 
            port->setError(wxSERIAL_READ_ERROR);
         else
            data[count]=0;

         delete data;
      }
      ClearCommError( port->info.id, &dwErrorFlags, &ComStat );
   }              

   return 0;
}  // wxSerial_CallBack::DoCallBack



DWORD FAR PASCAL CommWatchProc( LPSTR lpData )
{
   DWORD       dwTransfer, dwEvtMask;
   ThreadInfo  *info = (ThreadInfo*) lpData;
   OVERLAPPED  os;

   memset( &os, 0, sizeof( OVERLAPPED ) );

   // create I/O event used for overlapped read

   os.hEvent = CreateEvent( NULL,   // no security
                            TRUE,   // explicit reset req
                            FALSE,  // initial event reset
                            NULL ); // no name
   if (os.hEvent == NULL)
   {
#if USING_WXWINDOWS
      if (ShowErrorMsg)
         wxMessageBox ("Error: Cannot create Thread event"); 
#endif
      return ( FALSE );
   }

   if (!SetCommMask( info->id, EV_RXCHAR ))
      return ( FALSE );

   while ( info->connected )                          
   {
      dwEvtMask = 0;           
      if (!WaitCommEvent( info->id, &dwEvtMask, &os ))
      {
         if (ERROR_IO_PENDING == GetLastError())
         {
            GetOverlappedResult( info->id, &os, &dwTransfer, TRUE );
            os.Offset += dwTransfer;
         }
      }

      if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR)
      {
         WaitForSingleObject( info->hPostEvent, 0xFFFFFFFF );

         // reset event
         ResetEvent( info->hPostEvent );

         if (info->softEnable)
         {
            // last message was processed, O.K. to post
            PostMessage( info->hPostWnd, WM_COMMNOTIFY,
                         (WPARAM) info->portName, MAKELONG( CN_EVENT, 0 ) );
         }
      }
   }

   info->threadId = 0   ;

   // get rid of event handle
   CloseHandle( os.hEvent );

   return( TRUE );

}  // CommWatchProc()
