/*--------------------------------------------------------------------------
                     E U R O D R I V

 This file contains the implementation for the Eurotherm temperature
 controller as used at SANS. The Eurotherm is a grossly strange device
 which has a very weird command protocoll. The implementation here uses
 the EI-Bisynch Protocoll option. This has to be configured in the 
 Eurotherm on the manual interface!! Also watch out for the unusual RS232-
 setting: 7 bits, even parity, 1 stop bit.   

 Mark Koennecke, May 1999 

 Copyright: see copyright.h
----------------------------------------------------------------------------*/
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <assert.h>
#include <fortify.h>
#include <conman.h>
#include <servlog.h>
#include <fortify.h>
#include <strlutil.h>

#include "evdriver.h"
#include "hardsup/el734_def.h"
#include "hardsup/el734fix.h"
#include "hardsup/serialsinq.h"
#include "eurodriv.h"

#define INVALIDANSWER -1005
#define INVALIDNUMBER -1006
#define ERRNAK        -1007
#define NOSEND        -1008
/*-----------------------------------------------------------------------*/
typedef struct {
  void *pData;
  char *pHost;
  int iPort;
  int iChannel;
  int iLastError;
} EuroDriv, *pEuroDriv;

/*------------------------------------------------------------------------*/
int EuroGetParameter(void **pData, char *pPar, int iLen, float *fVal)
{
  char pCommand[20];
  char pReply[20];
  char *pStart = NULL, *pEnd = NULL;
  int iRet, i;

  /* configure the serial port */
  SerialATerm(pData, "1\x3");   /* ETX */
  pCommand[0] = '\x4';          /* EOT */
  pCommand[1] = '0';            /* GID */
  pCommand[2] = '0';            /* GID */
  pCommand[3] = '1';            /* UID */
  pCommand[4] = '1';            /* UID */
  pCommand[5] = '1';            /* CHAN */
  for (i = 0; i < iLen; i++) {
    pCommand[6 + i] = pPar[i];
  }
  pCommand[6 + iLen] = '\x5';   /* ENQ */
  pCommand[7 + iLen] = '\0';

  /* send */
  iRet = SerialWriteRead(pData, pCommand, pReply, 19);
  if (iRet != 1) {
    return iRet;
  }

  /* decode reply */
  pStart = strstr(pReply, pPar);
  if (!pStart) {
    if (strstr(pReply, "?TMO")) {
      return EL734__BAD_TMO;
    } else {
      return INVALIDANSWER;
    }
  }
  iRet = sscanf(pStart + strlen(pPar), "%f", fVal);
  if (iRet != 1) {
    return INVALIDNUMBER;
  }
  return 1;
}

/*------------------------------------------------------------------------*/
int EuroSetParameter(void **pData, char *pPar, int iLen,
                     char *pFormat, float fVal)
{
  char pCommand[30];
  char pNum[10];
  char *pPtr, *pPtr2;
  char pReply[20];
  char bcc;
  int iRet, i;


  /* configure the serial port */
  SerialATerm(pData, "1\x06\x15");      /* ACK,NAK */
  pCommand[0] = '\x04';         /* EOT */
  pCommand[1] = '0';            /* GID */
  pCommand[2] = '0';            /* GID */
  pCommand[3] = '1';            /* UID */
  pCommand[4] = '1';            /* UID */
  pCommand[5] = '\x02';
  pCommand[6] = '1';            /* CHAN */
  for (i = 0; i < iLen; i++) {
    pCommand[7 + i] = pPar[i];
  }
  pPtr = pCommand + 7 + iLen;
  sprintf(pNum, pFormat, fVal);
  strcpy(pPtr, pNum);
  pPtr += strlen(pNum);
  *pPtr = '\x03';
  pPtr++;

  /* build the checksum */
  bcc = pCommand[6];
  pPtr2 = &pCommand[7];
  while (pPtr2 != pPtr) {
    bcc = bcc ^ *pPtr2;
    pPtr2++;
  }
  *pPtr = bcc;
  pPtr++;
  *pPtr = '\0';

  /* send */
  iRet = SerialSend(pData, pCommand);
  if (iRet != 1) {
    return iRet;
  }
  iRet = SerialReceiveWithTerm(pData, pReply, 19, &bcc);
/*
     printf("%s\n",pReply);
*/
  if (iRet != 1) {
    return iRet;
  }
  if (bcc == '\x15') {
    return ERRNAK;
  }
  if (strstr(pReply, "?TMO")) {
    return EL734__BAD_TMO;
  }

  return 1;
}

/*---------------------------------------------------------------------------*/
static int GetEuroPos(pEVDriver self, float *fPos)
{
  pEuroDriv pMe = NULL;
  int iRet;

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

  iRet = EuroGetParameter(&(pMe->pData), "PV", 2, fPos);
  if (iRet != 1) {
    pMe->iLastError = iRet;
    return 0;
  }
  return 1;
}

/*----------------------------------------------------------------------------*/
static int EuroRun(pEVDriver self, float fVal)
{
  pEuroDriv pMe = NULL;
  int iRet;

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

  iRet = EuroSetParameter(&(pMe->pData), "SL", 2, "%4.1f", fVal);
  if (iRet != 1) {
    pMe->iLastError = iRet;
    return 0;
  }
  return 1;
}

/*--------------------------------------------------------------------------*/
static int EuroError(pEVDriver self, int *iCode, char *error, int iErrLen)
{
  pEuroDriv pMe = NULL;

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

  *iCode = pMe->iLastError;
  switch (pMe->iLastError) {
  case INVALIDANSWER:
    strlcpy(error, "Unexpected reply from Eurotherm", iErrLen);
    break;
  case INVALIDNUMBER:
    strlcpy(error, "No number in Eurotherm answer", iErrLen);
    break;
  case ERRNAK:
    strlcpy(error, "Eurothem did NOT acknowledge command", iErrLen);
    break;
  case NOSEND:
    strlcpy(error,
            "Eurotherm has a bizarre protocoll, sending things is very STUPID",
            iErrLen);
    break;
  default:
    SerialError(pMe->iLastError, error, iErrLen);
    break;
  }

  return 1;
}

/*--------------------------------------------------------------------------*/
static int EuroSend(pEVDriver self, char *pCommand, char *pReply, int iLen)
{
  pEuroDriv pMe = NULL;

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

  pMe->iLastError = NOSEND;
  strlcpy(pReply, "ERROR: Eurotherm does not support send functionality",
          iLen);
  return 0;
}

/*--------------------------------------------------------------------------*/
static int EuroInit(pEVDriver self)
{
  pEuroDriv pMe = NULL;
  int iRet;

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

  pMe->pData = NULL;
  iRet = SerialOpen(&pMe->pData, pMe->pHost, pMe->iPort, pMe->iChannel);
  if (iRet != 1) {
    pMe->iLastError = iRet;
    return 0;
  }
  SerialSendTerm(&pMe->pData, "");
  return 1;
}

/*--------------------------------------------------------------------------*/
static int EuroClose(pEVDriver self)
{
  pEuroDriv pMe = NULL;
  int iRet;

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

  SerialClose(&pMe->pData);
  return 1;
}

/*---------------------------------------------------------------------------*/
static int EuroFix(pEVDriver self, int iError)
{
  pEuroDriv pMe = NULL;
  int iRet;

  assert(self);
  pMe = (pEuroDriv) 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:
    EuroClose(self);
    iRet = EuroInit(self);
    if (iRet) {
      return DEVREDO;
    } else {
      return DEVFAULT;
    }
    break;
    /* handable protocoll errors */
  case EL734__BAD_TMO:
    return DEVREDO;
    break;
  case ERRNAK:
  case NOSEND:
  case INVALIDANSWER:
  case INVALIDNUMBER:
    return DEVFAULT;
  default:
    return DEVFAULT;
    break;
  }
  return DEVFAULT;
}

/*--------------------------------------------------------------------------*/
static int EuroHalt(pEVDriver * self)
{
  assert(self);

  return 1;
}

/*------------------------------------------------------------------------*/
void KillEuro(void *pData)
{
  pEuroDriv pMe = NULL;

  pMe = (pEuroDriv) pData;
  assert(pMe);

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

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

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

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

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

  /* initialise function pointers */
  pNew->SetValue = EuroRun;
  pNew->GetValue = GetEuroPos;
  pNew->Send = EuroSend;
  pNew->GetError = EuroError;
  pNew->TryFixIt = EuroFix;
  pNew->Init = EuroInit;
  pNew->Close = EuroClose;

  return pNew;
}
