// ******************** START OF WIN16.CPP ********************
//
//
// This file contains the source code that implements the
// Win16Port class. This class uses the Windows 3.1 comm
// driver.

#include <windows.h>
#include <ctype.h>
#include "win16.h"

#define INPUT_BUFFER_SIZE 1024
#define OUTPUT_BUFFER_SIZE 1024

// The Win16Port constructor opens the port using the MS-Windows
// function call OpenComm().  It then does the standard reading of
// the existing settings, saving them, and then writing the new
// settings.  Note that we acquire a pointer to the internal driver
// byte that has the Modem Status Lines.

Win16Port::Win16Port( RS232PortName port,
                      long baud_rate,
                      char parity,
                      int word_length,
                      int stop_bits,
                      int dtr,
                      int rts,
                      int xon_xoff,
                      int rts_cts,
                      int dtr_dsr )
{
    char name[ 15 ];

    port_name = port;
    error_status = RS232_SUCCESS;
    line_status = 0;

    first_debug_output_line = RS232::FormatDebugOutput();
    debug_line_count = FormatDebugOutput();

    wsprintf( name, "COM%d", port_name + 1 );
    handle = OpenComm( name, INPUT_BUFFER_SIZE, OUTPUT_BUFFER_SIZE );
    if ( handle < 0 ) {
        error_status = translate_windows_error( handle );
        return;
    }
    modem_status_register = (char FAR *) SetCommEventMask( handle, 0 );
    modem_status_register += 35;
    read_settings();
    saved_settings = settings;
    saved_dcb = dcb;
    settings.Adjust( baud_rate,
                     parity,
                     word_length,
                     stop_bits,
                     dtr,
                     rts,
                     xon_xoff,
                     rts_cts,
                     dtr_dsr );
    write_settings();
}

// The destructor restores the previous settings, then closes the
// port using the Windows call,

Win16Port::~Win16Port( void )
{
    if ( error_status == RS232_SUCCESS ) {
        settings = saved_settings;
        dcb = saved_dcb;
        write_settings();
        CloseComm( handle );
    }
}

// The set function looks much like the Set function for all the other
// RS232 derivatives seen in the book.  It just adjusts the current
// port settings, then calls write_settings() to do the dirty work.

RS232Error Win16Port::Set( long baud_rate,
                           int parity,
                           int word_length,
                           int stop_bits )
{
    settings.Adjust( baud_rate,
                     parity,
                     word_length,
                     stop_bits,
                     UNCHANGED,
                     UNCHANGED,
                     UNCHANGED,
                     UNCHANGED,
                     UNCHANGED );
    return write_settings();
}

// read_settings() does most of its work on the DCB associated with
// the current port.  All of the settings that it can figure out
// are found in the DCB.  The current settings of RTS and DTR can't
// be found, so they are set to -1, indicating that they are unknown.

void Win16Port::read_settings( void )
{
    int status;
    RS232Error error;

    status = GetCommState( handle, &dcb );
    if ( status < 0 ) {
        error = translate_windows_error( status );
        if ( error >= RS232_ERROR ) 
            error_status = error;
        return;
    }
    if ( ( dcb.BaudRate & 0xff00 ) == 0xff00 )
        switch( dcb.BaudRate ) {
            case CBR_110    : settings.BaudRate = 110;     break;
            case CBR_300    : settings.BaudRate = 300;     break;
            case CBR_600    : settings.BaudRate = 600;     break;
            case CBR_1200   : settings.BaudRate = 1200;    break;
            case CBR_2400   : settings.BaudRate = 2400;    break;
            case CBR_4800   : settings.BaudRate = 4800;    break;
            case CBR_9600   : settings.BaudRate = 9600;    break;
            case CBR_14400  : settings.BaudRate = 14400;   break;
            case CBR_19200  : settings.BaudRate = 19200;   break;
            case CBR_38400  : settings.BaudRate = 38400L;  break;
            case CBR_56000  : settings.BaudRate = 56000L;  break;
            case CBR_128000 : settings.BaudRate = 128000L; break;
            case CBR_256000 : settings.BaudRate = 256000L; break;
            default         : settings.BaudRate = -1;      break;
    } else
        settings.BaudRate = dcb.BaudRate;
    switch ( dcb.Parity ) {
        case NOPARITY    : settings.Parity = 'N'; break;
        case ODDPARITY   : settings.Parity = 'O'; break;
        case EVENPARITY  : settings.Parity = 'E'; break;
        case MARKPARITY  : settings.Parity = 'M'; break;
        case SPACEPARITY : settings.Parity = 'S'; break;
        default          : settings.Parity = '?'; break;
    }
    settings.WordLength = dcb.ByteSize;
    switch ( dcb.StopBits ) {
        case ONESTOPBIT   : settings.StopBits = 1; break;
        case ONE5STOPBITS : 
        case TWOSTOPBITS  : settings.StopBits = 2; break;
        default           : settings.StopBits = -1; break;
    }
    settings.Rts = -1;
    settings.Dtr = -1;
    settings.XonXoff = dcb.fOutX;
    settings.RtsCts = dcb.fRtsflow;
    settings.DtrDsr = dcb.fDtrflow;
}

// write_settings() does almost everything it needs to do by
// modifying the DCB and then calling SetCommState().  Setting
// RTS and DTR is done by sending an escape function to the
// driver.

RS232Error Win16Port::write_settings( void )
{
    int set_status;
    RS232Error status = RS232_SUCCESS;

    if ( settings.BaudRate <= 19200L )
        dcb.BaudRate = (int) settings.BaudRate;
    else if ( settings.BaudRate == 38400L )
        dcb.BaudRate = CBR_38400;
    else
       status = RS232_ILLEGAL_BAUD_RATE;
    switch ( toupper( settings.Parity ) ) {
        case 'N' : dcb.Parity = NOPARITY; break;
        case 'E' : dcb.Parity = EVENPARITY; break;
        case 'O' : dcb.Parity = ODDPARITY; break;
        case 'M' : dcb.Parity = MARKPARITY; break;
        case 'S' : dcb.Parity = SPACEPARITY; break;
        default : status = RS232_ILLEGAL_PARITY_SETTING; break;
    }
    switch ( settings.WordLength ) {
        case 8 : dcb.ByteSize = 8; break;
        case 7 : dcb.ByteSize = 7; break;
        case 6 : dcb.ByteSize = 6; break;
        case 5 : dcb.ByteSize = 5; break;
        default : status = RS232_ILLEGAL_PARITY_SETTING; break;
    }
    switch ( settings.StopBits ) {
        case ONESTOPBIT   : dcb.StopBits = 1; break;
        case ONE5STOPBITS :
        case TWOSTOPBITS  : dcb.StopBits = 2; break;
        default : status = RS232_ILLEGAL_STOP_BITS; break;
    }
    if ( settings.Rts == 0 )
        EscapeCommFunction( handle, CLRRTS );
    else if ( settings.Rts == 1 )
        EscapeCommFunction( handle, SETRTS );
    if ( settings.Dtr == 0 )
        EscapeCommFunction( handle, CLRDTR );
    else if ( settings.Dtr == 1 )
        EscapeCommFunction( handle, SETDTR );
    dcb.fOutX = dcb.fInX = ( settings.XonXoff != 0 );
    dcb.fOutxCtsFlow = dcb.fRtsflow = ( settings.RtsCts != 0 );
    dcb.fOutxDsrFlow = dcb.fDtrflow = ( settings.DtrDsr != 0 );
    set_status = SetCommState( &dcb );
    if ( set_status == 0 )
        return status;
    return translate_windows_error( set_status );
}

// The Windows port driver sends back its own error codes in
// certain stituations.  This function translates them to their
// class RS232 equivalents.

RS232Error Win16Port::translate_windows_error( int error )
{
    switch ( error ) {
        case IE_BADID     : return RS232_PORT_NOT_FOUND;
        case IE_BAUDRATE  : return RS232_ILLEGAL_BAUD_RATE;
        case IE_BYTESIZE  : return RS232_ILLEGAL_WORD_LENGTH;
        case IE_DEFAULT   : return (RS232Error) WINDOWS_PORT_DEFAULT_PARAMETERS;
        case IE_HARDWARE  : return RS232_PORT_IN_USE;
        case IE_MEMORY    : return RS232_MEMORY_ALLOCATION_ERROR;
        case IE_NOPEN     : return (RS232Error) WINDOWS_PORT_NOT_OPEN;
        case IE_OPEN      : return (RS232Error) WINDOWS_PORT_ALREADY_OPEN;
        default           : return RS232_ERROR;
    }
}

// ReadComm() does all the work necessary to implement this function.

int Win16Port::read_byte( void )
{
    int result;
    unsigned char c;
    COMSTAT comstat;

    if ( error_status < 0 )
        return error_status;
    result = ReadComm( handle, &c, 1 );
    if ( result > 0 )
        return (int) c;
    line_status |= GetCommError( handle, &comstat );
    return RS232_TIMEOUT;
}

// Before calling WriteComm(), I check to be sure there is room
// for a new character in the output queue.  Once that is
// determined, WriteComm() does the rest of the work.

int Win16Port::write_byte( int c )
{
    int result;
    COMSTAT comstat;

    if ( error_status < 0 )
        return error_status;
    line_status |= GetCommError( handle, &comstat );
    if ( comstat.cbOutQue == OUTPUT_BUFFER_SIZE )
        return RS232_TIMEOUT;
    result = WriteComm( handle, &c, 1 );
    if ( result > 0 )
        return RS232_SUCCESS;
    line_status |= GetCommError( handle, &comstat );
    return RS232_TIMEOUT;
}

// The read_buffer() routine is slightly more complicated than
// read_byte(), but it still calls ReadComm() to do most of the
// work.  It just has to take into account the possibility that
// an incomplete read may take place.

int Win16Port::read_buffer( char *buffer, unsigned int count )
{
    int result;
    COMSTAT comstat;

    ByteCount = 0;
    if ( error_status < 0 )
        return error_status;
    result = ReadComm( handle, buffer, (int) count );
    if ( result > 0 )
        ByteCount = result;
    else {
        ByteCount = -result;
        line_status |= GetCommError( handle, &comstat );
    }
    if ( ByteCount < count )
        return RS232_TIMEOUT;
    else
        return RS232_SUCCESS;
}

// Like read_buffer(), this routine is basically just an extension
// of its single byte sibling.  However, it has to take into account
// the possibility that a partial buffer write may occur.

int Win16Port::write_buffer( char *buffer, unsigned int count )
{
    int result;
    COMSTAT comstat;
    unsigned int buffer_space;

    ByteCount = 0;
    if ( error_status < 0 )
        return error_status;
    line_status |= GetCommError( handle, &comstat );
    buffer_space = OUTPUT_BUFFER_SIZE - comstat.cbOutQue;
    if ( buffer_space > count )
        result = WriteComm( handle, buffer, count );
    else
        result = WriteComm( handle, buffer, buffer_space );
    if ( result > 0 )
        ByteCount = result;
    else {
        ByteCount = -result;
        line_status |= GetCommError( handle, &comstat );
    }
    if ( ByteCount < count )
        return RS232_TIMEOUT;
    return RS232_SUCCESS;
}

// This function has a dedicated Windows API call to do its work.

int Win16Port::FlushRXBuffer( void )
{
    int status;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    status = FlushComm( handle, 1 );
    if ( status != 0 )
        return translate_windows_error( status );
    return RS232_SUCCESS;
}

// The COMSTAT structure returned from GetCommError contains
// the information we need to determine how the space in both
// the TX and RX buffers is presently being used.

int Win16Port::TXSpaceFree( void )
{
    COMSTAT comstat;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    line_status |= GetCommError( handle, &comstat );
    return OUTPUT_BUFFER_SIZE - comstat.cbOutQue;
}

int Win16Port::RXSpaceUsed( void )
{
    COMSTAT comstat;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    line_status |= GetCommError( handle, &comstat );
    return comstat.cbInQue;
}

// The Windows API function takes care of sending the break.

int Win16Port::Break( long milliseconds )
{
    long timer;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    SetCommBreak( handle );
    timer = ReadTime() + milliseconds;
    while ( ReadTime() < timer )
        IdleFunction();
    ClearCommBreak( handle );
    return RS232_SUCCESS;
}

// The four Modem Status routines all take advantage of directly
// peeking at the MSR byte inside the driver.  They just mask off
// the bit they are interested in, and return a logical result to
// the calling routine.

int Win16Port::Cd( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return ( *modem_status_register & MSR_CD ) != 0;
}

int Win16Port::Ri( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return ( *modem_status_register & MSR_RI ) != 0;
}

int Win16Port::Cts( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return ( *modem_status_register & MSR_CTS ) != 0;
}

int Win16Port::Dsr( void )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    return ( *modem_status_register & MSR_DSR ) != 0;
}

// The four line status routines and the software overrrun error
// detect routine all get their information from the
// Windows API function GetCommError().  It returns all the line
// status bits, rearranged by Windows just for fun.

int Win16Port::SoftwareOverrunError( int clear )
{
    COMSTAT comstat;
    int return_value;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    line_status |= GetCommError( handle, &comstat );
    return_value = ( ( line_status & CE_RXOVER ) != 0 );
    if ( clear != UNCHANGED && clear != 0 )
        line_status &= ~CE_RXOVER;
    return return_value;
}

int Win16Port::ParityError( int reset )
{
    COMSTAT comstat;
    int return_value;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    line_status |= GetCommError( handle, &comstat );
    return_value = ( ( line_status & CE_RXPARITY ) != 0 );
    if ( reset != UNCHANGED && reset != 0 )
        line_status &= ~CE_RXPARITY;
    return return_value;
}

int Win16Port::BreakDetect( int reset )
{
    COMSTAT comstat;
    int return_value;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    line_status |= GetCommError( handle, &comstat );
    return_value = ( ( line_status & CE_BREAK ) != 0 );
    if ( reset != UNCHANGED && reset != 0 )
        line_status &= ~CE_BREAK;
    return return_value;
}

int Win16Port::FramingError( int reset )
{
    COMSTAT comstat;
    int return_value;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    line_status |= GetCommError( handle, &comstat );
    return_value = ( ( line_status & CE_FRAME ) != 0 );
    if ( reset != UNCHANGED && reset != 0 )
        line_status &= ~CE_FRAME;
    return return_value;
}

int Win16Port::HardwareOverrunError( int reset )
{
    COMSTAT comstat;
    int return_value;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    line_status |= GetCommError( handle, &comstat );
    return_value = ( ( line_status & CE_OVERRUN ) != 0 );
    if ( reset != UNCHANGED && reset != 0 )
        line_status &= ~CE_OVERRUN;
    return return_value;
}

// All of the handshaking functions rely on write_settings() to do
// the dirty work of actually changing the settings.  They then
// return the current setting to the caller.

int Win16Port::XonXoffHandshaking( int setting )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    if ( setting != UNCHANGED ) {
        settings.XonXoff = ( setting != 0 );
        write_settings();
    }
    return( settings.XonXoff );
}

int Win16Port::RtsCtsHandshaking( int setting )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    if ( setting != UNCHANGED ) {
        settings.RtsCts = ( setting != 0 );
        write_settings();
    }
    return( settings.RtsCts );
}

int Win16Port::DtrDsrHandshaking( int setting )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    if ( setting != UNCHANGED ) {
        settings.DtrDsr = ( setting != 0 );
        write_settings();
    }
    return( settings.DtrDsr );
}

// Setting DTR and RTS is done with a Windows API Escape code.
// The escape sequence is undocumented but widely known, and
// used by so much software it is unlikely to ever change.

int Win16Port::Dtr( int setting )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    if ( setting != UNCHANGED ) {
        if ( settings.DtrDsr == 1 )
            return WINDOWS_PORT_HANDSHAKE_LINE_IN_USE;
        else {
            if ( ( settings.Dtr = setting ) == 1 )
                EscapeCommFunction( handle, SETDTR );
            else
                EscapeCommFunction( handle, CLRDTR ); 
        }
    }
    return settings.Dtr;
}

int Win16Port::Rts( int setting )
{
    if ( error_status < RS232_SUCCESS )
        return error_status;
    if ( setting != UNCHANGED ) {
        if ( settings.RtsCts == 1 )
            return WINDOWS_PORT_HANDSHAKE_LINE_IN_USE;
        else {
            if ( ( settings.Rts = setting ) == 1 )
                EscapeCommFunction( handle, SETRTS );
            else
                EscapeCommFunction( handle, CLRRTS ); 
        }
    }
    return settings.Rts;
}

// All the information needed to perform the following functions is
// found in the COMSTAT function returned by GetCommError().


int Win16Port::RXSpaceFree( void )
{
    COMSTAT comstat;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    line_status |= GetCommError( handle, &comstat );
    return INPUT_BUFFER_SIZE - comstat.cbInQue;
}

int Win16Port::TXSpaceUsed( void )
{
    COMSTAT comstat;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    line_status |= GetCommError( handle, &comstat );
    return comstat.cbOutQue;
}

// The Windows API provides a dedicated function to perform this task.

int Win16Port::FlushTXBuffer( void )
{
    int status;

    if ( error_status < RS232_SUCCESS )
        return error_status;
    status = FlushComm( handle, 0 );
    if ( status != 0 )
        return translate_windows_error( status );
    return RS232_SUCCESS;
}

// The debug output includes a complete dump of the current DCB
// structure for the port, which describes virtually everything
// windows knows about the port.  The only thing left out here
// which could be interesting in the COMSTAT structure.

int Win16Port::FormatDebugOutput( char *buffer, int line_number )
{
    if ( buffer == 0 )
        return( first_debug_output_line +  7 );
    if ( line_number < first_debug_output_line )
        return RS232::FormatDebugOutput( buffer, line_number );
    switch( line_number - first_debug_output_line ) {
        case 0 :
            wsprintf( buffer,
                      (LPSTR) "Derived class: Win16Port  "
                      "Ri: %2d  Cts: %2d  Cd: %2d  Dsr: %2d  "
                      "RX Overrun: %d",
                      Ri(), Cts(), Cd(), Dsr(),
                      SoftwareOverrunError() );
            break;
        case 1 :
            wsprintf( buffer,
                      "TX Used: %5d   RX Free: %5d  "
                      "Parity Err: %d  Break: %d  "
                      "Overrun: %d  Framing Err: %d",
                      TXSpaceUsed(), RXSpaceFree(),
                      ParityError(),
                      BreakDetect(),
                      HardwareOverrunError(),
                      FramingError() );
            break;
        case 2 :
            wsprintf( buffer,
                      "DCB: RlsTimeout: %04x  CtsTimeout: %04x  "
                      "DsrTimeout: %04x  fBinary: %1d",
                      dcb.RlsTimeout,
                      dcb.CtsTimeout,
                      dcb.DsrTimeout,
                      dcb.fBinary );
            break;
        case 3 :
            wsprintf( buffer,
                      "DCB: fRtsDisable: %1d  fParity: %1d  "
                      "fOutxCtsFlow: %1d  fOutxDsrFlow: %1d",
                      dcb.fRtsDisable,
                      dcb.fParity,
                      dcb.fOutxCtsFlow,
                      dcb.fOutxDsrFlow );
            break;
        case 4 :
            wsprintf( buffer,
                      "DCB: fDtrDisable: %1d  fOutX: %1d  fInx: %1d  "
                      "fPeChar: %1d  fNull: %1d  fChEvt: %1d",
                      dcb.fDtrDisable,
                      dcb.fOutX,
                      dcb.fInX,
                      dcb.fPeChar,
                      dcb.fNull,
                      dcb.fChEvt );
            break;
        case 5 :
            wsprintf( buffer,
                      "DCB: fDtrflow: %1d  fRtsflow: %1d  XonChar: %02x  "
                      "XoffChar: %02x  XonLim: %04x  XoffLim: %04x",
                      dcb.fDtrflow,
                      dcb.fRtsflow,
                      dcb.XonChar,
                      dcb.XoffChar,
                      dcb.XonLim,
                      dcb.XoffLim );
            break;
        case 6 :
            wsprintf( buffer,
                      "DCB: PeChar: %02x  EofChar: %02x  EvtChar: %02x  "
                      "TxDelay:  %04x",
                      dcb.PeChar,
                      dcb.EofChar,
                      dcb.EvtChar,
                      dcb.TxDelay );
            break;
        default :
            return RS232_ILLEGAL_LINE_NUMBER;
    }
    return RS232_SUCCESS;
}


char * Win16Port::ErrorName( int error )
{
    if ( error < RS232_NEXT_FREE_ERROR && error >= RS232_ERROR )
        return RS232::ErrorName( error );
    if ( error < RS232_NEXT_FREE_WARNING && error >= RS232_WARNING )
        return RS232::ErrorName( error );
    if ( error >= RS232_SUCCESS )
        return RS232::ErrorName( error );
    switch ( error ) {
        case WINDOWS_PORT_DEFAULT_PARAMETERS :
            return "Default parameters in error";
        case WINDOWS_PORT_NOT_OPEN :
            return "Port not open";
        case WINDOWS_PORT_ALREADY_OPEN :
            return "Port already open";
        case WINDOWS_PORT_HANDSHAKE_LINE_IN_USE :
            return "Handshake line in use";
        default :
            return( "Undefined error" );
    }
}

// For Win16 we need a pair of OS specific routines to handle a couple
// of timing issues properly. In a program that really uses the idle
// function you might want to replace this function with one that
// performs a PeekMessage() call

int RS232::IdleFunction( void )
{
    return RS232_SUCCESS;
}

//
// ReadTime() returns the current time of day in milliseconds.  It uses
// the Windows specific tick count function, instead of polling MS-DOS
// or the BIOS.
//

long ReadTime( void )
{
    return GetTickCount();
}

// ******************** END OF WIN16.CPP ********************

