/****************************************************************************/
/* Copyright (c) 2000 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  :                                                               */
/* Filename : Seabird.cc                                                        */
/* Author   :                                                               */
/* Project  :                                                               */
/* Version  : 1.0                                                           */
/* Created  : 02/07/2000                                                    */
/* Modified :                                                               */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
#include <unistd.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <errno.h>
#include <iostream.h>
#include <time.h>
#include <math.h>
#include <FloatAttribute.h>
#include <AttributeParser.h>
#include <StringAttribute.h>
#include <IntegerAttribute.h>
#include "Seabird.h"
#include "Syslog.h"
#include "CtdIF.h"

#define MaxRecordBytes 200

// keep these here to obfuscate them
#define BAUD 4800
#define PARITY "EVEN"
#define STOP_BITS 1
#define DATA_BITS 7
#define READ_TIMEOUT 900
#define CTDPERIOD 1000
#define SHORT_WAIT 4000
#define LONG_WAIT 8000


// The instName parameter should be a unique name for each instance
// of the seabird server/driver. The name "ctdDriver" is recognized by
// the rest of the system as the "main" source of CTD data and
// therefore should always be used as one of the instance names.
//
Seabird::Seabird(SerialDevice *serialDevice, char *configFile, char *instName )
     : StreamSerialDriver(instName, serialDevice, MaxRecordBytes, "\xa",
			  READ_TIMEOUT),
       attributes(instName)
{
  char seabirdOutputName[256];

  // Use the instance name to create a unique shared memory area
  //
  strcpy(seabirdOutputName, instName);
  strcat(seabirdOutputName, SeabirdOutputName);
  _output = new SeabirdOutput(seabirdOutputName, SharedData::ReadWrite);

  createCTDAttributes();
  loadCTDConfigFile( configFile );
  reportCTDAttributes();

  depthSensorRunning = False;

  Syslog::write("Seabird/%s::Seabird() - create interface to depth sensor...",
		instName);
  try {
       depthSensor = new DepthSensorIF("depthSensor");
       depthSensorRunning = True;
  } 
  catch(SharedObjectClient::MissingServer e) {
    depthSensor_pres = 10.1;
    depthSensorRunning = False;
    Syslog::write("Seabird/%s -- DepthSensor server is not running. "
		  "Using default pres of %lf", instName, depthSensor_pres);
  }
  
  Syslog::write("Seabird/%s -- constructing Log...\n", instName);
  try {
    _log = new SeabirdLog(this, instName);
  }
  catch ( ... ) {
    throw Exception("Seabird.cc - CtdLog constructor failed\n");
  }

  _lastFlush = time((long*)0);
  _flushInterval = (time_t)300;      // flush log every 5 minutes


  Syslog::write("Seabird/%s -- constructor succeeded\n", instName);
}

void Seabird::createCTDAttributes()
{
     attributes.add( new FloatAttribute("t_a","t_a", &T_A ) );
     attributes.add( new FloatAttribute("t_b","t_b", &T_B ) );
     attributes.add( new FloatAttribute("t_c","t_c", &T_C ) );
     attributes.add( new FloatAttribute("t_d","t_d", &T_D ) );
     attributes.add( new FloatAttribute("t_f0","t_f0", &T_F0 ) );

     attributes.add( new FloatAttribute("c_a","c_a", &C_A ) );
     attributes.add( new FloatAttribute("c_b","c_b", &C_B ) );
     attributes.add( new FloatAttribute("c_c","c_c", &C_C ) );
     attributes.add( new FloatAttribute("c_d","c_d", &C_D ) );
     attributes.add( new FloatAttribute("c_m","c_m", &C_M ) );

     attributes.add( new FloatAttribute("eps","eps", &EPS ) );

     /*
     NUMVOLTAGES = 6;
     v1name = "v1";
     v2name = "v2";
     v3name = "v3";
     v4name = "v4";
     v5name = "v5";
     v6name = "v6";
     */
     
     attributes.add( new IntegerAttribute("NUMVOLTAGES","NUMVOLTAGES",(long *)&NUMVOLTAGES) );
    
     attributes.add( new StringAttribute("v1name","v1name",&_output->data.v1name,"v1") );
     attributes.add( new StringAttribute("v2name","v2name",&_output->data.v2name,"v2") );
     attributes.add( new StringAttribute("v3name","v3name",&_output->data.v3name,"v3") );
     attributes.add( new StringAttribute("v4name","v4name",&_output->data.v4name,"v4") );
     attributes.add( new StringAttribute("v5name","v5name",&_output->data.v5name,"v5") );
     attributes.add( new StringAttribute("v6name","v6name",&_output->data.v6name,"v6") );

		     
}

void Seabird::reportCTDAttributes()
{
     Syslog::write("CTD/%s -- Calibration constants:\n"
		   "\tT_a  = %7.6le\n"
		   "\tT_b  = %7.6le\n"
		   "\tT_c  = %7.6le\n"
		   "\tT_d  = %7.6le\n"
		   "\tT_f0 = %lf\n"
		   "\tC_a  = %7.6le\n"
		   "\tC_b  = %7.6le\n"
		   "\tC_c  = %lf\n"
		   "\tC_d  = %7.6le\n"
		   "\tC_m  = %4.3lf\n"
		   "\tEPS  = %7.6le", name(),
		   T_A, T_B, T_C, T_D, T_F0,
		   C_A, C_B, C_C, C_D, C_M, EPS );
}

void Seabird::loadCTDConfigFile( char *filename )
{
     Syslog::write("Seabird/%s -- Loading CTD constants from %s\n", 
		   name(), filename );
     AttributeParser::parse(filename, &attributes); 
     // check that numvoltages is between 0 and 6, inclusively
     if ((NUMVOLTAGES > 6)||(NUMVOLTAGES<0))
       {
	 Syslog::write("Seabird/%s::loadCTDConfigFile -- error in number of voltages",
		       name());
	 throw Exception("Seabird::loadCTDConfigFile -- error in number of voltages");
       }
     
}


Seabird::~Seabird()
{
  delete _output;
  delete depthSensor;
  delete _log ;
}


DeviceIF::Status Seabird::initialize()
{
  char buf[30];
  int i;
   
  _device->commsDebugMode(SerialDevice::DebugOff);
 
  Syslog::write("Seabird/%s:initializing Seabird...\n", name());

  // initialize serial port
  _device->setLineFormat(BAUD, DATA_BITS, STOP_BITS, PARITY);
  _device->printTermios();

  // clear serial port
  _device->clearPort();
  _device->flushReceiveBuf();
  
  /* First send a return & see if we get back S>, then it isn't in the
     initialized state.  Then, if not, send a Ctrl-Z in case we are data
     logging, then send a few returns to get past the set date part. */
  
  sprintf(buf,"\xd");
  _device->write(buf,strlen(buf));
  
  if (_device->confirm("S>",SHORT_WAIT) == -1) {
    Syslog::write("Seabird/%s::initialize() - confirm() timed out; sleep a bit",
		  name());
    sleep(5);
  }

  _device->write(buf, strlen(buf));

  if (_device->confirm("S>",LONG_WAIT) == -1) {
    Syslog::write("Seabird/%s::initialize() - "
		  "confirm() timed out again, send multiple newlines", name());

    /* sending lots of returns to get past the
       part where it wants you to set the date */
      
    sprintf(buf,"\x1a\xd");
    _device->write(buf,strlen(buf));
    Syslog::write("Seabird/%s: first Ctrl-Z  sent...\n", name());
      
    if (_device->confirm("S>",LONG_WAIT) == -1) {
      i = _device->confirm("power on", 4000 );
      if (i) {
	Syslog::write("Seabird/%s: confirmed power on returns: %d\n",name(), i);
      } 
      if (i==-1) { 
	Syslog::write("Seabird/%s: no power on detected yet...\n", name());
	sprintf(buf,"\r\r\r");
	_device->write(buf, strlen(buf));
	i = _device->confirm("power on", 4000);
	  
	if (i==-1) {
	  Syslog::write("Seabird/%s: no power on detected yet...\n", name());
	  sprintf(buf,"\r\r\r");
	  _device->write(buf, strlen(buf));
	      
	  i = _device->confirm("power on", 8000);
	      
	  _device->clearPort(); 
	  if (i==-1) {
	    //	    fprintf(stderr, "%s",error.msg);
	    return DeviceIF::Error;
	    //throw Exception("Seabird: no power on detected..bailing!\n");
	  }
	}
      }
    }
  }
  
  
  /* configure */
  
  Syslog::write("Seabird/%s: sending configure commands...\n", name());
  
  sprintf(buf,"CC\xd");
  _device->write(buf, strlen(buf));
  if ( _device->confirm("Y/N ? ", SHORT_WAIT) == -1 ) {
    return DeviceIF::Error;
    
    //    throw Exception("Seabird: bailout #1\n");
  }
  
  /* send yes to 1st prompt */
  
  sprintf(buf,  "Y\xd" );
  _device->write(buf, strlen(buf));
  
  if ( _device->confirm( "^Y/N ? ", SHORT_WAIT) == -1 ) {
    return DeviceIF::Error;
	    
    //    throw Exception("Seabird: bailout #2\n");
  }

  /* send Ctrl-Y (0x19, 25 dec) */
   
  sprintf(buf,  "\x19\xd" );
  _device->write(buf, strlen(buf));
 
  if ( _device->confirm( "new value = ", SHORT_WAIT) == -1 ) {
    // throw Exception("Seabird: init failed waiting for scans line\n");
    return DeviceIF::Error;
	    
  }
   
  /* average 1 scans */
  Syslog::write("Seabird/%s: setting scans...\n", name());
  sprintf(buf,  "1\xd" );
  _device->write(buf, strlen(buf));
   
  if ( _device->confirm( "new value = ", SHORT_WAIT) == -1 ) {
    //    throw Exception("Seabird: init failed waiting for pump freq msg \n");
    return DeviceIF::Error;
    
  }
   
   
  /*
    (from Odyssey Code)
     
    Discovered that one unit asks for the pressure range and temperature
    compensation values and one doesn't, hence the need to distinguish them
    during initialization sequence.  May need to pay more attention to this
    should it prove brittle. (BAM, 27-Jan-1998) */
   
  //#ifdef SBE_PRES_RANGE  // CURRENT TESTED CTD DOES NOT ASK FOR THIS 
  //JR - 12/9/99
   
  /* pressure sensor range */
   
   
  /* min freq for pump turn on */
  Syslog::write("Seabird/%s: setting pump freq...\n", name());
  sprintf(buf,  "3500\xd" );   
// LOW ENOUGH SO THAT PUMP IS ALWAYS ON
  //JR and Jim B. 1-18-00  
  _device->write(buf, strlen(buf));
   
  if ( _device->confirm( "new value = ", LONG_WAIT) == -1 ) {
    //throw Exception("Seabird::initialize() failed waiting for upcast msg\n");
    return DeviceIF::Error;

  }
				/* stop on upcast? */
  /* send a "n" instead of merely a carriage return,
     in case the CTD has pooped since last initialization */
  Syslog::write("Seabird/%s: setting upcast...\n", name());
  sprintf(buf,  "n\xd" );
  _device->write(buf, strlen(buf));
      
  if ( _device->confirm( "new", LONG_WAIT) == -1 ) {
    //    throw Exception("Seabird::initialize() failed while setting upcast\n");
    return DeviceIF::Error;

  }
				/* battery type */
  Syslog::write("Seabird/%s: setting battery type...\n", name());
  sprintf(buf,  "\xd" );
  _device->write(buf, strlen(buf));

  if ( _device->confirm( "new value = ", SHORT_WAIT) == -1 ) {
    //throw Exception("Seabird::initialize() failed setting battery type\n");
    return DeviceIF::Error;

  }
				/* external voltages */
  Syslog::write("Seabird/%s: setting external voltages to %d...\n",
		name(), NUMVOLTAGES);
  sprintf(buf,  "%d\xd",NUMVOLTAGES );
  _device->write(buf, strlen(buf));
     
  if ( _device->confirm( "new value =", SHORT_WAIT) == -1 ) {
    //     throw Exception("Seabird::initialize() failed setting external voltages\n");
    return DeviceIF::Error;
    
  }
  // now loop for all voltages
  for (i=1;i<=NUMVOLTAGES;i++)
    {
      Syslog::write("Seabird/%s::initialize() - confirming voltage...\n", name());
      sprintf(buf,  "Y\xd" );
      _device->write(buf, strlen(buf));
      
      if (i < NUMVOLTAGES)
	{
	  if (_device->confirm("new value =", SHORT_WAIT) == -1)
	    
	    {
	      Syslog::write("%s...failed on  %d\n", name(), i);
	      //      throw Exception("Seabird::initialize() failed on external voltage setup\n");
return DeviceIF::Error;

	    }
	}
    }
				/* voltage 1 */
  //  _device->commsDebugMode(SerialDevice::DebugOn);
  if ( _device->confirm( "S>", 5*LONG_WAIT) == -1 ) {
    Syslog::write("Seabird/%s: error reading S> prompt\n", name());
    //    throw Exception("Seabird::initialize() failed confirming voltage\n");
    return DeviceIF::Error;
 
  }
				/* disable switch */
  Syslog::write("Seabird/%s::initialize() - disabling switch...\n", name());
  sprintf(buf,  "SD\xd" );
  _device->write(buf, strlen(buf));

  if ( _device->confirm( "S>", SHORT_WAIT) == -1 ) {
    //throw Exception("Seabird::initialize() failed disabling switch\n");
    return DeviceIF::Error;

  }  
				/* DS to display setup into syslog */
  Syslog::write("Seabird/%s: sending DS command...\n", name());
  sprintf(buf,  "DS\xd" );
  _device->write(buf, strlen(buf));
     
  if ( _device->confirm( "S>", 5*LONG_WAIT) == -1 ) {       
    //    throw Exception("Seabird::initialize() failed on DS command\n");
    return DeviceIF::Error;

  }

  Syslog::write("Seabird/%s::initialize() - starting logging...\n", name());

  // Start logging
  sprintf(buf,  "GL\xd" );
  _device->write(buf, strlen(buf));

  // Are you Sure?
  if (_device->confirm("Y/N ? ",SHORT_WAIT) == -1) {
    //throw Exception("Seabird: failed waiting for yes/no prompt\n");
    return DeviceIF::Error;

  }

  sprintf(buf,  "Y\xd" );
  _device->write(buf, strlen(buf));
  // Cntrl-Y to confirm
  if (_device->confirm("^Y/N ? ",SHORT_WAIT) == -1) {
    //    throw Exception("Seabird: failed waiting for ctrl-yes/no prompt\n");
return DeviceIF::Error;

  }
  
  //\x19 is Ctrl-Y (!)
  sprintf(buf,  "\x19\xd" );
  _device->write(buf, strlen(buf));
      
  if (_device->confirm("S>",SHORT_WAIT) == -1) {
    //    throw Exception("Seabird: failed waiting for S> prompt\n");
    return DeviceIF::Error;

  }
  //done with init
  Syslog::write("Ctd/%s:initialize() - Logging Started...\n", name());

  _output->data.deviceReady = True;
  _output->write();
  Syslog::write("Ctd/%s:initialize() - ready!", name());

  return DeviceIF::Ok;
}
 

DeviceIF::Status Seabird::processRecord(unsigned char *record, int nBytes)
{
  Boolean debug = True;

  char *reply = (char *)record;

  int val1, val2, val3, val4, nconv, nc;

  nconv = sscanf(reply, "%2x%2x%2x%n", &val1, &val2, &val3, &nc);

  if (nconv == 3 && nc == 6) {
    // value = val1*256+val2+val3/256
    read_tfreq = (val1*256) + val2 +((double)val3/256);
    read_temp = calculate_Temp(read_tfreq);
    // dprintf("Seabird: tfreq: %f, temp: %f\n",read_tfreq,read_temp);
    clock_gettime(CLOCK_REALTIME,&temp_readtime);
  }
  else {
    Syslog::write("Seabird/%s: error sscanfing %d bytes in reply: %s\n", 
		  name(), nBytes, reply);
  }
      
  nconv = sscanf(reply+6, "%2x%2x%2x%n",&val1,&val2,&val3,&nc);

  if (nconv == 3 && nc == 6) {

    // value = val1*256+val2+val3/256

    read_cfreq = (val1*256) + val2 +((double)val3/256);
    read_temp = calculate_Temp(read_tfreq);
	  
    // ??!????
	  
    TimeIF::TimeSpec t;
      
    if (depthSensorRunning) {
      depthSensor->pressure(&depthSensor_pres, &t);
      depthSensor_pres = 10*depthSensor_pres;
    }
    else {
      depthSensor_pres = 10.1 ; //in dbar -- just an assumption for now...
    }
    //pres = 10*sensor(M_PRESSURE);
      
    /* THIS IS CALCULATED ABOVE in  read_temp*/
      
    //temp = sensor(WATER_TEMP)  
	  
    read_cond = calculate_Cond(0.001*read_cfreq, read_temp, depthSensor_pres);

  }
  else
  {
    Syslog::write("Seabird/%s: error sscanf'ing conductivity data string\n",
		  name());
  }
  
  offset = 16;
  for (i=0;i< NUMVOLTAGES;i++)
  {
    //      Syslog::write("Seabird: scanning voltage %d...\n",i);

    if ((i == (NUMVOLTAGES - 1)) && ((NUMVOLTAGES/2) == ((NUMVOLTAGES-1)/2)))
      offset++;

    nconv = sscanf(reply+offset,"%3x%n",&val1,&nc);

    //      Syslog::write("Seabird: nconv: %d, val1: %3x, nc: %d\n",nconv,val1,nc);
      
    if ((nconv==1)&&(nc==3))
    {
      voltages[i] = ((double)val1)/819;
      //	  Syslog::write("Seabird: value of %d is: %f\n",i+1,voltages[i]);
      //  dprintf("Seabird: value of %d is: %f\n",i+1,voltages[i]);
    }
    else
    {
      Syslog::write("Seabird/%s: error reading voltages\n", name());
      dprintf("Seabird/%s: error reading voltages\n", name());
    }
    offset+=3;
  }
  
  clock_gettime(CLOCK_REALTIME,&cond_readtime);
  
  _output->data.conductivity = read_cond;
  _output->data.temperature = read_temp;
  _output->data.cfreq = read_cfreq;
  _output->data.tfreq = read_tfreq;
  _output->data.temp_updateTime.tv_sec = temp_readtime.tv_sec;
  _output->data.temp_updateTime.tv_nsec = temp_readtime.tv_nsec;
  _output->data.cond_updateTime.tv_sec = cond_readtime.tv_sec;
  _output->data.cond_updateTime.tv_nsec = cond_readtime.tv_nsec;
  for(i=0;i<NUMVOLTAGES;i++)
    {
      _output->data.voltages[i] = voltages[i];
    }


  // empty these up
  // free(_output->data.v1name);
  //free(_output->data.v2name);
  //free(_output->data.v3name);
  //free(_output->data.v4name);
  //free(_output->data.v5name);
  //free(_output->data.v6name);


  //and now copy them
  //_output->data.v1name = strdup(v1name);
  //_output->data.v2name = strdup(v2name);
  //_output->data.v3name = strdup(v3name);
  //_output->data.v4name = strdup(v4name);
  //_output->data.v5name = strdup(v5name);
  //_output->data.v6name = strdup(v6name);


  try
    {
      _output->write();
      
    }
  catch (SharedData::AccessError error)
    {
      fprintf(stderr, "%s",error.msg);
      exit(1);
    }
  // now write to log file
  _log->write();
  time_t now = time((long*)0);
  if ((now - _lastFlush) > _flushInterval)
  {
    _lastFlush = now;
    _log->flush();
  }
  
  
#if defined (FLBS_PORT) && defined (BS_HACK)
  nconv = sscanf(reply+16,"%4x%n",&val1,&nc);
  if (nconv == 1 && nc == 4) {
    read_m_bs_voltage = 5000*val1/4095;
    
  }  
#endif
  
  return DeviceIF::Ok;

}

void Seabird::standby(void)
{
  char buf[10];
  sprintf(buf,  "\xd" );
  _device->write(buf, strlen(buf));
}

void Seabird::halt(void)
{
  char buf[10];
  sprintf(buf,  "\x1a" );
  _device->write(buf, strlen(buf));
}
/*-----------------------------------------------------------------------*
  (from Odyssey Code)

  First do temp conversion:

      lf = log( f0 / f Hz )

       T = 1 / (a + b*lf + c*lf^2 + d*lf^3)  - 273.15

  Next conductivity (uses temperature)

      C (S/m) = (a*(c kHz)^m + b*(c kHz)^2 + c + d*T) /
                (10*(1 + eps*P))
      where P is dbar and T is deg C
 *-----------------------------------------------------------------------*/


double Seabird::calculate_Temp(double f)
{
  double T;

  f = ::log(T_F0/f);

  T = 1/(T_A + (T_B + (T_C + T_D*f)*f)*f) - 273.15;
  
  return T;
}

double Seabird::calculate_Cond(double f, double t, double p)
{
  double C;

  //  Syslog::write("Seabird debugging:\n");
  // Syslog::write("C_A = %f\n C_B = %f\nC_C = %f\nC_D = %f\nC_M = %f, EPS = %f\n", C_A, C_B, C_C, C_D, C_M, EPS);
  //Syslog::write("f  = %f, t = %f, p = %f\n",f,t,p);

  C = (C_A*pow(f,C_M) + C_B*f*f +C_C +C_D*t)/(10*(1+EPS*p));

  //Syslog::write("C = %f\n",C);
  return C;
  
}
 
//EventCode Seabird::initializedEventCode()
//{
//  return CTDIF::Initialized;
//}
