/* FILE: srparxchewsend.c           Copyright (c), Symmetric Research, 2004-2006
 *
 * SrParxchEwSend is a program for sending data from a SR PARxCH 24 bit
 * system to an Earthworm WAVE_RING buffer as TYPE_TRACEBUF messages.  It
 * works with all three PARxCH systems, one, four, and eight channels.
 * It is similar in concept and organization to the ADSEND module except it
 * works for the SR PARxCH DAQs.
 *
 *
 * What you need to run:
 *    The Earthworm system installed
 *    A Symmetric Research PARxCH A/D board (PARGPS recommended too)
 *    The srparxchewsend executable (.exe), config (.d) and error (.desc) files
 *    You must change the values in srparxchewsend.d so they match your A/D
 *    You will probably also want to change some values in other .d files
 *       like wave_serverV.d or export.d to save or process the acquired data
 *    
 * 
 * What you need to compile:
 *   The Earthworm include and library files
 *   The srparxchewsend source
 *   The PARxCH software source (you must define GPS_AVAILABLE at compile time)
 *   The PARGPS software source (required even if not using the PARGPS hardware)
 *   Note: The PARxCH and PARGPS source is available free
 *         from the symres.com website
 *
 *
 * How this program is organized:
 *
 * The main routine calls several subroutines to initialize Earthworm,
 * the PARxCH and PARGPS, and some software buffers.  Then it starts the
 * PARxCH and PARGPS and goes into an endless loop reading data and
 * sending out heartbeat and tracebuf messages.  The loop can be
 * terminated by setting the Earthworm terminate flag.
 *
 * SrParxchEwSend tries to acquire roughly 1 second of data at a time.
 * Once it has done so, the heartbeat and tracebuf messages are sent.
 * This means heartbeats will never be sent any faster than 1 per second
 * and sampling rates beyond 1000 samples/second are forbidden since
 * they would exceed the size allocated for tracebuf messages.  While a
 * sampling rate of 1000 SPS has been achieved for a short time on our
 * test machines, there are no guarantees that your Earthworm
 * configuration can support rates this fast over the long term.
 *
 * The Earthworm initialization involves setting up the log file,
 * reading the .d configuration file, looking up Earthworm info from the
 * earthworm.h file, and attaching to a shared memory region or "ring"
 * which allows various Earthworm modules, each of which is a separate
 * program, to communicate with each other.
 *
 * The PARxCH initialization involves opening the PARxCH and PARGPS
 * drivers, initializing the PARxCH and PARGPS hardware, and setting
 * default values for a variety of acquisition parameters.  Routines
 * with sr_atod and sr_ts in their name are the only ones which call PARxCH 
 * and PARGPS hardware specific functions supplied by the PARxCH and PARGPS
 * libraries that come with the hardware and are available for free
 * download from the Symmetric Research website, symres.com.  Use of the
 * PARGPS for timestamping the data is optional but highly recommended.  
 * If it is not used, the time assigned to the acquired data will be
 * determined by adding an elapsed time, computed assuming the nominal
 * sampling rate is totally accurate, to the PC time when acquisition
 * started.
 *
 * Three large arrays are allocated during the initialization phase:
 * AtodBuffer1,2 and TraceBuffer.  AtodBufferFill points to the buffer
 * which will be filled with acquired data read from the PARxCH, until
 * it is copied into TraceBuffer which holds the data in Earthworm
 * tracebuf message format until it is written out to the WAVE_RING.
 * Because two data buffers will be used, AtodBufferWrite points to the
 * other buffer, which was filled earlier, and is the one used to copy
 * data into TraceBuffer.
 *
 * The routine sr_atod_getdata takes care of reading in the acquired
 * data and, if the PARGPS is being used, the PPS and serial NMEA timing
 * information.  A loop is used for reading in the requested amount of
 * acquired data.  After each pass through the loop, if more data is
 * still needed to fill the request, the program sleeps awhile to let
 * other programs run while it gives the remaining data a chance to
 * arrive.  The sleep time has been set so 5 or 6 passes through the
 * loop should be needed to satisfy the request.  If the data request is
 * satisfied on the very first pass, it means data has accumulated in
 * the FIFO on the PARxCH board faster than it is being read, presumably
 * because SrParxchEwSend is not getting enough CPU cycles to keep up.
 * While this can be tolerated for a little while, the FIFO will
 * eventually overflow if this situation persists.
 *
 * If the PARGPS is being used to time stamp the data, sr_atod_getdata
 * also reads in three different types of GPS information which it
 * stores in a temporary timestamp area.  These three types of GPS
 * information are 1) mark data, 2) PPS data, and 3) serial NMEA data.
 * Once all three types are available for a given second, the info is
 * save as a timestamp.
 *
 * We maintain two sets of timestamps.  One set, TsReal, contains the
 * last four raw timestamps.  The information in these timestamps is
 * exactly what has been extracted from the data.  The other set,
 * TimeStamp, contains the last three final timestamps.  These timestamps
 * are used to compute the time for each tracebuf.  They may have been
 * modified from the raw values in order to correct for an outlier.
 *
 * Mark data refers to the index or sample number of the mark point, the
 * first sample acquired after the start of a second as indicated by the
 * arrival of the high precision PPS pulse per second signal.  PPS data
 * refers to the values the high speed PC counter has at the time of PPS
 * and again at the time of Dready (data ready) for the Mark point.
 * Serial NMEA data refers to the coarse GPS timing and location
 * information containing the time and date for the PPS second.  These
 * three types of information are stored at slightly different times and
 * in different structures within the PARxCH and PARGPS drivers.  So
 * each PPS signal is given an index, known as the PPS event number or
 * the mark number, which is used to tie the three types of GPS
 * information together.
 *
 * Because each buffer of acquired data is roughly 1 second long, we 
 * should see one PPS signal per buffer.  However, the buffers are not
 * exactly 1 second long, but are slightly longer.  This means we should
 * periodically get a buffer that includes two PPS signals.  This "extra"
 * PPS is correct and expected.  It is logged at Debug level 2.
 * 
 *
 * Note: Global variables are capitalized, locals have first letter lower case.
 *       Tab stops are set at 3.
 */

/* changes:

  Revision 1.2  2006/02/25  W.Tucker:
     delayed tracebuf output by 1 sec to ensure endtimes are calculated
         with valid timestamps
     updated to support the new TYPE_TRACEBUF2 messages and SCNL location codes
     improved messages for various Debug output levels
     
  Revision 1.1  2004/12/30  W.Tucker:
     improved non-GPS timing to use fractional seconds
     added GPS lock status logging
     
  Revision 1.0  2004/11/10  W.Tucker:
     first version
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <earthworm.h>   /* inst, mod + ring globals, logit utilities, etc */
#include <kom.h>         /* configuration parsing  k_xxx                   */
#include <time_ew.h>     /* OS independent time functions                  */
#include <transport.h>   /* ring transport utilities  tport_xxx            */
#include <trace_buf.h>   /* tracebuf waveform structure                    */

#include "parxch.h"      /* PARxCH DAQ defines and library routines        */
#include "pargps.h"      /* PARGPS timing defines and library routines     */


/* This code is designed to support both v7.0 which knows about the new
 * TYPE_TRACEBUF2 and v6.3 which doesn't.  If the new TRACE2 defines exist
 * all is OK, otherwise, we just dummy them out to match the old versions.
 **************************************************************************/
#if defined( TRACE2_VERSION0 )
#define TRACE2_OK        1
#else
#define TRACE2_OK        0
#define TRACE2_CHAN_LEN  4
#define TRACE2_LOC_LEN	 3
#define TRACE2_VERSION0  '2'   /* version[0] for TYPE_TRACEBUF2       */
#define TRACE2_VERSION1  '0'   /* version[1] for TYPE_TRACEBUF2       */
#define LOC_NULL_STRING  "--"  /* NULL string for location code field */
#endif

#define TRACE_POS_LOC    (TRACE2_CHAN_LEN)
#define TRACE_POS_VER    (TRACE_POS_LOC+TRACE2_LOC_LEN)


/* Prototypes of functions in this file 
 **************************************/
int   main( int, char ** );
void  sr_ew_init( char *, char * );
void  sr_atod_init( void );
void  sr_atod_open( void );
int   sr_atod_set_portmode( char * );
int   sr_atod_set_xchmodel( char * );
int   sr_atod_set_gpsmodel( char * );
void  sr_atod_correct_sps( double * );
void  sr_ts_init( void );
void  sr_filter_init( void );
void  sr_allocate_arrays( void );
void  sr_atod_start( void );
int   sr_atod_getdata( void );
void  sr_send_traces( void );
unsigned int sr_ts_process_analog( void );
unsigned int sr_ts_process_pps( void );
unsigned int sr_ts_process_serial( void );
int   sr_ts_update( void );
void  sr_filter( int, double );
void  sr_ts_set_tracebuf_time( void );
int   sr_ts_select( long, int, int *, int * );
void  sr_atod_stop( void );
void  sr_atod_close( void );
void  sr_atod_overflow_recovery( void );
void  sr_check_terminate( void );
void  sr_terminate( void );
void  sr_read_config( char * );
void  sr_lookup_ew( void );
void  sr_heartbeat( void );
void  sr_atod_toggle_led( void );
void  sr_error( int, char * );
int   sr_strncpy( char *, char *, int );
void  sr_log_gap_details( int, double, double );
void  sr_log_trace_head( void );



/* Defines and typedefs
 **********************/
#define REV_STRING  "SrParxchEwSend Rev 02/25/06\n"
#define MAX_STR      256      /* Maximum dimension for some strings      */
#define MAX_CHAN      11      /* Maximum number of channels allowed      */
#define MAX_TS         5      /* Maximum number of good timestamps       */
#define MAX_TSR        4      /* Maximum number of raw timestamps        */
#define MAX_GPS        2      /* Max num GPS records read at once        */
#define QUALITY_OK   0x0      /* See trace_buf.h for other codes         */
#define BEHIND_LIMIT  20      /* Num times acquisition falls behind
                                 before printing a warning               */
#define CBP_RANGE  0.00005L   /* cnt betw PPS must be w/in 50 ppm        */
#define CBD_RANGE  0.00005L   /* cnt betw Drdy must be w/in 50 ppm       */

#define FILTER_PPS     1      /* filter best count between PPS           */
#define FILTER_DREADY  2      /* filter best count between Dready        */

#define EW_ERROR_BASE_PARXCH  100 /* Convert PARXCH_ERROR_XXX to EW ierr */
#define EW_ERROR_BASE_PARGPS  200 /* Convert PARGPS_ERROR_XXX to EW ierr */

#define SLOPMS         0.01L  /* Windows system time only good to 10ms   */

#define MAXGAPTRAIL    2      /* Dbg: Num buffers to log after a gap     */
#define MAX_PREV_SER   6      /* Dbg: Num prev serial to show after gap  */

#define MARKCHANNEL_NONE -1   /* No GPS, must = SRDAT_INDEXCHANNEL_NONE  */

typedef struct _SCNL          /* Channel information structure           */
{
   char sta[ TRACE_STA_LEN];
   char comp[TRACE_CHAN_LEN]; /* Older value has larger dimension        */
   char net[ TRACE_NET_LEN];
   char loc[ TRACE2_LOC_LEN];
   char ver[2];
   int  pin;
}
SCNL;

/* Global variables
 ******************/

/* Things to read or derive from configuration file
 **************************************************/
static char   MyModName[MAX_MOD_STR];   /* speak as this module name/id      */
static char   RingName[MAX_RING_STR];   /* name of transport ring for i/o    */
static int    LogSwitch;                /* 0 if no logfile should be written */
static long   HeartBeatInterval;        /* seconds between heartbeats        */

static char   AtodDriverName[MAX_STR];  /* PARxCH driver name                */
static char   AtodXchModelName[MAX_STR];/* PARxCH model name                 */
static char   AtodPortModeName[MAX_STR];/* PARxCH port mode name             */
static double AtodRequestedSps;         /* requested sampling rate           */
static int    UsingGps;                 /* Is PARGPS being used              */
static char   GpsDriverName[MAX_STR];   /* PARGPS driver name                */
static char   GpsModelName[MAX_STR];    /* GPS model name (eg PARGPS)        */
static int    GpsSerialPort;            /* PC serial COM port for GPS        */
static SCNL   ChanList[MAX_CHAN];       /* Channel info:site,comp,net,pinno  */
static int    OutputMsgType;            /* TYPE _TRACEBUF or _TRACEBUF2      */



/* Things to look up in the earthworm.d tables
 *********************************************/
static long          RingKey;           /* key of transport ring for i/o     */
static unsigned char InstId;            /* local installation id             */
static unsigned char MyModId;           /* module id for this program        */
static unsigned char TypeHeartBeat;     /* msg type id for heartbeats        */
static unsigned char TypeError;         /* msg type id for errors            */
static unsigned char TypeTraceBuf;      /* msg type id for old tracebuf      */
static unsigned char TypeTraceBuf2;     /* msg type id for new tracebuf2     */



/* Things computed or set in the code
 ************************************/
static SHM_INFO       Region;          /* shared memory region to use for i/o    */
static MSG_LOGO       Logo;            /* msg header with module,type,instid     */
static pid_t          MyPid;           /* for restarts by startstop              */
static long           TimeNow;         /* current time in sec since 1970         */       
static long           TimeLastBeat;    /* time last heartbeat was sent           */

static long           TraceBufSize;    /* size in bytes of tracebuf message      */
static char          *TraceBuffer;     /* tracebuf message buffer                */
static long          *TraceDat;        /* where in TraceBuffer the data starts   */
static TRACE_HEADER  *TraceHead;       /* where in TraceBuffer old header starts */

static long          AtodNumChannels;   /* number of DAQ channels being acquired  */
static long          AtodNumSamples;    /* number of data samples per tracebuf    */
static long          AtodNumSps;        /* number of data samples per sec rounded */
static double        AtodSps;           /* actual sampling rate                   */
static int           AtodLedValue;      /* state of PARxCH yellow LED             */
static long          AtodBufSize;       /* size in bytes of acquired data buffer  */
static long         *AtodBuffer1;       /* pointer to first acquired data buffer  */
static long         *AtodBuffer2;       /* pointer to next acquired data buffer   */
static long         *AtodBufferFill;    /* pointer to data buffer to fill         */
static long         *AtodBufferWrite;   /* pointer to data buffer to write out    */
static int           AtodXchModel;      /* which PARxCH: 1CH, 4CH, or 8CH         */
static int           AtodPortMode;      /* PC port mode: EPP, BPP, etc            */
static double        AtodStartTime;     /* start time of next tracebuf            */
static double        AtodEndTimeLast;   /* end time of last tracebuf              */
static int           AtodDataRequest;   /* what data to read, eg analog, gps, etc */
static int           AtodCntrChan;      /* index of PAR8CH GPS OnBoard Count chan */
static int           AtodMarkChan;      /* index of PARxCH GPS mark chan          */
static long          AtodCurrentPtFill; /* index of current fill pt since start  */
static long          AtodCurrentPtWrite;/* index of current write pt since start */
static unsigned long AtodBufferNum;     /* index numer of current buffer          */
static int           GpsModel;          /* which Gps model (eg PARGPS)            */
   
static DEVHANDLE ParXchHandle;          /* handle to PARxCH device driver         */
static DEVHANDLE ParGpsHandle;          /* handle to PARGPS device driver         */

static TIMESTAMP TimeStamp[MAX_TS];     /* Good or corrected time stamps          */
static TIMESTAMP TsReal[MAX_TSR];       /* Time stamps from raw data, no mods     */
static TIMESTAMP TsFix;                 /* Time stamp corrected for outlier       */

static PPSTDATA   GpsPpsBuffer[MAX_GPS];    /* GPS PPS data                        */
static SERIALDATA GpsSerialBuffer[MAX_GPS]; /* GPS serial NMEA data                */
static SERIALDATA GpsPrevSerial[MAX_PREV_SER]; /* Dbg: Prev GPS serial to save     */

static long       AtodNumPps;               /* Number GPS PPS records read         */
static long       AtodNumSerial;            /* Number GPS serial NMEA records read */
static double     ObcCountPerSample;        /* PAR8CH OBC counts per data point    */
static double     BestObcCountsPerSample;   /* PAR8CH OBC counts per data point    */
static int        FirstTimeFilter;          /* 1 if BestCountBetweenXxx need init  */
static int        FirstTimeSerial;          /* 1 if PrevSecSince1970 needs init    */
static int        FirstTimePctime;          /* 1 if starting to use time from PC   */
static int        SleepMs;                  /* Ms to wait for more data to arrive  */
static int        PcCounter1Type;           /* PC counter depends on OS            */
static int        PcCounter2Type;           /* PC counter depends on OS            */
static int        UseTimeMethod;            /* how to compute trace start time     */
static int        LastTimeMethod;           /* how last trace start time computed  */
static int        TsValid;                  /* TS_VALID_ALL unless time from PC    */

static double     BestCountsBetweenPps;     /* 64 bit PC counts between 2 PPS      */
static double     BestCountsBetweenDready;  /* 64 bit PC counts between 2 Dready   */
static double     BestCountsPerSecond;      /* 64 bit PC counts between 2 seconds  */
static double     CbpLimit;                 /* ppm range around PC counter freq    */
static double     CbdLimit;                 /* ppm range around PC counts/sample   */
static double     CbpCoeff;                 /* count betw. PPS filter coefficient  */
static double     CbpScale;                 /* count betw. PPS filter scaling      */
static double     CbdCoeff;                 /* count betw. Dready filter coeff.    */
static double     CbdScale;                 /* count betw. Dready filter scaling   */

static long       GpsLockReportInterval;    /* sec in log betw. gps lock reports   */
static long       GpsLockBadLimit;          /* num bad locks/interval for error    */
static long       GpsLockCount;             /* num sec so far in gps lock summary  */
static long       GpsLockGood;              /* num good locks in current count     */
static long       GpsLockBad;               /* num bad  locks in current count     */
static long       GpsLockStatus;            /* 1 if last gps lock report was good  */

static long       SummaryReportInterval;    /* sec in log between summary reports  */
static long       SummaryCount;             /* num sec so far in general summary   */
static long       SummaryMin[MAX_CHAN];     /* min data value per channel          */
static long       SummaryMax[MAX_CHAN];     /* max data value per channel          */

static int Debug = 0;                       /* 0,1,2,3 = few, some, many... logits */


double DbgLastStartDiff, DbgLastStart;
double DbgLastEndDiff, DbgLastEnd;

        
/* Shared string for temp use
 ****************************/
static char Msg[MAX_STR];                   /* string for log messages       */
static char ErrMsg[MAX_STR];                /* string for error messages     */






/******************************************************************************
 *  Function: srparxchewsend main                                             *
 *  Purpose:  SrParxchEwSend is an earthworm module for acquiring waveform    *
 *            data from one of the Symmetric Research 24 bit PARxCH A/D       *
 *            systems.  After several initialization routines are called,     *
 *            this program goes into a loop acquiring data and sending it out *
 *            to an earthworm shared memory region (ring).  A heartbeat       *
 *            message is also sent on each pass through the loop.  The loop   *
 *            continues until an earthworm terminate request is received, a   *
 *            Ctrl+C is pressed, or a fatal error occurs.                     *
 ******************************************************************************/
int main( int argc, char **argv )
{
  /* Show program banner
   *********************/
//   printf( REV_STRING );


  /* Check command line arguments 
   ******************************/
   if ( argc != 2 )
   {
        fprintf( stderr, "Usage: srparxchewsend <configfile>\n" );
        exit( 0 );
   }


  /* Earthworm initialization
   **************************/
   sr_ew_init( argv[0], argv[1] );



  /* PARxCH DAQ initialization
   ***************************/
   sr_atod_init();


  /* Allocate acquired data (input) + tracebuf (output) arrays
   ***********************************************************/
   sr_allocate_arrays();
   

  /* Begin acquiring
   *****************/
   sr_atod_start();

   
  /* Main acquisition loop
   ***********************/
   sr_heartbeat();
   sr_atod_getdata();                /* Get a starting buffer of data */
   sr_heartbeat();

   while ( 1 )
   {
       sr_check_terminate();         /* Exit if termination requested */
       
       if ( sr_atod_getdata() > 0 )  /* Read new acquired data        */
       {
           sr_heartbeat();           /* Send heartbeat message        */
           sr_send_traces();         /* Send tracebuf messages        */
       }

   } /* end while */

   exit( 0 );
}

/******************************************************************************
 *  Function: sr_ew_init                                                      *
 *  Purpose:  Perform the standard initialization tasks for a well behaved    *
 *            earthworm module.  These include starting a log file, reading   *
 *            the configuration file, looking up any global earthworm data,   *
 *            getting the process id, and attaching to an earthworm shared    *
 *            memory region (ring).                                           *
 ******************************************************************************/
void sr_ew_init( char *argv0, char *argv1 )
{
   if ( Debug >= 6 )  logit( "" , "Starting sr_ew_init\n" );


  /* Init some values to ensure safe exit on error
   ***********************************************/
    ParXchHandle = BAD_DEVHANDLE;
    ParGpsHandle = BAD_DEVHANDLE;
    Region.addr  = NULL;

   
  /* Initialize and open log-file
   ******************************/
   logit_init( argv1, 0, 256, 1 );
   if ( Debug >= 0 )  logit( "", REV_STRING );

   
  /* Read the configuration file
   *****************************/
   sr_read_config( argv1 );
   if ( Debug >= 0 )  logit( "" , "%s: Read command file <%s>\n", argv0, argv1 );
   

  /* Look up important info from earthworm.d tables
   ************************************************/
   sr_lookup_ew();

   Logo.instid = InstId;
   Logo.mod    = MyModId;


  /* Reinitialize logit to desired logging level 
   *********************************************/
   logit_init( argv1, 0, 256, LogSwitch );


  /* Get process ID for heartbeat messages
   ***************************************/
   MyPid = getpid();
   if ( MyPid == -1 )
   {
     logit( "e", "srparxchewsend: Cannot get pid. Exiting.\n" );
     exit( -1 );
   }


  /* Attach to shared memory ring 
   ******************************/
   tport_attach( &Region, RingKey );
   if ( Debug >= 0 )
      logit( "", "Attached to public memory region %s: %d\n",
             RingName, RingKey );


  /* Force a heartbeat to be issued on the first call
   **************************************************/
   TimeLastBeat = time( &TimeNow ) - HeartBeatInterval - 1;

   if ( Debug >= 6 )  logit( "" , "Leaving  sr_ew_init\n" );
   return;
}


/******************************************************************************
 *  Function: sr_atod_init                                                    *
 *  Purpose:  Initialize the PARxCH DAQ and optional PARGPS timing modules    *
 *            and the parameters related to them.  First open the device      *
 *            drivers and initialize the hardware.  Next set various DAQ      *
 *            parameters.  Some are common to all PARxCH, while others        *
 *            depend on which XchModel is being used.  Finally, initialize    *
 *            the GPS timestamp structures which are used in determining the  *
 *            start time for each tracebuf.                                   *
 ******************************************************************************/
void sr_atod_init( void )
{
    int  i;
    long pcCounter1FreqHi, pcCounter1FreqLo, pcCounter2FreqHi, pcCounter2FreqLo;

    if ( Debug >= 6 )  logit( "" , "Starting sr_atod_init\n" );


   /* Set some ints from their respective config strings
    ****************************************************/
    AtodPortMode = sr_atod_set_portmode( AtodPortModeName );
    AtodXchModel = sr_atod_set_xchmodel( AtodXchModelName );
    GpsModel     = sr_atod_set_gpsmodel( GpsModelName );

   /* Correct requested Sps
    ***********************/
    sr_atod_correct_sps( &AtodRequestedSps );

   
   /* Open and initialize the PARxCH and PARGPS
    *******************************************/
    sr_atod_open();


   /* Set some default DAQ values
    *****************************/
    AtodLedValue       = 0;
    AtodCurrentPtFill  = 0L;
    AtodCurrentPtWrite = 0L;
    AtodBufferNum      = 0L;
    AtodNumPps         = 0L;
    AtodNumSerial      = 0L;

    // WCT: Right now this limits us to about 1000 samples per second
    AtodNumSamples    = (long)AtodSps + 1;      /* want 1 sec of data per tracebuf */
    AtodNumSps        = (long)(AtodSps + 0.5);  /* rounded number of samples per sec */

    AtodDataRequest   = PARXCH_REQUEST_ANALOG | PARXCH_REQUEST_DIGITAL;
    AtodMarkChan      = MARKCHANNEL_NONE;
    AtodCntrChan      = MARKCHANNEL_NONE;
    UseTimeMethod     = TIME_METHOD_SPS;
    LastTimeMethod    = TIME_METHOD_NONE;
    ObcCountPerSample = OBC_FREQ / AtodSps;
    SleepMs           = 1000 / 5;  /* If we're keeping up with the data it */
                                   /* will take 5 tries to get 1 sec worth */

    BestObcCountsPerSample = ObcCountPerSample;


   /* Set DAQ values that depend on XchModel and GpsModel
    *****************************************************/
    if ( AtodXchModel == PARXCH_MODEL_PAR1CH )
    {
        AtodNumChannels = PAR1CH_ANALOG_CHANNELS +
                          PAR1CH_DIGITAL_CHANNELS;
        if ( UsingGps )
        {
            AtodNumChannels += PAR1CH_GPS_CHANNELS;
            AtodDataRequest |= PARXCH_REQUEST_GPS;
            AtodMarkChan     = AtodNumChannels-1;
            AtodCntrChan     = MARKCHANNEL_NONE;
            UseTimeMethod    = TIME_METHOD_GPS;
            if (GpsModel == GPSMODEL_PCTIME)
                    UseTimeMethod = TIME_METHOD_PCT;
        }
    }
    else if ( AtodXchModel == PARXCH_MODEL_PAR4CH )
    {
        AtodNumChannels = PAR4CH_ANALOG_CHANNELS +
                          PAR4CH_DIGITAL_CHANNELS;
        if ( UsingGps )
        {
            AtodNumChannels += PAR4CH_GPS_CHANNELS;
            AtodDataRequest |= PARXCH_REQUEST_GPS;
            AtodMarkChan     = AtodNumChannels-1;
            AtodCntrChan     = MARKCHANNEL_NONE;
            UseTimeMethod    = TIME_METHOD_GPS;
            if (GpsModel == GPSMODEL_PCTIME)
                    UseTimeMethod = TIME_METHOD_PCT;
        }
    }
    else if ( AtodXchModel == PARXCH_MODEL_PAR8CH )
    {
        AtodNumChannels = PAR8CH_ANALOG_CHANNELS +
                          PAR8CH_DIGITAL_CHANNELS;
        if ( UsingGps )
        {
            AtodNumChannels += PAR8CH_GPS_CHANNELS;
            AtodDataRequest |= PARXCH_REQUEST_GPS;
            AtodMarkChan     = AtodNumChannels-1;
            AtodNumChannels += PAR8CH_COUNTER_CHANNELS;
            AtodDataRequest |= PARXCH_REQUEST_COUNTER;
            AtodCntrChan     = AtodNumChannels-1;
            UseTimeMethod    = TIME_METHOD_OBC;
            if (GpsModel == GPSMODEL_PCTIME)
                    UseTimeMethod = TIME_METHOD_PCT;
        }
    }
    else
    {
        logit( "e", "srparxchewsend: Unknown PARxCH requested.  Exiting.\n" );
        sr_terminate();
    }


    if (UseTimeMethod == TIME_METHOD_PCT)
            TsValid = TS_VALID_MOST;    /* Time from pc is sufficient     */
    else
            TsValid = TS_VALID_ALL;     /* Serial input required for time */
    

   /* Announce acquisition channels to log file
    *******************************************/
    if ( Debug >= 0 )
    {
       logit( "", "Acquiring from the following %d channels\n", AtodNumChannels );
       logit( "", "               Sta Comp Net Loc Pin\n", AtodNumChannels );

       for ( i = 0 ; i < AtodNumChannels ; i++ )
          logit( "", "Channel %2d = %6s %3s %3s %2s %4d\n",
                 i, ChanList[i].sta, ChanList[i].comp, 
                 ChanList[i].net, ChanList[i].loc, ChanList[i].pin );
    }



   /* Initialize general logging parameters
    ***************************************/
    SummaryCount = 0;
    for ( i = 0 ; i < AtodNumChannels ; i++ )
    {
       SummaryMin[i] = 0x00FFFFFFL;
       SummaryMax[i] = 0x00000000L;
    }



    PcCounter1Type   = DEFAULT_PC_COUNTER_TYPE;
    pcCounter1FreqLo = 1;
    PcCounter2Type   = DEFAULT_PC_COUNTER2_TYPE;
    pcCounter2FreqLo = 1;
    
    if ( UsingGps )
    {
        ParGpsGetFullCounterFrequency( ParGpsHandle,
                                       &pcCounter1FreqHi, &pcCounter1FreqLo,
                                       &pcCounter2FreqHi, &pcCounter2FreqLo
                                     );
        if (pcCounter1FreqLo < 0.0001)  /* prevent future divide by 0 */
            pcCounter1FreqLo = 1;
        if (pcCounter2FreqLo < 0.0001)  /* prevent future divide by 0 */
            pcCounter2FreqLo = 1;
    }



   /* PC counter runs up to 100 ppm faster or slower than the official 
    * rate, so consider anything beyond the ppm range to be an outlier.
    * We want to track the number of 64 bit PC counts between one PPS
    * and the next and the counts between one Dready mark and the next.
    * This allows us to know if any other program has excessively delayed 
    * the servicing of PC interrupts in that time which would create
    * outliers in our data that we'd like to exclude.
    ******************************************************************/
    BestCountsBetweenPps    = pcCounter1FreqLo;
    CbpLimit                = BestCountsBetweenPps * 0.002L;     /* 2000 ppm */

    BestCountsBetweenDready = ((double)pcCounter1FreqLo * AtodNumSps) / AtodSps;
    CbdLimit                = BestCountsBetweenDready * 0.002L;  /* 2000 ppm */

    BestCountsPerSecond     = pcCounter2FreqLo; /* For world time counter */
    
    if ( Debug >= 3 )
        logit( "", "Starting BestCountsBetween Pps %lf, Dready %lf, Sec %lf (Nsps %ld, Sps %lf)\n",
               BestCountsBetweenPps, BestCountsBetweenDready, BestCountsPerSecond,
               AtodNumSps, AtodSps );


   /* Initialize GPS logging parameters
    ***********************************/
    GpsLockCount = GpsLockGood = GpsLockBad = 0;
    GpsLockStatus = -1;


   /* Initialize GPS timestamp structures
    *************************************/
    sr_ts_init();


   /* Initialize averaging filters
    ******************************/
    sr_filter_init( );


    if ( Debug >= 6 )  logit( "" , "Leaving  sr_atod_init\n" );
   
    return;
}

/******************************************************************************
 *  Function: sr_atod_open                                                    *
 *  Purpose:  Open the PARxCH DAQ and optional PARGPS timing module device    *
 *            drivers.  Once both drivers are opened, they must be 'attached' *
 *            so they can communication with each other.  The hardware is     *
 *            initialized when the drivers are opened.   This routine exits   *
 *            if any of these steps fail.                                     *
 ******************************************************************************/
void sr_atod_open( void )
{
    int parxchError, pargpsError;

    if ( Debug >= 6 )  logit( "" , "Starting sr_atod_open\n" );

   /* Start with invalid driver handles 
    ***********************************/
    ParXchHandle = BAD_DEVHANDLE;
    ParGpsHandle = BAD_DEVHANDLE;

    
   /* Open the PARxCH driver and initialize the PARxCH DAQ board
    ************************************************************/
    if ( Debug >= 5 )
    {
        logit( "", "Calling ParXchOpen with:\n" );
        logit( "", "     Driver   %s\n",  AtodDriverName );
        logit( "", "     XchModel %d\n",  AtodXchModel );
        logit( "", "     PortMode %d\n",  AtodPortMode );
        logit( "", "     Sps      %lf\n", AtodRequestedSps );
    }
    ParXchHandle = ParXchOpen( AtodDriverName, AtodXchModel,
                               AtodPortMode, AtodRequestedSps,
                               &AtodSps, &parxchError );

    if ( Debug >= 5 )  logit( "", "Result is Sps %lf, error %d = %s\n",
                            AtodSps, parxchError, PARXCH_ERROR_MSG[parxchError] );
   
    if ( ParXchHandle == BAD_DEVHANDLE )
    {
        sprintf( Msg, "Failed to open PARxCH driver %s (Err=%s)",
                 AtodDriverName, PARXCH_ERROR_MSG[parxchError] );
        sr_error( EW_ERROR_BASE_PARXCH+parxchError, Msg );
        sr_terminate( );
    }
    if ( Debug >= 0 )
       logit( "", "Opened %s driver %s port mode %s with SPS = %lf\n",
              AtodXchModelName, AtodDriverName, AtodPortModeName, AtodSps );

   


   /* If using the PARGPS, then open and initialize it too
    ******************************************************/
    if ( UsingGps )
    {
        if ( Debug >= 5 )  logit( "", "Calling PARGPS open with Driver %s, serial COM%d\n",
                                 GpsDriverName, GpsSerialPort );
   
        ParGpsHandle = ParGpsFullOpen( GpsDriverName,
                                       GpsModel,
                                       GpsSerialPort,
                                       NULL,
                                       &pargpsError );
        if ( ParGpsHandle == BAD_DEVHANDLE )
        {
            sprintf( Msg, "Failed to open PARGPS driver %s (Err=%s)",
                     GpsDriverName, PARGPS_ERROR_MSG[pargpsError] );
            sr_error( EW_ERROR_BASE_PARGPS+pargpsError, Msg );
            if ( pargpsError == PARGPS_ERROR_SERIAL_PORT_NOT_OPEN )
                logit( "e", "                Is serial port COM%d already in use?",
                       GpsSerialPort );
            sr_terminate( );
        }
        if ( Debug >= 0 )
           logit( "", "Opened PARGPS driver %s with serial port = COM%d, GpsModel %s\n",
                  GpsDriverName, GpsSerialPort, GpsModelName );

        if ( Debug >= 5 )
           logit( "", "Successful PARGPS open (Xch = 0x%08X, Gps = 0x%08X)\n",
                  ParXchHandle, ParGpsHandle );
   


       /* Attach PARxCH and PARGPS drivers together
        *******************************************/
        if ( !ParXchAttachGps( ParXchHandle, ParGpsHandle, &parxchError ) )
        {
            sprintf( Msg, "%s attach to PARGPS failed (Err=%s)",
                     AtodXchModelName, PARXCH_ERROR_MSG[parxchError] );
            sr_error( EW_ERROR_BASE_PARXCH+parxchError, Msg );
            sr_terminate( );
        }

    } /* end if UsingGps */

 

    if ( Debug >= 6 )  logit( "" , "Leaving  sr_atod_open\n" );
    return;
}

/******************************************************************************
 *  Function: sr_atod_set_portmode                                            *
 *  Purpose:  Set the port mode variable according to the corresponding       *
 *            string read from the configuration file.                        *
 ******************************************************************************/
int sr_atod_set_portmode( char *portModeString )
{
    int portMode;

    if ( Debug >= 6 )  logit( "" , "Starting sr_atod_set_portmode\n" );


    if ( strcmp( portModeString, "EPP" ) == 0 )
        portMode = PARXCH_PORT_MODE_EPP;

    else if ( strcmp( portModeString, "BPP" ) == 0 )
        portMode = PARXCH_PORT_MODE_BPP;

    else if ( strcmp( portModeString, "ECP/EPP" ) == 0 )
        portMode = PARXCH_PORT_MODE_ECP_EPP;

    else /* if ( strcmp( portModeString, "ECP/BPP" ) == 0 ) */
        portMode = PARXCH_PORT_MODE_ECP_BPP;

    if ( Debug >= 6 )  logit( "" , "Leaving  sr_atod_set_portmode\n" );
    return( portMode );
}

/******************************************************************************
 *  Function: sr_atod_set_xchmodel                                            *
 *  Purpose:  Set the xch model variable according to the corresponding       *
 *            string read from the configuration file.                        *
 ******************************************************************************/
int sr_atod_set_xchmodel( char *xchModelString )
{
    int xchModel;

    if ( Debug >= 6 )  logit( "" , "Starting sr_atod_set_xchmodel\n" );


    if ( strcmp( xchModelString, "PAR1CH" ) == 0 )
        xchModel = PARXCH_MODEL_PAR1CH;
    
    else if ( strcmp( xchModelString, "PAR4CH" ) == 0 )
        xchModel = PARXCH_MODEL_PAR4CH;
    
    else /* if ( strcmp( xchModelString, "PAR8CH" ) == 0 ) */
        xchModel = PARXCH_MODEL_PAR8CH;


    if ( Debug >= 6 )  logit( "" , "Leaving  sr_atod_set_xchmodel\n" );
    return( xchModel );
}

/******************************************************************************
 *  Function: sr_atod_set_gpsmodel                                            *
 *  Purpose:  Set the GPS model variable according to the corresponding       *
 *            string read from the configuration file.                        *
 ******************************************************************************/
int sr_atod_set_gpsmodel( char *gpsModelString )
{
    int gpsModel;

    if ( Debug >= 6 )  logit( "" , "Starting sr_atod_set_gpsmodel\n" );


    if ( strcmp( gpsModelString, "PARGPS" ) == 0 )
        gpsModel = GPSMODEL_PARGPS;
    
    else if ( strcmp( gpsModelString, "ONCORE" ) == 0 )
        gpsModel = GPSMODEL_ONCORE;
    
    else if ( strcmp( gpsModelString, "PCTIME" ) == 0 )
        gpsModel = GPSMODEL_PCTIME;
    
    else /* default */
        gpsModel = GPSMODEL_PARGPS;


    if ( Debug >= 6 )  logit( "" , "Leaving  sr_atod_set_gpsmodel\n" );
    return( gpsModel );
}

/******************************************************************************
 *  Function: sr_atod_correct_sps                                             *
 *  Purpose:  This corrects the requested sampling rate (sps) to the closest  *
 *            rate actually achievable on the PARxCH hardware.                *
 ******************************************************************************/
void sr_atod_correct_sps( double *sps )
{
    int    gl, tb1, tb2, dec1, dec2, extdec1, extdec2, dummy;
    double sps0, sps1, sps2, diff1, diff2;

    if ( Debug >= 5 )
        logit( "" , "Starting sr_atod_correct_sps with requested sps = %lf\n", *sps );


   /* Compute decimation from requested sps
    ***************************************/
    sps0 = *sps;
    gl   = 0;                                              /* log(gain of 1) */
    ParXchSpsGainToTde( sps0, gl, &tb1, &dec1, &extdec1 );


   /* Set up next decimation too
    ****************************/
    tb2     = tb1;                                         /* turbo mode */
    dec2    = dec1 + 1;
    extdec2 = extdec1;


   /* Use both decimations to compute actual sps
    ********************************************/
    ParXchTdeToSpsGain( tb1, dec1, extdec1, &sps1, &dummy );
    ParXchTdeToSpsGain( tb2, dec2, extdec2, &sps2, &dummy );


   /* Compare both actual sps values with requested sps
    ***************************************************/   
    diff1 = sps1 - sps0;
    diff2 = sps0 - sps2;


   /* Pick the closer one
    *********************/
    if (diff1 < diff2)
        *sps = sps1;
    else
        *sps = sps2;


    if ( Debug >= 5 )
        logit( "" , "Leaving  sr_atod_correct_sps with corrected sps = %lf\n", *sps );

    return;
}

/******************************************************************************
 *  Function: sr_ts_init                                                      *
 *  Purpose:  Initialize the good and raw timestamp structures.               *
 ******************************************************************************/
void sr_ts_init( void )
{
    int i;

    if ( Debug >= 6 )  logit( "" , "Starting sr_ts_init\n" );

    
   /* Clear the good timestamp structures
    *************************************/
    for ( i = 0 ; i < MAX_TS ; i++ )
    {
        ParGpsTsClear( &TimeStamp[i] );
    }

   /* Clear the raw timestamps
    **************************/
    for ( i = 0 ; i < MAX_TSR ; i++ )
    {
        ParGpsTsClear( &TsReal[i] );
    }

    if ( Debug >= 6 )  logit( "" , "Leaving  sr_ts_init\n" );
    return;
}

/******************************************************************************
 *  Function: sr_filter_init                                                  *
 *  Purpose:  This initializes the scale and coefficient values used in       *
 *            the two term filter formulas described in sr_filter and used    *
 *            to update the BestCountsBetweenPps and BestCountsBetweenDready  *
 *            values.                                                         *
 *                                                                            *
 *            For this application, choose the coefficient C to be .95 and    *
 *            assume an N of 150 is a long enough tail.  Then C**N => .001    *
 *            which means that the 150th previous sample is contributing only *
 *            a very small amount to the average.  This also means that the   *
 *            amount that the current sample contributes is about 1/20th      *
 *            of the contribution from all previous samples so one point      *
 *            wont't change the average too quickly.  This number comes from  *
 *            computing C/1-C which is 19 for a C of .95.                     *
 ******************************************************************************/
void sr_filter_init( void )
{
    if ( Debug >= 6 )  logit( "" , "Starting sr_filter_init\n" );

   /* Set filter coefficients
    *************************/
    CbpCoeff = 0.95;
    CbdCoeff = 0.95;

   /* Set scale to 1-C to normalize
    *******************************/
    CbpScale = 1.0L - CbpCoeff;
    CbdScale = 1.0L - CbdCoeff;

    if ( Debug >= 5 )
    {
        logit( "", "Pps    filter scale %lf, coeff %lf\n", CbpScale, CbpCoeff );
        logit( "", "Dready filter scale %lf, coeff %lf\n", CbdScale, CbdCoeff );
    }

    if ( Debug >= 6 )  logit( "" , "Leaving  sr_filter_init\n" );
    return;
}

/******************************************************************************
 *  Function: sr_allocate_arrays                                              *
 *  Purpose:  Allocate space for the input acquired data and output tracebuf  *
 *            arrays.  Also initialize some common values in the output       *
 *            header that are shared for all channels.                        *
 ******************************************************************************/
void sr_allocate_arrays( void )
{
    unsigned int bufferSize;
    
    if ( Debug >= 6 )  logit( "" , "Starting sr_allocate_arrays\n" );
        

   /* Allocate space for the input acquired data.  We will alternately
    * fill two buffers.  By allowing a second buffer of data to be read
    * before the first is output, we ensure there is enough information
    * to accurately determine the end time of the first buffer before.
    ********************************************************************/
    bufferSize  = AtodNumChannels * AtodNumSamples; /* size in samples */
    AtodBufSize = bufferSize * sizeof(long);        /* size in bytes   */

    AtodBuffer1 = (long *) calloc( bufferSize, sizeof(long) );
    if ( AtodBuffer1 == NULL )
    {
        logit( "e", "srparxchewsend: Cannot allocate A/D buffer\n" );
        sr_terminate( );
    }

    AtodBuffer2 = (long *) calloc( bufferSize, sizeof(long) );
    if ( AtodBuffer2 == NULL )
    {
        logit( "e", "srparxchewsend: Cannot allocate A/D buffer\n" );
        sr_terminate( );
    }

    AtodBufferFill  = AtodBuffer2; /* Initialize with buffer 2 since */
    AtodBufferWrite = AtodBuffer1; /* switch occurs before filling   */

    if ( Debug >= 5 )
        logit( "", "Starting with Fill (2) = 0x%lX and Write (1) = 0x%lX\n",
               AtodBufferFill, AtodBufferWrite );

   
   /* Allocate space for the output trace buffer including both header + data
    *************************************************************************/
    TraceBufSize = sizeof(TRACE_HEADER) + (AtodNumSamples * sizeof(long));
    if ( Debug >= 0 )  logit( "", "Trace buffer size: %d bytes\n", TraceBufSize );

    if ( TraceBufSize > MAX_TRACEBUF_SIZ )
    {
        logit( "e", "srparxchewsend: Tracebuf size %ld exceeds %ld max; reduce sampling rate\n",
               TraceBufSize, MAX_TRACEBUF_SIZ );
        sr_terminate( );
    }

    TraceBuffer = (char *) malloc( TraceBufSize );
    if ( TraceBuffer == NULL )
    {
        logit( "e", "srparxchewsend: Cannot allocate the trace buffer\n" );
        sr_terminate( );
    }

   /* Set up pointers to the tracebuf header and data parts
    *******************************************************/
    TraceHead  = (TRACE_HEADER  *) &TraceBuffer[0];
    TraceDat   = (long *) &TraceBuffer[sizeof(TRACE_HEADER)];


   /* Set output values common to all channels
    ******************************************/
    TraceHead->nsamp      = AtodNumSamples;     /* number of samples in message   */
    TraceHead->samprate   = AtodSps;            /* sample rate; nominal           */
    TraceHead->quality[0] = QUALITY_OK;         /* one bit per condition          */
    TraceHead->quality[1] = QUALITY_OK;         /* one bit per condition          */
    TraceHead->pad[0]     = 'S';
    TraceHead->pad[1]     = 'R';
    sr_strncpy( TraceHead->datatype, "i4", 3 ); /* data format code (intel long)  */

    if (OutputMsgType == TypeTraceBuf)          /* old default loc + version info */
    {
       sr_strncpy( &TraceHead->chan[TRACE_POS_LOC], LOC_NULL_STRING, 3 );
       TraceHead->chan[TRACE_POS_VER]   = '1';
       TraceHead->chan[TRACE_POS_VER+1] = '0';
    }
    if (OutputMsgType == TypeTraceBuf2)         /* new tracebuf2 version info     */
    {
       TraceHead->chan[TRACE_POS_VER]   = TRACE2_VERSION0;
       TraceHead->chan[TRACE_POS_VER+1] = TRACE2_VERSION1;
    }

    TraceHead->starttime  = 0.0L;
    TraceHead->endtime    = 0.0L;


    if ( Debug >= 6 )  logit( "" , "Leaving  sr_allocate_arrays\n" );
    return;
}


   
/******************************************************************************
 *  Function: sr_atod_start                                                   *
 *  Purpose:  Start the PARxCH DAQ and optional PARGPS timing module          *
 *            acquiring data.                                                 *
 ******************************************************************************/
void sr_atod_start( void )
{
    if ( Debug >= 6 )  logit( "" , "Starting sr_atod_start\n" );


    logit( "et", "srparxchewsend: Starting acquisition\n" );

   
   /* Start the optional PARGPS
    ***************************/
    if ( UsingGps )
        ParGpsStart( ParGpsHandle );



   /* Start the PARxCH DAQ
    **********************/
    ParXchStart( ParXchHandle );


   /* Get start time from the PC
    ****************************/
    hrtime_ew( &AtodStartTime );                        /* start of first tracebuf */
    AtodEndTimeLast    = AtodStartTime - (1 / AtodSps); /* end of "prev" tracebuf  */
    AtodCurrentPtFill  = 0L;
    AtodCurrentPtWrite = 0L;
    if ( Debug >= 5 )
       logit( "" , "A/D last end time %lf, this start time %lf \n",
              AtodEndTimeLast, AtodStartTime );


   /* We'll need to re-initialize some values later
    ***********************************************/
    FirstTimeFilter = 1;
    FirstTimeSerial = 1;
    FirstTimePctime = 1;

    if ( Debug >= 6 )  logit( "" , "Leaving  sr_atod_start\n" );
    return;
}

/******************************************************************************
 *  Function: sr_atod_getdata                                                 *
 *  Purpose:  Read AtodNumSamples points for each of AtodNumChannels channels *
 *            into the AtodBuffer.                                            *
 ******************************************************************************/
int sr_atod_getdata( void )
{
    static unsigned int lastDataPpsEvent   = 0;
    static unsigned int lastPpsPpsEvent    = 0;
    static unsigned int lastSerialPpsEvent = 0;
    static int          nbehind            = 0;
    
    int          i, istr, npass, nextpt, parxchError, pargpsError, firstTry, isSummary;
    unsigned int nremain, nread, serialDelay;
    unsigned int dataPpsEvent, ppsPpsEvent, serialPpsEvent, minPpsEvent, maxPpsEvent;
    long         nDataEvents, nPpsEvents, nSerialEvents, diffDP;

    if ( Debug >= 6 )  logit( "" , "Starting sr_atod_getdata\n" );
    
    nextpt  = 0;
    nremain = (unsigned int)(AtodNumSamples * AtodNumChannels);

    if (SummaryReportInterval > 0 && SummaryCount >= SummaryReportInterval-1)
       isSummary = 1;
    else
       isSummary = 0;
    if ( isSummary || Debug >= 5 )
        logit( "t", "srparxchewsend: summary info follows ...\n" );


   /* Select which of two alternating buffers is to be used
    *******************************************************/
    if ( AtodBufferFill == AtodBuffer1 )
    {        
        AtodBufferFill  = AtodBuffer2;
        AtodBufferWrite = AtodBuffer1;
    }
    else /* ( AtodBufferFill == AtodBuffer2 ) */
    {        
        AtodBufferFill  = AtodBuffer1;
        AtodBufferWrite = AtodBuffer2;
    }


    if ( Debug >= 5 )  logit( "", "Now Filling = 0x%lX\n", AtodBufferFill );
    

   /* Read Analog data (plus digital, GPS, and counter if requested)
    ****************************************************************/
    npass = 0;

    if ( isSummary || Debug >= 5 )
        logit( "", "Reading Analog Info:\n" );

    while ( nremain > 0 )
    {
        nread = ParXchFullReadData( ParXchHandle, &AtodBufferFill[nextpt],
                                    nremain, AtodDataRequest, &parxchError );

        if ( parxchError == PARXCH_ERROR_OVERFLOW )
        {
            sprintf( Msg, "ReadData error %s", PARXCH_ERROR_MSG[parxchError] );
            sr_error( EW_ERROR_BASE_PARXCH+parxchError, Msg );
            sr_atod_overflow_recovery( );
            return( 0 );
        }        
        if ( parxchError != PARXCH_ERROR_NONE )
        {
            sprintf( Msg, "ReadData error %s", PARXCH_ERROR_MSG[parxchError] );
            sr_error( EW_ERROR_BASE_PARXCH+parxchError, Msg );
            return( 0 );
        }

        nremain -= nread;
        nextpt  += nread;
        npass++;

        if ( isSummary || Debug >= 5 )
            logit( "" , "  Read %4u pts, nextpt = %4d, nremain = %4u\n",
                         nread, nextpt, nremain );
        

       /* If more points wanted, give them time to arrive
        *************************************************/        
        if ( nremain > 0 )
            sleep_ew( SleepMs );   /* Wait SleepMs milliseconds (eg 200) */


    } /* end while nremain > 0 */


   /* If we read all the requested data in right away,
    * it means we're starting to fall behind.  If this 
    * condition lasts too long, give a warning.
    **************************************************/
    if ( npass == 1 )
        nbehind++;
    else
        nbehind = 0;

    if ( nbehind > 0 )
        logit( "et", "srparxchewsend: Earthworm running slow but still OK ...\n     (data built up in FIFO %d times)\n", nbehind );
        
    if ( nbehind == BEHIND_LIMIT )
    {
        sprintf( Msg, "Warning - Earthworm is not keeping up with acquisition, overflow could result" );
        sr_error( EW_ERROR_BASE_PARXCH+PARXCH_ERROR_OVERFLOW, Msg );
    }


    

    if ( UsingGps )
    {

       /* Look in analog data for GPS marks and OBC counts
        **************************************************/
        dataPpsEvent = sr_ts_process_analog( );
       
        minPpsEvent      = lastDataPpsEvent+1;
        maxPpsEvent      = dataPpsEvent;
        nDataEvents      = dataPpsEvent - lastDataPpsEvent;
        lastDataPpsEvent = dataPpsEvent;

        if ( isSummary || Debug >= 4 )
            logit( "", "  Analog data points %ld to %ld contain PPS Event %d\n",
                   AtodCurrentPtFill, (AtodCurrentPtFill+AtodNumSamples-1), dataPpsEvent );

        
        if ( Debug >= 1 )
        {
            if ( nDataEvents == 0 )
                logit("et", "srparxchewsend: No GPS marks found!\n");

            if ( dataPpsEvent < minPpsEvent )
                logit( "t", "srparxchewsend: Missed PPS Event in Data   (found %ld, expected %ld)\n",
                      dataPpsEvent, minPpsEvent );

            else if ( dataPpsEvent > minPpsEvent )
                logit( "t", "srparxchewsend: Extra  PPS Event in Data   (found %ld, expected %ld - this is %s)\n",
                       dataPpsEvent, minPpsEvent, (dataPpsEvent == minPpsEvent+1)?"Normal":"Unexpected" );
        }


    
       /* Read and process GPS PPS data
        *******************************/
        if ( isSummary || Debug >= 5 )
            logit( "", "Reading GPS PPS Info:\n" );
        firstTry   = 1;
        nPpsEvents = nDataEvents;     /* Only read PPS data for events we */
        while( nPpsEvents > 0 )       /* already have analog data for.    */
        {
            AtodNumPps = ParGpsReadPpsData( ParGpsHandle, GpsPpsBuffer,
                                            1, &pargpsError );
            if ( pargpsError != PARGPS_ERROR_NONE )
            {
                sprintf( Msg, "ReadPpsData error %s", PARGPS_ERROR_MSG[pargpsError] );
                sr_error( EW_ERROR_BASE_PARGPS+pargpsError, Msg );
            }
            if ( isSummary || Debug >= 4 )
            {
               logit( "", "  PPS Event # %d with\n", GpsPpsBuffer[0].PpsEventNum );
               diffDP = (long)(GpsPpsBuffer[0].CountAtDready.QuadPart -
                               GpsPpsBuffer[0].CountAtPps.QuadPart);
               logit( "", "  PC Counter1 at PPS 0x%08lX %08lX  at Dready 0x%08lX %08lX  (D-P=%ld)\n",
                     GpsPpsBuffer[0].CountAtPps.u.HighPart,
                     GpsPpsBuffer[0].CountAtPps.u.LowPart,
                     GpsPpsBuffer[0].CountAtDready.u.HighPart,
                     GpsPpsBuffer[0].CountAtDready.u.LowPart,
                     diffDP );
               diffDP = (long)(GpsPpsBuffer[0].PctimeAtDready.QuadPart -
                               GpsPpsBuffer[0].PctimeAtPps.QuadPart);
               logit( "", "  PC Counter2 at PPS 0x%08lX %08lX  at Dready 0x%08lX %08lX  (D-P=%ld)\n",
                     GpsPpsBuffer[0].PctimeAtPps.u.HighPart,
                     GpsPpsBuffer[0].PctimeAtPps.u.LowPart,
                     GpsPpsBuffer[0].PctimeAtDready.u.HighPart,
                     GpsPpsBuffer[0].PctimeAtDready.u.LowPart,
                     diffDP );
            }

           /* If no PPS data read, wait and try again once
            **********************************************/
            if ( AtodNumPps <= 0 )
            {
                if ( firstTry )
                {
                    firstTry = 0;
                    sleep_ew( 10 );
                    continue;
                }
                else
                {
                    logit( "et", "srparxchewsend: No PPS data was read\n" );
                    break;
                }
            }


           /* Get PpsEvent number for PPS data and error check
            **************************************************/
            ppsPpsEvent = GpsPpsBuffer[0].PpsEventNum;

            if ( ppsPpsEvent < minPpsEvent )        /* PpsEvent too old, discard */
            {
                logit("et", "srparxchewsend: Discarding old PPS PpsEvent %d, current min is %d\n",
                      ppsPpsEvent, minPpsEvent );
                continue;
            }
            else if ( ppsPpsEvent > maxPpsEvent )   /* PpsEvent too new */
            {
                logit( "et", "srparxchewsend: PPS PpsEvent %d read too soon, current max is %d\n",
                      ppsPpsEvent, maxPpsEvent );
                  nPpsEvents--;
                  // WCT: Consider making synthetic tempstamp for missed PpsEvent here
            }
               
            if ( ppsPpsEvent != lastPpsPpsEvent+1 )
                logit( "et", "srparxchewsend: missed PPS Event in PPS,    found %ld, expected %ld\n",
                       ppsPpsEvent, lastDataPpsEvent+1);


           /* PpsEvent is good, extract info and save in timestamp
            ******************************************************/
            sr_ts_process_pps( );
            
            nPpsEvents--;
            lastPpsPpsEvent = ppsPpsEvent;


           /* Exit loop if PPS data caught up to analog data
            ************************************************/
            if ( ppsPpsEvent >= dataPpsEvent )
                break;

        } /* end while 1 to read PPS data */


        
       /* Read and process GPS serial data
        **********************************/
        if ( isSummary || Debug >= 5 )
            logit( "", "Reading GPS Serial Info:\n" );
        firstTry      = 1;
        serialDelay   = 2;
        nSerialEvents = nDataEvents;           /* Only read serial data if we have */
        while( GpsModel != GPSMODEL_PCTIME &&  /* serial data expected and         */
               dataPpsEvent > serialDelay &&   /* matching analog data already     */
               nSerialEvents > 0 ) 
        {
            AtodNumSerial = ParGpsReadSerialData( ParGpsHandle, GpsSerialBuffer, 
                                                  1, &pargpsError );
            if ( pargpsError != PARGPS_ERROR_NONE )
            {
                sprintf( Msg, "ReadSerialData error %s", PARGPS_ERROR_MSG[pargpsError] );
                sr_error( EW_ERROR_BASE_PARGPS+pargpsError, Msg );
            }
            if ( isSummary || Debug >= 4 )
            {
               logit( "", "  Serial Event # %d with %d NMEA strings\n",
                     GpsSerialBuffer[0].PpsEventNum, GpsSerialBuffer[0].NmeaCount );
               for ( i = 0 ; i < GpsSerialBuffer[0].NmeaCount ; i++ )
               {
                  istr = i * MAX_NMEA_SIZE;
                  logit( "","  %d: %s", i, &GpsSerialBuffer[0].NmeaMsg[istr] );
               }
            }

           /* If no serial data read, wait and try again once
            *************************************************/
            if ( AtodNumSerial <= 0 )
            {
                if ( firstTry )
                {
                    firstTry = 0;
                    sleep_ew( 10 );
                    continue;
                }
                else
                {
                    logit( "et", "srparxchewsend: No serial data was read\n" );
                    break;
                }
            }


           /* Get PpsEvent number for serial data and error check
            *****************************************************/
            serialPpsEvent = GpsSerialBuffer[0].PpsEventNum;

            if ( serialPpsEvent+serialDelay < minPpsEvent )      /* PpsEvent too old, discard */
            {
                logit( "et", "srparxchewsend: Discarding old serial PpsEvent %d (+delay=%d), current min is %d\n",
                      serialPpsEvent, serialPpsEvent+serialDelay, minPpsEvent );
                continue;
            }
            else if ( serialPpsEvent+serialDelay > maxPpsEvent ) /* PpsEvent too new */
            {
                logit( "et", "srparxchewsend: Serial PpsEvent %d (+delay=%d) read too soon, current max is %d\n",
                      serialPpsEvent, serialPpsEvent+serialDelay, maxPpsEvent );
                nSerialEvents--;
                // WCT: Consider making synthetic tempstamp for missed PpsEvent here
            }
               
            if (serialPpsEvent != lastSerialPpsEvent+1)
                logit( "et", "srparxchewsend: missed PPS Event in Serial, found %ld, expected %ld (%ld->%ld)\n",
                      serialPpsEvent, lastSerialPpsEvent+1,
                      serialPpsEvent, serialPpsEvent+serialDelay);

 
           
           /* PpsEvent is good, extract info and save in timestamp
            ******************************************************/
            sr_ts_process_serial( );

            nSerialEvents--;
            lastSerialPpsEvent = serialPpsEvent;


           /* Exit loop if serial data caught up to analog data
            * (note, we expect serial data to be delayed by 2 sec)
            *****************************************************/
            if ( serialPpsEvent+serialDelay >= dataPpsEvent )
            {
                if ( Debug >= 5 )  logit( "", "Serial data read caught up\n" );
                break;
            }

        } /* end while 1 to read serial data */


    } /* end if UsingGps */


   /* Update current point and buffer count
    ***************************************/
    if ( Debug >= 5 )  logit( "" , "Completed buffer %lu\n", AtodBufferNum );
    AtodBufferNum++;
    AtodCurrentPtFill += AtodNumSamples;
    

    if ( Debug >= 6 )  logit( "" , "Leaving  sr_atod_getdata with %d pts\n", nextpt );
    return( nextpt );
}



/******************************************************************************
 *  Function: sr_ts_process_analog                                            *
 *  Purpose:  To extract time stamp information from the analog data buffer.  *
 *            This includes the sample index for the mark point and, for the  *
 *            PAR8CH, the value of the on board counter (OBC).                *
 *                                                                            *
 *            Return 0 or last PPS event processed.                           *
 ******************************************************************************/
unsigned int sr_ts_process_analog( void )
{
    int          nValid;
    unsigned int currEvent;

    if ( Debug >= 6 )  logit( "" , "Starting sr_ts_process_analog\n" );


   /* Nothing to check if neither channel exists
    ********************************************/
    if ( AtodMarkChan == MARKCHANNEL_NONE && AtodCntrChan == MARKCHANNEL_NONE )
    {
       if ( Debug >= 5 )
          logit( "", "No mark or counter channel to check\n" );
       return( 0 );
    }

    if ( AtodMarkChan == MARKCHANNEL_NONE && AtodCntrChan != MARKCHANNEL_NONE )
    {
        logit( "e", "srparxchewsend: Error GPS channel MUST exist if OBC does\n" );
        return( 0 );
    }


   /* Process the analog data for timestamp information
    ***************************************************/
    currEvent = 0;
    if ( Debug >= 5 )  logit( "", "Now Processing = 0x%lX\n", AtodBufferFill );
    ParGpsTsProcessAnalog( AtodBufferFill, AtodNumSamples, AtodNumChannels,
                           AtodCurrentPtFill, AtodMarkChan, AtodCntrChan, 
                           (long)BestObcCountsPerSample, TsValid,
                           &currEvent, &nValid );

    if ( Debug >= 6 )  logit( "", "AtodCurrentPtFill %ld, currEvent %u, nValid %d\n",
                             AtodCurrentPtFill, currEvent, nValid );


   /* If any new timestamps are valid, update
    * the good ones we'll use to compute time
    *****************************************/
    if ( nValid > 0 )
        sr_ts_update( );
           

    if ( Debug >= 6 )  logit( "" , "Leaving  sr_ts_process_analog\n" );

    return( currEvent );
}

/******************************************************************************
 *  Function: sr_ts_process_pps                                               *
 *  Purpose:  To extract time stamp information from the PPS data buffer.     *
 *            This includes the 64 bit PC counter value at PPS (CountAtPps)   *
 *            and at the next data ready (CountAtDready).                     *
 *                                                                            *
 *            Return 0 or last PPS event processed.                           *
 ******************************************************************************/
unsigned int sr_ts_process_pps( void )
{
    int          nValid;
    unsigned int currEvent;
    
    if ( Debug >= 6 )  logit( "", "Starting sr_ts_process_pps\n" );

    
   /* Process the pps data for timestamp information
    ************************************************/
    currEvent = 0;
    ParGpsTsProcessPps( GpsPpsBuffer, 1, BestCountsPerSecond,
                        GpsModel, TsValid, &currEvent, &nValid );
    
    if ( Debug >= 6 )  logit( "", "Pps currEvent %u, nValid %d\n",
                             currEvent, nValid );

    
   /* If any new timestamps are valid, update
    * the good ones we'll use to compute time
    *****************************************/
    if ( nValid > 0 )
        sr_ts_update( );
           

    if ( Debug >= 6 )  logit( "", "Leaving  sr_ts_process_pps\n" );
    return( currEvent );
}

/******************************************************************************
 *  Function: sr_ts_process_serial                                            *
 *  Purpose:  To extract time stamp information from the serial data buffer.  *
 *            This includes the time in secons since 1970 (SecSince1970), the *
 *            number of satellites in view (NumSat), and the source of the    *
 *            year-month-day (YmdSource) and hour-minute-second (HmsSource)   *
 *            component of the time.                                          *
 *                                                                            *
 *            Because of the way it is acquired, the GPS serial data tends to *
 *            be unavailable until about 2 seconds after the corresponding    *
 *            PPS event has occurred.  Since we don't like waiting that long, *
 *            we start with serial data from the 2nd prior PPS event and add  *
 *            two seconds to get the correct time for the current PPS event.  *
 *                                                                            *
 *            Return 0 or last PPS event processed.                           *
 ******************************************************************************/
unsigned int sr_ts_process_serial( void )
{
    int                 i, nValid, serialDelay;
    unsigned int        currEvent;
    static unsigned int PrevPpsEvent;
    static double       PrevSecSince1970;

    if ( Debug >= 6 )  logit( "" , "Starting sr_ts_process_serial\n" );


   /* Adjust for 2 second delay
    ***************************/
    serialDelay = 2;


   /* First time init
    *****************/
    if ( FirstTimeSerial )
    {
        PrevPpsEvent     = INVALID_PPSEVENT;
        PrevSecSince1970 = AtodStartTime + serialDelay;
        FirstTimeSerial  = 0;
        if ( Debug >= 5 ) logit( "", "First time init for serial info AtodStartTime %lf\n",
                                AtodStartTime );
    }


   /* Save previous GPS serial NMEA info for future debugging purposes
    ******************************************************************/
    for ( i = 0 ; i < MAX_PREV_SER-1 ; i++ )
            GpsPrevSerial[i] = GpsPrevSerial[i+1];

    GpsPrevSerial[MAX_PREV_SER-1] = GpsSerialBuffer[0];



   /* Process the pps data for timestamp information
    ************************************************/
    currEvent   = 0;
    ParGpsTsProcessSerial( GpsSerialBuffer, 1, serialDelay, TsValid,
                           &PrevPpsEvent, &PrevSecSince1970,
                           &currEvent, &nValid );

    if ( Debug >= 6 )
       logit( "", "Serial PrevEv %u, PrevSec %lf, currEvent %u, nValid %d\n",
              PrevPpsEvent, PrevSecSince1970, currEvent, nValid );

    
   /* If any new timestamps are valid, update
    * the good ones we'll use to compute time
    *****************************************/
    if ( nValid > 0 )
        sr_ts_update( );


    if ( Debug >= 6 )  logit( "" , "Leaving  sr_ts_process_serial\n" );
    return( currEvent );
}

/******************************************************************************
 *  Function: sr_ts_update                                                    *
 *  Purpose:  When a new raw timestamp is available from the ParGps library,  *
 *            we process it.  This involves making space for it, reading it   *
 *            in from the library, checking to see if it is an outlier and    *
 *            correcting it if it is, using it to update the final timestamps *
 *            that will be used to compute tracebuf time.  If the new raw     *
 *            timestamp is not an outlier, it is also used to update the long *
 *            term averages BestCountsBetweenPps and BestCountsBetweenDready. *
 ******************************************************************************/
int sr_ts_update( void )
{
    int    i, isGood;
    double cbpCounts, cbdCounts;

    if ( Debug >= 6 )  logit( "" , "Starting sr_ts_update\n" );


   /* Read new valid timestamp from ParGps library
    **********************************************/
    if ( !ParGpsTsReadValid( &TsReal[0], TsValid ) )
            logit( "", "TsReal TimeStamp invalid\n" );

    if ( Debug >= 6 )
    {
    
        logit( "", "TsReal Valid   Pps   Samp       ObcCount  CountAtP             CountAtD            PctimeAtP             PctimeAtD        YmdSrc  HmsSrc   SecSince1970        PCTPsec1970         PCTDsec1970\n");

        for ( i = 0 ; i < 1 ; i++ ) // was MAX_TSR
        {
//            ParGpsSysTimeToSec1970( TsReal[i].PctimeAtPps,    PcCounter1Type, BestCountsPerSecond, &PCTPsec1970, 0 );
//            ParGpsSysTimeToSec1970( TsReal[i].PctimeAtDready, PcCounter1Type, BestCountsPerSecond, &PCTDsec1970, 0 );
//            logit( "", "TSR:   0    0x%X   %5u  %5ld  0x%08lX  0x%08lX %08lX  0x%08lX %08lX  0x%08lX %08lX   0x%08lX %08lX   %2d    %2d     %lf   %lf   %lf\n",
            logit( "", "TSR:   0    0x%X   %5u  %5ld  0x%08lX  0x%08lX %08lX  0x%08lX %08lX  0x%08lX %08lX   0x%08lX %08lX   %2d    %2d     %lf\n",
                   TsReal[i].Valid, TsReal[i].PpsEvent, TsReal[i].Sample, TsReal[i].CountObc,
                   TsReal[i].CountAtPps.u.HighPart,     TsReal[i].CountAtPps.u.LowPart,
                   TsReal[i].CountAtDready.u.HighPart,  TsReal[i].CountAtDready.u.LowPart,
                   TsReal[i].PctimeAtPps.u.HighPart,    TsReal[i].PctimeAtPps.u.LowPart,
                   TsReal[i].PctimeAtDready.u.HighPart, TsReal[i].PctimeAtDready.u.LowPart,
                   TsReal[i].YmdSource, TsReal[i].HmsSource, TsReal[i].SecSince1970 );
//                   PCTPsec1970, PCTDsec1970 );
        }
    }


   /* Check if new raw timestamp is an outlier */

    isGood    = 1; // WCT - Hardwired good for now
    cbpCounts = BestCountsBetweenPps;
    cbdCounts = BestCountsBetweenDready;
    TsFix     = TsReal[0];
    
    
   /* If it is an outlier (ie not good), then fix it up */

    if ( !isGood )
    {
        // WCT - Code to correct TsFix goes here
    }



   /* Shuffle final timestamps to make room for one more
    * and add the new raw (possibly fixed) timestamp
    *****************************************************/
    for ( i = 0 ; i < MAX_TS-1 ; i++ )
    {
        TimeStamp[i] = TimeStamp[i+1];
        if ( Debug >= 5 )
            logit( "", "TS%d Sample %ld, Valid = 0x%X, PpsEvent = %ld, SecSince1970 = %lf\n",
                   i, TimeStamp[i].Sample, TimeStamp[i].Valid,
                      TimeStamp[i].PpsEvent, TimeStamp[i].SecSince1970 );
    }
    
     TimeStamp[MAX_TS-1] = TsFix;
        if ( Debug >= 5 )
            logit( "", "TS%d Sample %ld, Valid = 0x%X, PpsEvent = %ld, SecSince1970 = %lf\n",
                   MAX_TS-1,
                   TimeStamp[MAX_TS-1].Sample, TimeStamp[MAX_TS-1].Valid,
                   TimeStamp[MAX_TS-1].PpsEvent, TimeStamp[MAX_TS-1].SecSince1970 );
     
        if ( Debug >= 2 )
        {
            if ( TimeStamp[MAX_TS-1].SecSince1970 > 0.0L &&
                (TimeStamp[MAX_TS-1].SecSince1970 - TimeStamp[MAX_TS-2].SecSince1970) < 0.5L )
            {
                logit( "", "WARNING: New timestamp is less than 1 second from previous\n" );
                logit( "", "   Prev %5u  %5ld  0x%08lX %08lX  0x%08lX %08lX     %lf\n",
                       TimeStamp[MAX_TS-2].PpsEvent,
                       TimeStamp[MAX_TS-2].Sample, 
                       TimeStamp[MAX_TS-2].CountAtPps.u.HighPart,
                       TimeStamp[MAX_TS-2].CountAtPps.u.LowPart,
                       TimeStamp[MAX_TS-2].CountAtDready.u.HighPart,
                       TimeStamp[MAX_TS-2].CountAtDready.u.LowPart,
                       TimeStamp[MAX_TS-2].SecSince1970
                       );
                logit( "", "    New %5u  %5ld  0x%08lX %08lX  0x%08lX %08lX     %lf\n",
                       TimeStamp[MAX_TS-1].PpsEvent,
                       TimeStamp[MAX_TS-1].Sample, 
                       TimeStamp[MAX_TS-1].CountAtPps.u.HighPart,
                       TimeStamp[MAX_TS-1].CountAtPps.u.LowPart,
                       TimeStamp[MAX_TS-1].CountAtDready.u.HighPart,
                       TimeStamp[MAX_TS-1].CountAtDready.u.LowPart,
                       TimeStamp[MAX_TS-1].SecSince1970
                       );
            }
        }
    


   /* If the new one is good (not outlier), update the long term averages,
    * BestCountsBetweenPps and BestCountsBetweenDready.  These allows us to
    * throw out outliers later.
    *
    * Here, cbpCounts is the measured difference in counts from one PPS to
    * the next.  This time is exactly 1 second as defined by the "big clock
    * in the sky" (eg GPS time).  If the PC counter were perfect, this count
    * would equal the counter frequency.
    *************************************************************************/
    if ( isGood )
    {
        /* Update long term averages */
        
        if ( FirstTimeFilter )
        {
            BestCountsBetweenPps    = cbpCounts;
            BestCountsBetweenDready = cbdCounts;
            FirstTimeFilter         = 0;
            if ( Debug >= 5 )  logit( "", "First time init for filter info\n" );
        }
        
        if ( Debug >= 6 )  logit( "", "Before BCBP = %lf, BCBD = %lf\n",
                                 BestCountsBetweenPps, BestCountsBetweenDready );
               
        sr_filter( FILTER_PPS,    cbpCounts );
        sr_filter( FILTER_DREADY, cbdCounts );
        
        if ( Debug >= 6 )  logit( "", "After  BCBP = %lf, BCBD = %lf\n",
                                 BestCountsBetweenPps, BestCountsBetweenDready );
        
    }


    if ( Debug >= 6 )  logit( "" , "Leaving  sr_ts_update\n" );
    return( 1 );
}

/******************************************************************************
 *  Function: sr_filter                                                       *
 *  Purpose:  This updates the long term "counts between" averages.  It can   *
 *            be called for BestCountsBetweenPps or BestCountsBetweenDready.  *
 *            The new average value is computed based on one new value for    *
 *            measured counts in combination with the previous long term      *
 *            average value.  The formula used is a simple two term filter    *
 *            of the form:                                                    *
 *                                                                            *
 *            f[i] = S * x[i] +  C * f[i-1]                                   *
 *                                                                            *
 *            where x[i] is the new data point, S is a scaling factor, C is   *
 *            the filter coefficient that controls how much averaging is      *
 *            applied, and f[i] is the resulting filter value.                *
 *                                                                            *
 *            The formula above can also be written in expanded form as:      *
 *                                                                            *
 *                                1            2                              *
 *            f[i] = S * (x[i] + C * x[i-1] + C * x[i-2] + ...                *
 *                                                                            *
 *            The scale factor S should be set to 1-C for normalized data.    *
 *                                                                            *
 *            When the coefficient C is chosen to be 0, no averaging is       *
 *            applied (f[i] = x[i]) and the output is equal to the current    *
 *            new data value.                                                 *
 *                                                                            *
 *            We want to select C so, in the expanded form, the coefficient   *
 *            appearing before the Nth previous data point (x[i-N]) is very   *
 *            small so that it and all earlier data points make almost no     *
 *            contribution to the filtered result.                            *
 *                                                                            *
 *            See sr_filter_init for the setting chosen for C.                *
 ******************************************************************************/
void sr_filter( int target, double newCount )
{
    if ( Debug >= 7 )  logit( "" , "Starting sr_filter\n" );


    if ( target == FILTER_PPS )
    {
        BestCountsBetweenPps = (CbpScale * newCount) +
                               (CbpCoeff * BestCountsBetweenPps);
        CbpLimit             = BestCountsBetweenPps * CBP_RANGE;

        if ( Debug >= 6 )
           logit( "", "New measured CBP %lf, BestCountsBetweenPps    %lf, limit %lf\n",
                  newCount, BestCountsBetweenPps, CbpLimit );
    }

    else if ( target == FILTER_DREADY )
    {
        BestCountsBetweenDready = (CbdScale * newCount) +
                                  (CbdCoeff * BestCountsBetweenDready);
        CbdLimit                = BestCountsBetweenDready * CBD_RANGE;

        if ( Debug >= 6 )
           logit( "", "New measured CBD %lf, BestCountsBetweenDready %lf, limit %lf\n",
                  newCount, BestCountsBetweenDready, CbdLimit );
    }

    else
    {
        logit( "", "Filter function called with unknown type %d\n", target );
    }
    
    if ( Debug >= 7 )  logit( "" , "Leaving  sr_filter\n" );
    return;
}

/******************************************************************************
 *  Function: sr_send_traces                                                  *
 *  Purpose:  Fill a earthworm tracebuf messages with data just acquired by   *
 *            the PARxCH and send it out to the ring.  The data is demuxed    *
 *            here so there will be one message per channel.                  *
 *                                                                            *
 *            Note: GpsLockCount increments for each buffer of data and       *
 *            GpsLockReportInterval is measured in seconds, so comparing      *
 *            them is only valid because each buffer is about 1 second long.  *
 ******************************************************************************/
void sr_send_traces( void )
{
    static int    lastObcMethod  = TIME_METHOD_NONE;
    static int    lastGpsMethod  = TIME_METHOD_NONE;
    static int    lastPctMethod  = TIME_METHOD_NONE;
    static int    lastSpsMethod  = TIME_METHOD_NONE;
    static int    firstPass = 1;
    static double lastObcEndTime, lastObcStartTime;
    static double lastGpsEndTime, lastGpsStartTime;
    static double lastPctEndTime, lastPctStartTime;
    static double lastSpsEndTime, lastSpsStartTime;
    int    i, j, rc, saveTimeMethod, saveLastMethod;
    long  *tracePtr, *atodPtr;
    double saveStartTime, saveEndTime, saveTHstart, saveTHend;
    char  *gpsLockCurrStr;

    if ( Debug >= 6 )  logit( "" , "Starting sr_send_traces\n" );

    
   /* Debugging code that compares four time computation methods
    ************************************************************/
    if ( Debug >= 7 )
    {
        if (firstPass)
        {
            lastObcStartTime = AtodStartTime;
            lastGpsStartTime = AtodStartTime;
            lastPctStartTime = AtodStartTime;
            lastSpsStartTime = AtodStartTime;
            lastObcEndTime   = AtodEndTimeLast;
            lastGpsEndTime   = AtodEndTimeLast;
            lastPctEndTime   = AtodEndTimeLast;
            lastSpsEndTime   = AtodEndTimeLast;
            firstPass        = 0;
        }
    
        /* Save true values */
        saveTimeMethod = UseTimeMethod;
        saveLastMethod = LastTimeMethod;
        saveStartTime  = AtodStartTime;
        saveEndTime    = AtodEndTimeLast;
        saveTHstart    = TraceHead->starttime;
        saveTHend      = TraceHead->endtime;


        /* Use OBC method */
        UseTimeMethod        = TIME_METHOD_OBC;
        LastTimeMethod       = lastObcMethod;
        AtodStartTime        = lastObcStartTime;
        AtodEndTimeLast      = lastObcEndTime;
        TraceHead->starttime = saveTHstart;
        TraceHead->endtime   = saveTHend;
        sr_ts_set_tracebuf_time();
        lastObcMethod        = LastTimeMethod;
        lastObcStartTime     = AtodStartTime;
        lastObcEndTime       = AtodEndTimeLast;


        /* Use GPS method */
        UseTimeMethod        = TIME_METHOD_GPS;
        LastTimeMethod       = lastGpsMethod;
        AtodStartTime        = lastGpsStartTime;
        AtodEndTimeLast      = lastGpsEndTime;
        TraceHead->starttime = saveTHstart;
        TraceHead->endtime   = saveTHend;
        sr_ts_set_tracebuf_time();
        lastGpsMethod        = LastTimeMethod;
        lastGpsStartTime     = AtodStartTime;
        lastGpsEndTime       = AtodEndTimeLast;


        /* Use PCTIME method */
        UseTimeMethod        = TIME_METHOD_PCT;
        LastTimeMethod       = lastPctMethod;
        AtodStartTime        = lastPctStartTime;
        AtodEndTimeLast      = lastPctEndTime;
        TraceHead->starttime = saveTHstart;
        TraceHead->endtime   = saveTHend;
        sr_ts_set_tracebuf_time();
        lastPctMethod        = LastTimeMethod;
        lastPctStartTime     = AtodStartTime;
        lastPctEndTime       = AtodEndTimeLast;

        
        /* Use SPS method */
        UseTimeMethod        = TIME_METHOD_SPS;
        LastTimeMethod       = lastSpsMethod;
        AtodStartTime        = lastSpsStartTime;
        AtodEndTimeLast      = lastSpsEndTime;
        TraceHead->starttime = saveTHstart;
        TraceHead->endtime   = saveTHend;
        sr_ts_set_tracebuf_time();
        lastSpsMethod        = LastTimeMethod;
        lastSpsStartTime     = AtodStartTime;
        lastSpsEndTime       = AtodEndTimeLast;


        /* Restore true values */
        AtodStartTime        = saveStartTime;
        AtodEndTimeLast      = saveEndTime;
        TraceHead->starttime = saveTHstart;
        TraceHead->endtime   = saveTHend;
        UseTimeMethod        = saveTimeMethod;
        LastTimeMethod       = saveLastMethod;

    } /* end if Debug >= 7 */

    

   /* Set trace start and end times (sec since 1970) same for all channels
    **********************************************************************/
    sr_ts_set_tracebuf_time();


//FIX -- Check TS number

    if ( UsingGps )
    {
       /* Update GPS lock summary info
        ******************************/
        if ( TimeStamp[MAX_TS-1].NumSat >= 3 )
        {
            GpsLockGood++;
            gpsLockCurrStr = "Good";
        }
        else
        {
            GpsLockBad++;
            gpsLockCurrStr = "Bad";
        }
        GpsLockCount++;
    
    
       /* We've reached the end of a GPS reporting interval
        ***************************************************/
        if ( GpsLockCount >= GpsLockReportInterval && GpsLockReportInterval > 0 )
        {

           /* Log the GPS lock info
            ***********************/
            if ( Debug >= 1 )
                logit( "t", "srparxchewsend: GPS Lock Stats = Good %5ld, Bad %5ld, Currently %s\n",
                       GpsLockGood, GpsLockBad, gpsLockCurrStr );


           /* Send error message if status changed
            **************************************/
            if ( GpsLockBad < GpsLockBadLimit ) /* GPS lock good now */
            {            
                if ( GpsLockStatus != 1 )       /* but bad before    */
                {
                    sprintf( Msg, "GPS satellite count now OK (Good %ld / %ld seconds)",
                             GpsLockGood, GpsLockReportInterval );
                    sr_error( EW_ERROR_BASE_PARGPS+PARGPS_ERROR_NOT_AVAILABLE, Msg );
                }
                GpsLockStatus = 1;
            }

            else /* ( GpsLockBad >= GpsLockBadLimit ) */ /* GPS lock bad now */
            {
                if ( GpsLockStatus != 0)                 /* but good before  */
                {
                    sprintf( Msg, "GPS satellite count too low (Bad %ld / %ld seconds)",
                             GpsLockBad, GpsLockReportInterval );
                    sr_error( EW_ERROR_BASE_PARGPS+PARGPS_ERROR_NOT_AVAILABLE, Msg );
                }
                GpsLockStatus = 0;
            }


           /* Reset lock counts for next interval
            *************************************/
            GpsLockCount = GpsLockGood = GpsLockBad = 0;


        } /* end if GpsLockCount > GpsLockReportInterval */


    } /* end if UsingGps */
        


   /* Set tracebuf type as new or old format
    ****************************************/
    if (OutputMsgType == TypeTraceBuf2 && TRACE2_OK)
        Logo.type = TypeTraceBuf2;   /* We'll send the new tracebuf2s */
    else
        Logo.type = TypeTraceBuf;    /* We'll send the old tracebufs  */



   /* Loop making one tracebuf message per channel
    **********************************************/
    if ( Debug >= 5 )
        logit( "", "Preparing to send traces\n" );

    DbgLastStartDiff = TraceHead->starttime - DbgLastStart;
    DbgLastEndDiff   = TraceHead->endtime   - DbgLastEnd;
    
    if (Debug >= 5)
            logit( "", "Buf %3lu, Start %lf, End %lf, rate = %10.6lf, (Diff S = %lf, E = %lf)\n",
                   AtodBufferNum-1,
                   TraceHead->starttime,
                   TraceHead->endtime,
                   TraceHead->samprate,
                   DbgLastStartDiff,
                   DbgLastEndDiff
                   );
    
    DbgLastStart = TraceHead->starttime;
    DbgLastEnd   = TraceHead->endtime;
    
    if ( Debug >= 5 )  logit( "", "Now Writing = 0x%lX\n", AtodBufferWrite );
    
    for ( i = 0 ; i < AtodNumChannels ; i++ )
    {

       /* Fill the trace buffer header
        ******************************/
        sr_strncpy( TraceHead->sta,  ChanList[i].sta,  TRACE_STA_LEN );  /* Site      */
        sr_strncpy( TraceHead->net,  ChanList[i].net,  TRACE_NET_LEN );  /* Network   */
        if (OutputMsgType == TypeTraceBuf2)
        {
           sr_strncpy(  TraceHead->chan,                ChanList[i].comp, TRACE2_CHAN_LEN );
           sr_strncpy( &TraceHead->chan[TRACE_POS_LOC], ChanList[i].loc,  TRACE2_LOC_LEN );
        }
        else /* (OutputMsgType == TypeTraceBuf) */
           sr_strncpy( TraceHead->chan, ChanList[i].comp, TRACE_CHAN_LEN );
        
        TraceHead->pinno = ChanList[i].pin;                           /* Pin num   */



       /* Transfer samples from DAQ buffer to tracebuf buffer
        *****************************************************/
        tracePtr = &TraceDat[0];
        atodPtr  = &AtodBufferWrite[i];

        for ( j = 0 ; j < AtodNumSamples ; j++ )
        {
            *tracePtr = *atodPtr;
            
            if (SummaryMin[i] > *tracePtr)
               SummaryMin[i] = *tracePtr;
            else if (SummaryMax[i] < *tracePtr)
               SummaryMax[i] = *tracePtr;
            
            tracePtr++;
            atodPtr += AtodNumChannels;  /* Demux as we go */
        }


        if ( Debug >= 5 )
        {
            logit( "", "  S=%s C=%s N=%s L=%s pin=%d, ver=%c%c\n",
                  TraceHead->sta,
                  TraceHead->chan,
                  TraceHead->net,
                 &TraceHead->chan[TRACE_POS_LOC],
                  TraceHead->pinno,
                  TraceHead->chan[TRACE_POS_VER],
                  TraceHead->chan[TRACE_POS_VER+1]
                 );
        }

        
       /* Send tracebuf message to the transport ring
        *********************************************/
        rc = tport_putmsg( &Region, &Logo, TraceBufSize, TraceBuffer );
              
        if ( rc == PUT_TOOBIG )
            logit( "e", "srparxchewsend: Trace message for channel %d too big\n", i );
              
        if ( rc == PUT_NOTRACK )
            logit( "e", "srparxchewsend: Tracking error while sending channel %d\n", i );


      } /* end for i < AtodNumChannels */


    
   /* Log summary information periodically
    **************************************/
    SummaryCount++;
    if ( SummaryCount >= SummaryReportInterval && SummaryReportInterval > 0 )
    {
       if ( Debug >= 1 )
       {
          logit( "", "Summary Info:\n    STA CMP NET LOC  min val               max val\n" );
          for ( i = 0 ; i < AtodNumChannels ; i++ )
             logit( "", "  %5s %3s %3s %2s 0x%08lX (%8ld) 0x%08lX (%8ld)\n",
                    ChanList[i].sta,
                    ChanList[i].comp,
                    ChanList[i].net,
                    ChanList[i].loc,
                    SummaryMin[i], SummaryMin[i],
                    SummaryMax[i], SummaryMax[i]
                    );
          
       } /* end if Debug >= 1 */


      /* Reset summary info for next interval
       **************************************/
       SummaryCount = 0;
       for ( i = 0 ; i < AtodNumChannels ; i++ )
       {
          SummaryMin[i] = 0x00FFFFFFL;
          SummaryMax[i] = 0x00000000L;
       }

    } /* end if SummaryCount */



   /* Update current point count to start of next acquired data buffer
    ******************************************************************/
    AtodCurrentPtWrite += AtodNumSamples;

    
    if ( Debug >= 6 )
        logit( "" , "Leaving  sr_send_traces with new current pt Fill = %d, Write = %d\n",
               AtodCurrentPtFill, AtodCurrentPtWrite );
    return;
}

/******************************************************************************
 *  Function: sr_ts_set_tracebuf_time                                         *
 *  Purpose:  Set the trace start and end times in seconds since 1970.  This  *
 *            value is the same for all the channels in the current buffer.   *
 *            The computation is slightly different, depending on what data   *
 *            is available.  The best is using the GPS counter info from the  *
 *            the PAR8CH on board counter.  Next best is using GPS info from  *
 *            the PC counter info kept in the PPS data area of the PARGPS.    *
 *            If no GPS is available, then just compute what the expected     *
 *            time would be assuming the sampling is perfectly stable and     *
 *            accurate.                                                       *
 ******************************************************************************/
void sr_ts_set_tracebuf_time( void )
{
    int        setTime, setEndTime, tsCurrLow, tsCurrHigh, tsEndLow, tsEndHigh;
    int        i, thisMethod, timesOk, ok1, ok2, iTS;
    long       atodEndPt;
    double     newTime, expectedStart, atodEndTime, newEndTime, sampleTime;
    double     correctedTime, computedEndTime, computedSampRate;
    static int nLowSat = 0;
    static int gapTrailer = 0;
    
    if ( Debug >= 6 )  logit( "" , "Starting sr_ts_set_tracebuf_time, UseTimeMethod = %d\n", UseTimeMethod );

    iTS        = MAX_TS-2;  // This should be the timestamp between the start + end pts
    setTime    = 0;
    setEndTime = 0;
    atodEndPt  = AtodCurrentPtWrite + (AtodNumSamples-1);

   /* Determine which time stamps to use for the start and end points */

    if ( Debug >= 6 )
    {
        logit( "", "  Ts# Valid  Pps   Samp     CountAtP             CountAtD            PctimeAtP             PctimeAtD        YmdSrc  HmsSrc   SecSince1970\n");

        for ( i = 0 ; i < MAX_TS ; i++ )
        {
            logit( "", "  %d   0x%X  %5u  %5ld   0x%08lX %08lX  0x%08lX %08lX  0x%08lX %08lX   0x%08lX %08lX   %2d    %2d     %lf\n",
                   i, TimeStamp[i].Valid, TimeStamp[i].PpsEvent, TimeStamp[i].Sample, 
                   TimeStamp[i].CountAtPps.u.HighPart,     TimeStamp[i].CountAtPps.u.LowPart,
                   TimeStamp[i].CountAtDready.u.HighPart,  TimeStamp[i].CountAtDready.u.LowPart,
                   TimeStamp[i].PctimeAtPps.u.HighPart,    TimeStamp[i].PctimeAtPps.u.LowPart,
                   TimeStamp[i].PctimeAtDready.u.HighPart, TimeStamp[i].PctimeAtDready.u.LowPart,
                   TimeStamp[i].YmdSource, TimeStamp[i].HmsSource, TimeStamp[i].SecSince1970 );
        }
    }

    ok1 = sr_ts_select( AtodCurrentPtWrite, TsValid, &tsCurrLow, &tsCurrHigh );
    ok2 = sr_ts_select( atodEndPt,          TsValid, &tsEndLow,  &tsEndHigh );
    if (ok1 && ok2)
            timesOk = 1;
    else
            timesOk = 0;
    

    if ( Debug >= 6 )
    {
        logit( "", "CurrPtW %ld, TS low %d, TS hi %d\n", AtodCurrentPtWrite, tsCurrLow, tsCurrHigh );
        logit( "", " EndPt  %ld, TS low %d, TS hi %d\n", atodEndPt,          tsEndLow,  tsEndHigh );
    }
    

    
   /* Use PAR8CH 800 ns On Board Counter
    ************************************/
    if ( timesOk && UseTimeMethod == TIME_METHOD_OBC )
    {
        if ( ParGpsTsComputeTimeObc( &TimeStamp[tsCurrLow], &TimeStamp[tsCurrHigh],
                                     BestObcCountsPerSample,
                                     TsValid, AtodCurrentPtWrite, &newTime ) )
        {        
            AtodStartTime = newTime;
            setTime       = 1;
            thisMethod    = TIME_METHOD_OBC;
            if ( Debug >= 3 )
                logit( "t", "srparxchewsend: Buf start time (obc) = %lf, Point %ld, GPS %d satellites\n",
                       AtodStartTime, AtodCurrentPtWrite, TimeStamp[iTS].NumSat );
        }
        if ( ParGpsTsComputeTimeObc( &TimeStamp[tsEndLow], &TimeStamp[tsEndHigh],
                                     BestObcCountsPerSample,
                                     TsValid, atodEndPt, &newEndTime ) )
        {        
            atodEndTime = newEndTime;
            setEndTime  = 1;
            if ( Debug >= 4 )
                logit( "t", "srparxchewsend: Buf end time   (obc) = %lf, Point %ld\n",
                       atodEndTime, atodEndPt );
        }
   } /* end if UseTimeMethod == TIME_METHOD_OBC */


   /* Use PC counter from PARGPS PPS area
    *************************************/
    else if ( timesOk && UseTimeMethod == TIME_METHOD_GPS )
    {
        if ( ParGpsTsComputeTimePps( &TimeStamp[tsCurrLow], &TimeStamp[tsCurrHigh],
                                     BestCountsBetweenPps,
                                     TsValid, AtodCurrentPtWrite, &newTime ) )
        {
            AtodStartTime = newTime;
            setTime       = 1;
            thisMethod    = TIME_METHOD_GPS;
            if ( Debug >= 3 )
                logit( "t", "srparxchewsend: Buf start time (pps) = %lf, Point %ld, GPS %d satellites\n",
                       AtodStartTime, AtodCurrentPtWrite, TimeStamp[iTS].NumSat );
        }
        if ( ParGpsTsComputeTimePps( &TimeStamp[tsEndLow], &TimeStamp[tsEndHigh],
                                     BestCountsBetweenPps,
                                     TsValid, atodEndPt, &newEndTime ) )
        {
            atodEndTime = newEndTime;
            setEndTime  = 1;
            if ( Debug >= 4 )
                logit( "t", "srparxchewsend: Buf end time   (pps) = %lf, Point %ld\n",
                       atodEndTime, atodEndPt );
        }
    } /* end else if UseTimeMethod == TIME_METHOD_GPS */


   
   /* Use PC system time counter from PARGPS PPS area
    *************************************************/
    else if ( timesOk && UseTimeMethod == TIME_METHOD_PCT )
    {
        if ( Debug >= 4 )
        {
            logit("","TS%d %ld, AtodCurrentPtWrite %ld, TS%d %ld\n",
                  tsCurrLow, TimeStamp[tsCurrLow].Sample,
                  AtodCurrentPtWrite,
                  tsCurrHigh, TimeStamp[tsCurrHigh].Sample );
        
            logit("","TS%d %ld, atodEndPt %ld, TS%d %ld\n",
                  tsEndLow, TimeStamp[tsEndLow].Sample,
                  atodEndPt, 
                  tsEndHigh, TimeStamp[tsEndHigh].Sample );
        }
        
        if (AtodCurrentPtWrite < TimeStamp[tsCurrLow].Sample)
                logit("t","WARNING: current pt %ld is out of range below TS%d %ld\n",
                      AtodCurrentPtWrite, tsCurrLow, TimeStamp[tsCurrLow].Sample);
        if (AtodCurrentPtWrite > TimeStamp[tsCurrHigh].Sample)
                logit("t","WARNING: current pt %ld is out of range above TS%d %ld\n",
                      AtodCurrentPtWrite, tsCurrHigh, TimeStamp[tsCurrHigh].Sample);
        

        if (atodEndPt < TimeStamp[tsEndLow].Sample)
                logit("t","WARNING: thisend pt %ld is out of range below TS%d %ld\n",
                      atodEndPt, tsEndLow, TimeStamp[tsEndLow].Sample);
        if (atodEndPt > TimeStamp[tsEndHigh].Sample)
                logit("t","WARNING: thisend pt %ld is out of range above TS%d %ld\n",
                      atodEndPt, tsEndHigh, TimeStamp[tsEndHigh].Sample);
        

        if ( ParGpsTsComputeTimePctime( &TimeStamp[tsCurrLow], &TimeStamp[tsCurrHigh],
                                        BestCountsPerSecond,
                                        TsValid, AtodCurrentPtWrite, &newTime ) )
        {
            AtodStartTime = newTime;
            setTime       = 1;
            thisMethod    = TIME_METHOD_PCT;
            if ( Debug >= 3 )
                logit( "t", "srparxchewsend: Buf start time (pct) = %lf, Point %ld, GPS %d satellites\n",
                       AtodStartTime, AtodCurrentPtWrite, TimeStamp[iTS].NumSat );
        }
        else
        {
            if ( Debug >= 6 )
                logit( "", "Failed to compute PCT time\n" );
        }
        if ( ParGpsTsComputeTimePctime( &TimeStamp[tsEndLow], &TimeStamp[tsEndHigh],
                                        BestCountsPerSecond,
                                        TsValid, atodEndPt, &newEndTime ) )
        {
            atodEndTime = newEndTime;
            setEndTime  = 1;
            if ( Debug >= 4 )
                logit( "t", "srparxchewsend: Buf end time   (pct) = %lf, Point %ld\n",
                       atodEndTime, atodEndPt );
        }

        if ( Debug >= 5 )
        {                
            logit("","Pct last->this (1sps) %lf, this len %lf, Last end %lf\n",
                  (AtodStartTime-AtodEndTimeLast), (atodEndTime-AtodStartTime), AtodEndTimeLast );
        }
        
    } /* end else if UseTimeMethod == TIME_METHOD_PCT */


   
   /* Use sampling rate and previous time
    *************************************/
    else if ( timesOk && UseTimeMethod == TIME_METHOD_SPS )
    {
        if ( ParGpsTsComputeTimeSps( &TimeStamp[tsCurrLow], &TimeStamp[tsCurrHigh],
                                     AtodSps, TsValid, 
                                     AtodCurrentPtWrite, &newTime ) )
        {
            AtodStartTime = newTime;
            setTime       = 1;
            thisMethod    = TIME_METHOD_SPS;
            if ( Debug >= 3 )
                logit( "t", "srparxchewsend: Buf start time (sps) = %lf, Point %ld, GPS %d satellites\n",
                       AtodStartTime, AtodCurrentPtWrite, TimeStamp[iTS].NumSat );
        }
        if ( ParGpsTsComputeTimeSps( &TimeStamp[tsEndLow], &TimeStamp[tsEndHigh],
                                     AtodSps, TsValid, 
                                     atodEndPt, &newEndTime ) )
        {
            atodEndTime = newEndTime;
            setEndTime  = 1;
            if ( Debug >= 4 )
                logit( "t", "srparxchewsend: Buf end time   (sps) = %lf, Point %ld\n",
                       atodEndTime, atodEndPt );
        }
    } /* end else UseTimeMethod == TIME_METHOD_SPS */



   /* Compute based on global data sampling rate and previous time
    **************************************************************/
    if (!setTime) /* All methods failed or UseTimeMethod == TIME_METHOD_NONE */
    {
        AtodStartTime = AtodEndTimeLast + 1 / AtodSps;
        thisMethod    = TIME_METHOD_NONE;
        if ( Debug >= 3 )
            logit( "t", "srparxchewsend: Buf start time (default) = %lf, Point %ld\n",
                   AtodStartTime, AtodCurrentPtWrite );
    }
    if (!setEndTime) /* All methods failed or UseTimeMethod == TIME_METHOD_NONE */
    {
        atodEndTime = AtodStartTime + (AtodNumSamples-1) / AtodSps;
        if ( Debug >= 4 )
            logit( "t", "srparxchewsend: Buf end time   (default) = %lf, Point %ld\n",
                   atodEndTime, atodEndPt );
    }

    
    if ( Debug >= 5 )
    {                
        logit("","New Last end %lf, this start %lf, this end %lf\n",
              AtodEndTimeLast, AtodStartTime, atodEndTime );
        logit("","New last->this (1sps) %lf, this len %lf\n",
              (AtodStartTime-AtodEndTimeLast), (atodEndTime-AtodStartTime) );
    }

    if ( Debug >= 5 )
         logit( "", "Method used for computing time.  Last = %d, current = %d, Request = %d\n",
                LastTimeMethod, thisMethod, UseTimeMethod );



   /* Check for gaps or overlaps
    ****************************/
    timesOk       = 1;
    sampleTime    = (atodEndTime - AtodStartTime) / (double)(AtodNumSamples-1);
    expectedStart = AtodEndTimeLast + sampleTime*1.5; /* Allow .5 sample slop */

    if ( gapTrailer > 0 ) /* Print additional debugging info after a gap has occurred */
    {
        if (Debug >= 3)
        {
           logit( "", "Buffer %d after gap\n", MAXGAPTRAIL-gapTrailer );
           sr_log_gap_details( thisMethod, expectedStart, atodEndTime );
        }
        gapTrailer--;
    }
    
    if ( AtodStartTime < AtodEndTimeLast )
    {
        logit( "et", "srparxchewsend: Gap check: Overlap detected\n     (start %lf < last end %lf)\n",
               AtodStartTime, AtodEndTimeLast );
        sr_log_gap_details( thisMethod, expectedStart, atodEndTime );
        timesOk       = 0;
        correctedTime = AtodEndTimeLast;
        gapTrailer    = MAXGAPTRAIL;
    }

    if ( AtodStartTime > expectedStart )
    {
        logit( "et", "srparxchewsend: Gap check: Gap detected\n     (found %lf start > expected start %lf = \n      last end %lf + sample %lf)\n",
               AtodStartTime, expectedStart, AtodEndTimeLast, sampleTime );
        sr_log_gap_details( thisMethod, expectedStart, atodEndTime );
        timesOk       = 0;
        correctedTime = expectedStart;
        gapTrailer    = MAXGAPTRAIL;
    }


    /* If times are not consistent, fix them.  No gaps or overlaps are allowed. */

    if ( !timesOk )
    {
        if (thisMethod == LastTimeMethod  &&  LastTimeMethod != TIME_METHOD_NONE)
        {
            if (Debug >= 3 )
                logit("","Time corrected from %lf to %lf\n",
                      AtodStartTime, correctedTime );
            AtodStartTime = correctedTime;
        }
        else
        {
            if (Debug >= 4 ) logit("","No time correction if we are just starting out ....\n" );
        }

    }



   /* Set the start time and end time
    *********************************/
    if (Debug >= 5)
    {
        computedEndTime = AtodStartTime + (AtodNumSamples - 1)*sampleTime;
        logit( "", "End time: Interpolated = %lf, Computed = %lf\n",
                   atodEndTime, computedEndTime );
    }
                   
    TraceHead->starttime = AtodStartTime;
    TraceHead->endtime   = atodEndTime;      /* Use interpolated end time */


   /* Update sample rate for header
    *******************************/
    computedSampRate = (AtodNumSamples-1) / (TraceHead->endtime - TraceHead->starttime);
    if (Debug >= 5)
            logit( "", "Computed sample rate = %lf (std = %lf), keeping standard\n", computedSampRate, AtodSps );
//FIX - Do we change sample rate or not ?  Currently we do not
//      Wave_Viewer says NO, Decimate says YES?
//    TraceHead->samprate = computedSampRate;

    

   /* Update the quality flag
    *************************/
    if ( TimeStamp[iTS].NumSat < 3                          ||  /* not enough sats */
         (TimeStamp[iTS].YmdSource != TIME_SOURCE_GPS &&        /* YMD not GPS/OBC */
          TimeStamp[iTS].YmdSource != TIME_SOURCE_OBC)      ||
         (TimeStamp[iTS].HmsSource != TIME_SOURCE_GPS &&        /* HMS not GPS/OBC */
          TimeStamp[iTS].HmsSource != TIME_SOURCE_OBC)
                                                          )
    {
        TraceHead->quality[0] |= TIME_TAG_QUESTIONABLE;
        if ( Debug >= 5 )  logit( "" , "Setting time tag questionable (Nsat = %d)\n", TimeStamp[iTS].NumSat );
    }
    else
        TraceHead->quality[0] = QUALITY_OK;


    /* Prepare for next pass */

    LastTimeMethod  = thisMethod;
    AtodEndTimeLast = TraceHead->endtime;

    
    if ( Debug >= 6 )  logit( "" , "Leaving  sr_ts_set_tracebuf_time\n" );
    return;
}

/******************************************************************************
 *  Function: sr_ts_select                                                    *
 *  Purpose:  To select which entries in the global list of available         *
 *            timestamp structures bound the target point.  These two can     *
 *            then be used to interpolate the value of time at the target.    *
 *            This function assumes that the timestamp list is filled in with *
 *            valid entries starting from the end, and that the sample number *
 *            for valid timestamps always increases from beginning to end.    *
 ******************************************************************************/
int sr_ts_select( long target, int valid, int *lowerTs, int *upperTs )
{
    int i, ret, tslow, tshigh, firstValid;

    if ( Debug >= 6 )  logit( "" , "Starting sr_ts_select\n" );

   /* Locate first valid timestamp
    ******************************/
    firstValid = -1;
    for ( i = 0 ; i < MAX_TS ; i++ )
    {
        if ( (TimeStamp[i].Valid & valid) == valid )
        {
            firstValid = i;
            if ( Debug >= 6 )
                 logit( "", "First valid timestamp at %d\n", firstValid );
            break;
        }
    }



    /* Error check validity
     **********************/
    if ( firstValid == -1 )
    {
        if ( Debug >= 6 )  logit( "", "No valid timestamps yet\n");
        tslow  = -1;
        tshigh = -1;
        ret    = 0;
    }


   /* Error check target too small
    ******************************/ 
    else if ( target < TimeStamp[firstValid].Sample )
    {
        if ( Debug >= 6 )  logit( "", "Target %ld out of range below TS%d %ld\n",
                 target, firstValid, TimeStamp[firstValid].Sample );
        tslow  = -1;
        tshigh = -1;
        ret    = 0;
    }


   /* Error check target too large
    ******************************/ 
    else if ( target > TimeStamp[MAX_TS-1].Sample )
    {
        if ( Debug >= 6 )
           logit( "", "Target %ld out of range above TS%d %ld\n",
                  target, (MAX_TS-1), TimeStamp[MAX_TS-1].Sample );
        tslow  = -1;
        tshigh = -1;
        ret    = 0;
    }


   /* Also check last point in last interval
    ****************************************/
    else if ( target == TimeStamp[MAX_TS-1].Sample )
    {
        if ( Debug >= 6 )
           logit( "", "Target %ld squeaked in as last point of TS%d of %ld\n",
                  target, MAX_TS-1, TimeStamp[MAX_TS-1].Sample);
        tslow  = MAX_TS-2;
        tshigh = MAX_TS-1;
        ret    = 1;
    }


   /* Now check each interval in the list
    *************************************/
    else
    {
        for ( i = 0 ; i < MAX_TS-1 ; i++ )
        {
            if ( Debug >= 7 )
               logit( "", "Checking for %ld between positions %d and %d\n", target, i, i+1 );
            if ( (target >= TimeStamp[i].Sample) && (target < TimeStamp[i+1].Sample) )
            {
               if ( Debug >= 6 )
                  logit( "", "Target %ld between TS%d of %ld and TS%d of %ld\n",
                          target, i, TimeStamp[i].Sample, (i+1), TimeStamp[i+1].Sample);
               tslow  = i;
               tshigh = i+1;
               ret    = 1;
               break;
            }
        } /* end for i */

    } /* end else */



   /* Fill return variables
    ***********************/
    if (lowerTs)  *lowerTs = tslow;
    if (upperTs)  *upperTs = tshigh;
    
   if ( Debug >= 6 )  logit( "" , "tslow  %d, *lowerTs %d\n", tslow, *lowerTs );
   if ( Debug >= 6 )  logit( "" , "tshigh %d, *upperTs %d\n", tshigh, *upperTs );
    
   if ( Debug >= 6 )  logit( "" , "Leaving  sr_ts_select\n" );
   return( ret );
}



/******************************************************************************
 *  Function: sr_atod_stop                                                    *
 *  Purpose:  Stop the PARxCH DAQ and PARGPS timing module from acquiring.    *
 ******************************************************************************/
void sr_atod_stop( void )
{
    if ( Debug >= 6 )  logit( "" , "Starting sr_atod_stop\n" );


   /* Stop ParXch DAQ
    *****************/
    if ( ParXchHandle != BAD_DEVHANDLE )
        ParXchStop( ParXchHandle );



   /* If using ParGps, stop it
    **************************/
    if ( UsingGps && ParGpsHandle != BAD_DEVHANDLE  )
        ParGpsStop( ParGpsHandle );



   if ( Debug >= 6 )  logit( "" , "Leaving  sr_atod_stop\n" );
   return;
}


/******************************************************************************
 *  Function: sr_atod_close                                                   *
 *  Purpose:  Perform a clean and orderly shutdown of the PARxCH DAQ and      *
 *            optional PARGPS timing unit.                                    *
 ******************************************************************************/
void sr_atod_close( void )
{
   if ( Debug >= 6 )  logit( "" , "Starting sr_atod_close\n" );
        
   /* Stop ParXch DAQ and PARGPS
    ****************************/
    sr_atod_stop();



   /* If using ParGps, disconnect it from ParXch driver, and close it
    *****************************************************************/
    if ( UsingGps                      &&
         ParXchHandle != BAD_DEVHANDLE &&
         ParGpsHandle != BAD_DEVHANDLE )
    {
        ParXchReleaseGps( ParXchHandle );
        ParGpsClose( ParGpsHandle );
        ParGpsHandle = BAD_DEVHANDLE;
    }



   /* Close ParXch DAQ
    ******************/
    if ( ParXchHandle != BAD_DEVHANDLE )
    {
        ParXchClose( ParXchHandle );
        ParXchHandle = BAD_DEVHANDLE;
    }


   if ( Debug >= 6 )  logit( "" , "Leaving  sr_atod_close\n" );
   return;
}

/******************************************************************************
 *  Function: sr_atod_overflow_recovery                                       *
 *  Purpose:  After an overflow, future data would be distorted.  So, we      *
 *            throw away any existing data in the FIFO then stop and restart  *
 *            the DAQ.  This ensures that future data is good.                *
 *            Send some earthworm heartbeats since this error will make us    *
 *            miss at least one pass through the main loop.                   *
 ******************************************************************************/
void sr_atod_overflow_recovery( void )
{
   if ( Debug >= 3 )  logit( "" , "Starting sr_atod_overflow_recovery\n" );

   sr_atod_stop();     sr_heartbeat();
   sr_atod_start();    sr_heartbeat();

   if ( Debug >= 3 )  logit( "" , "Leaving  sr_atod_overflow_recovery\n" );
   return;
}

/******************************************************************************
 *  Function: sr_check_terminate()                                            *
 *  Purpose:  Check to see if this earthworm ring is requesting a termination *
 *            and exit if it is.                                              *
 ******************************************************************************/
void sr_check_terminate( void )
{
    if ( Debug >= 7 )  logit( "" , "Starting sr_check_terminate\n" );


   /* Check for EW terminate request
    ********************************/
    if ( tport_getflag( &Region ) == TERMINATE ||
         tport_getflag( &Region ) == MyPid     )
    {    
        sr_terminate();
    }

    if ( Debug >= 7 )  logit( "" , "Leaving  sr_check_terminate\n" );
    return;
}


/******************************************************************************
 *  Function: sr_terminate()                                                  *
 *  Purpose:  Do the earthworm and DAQ exit tasks.  These include detaching   *
 *            from the earthworm ring and stopping and closing the DAQ.       *
 ******************************************************************************/
void sr_terminate( void )
{
    if ( Debug >= 6 )  logit( "" , "Starting sr_terminate\n" );

   /* Clean exit for earthworm
    **************************/
    if ( Region.addr != NULL )
        tport_detach( &Region );

   /* Clean exit for DAQ
    ********************/
    sr_atod_close();

   /* Notify user
    *************/
    logit( "t", "srparxchewsend: Termination requested; exiting!\n" );
    fflush( stdout );

   /* Bye bye
    *********/
    exit( 0 );
}


/******************************************************************************
 *  Function: sr_read_config                                                  *
 *  Purpose:  Process command file(s) using kom.c functions;                  *
 *            exits if any errors are encountered.                            *
 ******************************************************************************/

#define CMD_MODULEID         0
#define CMD_RINGNAME         1
#define CMD_LOGFILE          2
#define CMD_HEARTBEAT        3
#define CMD_ATODDRIVER       4
#define CMD_ATODXCHMODEL     5
#define CMD_ATODPORTMODE     6
#define CMD_ATODSPS          7
#define CMD_USINGGPS         8
#define CMD_GPSDRIVER        9
#define CMD_GPSSERIALPORT   10
#define MAX_CMD             11

#define OPT_SUMMARYINTERVAL  0
#define OPT_LOCKINTERVAL     1
#define OPT_LOCKLIMIT        2
#define OPT_OUTPUTMSGTYPE    3
#define OPT_CHANNEL          4
#define OPT_CHANNELSCNL      5
#define OPT_GPSMODEL         6
#define MAX_OPT              7

void sr_read_config( char *configfile )
{
   char     init[MAX_CMD];    /* init flags, one byte for each required command */
   char    *cmdstr[MAX_CMD];  /* pointer to required command string to match             */
   char    *optstr[MAX_OPT];  /* pointer to optional command string to match             */
   int      nmiss;            /* number of required commands that were missed   */
   char    *com;
   char    *str;
   int      nfiles;
   int      success;
   int      i;
   int      chan;
   int      rc;
   int      is_scnl;
   int      firstwarn;

   if ( Debug >= 6 )  logit( "" , "Starting sr_read_config\n" );


  /* Set optional command defaults
   *******************************/
   SummaryReportInterval = 0;
   GpsLockReportInterval = 0;
   GpsLockBadLimit       = 1;
   sr_strncpy( GpsModelName, "PARGPS", MAX_STR );

   if ( TRACE2_OK )
      OutputMsgType = -2; /* Set to TypeTraceBuf2 when defined */
   else
      OutputMsgType = -1; /* Set to TypeTraceBuf  when defined */

   for ( i = 0 ; i < MAX_CHAN ; i++ )
   {
       sprintf( ChanList[i].sta, "CH%02d", i );
       sprintf( ChanList[i].comp, "xxx" );
       sprintf( ChanList[i].net, "SR" );
       sprintf( ChanList[i].loc, LOC_NULL_STRING );
       ChanList[i].ver[0] = TRACE2_VERSION0;
       ChanList[i].ver[1] = TRACE2_VERSION1;
       ChanList[i].pin = i;
   }

   firstwarn = 1;


   /* These are the command strings to match
    ****************************************/
    cmdstr[CMD_MODULEID]        = "MyModuleId";
    cmdstr[CMD_RINGNAME]        = "RingName";
    cmdstr[CMD_LOGFILE]         = "LogFile";
    cmdstr[CMD_HEARTBEAT]       = "HeartBeatInterval";
    cmdstr[CMD_ATODDRIVER]      = "AtodDriverName";
    cmdstr[CMD_ATODXCHMODEL]    = "AtodModelName";
    cmdstr[CMD_ATODPORTMODE]    = "PortMode";
    cmdstr[CMD_ATODSPS]         = "SamplingRate";
    cmdstr[CMD_USINGGPS]        = "GpsEnable";
    cmdstr[CMD_GPSDRIVER]       = "GpsDriverName";
    cmdstr[CMD_GPSSERIALPORT]   = "GpsSerialPort";

    optstr[OPT_SUMMARYINTERVAL] = "SummaryInterval";
    optstr[OPT_LOCKINTERVAL]    = "GpsReportInterval";
    optstr[OPT_LOCKLIMIT]       = "GpsBadLimit";
    optstr[OPT_GPSMODEL]        = "GpsModelName";
    optstr[OPT_OUTPUTMSGTYPE]   = "OutputMsgType";
    optstr[OPT_CHANNEL]         = "EwChannel";
    optstr[OPT_CHANNELSCNL]     = "EwChannelScnl";

   

   /* Zero out one init flag for each required command 
    **************************************************/   
    for ( i = 0 ; i < MAX_CMD ; i++ )
        init[i] = 0;


   /* Open the main configuration file 
    **********************************/
    nfiles = k_open( configfile ); 
    if ( nfiles == 0 )
    {
        logit( "e",
                "srparxchewsend: Error opening command file <%s>; exiting!\n", 
                 configfile );
        exit( -1 );
    }


   /* Process all command files
    ***************************/
    while ( nfiles > 0 )   /* While there are command files open */
    {
        while ( k_rd() )        /* Read next line from active file  */
        {  
            com = k_str();         /* Get the first token from line */

           /* Ignore blank lines & comments
            *******************************/
            if ( !com )           continue;  /* Ignore blank lines            */
            if ( com[0] == '#' )  continue;  /* Ignore comment lines          */
            if ( com[0] == ';' )  continue;  /* Ignore .ini style comment too */

            
           /* Open a nested configuration file 
            **********************************/
            if ( com[0] == '@' )
            {
               success = nfiles+1;
               nfiles  = k_open( &com[1] );
               if ( nfiles != success )
               {
                  logit( "e", 
                          "srparxchewsend: Error opening command file <%s>; exiting!\n",
                           &com[1] );
                  exit( -1 );
               }
               continue;
            }

           /* Process anything else as a command 
            ************************************/
  /*=*/     if ( k_its( "=" ) )
            {
                ; /* skip = so .ini style files can be used */
            }
/*Dbg*/     if ( k_its( "Debug" ) )
            {
                Debug = k_int();
            }
  /*0*/     else if ( k_its( cmdstr[CMD_MODULEID] ) )
            {
                str = k_str();
                if (str) sr_strncpy( MyModName, str, MAX_MOD_STR );
                init[CMD_MODULEID] = 1;
                
                if ( Debug >= 2 )  logit( "", "Found <%s> value %s\n",cmdstr[CMD_MODULEID], MyModName );
            }
  /*1*/     else if ( k_its( cmdstr[CMD_RINGNAME] ) )
            {
                str = k_str();
                if (str) sr_strncpy( RingName, str, MAX_RING_STR );
                init[CMD_RINGNAME] = 1;
                
                if ( Debug >= 2 )  logit( "", "Found <%s> value %s\n",cmdstr[CMD_RINGNAME], RingName );
            }
  /*2*/     else if ( k_its( cmdstr[CMD_LOGFILE] ) )
            {
                LogSwitch = k_int();
                init[CMD_LOGFILE] = 1;
                
                if ( Debug >= 2 )  logit( "", "Found <%s> value %d\n",cmdstr[CMD_LOGFILE], LogSwitch );
            }
  /*3*/     else if ( k_its( cmdstr[CMD_HEARTBEAT] ) )
            {
                HeartBeatInterval = k_long();
                init[CMD_HEARTBEAT] = 1;
                
                if ( Debug >= 2 )  logit( "", "Found <%s> value %ld\n",cmdstr[CMD_HEARTBEAT], HeartBeatInterval );
            }
  /*4*/     else if ( k_its( cmdstr[CMD_ATODDRIVER] ) )
            {
                str = k_str();
                if (str) sr_strncpy( AtodDriverName, str, MAX_STR );
                init[CMD_ATODDRIVER] = 1;
                
                if ( Debug >= 2 )  logit( "", "Found <%s> value %s\n",cmdstr[CMD_ATODDRIVER], AtodDriverName );
            }
  /*5*/     else if ( k_its( cmdstr[CMD_ATODXCHMODEL] ) )
            {
                str = k_str();
                sr_strncpy( AtodXchModelName, str, MAX_STR );
                if ( strcmp( AtodXchModelName, "PAR1CH" ) == 0  ||
                     strcmp( AtodXchModelName, "PAR4CH" ) == 0  ||
                     strcmp( AtodXchModelName, "PAR8CH" ) == 0 )
                    init[CMD_ATODXCHMODEL] = 1;
                else
                    init[CMD_ATODXCHMODEL] = 0;
                
                if ( Debug >= 2 )  logit( "", "Found <%s> value %s\n",
                                         cmdstr[CMD_ATODXCHMODEL],
                                         AtodXchModelName );
            }
  /*6*/     else if ( k_its( cmdstr[CMD_ATODPORTMODE] ) )
            {
                str = k_str();
                init[CMD_ATODPORTMODE] = 1;
                sr_strncpy( AtodPortModeName, str, MAX_STR );
                if ( strcmp( AtodPortModeName, "EPP" )     == 0  ||
                     strcmp( AtodPortModeName, "BPP" )     == 0  ||
                     strcmp( AtodPortModeName, "ECP/EPP" ) == 0  ||
                     strcmp( AtodPortModeName, "ECP/BPP" ) == 0 )
                    init[CMD_ATODPORTMODE] = 1;
                else
                    init[CMD_ATODPORTMODE] = 0;
                
                if ( Debug >= 2 )  logit( "", "Found <%s> value %s\n",cmdstr[CMD_ATODPORTMODE], AtodPortModeName );
            }
  /*7*/     else if ( k_its( cmdstr[CMD_ATODSPS] ) )
            {
                AtodRequestedSps = k_val();
                init[CMD_ATODSPS] = 1;
                
                if ( Debug >= 2 )  logit( "", "Found <%s> value %lf\n",cmdstr[CMD_ATODSPS], AtodRequestedSps );
            }
  /*8*/     else if ( k_its( cmdstr[CMD_USINGGPS] ) )
            {
                str = k_str();
                init[CMD_USINGGPS] = 1;
                if ( strcmp( "ON", str ) == 0 )
                        UsingGps = 1;
                else if ( strcmp( "OFF", str ) == 0 )
                        UsingGps = 0;
                else
                    init[CMD_USINGGPS] = 0;

                if ( Debug >= 2 )  logit( "", "Found <%s> value %d\n",cmdstr[CMD_USINGGPS], UsingGps );
            }
  /*9*/     else if ( k_its( cmdstr[CMD_GPSDRIVER] ) )
            {
                str = k_str();
                if (str) sr_strncpy( GpsDriverName, str, MAX_STR );
                init[CMD_GPSDRIVER] = 1;

                if ( Debug >= 2 )  logit( "", "Found <%s> value %s\n",cmdstr[CMD_GPSDRIVER], GpsDriverName );
            }
 /*10*/     else if ( k_its( cmdstr[CMD_GPSSERIALPORT] ) )
            {
                GpsSerialPort = k_int();
                init[CMD_GPSSERIALPORT] = 1;

                if ( Debug >= 2 )  logit( "", "Found <%s> value %d\n",cmdstr[CMD_GPSSERIALPORT], GpsSerialPort );
            }

 
          /* Get optional commands
           ***********************/
            else if ( k_its( optstr[OPT_SUMMARYINTERVAL] ) )
            {
                SummaryReportInterval = k_long();
                
                if ( Debug >= 2 )  logit( "", "Found <%s> value %ld\n",optstr[OPT_SUMMARYINTERVAL], SummaryReportInterval );
            }
            else if ( k_its( optstr[OPT_LOCKINTERVAL] ) )
            {
                GpsLockReportInterval = k_long();
                
                if ( Debug >= 2 )  logit( "", "Found <%s> value %ld\n",optstr[OPT_LOCKINTERVAL], GpsLockReportInterval );
            }
            else if ( k_its( optstr[OPT_LOCKLIMIT] ) )
            {
                GpsLockBadLimit = k_long();
                
                if ( Debug >= 2 )  logit( "", "Found <%s> value %ld\n",optstr[OPT_LOCKLIMIT], GpsLockBadLimit );
            }
            else if ( k_its( optstr[OPT_GPSMODEL] ) )
            {
                str = k_str();
                if ( strcmp( str, "PARGPS" ) == 0  ||
                     strcmp( str, "ONCORE" ) == 0  ||
                     strcmp( str, "PCTIME" ) == 0 )
                    sr_strncpy( GpsModelName, str, MAX_STR );
                else
                    logit( "e", "srparxchewsend: <%s> value %s not allowed, using default of %s instead.\n", 
                            optstr[OPT_GPSMODEL], str, GpsModelName );

                if ( Debug >= 2 )  logit( "", "Found <%s> value %s\n",optstr[OPT_GPSMODEL], GpsModelName );
            }
            else if ( k_its( optstr[OPT_OUTPUTMSGTYPE] ) )
            {
                str = k_str();
                if ( strcmp( "TYPE_TRACEBUF", str ) == 0 )
                    OutputMsgType = -1;      /* Set to TypeTraceBuf when defined */
                else if ( strcmp( "TYPE_TRACEBUF2", str ) == 0 )
                {
                    if ( TRACE2_OK )
                        OutputMsgType = -2;  /* Set to TypeTraceBuf2 when defined */
                    else
                        logit( "e", "srparxchewsend: <%s> TYPE_TRACEBUF2 not allowed in this version, using TYPE_TRACEBUF instead <%s>.\n", 
                                       com, configfile );
                }

                if ( Debug >= 2 )  logit( "", "Found <%s> value %s (%ld)\n",optstr[OPT_OUTPUTMSGTYPE], str, OutputMsgType );
            }


           /* Get the channel list
            **********************/
            else if ( (is_scnl = k_its( optstr[OPT_CHANNELSCNL] ))  ||  
                                 k_its( optstr[OPT_CHANNEL]     )   )
            {

            if ( firstwarn && is_scnl && (OutputMsgType == -1) )
            {
               firstwarn = 0;
               logit( "et", "srparxchewsend: Warning.  OutputMsgType is old style\n      TYPE_TRACEBUF but channel list is new %s.\n", optstr[OPT_CHANNELSCNL] );
            }

            chan = k_int();                         /* Get channel number */
            if ( chan >= MAX_CHAN || chan < 0 )
            {
               logit( "e", "srparxchewsend: Error. Bad channel number (%d) in config file.\n", chan );
               exit( 0 );
            }

            sr_strncpy( ChanList[chan].sta,  k_str(), TRACE_STA_LEN );    /* station   */
            sr_strncpy( ChanList[chan].comp, k_str(), TRACE_CHAN_LEN );   /* component */
            sr_strncpy( ChanList[chan].net,  k_str(), TRACE_NET_LEN );    /* network   */
            if ( is_scnl )
               sr_strncpy( ChanList[chan].loc, k_str(), TRACE2_LOC_LEN ); /* location  */
            ChanList[chan].pin = k_int();                                 /* pin num   */

            rc = k_err();

            if ( rc == -1 )                          /* No more tokens in line     */
               ChanList[chan].pin = chan;            /* Default pin to chan number */

            if ( rc == -2 )                          /* Unreadable pin number      */
            {
               logit( "e", "srparxchewsend: Bad pin number for DAQ channel %d\n", chan );
               exit( 0 );
            }
                if ( Debug >= 2 )
                    logit( "", "Found %s %2d Sta=%s Chan=%s Net=%s Loc=%s pin=%d\n",
                       (is_scnl ? optstr[OPT_CHANNELSCNL] : optstr[OPT_CHANNEL]),
                       chan,
                       ChanList[chan].sta,
                       ChanList[chan].comp,
                       ChanList[chan].net,
                       ChanList[chan].loc,
                       ChanList[chan].pin );
            
         } /* end of if EwChannel or EwChannelScnl */

  

           /* Unknown command
            *****************/ 
            else
            {
                logit( "e", "srparxchewsend: <%s> Unknown command in <%s>.\n", 
                         com, configfile );
                continue;
            }

           /* See if there were any errors processing the command 
            *****************************************************/
            if ( k_err() )
            {
               logit( "e", 
                       "srparxchewsend: Bad <%s> command in <%s>; exiting!\n",
                        com, configfile );
               exit( -1 );
            }

        } /* end while k_rd */

        nfiles = k_close();

   } /* end while nfiles > 0 */

   
  /* After all files are closed, check init flags for missed commands
   ******************************************************************/
   nmiss = 0;
   for ( i = 0 ; i < MAX_CMD ; i++ )
   {
       if ( !init[i] )
           nmiss++;
   }
   if ( nmiss )
   {
       logit( "e", "srparxchewsend: ERROR, no " );
       if ( !init[CMD_MODULEID] )      logit( "e", "<%s> ", cmdstr[CMD_MODULEID] );
       if ( !init[CMD_RINGNAME] )      logit( "e", "<%s> ", cmdstr[CMD_RINGNAME] );
       if ( !init[CMD_LOGFILE] )       logit( "e", "<%s> ", cmdstr[CMD_LOGFILE] );
       if ( !init[CMD_HEARTBEAT] )     logit( "e", "<%s> ", cmdstr[CMD_HEARTBEAT] );
       if ( !init[CMD_ATODDRIVER] )    logit( "e", "<%s> ", cmdstr[CMD_ATODDRIVER] );
       if ( !init[CMD_ATODXCHMODEL] )  logit( "e", "<%s> ", cmdstr[CMD_ATODXCHMODEL] );
       if ( !init[CMD_ATODPORTMODE] )  logit( "e", "<%s> ", cmdstr[CMD_ATODPORTMODE] );
       if ( !init[CMD_ATODSPS] )       logit( "e", "<%s> ", cmdstr[CMD_ATODSPS] );
       if ( !init[CMD_USINGGPS] )      logit( "e", "<%s> ", cmdstr[CMD_USINGGPS] );
       if ( !init[CMD_GPSDRIVER] )     logit( "e", "<%s> ", cmdstr[CMD_GPSDRIVER] );
       if ( !init[CMD_GPSSERIALPORT] ) logit( "e", "<%s> ", cmdstr[CMD_GPSSERIALPORT] );
       logit( "e", "command(s) in <%s>; exiting!\n", configfile );
       exit( -1 );
   }

   if ( Debug >= 6 )  logit( "" , "Leaving  sr_read_config\n" );
   return;
}

/******************************************************************************
 *  Function: sr_lookup_ew                                                    *
 *  Purpose:  Look up important info from earthworm.h tables including        *
 *               RingKey                                                      *
 *               InstId                                                       *
 *               ModId                                                        *
 *               several message types                                        *
 ******************************************************************************/
void sr_lookup_ew( void )
{
   if ( Debug >= 6 )  logit( "" , "Starting sr_lookup_ew\n" );
        
  /* Look up key to shared memory region
   *************************************/
   if ( ( RingKey = GetKey( RingName ) ) == -1 )
   {
       logit( "e",
              "srparxchewsend: Invalid ring name <%s>; exiting!\n", RingName );
       exit( -1 );
   }
   

  /* Look up installation of interest
   **********************************/
   if ( GetLocalInst( &InstId ) != 0 )
   {
      logit( "e", 
             "srparxchewsend: Error getting local installation id; exiting!\n" );
      exit( -1 );
   }


  /* Look up module of interest
   ****************************/
   if ( GetModId( MyModName, &MyModId ) != 0 )
   {
      logit( "e", 
             "srparxchewsend: Invalid module name <%s>; exiting!\n", MyModName );
      exit( -1 );
   }
   

  /* Look up message types of interest
   ***********************************/
   if ( GetType( "TYPE_HEARTBEAT", &TypeHeartBeat ) != 0 )
   {
      logit( "e", 
             "srparxchewsend: Invalid message type <TYPE_HEARTBEAT>; exiting!\n" );
      exit( -1 );
   }

   if ( GetType( "TYPE_ERROR", &TypeError ) != 0 )
   {
      logit( "e", 
             "srparxchewsend: Invalid message type <TYPE_ERROR>; exiting!\n" );
      exit( -1 );
   }

   if ( GetType( "TYPE_TRACEBUF", &TypeTraceBuf ) != 0 )
   {
      logit( "e", 
             "srparxchewsend: Invalid message type <TYPE_TRACEBUF>; exiting!\n" );
      exit( -1 );
   }

   if ( TRACE2_OK )
   {
      if ( GetType( "TYPE_TRACEBUF2", &TypeTraceBuf2 ) != 0 )
      {
         logit( "e", 
                "srparxchewsend: Invalid message type <TYPE_TRACEBUF2>; exiting!\n" );
         exit( -1 );
      }
   }
   else
   {
      TypeTraceBuf2 = TypeError;
   }


  /* Reset OutputMsgType now that TypeTraceBuf is defined
   ******************************************************/
   if ( OutputMsgType == -1 )
           OutputMsgType = TypeTraceBuf;
   else if ( OutputMsgType == -2 )
           OutputMsgType = TypeTraceBuf2;
   if ( Debug >= 2 )  logit( "" , "OutputMsgType reset to %d\n", OutputMsgType );
   
   if ( Debug >= 6 )  logit( "" , "Leaving  sr_lookup_ew\n" );
   return;
} 

/******************************************************************************
 *  Function: sr_heartbeat                                                    *
 *  Purpose:  Send a heartbeat message if enough time has elapsed since the   *
 *            last one.  Also toggle the PARxCH yellow LED.  Depending on the *
 *            selected sampling rate, the yellow LED may blink faster or      *
 *            slower than the rate at which heartbeat messages are sent to    *
 *            the earthworm shared memory ring.                               *
 ******************************************************************************/
void sr_heartbeat( void )
{
   long msgLen; /* Length of the heartbeat message */

   if ( Debug >= 6 )  logit( "" , "Starting sr_heartbeat\n" );
   

   /* Send heartbeat message if enough time has elapsed
    ***************************************************/
    if ( time( &TimeNow ) - TimeLastBeat  >=  HeartBeatInterval ) 
    {
        sprintf( Msg, "%ld %ld\n\0", TimeNow, MyPid ); /* Fill message string */
        msgLen    = strlen( Msg );                     /* Set message size    */
        Logo.type = TypeHeartBeat;                     /* Set message type    */


        if ( Debug >= 5 ) logit( "", "Heartbeat string (time,pid) = %s", Msg );
        
        if ( tport_putmsg( &Region, &Logo, msgLen, Msg ) != PUT_OK )
            logit( "et", "srparxchewsend:  Error sending heartbeat.\n" );

        TimeLastBeat = TimeNow;                 /* Update last heartbeat time */
    }


  /* Toggle yellow LED
   *******************/
   sr_atod_toggle_led( );


   if ( Debug >= 6 )  logit( "" , "Leaving  sr_heartbeat\n" );
   return;
}

/******************************************************************************
 *  Function: sr_atod_toggle_led                                              *
 *  Purpose:  Change the ON/OFF state of the yellow LED on the PARxCH front   *
 *            panel to provide a visible heartbeat and indicate another       *
 *            buffer of data has been read.                                   *
 ******************************************************************************/
void  sr_atod_toggle_led( void )
{
   if ( Debug >= 7 )  logit( "" , "Starting sr_atod_toggle_led\n" );

   AtodLedValue ^= 1;                            /* twiddle led state */
   ParXchUserLed( ParXchHandle, AtodLedValue );

   if ( Debug >= 7 )  logit( "" , "Leaving  sr_atod_toggle_led\n" );
   return;
}

/******************************************************************************
 *  Function: sr_error                                                        *
 *  Purpose:  Build and send an earthworm error message to the shared memory  *
 *            region (ring) and logs it.                                      *
 ******************************************************************************/
void sr_error( int ierr, char *note )
{
   long msgLen;  /* Length of the error message */

   if ( Debug >= 6 )  logit( "" , "Starting sr_error\n" );

  /* Build the message
   *******************/
   time( &TimeNow );
   sprintf( ErrMsg, "%ld %d %s\n\0", TimeNow, ierr, note); /* Fill errmsg str */
   msgLen    = strlen( ErrMsg );                           /* Set errmsg size */     
   Logo.type = TypeError;                                  /* Set errmsg type */


  /* Log it and send it to shared memory
   *************************************/
   logit( "et", "srparxchewsend: %s\n", note );

   if ( tport_putmsg( &Region, &Logo, msgLen, ErrMsg ) != PUT_OK )
           logit( "et", "srparxchewsend:  Error sending error:%d.\n", ierr );

   if ( Debug >= 6 )  logit( "" , "Leaving  sr_error\n" );
   return;
}

/******************************************************************************
 *  Function: sr_strncpy                                                      *
 *  Purpose:  Copy a string from src to dest and ensure the destination       *
 *            is null terminated.  Unlike the standard C runtime function of  *
 *            similar name, the length parameter should be the number of      *
 *            characters INCLUDING the terminating null.  For example, if     *
 *            len = 4, and src = "abcdef", then dest = "abc", ie 3 characters *
 *            plus 1 null = 4.                                                *
 ******************************************************************************/
int sr_strncpy( char *dest, char *src, int len )
{
   int   i, ok;
   char *dstart;
    
   if ( Debug >= 8 )  logit( "" , "Starting sr_strncpy\n" );


   ok = 0;
   if ( dest && src && (len > 0) )  /* Error check inputs */
   {

     /* Copy characters from src to dest, stopping after len-1
      * characters or reaching a null at the end of the source string
      ****************************************************************/
      dstart = dest;
      i      = 1;
      while ( (i < len)  &&  (*dest++ = *src++) )
         i++;

     /* If copying stopped because of hitting the character limit, fill
      * the last character with null (it will already be null if the copy
      * stopped because of reaching the end of the source string).
      *******************************************************************/
      if ( i == len )
         *dest = '\0';

      ok = 1;
      
      if ( Debug >= 8)  logit( "" , "src |%s|, dest |%s|\n", src, dstart );
      
   }

   if ( Debug >= 8) logit( "" , "Leaving  sr_strncpy\n" );
   return( ok );
}

/******************************************************************************
 *  Function: sr_log_gap_details                                              *
 *  Purpose:  Gaps in the data can cause problems for downstream programs.    *
 *            If one occurs, we want to log lots of details about it so we    *
 *            track it down and eliminate it in the future.                   *
 ******************************************************************************/
void sr_log_gap_details( int thisMethod, double expectedStart, double atodEndTime  )
{
   int     i, j, k, ok1, ok2;
   int     tsCurrLow, tsCurrHigh, tsEndLow, tsEndHigh;
   long    atodEndPt;
   double  diff[MAX_TS], des, dse, dss;

   if ( Debug >= 6) logit( "" , "Starting  sr_log_gap_details\n" );
    
   if ( Debug >= 3 )
   {

      logit( "", "Gap Details:\n" );


      for ( i = 0 ; i < MAX_PREV_SER ; i++ )
      {
         logit( "", "  Serial %d: Valid 0x%X, NmeaCount %d, PpsEvent %8d\n",
                i, 
                GpsPrevSerial[i].ValidFields,
                GpsPrevSerial[i].NmeaCount,
                GpsPrevSerial[i].PpsEventNum
               );
          for ( j = 0 ; j < GpsPrevSerial[i].NmeaCount ; j++ )
          {
             k = MAX_NMEA_SIZE * j;
             logit( "", "     NMEA %d: %s", j, &GpsPrevSerial[i].NmeaMsg[k] );
           } /* End for j */

      } /* End for i */
        
      atodEndPt  = AtodCurrentPtWrite + (AtodNumSamples-1);
      logit( "", "  Current StartPt = %ld, EndPt = %ld\n",
              AtodCurrentPtWrite, atodEndPt );

      ok1 = sr_ts_select( AtodCurrentPtWrite, TsValid, &tsCurrLow, &tsCurrHigh );
      ok2 = sr_ts_select( atodEndPt,          TsValid, &tsEndLow,  &tsEndHigh );
      logit( "", "  TS Start ok = %d, Low = %d, High = %d\n", ok1, tsCurrLow, tsCurrHigh );
      logit( "", "  TS End   ok = %d, Low = %d, High = %d\n", ok1, tsEndLow, tsEndHigh );


      logit( "", "  Ts# Valid  Pps   Samp     CountAtP             CountAtD            PctimeAtP             PctimeAtD        YmdSrc  HmsSrc   SecSince1970\n");

      for ( i = 0 ; i < MAX_TS ; i++ )
      {
         logit( "", "  %d   0x%X  %5u  %5ld   0x%08lX %08lX  0x%08lX %08lX  0x%08lX %08lX   0x%08lX %08lX   %2d    %2d     %lf\n",
                 i, TimeStamp[i].Valid, TimeStamp[i].PpsEvent, TimeStamp[i].Sample, 
                 TimeStamp[i].CountAtPps.u.HighPart,     TimeStamp[i].CountAtPps.u.LowPart,
                 TimeStamp[i].CountAtDready.u.HighPart,  TimeStamp[i].CountAtDready.u.LowPart,
                 TimeStamp[i].PctimeAtPps.u.HighPart,    TimeStamp[i].PctimeAtPps.u.LowPart,
                 TimeStamp[i].PctimeAtDready.u.HighPart, TimeStamp[i].PctimeAtDready.u.LowPart,
                 TimeStamp[i].YmdSource, TimeStamp[i].HmsSource, TimeStamp[i].SecSince1970 );
           ParGpsLargeIntDiffFull( TimeStamp[i].CountAtDready, TimeStamp[i].CountAtPps,
                                   NULL, &diff[i] );
      }

      logit( "", "  (D-P = %lf %lf %lf %lf %lf)\n", diff[0], diff[1], diff[2], diff[3], diff[4] );
        
      for ( i = 0 ; i < MAX_TS-1 ; i++ )
      {
         ParGpsLargeIntDiffFull( TimeStamp[i+1].CountAtPps, TimeStamp[i].CountAtPps,
                                 NULL, &diff[i] );
      }
      logit( "", "  (P-P = %lf %lf %lf %lf)\n", diff[0], diff[1], diff[2], diff[3] );

      for ( i = 0 ; i < MAX_TS-1 ; i++ )
      {
         ParGpsLargeIntDiffFull( TimeStamp[i+1].CountAtDready, TimeStamp[i].CountAtDready,
                                 NULL, &diff[i] );
      }
      logit( "", "  (D-D = %lf %lf %lf %lf)\n", diff[0], diff[1], diff[2], diff[3] );
        
      logit( "", "  This time method %d, last %d\n", thisMethod, LastTimeMethod );
      logit( "", "  Last end %lf, this start %lf, this end %lf\n",
             AtodEndTimeLast, AtodStartTime, atodEndTime );

      des =  AtodStartTime - AtodEndTimeLast;
      dse =  atodEndTime   - AtodStartTime;
      logit( "", "  Last end to this start %lf, this start to this end %lf\n", des, dse );
      dss =  AtodStartTime - expectedStart;
      logit( "", "  expected start %lf, to this start %lf\n", expectedStart, dss );
        
   }

   if ( Debug >= 6 ) logit( "" , "Leaving  sr_log_gap_details\n" );
   return;
}

/******************************************************************************
 *  Function: sr_log_trace_head                                               *
 *  Purpose:  Output the trace header for debugging purposes.                 *
 *            Currently, not called by any routines.                          *
 ******************************************************************************/
void sr_log_trace_head( void )
{
   if ( Debug >= 6 ) logit( "" , "Starting  sr_log_trace_head\n" );

   logit( "", "TraceHeader:\n" );
   logit( "", "  Pinno   %d\n",   TraceHead->pinno );
   logit( "", "  Nsamp   %d\n",   TraceHead->nsamp );
   logit( "", "  Start   %lf\n",  TraceHead->starttime );
   logit( "", "  End     %lf\n",  TraceHead->endtime );
   logit( "", "  Rate    %lf\n",  TraceHead->samprate );
   logit( "", "  Sta     %s\n",   TraceHead->sta );
   logit( "", "  Net     %s\n",   TraceHead->net );
   logit( "", "  Chan    %s\n",   TraceHead->chan );
   logit( "", "  Loc     %s\n",  &TraceHead->chan[TRACE_POS_LOC] );
   logit( "", "  Version %c%c\n", TraceHead->chan[TRACE_POS_VER], TraceHead->chan[TRACE_POS_VER+1] );
   logit( "", "  Type    %s\n",   TraceHead->datatype );
   logit( "", "  Quality %X%X\n", TraceHead->quality[0], TraceHead->quality[1] );
   logit( "", "  Pad     %X%X\n", TraceHead->pad[0], TraceHead->pad[1] );
    
   if ( Debug >= 6 ) logit( "" , "Leaving  sr_log_trace_head\n" );
   return;
}

