////////////////////////////////////////////////////////////////////////////////
//
// PURPOSE:  Dvl Driver.
// AUTHOR:   Rob McEwen, using code from Tom O'Reilly, Janice Tarrant, and
//           Don Green.
// DATE:     01/3/9
// COMMENTS: This is the code that talks to the instrument.
//
////////////////////////////////////////////////////////////////////////////////
//

#include <stdio.h>
#include "SerialDevice.h"
#include "Dvl.h"
#include "WorkSiteIF.h"
#include "DvlOutput.h"
#include "Syslog.h"
#include "System.h"
#include "matrixMath.h"

#include <ioctl.h>
//
// The dvl will be in hex/ascii mode, NOT binary mode.
//
// For PD5, the output format still will match Figure D-12 & 13 on
// pp. D-12 & 13 of the manual, except there will be twice as many bytes,
// because each hex char is a byte.  See also p D-24, and note the first column.
//
// 88 bytes = 176 hex chars.
// #define MaxRecordBytes 176
//
// The record must now be able to hold the maximum size for PD0, which is
// 5564
//
#define MaxRecordBytes 5570

#define RecordTerminator "\r"
#define ReadTimeout 2000
#define MaxInitTries 2
//
// Define the firmware version number for parsing the PD0 format.  Later, 
// I'll automate this.
//
//const char *version = "9.11";
const char *version = "9.17";
//
// Initialize the direction cosine matrix that takes a vector from RDI ship
// coordinates to SNAME standard ship coordinates.  See my handwritten notes
// on page C-26 of the RDI manual.
//
// I didn't make this a const because that will conflict with the type
// declaration in the argument list of the C function that I pass it to.
//
double T_SNAME_RDI[NX][NX] = {
                                { 0., 1., 0.},
                                { 1., 0., 0.},
                                { 0., 0.,-1.},
                             };
//
// Build the table of default Dvl configuration commands.
//
// I would like this to be a const, but that conflicts with writeReadAscii's
// argument list type declarations.
//
char *cmdTab[] = {
  //
  // Reset the DVL to the factory settings.
  //
  {"CR1"},
  //
  // Select PD5 output format.
  //
  {"PD5"},
  //
  // Set BK1, which sends a water-mass layer after every bottom-track ping.
  // We should consider setting this to BK2 for operational use, which would
  // send a water-mass layer ping only if the bottom-track ping doesn't return.
  // This presumably would save power, and decrease the sampling period.
  //
  {"BK1"},
  //
  // Set the Dvl to the minimum time between pings.
  //
  {"TP00:00.00"},
  //
  // Set the water-tracking layer to 10 meters in depth, beginning from the
  // vehicle and extending 10 m down.
  //
  {"BL,100,000,100"},
  //
  // Set BP001, which selects only one ping per ensemble.
  //
  {"BP001"},
  //
  // Select hex/ascii format (not binary).
  //
  {"CF11010"},
  //
  // Select Ship Coordinates.  Do not apply the Pitch/Roll from the tilt
  // sensors in the coordinate transformation.  Allow 3-beam solutions.
  // Turn on bin mapping, although it really doesn't matter since the
  // tilt sensors aren't applied.
  //
  {"EX10011"},
  //
  // Set the mounting angle to 45 Deg.  This means that the alignment groove
  // on the DVL head should be coincident with the vehicle centerline, and
  // the alignment notch should be forward.  This places the number 3 beam
  // forward and to starboard.
  //
  {"EA+04500"}
};

const int nCmds = sizeof( cmdTab ) /sizeof( cmdTab[0] );

//
// Used in translating from the Dvl baud rate command CB to the baud value.
//
//                       0   1    2    3    4     5     6     7     8
const int baudRate[] = {300,1200,2400,4800,9600,19200,38400,57600,115200};


Dvl::Dvl(SerialDevice *device, Boolean verbose)
  : SerialDeviceDriver("Dvl",
                       device,
                       MaxRecordBytes,
                       RecordTerminator,
                       ReadTimeout,
                       MaxInitTries)
{
  m_log = new DvlLog(this, DataLog::BinaryFormat);
  m_output  = new DvlOutput();
  m_output->data.badComms = 0;
  //
  // Build the file name and path, and open it to write the raw Dvl
  // ascii data to.  Begin by getting the path name.
  //
  fpAsc = NULL;
  char *auvLogDir = getenv(AuvLogDirName);
  if (auvLogDir == 0)
  {
    Syslog::write("Dvl::Dvl() - environment variable %s not set\n",
                  AuvLogDirName);
    exit(1);
  }
  //
  // AscFileName contains the full path and file name DvlData.asc
  //
  sprintf(AscFileName, "%s/%s/%s.asc", auvLogDir, LatestLogDirName, "DvlData");
  sprintf(BinFileName, "%s/%s/%s.bin", auvLogDir, LatestLogDirName, "DvlData");
  //
  // Now, check to see if this file is already there (John Rieffel).  If so,
  // append a file number so that previous files don't get overwritten.
  //
  FILE *logfile;
  char origName[100];
  memset(origName, 0, 100);
  strcpy(origName, AscFileName);
  //
  // First, attempt to open the file in read mode, to see if it already exists.
  // If so, rename it.
  //
  int numtries = 1;
#if WRITE_ASC_FILE
  while ((logfile = fopen(AscFileName, "r")) != NULL)
  {
    printf("%s already exists; appending a %d\n", AscFileName, numtries);
    sprintf(AscFileName, "%s.%d",origName,numtries);
    fclose(logfile);
    numtries++;
  }
  //
  // Open the latest filename to write the Ascii data to.
  //
  fpAsc = fopen( AscFileName, "w" );
  if( fpAsc == NULL )
  {
    Syslog::write( " Dvl::Dvl() - Error opening %s.\n", AscFileName );
  }
#endif
  //
  // Now, do the same for the binary version.
  //
  fdescBin = -1;
  strcpy(origName, BinFileName);
  //
  // First, attempt to open the file in read mode, to see if it already exists.
  // If so, rename it.
  //
  numtries = 1;
//int wper = O_WRONLY | O_TRUNC | O_CREAT;
  int wper = O_RDWR | O_TRUNC | O_CREAT;
  int rper = O_RDONLY;
  while ((fdescBin = open(BinFileName, rper)) != -1)
  {
    printf("%s already exists; appending a %d\n", BinFileName, numtries);
    sprintf(BinFileName, "%s.%d",origName,numtries);
    close(fdescBin);
    numtries++;
  }

  fdescBin = open( BinFileName, wper, 0666 );
  if( fdescBin == -1 )
  {
    Syslog::write( " Dvl::Dvl() - Error opening %s.\n", BinFileName );
  }
  

  //
  // Now, open dvl.cfg. This file will contain the Dvl setup commands.
  //
  char *auvConfigDir = getenv(AuvConfigDirName);
  if (auvConfigDir == 0)
  {
    Syslog::write("Dvl::Dvl() - environment variable %s not set\n",
                  AuvConfigDirName);
    exit(1);
  }
  //
  // ConfigFileName contains the full path and file name dvl.cfg
  //
  sprintf(ConfigFileName, "%s/dvl.cfg", auvConfigDir);
  fpCfg = NULL;
  fpCfg = fopen( ConfigFileName, "r" );
  if( fpCfg == NULL )
  {
    Syslog::write( " Dvl::Dvl() - Error opening %s.\n", ConfigFileName );
  }
  //
  // Copy dvl.cfg to the latest log dir:
  //
  System::copyToLogDir( ConfigFileName );
  //
  // The object workSite is local to this constructor.
  //
  WorkSiteIF workSite("workSite");
  m_sos = (int) workSite.soundSpeed();

  m_verbose = verbose;
  m_timeout = ReadTimeout;                 //milli-seconds

//m_bufSize = sizeof(m_buf);
  m_bufSize = DVL_OUTBUF_SIZE;

  //
  // Initialize badComms to 0.
  //
  m_output->data.badComms = 0;

  //
  // Hardcode the number of data types to 7.  Later, read this from 
  // the WD command, and make the corresponding record length calc.
  numDataTypes = 7;

  //
  // Initialize the PD5Header, which is a constant.  initialize() computes
  // the PD0 header because it depends on the number of depth cells.
  //
  PD5Header = "7D0156";
}


Dvl::~Dvl()
{
  Boolean debug = m_verbose;
  char cmd[32];
  char cmdnr[32];

  delete m_log;
  delete m_output;

  delete binRecord;

  //
  // Close the Ascii log file and the .cfg file:
  //
#if WRITE_ASC_FILE
  if( fpAsc ) fclose( fpAsc );
#endif
  if( fpCfg ) fclose( fpCfg );
  if( fdescBin != -1 ) close( fdescBin );

  //
  // Stop it from pinging
  //
  wakeupDvl();

  //
  // Power down.
  //
  DeviceIF::Status status;
  //
  // This doesn't work because apparently the DVL doesn't return the
  // terminating character "<".
  //
  // status = writeReadAscii( "CZ", m_timeout );
  //
  status = writeAscii( "CZ" );
  if( status != DeviceIF::Ok ) Syslog::write(" Dvl::~Dvl: CZ not sent!\n");

  Syslog::write(" Dvl::~Dvl: Destructor Finished.\n");
}
////////////////////////////////////////////////////////////////////////////////
//
// Initialize the Dvl.  This routine is apparently called by SerialDeviceDriver's
// constructor.
//
// NOTE: _device is a member of the class SerialDeviceDriver, which this
//       inherits from.
//
////////////////////////////////////////////////////////////////////////////////
//
DeviceIF::Status Dvl::initialize()
{
  Boolean debug = m_verbose;
  Syslog::write("\n Starting DVL Initialization.\n");
  DeviceIF::Status status = DeviceIF::Ok;

  //
  // Set up the serial line format:
  //
  if(_device->setLineFormat(BAUD_RATE,DATA_BITS,STOP_BITS,PARITY) == ERROR )
  {
    Syslog::write(" DVL::initialize: ERROR - Serial Port Formatting\n");
    return DeviceIF::Error;
  }
  //
  // Send a 300 ms Tx ON to get it's attention:
  //
  wakeupDvl();

  try{_device->clearPort();}
  catch( Exception errorObject )
  {
    Syslog::write("Dvl::initialize() clearPort() %s", errorObject.msg );
  }
  char cmd[128], buf[128];
  int nLines = 0, nGoodLines = 0;
  //
  // Read in Dvl configuration commands from ConfigFileName:
  //
  while ( (fpCfg != NULL) && (fgets(buf, sizeof(buf), fpCfg) != NULL) )
  {
    nLines++;
    //
    // Ignore whitespace, and read in the first whitespace-terminated string.
    //
    sscanf( buf, "%s", cmd );
    //
    // Check to see if it's a comment.  If so, skip this line and read the next.
    //
    if( !strncmp( cmd, "//" , 2 ) ) continue;
    //
    // Sanity check: a command must be > 2 chars
    //
    if( strlen(cmd) < 3 )
    {
      Syslog::write(" Dvl::initialize - Bad command in dvl.cfg on line %d.\n",
                    nLines );
      continue;
    }
    //
    // Disallow setting of the WD command because it must remain 7.
    //
    if( !strncmp( cmd, "WD", 2 ) )
    {
      Syslog::write(" Dvl::initialize - Error. WD must remain 111 100 000.\n");
      continue;
    }
    //
    // Disallow setting of the EC command because it's read in from Worksite.
    //
    if( !strncmp( cmd, "EC", 2 ) )
    {
      Syslog::write(" Dvl::initialize - Error. EC speed of sound is set"
                    " in worksite.cfg.\n");
      continue;
    }
    nGoodLines++;
    status = writeReadAscii( cmd, m_timeout);
    if( status != DeviceIF::Ok )
    {
      Syslog::write(" Dvl::initialize - Error setting command %s.\n", cmd);
    }
    //
    // Now, do some computations for certain commands.  First, set the
    // member variable "Format".
    //
    if( !strncmp( cmd, "PD", 2 ) ) setFormat( cmd );
    //
    // Set the member variable numCells.
    //
    if( !strncmp( cmd, "WN", 2 ) )
    {
      numCells = atoi(cmd+2);
      Syslog::write(" Dvl::initialize - The number of cells is %d.\n",
                    numCells);
    }
    //
    // Set the baud rate
    //
    if( !strncmp( cmd, "CB", 2 ) )
    {
      int index;
      char ascNum[2];
      //
      // Select out the first numeral, then add the terminating null.
      //
      strncpy( ascNum, cmd+2, 1 );
      ascNum[1] = '\0';
      index = atoi( ascNum );

      printf(" Baud index = %d.\n", index );
      if( index > 8 || index < 0 )
      {
	Syslog::write("DVL::initialize: ERROR - Bad baud rate index.\n");
	return DeviceIF::Error;
      }
      //
      // Reset the serial line format:
      //
      if(_device->setLineFormat(baudRate[index],
				DATA_BITS,STOP_BITS,PARITY) == ERROR )
      {
	Syslog::write("DVL::initialize: ERROR - The driver cannot reset its "
		      "baud rate to %d.\n", baudRate[index]);
	return DeviceIF::Error;
      }
      Syslog::write(" Dvl::initialize - Baud rate set to %d.\n", 
		    baudRate[index]);
    }
  } //   while ( (fpCfg != NULL) && (fgets(buf, sizeof(buf), fpCfg) != NULL) )

  Syslog::write(" Dvl::initialize - %d valid commands read in from dvl.cfg.\n",
                nGoodLines );
  //
  // If initialize() can't find dvl.cfg, or it is empty, use the
  // default settings.
  //
  if( nGoodLines == 0 )
  {
    for( int i=0; i<nCmds; i++ )
    {
      status = writeReadAscii( cmdTab[i], m_timeout);
      if( status != DeviceIF::Ok )
      {
        Syslog::write(" Dvl::initialize - Error setting command %s.\n", cmd);
      } //if
      if( !strncmp( cmdTab[i], "PD", 2 ) ) setFormat( cmdTab[i] );
    } // for
  } // if( nGoodLines == 0 )

  //
  // Set the speed of sound.
  //
  sprintf(cmd, "EC%d\r", m_sos );
  status = writeReadAscii( cmd, m_timeout );
  if( status != DeviceIF::Ok )
  {
    Syslog::write(" Dvl::initialize - Error setting command %s.\n", cmd);
  } //if
  //
  // Set the Dvl's clock to be the same as the MVC:
  //
  char timestr[128];
  size_t len;
  time_t timer;
  struct tm tmTime;
  struct timespec currentTime;
  //
  // Read the MVC clock, than massage these time structures around until
  // the right time format is produced as a character string in timestr.
  //
  clock_gettime(CLOCK_REALTIME, &currentTime);
  timer = currentTime.tv_sec;
  _gmtime(&timer, &tmTime);
  len = strftime(timestr, 32, "%y/%m/%d,%T", &tmTime);
  //
  // Send the command to the Dvl:
  //  
  sprintf( cmd, "TS%s", timestr );
  //printf("Time String Command = %s.\n", cmd);
  status = writeReadAscii( cmd, m_timeout );
  if( status != DeviceIF::Ok )
  {
    Syslog::write(" Dvl::initialize - Error setting command %s.\n", cmd);
  } //if

  //
  // Set the record length depending on what format the Dvl is outputting.
  // The column below shows the number of bytes for each segment.
  //
  // WARNING: This format will be DIFFERENT for different firmware versions.
  // This one is valid for 9.11.  Check their firmware change-log on the web,
  // as well as the manual, to attempt to surmise the layout for other
  // firmware versions. Pay careful attention to the manual publication date,
  // and the firmware release date.
  //
  // For this example, assume numCells = 30.
  //
  //
  // Header:      20 = 6+2*numDataTypes  -
  // F Leader:    52                  - CQ cmd N/A to the WHN
  // V Leader:    57                  - 65 for V>9.11??
  // Velocity:    2+8*numCells
  // Correl:      2+4*numCells
  // Echo:        2+4*numCells
  // Percent G:   2+4*numCells
  // Bottom Trk:  81                  - 85 for V>9.11
  // Reserved:    2
  //              --
  //            820 bytes             - Not counting the 2 byte checksum
  //
  // Now, repeat this for the CTD vehcle DVL, which has Version 9.17:
  //
  //
  // Header:      20 = 6+2*numDataTypes  -
  // F Leader:    59                  - 7 more than 9.11
  // V Leader:    57                  - same as before
  // Velocity:    2+8*numCells
  // Correl:      2+4*numCells
  // Echo:        2+4*numCells
  // Percent G:   2+4*numCells
  // Bottom Trk:  85                  - 4 more than 9.11
  // Reserved:    2
  //              --
  //            831 bytes             - Not counting the 2 byte checksum

  if( Format == PD0 )
  {
    char asciiStr[5];
    if( !strcmp(version, "9.11" ) )        //version IS 9.11
    {
      recordBytes = ( 6 + 2*numDataTypes + 52 + 57 + (2 + 8*numCells) +
		      3*(2 + 4*numCells) + 81 + 2 );
    }
    else 
    {
      if(strcmp(version, "9.17" ) )       //version is NOT 9.17
      {
	Syslog::write(" Dvl.cc - Unidentified Firmware Version. "
		      " Assume 9.17.\n");
      }
      recordBytes = ( 6 + 2*numDataTypes + 59 + 57 + (2 + 8*numCells) +
		      3*(2 + 4*numCells) + 85 + 2 );
    }
    //
    // recordSize is the number of ASCII chars, which is 2*number of bytes,
    // plus the checksum, which is 2 bytes.
    //
    recordSize = 2*(recordBytes + 2);
    sprintf( asciiStr, "%04X", recordBytes );
    strcpy( PD0Header, "7F7F" );
    strncat( PD0Header, asciiStr+2, 2 );  //lsb first
    strncat( PD0Header, asciiStr,   2 );  //msb
    Syslog::write(" PD0Header = %s", PD0Header );
    Header = PD0Header;
  }
  else if( Format == PD5 )
  {
    Header = "7D0156";
    recordSize = 176;
    recordBytes= 86;     //record length in bytes, not including checksum.
  }
  else
    Syslog::write(" Dvl::initialize() - Error in PD format specification.\n");

  //
  // Allocate memory for the binary data record, which is recordBytes + 2 bytes
  // long, including the checksum.
  //
  binRecord = new unsigned char [recordBytes + 2];

  //
  // Start it pinging. The dvl doesn't echo a ">" after the CS command.
  //
  status = writeAscii("CS");

  // set up minimum number of chars to be record size
//  _device->raw(recordSize, 1);

  if( status == DeviceIF::Ok )
    Syslog::write(" Dvl::initialize() - The DVL is initialized.\n");
  else
    Syslog::write(" Dvl::initialize() - The DVL initialization failed.\n");
  return status;

  
  
}
//
////////////////////////////////////////////////////////////////////////////////
//
// Send a 300 ms Tx ON to wake up the Dvl.
//
////////////////////////////////////////////////////////////////////////////////
//
DeviceIF::Status Dvl::wakeupDvl()
{
  Boolean debug = m_verbose;
  //
  // This routine was modified from Don Green's code.
  //
  dprintf("Dvl::wakeupDvl: Begin\n");
  //
  // A break to a RD Instruments Workhorse is a 300ms Tx High
  //
  int count = 1;
  Boolean tryagain = True;

  while((count<3) && (tryagain))
  {
    dprintf("Attempting to interrupt the DVL; try number %d", count );

    if(_device->sendBreak() == ERROR)
    {
      Syslog::write("Dvl::wakeupDvl: Error sending break signal to the DVL");
      return DeviceIF::Error;
    }
    //
    // If the break was successful, the Dvl is reset to 9600 baud, assuming 
    // that this value has not been changed in the user parameter memory with
    // the CK command.  So, set the driver back to 9600.
    //
    if(_device->setLineFormat(BAUD_RATE,DATA_BITS,STOP_BITS,PARITY) == ERROR )
    {
      Syslog::write(" DVL::wakeupDvl: ERROR - Serial Port Formatting\n");
      return DeviceIF::Error;
    }
    //
    // confirm reply
    // allow for long wait
    //
    try
    {
      if( _device->confirm(">",3*m_timeout) == ERROR)
      {
        Syslog::write(" Dvl::wakeupDvl: Failed to establish comms with DVL");
        count++;
        tryagain = True;
        int fd = _device->getFd();
        tcflush(fd,TCIFLUSH);
      }
      else
      {
        tryagain = False;
      }
    }
    catch(SerialDevice::TimedOut e)
    {
      dprintf(" Dvl::wakeupDvl: Caught a Timeout from SerialDevice");
      count++;
      tryagain = True;
      int fd = _device->getFd();
      tcflush(fd,TCIFLUSH);
    }
    catch(Exception e)
    {
      dprintf(" Dvl::wakeupDvl: Caught %s", e.msg );
      count++;
      tryagain = True;
      int fd = _device->getFd();
      tcflush(fd,TCIFLUSH);
    }
    catch(...)
    {
      dprintf(" Dvl::wakeupDvl: Caught an unknown exception");
      count++;
      tryagain = True;
      int fd = _device->getFd();
      tcflush(fd,TCIFLUSH);
    }
  }

  dprintf(" Dvl::wakeupDvl: End\n");

  if(tryagain == True)
    return DeviceIF::Error;
  else
    return DeviceIF::Ok;
}
//
////////////////////////////////////////////////////////////////////////////////
//
// This routine overrides the readRecord declared in the base class, because
// we don't get a terminating character.  We just look for a fixed number
// of bytes each time. For PD5, it's 176 bytes.
//
// If the checksum fails, this code attempts to re-locate the head of
// the record.
//
////////////////////////////////////////////////////////////////////////////////
//
DeviceIF::Status Dvl::readRecord(unsigned char *record,
                                           int maxRecordBytes,
                                           const char *recordTerminator,
                                           unsigned readTimeout,
                                           int *nBytesRead)
{
  Boolean debug = m_verbose;
  Boolean readError = False;

//  _device->commsDebugMode(SerialDevice::DebugOn);

  try
  {
    //
    // Read data record from device
    //
//    *nBytesRead = ::read(_device->getFd(), record,recordSize);
    *nBytesRead = _device->readNChars((char *)record, recordSize,
                                      readTimeout);

//   Syslog::write(" recordSize = %d\n", recordSize);
  }
  catch (SerialDevice::TimedOut) {
    readError = True;
    m_output->data.badComms = 1;
    Syslog::write("%s::readRecord() - Dvl serial device timed out", name());
    Syslog::write("%s::readRecord() - record so far: %s", name(), record);
  }
  catch (SerialDevice::BufferFull) {
    readError = True;
    m_output->data.badComms = 1;
    Syslog::write("%s::readRecord() - Dvl serial device buffer full", name());
  }
  catch (Exception e) {
    readError = True;
    m_output->data.badComms = 1;
    m_output->write();
    Syslog::write("%s::readRecord() - caught exception:  abort", name());
    throw;
  }

  //dprintf("Dvl::readRecord():  %d bytes read.\n", *nBytesRead );
  //Syslog::write("Dvl::readRecord():  %s \n", (char *)record );
  Syslog::write("Dvl::readRecord():  %d bytes read.\n", *nBytesRead );

  if( *nBytesRead != recordSize )
  {
    Syslog::write( "Dvl::readRecord(): Incorrect number of bytes read.\n");
    m_output->data.badComms = 1;
    m_output->write();
    return DeviceIF::Error;
  }
  //
  // Check the header to verify that we're at the beginning of the record:
  //
  int nBytes = 0;
  if( strncmp( (char *)record, Header, strlen(Header) ) )
  {
    //
    // They're not equal.  Look for the header.
    //
    Syslog::write(" Dvl record header not found.  Resynchronize.\n");
    nBytes = _device->readUntil( (char *)record, recordSize,
                                 Header, m_timeout );
    if( nBytes >= recordSize )
    {
      Syslog::write( "Dvl::readRecord(): Resync - readUntil() "
                     "couldn't find %s.\n", Header );
      m_output->data.badComms = 1;
      m_output->write();
      return DeviceIF::Error;
    }
    //
    // Ok, we've read 6 bytes into the next record.  Read the remaining
    // bytes (for PD5 there are 170) and throw them out.  Then, the next read
    // should be in sync.
    //
    int bytesLeft;
    bytesLeft = recordSize - strlen(Header);
    *nBytesRead = _device->readNChars((char *)record, bytesLeft,
                                      readTimeout);
    if( *nBytesRead != bytesLeft )
    {
      Syslog::write(" Dvl::readRecord(): resynch. Couldn't read remaining %d "
               "bytes.\n", bytesLeft );
      m_output->data.badComms = 1;
      m_output->write();
      return DeviceIF::Error;
    }
    Syslog::write(" Dvl:: Serial resynchronization completed.\n");
    //
    // Return an error here because this record is wrong.  The next one should
    // be right.
    //
    return DeviceIF::Error;
  }
  //
  // Compute the checksum for the record that we just read in:
  //
  int   byte;
  unsigned char byteVal,checkSumLsb, checkSumMsb;
  unsigned short checksum = 0;
  unsigned short advChecksum;

  for (byte = 0; byte < (*nBytesRead-4); byte+=2)
  {
    sscanf( (char *)( record + byte ), "%2x", &byteVal );
    checksum += byteVal;
  }
  dprintf(" %s::readRecord() - checksum = %d\n", name(), checksum );
  //
  // Now, read in the checksum that the DVL computed, and compare with the
  // one that we computed here.
  //
  sscanf( (char *) (record + *nBytesRead - 4), "%2x %2x",
          &checkSumLsb, &checkSumMsb);
  advChecksum = checkSumLsb | (checkSumMsb << 8);

  if (checksum != advChecksum)
  {
    readError = True;
    Syslog::write("Doppler System WARNING : ensemble checksum incorrect\n"
                  "Computed checksum = %d\n"
                  "DVL checksum      = %d\n", checksum, advChecksum);
  }

  if (readError)
  {
    m_output->data.badComms = 1;
    m_output->write();
    return DeviceIF::Error;
  }
  else
  {
#if WRITE_ASC_FILE
    fprintf( fpAsc, "%s\n", record );
#endif
    int error;
    error = cvt2bin( (char *) record, binRecord );
    if(error) Syslog::write("Dvl::readRecord. Error in binary conversion.\n");
    //
    // Some day, figure out why this didn't work.  It really should have.
    //
    //fprintf( fpAsc, "%.*s", recordBytes+2, binRecord );  //+2 includes checksum
    //
    write( fdescBin, binRecord, recordBytes+2 );
    return DeviceIF::Ok;
  }
}
//
////////////////////////////////////////////////////////////////////////////////
//
// Process the data that was just read:
//
////////////////////////////////////////////////////////////////////////////////
//

virtual DeviceIF::Status Dvl::processRecord(unsigned char *record,
                                                      int nRecordBytes)
{
  Boolean debug = m_verbose;
  double velocity_RDI[NXE];

  //  dprintf("Dvl::processRecord() - record = %s \n", record);

  //
  // Initialize these on each call.
  //
  bottomTrackVelocityStatus = 0;        /* velocity status            */
  waterMassVelocityStatus = 0;
  //
  // In the case of PDO, we must compute the locations of the data in the record.
  //
  if( Format == PD0 )
  {
    short msb, lsb;
    short dataOffset[7];
    sscanf( (char *) (record + 5*2), "%2x", &numDataTypes );
    //Syslog::write(" numDataTypes = %d\n", numDataTypes);
    if( numDataTypes != 7 )
    {
      //
      // For now, I'm going to require that all 7 data types be present.
      // Consequently, WD must have the value 111 100 000.
      // A future upgrade can read the header and handle the case where there
      // are fewer data types.
      //
      // dataOffset[0] - Start of Fixed Leader Data.
      // dataOffset[1] - Start of Variable Leader Data.
      // dataOffset[2] - Start of Velocity Data.
      // dataOffset[3] - Start of Correlation Data.
      // dataOffset[4] - Start of Echo Intensity Data.
      // dataOffset[5] - Start of Percent Good Data.
      // dataOffset[6] - Start of Bottom Track Data.
      //
      //printf(" Dvl::processRecord Error. "
      //       "PD0 has %d data types.\n", numDataTypes);
      Syslog::write(" Dvl::processRecord Error. "
                    "numDataTypes != 7.\n");
      return DeviceIF::Error;
    }
    for( int i=0; i<numDataTypes; i++ )
    {
      sscanf( (char *) (record + (2*i + 6)*2), "%2x %2x", &lsb, &msb );
      dataOffset[i] = lsb | (msb << 8);
      //Syslog::write(" dataOffset[%d] = %d\n", i, dataOffset[i]);  //debug
    }
    //
    // Parse the record. Start with Variable Leader Data.  The various locations
    // of the data bytes in the record are hardcoded below.  See the PD0
    // memory map starting on page 109 of the Aug 01 RDI manual.
    //
    sscanf( (char *) (record + (dataOffset[1] + 7)*2),
            "%2x %2x %2x %2x", &hour, &min, &sec, &centisec);
    sscanf( (char *) (record + (dataOffset[1] + 18)*2), "%2x %2x",
            &rawHeading[DOPPLER_LSB], &rawHeading[DOPPLER_MSB]);
    sscanf( (char *) (record + (dataOffset[1] + 20)*2), "%2x %2x",
            &rawPitch[DOPPLER_LSB], &rawPitch[DOPPLER_MSB]);
    sscanf( (char *) (record + (dataOffset[1] + 22)*2), "%2x %2x",
            &rawRoll[DOPPLER_LSB], &rawRoll[DOPPLER_MSB]);
    sscanf( (char *) (record + (dataOffset[1] + 26)*2), "%2x %2x",
            &rawTemp[DOPPLER_LSB], &rawTemp[DOPPLER_MSB]);
    //
    // Bottom-Track data:
    //
    sscanf( (char *) (record + (dataOffset[6] + 6)*2), "%2x",
            &bottomTrackCorMin );
    sscanf( (char *) (record + (dataOffset[6] + 7)*2), "%2x",
            &bottomTrackAmpMin );
    sscanf( (char *) (record + (dataOffset[0] + 17)*2), "%2x",  //Fixed Leader
            &waterMassCorMin );
    sscanf( (char *) (record + (dataOffset[6] + 16)*2),
            "%2x %2x %2x %2x %2x %2x %2x %2x",
            &rawRange[BM1][DOPPLER_LSB], &rawRange[BM1][DOPPLER_MSB],
            &rawRange[BM2][DOPPLER_LSB], &rawRange[BM2][DOPPLER_MSB],
            &rawRange[BM3][DOPPLER_LSB], &rawRange[BM3][DOPPLER_MSB],
            &rawRange[BM4][DOPPLER_LSB], &rawRange[BM4][DOPPLER_MSB]);
    sscanf( (char *) (record + (dataOffset[6] + 24)*2),
            "%2x %2x %2x %2x %2x %2x %2x %2x",
            &xBottomTrackVelocity[DOPPLER_LSB], &xBottomTrackVelocity[DOPPLER_MSB],
            &yBottomTrackVelocity[DOPPLER_LSB], &yBottomTrackVelocity[DOPPLER_MSB],
            &zBottomTrackVelocity[DOPPLER_LSB], &zBottomTrackVelocity[DOPPLER_MSB],
            &eBottomTrackVelocity[DOPPLER_LSB], &eBottomTrackVelocity[DOPPLER_MSB]);
    sscanf( (char *) (record + (dataOffset[6] + 32)*2), "%2x %2x %2x %2x",
            &bottomTrackCor[0], &bottomTrackCor[1],
            &bottomTrackCor[2], &bottomTrackCor[3] );
    sscanf( (char *) (record + (dataOffset[6] + 36)*2), "%2x %2x %2x %2x",
            &bottomTrackAmp[0], &bottomTrackAmp[1],
            &bottomTrackAmp[2], &bottomTrackAmp[3] );
    sscanf( (char *) (record + (dataOffset[6] + 50)*2),
            "%2x %2x %2x %2x %2x %2x %2x %2x",
            &xWaterMassVelocity[DOPPLER_LSB], &xWaterMassVelocity[DOPPLER_MSB],
            &yWaterMassVelocity[DOPPLER_LSB], &yWaterMassVelocity[DOPPLER_MSB],
            &zWaterMassVelocity[DOPPLER_LSB], &zWaterMassVelocity[DOPPLER_MSB],
            &eWaterMassVelocity[DOPPLER_LSB], &eWaterMassVelocity[DOPPLER_MSB]);
    sscanf( (char *) (record + (dataOffset[6] + 58)*2), "%2x %2x %2x %2x",
            &waterMassCor[0], &waterMassCor[1],
            &waterMassCor[2], &waterMassCor[3] );
    //
    // Form the bottomStatus and waterStatus words.  See page 144 of the Aug 01
    // manual.
    //
    *bottomStatus = 0;
    *waterStatus  = 0;
    for( i=0; i<NUM_BEAMS; i++ )
    {
      short bit = 1;
      if( bottomTrackCor[i] < bottomTrackCorMin )
        *bottomStatus |= (bit << (2*i));
      if( bottomTrackAmp[i] < bottomTrackAmpMin )
        *bottomStatus |= (bit << (2*i+1));
      if( waterMassCor[i] < waterMassCorMin )
        *waterStatus  |= (bit << i);
      //Syslog::write(" PD0 bottomTrackCor[%d] = %d\n", i, bottomTrackCor[i]);
    }
    //Syslog::write(" PD0 bottomTrackCorMin = %d\n", bottomTrackCorMin);
  }
  else if( Format == PD5 )
  {
    //
    // Parse the record:
    //
    sscanf( (char *) (record + 5*2),
      "%2x %2x %2x %2x %2x %2x %2x %2x",
       &xBottomTrackVelocity[DOPPLER_LSB], &xBottomTrackVelocity[DOPPLER_MSB],
       &yBottomTrackVelocity[DOPPLER_LSB], &yBottomTrackVelocity[DOPPLER_MSB],
       &zBottomTrackVelocity[DOPPLER_LSB], &zBottomTrackVelocity[DOPPLER_MSB],
       &eBottomTrackVelocity[DOPPLER_LSB], &eBottomTrackVelocity[DOPPLER_MSB]);
    sscanf( (char *) (record + 13*2),
            "%2x %2x %2x %2x %2x %2x %2x %2x",
            &rawRange[BM1][DOPPLER_LSB], &rawRange[BM1][DOPPLER_MSB],
            &rawRange[BM2][DOPPLER_LSB], &rawRange[BM2][DOPPLER_MSB],
            &rawRange[BM3][DOPPLER_LSB], &rawRange[BM3][DOPPLER_MSB],
            &rawRange[BM4][DOPPLER_LSB], &rawRange[BM4][DOPPLER_MSB]);
    sscanf( (char *) (record + 21*2), "%2x", bottomStatus);
    sscanf( (char *) (record + 22*2),
            "%2x %2x %2x %2x %2x %2x %2x %2x",
            &xWaterMassVelocity[DOPPLER_LSB], &xWaterMassVelocity[DOPPLER_MSB],
            &yWaterMassVelocity[DOPPLER_LSB], &yWaterMassVelocity[DOPPLER_MSB],
            &zWaterMassVelocity[DOPPLER_LSB], &zWaterMassVelocity[DOPPLER_MSB],
            &eWaterMassVelocity[DOPPLER_LSB], &eWaterMassVelocity[DOPPLER_MSB]);
    sscanf( (char *) (record + 34*2), "%2x", waterStatus);
    sscanf( (char *) (record + 35*2),
            "%2x %2x %2x %2x", &hour, &min, &sec, &centisec);
    sscanf( (char *) (record + 43*2), "%2x %2x",
            &rawTemp[DOPPLER_LSB], &rawTemp[DOPPLER_MSB]);
    sscanf( (char *) (record + 48*2), "%2x %2x",
            &rawPitch[DOPPLER_LSB], &rawPitch[DOPPLER_MSB]);
    sscanf( (char *) (record + 50*2), "%2x %2x",
            &rawRoll[DOPPLER_LSB], &rawRoll[DOPPLER_MSB]);
    sscanf( (char *) (record + 52*2), "%2x %2x",
            &rawHeading[DOPPLER_LSB], &rawHeading[DOPPLER_MSB]);
  }
  else
  {
    Syslog::write(" Dvl::processRecord Error.  Unrecognized PD format.\n");
    return DeviceIF::Error;
  }
  //
  // Convert the raw velocity reading to engineering units.
  //
  // The variable bottomTrackVelocityStatus essentially acts as a boolean that
  // is set if *bottomStatus != BEAM_OK or any of the velocity components is
  // equal to -32768.  bottomStatus contains flags on the state of each beam.
  // waterMassVelocityStatus and waterStatus have analagous meanings for
  // the water-referenced velocity.
  //
  if (*bottomStatus == BEAM_OK)         /* convert from mm/s to m/s   */
  {
    if (mmToMetres(xBottomTrackVelocity,
                   yBottomTrackVelocity,
                   zBottomTrackVelocity,
                   eBottomTrackVelocity,
                   velocity_RDI) == ERROR)
    {
      bottomTrackVelocityStatus = BAD_BOTTOM_TRACK_VELOCITY;
    }
    else   /* mmToMetres successfully computed velocity_RDI */
    {
      /*
      ** The PD0 format measures the velocity of the bottom or water with 
      ** respect to the vehicle, not the other way around as does PD5, so 
      ** we must flip the sign
      */
      if( Format == PD0 )
      {
	velocity_RDI[0] = -1.0*velocity_RDI[0];
	velocity_RDI[1] = -1.0*velocity_RDI[1];
	velocity_RDI[2] = -1.0*velocity_RDI[2];
      }
      /*
      ** Convert from RDI ship coordinates (y ahead, z up)  to SNAME ship
      ** ship coordinates (x ahead, z down).  Beware that this transformation
      ** only operates on the first 3 elements of velocity_RDI.  The error
      ** component, in the 4th element, is unchanged.
      */
      TVMult( bottomTrackVelocity, T_SNAME_RDI, velocity_RDI );
      bottomTrackVelocity[3] = velocity_RDI[3];
    }
  }
  else                                  /* beam error                 */
  {
    if( m_verbose )
    {
      if ((*bottomStatus & BOTTOM_BEAM1_CORRELATION) ==
          BOTTOM_BEAM1_CORRELATION)
          Syslog::write("Doppler System WARNING : beam 1 low correlation\n");

      if ((*bottomStatus & BOTTOM_BEAM1_ECHO_AMPLITUDE) ==
          BOTTOM_BEAM1_ECHO_AMPLITUDE)
          Syslog::write("Doppler System WARNING : beam 1 low echo amplitude\n");

      if ((*bottomStatus & BOTTOM_BEAM2_CORRELATION) ==
          BOTTOM_BEAM2_CORRELATION)
          Syslog::write("Doppler System WARNING : beam 2 low correlation\n");

      if ((*bottomStatus & BOTTOM_BEAM2_ECHO_AMPLITUDE) ==
          BOTTOM_BEAM2_ECHO_AMPLITUDE)
          Syslog::write("Doppler System WARNING : beam 2 low echo amplitude\n");

      if ((*bottomStatus & BOTTOM_BEAM3_CORRELATION) ==
          BOTTOM_BEAM3_CORRELATION)
          Syslog::write("Doppler System WARNING : beam 3 low correlation\n");

      if ((*bottomStatus & BOTTOM_BEAM3_ECHO_AMPLITUDE) ==
          BOTTOM_BEAM3_ECHO_AMPLITUDE)
          Syslog::write("Doppler System WARNING : beam 3 low echo amplitude\n");

      if ((*bottomStatus & BOTTOM_BEAM4_CORRELATION) ==
          BOTTOM_BEAM4_CORRELATION)
          Syslog::write("Doppler System WARNING : beam 4 low correlation\n");

      if ((*bottomStatus & BOTTOM_BEAM4_ECHO_AMPLITUDE) ==
          BOTTOM_BEAM4_ECHO_AMPLITUDE)
          Syslog::write("Doppler System WARNING : beam 4 low echo amplitude\n");
    }
    bottomTrackVelocityStatus = BAD_BOTTOM_TRACK_VELOCITY;
  }

/* check the water-track velocity status                                      */
  if (*waterStatus == BEAM_OK)          /* convert from mm/s to m/s   */
  {
    if (mmToMetres(xWaterMassVelocity,
                   yWaterMassVelocity,
                   zWaterMassVelocity,
                   eWaterMassVelocity,
                   velocity_RDI) == ERROR)
    {
      waterMassVelocityStatus = BAD_WATER_MASS_VELOCITY;
    }
    else   /* mmToMetres successfully computed velocity_RDI */
    {
      /*
      ** The PD0 format measures the velocity of the bottom or water with 
      ** respect to the vehicle, not the other way around as does PD5, so 
      ** we must flip the sign
      */
      if( Format == PD0 )
      {
	velocity_RDI[0] = -1.0*velocity_RDI[0];
	velocity_RDI[1] = -1.0*velocity_RDI[1];
	velocity_RDI[2] = -1.0*velocity_RDI[2];
      }
      /*
      ** Convert from RDI ship coordinates (y ahead, z up)  to SNAME ship
      ** ship coordinates (x ahead, z down).  Beware that this transformation
      ** only operates on the first 3 elements of velocity_RDI.  The error
      ** component, in the 4th element, is unchanged.
      */
      TVMult( waterMassVelocity, T_SNAME_RDI, velocity_RDI );
      waterMassVelocity[3] = velocity_RDI[3];
    }
  }
  else                                  /* beam error                 */
  {
    if( m_verbose )
    {
      if ((*waterStatus & ALTITUDE_TOO_SHALLOW) == ALTITUDE_TOO_SHALLOW)
          Syslog::write("Doppler System WARNING : altitude too shallow\n");

      if ((*waterStatus & WATER_BEAM1_CORRELATION) ==
          WATER_BEAM1_CORRELATION)
          Syslog::write("Doppler System WARNING : beam 1 low correlation\n");

      if ((*waterStatus & WATER_BEAM2_CORRELATION) ==
          WATER_BEAM2_CORRELATION)
          Syslog::write("Doppler System WARNING : beam 2 low correlation\n");

      if ((*waterStatus & WATER_BEAM3_CORRELATION) ==
          WATER_BEAM3_CORRELATION)
          Syslog::write("Doppler System WARNING : beam 3 low correlation\n");

      if ((*waterStatus & WATER_BEAM4_CORRELATION) ==
          WATER_BEAM4_CORRELATION)
          Syslog::write("Doppler System WARNING : beam 4 low correlation\n");
    }
    waterMassVelocityStatus = BAD_WATER_MASS_VELOCITY;
  }


  *dvlDataStatus = (bottomTrackVelocityStatus | waterMassVelocityStatus);

  /*
  ** Extract the time of the ping, in seconds:
  */
  *dvlPingTime  = ( (double) hour )*3600. + ( (double) min )*60. +
                  (double) sec +  ( (double) centisec )/100.;
  dprintf(" dvlPingTime = %f\n", *dvlPingTime );

  /*
  ** Convert the temperature into Degrees centigrade.
  */
  *dvlTemp  = (double) ( rawTemp[DOPPLER_LSB] | (rawTemp[DOPPLER_MSB] << 8) );
  *dvlTemp /= 100.;                           /* 100 counts = 1 Degree C    */
  dprintf(" dvlTemp = %f\n", *dvlTemp );

  /*
  ** Convert the tilt sensor pitch into Degrees
  */
  *dvlPitch  = (double) ( rawPitch[DOPPLER_LSB] | (rawPitch[DOPPLER_MSB] << 8) );
  *dvlPitch /= 100.;                           /* 100 counts = 1 Degree      */
  dprintf(" dvlPitch = %f\n", *dvlPitch );

  /*
  ** Convert the tilt sensor Roll into Degrees
  */
  *dvlRoll  = (double) ( rawRoll[DOPPLER_LSB] | (rawRoll[DOPPLER_MSB] << 8) );
  *dvlRoll /= 100.;                           /* 100 counts = 1 Degree      */
  dprintf(" dvlRoll = %f\n", *dvlRoll );

  /*
  ** Convert the DVL compass heading into Degrees
  */
  *dvlHeading =
    (double) ( rawHeading[DOPPLER_LSB] | (rawHeading[DOPPLER_MSB] << 8) );
  *dvlHeading /= 100.;                           /* 100 counts = 1 Degree      */
  dprintf(" dvlHeading = %f\n", *dvlHeading );

  /*
  ** Compute range:
  */
  m_status = cvtRange( rawRange, dvlRange );
  if( m_status == ERROR )
  {
    Syslog::write(" Doppler System - Error in range computation.\n");
  }
  //
  // Write dvlDataStatus to the output array.  It needs a cast, so it wasn't
  // done with a pointer.
  //
  for( int i=0; i<NXE; i++ )
  {
    m_output->data.bottomTrackVelocity[i] =  bottomTrackVelocity[i];
    m_output->data.waterMassVelocity[i]   =  waterMassVelocity[i];
  }

  m_output->data.bottomStatus = *bottomStatus;
  m_output->data.waterStatus  = *waterStatus;
  m_output->data.pingTime     = *dvlPingTime;
  m_output->data.temp         = *dvlTemp;
  m_output->data.range        = *dvlRange;
  m_output->data.pitch        = *dvlPitch;
  m_output->data.roll         = *dvlRoll;
  m_output->data.heading      = *dvlHeading;
  m_output->data.badComms     = 0;     //Comms are good if we got here.
  m_output->data.dataStatus   = (long) *dvlDataStatus;
  m_output->write();
  m_log->write();
  return DeviceIF::Ok;
}

/////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: writeAscii
// PURPOSE:  See Dvl.h
//
/////////////////////////////////////////////////////////////////////////////
DeviceIF::Status   Dvl::writeAscii( char *cmd )
{
  char cmdr[DVL_INBUF_SIZE];

  strcpy( cmdr, cmd );                //Make room for the '\r'.
  strcat( cmdr, "\r" );               //Add the '\r'.

  if (_device->write(cmdr, strlen(cmdr)) == strlen(cmdr))
  {
    return DeviceIF::Ok;
  }
  else
  {
    //
    // The _device->write() didn't work
    //
    Syslog::write("Dvl::writeAscii _device->write(%s) didn't work.\n", cmd);
    return DeviceIF::Error;
  }
}

/////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: readAscii
// PURPOSE:  See Dvl.h
//
/////////////////////////////////////////////////////////////////////////////
DeviceIF::Status   Dvl::readAscii( char *buf, int bufSize, int readTimeOut )
{
  try{ _device->readUntil( buf, bufSize, ">", readTimeOut ); }

  catch( SerialDevice::TimedOut errorObject )
  {
    Syslog::write("Dvl::readAscii() timed out.  %d bytes read.\n",
                  errorObject.nBytesRead() );
    Syslog::write("%s", buf);
    return DeviceIF::Error;
  }
  return DeviceIF::Ok;
}


/////////////////////////////////////////////////////////////////////////////
//
// FUNCTION: writeReadAscii
// PURPOSE:  See Dvl.h
//
/////////////////////////////////////////////////////////////////////////////
DeviceIF::Status   Dvl::writeReadAscii( char *cmd, int readTimeOut )
{
  DeviceIF::Status status = DeviceIF::Ok;

  status = writeAscii( cmd );
  if( status != DeviceIF::Ok ) return( status );

  status = readAscii( m_buf, m_bufSize, readTimeOut );
  if( status != DeviceIF::Ok )
  {
    Syslog::write(" Dvl::writeReadAscii() - Error. Sent %s. Received "
                  "%s.\n",
                  cmd, m_buf );
    return( status );
  }

  //if( m_verbose )  Syslog::write(m_buf);
  Syslog::write("%s", m_buf);

  return( status );
}


/******************************************************************************/
/* Function : cvtRange                                                        */
/* Purpose  : Converts raw DVL range measurement into Eng. units of meters.   */
/* Inputs   : 4 beam range-to-bottoms, lsb and msb.                           */
/* Outputs  : Returns OK or ERROR on bad velocity status.                     */
/******************************************************************************/
Boolean Dvl::cvtRange(long rawRange[NUM_BEAMS][NBYTES],   double *dvlAltitude)
{
  short rangeCM;                                /* range in centimeters       */
  double range = 0., minRange = 200.;
  short i;
  const Boolean useMinBeam = True;

  for( i=0; i<NUM_BEAMS; i++ )
  {
    /*
    ** Get range from beam i
    */
    rangeCM = rawRange[i][DOPPLER_LSB] | (rawRange[i][DOPPLER_MSB] << 8);
    if (rangeCM == BAD_VELOCITY)
      return(ERROR);
    else
	{
      /*
      ** Sum up all ranges, or take the minimum range of the four beams
      */
      if( useMinBeam )
	  {
        range = (double) rangeCM * CM_TO_METRES;
		if( range < minRange ) minRange = range;
	  }
	  else
        range += (double) rangeCM * CM_TO_METRES;
	}
  }
  /*
  ** If we're computing average altitude, divide by NUM_BEAMS to get the 
  ** average beam range, then multiply by cos(pi/6) to get LOS range.
  ** Otherwise, just convert the minimum range to LOS range.
  */
  if( useMinBeam )
	  *dvlAltitude = cos30 * range;
  else
	  *dvlAltitude = cos30 * range / ( (double) NUM_BEAMS );

  return(OK);
} /* cvtRange */


/******************************************************************************/
/* Function : mmToMetres                                                      */
/* Purpose  : Converts velocity in mm/s to m/s.                               */
/* Inputs   : X, y and z velocity lsb and msb, doppler velocity.              */
/* Outputs  : Returns OK or ERROR on bad velocity status.                     */
/******************************************************************************/
Boolean Dvl::mmToMetres(long xVelocity[], long yVelocity[], long zVelocity[],
                        long eVelocity[], double velocity[])
{
    short velocityMM;                           /* velocity in mm             */

/* get x velocity, check if bad, convert to metres per second                 */
    velocityMM = xVelocity[DOPPLER_LSB] | (xVelocity[DOPPLER_MSB] << 8);
    if (velocityMM == BAD_VELOCITY)
        return(ERROR);
    else
        velocity[X_INDEX] = (double) velocityMM * MM_TO_METRES;

/* get y velocity, check if bad, convert to metres per second                 */
    velocityMM = yVelocity[DOPPLER_LSB] | (yVelocity[DOPPLER_MSB] << 8);
    if (velocityMM == BAD_VELOCITY)
        return(ERROR);
    else
        velocity[Y_INDEX] = (double) velocityMM * MM_TO_METRES;

/* get z velocity, check if bad, convert to metres per second                 */
    velocityMM = zVelocity[DOPPLER_LSB] | (zVelocity[DOPPLER_MSB] << 8);
    if (velocityMM == BAD_VELOCITY)
        return(ERROR);
    else
        velocity[Z_INDEX] = (double) velocityMM * MM_TO_METRES;

/* get e velocity, check if bad, convert to metres per second                 */
    velocityMM = eVelocity[DOPPLER_LSB] | (eVelocity[DOPPLER_MSB] << 8);
    if (velocityMM == BAD_VELOCITY)
        return(ERROR);
    else
        velocity[E_INDEX] = (double) velocityMM * MM_TO_METRES;

    return(OK);

} /* mmToMetres */

//
////////////////////////////////////////////////////////////////////////////////
//
// PURPOSE: Set the member variable "Format" to the value in the "cmd" string.
//
////////////////////////////////////////////////////////////////////////////////
//
void Dvl::setFormat( char *cmd )
{
  if( strncmp(cmd, "PD", 2) )
  {
    Syslog::write(" Dvl::setFormat - Invalid argument.\n");
    Format = PD5;
    return;
  }

  int formatNum = atoi(cmd+2);
  switch( formatNum )
  {
  case 0:
    Format = PD0;
    break;
  case 5:
    Format = PD5;
    break;
  default:
    Syslog::write(" Dvl::intialize - Error PD%d unrecogized format.\n",
                  formatNum );
    break;
  } // switch
  return;
}

//
////////////////////////////////////////////////////////////////////////////
//
// PURPOSE: Convert a null-terminated character string to binary.
//
// INPUTS:  ascbuf  - The usual null-terminated character string.  
//          binbuf  - Binary equivalent
//                
// OUTPUTS: int     - 0 if successful, nonzero if failed.
//
////////////////////////////////////////////////////////////////////////////
//
int Dvl::cvt2bin( char *ascbuf, unsigned char *binbuf )
{
  unsigned char *ptr = binbuf;
  int i;
  char  charByte[3];
  long  longByte;
  unsigned char byte;
  //
  // Ensure an even number of characters:
  //
  if( strlen(ascbuf) % 2 ) return -1;
  //
  // Walk down the character buffer, looking for the terminating NULL.
  //
  for( i=0; *(ascbuf+i) != NULL; i++ )
  {
    if( i%2 == 0 ) 
    {
      //
      // Copy out every two chars, and convert them to a long.  Then cast the
      // long to a byte and write it to binbuf.
      //
      strncpy( charByte, ascbuf+i, 2 );
      longByte = strtol( charByte, NULL, 16 );
      *ptr = (unsigned char) longByte;
      ptr++;
    } // if
  } // for
  return 0;
}


