/*--------------------------------------------------------------------------
 
                           S A N S C O O K

 This is a controller driver for a heaiting device developed especially
 for use  with SANS at SINQ by somebody at the Some God Forsaken University
 (SGFU). As this device comes with two motors in addition to the heater
 the general controller mechanism as described in choco.tex is used.

 copyright: see copyright.h

 Mark Koennecke, July 2000
---------------------------------------------------------------------------*/
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#include <fortify.h>
#include <sics.h>
#include <stringdict.h>
#include "hardsup/serialsinq.h"
#include "hardsup/el734_errcodes.h"
#include "hardsup/el734fix.h"
#include <codri.h>

/*-----------------------------------------------------------------------
  A private data structure for the SANS cooker
-------------------------------------------------------------------------*/
typedef struct {
  char *pHost;
  int iPort;
  int iChannel;
  void *pData;
  int iStop;
  int iError;
} SANSCook, *pSANSCook;
/*
  pHost, iPort and iChannel combined are the adress of the cooker
  controller at the  Macintosh terminal server. pData is the serial 
  port connection data structure needed and managed by the SerialIO
  functions.

  iError is the last error reported on this device. If no error: 0

-----------------------------------------------------------------------*/

/*
 ERROR CODES:
*/

#define NOTINIT -9001
#define POWEROFF -9002
#define LOWRANGE -9003
#define HIGHRANGE -9004
#define CONTIMEOUT  -9005
#define BADCOMMAND -9006
#define NOANSWER   -9007
#define BADINPUT   -9008
#define MOTERROR   -9009
#define CONBUSY    -9010
#define BADREPLY   -9011

/*-------------------------------------------------------------------------*/
static int CookerInit(pCodri self)
{
  pSANSCook pPriv = NULL;
  int iRet;
  char pReply[131];

  assert(self);
  pPriv = (pSANSCook) self->pPrivate;
  assert(pPriv);
  pPriv->iError = 0;

  /* first open the connection to the serial port server and channel */
  iRet = SerialOpen(&(pPriv->pData), pPriv->pHost, pPriv->iPort,
                    pPriv->iChannel);
  if (iRet <= 0) {
    pPriv->iError = iRet;
    return 0;
  }
  /* configure the connection */
  SerialATerm(&(pPriv->pData), "1\r\n");
  SerialSendTerm(&(pPriv->pData), "\r");

  pPriv->iStop = 0;

  /* switch everything on, but do not deal with errors here */
  /*
     SerialWriteRead(&(pPriv->pData),"mpon",pReply,131);
     SerialWriteRead(&(pPriv->pData),"mzon",pReply,131);
     SerialWriteRead(&(pPriv->pData),"ton",pReply,131);
   */

  return 1;
}

/*------------------------------------------------------------------------*/
static int CookerClose(pCodri self)
{
  pSANSCook pPriv = NULL;
  int iRet;
  long lVal;

  assert(self);
  pPriv = (pSANSCook) self->pPrivate;
  assert(pPriv);

  if (pPriv->pData) {
    SerialClose(&(pPriv->pData));
    pPriv->pData = NULL;
  }
  return 1;
}

/*-----------------------------------------------------------------------*/
static int CookerDelete(pCodri self)
{
  pSANSCook pPriv = NULL;

  assert(self);
  pPriv = (pSANSCook) self->pPrivate;
  assert(pPriv);

  if (pPriv->pData) {
    SerialClose(&(pPriv->pData));
    pPriv->pData = NULL;
  }

  if (pPriv->pHost)
    free(pPriv->pHost);

  free(pPriv);

  return 1;
}

/*-----------------------------------------------------------------------*/
static int CookerSetPar(pCodri self, char *parname, float fValue)
{
  pSANSCook pPriv = NULL;
  char pCommand[80], pReply[132];
  int iRet, iValue;

  assert(self);
  pPriv = (pSANSCook) self->pPrivate;
  assert(pPriv);
  pPriv->iError = 0;

  /* handle the numeric parameters */
  iValue = (int) fValue;

  if (strcmp(parname, "mp") == 0) {
    sprintf(pCommand, "mps%3.3d", iValue);
  } else if (strcmp(parname, "mz") == 0) {
    sprintf(pCommand, "mzs%3.3d", iValue);
  } else if (strcmp(parname, "ts") == 0) {
    sprintf(pCommand, "ts%3.3d", iValue);
  } else {
    pPriv->iError = BADINPUT;
    return 0;
  }
  /* send command and check for errors right here */
  iRet = SerialWriteRead(&(pPriv->pData), pCommand, pReply, 131);
  if (iRet != 1) {
    pPriv->iError = iRet;
    return 0;
  } else {
    if (strstr(pReply, "Error") != NULL) {
      pPriv->iError = MOTERROR;
      return 0;
    }
    if (strstr(pReply, "Power OFF") != NULL) {
      pPriv->iError = POWEROFF;
      return 0;
    }
    if (strstr(pReply, "not init.") != NULL) {
      pPriv->iError = NOTINIT;
      return 0;
    }
    if (strstr(pReply, "Timeout") != NULL) {
      pPriv->iError = CONTIMEOUT;
      return 0;
    }
    if (strstr(pReply, "Busy") != NULL) {
      pPriv->iError = CONBUSY;
      return 0;
    }
    if ((strstr(pReply, "Max") != NULL)
        || (strstr(pReply, "high") != NULL)) {
      pPriv->iError = HIGHRANGE;
      return 0;
    }
    if ((strstr(pReply, "Min") != NULL)
        || (strstr(pReply, "down") != NULL)) {
      pPriv->iError = LOWRANGE;
      return 0;
    }
    return 1;
  }


}

/*------------------------------------------------------------------------*/
static int CookerSetPar2(pCodri self, char *parname, char *pValue)
{
  pSANSCook pPriv = NULL;
  char pCommand[80], pReply[132];
  int iRet, iAct = 0;
  float fValue;

  assert(self);
  pPriv = (pSANSCook) self->pPrivate;
  assert(pPriv);
  pPriv->iError = 0;

  /* handle our parameters. The first set is the power settings */
  if (strcmp(parname, "mp.power") == 0) {
    if (strstr(pValue, "on") != NULL) {
      strcpy(pCommand, "mpon");
      iAct = 1;
    } else if (strstr(pValue, "off") != NULL) {
      strcpy(pCommand, "mpoff");
      iAct = 1;
    } else {
      pPriv->iError = BADINPUT;
      return 0;
    }
  } else if (strcmp(parname, "mz.power") == 0) {
    if (strstr(pValue, "on") != NULL) {
      strcpy(pCommand, "mzon");
      iAct = 1;
    } else if (strstr(pValue, "off") != NULL) {
      strcpy(pCommand, "mzoff");
      iAct = 1;
    } else {
      pPriv->iError = BADINPUT;
      return 0;
    }
  } else if (strcmp(parname, "ts.power") == 0) {
    if (strstr(pValue, "on") != NULL) {
      iAct = 1;
      strcpy(pCommand, "ton");
    } else if (strstr(pValue, "off") != NULL) {
      iAct = 1;
      strcpy(pCommand, "toff");
    } else {
      pPriv->iError = BADINPUT;
      return 0;
    }
  }
  if (iAct == 1) {
    /* send command and check for errors right here */
    iRet = SerialWriteRead(&(pPriv->pData), pCommand, pReply, 131);
    if (iRet != 1) {
      pPriv->iError = iRet;
      return 0;
    } else {
      if (strstr(pReply, "Error") != NULL) {
        pPriv->iError = MOTERROR;
        return 0;
      }
      return 1;
    }
  }


  /* second set: init */
  if (strcmp(parname, "mp.init") == 0) {
    strcpy(pCommand, "mpi");
    iAct = 1;
  } else if (strcmp(parname, "mz.init") == 0) {
    strcpy(pCommand, "mzi");
    iAct = 1;
  }
  if (iAct) {
    /* send command and check for errors right here */
    iRet = SerialWriteRead(&(pPriv->pData), pCommand, pReply, 131);
    if (iRet != 1) {
      pPriv->iError = iRet;
      return 0;
    } else {
      if (strstr(pReply, "Error") != NULL) {
        pPriv->iError = MOTERROR;
        return 0;
      }
      return 1;
    }
  }

  /*
     what is left here is either the para's themselves which will be 
     handled in the second function after convertion to float or an
     error
   */
  iRet = sscanf(pValue, "%f", &fValue);
  if (iRet != 1) {
    pPriv->iError = BADINPUT;
    return 0;
  }

  return CookerSetPar(self, parname, fValue);
}

/*-----------------------------------------------------------------------*/
static int CookerHalt(pCodri self)
{
  pSANSCook pPriv = NULL;

  assert(self);
  pPriv = (pSANSCook) self->pPrivate;
  assert(pPriv);
  pPriv->iError = 0;

  /* no commands are defined to halt a thing! */
  return 1;
}

/*----------------------------------------------------------------------*/
extern char *stptok(const char *s, char *tok, size_t toklen, char *brk);

static int CookerGetPar(pCodri self, char *parname,
                        char *pBuffer, int iLen)
{
  pSANSCook pPriv = NULL;
  char pBueffel[256], pCommand[80], pReply[80];
  float fVal;
  char *pPtr;
  int iAct = 0, iRet;

  assert(self);
  pPriv = (pSANSCook) self->pPrivate;
  assert(pPriv);
  pPriv->iError = 0;

  /* first the power stuff */
  if (strcmp(parname, "mp.power") == 0 || strcmp(parname, "mp.init") == 0) {
    strcpy(pBueffel, parname);
    strcpy(pCommand, "mps");
    iAct = 1;
  } else if (strcmp(parname, "mz.power") == 0
             || strcmp(parname, "mz.init") == 0) {
    strcpy(pBueffel, parname);
    strcpy(pCommand, "mzs");
    iAct = 1;
  } else if (strcmp(parname, "ts.power") == 0) {
    strcpy(pBueffel, parname);
    strcpy(pCommand, "ts");
    iAct = 1;
  }
  if (iAct) {
    iRet = SerialWriteRead(&(pPriv->pData), pCommand, pReply, 80);
    if (iRet != 1) {
      pPriv->iError = iRet;
      return 0;
    } else {
      if (strstr(pReply, "OFF") != NULL || strstr(pReply, "Off") != NULL) {
        strcat(pBueffel, " off");
      } else if (strstr(pReply, "not init") != NULL) {
        strcat(pBueffel, " not");
      } else {
        strcat(pBueffel, " on");
      }

    }
    strlcpy(pBuffer, pBueffel, iLen);
    return 1;
  }

  /* now request values for the parameter */
  if (strcmp(parname, "mp") == 0) {
    strcpy(pCommand, "mps");
  } else if (strcmp(parname, "mz") == 0) {
    strcpy(pCommand, "mzs");
  } else if (strcmp(parname, "ts") == 0) {
    strcpy(pCommand, "ts");
  } else {
    pPriv->iError = BADINPUT;
    return 0;
  }
  iRet = SerialWriteRead(&(pPriv->pData), pCommand, pReply, 80);
  if (iRet != 1) {
    pPriv->iError = iRet;
    return 0;
  }
  /* decode value */
  pPtr = pReply;
  pPtr = stptok(pPtr, pBueffel, 255, "/\0");
  pPtr = stptok(pPtr, pBueffel, 255, "/\0");
  iRet = sscanf(pBueffel, "%f", &fVal);
  if (iRet != 1) {
    pPriv->iError = BADREPLY;
    return 0;
  }
  sprintf(pBueffel, "%f", fVal);
  strlcpy(pBuffer, pBueffel, iLen);
  return 1;
}

/*-----------------------------------------------------------------------*/
static int CookerCheckPar(pCodri self, char *parname)
{
  pSANSCook pPriv = NULL;
  char pCommand[80], pReply[80];
  int iRet;

  assert(self);
  pPriv = (pSANSCook) self->pPrivate;
  assert(pPriv);

  if (strcmp(parname, "mp") == 0) {
    strcpy(pCommand, "mps");
  } else if (strcmp(parname, "mz")) {
    strcpy(pCommand, "mzs");
  } else {
    pPriv->iError = BADINPUT;
    return 0;
  }

  /* get a status reply */
  iRet = SerialWriteRead(&(pPriv->pData), pCommand, pReply, 80);
  if (iRet != 1) {
    pPriv->iError = iRet;
    return HWFault;
  }

  if (strstr(pReply, "Start") != NULL || strstr(pReply, "Move") != NULL) {
    return HWBusy;
  } else {
    return HWIdle;
  }
  return HWIdle;
}

/*---------------------------------------------------------------------*/
static int CookerError(pCodri self, int *iCode, char *pError, int iLen)
{
  pSANSCook pPriv = NULL;
  char pCommand[80], pReply[80];
  int iRet;

  assert(self);
  pPriv = (pSANSCook) self->pPrivate;
  assert(pPriv);

  *iCode = pPriv->iError;
  switch (pPriv->iError) {
  case NOTINIT:
    strlcpy(pError, "ERROR: NOT Initialized!", iLen);
    break;
  case POWEROFF:
    strlcpy(pError, "ERROR: Power is OFF", iLen);
    break;
  case LOWRANGE:
    strlcpy(pError, "ERROR: Lower limit violated", iLen);
    break;
  case HIGHRANGE:
    strlcpy(pError, "ERROR: Upper limit violated", iLen);
    break;
  case CONTIMEOUT:
    strlcpy(pError, "ERROR: Internal controller timeout ", iLen);
    break;
  case BADCOMMAND:
    strlcpy(pError, "ERROR: Controller did not understand command", iLen);
    break;
  case BADINPUT:
    strlcpy(pError, "A bad parameter was entered", iLen);
    break;
  case MOTERROR:
    strlcpy(pError, "ERROR: Iternal motor error in controller", iLen);
    break;
  case CONBUSY:
    strlcpy(pError, "ERROR: Controller is busy", iLen);
    break;
  default:
    SerialError(pPriv->iError, pError, iLen);
    break;
  }
  pPriv->iError = 0;
  return 1;
}

/*----------------------------------------------------------------------*/
static int CookerFix(pCodri self, int iCode)
{
  pSANSCook pPriv = NULL;
  int iRet;

  assert(self);
  pPriv = (pSANSCook) self->pPrivate;
  assert(pPriv);

  switch (iCode) {
  case NOTINIT:
  case POWEROFF:
  case LOWRANGE:
  case HIGHRANGE:
  case BADCOMMAND:
  case BADINPUT:
  case MOTERROR:
    return CHFAIL;
  case CONTIMEOUT:
  case CONBUSY:
    return CHREDO;
    /* 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:
  case NOCONNECTION:
    SerialForceClose(&(pPriv->pData));
    pPriv->pData = NULL;
    iRet = SerialOpen(&(pPriv->pData), pPriv->pHost,
                      pPriv->iPort, pPriv->iChannel);
    if (iRet == 1) {
      return CHREDO;
    } else {
      return CHFAIL;
    }
    break;
  case EL734__FORCED_CLOSED:
    iRet = CookerInit(self);
    if (iRet) {
      return CHREDO;
    } else {
      return CHFAIL;
    }
    break;
  default:
    return CHFAIL;
    break;

  }
}

/*------------------------------------------------------------------------*/
pCodri MakeCookerDriver(char *pHost, int iPort, int iChannel)
{
  pCodri pNew = NULL;
  pSANSCook pPriv = NULL;
  char *pText;

  /* allocate memory */
  pText = (char *) malloc(1024 * sizeof(char));
  pNew = (pCodri) malloc(sizeof(Codri));
  pPriv = (pSANSCook) malloc(sizeof(SANSCook));
  if (!pText || !pNew || !pPriv) {
    return NULL;
  }
  memset(pText, 0, 1024);
  memset(pNew, 0, sizeof(Codri));
  memset(pPriv, 0, sizeof(SANSCook));

  /* initialize private data structure */
  pPriv->pHost = strdup(pHost);
  pPriv->iPort = iPort;
  pPriv->iChannel = iChannel;
  pPriv->pData = NULL;

  /* set known parameter names */
  strcpy(pText, "mp");
  strcat(pText, ",mp.power");
  strcat(pText, ",mp.init");
  strcat(pText, ",mz");
  strcat(pText, ",mz.power");
  strcat(pText, ",mz.init");
  strcat(pText, ",ts");
  strcat(pText, ",ts.power");


  /* install codri */
  pNew->Init = CookerInit;
  pNew->Close = CookerClose;
  pNew->Delete = CookerDelete;
  pNew->SetPar = CookerSetPar;
  pNew->SetPar2 = CookerSetPar2;
  pNew->GetPar = CookerGetPar;
  pNew->CheckPar = CookerCheckPar;
  pNew->GetError = CookerError;
  pNew->TryFixIt = CookerFix;
  pNew->Halt = CookerHalt;
  pNew->pParList = pText;
  pNew->pPrivate = pPriv;

  return pNew;
}
