/*---------------------------------------------------------------------------
ease.c

basics for (ea)sy implementable (s)ample (e)nvironment devices.
handles background activity and connections over rs232.

Markus Zolliker, March 2005
----------------------------------------------------------------------------
*/

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <fortify.h>
#include <ctype.h>
#include <math.h>
#include <errno.h>
#include "sics.h"
#include "splitter.h"
#include "ease.h"

#define EASE_FLAGBITS (8*sizeof(long))

static ParClass easeBaseClass = { "EaseBase", sizeof(EaseBase) };
static ParClass easeDrivClass = { "EaseDriv", sizeof(EaseDriv) };
static int parameterChange = 0;

/*----------------------------------------------------------------------------*/
ParClass *EaseBaseClass(void)
{
  return ParMakeClass(&easeBaseClass, NULL);
}

/*----------------------------------------------------------------------------*/
ParClass *EaseDrivClass(void)
{
  return ParMakeClass(&easeDrivClass, EaseBaseClass());
}

/*----------------------------------------------------------------------------*/
EaseBase *EaseBaseCast(void *object)
{
  return ParCast(&easeBaseClass, object);
}

/*----------------------------------------------------------------------------*/
EaseDriv *EaseDrivCast(void *object)
{
  return ParCast(&easeDrivClass, object);
}

/*----------------------------------------------------------------------------*/
void EaseStop(EaseBase * eab, char *reason)
{
  if (eab->state >= EASE_idle) {
    FsmStop(eab->task, eab->idle);
    closeRS232(eab->ser);
  }
  if (reason != NULL) {
    snprintf(eab->msg, sizeof eab->msg, "offline (%s)", reason);
    ParPrintf(eab, eLogError, "ERROR: %s", eab->msg);
  }
  eab->state = EASE_notconnected;
}

/*----------------------------------------------------------------------------*/
void EaseWriteError(EaseBase * eab)
{
  int l;
  switch (eab->errCode) {
  case NOTCONNECTED:
    ParPrintf(eab, eError, "ERROR: unconnected socket for %s",
              eab->p.name);
    break;
  case FAILEDCONNECT:
    ParPrintf(eab, eError,
              "ERROR: can not connect to %s:%d (terminalserver off or no ethernet connection)",
              eab->ser->pHost, eab->ser->iPort);
    break;
  case TIMEOUT:
    ParPrintf(eab, eError, "ERROR: timeout on %s\ncmd: %s\nans: %s",
              eab->p.name, eab->cmd, eab->ans);
    break;
  case INCOMPLETE:
    ParPrintf(eab, eError, "ERROR: incomplete answer %s from %s", eab->ans,
              eab->p.name);
    break;
  case EASE_ILL_ANS:
    if (eab->p.verbose < 1)
      break;
    l = strlen(eab->cmd);
    if (l > 0 && eab->cmd[l - 1] < ' ') {
      l--;
    }
    ParPrintf(eab, eError, "ERROR: illegal answer %s from %s (cmd: %*s)",
              eab->ans, eab->p.name, l, eab->cmd);
    break;
  case EASE_DEV_CHANGED:
    ParPrintf(eab, eError, "ERROR: controller was exchanged on %s",
              eab->p.name);
    EaseStop(eab, "controller exchanged");
    break;
  case EASE_FAULT:
    ParPrintf(eab, eError, "ERROR: error on %s", eab->p.name);
    break;
  default:
    ParPrintf(eab, eError, "ERROR: error code %d on %s", eab->errCode,
              eab->p.name);
    break;
  }
}

/*----------------------------------------------------------------------------*/
void EaseWrite(EaseBase * eab, char *cmd)
{
  /* cmd==NULL: repeat the same command */
  int iRet;
  char trash[64];
  int l;

  if (eab->errCode) {
    eab->state = EASE_abort;
    return;
  }
  if (eab->state == EASE_expect || eab->state == EASE_abort)
    return;
  while (availableRS232(eab->ser) == 1) {
    l = sizeof(trash);
    iRet = readRS232TillTerm(eab->ser, trash, &l);
    if (iRet < 0)
      break;
    ParPrintf(eab, -2, "trash: %s\n", trash);
  }
  if (cmd) {
    snprintf(eab->cmd, sizeof(eab->cmd), "%s%s", cmd,
             eab->ser->sendTerminator);
  }
  iRet = writeRS232(eab->ser, eab->cmd, strlen(eab->cmd));
  if (iRet < 0) {
    FsmStop(eab->task, eab->idle);
    snprintf(eab->msg, sizeof eab->msg,
             "offline (connection to %s:%d lost)", eab->ser->pHost, eab->ser->iPort);
    ParPrintf(eab, eError, "ERROR: %s", eab->msg);
    closeRS232(eab->ser);
    eab->state = EASE_notconnected;
    eab->errCode = iRet;
    return;
  }
  if (cmd) {
    ParPrintf(eab, -2, "cmd: %s", cmd);
  } else {
    ParPrintf(eab, -2, "cmd: %s", eab->cmd);
  }
  eab->state = EASE_expect;
  eab->cmdtime = time(NULL);
  eab->chktime = time(NULL) + 5;
}

/*----------------------------------------------------------------------------*/
int EaseWaitRead(EaseBase * eab)
{
  int cnt = 0;
  time_t start;

  if (eab->state < EASE_idle) {
    return NOTCONNECTED;
  }
  FsmPause(eab->task, 1);
  time(&start);
  while (eab->state == EASE_expect) {
    /* TaskYield(pServ->pTasker); does not work (pardef is not recursive) */
    FsmTaskHandler(eab->task);
    cnt++;
    if (time(NULL) > start + 5) {
      eab->ans[0] = '\0';
      eab->state = EASE_read;
      return TIMEOUT;
    }
  }
  FsmPause(eab->task, 0);
  /* if (cnt>1) ParPrintf(eab, eError, "TaskYield %d\n", cnt); */
  return 1;
}

/*----------------------------------------------------------------------------*/
void EaseParHasChanged(void)
{
  parameterChange = 1;
}

/*----------------------------------------------------------------------------*/
void EasePchk(void *drv)
{
  int pc = FsmPc();
  if (pc > 0) {
    ParPrintf(drv, eError, "ERROR: illegal line in fsm %d", pc);    
  }
}

/*----------------------------------------------------------------------------*/
void EaseSavePars(void)
{
  char *pFile = NULL;

  if (parameterChange) {
    parameterChange = 0;

    assert(pServ->pSics);

    pFile = IFindOption(pSICSOptions, "statusfile");
    if (pFile) {
      WriteSicsStatus(pServ->pSics, pFile, 0);
    }
  }
}

/*----------------------------------------------------------------------------*/
static int EaseRestart(EaseBase * eab)
{
  int iRet;

  eab->errCode = 0;
  if (eab->task) {
    FsmStop(eab->task, eab->idle);
    FsmRestartTask(eab->task, eab->idle);
  }
  eab->startOk = 0;
  eab->todo = eab->start;
  eab->state = EASE_connecting;
  iRet = initRS232WithFlags(eab->ser, 3);
  if (iRet != 1) {
    eab->errCode = iRet;
    EaseWriteError(eab);
    return -1;
  }
  if (eab->cmdtime == 0) {
    snprintf(eab->msg, sizeof eab->msg,
             "connecting to %s:%d", eab->ser->pHost, eab->ser->iPort);
  }
  return 0;
}

/*----------------------------------------------------------------------------*/
int EaseHandler(EaseBase * eab)
{
  EaseDriv *ead = EaseDrivCast(eab);;
  int iret;
  
  EaseSavePars();
  if (ead && ead->stopped && ead->hwstate == HWBusy) {
    ead->stopped = 0;
    ead->hwstate = HWIdle;
    if (FsmStop(eab->task, eab->doit)) {
      ParPrintf(eab, eWarning, "%s stopped", eab->p.name);
      return 0;
    }
  }

  if (eab->state == EASE_offline) {
    return 0;
  }
  if (eab->state == EASE_expect) {
    if (eab->cmdtime != 0 && time(NULL) > eab->cmdtime + eab->p.period) {
      snprintf(eab->msg, sizeof eab->msg, "no response");
    }
    return 0;
  }
  if (eab->state == EASE_lost) {
    snprintf(eab->msg, sizeof eab->msg, "no response");
  }
  if (eab->state == EASE_notconnected) {
    if (time(0) >= eab->cmdtime + 10) {
      EaseRestart(eab);
    }
    return 0;
  }
  if (eab->state == EASE_connecting) {
    iret = initRS232Finished(eab->ser);
    if (iret == 0)
      return 0;                 /* waiting for connection */
    if (iret < 0) {
      if (errno == ECONNREFUSED) {
        snprintf(eab->msg, sizeof eab->msg,
                "Connection refused");
      } else {
        snprintf(eab->msg, sizeof eab->msg,
                "Connection failed (%s)", strerror(errno));
      }
      if (eab->cmdtime == 0) {
        ParPrintf(eab, eError, "%s: %s", eab->p.name, eab->msg);
      }
      closeRS232(eab->ser);
      eab->state = EASE_notconnected;
      eab->cmdtime = time(0);
      return 0;
    } else {
      if (eab->tmo > 20) {
        eab->tmo = 20;
      }
      eab->msg[0] = '\0';
      eab->state = EASE_idle;
    }
  }
  if (eab->errCode) {
    if (eab->errCode == BADSEND) {
      eab->cmdtime = 0; /* means: this is not a repeated restart */
      EaseRestart(eab);
      return 0;
    }
    if (eab->state != EASE_abort) {
      EaseWriteError(eab);
    }
    eab->errCode = 0;
    if (ead) {
      ead->hwstate = HWFault;
      FsmStop(eab->task, eab->doit);
    }
    return 0;
  }
  if (ead) {
    if (eab->state == EASE_lost) {
      if (FsmStop(eab->task, eab->doit)) {
        ParPrintf(eab, eWarning, "stop %s caused by timeout", eab->p.name);
      }
      return 0;
    }
  } else {
    if (eab->state == EASE_lost) {
      return 0;
    }
  }
  return 1;
}

/*----------------------------------------------------------------------------*/
int EaseNextFullRead(EaseBase * eab)
{
  time_t thisPeriod;

  thisPeriod = time(NULL) / eab->p.period;
  if (thisPeriod != eab->readPeriod) {
    eab->readPeriod = thisPeriod;
    return 1;
  }
  return 0;
}

/*----------------------------------------------------------------------------*/
static long EaseSendIt(long pc, void *object)
{
  EaseBase *eab = EaseBaseCast(object);

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(eab);
    EaseWrite(eab, eab->sendCmd);
    ParPrintf(eab, eWarning, "send cmd> %s", eab->sendCmd);
    eab->sendCmd = NULL;
    return __LINE__;
  case __LINE__:                 /**********************************/
    ParPrintf(eab, eWarning, "response> %s", eab->ans);
    return 0;
  }                             /* FSM END ******************************************* */
}

/*----------------------------------------------------------------------------*/
int EaseCheckDoit(EaseBase * eab)
{
  int i, n;

  if (eab->todo == NULL) {
    if (eab->sendCmd != NULL) {
      eab->todo = EaseSendIt;
      return 1;
    }
    n = eab->maxflag / EASE_FLAGBITS;
    for (i = 0; i <= n; i++) {
      if (eab->updateFlags[i]) {
        eab->todo = eab->doit;
        return 1;
      }
    }
  }
  return eab->todo != NULL;
}

/*----------------------------------------------------------------------------*/
static long EaseIdle(long pc, void *object)
{
  EaseBase *eab = EaseBaseCast(object);
  EaseDriv *ead = EaseDrivCast(object);
  struct timeval tm;
  FsmFunc todo;

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(eab);

  idle:
    if (!EaseCheckDoit(eab))
      goto rd;
  doit:
    todo = eab->todo;
    eab->todo = NULL;
    FsmCall(todo);
    return __LINE__;
  case __LINE__:                 /**********************************/

    eab->startOk = 1;
    if (eab->state == EASE_abort) {
      eab->state = EASE_idle;
    }
  rd:
    /*
       if (eab->state == EASE_lost) {
       snprintf(eab->msg, sizeof eab->msg, "no response from %s",
       eab->p.type->name);
       }
     */
    FsmCall(eab->read);
    return __LINE__;
  case __LINE__:                 /**********************************/

    if (EaseCheckDoit(eab))
      goto doit;
    /*
       gettimeofday(&tm, NULL);
       printf("stop  %s %f\n", eab->evc->pName, tm.tv_usec / 1e6);
     */
/*    FsmWait(1); do we really need that? -- remove for faster response to send */
    return __LINE__;
  case __LINE__:                 /**********************************/
    /*
       gettimeofday(&tm, NULL);
       printf("start %s %f\n", eab->evc->pName, tm.tv_usec / 1e6);
     */
    goto idle;

    /* never return */
    return 0;
  }                             /* FSM END ******************************************* */

}

/*----------------------------------------------------------------------------*/
static float EaseGetValue(void *obj, SConnection * pCon)
{
  float val;
  EaseBase *eab;

  assert(eab = EaseBaseCast(obj));
  ParGetFloat(pCon, eab, "", &val);
  return val;
}

/*----------------------------------------------------------------------------*/
int EaseUpdate(int flag)
{
  EaseBase *eab = EaseBaseCast(ParObject());

  ParAccess(usUser);
  ParSave(0);
  if (ParActionIs(PAR_SET) > 0) {
    assert(flag >= 0);
    assert(flag <= eab->maxflag);
    eab->updateFlags[flag / EASE_FLAGBITS] |= 1 << (flag % EASE_FLAGBITS);
    if (eab->task)
      FsmSpeed(eab->task);
    return 1;
  }
  return 0;
}

/*----------------------------------------------------------------------------*/
int EaseNextUpdate(void *object)
{
  EaseBase *eab = EaseBaseCast(object);
  unsigned long mask, p;
  int i, n, flag;

  n = eab->maxflag / EASE_FLAGBITS;
  for (i = 0; i <= n; i++) {
    mask = eab->updateFlags[i];
    p = 1;
    flag = 0;
    /* find first */
    while (flag < 32) {
      if (mask & p) {
        eab->updateFlags[i] &= ~p;
        return flag + i * EASE_FLAGBITS;
      }
      p <<= 1;
      flag++;
    }
  }
  return -1;
}

/*----------------------------------------------------------------------------*/
int EaseGetUpdate(void *object, int flag)
{
  EaseBase *eab = EaseBaseCast(object);
  int i;

  assert(flag >= 0);
  assert(flag <= eab->maxflag);
  i = flag / EASE_FLAGBITS;
  if ((1 << (flag % EASE_FLAGBITS)) & eab->updateFlags[i]) {
    return 1;
  }
  return 0;
}

/*----------------------------------------------------------------------------*/
void EaseSetUpdate(void *object, int flag, int state)
{
  EaseBase *eab = EaseBaseCast(object);

  assert(flag >= 0);
  assert(flag <= eab->maxflag);

  if (state) {
    eab->updateFlags[flag / EASE_FLAGBITS] |= 1 << (flag % EASE_FLAGBITS);
  } else {
    eab->updateFlags[flag / EASE_FLAGBITS] &=
        ~(1 << (flag % EASE_FLAGBITS));
  }
}

/*----------------------------------------------------------------------------*/
static long EaseRun(void *obj, SConnection * pCon, float fVal)
{
  EaseBase *eab = EaseBaseCast(obj);
  EaseDriv *ead = EaseDrivCast(obj);
  int canc;

  assert(ead);

  ParSaveConn(eab, pCon);
  if (!eab->doit) {
    ParPrintf(ead, eError, "ERROR: missing run function %s", eab->p.name);
    return 0;
  }
  if (!eab->startOk) {
    ParPrintf(ead, eError, "ERROR: %s is not ready to run", eab->p.name);
    return 0;
  }
  if (FsmStop(eab->task, eab->doit)) {
    canc = 1;
    ParPrintf(ead, eWarning, "running %s cancelled", eab->p.name);
  } else {
    canc = 0;
  }
  if (eab->todo) {
    ParPrintf(ead, eError, "ERROR: %s busy", eab->p.name);
    return 0;
  }
  ead->targetValue = fVal;
  EaseParHasChanged();          /* assume that targetValue has to be saved */
  ead->stopped = 0;
  /* eab->todo = eab->doit; */
  EaseSetUpdate(eab, EASE_RUN, 1);
  ead->hwstate = HWBusy;
  if (ead->maxwait >= 0) {
    ead->timeout = time(NULL) + ead->maxwait;
  } else {
    ead->maxwait = -1;
    ead->timeout = time(NULL) + 9999999;        /* approx. infinite */
  }
  ead->finish = 0;
  ead->usedSettle = 0;
  ead->tolState = EASE_outOfTolerance;
  if (canc) {
    ParPrintf(ead, eWarning, "rerun %s", eab->p.name);
  }
  return 1;
}

/*----------------------------------------------------------------------------*/
static int EaseHalt(void *obj)
{
  EaseDriv *ead;

  assert(ead = EaseDrivCast(obj));
  ead->stopped = 1;
  return 1;
}

/*----------------------------------------------------------------------------*/
static int EaseIsInTol(void *obj)
{
  EaseDriv *ead = EaseDrivCast(obj);
  float f;

  f = EaseGetValue(ead, NULL) - ead->targetValue;
  if (ead->tolState == EASE_outOfTolerance) {
    if (fabsf(f) < ead->tolerance) {
      ParPrintf(obj, eWarning, "%s is within tolerance", ead->b.p.name);
      ead->tolState = EASE_inTolerance;
      return 1;
    }
    return 0;
  } else {
    if (fabsf(f) > ead->tolerance * 1.11) {
      if (ead->tolState == EASE_inTolerance) {
        ParPrintf(obj, eWarning, "%s is out of tolerance by %f",
                  ead->b.p.name, f);
      }
      ead->tolState = EASE_outOfTolerance;
      return 0;
    }
    return 1;
  }
}

/*----------------------------------------------------------------------------*/
static int EaseErrHandler(void *obj)
{
  /* to be implemented */
  return 1;
}

/*----------------------------------------------------------------------------*/
static int EaseCheckStatus(void *obj, SConnection * pCon)
{
  EaseDriv *ead = EaseDrivCast(obj);
  time_t now, t;

  assert(ead);
  ParSaveConn(ead, pCon);
  if (ead->stopped) {
    return HWIdle;
  }
  now = time(NULL);
  if (now > ead->timeout) {
    ParPrintf(obj, eWarning, "maxwait expired when driving %s",
              ead->b.p.name);
    ead->hwstate = HWIdle;
  } else if (EaseIsInTol(obj)) {
    if (ead->finish == 0) {
      ead->finish = now - ead->usedSettle;
      if (now < ead->finish + ead->settle) {
        ParPrintf(obj, eWarning, "settle %s for %ld sec",
                  ead->b.p.name, (long) (ead->finish + ead->settle - now));
      }
    }
    if (now > ead->finish + ead->settle && ead->hwstate != HWIdle) {
      ParPrintf(obj, eWarning, "%s has reached target", ead->b.p.name);
      ead->hwstate = HWIdle;
    }
  } else if (ead->finish != 0) {
    ead->usedSettle = now - ead->finish;
    ead->finish = 0;
  }
  return ead->hwstate;
}

/*----------------------------------------------------------------------------*/
static EVMode EaseGetMode(void *obj)
{
  EaseDriv *ead = EaseDrivCast(obj);

  assert(ead);

  if (ead->hwstate == HWBusy) {
    return EVDrive;
  }
  if (ead->tolState == EASE_notMonitored) {
    return EVIdle;
  }
  return EVMonitor;
}

/*----------------------------------------------------------------------------*/
static int EaseCheckLimits(void *obj, float fVal, char *error, int errLen)
{
  EaseDriv *ead;

  assert(ead = EaseDrivCast(obj));

  /* lower limit */
  if (fVal < ead->lowerLimit) {
    snprintf(error, errLen, "ERROR: %g violates lower limit of device",
             fVal);
    return 0;
  }
  /* upper limit */
  if (fVal > ead->upperLimit) {
    snprintf(error, errLen, "ERROR: %g violates upper limit of device",
             fVal);
    return 0;
  }
  return 1;
}

/*----------------------------------------------------------------------------*/
static int EaseStdHandler(void *object)
{
  int iret, l;
  EaseBase *eab = EaseBaseCast(object);
  char *corr;

  assert(eab);
  if (eab->state < EASE_idle)
    goto quit;
  if (availableNetRS232(eab->ser) || availableRS232(eab->ser)) {
    if (strncmp(eab->msg, "ERROR:", 6) != 0) {
      eab->msg[0] = '\0';
    }
    l = sizeof(eab->ans);
    iret = readRS232TillTerm(eab->ser, eab->ans, &l);
    if (eab->state != EASE_expect && eab->state != EASE_lost) {
      if (iret == 1) {
        ParPrintf(eab, eError, "unexpected answer: %s", eab->ans);
      }
      goto quit;
    }
    if (iret == 1) {
      ParPrintf(eab, -2, "ans: %s", eab->ans);
      if (eab->state == EASE_lost) {
        goto quit;
      } else {
        eab->tmo = 120;
      }
    }
    if (iret != 1) {
      eab->errCode = iret;
      eab->state = EASE_idle;
      goto error;
    }
    eab->state = EASE_read;
  } else if (eab->state == EASE_expect) {
    if (time(NULL) > eab->cmdtime + eab->tmo) {
      eab->state = EASE_lost;
    }
  } else if (eab->state == EASE_lost) {
  }
  goto quit;
error:
  /* EaseWriteError(eab); */
quit:
  return EaseHandler(eab);
}

/*----------------------------------------------------------------------------*/
static int EaseInit(SConnection * pCon, EaseBase * eab, int argc,
                    char *argv[], int maxflag, FsmHandler handler,
                    FsmFunc start, FsmFunc idle, FsmFunc read)
{

  /* args (starting from argv[0]):
     <rs232>
     <host> <port>
     <host>:<port>
   */
  int port=0, iRet, i;
  rs232 *ser;
  char *colon, *host;
  char buf[64];

  if (handler == NULL) {
    handler = EaseStdHandler;
  }
  assert(eab);
  assert(start);
  assert(read || idle);
  eab->handler = handler;
  eab->start = start;
  eab->read = read;
  if (idle) {
    eab->idle = idle;
  } else {
    eab->idle = EaseIdle;
  }

  if (argc < 1 || argc > 2) {
    SCWrite(pCon, "illegal number of arguments", eError);
    return 0;
  }

  colon = strchr(argv[0], ':');
  if (!colon && argc == 1) {    /* use a rs232 object */
    ser = FindCommandData(pServ->pSics, argv[0], "RS232 Controller");
    if (ser == NULL) {
      SCWrite(pCon, "ERROR: rs232 not found", eError);
      return 0;
    }
  } else {
    if (argc == 1) {            /* host:port syntax */
      port = atoi(colon + 1);
      i = colon - argv[0];
      if (i >= sizeof buf) {
        SCWrite(pCon, "ERROR: host name too long", eError);
        return 0;
      }
      strncpy(buf, argv[0], i);
      buf[i] = '\0';
      host = buf;
    } else if (argc == 2) {     /* host port syntax */
      host = argv[0];
      port = atoi(argv[1]);
    }
    if (port == 0) {
      SCWrite(pCon, "ERROR: illegal port number", eError);
      return 0;
    }
    ser = createRS232(host, port);
    if (ser == NULL) {
      SCWrite(pCon, "out of memory", eError);
      return 0;
    }
  }
  eab->ser = ser;

  eab->startOk = 0;
  eab->errCode = 0;
  eab->version[0] = '\0';
  eab->maxflag = maxflag;
  eab->sendCmd = NULL;
  eab->tmo = 20;
  eab->updateFlags =
      calloc(maxflag / EASE_FLAGBITS + 1, sizeof(*eab->updateFlags));
  if (eab->updateFlags == NULL) {
    SCWrite(pCon, "out of memory", eError);
    return 0;
  }

  setRS232ReplyTerminator(eab->ser, "\r");
  setRS232SendTerminator(eab->ser, "\r");
  setRS232Timeout(eab->ser, 5000);      /* milliseconds */
  setRS232Debug(eab->ser, 0);

  eab->task = NULL;
  eab->cmdtime = 0; /* means: this is not a repeated restart */
  if (EaseRestart(eab) < 0)
    return -1;
  eab->task = FsmStartTask(eab, eab->handler, eab->idle, eab->p.name);

  TaskRegisterN(pServ->pTasker,"ease",  (TaskFunc) FsmTaskHandler, NULL, FsmKill,
               eab->task, 0);
  return 1;
}

/*----------------------------------------------------------------------------*/
static void *EaseGetInterface(void *obj, int iCode)
{
  EaseBase *eab = EaseBaseCast(obj);
  EaseDriv *ead = EaseDrivCast(obj);

  assert(eab);
  if (iCode == DRIVEID) {
    if (ead) {
      return ead->drivInt;
    } else {
      return NULL;
    }
  } else if (iCode == ENVIRINTERFACE) {
    return ead->evInt;
  }
  return NULL;
}

/*----------------------------------------------------------------------------*/
void *EaseMakeBase(SConnection * con, void *class, int argc, char *argv[],
                   int dynamic, int maxflag,
                   ParDef pardef,
                   FsmHandler handler,
                   FsmFunc start, FsmFunc idle, FsmFunc read)
{

  /* args (starting from argv[0]):
     MakeObject objectname <driver> <rs232>
     <host> <port>
   */
  EaseBase *eab;
  char *creationCmd = NULL;

  if (dynamic) {
    creationCmd = Arg2Tcl(argc, argv, NULL, 0);
  }
  eab = ParMake(con, argv[1], class, pardef, creationCmd);
  if (!eab)
    return NULL;
  ParCheck(&easeBaseClass, eab);
  if (!EaseInit(con, eab, argc - 3, argv + 3, maxflag,
                handler, start, idle, read)) {
    RemoveCommand(pServ->pSics, argv[1]);
    return NULL;
  }
  return eab;
}

/*----------------------------------------------------------------------------*/
void *EaseMakeDriv(SConnection * con, void *class, int argc, char *argv[],
                   int dynamic, int maxflag,
                   ParDef pardef,
                   FsmHandler handler,
                   FsmFunc start, FsmFunc idle, FsmFunc read, FsmFunc run)
{

  int iret;
  EaseDriv *ead;
  char *creationCmd = NULL;

  assert(run);
  if (dynamic) {
    creationCmd = Arg2Tcl(argc, argv, NULL, 0);
  }
  ead = ParMake(con, argv[1], class, pardef, creationCmd);
  if (!ead)
    return NULL;
  ParCheck(&easeDrivClass, ead);
  ead->b.doit = run;

  ead->evInt = CreateEVInterface();
  ead->drivInt = CreateDrivableInterface();
  ead->b.p.desc->GetInterface = EaseGetInterface;

  if (!ead->evInt || !ead->drivInt ||
      !EaseInit(con, (EaseBase *) ead, argc - 3, argv + 3, maxflag,
                handler, start, idle, read)) {
    RemoveCommand(pServ->pSics, argv[1]);
    return NULL;
  }

  ead->evInt->GetMode = EaseGetMode;
  ead->evInt->IsInTolerance = EaseIsInTol;
  ead->evInt->HandleError = EaseErrHandler;

  ead->hwstate = HWIdle;
  ead->drivInt->Halt = EaseHalt;
  ead->drivInt->CheckLimits = EaseCheckLimits;
  ead->drivInt->SetValue = EaseRun;
  ead->drivInt->CheckStatus = EaseCheckStatus;
  ead->drivInt->GetValue = EaseGetValue;

  ead->maxwait = -1;
  ead->lowerLimit = 0;
  ead->upperLimit = 1000;
  ead->settle = 0;
  ead->tolerance = 1;
  ead->targetValue = 0;

  /* EMon interface to be implemented */
  return ead;
}

/*----------------------------------------------------------------------------*/
void EaseMsgPar(void *object)
{
  EaseBase *eab = EaseBaseCast(object);

  assert(eab);

  ParName("status");
  ParAccess(usUser);
  ParLogAs(NULL);
  if (eab->msg[0] == '\0') {
    ParList("");                /* do not list when empty */
  } else {
    ParList(NULL);
  }
  ParFixedStr(eab->msg, sizeof eab->msg, NULL);
  return;
}

/*----------------------------------------------------------------------------*/
int EaseRestartWrapper(void *object, void *userarg, int argc, char *argv[])
{
  EaseBase *eab = EaseBaseCast(object);
  char *colon;
  char *hostpos;
  int port, i;
  char host[64], hostport[80];
  char *cCmd;
  
  /* short string with length 0 or 1: connect to previous host/port */
  if (argc > 0 && strlen(argv[0]) > 1) {
    colon = strchr(argv[0], ':');
    if (!colon || argc > 1) {
      ParPrintf(object, eError, "ERROR: illegal syntax for: %s restart",
        eab->p.name);
      return 0;
    }
    port = atoi(colon + 1);
    i = colon - argv[0];
    if (i >= sizeof host) {
      ParPrintf(object, eError, "ERROR: host name too long");
      return 0;
    }
    strncpy(host, argv[0], i);
    host[i] = '\0';
    if (eab->p.creationCmd != NULL) {
      hostpos = strstr(eab->p.creationCmd, eab->ser->pHost);
      if (hostpos != NULL) {
        /* replace the second part of creationCmd with new host/port */
        snprintf(hostport, sizeof hostport, "%s:%d", host, port);
        hostpos[0]='\0';
        cCmd = calloc(1, strlen(eab->p.creationCmd) + strlen(hostport) + 1);
        strcpy(cCmd, eab->p.creationCmd);
        strcat(cCmd, hostport);
        free(eab->p.creationCmd);
        eab->p.creationCmd = cCmd;
      }
    }
    if (eab->ser->pHost != NULL) {
      free(eab->ser->pHost);
    }
    eab->ser->pHost = strdup(host);
    eab->ser->iPort = port;
  }
  if (eab->task) {
    EaseStop(eab, NULL);
  }
  eab->cmdtime = 0; /* means: this is not a repeated restart */
  EaseRestart(eab);
  return 1;
}

/*----------------------------------------------------------------------------*/
int EaseDisconnectWrapper(void *object, void *userarg, int argc, char *argv[])
{
  EaseBase *eab = EaseBaseCast(object);

  snprintf(eab->msg, sizeof eab->msg, "disconnected");
  ParPrintf(object, eWarning, "%s", eab->msg);
  EaseStop(eab, NULL);
  eab->state = EASE_offline;
  return 1;
}

/*----------------------------------------------------------------------------*/
void EaseBasePar(void *object)
{
  EaseBase *eab = EaseBaseCast(object);

  assert(eab);

  ParName("restart");
  ParAccess(usUser);
  ParCmd(EaseRestartWrapper, NULL);

  ParName("reconnect");
  ParAccess(usUser);
  ParCmd(EaseRestartWrapper, NULL);

  ParName("disconnect");
  ParAccess(usUser);
  ParCmd(EaseDisconnectWrapper, NULL);

  if (ParActionIs(PAR_KILL)) {
    if (eab->ser) {
      KillRS232(eab->ser);
      free(eab->ser);
      eab->ser = NULL;
    }
    if (pServ->pTasker && eab->task) {
      FsmStopTask(eab->task);
    }
    if (eab->updateFlags) {
      free(eab->updateFlags);
      eab->updateFlags = NULL;
    }
  }

  return;
}

/*----------------------------------------------------------------------------*/
int EaseSend(void *object, void *userarg, int argc, char *argv[])
{
  EaseBase *eab = EaseBaseCast(object);
  int iret;
  char cmd[32];
  char ans[64];
  char *term;

  iret = EaseWaitRead(eab);
  if (iret >= 0) {
    eab->sendCmd = ParArg2Str(argc, argv, NULL, 0);
/*    
    ParPrintf(eab, -2, "ans: %s", ans);
    ParPrintf(eab, eValue, "%s", ans);
*/
  }
  if (iret < 0) {
    eab->errCode = iret;
    EaseWriteError(eab);
  }
  return 1;
}

/*----------------------------------------------------------------------------*/
void EaseSendPar(void *object)
{
  ParName("send");
  ParAccess(usUser);
  ParCmd(EaseSend, NULL);
  return;
}

/*----------------------------------------------------------------------------*/
void EaseKillDriv(EaseDriv * ead)
{
  if (ead->drivInt) {
    free(ead->drivInt);
  }
  if (ead->evInt) {
    free(ead->evInt);
  }
}

/*----------------------------------------------------------------------------*/
void EaseDrivPar(void *object, char *fmt, char *unit)
{
  EaseDriv *ead;

  assert(ead = EaseDrivCast(object));

  ParName("upperLimit");
  ParFmt(fmt);
  ParTail(unit);
  ParAccess(usUser);
  ParSave(1);
  ParFloat(&ead->upperLimit, 310.);

  ParName("lowerLimit");
  if (ead->lowerLimit != 0) {
    ParFmt(fmt);
    ParTail(unit);
  };
  ParAccess(usUser);
  ParSave(1);
  ParFloat(&ead->lowerLimit, 0.0);

  ParName("tolerance");
  ParFmt(fmt);
  ParTail(unit);
  ParAccess(usUser);
  ParSave(1);
  ParFloat(&ead->tolerance, 1.0);

  ParName("maxwait");
  if (ead->maxwait < 0) {
    ParTail("disabled");
  } else {
    ParTail("sec");
  }
  ParAccess(usUser);
  ParSave(1);
  ParInt(&ead->maxwait, 7200);

  ParName("settle");
  ParTail("sec");
  ParAccess(usUser);
  ParSave(1);
  ParInt(&ead->settle, 0);

  ParName("targetValue");
  ParFmt(fmt);
  ParTail(unit);
  ParFloat(&ead->targetValue, PAR_NAN);

  if (ParActionIs(PAR_KILL)) {
    EaseKillDriv(ead);
  }
  return;
}
