/*-------------------------------------------------------------------------
                           L T C 1 1
    an environment control device driver for a Neocera LTC-11 temperature
    controller.                        
    
    copyright: see copyright.h
    
    Mark Koennecke, November 1998                   
---------------------------------------------------------------------------*/
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <tcl.h>
#include <math.h>
#include <assert.h>
#include <fortify.h>
#include <sics.h>
#include <obpar.h>
#include <evcontroller.h>
#include <evcontroller.i>
#include <evdriver.i>
#include "hardsup/serialsinq.h"
#include "hardsup/el734_errcodes.h"
#include "hardsup/el734fix.h"
#include "ltc11.h"

/*
#define debug 1
*/
/*-----------------------------------------------------------------------
             The LTC11 Data Structure 
*/
typedef struct {
  void *pData;
  char *pHost;
  int iPort;
  int iChannel;
  int iMode;
  int iSensor;
  int iControlHeat;
  int iControlAnalog;
  int iLastError;
  time_t lastRequest;
  float fLast;
} LTC11Driv, *pLTC11Driv;
/*-----------------------------------------------------------------------
  A couple of defines for LTC11 modes and special error conditions
*/
#define ANALOG 2
#define HEATER 1
#define MISERABLE 3

/* errors */
#define BADSTATE -920
#define NOCONN   -921
#define BADANSWER -923
#define BADCONFIRM -924
/*-----------------------------------------------------------------------*/
static void LTC11Unlock(pLTC11Driv self)
{
  SerialNoReply(&(self->pData), "SLLOCK 0;");
}

/*-------------------------------------------------------------------------
  The LTC11 can either control a heater or an analog output. It is a common
  task to figure out which mode is active. If the value returned from QOUT
  is 3, no sensor is defined, if it is 6 it is in monitor mode, in both cases
  control is NOT there.
*/
int LTC11GetMode(pEVDriver pEva, int *iMode)
{
  pLTC11Driv self = NULL;
  int iRet, iiMode;
  char pBueffel[80];

  self = (pLTC11Driv) pEva->pPrivate;
  assert(self);

  if (self->pData == NULL) {
    self->iLastError = NOCONN;
    return 0;
  }

  /* query the state, it can be in an invalid mode */
  iRet = SerialWriteRead(&(self->pData), "QISTATE?;", pBueffel, 79);
  LTC11Unlock(self);
  if (iRet != 1) {
    self->iLastError = iRet;
    return 0;
  }
  if (strcmp(pBueffel, "?TMO") == 0) {
    self->iLastError = TIMEOUT;
    return 0;
  }
  if (sscanf(pBueffel, "%d", &iiMode) != 1) {
    self->iLastError = EL734__BAD_ILLG;
    return 0;
  }
  if ((iiMode != 1) && (iiMode != 2)) {
    self->iLastError = BADSTATE;
    *iMode = MISERABLE;
    return 0;
  }

  /* check the sensor in heater mode */
  iRet = SerialWriteRead(&(self->pData), "QOUT?1;", pBueffel, 79);
  LTC11Unlock(self);
  if (iRet != 1) {
    self->iLastError = iRet;
    return 0;
  }
  if (strcmp(pBueffel, "?TMO") == 0) {
    self->iLastError = TIMEOUT;
    return 0;
  }
  if (sscanf(pBueffel, "%d", &iiMode) != 1) {
    self->iLastError = EL734__BAD_ILLG;
    return 0;
  }
  if ((iiMode != 3) && (iiMode != 6)) {
    *iMode = HEATER;
    self->iControlHeat = iiMode;
    return 1;
  }

  /* check the sensor in analog mode */
  iRet = SerialWriteRead(&(self->pData), "QOUT?2;", pBueffel, 79);
  LTC11Unlock(self);
  if (iRet != 1) {
    self->iLastError = iRet;
    return 0;
  }
  if (strcmp(pBueffel, "?TMO") == 0) {
    self->iLastError = TIMEOUT;
    return 0;
  }
  if (sscanf(pBueffel, "%d", &iiMode) != 1) {
    self->iLastError = EL734__BAD_ILLG;
    return 0;
  }
  if ((iiMode != 3) && (iiMode != 6)) {
    *iMode = ANALOG;
    self->iControlAnalog = iiMode;
    return 1;
  }
  /* if we are here something is very bad */
  self->iLastError = BADSTATE;
  return 0;
}

/*-----------------------------------------------------------------------
  iMode below 10 will be interpreted as heater control, above 10 as analog
  control.
*/
int LTC11SetMode(pEVDriver pEva, int iMode)
{
  pLTC11Driv self = NULL;
  int iRet, iiMode;
  char pBueffel[80], pCommand[20];

  self = (pLTC11Driv) pEva->pPrivate;
  assert(self);

  if (self->pData == NULL) {
    self->iLastError = NOCONN;
    return 0;
  }

  if (iMode < 10) {             /* heater mode */
    sprintf(pCommand, "SHCONT%1.1d;", iMode);
    iRet = SerialNoReply(&(self->pData), pCommand);
    LTC11Unlock(self);
    if (iRet != 1) {
      self->iLastError = iRet;
      return 0;
    }
    return 1;
  } else {
    iMode -= 10;
    sprintf(pCommand, "SACONT%1.1d;", iMode);
    iRet = SerialNoReply(&(self->pData), pCommand);
    LTC11Unlock(self);
    if (iRet != 1) {
      self->iLastError = iRet;
      return 0;
    }
    return 1;
  }
  /* should not get here */
  self->iLastError = BADSTATE;
  return 0;
}

/*-------------------------------------------------------------------------*/
static int LTC11Get(pEVDriver pEva, float *fValue)
{
  pLTC11Driv self = NULL;
  int iRet;
  char pBueffel[80];
  char pCommand[46];
  char c;
  float fVal;

  self = (pLTC11Driv) pEva->pPrivate;
  assert(self);

  if (self->pData == NULL) {
    self->iLastError = NOCONN;
    return 0;
  }

  if (time(NULL) < self->lastRequest) {
    *fValue = self->fLast;
    return 1;
  } else {
    self->lastRequest = time(NULL) + 5; /* buffer 5 seconds */
  }
  sprintf(pCommand, "QSAMP?%1.1d;", self->iSensor);
  iRet = SerialWriteRead(&(self->pData), pCommand, pBueffel, 79);
  LTC11Unlock(self);
  if (iRet != 1) {
    self->iLastError = iRet;
    return 0;
  }
  if (strcmp(pBueffel, "?TMO") == 0) {
    self->iLastError = TIMEOUT;
    return 0;
  }
  iRet = sscanf(pBueffel, "%f%c", fValue, &c);
  if (iRet != 2) {
    self->iLastError = BADANSWER;
    return 0;
  }
  if ((c != 'K') && (c != 'C') && (c != 'F') && (c != 'N')
      && (c != 'V') && (c != 'O')) {
    self->iLastError = BADANSWER;
    return 0;
  }
  self->fLast = *fValue;
  return 1;
}

/*-------------------------------------------------------------------------*/
static int LTC11Run(pEVDriver pEva, float fVal)
{
  pLTC11Driv self = NULL;
  int iRet, iMode;
  char pBueffel[80];
  char pCommand[40];
  float fTest = 0.0, fDelta;

  self = (pLTC11Driv) pEva->pPrivate;
  assert(self);

  if (self->pData == NULL) {
    self->iLastError = NOCONN;
    return 0;
  }

  /* find our operation mode */
  iRet = LTC11GetMode(pEva, &iMode);
  if ((iRet < 1) || (iMode == MISERABLE)) {
    return 0;
  }

  /* format command */
  sprintf(pCommand, "SETP %d,%f;", iMode, fVal);

  /* send command */
  iRet = SerialNoReply(&(self->pData), pCommand);
  if (iRet != 1) {
    self->iLastError = iRet;
    LTC11Unlock(self);
    return 0;
  }

  /* read back */
  sprintf(pCommand, "QSETP?%d;", iMode);
  iRet = SerialWriteRead(&(self->pData), pCommand, pBueffel, 79);
  LTC11Unlock(self);
  if (iRet != 1) {
    self->iLastError = iRet;
    return 0;
  }

  /* check confirmation */
  if (strcmp(pBueffel, "?TMO") == 0) {
    self->iLastError = TIMEOUT;
    return 0;
  }
  sscanf(pBueffel, "%f", &fTest);
  fDelta = fVal - fTest;
  if (fDelta < 0.0)
    fDelta = -fDelta;

  if (fDelta > 0.1) {
    self->iLastError = BADCONFIRM;
    return 0;
  }

  return 1;
}

/*------------------------------------------------------------------------*/
static int LTC11Error(pEVDriver pEva, int *iCode, char *pError,
                      int iErrLen)
{
  pLTC11Driv self = NULL;

  self = (pLTC11Driv) pEva->pPrivate;
  assert(self);

  *iCode = self->iLastError;
  switch (*iCode) {
  case NOCONN:
    strlcpy(pError, "No Connection to Bruker Controller", iErrLen);
    break;
  case MISERABLE:
  case BADSTATE:
    strlcpy(pError, "The LTC-11 is in a very bad state", iErrLen);
    break;
  case BADANSWER:
    strlcpy(pError, "The LTC-11 returned a bad reply", iErrLen);
    break;
  case BADCONFIRM:
    strlcpy(pError, "The LTC-11 did not accept the new set point",
            iErrLen);
    break;
  case TIMEOUT:
    strlcpy(pError, "Timeout receiving data from LTC-11", iErrLen);
    break;
  default:
    SerialError(*iCode, pError, iErrLen);
    break;
  }
  return 1;
}

/*---------------------------------------------------------------------------*/
static int LTC11Send(pEVDriver pEva, char *pCommand, char *pReply,
                     int iReplyLen)
{
  pLTC11Driv self = NULL;
  int iRet;

  self = (pLTC11Driv) pEva->pPrivate;
  assert(self);

  if (self->pData == NULL) {
    self->iLastError = NOCONN;
    return 0;
  }


  iRet = SerialWriteRead(&(self->pData), pCommand, pReply, iReplyLen);
  if (iRet != 1) {
    self->iLastError = iRet;
    return 0;
  }
  return 1;
}

/*--------------------------------------------------------------------------*/
static int LTC11Init(pEVDriver pEva)
{
  pLTC11Driv self = NULL;
  int iRet;
  char pBueffel[80], pCommand[20];

  self = (pLTC11Driv) pEva->pPrivate;
  assert(self);

  /* open port connection */
  self->pData = NULL;
  iRet =
      SerialOpen(&(self->pData), self->pHost, self->iPort, self->iChannel);
  if (iRet != 1) {
    self->iLastError = iRet;
    return 0;
  }
  /* configure serial port terminators */
  SerialSendTerm(&(self->pData), ";");
  SerialATerm(&(self->pData), "1;");
  SerialConfig(&(self->pData), 30000);

  self->iSensor = 1;

  /* initialize control sensors to unknown, then call GetMode
     to get real values 
   */
  self->iControlHeat = 6;
  self->iControlAnalog = 6;
  LTC11GetMode(pEva, &iRet);

  return 1;
}

/*-------------------------------------------------------------------------*/
static int LTC11Close(pEVDriver pEva)
{
  pLTC11Driv self = NULL;

  self = (pLTC11Driv) pEva->pPrivate;
  assert(self);

  SerialClose(&(self->pData));
  self->pData = 0;

  return 1;
}

/*---------------------------------------------------------------------------*/
static int LTC11Fix(pEVDriver self, int iError)
{
  pLTC11Driv pMe = NULL;
  int iRet;
  char pCommand[20], pBueffel[80];

  assert(self);
  pMe = (pLTC11Driv) self->pPrivate;
  assert(pMe);

  switch (iError) {
    /* network errors */
  case EL734__BAD_FLUSH:
  case EL734__BAD_RECV:
  case EL734__BAD_RECV_NET:
  case EL734__BAD_RECV_UNKN:
  case EL734__BAD_RECVLEN:
  case EL734__BAD_RECV1:
  case EL734__BAD_RECV1_PIPE:
  case EL734__BAD_RNG:
  case EL734__BAD_SEND:
  case EL734__BAD_SEND_PIPE:
  case EL734__BAD_SEND_NET:
  case EL734__BAD_SEND_UNKN:
  case EL734__BAD_SENDLEN:
    LTC11Close(self);
    iRet = LTC11Init(self);
    if (iRet) {
      return DEVREDO;
    } else {
      return DEVFAULT;
    }
    break;
  case EL734__FORCED_CLOSED:
  case NOCONN:
    iRet = LTC11Init(self);
    if (iRet) {
      return DEVREDO;
    } else {
      return DEVFAULT;
    }
    break;
    /* fixable LTC11 Errors */
  case MISERABLE:
  case BADSTATE:
    iRet = SerialNoReply(&(pMe->pData), "SCONT;");
    LTC11Unlock(pMe);
    return DEVREDO;
    break;
  case BADANSWER:
  case BADCONFIRM:
  case TIMEOUT:
    return DEVREDO;
    break;
  default:
    return DEVFAULT;
    break;
  }
  return DEVFAULT;
}

/*------------------------------------------------------------------------*/
void KillLTC11(void *pData)
{
  pLTC11Driv pMe = NULL;

  pMe = (pLTC11Driv) pData;
  assert(pMe);

  if (pMe->pHost) {
    free(pMe->pHost);
  }
  free(pMe);
}

/*------------------------------------------------------------------------*/
pEVDriver CreateLTC11Driver(int argc, char *argv[])
{
  pEVDriver pNew = NULL;
  pLTC11Driv pSim = NULL;

  /* check for arguments */
  if (argc < 3) {
    return NULL;
  }

  pNew = CreateEVDriver(argc, argv);
  pSim = (pLTC11Driv) malloc(sizeof(LTC11Driv));
  memset(pSim, 0, sizeof(LTC11Driv));
  if (!pNew || !pSim) {
    return NULL;
  }
  pNew->pPrivate = pSim;
  pNew->KillPrivate = KillLTC11;

  /* initalise LTC11Driver */
  pSim->iLastError = 0;
  pSim->pHost = strdup(argv[0]);
  pSim->iPort = atoi(argv[1]);
  pSim->iChannel = atoi(argv[2]);


  /* initialise function pointers */
  pNew->SetValue = LTC11Run;
  pNew->GetValue = LTC11Get;
  pNew->Send = LTC11Send;
  pNew->GetError = LTC11Error;
  pNew->TryFixIt = LTC11Fix;
  pNew->Init = LTC11Init;
  pNew->Close = LTC11Close;

  return pNew;
}

/*------------------------------------------------------------------------*/
static int LTC11AssignControl(pEVDriver pEva, int iMode, int iSensor)
{
  pLTC11Driv self = NULL;
  int iRet, iRead = 0;
  char pBueffel[80], pCommand[50];

  self = (pLTC11Driv) pEva->pPrivate;
  assert(self);
  assert((iMode == HEATER) || (iMode == ANALOG));

  if (!self->pData) {
    self->iLastError = NOCONN;
    return 0;
  }
  sprintf(pCommand, "SOSEN %d,%d;", iMode, iSensor);
  iRet = SerialNoReply(&(self->pData), pCommand);
  if (iRet != 1) {
    self->iLastError = iRet;
    return 0;
  }
  sprintf(pCommand, "QOUT?%d;", iMode);
  iRet = SerialWriteRead(&(self->pData), pCommand, pBueffel, 79);
  LTC11Unlock(self);
  if (strcmp(pBueffel, "?TMO") == 0) {
    self->iLastError = TIMEOUT;
    return 0;
  }
  sscanf(pBueffel, "%d;", &iRead);
  if (iRead != iSensor) {
    self->iLastError = BADCONFIRM;
    return 0;
  }
  if (iMode == ANALOG) {
    self->iControlAnalog = iSensor;
  } else {
    self->iControlHeat = iSensor;
  }
  /* switch back to control mode */
  SerialNoReply(&(self->pData), "SCONT;");
  return 1;
}

/*--------------------------------------------------------------------------
  handle LTC11 specific commands:
  - sensor           requests or sets read sensor
  - mode             requests or sets operation mode
  - controlheat      requests or sets sensor for heater control
  - controlanalog    requests or sets sensor for analog control
  in all other cases fall back and call EVControllerWrapper to handle it or
  eventually throw an error.
*/
int LTC11Action(SConnection * pCon, SicsInterp * pSics, void *pData,
                int argc, char *argv[])
{

  pEVControl self = NULL;
  int iRet, iMode;
  char pBueffel[256], pError[132];
  pLTC11Driv pMe = NULL;
  float fVal;

  self = (pEVControl) pData;
  assert(self);
  pMe = (pLTC11Driv) self->pDriv->pPrivate;
  assert(pMe);

  if (argc > 1) {
    strtolower(argv[1]);
/*------ sensor */
    if (strcmp(argv[1], "sensor") == 0) {
      if (argc > 2) {           /* set case */
        /* check permission */
        if (!SCMatchRights(pCon, usUser)) {
          return 0;
        }
        iRet = Tcl_GetInt(pSics->pTcl, argv[2], &iMode);
        if (iRet != TCL_OK) {
          sprintf(pBueffel, "ERROR: needed integer, got %s", argv[2]);
          SCWrite(pCon, pBueffel, eError);
          return 0;
        }
        pMe->iSensor = iMode;
        SCSendOK(pCon);
        return 1;
      } else {                  /* get case */

        sprintf(pBueffel, "%s.sensor = %d", argv[0], pMe->iSensor);
        SCWrite(pCon, pBueffel, eValue);
        return 1;
      }
    }
/*------ controlanalog */
    if (strcmp(argv[1], "controlanalog") == 0) {
      if (argc > 2) {           /* set case */
        /* check permission */
        if (!SCMatchRights(pCon, usUser)) {
          return 0;
        }
        iRet = Tcl_GetInt(pSics->pTcl, argv[2], &iMode);
        if (iRet != TCL_OK) {
          sprintf(pBueffel, "ERROR: needed integer, got %s", argv[2]);
          SCWrite(pCon, pBueffel, eError);
          return 0;
        }
        iRet = LTC11AssignControl(self->pDriv, ANALOG, iMode);
        if (iRet != 1) {
          self->pDriv->GetError(self->pDriv, &iMode, pError, 131);
          sprintf(pBueffel, "ERROR: failed to set sensor: %s", pError);
          SCWrite(pCon, pBueffel, eError);
          return 0;
        }
        SCSendOK(pCon);
        return 1;
      } else {                  /* get case */

        sprintf(pBueffel, "%s.controlanalog = %d", argv[0],
                pMe->iControlAnalog);
        SCWrite(pCon, pBueffel, eValue);
        return 1;
      }
    }
/*------ controlheat */
    if (strcmp(argv[1], "controlheat") == 0) {
      if (argc > 2) {           /* set case */
        /* check permission */
        if (!SCMatchRights(pCon, usUser)) {
          return 0;
        }
        iRet = Tcl_GetInt(pSics->pTcl, argv[2], &iMode);
        if (iRet != TCL_OK) {
          sprintf(pBueffel, "ERROR: needed integer, got %s", argv[2]);
          SCWrite(pCon, pBueffel, eError);
          return 0;
        }
        iRet = LTC11AssignControl(self->pDriv, HEATER, iMode);
        if (iRet != 1) {
          self->pDriv->GetError(self->pDriv, &iMode, pError, 131);
          sprintf(pBueffel, "ERROR: failed to set sensor: %s", pError);
          SCWrite(pCon, pBueffel, eError);
          return 0;
        }
        SCSendOK(pCon);
        return 1;
      } else {                  /* get case */

        sprintf(pBueffel, "%s.controlheat = %d", argv[0],
                pMe->iControlHeat);
        SCWrite(pCon, pBueffel, eValue);
        return 1;
      }
    }
/*-------- mode */
    else if (strcmp(argv[1], "mode") == 0) {
      if (argc > 2) {           /* set case */
        /* check permission */
        if (!SCMatchRights(pCon, usUser)) {
          return 0;
        }
        iRet = Tcl_GetInt(pSics->pTcl, argv[2], &iMode);
        if (iRet != TCL_OK) {
          sprintf(pBueffel, "ERROR: needed integer, got %s", argv[2]);
          SCWrite(pCon, pBueffel, eError);
          return 0;
        }
        iRet = LTC11SetMode(self->pDriv, iMode);
        if (iRet != 1) {
          self->pDriv->GetError(self->pDriv, &iMode, pError, 131);
          sprintf(pBueffel, "ERROR: failed to set mode %s", pError);
          SCWrite(pCon, pBueffel, eError);
          return 0;
        } else {
          SCSendOK(pCon);
          return 1;
        }
      } else {                  /* get case */

        iRet = LTC11GetMode(self->pDriv, &iMode);
        if (iRet != 1) {
          self->pDriv->GetError(self->pDriv, &iMode, pError, 131);
          sprintf(pBueffel, "ERROR: failed to get mode %s", pError);
          SCWrite(pCon, pBueffel, eError);
          return 0;
        }
        if (iMode == ANALOG) {
          sprintf(pBueffel, "%s.mode = Analog Control", argv[0]);
        } else {
          sprintf(pBueffel, "%s.mode = Heater Control", argv[0]);
        }
        SCWrite(pCon, pBueffel, eValue);
        return 1;
      }
    }
/*--------- list */
    else if (strcmp(argv[1], "list") == 0) {
      /* print generals first */
      EVControlWrapper(pCon, pSics, pData, argc, argv);
      /* print our add on stuff */
      sprintf(pBueffel, "%s.sensor = %d", argv[0], pMe->iSensor);
      SCWrite(pCon, pBueffel, eValue);
      sprintf(pBueffel, "%s.controlanalog = %d", argv[0],
              pMe->iControlAnalog);
      SCWrite(pCon, pBueffel, eValue);
      sprintf(pBueffel, "%s.controlheat = %d", argv[0], pMe->iControlHeat);
      SCWrite(pCon, pBueffel, eValue);
      iRet = LTC11GetMode(self->pDriv, &iMode);
      if (iRet != 1) {
        self->pDriv->GetError(self->pDriv, &iMode, pError, 131);
        sprintf(pBueffel, "ERROR: failed to get mode %s", pError);
        SCWrite(pCon, pBueffel, eError);
      }
      if (iMode == ANALOG) {
        sprintf(pBueffel, "%s.mode = Analog Control", argv[0]);
      } else {
        sprintf(pBueffel, "%s.mode = Heater Control", argv[0]);
      }
      SCWrite(pCon, pBueffel, eValue);
      return 1;
    } else {
      return EVControlWrapper(pCon, pSics, pData, argc, argv);
    }
  }
  return EVControlWrapper(pCon, pSics, pData, argc, argv);
}
