
       /*******************************************************
        *                        port.c                       *
        *                                                     *
        *  Get Trimble GPS data through a serial com port.    *
        *                                                     *
        *  This is Windows-specific code!                     *
        *******************************************************/


#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <earthworm.h>
#include <time_ew.h>
#include "adsendxs.h"

static HANDLE handle = 0;


/*****************************************************
  InitPort

  Initialize the PC com port.
 *****************************************************/

int InitPort( int ComPort, int BaudRate )
{
   COMMTIMEOUTS timeouts;
   DCB          dcb;                // Data control block
   char         portName[20];       // Com port name

/* Sanity checks
   *************/
   if ( ComPort < 1 || ComPort > 18 )
   {
      logit( "et", "Invalid value of ComPort (%d).\n", ComPort );
      return -1;
   }

   switch ( BaudRate )
   {
      case   1200: break;
      case   2400: break;
      case   4800: break;
      case   9600: break;
      case  14400: break;
      case  19200: break;
      case  38400: break;
      case  57600: break;
      case 115200: break;
      case 128000: break;
      case 256000: break;
      default:
         logit( "et", "Invalid baud rate: %d\n", BaudRate );
         return -1;
   }


/* Open the com port.
   Share mode must be zero, which allows exclusive access only.
   The Create parameter must be OPEN_EXISTING.
   The TemplateFile parameter must be NULL.
   ***********************************************************/
   sprintf( portName, "COM%d", ComPort );
   handle = CreateFile( portName,
                        GENERIC_READ|GENERIC_WRITE,     // Desired access
                        0,                              // Share mode
                        NULL,                           // Default security attributes
                        OPEN_EXISTING,                  // Create parameter
                        FILE_ATTRIBUTE_NORMAL,          // Flags and attributes
                        NULL );                         // Template file

   if ( handle == INVALID_HANDLE_VALUE )
   {
      int rc = GetLastError();

      if ( rc == ERROR_ACCESS_DENIED )
         logit( "et", "CreateFile() error. Access denied to port: %s\n", portName );
      else
         logit( "et", "Error %d in CreateFile.\n", rc );
      return -1;
   }

/* Set the baud rate, flow control, etc.
   These are the default settings for a
   Trimble Thunderbold E GPS receiver.
   ************************************/
   if ( GetCommState(handle, &dcb) == 0 )
   {
      logit( "et", "Error %d in GetCommState.\n", GetLastError() );
      return -1;
   }

   dcb.BaudRate        = (DWORD)BaudRate;
   dcb.ByteSize        = 8;            // Hardwired
   dcb.Parity          = NOPARITY;     // Hardwired
   dcb.StopBits        = ONESTOPBIT;   // Hardwired
   dcb.fOutxCtsFlow    = FALSE;        // Disable RTS/CTS flow control
   dcb.fOutxDsrFlow    = FALSE;        // Disable DTR/DSR flow control
   dcb.fDsrSensitivity = FALSE;        // Driver ignores DSR
   dcb.fOutX           = FALSE;        // Disable XON/XOFF out flow control
   dcb.fInX            = FALSE;        // Disable XON/XOFF in flow control
   dcb.fNull           = FALSE;        // Don't discard null bytes
   dcb.fAbortOnError   = TRUE;         // Abort rd/wr on error

   if ( SetCommState(handle, &dcb) == 0 )
   {
      logit( "et", "Error %d in SetCommState.\n", GetLastError() );
      logit( "et", "Possible invalid baud rate: %d\n", BaudRate );
      return -1;
   }

/* Configure timeout parameters so ReadFile will
   return immediately, whether any characters
   are available or not.
   *********************************************/
   timeouts.ReadIntervalTimeout         = MAXDWORD;
   timeouts.ReadTotalTimeoutMultiplier  = 0;
   timeouts.ReadTotalTimeoutConstant    = 0;
   timeouts.WriteTotalTimeoutMultiplier = 0;
   timeouts.WriteTotalTimeoutConstant   = 0;
   SetCommTimeouts( handle, &timeouts );
   return 0;
}


/******************************************************
  PurgeComInput

  Purge the input com port buffer.
 ******************************************************/
int PurgeComInput( void )
{
   if ( PurgeComm(handle, PURGE_RXCLEAR) == 0 )
   {
      logit( "et", "Error %d in PurgeComm.\n", GetLastError() );
      return -1;
   }
   return 0;
}


/********************************************************
  ReadGpsPacket

  Read one packet from the GPS receiver via com port.

  Returns number of bytes read (could be 0).
  Returns -1 if a read error occurred.
  Returns -2 if the buffer overflowed.

 The com port is left open on return.
 ********************************************************/

int ReadGpsPacket( UINT8 packet[] )
{
   DWORD numRead;
   BOOL  success;
   UINT8 chr;
   int   npacketchar = 0;
   int   nDLE = 0;
   int   first = 1;

/* Read a packet from the GPS receiver.
   The packet format is:
   <DLE><id><data string bytes><DLE><ETX>
   **************************************/
   while ( 1 )
   {
      success = ReadFile( handle, &chr, 1, &numRead, 0 );
      if ( !success )
      {
         int rc = GetLastError();
         char errbuf[200];
         int nchar;

         logit( "et", "ReadFile error %d\n", rc );
         nchar = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL,
                                rc, 0, (LPSTR)errbuf, 200, NULL );
         if ( nchar == 0 )
            logit( "et", "FormatMessage error.\n" );
         else
            logit( "et", "%s\n", errbuf );
         return -1;
      }

/* If no bytes are available, sleep a few milliseconds
   and try again to read a byte.
   ***************************************************/
      if ( numRead == 0 )
      {
         sleep_ew( 10 );
         continue;
      }

/* The first character must be a DLE
   *********************************/
      if ( first && (chr != DLE) )
         return -3;
      else
         first = 0;

/* Count the total DLE's received
   ******************************/
      if ( chr == DLE ) nDLE++;
      if ( (chr == ETX) && (nDLE % 2 == 0) ) break;

/* Check for buffer overflow.  Then, save the character.
   ****************************************************/
      if ( npacketchar + 1 > MAXGPSPACKETSIZE ) return -2;
      packet[npacketchar++] = chr;
   }
   packet[npacketchar++] = chr;   // Save the final ETX character
   return npacketchar;            // Return number of bytes read
}


/***************************************************************
  DecodeGpsPacket

  Extract packet id and data string bytes from the GPS packet.
  dsb  = Pointer to data string bytes
  ndsb = Number of data string bytes
 ***************************************************************/

void DecodeGpsPacket( UINT8 packet[], int packetsize, UINT8 *packet_id, UINT8 dsb[], int *ndsb )
{
   *packet_id = packet[1];
   *ndsb = packetsize - 4;
   memcpy( dsb, packet+2, *ndsb );
   return;
}


/********************************************************************
  RemoveDleBytes

  Remove any extra (stuffed) DLE bytes from the packet data string.
 ********************************************************************/

void RemoveDleBytes( UINT8 dsb[], int *ndsb )
{
   int newNdsb = *ndsb;
   int i;

   for ( i = 1; i < newNdsb; i++ )
   {
      if ( (dsb[i-1] == DLE) && (dsb[i] == DLE) )
      {
         newNdsb--;
         memmove( &dsb[i], &dsb[i+1], (newNdsb - i) );
      }
   }
   *ndsb = newNdsb;
   return;
}


/************************************************
  ClosePort

  Close the serial port.
 ************************************************/

void ClosePort( void )
{
   if ( handle != 0 )
      CloseHandle( handle );
   return;
}


/************************************************
  WriteGpsPacket

  Write packet to GPS receiver via com port.

  Returns  0 if all ok,
          -1 if a write error occurred
 ************************************************/

int WriteGpsPacket( UINT8 packet[], int nchar )
{
   BOOL  success;
   DWORD numWritten;

   success = WriteFile( handle, packet, nchar, &numWritten, 0 );
   if ( !success )
   {
      logit( "et", "Error %d in WriteFile.\n", GetLastError() );
      return -1;
   }
   return 0;
}


/*********************************************************
  DisableAutoPackets

  Disable automatic output packets from the GPS.
  By default, the primary and supplemental timing
  packets are sent after every one-second pulse.
 *********************************************************/

int DisableAutoPackets( void )
{
   UINT8 gpsPacket[] = {DLE, 0x8E, 0xA5, 0, 0, 0, 0, DLE, ETX};
   int   packetsize;
   UINT8 inPacket[MAXGPSPACKETSIZE];

/* Purge com port input buffer
   ***************************/
   if ( PurgeComInput() < 0 )
   {
      logit( "et", "PurgeComInput error.\n" );
      return -1;
   }

/* Disable packet broadcasts
   *************************/
   if ( WriteGpsPacket( gpsPacket, 9 ) < 0 )
   {
      logit( "et", "WriteGpsPacket error disabling auto packets.\n" );
      return -1;
   }

/* Read the response packet
   ************************/
   packetsize = ReadGpsPacket( inPacket );
   if ( packetsize == -1 )
   {
      logit( "et", "ReadGpsPacket read error.\n" );
      return -1;
   }

   if ( packetsize == -2 )
   {
      logit( "et", "ReadGpsPacket buffer overflow.\n" );
      return -1;
   }

   if ( packetsize == -3 )     // Packets must begin with a DLE character
   {
      logit( "et", "Error.  First byte in packet is not DLE.\n" );
      return -1;
   }
   if ( packetsize != 9 )
   {
      logit( "et", "Error.  Disable-auto-packet response packet length: %d\n", packetsize );
      logit( "et", "Packet length should be 9.\n" );
      return -1;
   }
   return 0;
}


/*************************************************************
  SetUtcMode

  Put the GPS into "UTC mode", instead of "GPS mode".
  This will cause times to be reported in UTC, and the
  one-PPS pulse will be referenced to UTC.
  Save the timing settings to non-volatile memory.
 *************************************************************/

int SetUtcMode( void )
{
   UINT8 modePacket[] = {DLE, 0x8E, 0xA2, 0x03, DLE, ETX};
   UINT8 savePacket[] = {DLE, 0x8E, 0x4C, 0x06, DLE, ETX};

   if ( WriteGpsPacket( modePacket, 6 ) < 0 )
   {
      logit( "et", "WriteGpsPacket error setting UTC mode.\n" );
      return -1;
   }

   if ( WriteGpsPacket( savePacket, 6 ) < 0 )
   {
      logit( "et", "WriteGpsPacket error saving timing settings to NVM.\n" );
      return -1;
   }
   return 0;
}


/************************
  Query PPS output status
  ***********************/

int GetPpsOutputStatus( void )
{
   static UINT8 requestPacket[] = {DLE, 0x8E, 0x4E, DLE, ETX};
   int    packetsize;
   UINT8  inPacket[MAXGPSPACKETSIZE];
   UINT8  packet_id;                // GPS packet id
   UINT8  dsb[10];
   int    ndsb;
   const  int nptp = 2;            // Num bytes in PPS status packet

/* Purge com port input buffer
   ***************************/
   if ( PurgeComInput() < 0 )
   {
      logit( "et", "PurgeComInput error.\n" );
      return -1;
   }

/* Request PPS characteristics
   ***************************/
   if ( WriteGpsPacket( requestPacket, 5 ) < 0 )
   {
      logit( "et", "WriteGpsPacket error.\n" );
      return -1;
   }

/* Read the response packet
   ************************/
   packetsize = ReadGpsPacket( inPacket );
   if ( packetsize == -1 )
   {
      logit( "et", "ReadGpsPacket read error.\n" );
      return -1;
   }

   if ( packetsize == -2 )
   {
      logit( "et", "ReadGpsPacket buffer overflow.\n" );
      return -1;
   }

   if ( packetsize == -3 )     // Packets must begin with a DLE character
   {
      logit( "et", "Error.  First byte in packet is not DLE.\n" );
      return -1;
   }

   if ( packetsize < 4 )
   {
      logit( "et", "Error.  Packet is too short. Size: %d\n", packetsize );
      return -1;
   }

/* Extract packet id and data-string bytes from the incoming packet.
   dsb  = Data string bytes from GPS packet
   ndsb = Number of data string bytes
   ****************************************************************/
   DecodeGpsPacket( inPacket, packetsize, &packet_id, dsb, &ndsb );

   if ( packet_id != 0x8F )           // Wrong packet id
   {
      logit( "et", "Error. packet_id = %02X  (should be 0x8E)\n", packet_id );
      return -1;
   }

/* Remove any stuffed DLE bytes from the data string bytes.
   Then, check dsp bytes for sanity.
   *******************************************************/
   RemoveDleBytes( dsb, &ndsb );

   if ( ndsb != nptp )        // Wrong number of bytes in packet data string
   {
      int i;
      logit( "et", "Error. Number of data bytes in packet= %d (should be %d)\n", ndsb, nptp );
      for ( i = 0; i < ndsb; i++ )    // For debugging
         logit( "e", " %02X", dsb[i] );
      logit( "e", "\n" );
      return -1;
   }

   if ( dsb[0] != requestPacket[2] )  // Wrong packet subcode
   {
      logit( "et", "Error. Packet subcode for PTP = %02X  (should be %02X)\n",
             dsb[0], requestPacket[2] );
      return -1;
   }
   if ( dsb[1] & 0x02 )
      logit( "et", "The 1-PPS pulse is always on.\n" );
   else
      logit( "et", "1-PPS status byte: 0x%02x\n", dsb[1] );
   return 0;
}


/********************************************************************
  GetPrimaryTimingPacket

  Request that the primary timing packet be returned immediately.
  Returns 0 if successful.
 ********************************************************************/

int GetPrimaryTimingPacket( UINT8 dsb[] )
{
   static UINT8 requestPacket[] = {DLE, 0x8E, 0xAB, 0x00, DLE, ETX};
   int    packetsize;
   UINT8  inPacket[MAXGPSPACKETSIZE];
   UINT8  packet_id;                // GPS packet id
   int    ndsb;
   const  int nptp = 17;            // Num bytes in primary timing packet

/* Purge com port input buffer
   ***************************/
   if ( PurgeComInput() < 0 )
   {
      logit( "et", "PurgeComInput error.\n" );
      return -1;
   }

/* Request primary timing packet
   *****************************/
   if ( WriteGpsPacket( requestPacket, 6 ) < 0 )
   {
      logit( "et", "WriteGpsPacket error.\n" );
      return -1;
   }

/* Read the primary timing packet
   ******************************/
   packetsize = ReadGpsPacket( inPacket );
   if ( packetsize == -1 )
   {
      logit( "et", "ReadGpsPacket read error.\n" );
      return -1;
   }

   if ( packetsize == -2 )
   {
      logit( "et", "ReadGpsPacket buffer overflow.\n" );
      return -1;
   }

   if ( packetsize == -3 )     // Packets must begin with a DLE character
   {
      logit( "et", "Error.  First byte in packet is not DLE.\n" );
      return -1;
   }

   if ( packetsize < 4 )
   {
      logit( "et", "Error.  Packet is too short. Size: %d\n", packetsize );
      return -1;
   }

/* Extract packet id and data-string bytes from the incoming packet.
   dsb  = Data string bytes from GPS packet
   ndsb = Number of data string bytes
   ****************************************************************/
   DecodeGpsPacket( inPacket, packetsize, &packet_id, dsb, &ndsb );

   if ( packet_id != 0x8F )           // Wrong packet id
   {
      logit( "et", "Error. packet_id = %02X  (should be 0x8F)\n", packet_id );
      return -1;
   }

/* Remove any stuffed DLE bytes from the data string bytes.
   Then, check dsp bytes for sanity.
   *******************************************************/
   RemoveDleBytes( dsb, &ndsb );

   if ( ndsb != nptp )        // Wrong number of bytes in packet data string
   {
      int i;
      logit( "et", "Error. Number of data bytes in packet= %d (should be %d)\n", ndsb, nptp );
      for ( i = 0; i < ndsb; i++ )    // For debugging
         logit( "e", " %02X", dsb[i] );
      logit( "e", "\n" );
      return -1;
   }

   if ( dsb[0] != requestPacket[2] )  // Wrong packet subcode
   {
      logit( "et", "Error. Packet subcode for PTP = %02X  (should be %02X)\n",
             dsb[0], requestPacket[2] );
      return -1;
   }
   return 0;
}


/**********************************************************************
  GetSupplementalTimingPacket

  Request that the supplemental timing packet be returned immediately.
  Returns 0 if successful.
 **********************************************************************/

int GetSupplementalTimingPacket( UINT8 dsb[] )
{
   static UINT8 requestPacket[] = {DLE, 0x8E, 0xAC, 0x00, DLE, ETX};
   int    packetsize;
   UINT8  inPacket[MAXGPSPACKETSIZE];
   UINT8  packet_id;                // GPS packet id
   int    ndsb;
   const  int nstp = 68;            // Num bytes in supplemental timing packet

/* Purge com port input buffer
   ***************************/
   if ( PurgeComInput() < 0 )
   {
      logit( "et", "PurgeComInput error.\n" );
      return -1;
   }

/* Request supplemental timing packet
   **********************************/
   if ( WriteGpsPacket( requestPacket, 6 ) < 0 )
   {
      logit( "et", "WriteGpsPacket error.\n" );
      return -1;
   }

/* Read the supplemental timing packet
   ***********************************/
   packetsize = ReadGpsPacket( inPacket );
   if ( packetsize == -1 )
   {
      logit( "et", "ReadGpsPacket read error.\n" );
      return -1;
   }

   if ( packetsize == -2 )
   {
      logit( "et", "ReadGpsPacket buffer overflow.\n" );
      return -1;
   }

   if ( packetsize == -3 )     // Packets must begin with a DLE character
   {
      logit( "et", "Error.  First byte in packet is not DLE.\n" );
      return -1;
   }

   if ( packetsize < 4 )
   {
      logit( "et", "Error.  Packet is too short. Size: %d\n", packetsize );
      return -1;
   }

// for ( i = 0; i < packetsize; i++ )       // For debugging
//    logit( "", " %02X", dsb[i] );
// logit( "", "\n" );

/* Extract packet id and data-string bytes from the incoming packet.
   dsb  = Data string bytes from GPS packet
   ndsb = Number of data string bytes
   ****************************************************************/
   DecodeGpsPacket( inPacket, packetsize, &packet_id, dsb, &ndsb );

   if ( packet_id != 0x8F )           // Wrong packet id
   {
      logit( "et", "Error. packet_id = %02X  (should be 0x8F)\n", packet_id );
      return -1;
   }

/* Remove any stuffed DLE bytes from the data string bytes.
   Then, check dsp bytes for sanity.
   *******************************************************/
   RemoveDleBytes( dsb, &ndsb );

   if ( ndsb != nstp )        // Wrong number of bytes in packet data string
   {
      logit( "et", "Error. Number of data bytes in packet= %d (should be %d)\n", ndsb, nstp );
      return -1;
   }

   if ( dsb[0] != requestPacket[2] )  // Wrong packet subcode
   {
      logit( "et", "Error. Packet subcode for STP = %02X  (should be %02X)\n",
             dsb[0], requestPacket[2] );
      return -1;
   }
   return 0;
}
