/****************************************************************************/
/* Copyright (c) 2000 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  :                                                               */
/* Filename : CTD.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 "CTD.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 750
#define CTDPERIOD 1000
#define SHORT_WAIT 4000
#define LONG_WAIT 8000

Ctd::Ctd(SerialDevice *serialDevice, char *configFile )
     : StreamSerialDriver("ctdDriver", serialDevice, MaxRecordBytes, "\xa",
			  READ_TIMEOUT),
       attributes("ctdDriver")
{
  _output = new CTDOutput(SharedData::ReadWrite);

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

  Parosci_Running = 0;

  Syslog::write("Ctd::Ctd() - create interface to depth sensor...");
  try {
       parosci = new ParosciIF("parosci");
       Parosci_Running = 1;
  } 
  catch(SharedObjectClient::MissingServer e) {
    parosci_pres = 10.1;
    Parosci_Running = 0;
    Syslog::write("CTD.cc -- Parosci server is not running. "
		  "Using default pres of %lf", parosci_pres);
  }
  
  Syslog::write("CTD.cc -- constructing Log...\n");
  try {
  _log = new CtdLog(this);
  }
  catch ( ... ) {
    throw Exception("Ctd:Ctd() - CtdLog constructor failed\n");
  }
  Syslog::write("CTD.cc -- constructor succeeded\n");
}

void Ctd::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",&v1name) );
     attributes.add( new StringAttribute("v2name","v2name",&v2name) );
     attributes.add( new StringAttribute("v3name","v3name",&v3name) );
     attributes.add( new StringAttribute("v4name","v4name",&v4name) );
     attributes.add( new StringAttribute("v5name","v5name",&v5name) );
     attributes.add( new StringAttribute("v6name","v6name",&v6name) );

		     
}

void Ctd::reportCTDAttributes()
{
     Syslog::write("CTD.cc -- 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",
		   T_A, T_B, T_C, T_D, T_F0,
		   C_A, C_B, C_C, C_D, C_M, EPS );
}

void Ctd::loadCTDConfigFile( char *filename )
{
     Syslog::write("CTD.cc -- Loading CTD constants from %s\n", filename );
     AttributeParser::parse(filename, &attributes); 
}


Ctd::~Ctd()
{
delete _output;
delete parosci;
delete _log ;
}


void Ctd::initialize()
{
  char buf[30];
  int i;
   
  _device->commsDebugMode(SerialDevice::DebugOff);
 
  Syslog::write("CTD:initializing CTD...\n");

  // 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("Ctd::initialize() - confirm() timed out; sleep a bit");
    sleep(5);
  }

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

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

    /* 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("CTD: first Ctrl-Z  sent...\n");
      
    if (_device->confirm("S>",LONG_WAIT) == -1) {
      i = _device->confirm("power on", 4000 );
      if (i) {
	Syslog::write("CTD: confirmed power on returns: %d\n",i);
      } 
      if (i==-1) { 
	Syslog::write("CTD: no power on detected yet...\n");
	sprintf(buf,"\r\r\r");
	_device->write(buf, strlen(buf));
	i = _device->confirm("power on", 4000);
	  
	if (i==-1) {
	  Syslog::write("CTD: no power on detected yet...\n");
	  sprintf(buf,"\r\r\r");
	  _device->write(buf, strlen(buf));
	      
	  i = _device->confirm("power on", 8000);
	      
	  _device->clearPort(); 
	  if (i==-1) {
	    throw Exception("CTD: no power on detected..bailing!\n");
	  }
	}
      }
    }
  }
  
  
  /* configure */
  
  Syslog::write("CTD: sending configure commands...\n");
  
  sprintf(buf,"CC\xd");
  _device->write(buf, strlen(buf));
  if ( _device->confirm("Y/N ? ", SHORT_WAIT) == -1 ) {
    throw Exception("CTD: 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 ) {
    throw Exception("CTD: 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("CTD: init failed waiting for scans line\n");
  }
   
  /* average 1 scans */
  Syslog::write("CTD: setting scans...\n");
  sprintf(buf,  "1\xd" );
  _device->write(buf, strlen(buf));
   
  if ( _device->confirm( "new value = ", SHORT_WAIT) == -1 ) {
    throw Exception("CTD: init failed waiting for pump freq msg \n");
  }
   
   
  /*
    (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("CTD: setting pump freq...\n");
  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("Ctd::initialize() failed waiting for upcast msg\n");
  }
				/* stop on upcast? */
  Syslog::write("CTD: setting upcast...\n");
  sprintf(buf,  "\xd" );
  _device->write(buf, strlen(buf));
      
  if ( _device->confirm( "new", LONG_WAIT) == -1 ) {
    throw Exception("Ctd::initialize() failed while setting upcast\n");
  }
				/* battery type */
  Syslog::write("CTD: setting battery type...\n");
  sprintf(buf,  "\xd" );
  _device->write(buf, strlen(buf));

  if ( _device->confirm( "new value = ", SHORT_WAIT) == -1 ) {
    throw Exception("Ctd::initialize() failed setting battery type\n");
  }
				/* external voltages */
  Syslog::write("CTD: setting external voltages to %d...\n", NUMVOLTAGES);
  sprintf(buf,  "%d\xd",NUMVOLTAGES );
  _device->write(buf, strlen(buf));
     
  if ( _device->confirm( "new value =", SHORT_WAIT) == -1 ) {
     throw Exception("Ctd::initialize() failed setting external voltages\n");
  }
  // now loop for all voltages
  for (i=1;i<=NUMVOLTAGES;i++)
    {
      Syslog::write("Ctd::initialize() - confirming voltage...\n");
      sprintf(buf,  "Y\xd" );
      _device->write(buf, strlen(buf));
      
      if (i < NUMVOLTAGES)
	{
	  if (_device->confirm("new value =", SHORT_WAIT) == -1)
	    
	    {
	      Syslog::write("...failed on  %d\n",i);
	      throw Exception("CTD::initialize() failed on external voltage setup\n");
	    }
	}
    }
				/* voltage 1 */
  //  _device->commsDebugMode(SerialDevice::DebugOn);
  if ( _device->confirm( "S>", 5*LONG_WAIT) == -1 ) {
    Syslog::write("CTD: error reading S> prompt\n");
    throw Exception("Ctd::initialize() failed confirming voltage\n");
  }
				/* disable switch */
  Syslog::write("Ctd::initialize() - disabling switch...\n");
  sprintf(buf,  "SD\xd" );
  _device->write(buf, strlen(buf));

  if ( _device->confirm( "S>", SHORT_WAIT) == -1 ) {
    throw Exception("Ctd::initialize() failed disabling switch\n");
  }  
				/* DS to display setup into syslog */
  Syslog::write("CTD: sending DS command...\n");
  sprintf(buf,  "DS\xd" );
  _device->write(buf, strlen(buf));
     
  if ( _device->confirm( "S>", 5*LONG_WAIT) == -1 ) {       
    throw Exception("Ctd::initialize() failed on DS command\n");
  }

  Syslog::write("Ctd::initialize() - starting logging...\n");

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

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

  sprintf(buf,  "Y\xd" );
  _device->write(buf, strlen(buf));
  // Cntrl-Y to confirm
  if (_device->confirm("^Y/N ? ",SHORT_WAIT) == -1) {
    throw Exception("CTD: failed waiting for ctrl-yes/no prompt\n");
  }
  
  //\x19 is Ctrl-Y (!)
  sprintf(buf,  "\x19\xd" );
  _device->write(buf, strlen(buf));
      
  if (_device->confirm("S>",SHORT_WAIT) == -1) {
    throw Exception("CTD: failed waiting for S> prompt\n");
  }
  //done with init
  Syslog::write("Ctd:initialize() - Logging Started...\n");

  _output->data.deviceReady = True;
  _output->write();
  Syslog::write("Ctd:initialize() - ready!");
}
 

void Ctd::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("CTD: tfreq: %f, temp: %f\n",read_tfreq,read_temp);
    clock_gettime(CLOCK_REALTIME,&temp_readtime);
  }
  else {
    Syslog::write("CTD: error sscanfing %d bytes in reply: %s\n", 
		  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);
	  
    // ??!????
	  
    long t;
      
    if (Parosci_Running) {
      parosci->pressure(&parosci_pres, &t);
      parosci_pres = 10*parosci_pres;
    }
    else {
      parosci_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,parosci_pres);

  }
  else
    {
      Syslog::write("CTD Driver: error sscanf'ing conductivity data string\n");
    }
  
  offset = 16;
  for (i=0;i< NUMVOLTAGES;i++)
    {
      //      Syslog::write("CTD: 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("CTD: nconv: %d, val1: %3x, nc: %d\n",nconv,val1,nc);
      
      if ((nconv==1)&&(nc==3))
	{
	  voltages[i] = ((double)val1)/819;
	  //	  Syslog::write("CTD: value of %d is: %f\n",i+1,voltages[i]);
	  //  dprintf("CTD: value of %d is: %f\n",i+1,voltages[i]);
	  
	}
      else
	{
	  Syslog::write("CTD: error reading voltages\n");
	  dprintf("CTD: error reading voltages\n");
 
	}
      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];
    }
  strcpy(_output->data.v1name, v1name);
  strcpy(_output->data.v2name, v2name);
  strcpy(_output->data.v3name, v3name);
  strcpy(_output->data.v4name, v4name);
  strcpy(_output->data.v5name, v5name);
  strcpy(_output->data.v6name, v6name);

  try
    {
      _output->write();
      
    }
  catch (SharedData::AccessError error)
    {
      fprintf(stderr, "%s",error.msg);
      exit(1);
    }
  // now write to log file
  _log->write();
  
  
#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
  
  

}

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

void Ctd::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 Ctd::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 Ctd::calculate_Cond(double f, double t, double p)
{
  double C;

  //  Syslog::write("CTD 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 Ctd::initializedEventCode()
{
  return CTDIF::Initialized;
}
