/*---------------------------------------------------------------------------
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 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 */
  int persmode; /* 0: leave switch on, 1: go to 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 heaterFault;
  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;
} Ips;

static ParClass ipsClass = { "IPS", sizeof(Ips) };
static char *onOff[] = { "off", "on", 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 (fabs(drv->persfield - drv->lastfield) < 1e-5) 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(onOff); 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("current"); ParFmt(drv->fmt); ParTail("Tesla equivalent");
  ParFloat(&drv->current, PAR_NAN);
  
  ParName("lastfield");
  ParSave(1);
  ParFloat(&drv->lastfield, 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;
  
  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': break;
  case '1': ParPrintf(drv, eError, "magnet quenched"); *code = EASE_FAULT; return;
  case '2': ParPrintf(drv, eError, "IPS overheated"); *code = EASE_FAULT; return;
  case '4': ParPrintf(drv, eError, "IPS warming up"); *code = EASE_FAULT; return;
  case '8': ParPrintf(drv, eError, "IPS fault"); *code = EASE_FAULT; return;
  default:
    ParPrintf(drv, eError, "illegal status response");
    *code = EASE_FAULT;
    return;
  }
  if (ans[6] != '3') {
    if (drv->remote == 2) {
      ParPrintf(drv, eError, "IPS switched to local");
      *code = EASE_FAULT;
      drv->remote = 1;
      return;
    }
    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->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;
  }
}
/*----------------------------------------------------------------------------*/
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;

  switch (pc) { default: /* FSM BEGIN *******************************/
    EaseWrite(eab, "X");
  return __LINE__; case __LINE__: /**********************************/
    IpsStatus(drv);
  rd:
    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 quit;
    }
    EaseWrite(eab, "R18"); /* read persistant field (in Tesla) */
  return __LINE__; case __LINE__: /**********************************/
    IpsSetField(drv, OxiGet(eab, 3, NULL, drv->persfield));
    
  quit:
    ParLog(drv);
  return 0; } /* FSM END ********************************************/

}
/*----------------------------------------------------------------------------*/
static long IpsStart(long pc, void *object) {
  Ips *drv = ParCast(&ipsClass, object);
  EaseBase *eab = object;
  
  switch (pc) { default: /* FSM BEGIN *******************************/
    EaseWrite(eab, "V");
  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(eab->msg, sizeof eab->msg, "unknown power supply version: %s", eab->version);
      ParPrintf(drv, eError, "ERROR: %s", eab->msg);
      EaseStop(eab);
      goto quit;
    }
    ParPrintf(drv, eStatus, "connected to %s", eab->version);
    if (eab->syntax) {
      drv->fmt = "%.4f";
    } else {
      drv->fmt = "%.3f";
    }
    FsmCall(IpsRead);
  return __LINE__; case __LINE__: /**********************************/

  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 *******************************/
    EaseSetUpdate(eab, EASE_RUN, 0);
    if (drv->nowait) {
      drv->d.hwstate = HWIdle;
      drv->d.eMode = EVMonitor; /* finish drive, continue in background */
    }
    EaseWrite(eab, "C3");
    drv->remote = 1;
  return __LINE__; case __LINE__: /**********************************/
    EaseWrite(eab, "F7"); /* switch to tesla on display */
  return __LINE__; case __LINE__: /**********************************/
    EaseWrite(eab, "A0"); /* hold */
  return __LINE__; case __LINE__: /**********************************/
    FsmCall(IpsRead);
  return __LINE__; case __LINE__: /**********************************/
    drv->remote = 2;
    if (!IpsOk(drv)) goto finish;
    if (fabs(drv->d.targetValue - drv->lastfield) < 1e-5) {
      ParPrintf(drv, -1, "IPS: we are already at field %f", drv->lastfield);
      if (drv->persmode) {
        if (!drv->perswitch) goto finish;
        goto target_reached;
      } else {
        if (drv->perswitch) goto finish;
      }
    }
    if (fabs(drv->current - drv->lastfield) < 1e-5) {
      goto switch_on;
    }
    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, "X");
  return __LINE__; case __LINE__: /**********************************/
    IpsStatus(drv); /* just check for errors */
    EaseWrite(eab, "R7"); /* read current (in Tesla) */
  return __LINE__; case __LINE__: /**********************************/
    drv->current = OxiGet(eab, 3, NULL, drv->current);
    ParLog(drv);
    if (fabs(drv->current - drv->lastfield) > 1e-5) goto stab1;
    
  stab2:
    FsmWait(1);
  return __LINE__; case __LINE__: /**********************************/
    EaseWrite(eab, "X");
  return __LINE__; case __LINE__: /**********************************/
    IpsStatus(drv);
    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__: /**********************************/
    drv->perswitch = 1;
    drv->swtim = time(NULL);
    
  wait_open:
    delay = drv->swtim + 30 - time(NULL);
    if (delay > 0)
      ParPrintf(drv, -1, "IPS: wait %d sec to open switch", delay);
    
  start_ramp:
    ParLog(drv);
    FsmWait(1);
  return __LINE__; case __LINE__: /**********************************/
    EaseWrite(eab, "X");
  return __LINE__; case __LINE__: /**********************************/
    IpsStatus(drv); /* check for errors */
    if (drv->heaterFault) {
      if (time(NULL) > drv->swtim + 3) {
        ParPrintf(drv, eError, "IPS heater fault");
        eab->errCode = EASE_FAULT;
        goto off_finish;
      }
    }
    if (time(NULL) < drv->swtim + 30) 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 */
    EaseWrite(eab, "X");
  return __LINE__; case __LINE__: /**********************************/
    IpsStatus(drv); /* just check for errors */
    EaseWrite(eab, "R9"); /* read back ramp rate (may be sweep limited) */
  return __LINE__; case __LINE__: /**********************************/
    ramp=OxiGet(eab, 3, NULL, drv->ramp);
    step=ramp/6; /* step = ramp * 10 sec */
    if (step < 0.001) step=0.001;
    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;
    }
    OxiSet(eab, "J", fld, 3);
  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) goto hold_finish;
    /* but we continue in the background */
    drv->tim = time(NULL);
  stab3:
    ParLog(drv);
    FsmWait(1);
  return __LINE__; case __LINE__: /**********************************/
    EaseWrite(eab, "X");
  return __LINE__; case __LINE__: /**********************************/
    IpsStatus(drv); /* just check for errors */
    if (time(NULL) < drv->tim + 10) goto stab3; /* stabilize */
    
    EaseWrite(eab, "A0"); /* hold */
  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: wait 30 sec to close switch");
    
  wait_closed:
    ParLog(drv);
    FsmWait(1);
  return __LINE__; case __LINE__: /**********************************/
    EaseWrite(eab, "R18"); /* read persistent field in Tesla */
  return __LINE__; case __LINE__: /**********************************/
    fld = OxiGet(eab, 3, NULL, drv->current);
    if (fld != drv->lastfield) {
      IpsSetField(drv, fld); /* set drv->current and callback */
      drv->lastfield = fld;
      EaseParHasChanged();
    }
    EaseWrite(eab, "X");
  return __LINE__; case __LINE__: /**********************************/
    IpsStatus(drv);
    if (time(NULL) < drv->swtim + 30) goto wait_closed; /* wait */
  
    if (drv->current == 0) goto finish;
    EaseWrite(eab, "A2"); /* goto zero */
    ParPrintf(drv, -1, "IPS: ramp current to 0");
  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;
    drv->d.hwstate = HWIdle;
  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.001;
  return 1;
}
/*----------------------------------------------------------------------------*/
void IpsStartup(void) {
  ParMakeClass(&ipsClass, EaseDrivClass());
  MakeDriver("IPS", IpsInit, 0, "OI Power Supply"); 
}
