/*---------------------------------------------------------------------------
ips2driv.c

Driver for the Oxford Instruments IPS/PS magnet power supply. 
Version 2 (based on ease).

Markus Zolliker, May 2005
----------------------------------------------------------------------------*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/time.h>
#include <math.h>
#include <tcl.h>
#include <fortify.h>
#include <sics.h>
#include <splitter.h>
#include <obpar.h>
#include <devexec.h>
#include <nserver.h>
#include <interrupt.h>
#include <emon.h>
#include <evcontroller.h>
#include <evcontroller.i>
#include <servlog.h>
#include <sicsvar.h>
#include <evdriver.i>
#include <rs232controller.h>
#include "oxinst.h"
#include "fsm.h"
#include "initializer.h"

typedef enum {NOTHING, SWITCH_OFF, RAMP_DOWN,
  RAMP_ALMOST, RAMP_ZERO, RAMPED_DOWN} DoThis;

typedef struct {
  EaseDriv d;
  float current;       /* current (in Tesla) */
  float persfield;     /* persistent field from IPS (in Tesla) */
  float lastfield;     /* persistent field from last drive */
  float confirmfield;  /* field confirmed in 1st step */
  float ramp;          /* actual ramp rate (Tesla/min) */
  float maxlimit;      /* hard field limit */
  float perscurrent;   /* current for persistent mode workaround (Tesla) */
  float perslimit;     /* field limit for activating persistent mode workaround (Tesla) */
  float voltage;       /* voltage */
  float measured;      /* measured current */
  float inductance;    /* induction (read only on startup) */
  float ampRamp;       /* ramp in amps (read only on startup) */
  float trainedTo;     /* use training mode when the field is higher than this value (slow ramp limits) */
  char *startScript;   /* script to be called on startup */
  int persmode;        /* 0: delayed persistant mode, 1: go to persistant mode, 2: leave switch on */
  int persdelay;       /* wait time for delayed persistant mode */ 
  int perswitch;       /* state of switch */
  int remote;          /* 0: local, 1: remote, do not check, 2: remote, check */
  int nowait;          /* 0: normal, 1: drive finishes immediately, ramp in background */
  int perswait;        /* wait time for persistent mode workaround (sec) */
  int heaterFault;
  int trainMode;       /* 0/1 read back for train mode, 2/3 command for trainMode */
  int external;        /* 0: normal,
                          1: go to external (script controlled) mode
                          2: external mode
                          3: go back to internal mode */
  DoThis dothis; 
  char *fmt;           /* fmt for field */
  int force;           /* force = 2: put heater switch even when stored field does not match */
  time_t swtim;        /* time when last switching the heater */
  time_t tim;          /* outside IpsChangeField: time of last Field Change */
} Ips;

static ParClass ipsClass = { "IPS", sizeof(Ips) };
static char *onOff[] = { "off", "on", NULL };
static char *persMode[] = { "auto", "on", "off", NULL };

/*----------------------------------------------------------------------------*/
static int IpsOk(Ips * drv)
{
  float dif;

  if (drv->d.b.msg[0] != '\0')
    return 1;                   /* connection not yet confirmed */
  if (drv->perswitch)
    return 1;
  if (drv->lastfield == PAR_NAN) {
    drv->lastfield = drv->persfield;
  }
  if (fabs(drv->persfield - drv->lastfield) < 1e-2) {
    drv->lastfield = drv->persfield;
    return 1;
  }
  if (drv->force != 0)
    return 1;
  ParPrintf(drv, eWarning,
            "\nit is not sure which field is in the magnet\n"
            "value stored in power supply: %f\n"
            "                 in software: %f\n"
            "use command\n \n  %s confirm ...\n \n"
            "to specify the persistent field\n \n", drv->persfield,
            drv->lastfield, drv->d.b.p.name);
  drv->force = 0;
  return 0;
}

/*----------------------------------------------------------------------------*/
static int IpsConfirm(void *object, void *userarg, int argc, char *argv[])
{
  Ips *drv = ParCast(&ipsClass, object);
  float fld;

  assert(drv);
  if (argc > 0) {
    if (argc > 1) {
      ParPrintf(object, eError, "Too many arguments");
      return 0;
    }
    fld = atof(argv[0]);
    if (fld > drv->d.upperLimit || fld < drv->d.lowerLimit) {
      ParPrintf(object, eError, "Field outside limit");
      return 0;
    }
    if (drv->perswitch) {
      ParPrintf(object, eWarning, "switch heater is on - field is %f",
                drv->current);
      return 0;
    }
    if (fabs(fld - drv->persfield) > 1e-5
        && fabs(fld - drv->lastfield) > 1e-5) {
      ParPrintf(object, eWarning,
                "Be aware that this does neither match the field"
                " stored in software\nnor the field stored in power supply.");
    }
    if (drv->force && fld != drv->confirmfield)
      drv->force = 0;
    if (drv->force == 0) {
      ParPrintf(object, eWarning,
                "Please repeat this command, to confirm again"
                " the persistent field of\n    %f Tesla.", fld);
      drv->confirmfield = fld;
      drv->force = 1;
    } else {
      drv->force = 2;
      drv->lastfield = fld;
      EaseParHasChanged();
      ParPrintf(object, eValue, "%s confirm = %f", drv->d.b.p.name, fld);
    }
  } else {
    ParPrintf(object, eValue, "%s lastfield = %f", drv->d.b.p.name,
              drv->lastfield);
  }
  return 1;
}

/*----------------------------------------------------------------------------*/
void IpsParDef(void *object)
{
  Ips *drv = ParCast(&ipsClass, object);

  ParName("");
  ParFmt(drv->fmt);
  ParTail("Tesla");
  ParFloat(&drv->persfield, PAR_NAN);

  ParName("persmode");
  ParAccess(usUser);
  ParEnum(persMode);
  ParList(0);
  ParInt(&drv->persmode, 1);

  ParName("perswitch");
  ParEnum(onOff);
  ParList(0);
  ParInt(&drv->perswitch, PAR_NAN);

  ParName("nowait");
  ParAccess(usUser);
  ParEnum(onOff);
  ParList(0);
  ParInt(&drv->nowait, 0);

  ParName("maxlimit");
  ParSave(1);
  ParFloat(&drv->maxlimit, 0.0);

  ParName("limit");
  ParAccess(usUser);
  ParFmt(drv->fmt);
  ParTail("Tesla");
  ParFloat(&drv->d.upperLimit, 0.0);
  if (ParActionIs(PAR_SET) > 0) {
    if (drv->maxlimit == 0) {   /* first time: set maxlimit */
      drv->maxlimit = drv->d.upperLimit;
    } else if (drv->d.upperLimit > drv->maxlimit) {
      drv->d.upperLimit = drv->maxlimit;
      ParPrintf(drv, eWarning, "limit is too high, set back to %.5g\n",
                drv->maxlimit);
    }
    drv->d.lowerLimit = -drv->d.upperLimit;
  }

  ParName("ramp");
  ParAccess(usUser);
  ParFmt(drv->fmt);
  ParTail("Tesla/min");
  ParFloat(&drv->ramp, 1.0);

  ParName("perscurrent");
  ParAccess(usUser);
  ParFmt(drv->fmt);
  ParTail("Tesla");
  ParFloat(&drv->perscurrent, 0.0);

  ParName("perslimit");
  ParAccess(usUser);
  ParFmt(drv->fmt);
  ParTail("Tesla");
  ParFloat(&drv->perslimit, 10.0);

  ParName("perswait");
  ParAccess(usUser);
  ParTail("sec");
  ParInt(&drv->perswait, 1800);

  ParName("persdelay");
  ParAccess(usUser);
  ParTail("sec");
  ParInt(&drv->persdelay, 1800);

  ParName("current");
  ParFmt(drv->fmt);
  ParTail("Tesla equivalent");
  ParFloat(&drv->current, PAR_NAN);

  ParName("measured");
  ParFmt(drv->fmt);
  ParTail("Amps");
  ParFloat(&drv->measured, PAR_NAN);

  ParName("voltage");
  ParFmt(drv->fmt);
  ParTail("Volt");
  ParFloat(&drv->voltage, PAR_NAN);

  ParName("lastfield");
  ParSave(1);
  ParFloat(&drv->lastfield, PAR_NAN);

  ParName("ampRamp");
  ParFmt("%g");
  ParTail("A");
  ParList("");
  ParFloat(&drv->ampRamp, 0.0);

  ParName("inductance");
  ParFmt("%g");
  ParTail("Henry");
  ParList("");
  ParFloat(&drv->inductance, 0.0);

  ParName("trainedTo");
  ParAccess(usUser);
  ParFmt(drv->fmt);
  ParTail("Tesla");
  ParFloat(&drv->trainedTo, 0.0);

  ParName("trainMode");
  ParEnum(onOff);
  ParList(0);
  ParInt(&drv->trainMode, PAR_NAN);

  ParName("external");
  ParAccess(usUser);
  ParEnum(onOff);
  ParList(0);
  ParInt(&drv->external, 0);

  ParName("startScript");
  ParAccess(usUser);
  ParList("");
  ParSave(1);
  ParStr(&drv->startScript, "0");

  ParName("confirm");
  ParCmd(IpsConfirm, NULL);

  EaseBasePar(drv);
  EaseSendPar(drv);
  ParStdDef();

  ParName("targetValue");
  ParFmt(drv->fmt);
  ParTail("Tesla");
  ParFloat(&drv->d.targetValue, PAR_NAN);

  if (ParActionIs(PAR_LIST) || ParActionIs(PAR_SET)
      || ParActionIs(PAR_SHOW)) {
    IpsOk(drv);
  }
  if (ParActionIs(PAR_KILL)) {
    EaseKillDriv(&drv->d);
  }
  EaseMsgPar(drv);
}

/*----------------------------------------------------------------------------*/
static void IpsStatus(Ips * drv)
{
  char *ans;
  int *code;
  int swi;
  char *errmsg;
  
  if (drv->d.b.state != EASE_read)
    return;
  ans = drv->d.b.ans;
  code = &drv->d.b.errCode;
  if (ans[0] != 'X' ||
      ans[3] != 'A' || ans[5] != 'C' || ans[7] != 'H' || ans[9] != 'M') {
    ParPrintf(drv, eError, "illegal status response");
    *code = EASE_FAULT;
    return;
  }
  switch (ans[1]) {
  case '0':
    if (ans[6] > '3') {
      errmsg = "ERROR: auto-run-down (low He level)";
    } else {
      errmsg = "";
    }
    break;
  case '1':
    errmsg = "ERROR: magnet quenched";
    drv->lastfield = PAR_NAN;
    break;
  case '2':
    errmsg = "ERROR: IPS overheated";
    break;
  case '4':
    errmsg = "ERROR: IPS warming up";
    break;
  case '8':
    errmsg = "ERROR: IPS fault";
    *code = EASE_FAULT;
    break;
  default:
    errmsg = "ERROR: illegal status response";
  }
  if (errmsg[0] != '\0') {
    if (strcmp(errmsg, drv->d.b.msg) != 0) {
      snprintf(drv->d.b.msg, sizeof drv->d.b.msg, "%s", errmsg);
      ParPrintf(drv, eError, "%s", errmsg);
    }
    *code = EASE_FAULT;
    return;
  } else {
    drv->d.b.msg[0]='\0'; /* clear status */
  }
  if (ans[6] != '3') {
    if (drv->remote == 2) { /* remote state monitoring local switch */ 
      ParPrintf(drv, eError, "IPS switched to local");
      *code = EASE_FAULT;
      drv->remote = 3; /* signal to switch back to C0 local locked */
      return;
    }
    if (ans[6] == '1') {
      drv->remote = 3; /* signal to switch back to C0 local locked */
    }
    if (drv->d.hwstate == HWBusy && drv->d.b.doit == NULL) {
      drv->d.hwstate = HWIdle;
      drv->d.stopped = 0;
    }
  }
  if (ans[8] == '5') {
    drv->heaterFault = 1;
    return;
  }
  drv->trainMode = (ans[10] >= '4');
  drv->heaterFault = 0;
  if (ans[8] == '1') {
    swi = 1;
  } else {
    swi = 0;
  }
  if (swi != drv->perswitch || drv->swtim == 0) {
    drv->swtim = time(NULL);
    drv->perswitch = swi;
    if (drv->perswitch && drv->dothis == RAMP_ALMOST) {
      drv->dothis = NOTHING;
    }
  }
}

/*----------------------------------------------------------------------------*/
static void IpsSetField(Ips * drv, float val)
{
  ParLog(drv);
  if (drv->perswitch) {
    drv->current = val;
    drv->lastfield = val;
    drv->persfield = val;
    EaseParHasChanged();
  } else {
    drv->persfield = val;
  }
}

/*----------------------------------------------------------------------------*/
static long IpsRead(long pc, void *object)
{
  Ips *drv = ParCast(&ipsClass, object);
  EaseBase *eab = object;
  time_t expire, now;

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    if (eab->state == EASE_abort) {
      eab->state = EASE_idle;
    }
    EasePchk(drv);
    if (drv->external == 2) return 0; /* external mode */
    if (drv->external == 3) { /* switch back to internal */
      drv->external = 0;
      goto localLocked;
    }
    if (drv->external != 1) goto getstatus;
    EaseWrite(eab, "C3");
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->external = 2;
    return 0;

  getstatus:
    EaseWrite(eab, "X");
    return __LINE__;
  case __LINE__:                 /**********************************/
    IpsStatus(drv);

    EaseWrite(eab, "R7");       /* read current (in Tesla) */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->current = OxiGet(eab, 3, NULL, drv->current);

    EaseWrite(eab, "R2");       /* read measured current (in amps) */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->measured = OxiGet(eab, 3, NULL, drv->measured);

    EaseWrite(eab, "R1");       /* read measured voltage */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->voltage = OxiGet(eab, 3, NULL, drv->voltage);

    if (drv->perswitch) {
      IpsSetField(drv, drv->current);
      goto checktodo;
    }
    EaseWrite(eab, "R18");      /* read persistant field (in Tesla) */
    return __LINE__;
  case __LINE__:                 /**********************************/
    IpsSetField(drv, OxiGet(eab, 3, NULL, drv->persfield));

  checktodo:
    if (eab->msg[0] != '\0') goto quit; /* no action when in error */
    now = time(NULL);
    if (drv->persmode == 2) { /* persistent mode off */
      drv->dothis = NOTHING;
      goto nothing;
    } else {
      if (drv->perswitch) {
        if ((drv->persmode == 1 && now > drv->tim + 10) || now > drv->tim + drv->persdelay) {
          drv->dothis = SWITCH_OFF;
        } else {
          drv->dothis = NOTHING;
          goto nothing;
        }
      } else if (drv->dothis == RAMP_ALMOST) {
        if (now > drv->tim + drv->perswait) {
          drv->dothis = RAMP_ZERO;
        } else {
          goto nothing;
        }
      } else if (drv->dothis == SWITCH_OFF) {
        if (now > drv->swtim + 30) {
          drv->dothis = RAMP_DOWN;
        } else {
          goto nothing;
        }
      } else {
        goto nothing;
      }
    }

    EaseWrite(eab, "C3");
    drv->remote = 1;   /* remote state */
    return __LINE__;
  case __LINE__:                 /**********************************/

    switch (drv->dothis) {
    case SWITCH_OFF: goto switch_off;
    case RAMP_DOWN: goto ramp_down;
    case RAMP_ZERO: goto ramp_zero;
    }
    /* we should never get here */
    goto quit;

  switch_off:
    EaseWrite(eab, "A0");
    return __LINE__;
  case __LINE__:                 /**********************************/
    EaseWrite(eab, "H0");
    drv->perswitch = 0;
    drv->swtim = time(NULL);
    drv->lastfield = drv->current;
    EaseParHasChanged();
    return __LINE__;
  case __LINE__:                 /**********************************/
    ParPrintf(drv, -1, "IPS: go to persistent mode, wait 30 sec for switch");
    goto localLocked;

  ramp_down:
    if (drv->current == 0)
      goto localLocked;
    if (drv->perscurrent == 0 || drv->persfield < drv->perslimit) {
      drv->dothis = RAMP_ZERO;
      goto ramp_zero;
    }

    OxiSet(eab, "J", drv->perscurrent, 3);  /* put set point to a low value */
    return __LINE__;
  case __LINE__:
    EaseWrite(eab, "A1");       /* goto set (almost zero) */
    ParPrintf(drv, -1, "IPS: ramp current to %f (almost zero)", drv->perscurrent);
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->tim = time(NULL);
    drv->dothis = RAMP_ALMOST;
    goto localLocked;

  ramp_zero:
    EaseWrite(eab, "A2");       /* goto zero */
    ParPrintf(drv, -1, "IPS: ramp current to 0");
    drv->dothis = RAMPED_DOWN;
    return __LINE__;
  case __LINE__:                 /**********************************/
    goto localLocked;

  nothing:
    if (drv->remote != 3) /* NOT signal to switch back to local locked */
      goto quit;

  localLocked:
    EaseWrite(eab, "C0");
    drv->remote = 0;  /* local state */
    return __LINE__;
  case __LINE__:                 /**********************************/

  quit:
    ParLog(drv);
    return 0;
  }                             /* FSM END ******************************************* */

}

/*----------------------------------------------------------------------------*/
static long IpsMeas(long pc, void *object)
{
  Ips *drv = ParCast(&ipsClass, object);
  EaseBase *eab = object;

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(drv);
    EaseWrite(eab, "X");
    return __LINE__;
  case __LINE__:                 /**********************************/
    IpsStatus(drv);

    EaseWrite(eab, "R2");       /* read measured current (in amps) */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->measured = OxiGet(eab, 3, NULL, drv->measured);

    EaseWrite(eab, "R1");       /* read measured voltage */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->voltage = OxiGet(eab, 3, NULL, drv->voltage);

  quit:
    ParLog(drv);
    return 0;
  }                             /* FSM END ******************************************* */

}

/*----------------------------------------------------------------------------*/
static long IpsStart(long pc, void *object)
{
  Ips *drv = ParCast(&ipsClass, object);
  EaseBase *eab = object;
  float value;
  Tcl_Interp *pTcl = NULL;
  int iRet;
  char msg[256];
  
  
  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EaseWrite(eab, "V\rQ4"); /* get version and switch to ext. resolution */
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (0 == strncmp(eab->version, "IPS120", 6)) {
      eab->syntax = 1;
    } else if (0 == strncmp(eab->version, "PS", 2)) {
      eab->syntax = 0;
    } else {
      snprintf(msg, sizeof msg,
               "unknown power supply version: %s", eab->version);
      EaseStop(eab, msg);
      goto quit;
    }
    ParPrintf(drv, eLog, "connected to %s", eab->version);
    if (eab->syntax) {
      drv->fmt = "%.4f";
    } else {
      drv->fmt = "%.3f";
    }
    drv->tim = time(NULL);
    FsmCall(IpsRead);
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->d.targetValue = drv->persfield;
    
    if (eab->syntax == 0) goto quit;
    
    EaseWrite(eab, "R24");
    return __LINE__;
  case __LINE__:
    drv->inductance = OxiGet(eab, 1, NULL, 0.0);
    
    if (drv->ramp == 0) {
      goto ramp_read;
    }
    
    EaseWrite(eab, "R9");
    return __LINE__;
  case __LINE__:
    value = OxiGet(eab, 3, NULL, drv->ramp);
    if (fabs(value - drv->ramp) == 0) goto ramp_ok;
    
    EaseWrite(eab, "C3");
    drv->remote = 1; /* remote state */
    return __LINE__;
  case __LINE__:                 /**********************************/

    OxiSet(eab, "T", drv->ramp, 3);
    return __LINE__;
  case __LINE__:                 /**********************************/

    EaseWrite(eab, "C0");
    drv->remote = 0;  /* local state */
    return __LINE__;
  case __LINE__:                 /**********************************/
    
  ramp_read:
    EaseWrite(eab, "R9");
    return __LINE__;
  case __LINE__:
    drv->ramp = OxiGet(eab, 3, NULL, drv->ramp);

  ramp_ok:
  
    EaseWrite(eab, "R6");
    return __LINE__;
  case __LINE__:
    drv->ampRamp = OxiGet(eab, 1, NULL, 0.0);
    
    if (drv->startScript && drv->startScript[0] != '\0'
        && 0 != strcmp(drv->startScript, "0")) {
      pTcl = InterpGetTcl(pServ->pSics);
      iRet = Tcl_Eval(pTcl, drv->startScript);
      if (iRet != TCL_OK) {
        snprintf(msg, sizeof msg, "%s", pTcl->result);
        EaseStop(eab, msg);
        goto quit;
      }
      eab->msg[0]='\0';
    }
      
  quit:
    return 0;
  }                             /* FSM END ******************************************* */

}

/*----------------------------------------------------------------------------*/
static long IpsChangeField(long pc, void *object)
{
  Ips *drv = ParCast(&ipsClass, object);
  EaseBase *eab = object;
  float fld;
  float step;
  float ramp;
  time_t delay;

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
  
    if (eab->syntax == 0) {
      EaseWrite(eab, "C3"); /* set remote */
    } else {
      EaseWrite(eab, "C3\rQ4"); /* set remote and repeat extended resolution (in case of power failure) */
    }
    drv->remote = 1; /* remote state */
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (!EaseGetUpdate(drv, EASE_RUN)) {
      /* an other flag than EASE_RUN was set -> should not be the case */
      return 0;
    }
    EaseSetUpdate(eab, EASE_RUN, 0);
    if (drv->nowait) {
      drv->d.hwstate = HWIdle;
      drv->d.eMode = EVMonitor; /* finish drive, continue in background */
    }
    EaseWrite(eab, "F7");       /* switch to tesla on display */
    return __LINE__;
  case __LINE__:                 /**********************************/

    EaseWrite(eab, "A0");       /* hold */
    return __LINE__;
  case __LINE__:                 /**********************************/

    EaseWrite(eab, "R7");       /* read current (in Tesla) */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->current = OxiGet(eab, 3, NULL, drv->current);

    if (drv->perswitch) {
      IpsSetField(drv, drv->current);
      goto skipR18;
    }

    EaseWrite(eab, "R18");      /* read persistant field (in Tesla) */
    return __LINE__;
  case __LINE__:                 /**********************************/
    fld = OxiGet(eab, 3, NULL, drv->persfield);
    IpsSetField(drv, fld);

  skipR18:
    FsmCall(IpsMeas);
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->remote = 2; /* remote state monitoring local switch */
    if (!IpsOk(drv))
      goto finish;
    if (drv->lastfield == PAR_NAN) {
      drv->lastfield = drv->persfield;
    }
    if (fabs(drv->d.targetValue - drv->lastfield) < 0.5e-5) {
      ParPrintf(drv, -1, "IPS: we are already at field %f",
                drv->lastfield);
      if (drv->persmode == 1) {
        if (!drv->perswitch)
          goto finish;
        goto target_reached;
      } else {
        if (drv->perswitch)
          goto finish;
      }
    }
    if (fabs(drv->current - drv->lastfield) >= 1.0e-5) {
      goto ramp2setp;
    }

    if (drv->perswitch)
      goto wait_open;
    if (drv->force == 2) {
      EaseWrite(eab, "H2");
    } else {
      EaseWrite(eab, "H1");
    }
    drv->force = 0;
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (strcmp(drv->d.b.ans, "H") == 0) {
      goto switched_on;
    }
    ParPrintf(drv, -1, "IPS: switching heater on failed, try again");

  ramp2setp:
    OxiSet(eab, "J", drv->lastfield, 3);        /* set point */
    return __LINE__;
  case __LINE__:                 /**********************************/
    EaseWrite(eab, "A1");
    ParPrintf(drv, -1, "IPS: ramp to current for %f Tesla",
              drv->lastfield);
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->tim = time(NULL);

  stab1:
    EaseWrite(eab, "R7");       /* read current (in Tesla) */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->current = OxiGet(eab, 3, NULL, drv->current);

    FsmCall(IpsMeas);
    return __LINE__;
  case __LINE__:                 /**********************************/

    if (fabs(drv->current - drv->lastfield) >= 1e-4)
      goto stab1;

  stab2:
    FsmWait(1);
    return __LINE__;
  case __LINE__:                 /**********************************/

    FsmCall(IpsMeas);
    return __LINE__;
  case __LINE__:                 /**********************************/

    if (time(NULL) < drv->tim + 10)
      goto stab2;               /* stabilize */

  switch_on:
    if (drv->perswitch)
      goto wait_open;
    if (drv->force == 2) {
      EaseWrite(eab, "H2");
    } else {
      EaseWrite(eab, "H1");
    }
    drv->force = 0;
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (strcmp(drv->d.b.ans, "?H1") == 0) {
      ParPrintf(drv, eError, "IPS: switching heater on failed");
      eab->errCode = EASE_FAULT;
      goto off_finish;
    }
  switched_on:
    drv->perswitch = 1;
    drv->dothis = NOTHING;
    drv->swtim = time(NULL);

  wait_open:
    delay = drv->swtim + 15 - time(NULL);
    if (delay > 0)
      ParPrintf(drv, -1, "IPS: wait %d sec to open switch", delay);

    ParLog(drv);
  start_ramp:
    FsmWait(1);
    drv->tim = time(NULL);
    return __LINE__;
  case __LINE__:                 /**********************************/

    FsmCall(IpsMeas);
    return __LINE__;
  case __LINE__:                 /**********************************/

    if (drv->heaterFault) {
      if (time(NULL) > drv->swtim + 3) {
        ParPrintf(drv, eError, "IPS: switch heater not connected");
        eab->errCode = EASE_FAULT;
        goto off_finish;
      }
    }

    if (time(NULL) < drv->swtim + 15)
      goto start_ramp;          /* wait */
    OxiSet(eab, "T", drv->ramp, 3);
    return __LINE__;
  case __LINE__:                 /**********************************/
    OxiSet(eab, "J", drv->current, 3);  /* put set point to actual value */
    return __LINE__;
  case __LINE__:                 /**********************************/
    EaseWrite(eab, "A1");       /* go to setpoint (do not yet run) */
    return __LINE__;
  case __LINE__:                 /**********************************/
    ParPrintf(drv, -1, "IPS: ramp to %f Tesla", drv->d.targetValue);

  ramping:
    ParLog(drv);
    FsmWait(1);
    return __LINE__;
  case __LINE__:                 /**********************************/
    EaseWrite(eab, "R7");       /* read "current" in Tesla */
    return __LINE__;
  case __LINE__:                 /**********************************/
    IpsSetField(drv, OxiGet(eab, 3, NULL, drv->current));       /* set drv->current and callback */

    FsmCall(IpsMeas);
    return __LINE__;
  case __LINE__:                 /**********************************/

    EaseWrite(eab, "R9");       /* read back ramp rate (may be sweep limited) */
    return __LINE__;
  case __LINE__:                 /**********************************/
    ramp = OxiGet(eab, 3, NULL, drv->ramp);
    step = fabs(ramp / 3) + 0.001;            /* step = ramp * 20 sec */
    if (drv->d.targetValue > drv->current + step) {
      fld = drv->current + step;
    } else if (drv->d.targetValue < drv->current - step) {
      fld = drv->current - step;
    } else {
      fld = drv->d.targetValue;
      if (fabs(drv->current - drv->d.targetValue) < 1e-5)
        goto target_reached;
    }
    if (fabs(fld) >= drv->trainedTo) {
      if (drv->trainMode == 0) {
        drv->trainMode = 3; /* remember that we must go to train mode */
      }
      drv->trainedTo = fabs(fld);
    } else {
      if (drv->trainMode == 1) {
        drv->trainMode = 2; /* remember that we must go to fast mode */
      }
    }
    OxiSet(eab, "J", fld, 3);
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (drv->trainMode < 2) goto ramping;
    if (drv->trainMode == 2) {
      ParPrintf(drv, -1, "IPS: switch off train mode");
      EaseWrite(eab, "M1");
    } else {
      ParPrintf(drv, -1, "IPS: switch to train mode (slow ramp)");
      EaseWrite(eab, "M5");
    }
    return __LINE__;
  case __LINE__:                 /**********************************/
    goto ramping;

  target_reached:
    drv->d.hwstate = HWIdle;
    drv->d.eMode = EVMonitor;   /* we are at field, drive has finished */
    if (drv->persmode != 1)
      goto hold_finish;
    /* but we continue in the background */
    drv->tim = time(NULL);
  stab3:
    FsmWait(1);
    return __LINE__;
  case __LINE__:                 /**********************************/

    FsmCall(IpsMeas);
    return __LINE__;
  case __LINE__:                 /**********************************/

    if (time(NULL) < drv->tim + 10)
      goto stab3;               /* stabilize */

    EaseWrite(eab, "A0");       /* hold */
    return __LINE__;
  case __LINE__:                 /**********************************/
    goto finish;
    
  hold_finish:
    EaseWrite(eab, "A0");
    return __LINE__;
  case __LINE__:                 /**********************************/
    goto finish;

  off_finish:
    if (drv->perswitch) {
      drv->lastfield = drv->current;
      drv->swtim = time(NULL);
    }
    EaseWrite(eab, "H0");
    return __LINE__;
  case __LINE__:                 /**********************************/

  finish:
    EaseWrite(eab, "C0");
    drv->remote = 0; /* local state */
    drv->d.hwstate = HWIdle;
    drv->tim = time(NULL);       /* time of last field change */
    return __LINE__;
  case __LINE__:                 /**********************************/

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

}

/*----------------------------------------------------------------------------*/
static int IpsInit(SConnection * con, int argc, char *argv[], int dynamic)
{
  /* args:
     MakeObject objectname ips <rs232>
     <host> <port>
   */
  Ips *drv;

  drv = EaseMakeDriv(con, &ipsClass, argc, argv, dynamic, 7,
                     IpsParDef, OxiHandler, IpsStart, NULL, IpsRead,
                     IpsChangeField);
  if (drv == NULL)
    return 0;
  drv->d.maxwait = 999999;
  drv->d.tolerance = 0.00011;
  drv->dothis = NOTHING;
  return 1;
}

/*----------------------------------------------------------------------------*/
void IpsStartup(void)
{
  ParMakeClass(&ipsClass, EaseDrivClass());
  MakeDriver("IPS", IpsInit, 0, "OI Power Supply");
}
