/*---------------------------------------------------------------------------
lsc370driv.c

Driver for the LakeShore Model 370 AC Resistance Bridge

Markus Zolliker, July 2006
----------------------------------------------------------------------------*/
#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 "lscsupport.h"
#include "fsm.h"
#include "initializer.h"

#define PID_FLAG 1
#define RDGRNG_FLAG 2
#define HTRRNG_FLAG 3
#define MAX_CHAN 4

typedef struct {
  EaseDriv d;
  float t;
  float htr;
  float set;
  float prop;
  float integ;
  float deriv;
  float resist;                 /* Ohm */
  float temp[MAX_CHAN];
  float res[MAX_CHAN];
  int channel[MAX_CHAN];
  int ighHeater;                /* IGH heater range (-1 if output is direct) */
  int htrRange;
  int currentEx;
  int voltageEx;
  int range;
  int autoRange;
  int index;
  int controlChan;
} Lsc370;

static ParClass lsc370Class = { "LSC370", sizeof(Lsc370) };

/*----------------------------------------------------------------------------*/
static float Lsc370Power(Lsc370 * drv, float percent)
{
  float power;                  /* mW */
  float current;                /* A */
  float voltage;                /* V */

  if (drv->htrRange == 0 || drv->ighHeater == 0) {
    power = 0;
  } else if (drv->ighHeater > 0) {
    current = pow(10, drv->htrRange * 0.5 - 5) * percent / 100;
    voltage = current * 500;
    if (voltage > 10)
      voltage = 10;
    power = pow(10, drv->ighHeater) * 0.0002 * voltage / 10;
  } else {
    current = pow(10, drv->htrRange * 0.5 - 5) * percent / 100;
    voltage = current * drv->resist;
    if (voltage > 10) {
      power = 10 * 10 / drv->resist * 1e3;
    } else {
      power = current * voltage * 1e3;
    }
  }
  return power;
}

/*----------------------------------------------------------------------------*/
static void Lsc370ParDef(void *object)
{
  Lsc370 *drv = ParCast(&lsc370Class, object);
  float power;
  int i;

  static char *heaterList[] = { "off",
    "30uA", "100uA", "300uA",
    "1mA", "3mA", "10mA", "30mA", "100mA", NULL
  };
  static char *currentList[] = { "off",
    "1pA", "3pA", "10pA", "30pA", "100pA", "300pA",
    "1nA", "3nA", "10nA", "30nA", "100nA", "300nA",
    "1uA", "3uA", "10uA", "30uA", "100uA", "300uA",
    "1mA", "3mA", "10mA", "30mA", NULL
  };
  static char *voltageList[] = { "off",
    "2uV", "6uV", "20uV", "60uV", "200uV", "600uV",
    "2mV", "6mV", "20mV", "60mV", "200mV", "600mV",
    NULL
  };
  static char *rangeList[] = { "auto",
    "2mOhm", "6mOhm", "20mOhm", "60mOhm", "200mOhm", "600mOhm",
    "2Ohm", "6Ohm", "20Ohm", "60Ohm", "200Ohm", " 600Ohm",
    "2kOhm", "6kOhm", "20kOhm", "60kOhm", "200kOhm", "600kOhm",
    "2MegaOhm", "6MegaOhm", "20MegaOhm", "60MegaOhm", NULL
  };
  static char *offOn[] = { "off", "on", NULL };

  static char *tNames[] = { "tsample", "tstill", "tmix", "tplate" };
  static char *rNames[] = { "rsample", "rstill", "rmix", "rplate" };
  static char *cNames[] = { "csample", "cstill", "cmix", "cplate" };

  ParName("");
  ParTail("K");
  ParFloat(&drv->temp[0], PAR_NAN);

  ParName("res");
  ParTail("Ohm");
  ParFloat(&drv->res[0], PAR_NAN);

  ParName("set");
  ParTail("K");
  if (EaseUpdate(EASE_RUN)) {
    ParFloat(&drv->d.targetValue, PAR_NAN);
  } else {
    ParFloat(&drv->set, PAR_NAN);
  }

  ParName("htr");
  ParTail("%");
  ParFloat(&drv->htr, PAR_NAN);

  for (i = 0; i < MAX_CHAN; i++) {
    if (drv->channel[i] > 0 && i > 0) {
      ParName(tNames[i]);
      ParTail("K");
      ParFloat(&drv->temp[i], PAR_NAN);

      ParName(rNames[i]);
      ParTail("Ohm");
      ParFloat(&drv->res[i], PAR_NAN);
    }
    ParName(cNames[i]);
    ParAccess(usUser);
    ParInt(&drv->channel[i], 0);
  }

  ParName("prop");
  ParTail("(gain)");
  EaseUpdate(PID_FLAG);
  ParFmt("%.3f");
  ParFloat(&drv->prop, PAR_NAN);

  ParName("integ");
  ParTail("sec");
  EaseUpdate(PID_FLAG);
  ParFmt("%.0f");
  ParFloat(&drv->integ, PAR_NAN);

  ParName("deriv");
  ParTail("sec");
  EaseUpdate(PID_FLAG);
  ParFmt("%.0f");
  ParFloat(&drv->deriv, PAR_NAN);

  ParName("resist");
  ParTail("Ohm");
  ParAccess(usUser);
  ParFloat(&drv->resist, 500.0);

  ParName("ighHeater");
  ParAccess(usUser);
  ParInt(&drv->ighHeater, -1);

  ParName("controlChan");
  ParList(NULL);
  ParAccess(usUser);
  ParInt(&drv->controlChan, 1);

  ParName("currentEx");
  EaseUpdate(RDGRNG_FLAG);
  ParEnum(currentList);
  if (drv->currentEx > 0)
    ParList(NULL);
  ParInt(&drv->currentEx, PAR_NAN);
  if (ParActionIs(PAR_SET) > 0) {
    if (drv->currentEx > 22) {
      drv->currentEx = 22;
    } else if (drv->currentEx <= 0) {
      drv->currentEx = 0;
    } else {
      drv->voltageEx = 0;
    }
  }

  ParName("voltageEx");
  EaseUpdate(RDGRNG_FLAG);
  ParEnum(voltageList);
  if (drv->voltageEx > 0)
    ParList(NULL);
  ParInt(&drv->voltageEx, PAR_NAN);
  if (ParActionIs(PAR_SET) > 0) {
    if (drv->voltageEx > 12) {
      drv->voltageEx = 12;
    } else if (drv->voltageEx <= 0) {
      drv->voltageEx = 0;
    } else {
      drv->currentEx = 0;
    }
  }

  ParName("range");
  EaseUpdate(RDGRNG_FLAG);
  ParEnum(rangeList);
  if (drv->autoRange == 0)
    ParList(NULL);
  ParInt(&drv->range, PAR_NAN);
  if (ParActionIs(PAR_SET) > 0) {
    if (drv->range > 22) {
      drv->range = 22;
      drv->autoRange = 0;
    } else if (drv->range <= 0) {
      drv->autoRange = 1;
      drv->range = 0;
    }
  }

  ParName("autoRange");
  EaseUpdate(RDGRNG_FLAG);
  ParEnum(offOn);
  ParList(NULL);
  ParInt(&drv->autoRange, PAR_NAN);
  if (ParActionIs(PAR_SET) > 0) {
    if (drv->autoRange) {
      drv->autoRange = 1;
    }
  }

  ParName("htrRange");
  EaseUpdate(HTRRNG_FLAG);
  ParEnum(heaterList);
  ParList(NULL);
  ParInt(&drv->htrRange, PAR_NAN);
  if (ParActionIs(PAR_SET) > 0) {
    if (drv->htrRange > 8) {
      drv->htrRange = 8;
    } else if (drv->htrRange <= 0) {
      drv->htrRange = 0;
    }
  }

  ParName("maxPower");
  ParTail("mW");
  power = Lsc370Power(drv, 100.0);
  ParFmt("%.3g");
  ParFloat(&power, 0.0);

  ParName("power");
  ParTail("mW");
  power = Lsc370Power(drv, drv->htr);
  ParFloat(&power, 0.0);

  EaseBasePar(drv);
  EaseSendPar(drv);
  EaseDrivPar(drv, "%.5g", "K");
  ParStdDef();
  EaseMsgPar(drv);

  ParName("period");
  ParAccess(usUser);
  ParInt(&drv->d.b.p.period, 5);
}

/*----------------------------------------------------------------------------*/
static long Lsc370Read(long pc, void *object)
{
  Lsc370 *drv = ParCast(&lsc370Class, object);
  EaseBase *eab = object;
  int mode, exi, rng, autoR, eoff;
  float x, y, z;
  char buf[16];

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    if (pc != 0) {
      return -2;
    }
    drv->index = 0;
  chanLoop:
    if (drv->channel[drv->index] == 0) {
      drv->temp[drv->index] = PAR_NAN;
      goto noRead;
    }

    snprintf(buf, sizeof buf, "RDGK?%d", drv->channel[drv->index]);
    EaseWrite(eab, buf);
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (1 == sscanf(eab->ans, "%f", &x)) {
      drv->temp[drv->index] = x;
    }

    snprintf(buf, sizeof buf, "RDGR?%d", drv->channel[drv->index]);
    EaseWrite(eab, buf);
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (1 == sscanf(eab->ans, "%f", &x)) {
      drv->res[drv->index] = x;
    }

  noRead:
    drv->index++;
    if (drv->index < MAX_CHAN)
      goto chanLoop;

    EaseWrite(eab, "HTR?");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (1 == sscanf(eab->ans, "%f", &x)) {
      drv->htr = x;
    }
    EaseWrite(eab, "SETP?");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (1 == sscanf(eab->ans, "%f", &x)) {
      if (drv->set != x && !EaseGetUpdate(drv, EASE_RUN)) {
        drv->d.targetValue = x;
      }
      drv->set = x;
    }
    if (EaseGetUpdate(drv, PID_FLAG))
      goto skipPid;
    EaseWrite(eab, "PID?");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (EaseGetUpdate(drv, PID_FLAG))
      goto skipPid;
    if (3 == sscanf(eab->ans, "%f,%f,%f", &x, &y, &z)) {
      drv->prop = x;
      drv->integ = y;
      drv->deriv = z;
    }
  skipPid:

    if (EaseGetUpdate(drv, HTRRNG_FLAG))
      goto skipHtrRng;
    EaseWrite(eab, "HTRRNG?");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (EaseGetUpdate(drv, HTRRNG_FLAG))
      goto skipHtrRng;
    if (1 == sscanf(eab->ans, "%d", &rng)) {
      drv->htrRange = rng;
/*
      if (rng == 0) {
        drv->set = 0;
      }
*/
    }
  skipHtrRng:

    if (EaseGetUpdate(drv, RDGRNG_FLAG))
      goto skipRdgRng;
    EaseWrite(eab, "RDGRNG?1");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (EaseGetUpdate(drv, RDGRNG_FLAG))
      goto skipRdgRng;
    if (5 ==
        sscanf(eab->ans, "%d,%d,%d,%d,%d", &mode, &exi, &rng, &autoR,
               &eoff)) {
      if (eoff) {
        drv->currentEx = 0;
        drv->voltageEx = 0;
      } else if (mode) {
        drv->currentEx = exi;
        drv->voltageEx = 0;
      } else {
        drv->voltageEx = exi;
        drv->currentEx = 0;
      }
      drv->range = rng;
      drv->autoRange = autoR;
    }
  skipRdgRng:


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

/*----------------------------------------------------------------------------*/
static long Lsc370Start(long pc, void *object)
{
  Lsc370 *drv = ParCast(&lsc370Class, object);
  EaseBase *eab = object;
  char msg[256];
  
  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(drv);
    EaseWrite(eab, "*IDN?");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (0 != strncmp(eab->version, "LSCI,MODEL370", 13)) {
      snprintf(msg, sizeof msg,
               "unknown temperature controller version: %s", eab->version);
      EaseStop(eab, msg);
      goto quit;
    }
    ParPrintf(drv, eLog, "connected to %s", eab->version);
    FsmCall(Lsc370Read);
    return __LINE__;
  case __LINE__:                 /**********************************/

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

/*----------------------------------------------------------------------------*/
static long Lsc370Set(long pc, void *object)
{
  Lsc370 *drv = ParCast(&lsc370Class, object);
  EaseBase *eab = object;
  char cmd[128];
  int upd, mode, exi, eoff;

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(drv);
    EaseWrite(eab, "MODE 1;MODE?");     /* remote mode */
    return __LINE__;
  case __LINE__:                 /**********************************/
  loop:
    upd = EaseNextUpdate(drv);
    if (upd == EASE_RUN)
      goto run;
    if (upd == HTRRNG_FLAG)
      goto htrrng;
    if (upd == PID_FLAG)
      goto pid;
    if (upd == RDGRNG_FLAG)
      goto rdgrng;
    goto finish;

  run:
    snprintf(cmd, sizeof cmd, "SETP %.5g;SETP?", drv->d.targetValue);
    EaseWrite(eab, cmd);
    return __LINE__;
  case __LINE__:                 /**********************************/
    /* fall through */

  htrrng:
    snprintf(cmd, sizeof cmd, "CSET %d,1,1,1,1,8,%g;CSET?",
             drv->controlChan, drv->resist);
    EaseWrite(eab, cmd);
    return __LINE__;
  case __LINE__:                 /**********************************/

    snprintf(cmd, sizeof cmd, "HTRRNG %d;HTRRNG?", drv->htrRange);
    EaseWrite(eab, cmd);
    return __LINE__;
  case __LINE__:                 /**********************************/
    goto loop;

  pid:
    snprintf(cmd, sizeof cmd, "PID %.5g,%.5g,%.5g;PID?", drv->prop,
             drv->integ, drv->deriv);
    EaseWrite(eab, cmd);
    return __LINE__;
  case __LINE__:                 /**********************************/
    goto loop;

  rdgrng:
    if (drv->voltageEx > 0) {
      mode = 0;
      drv->currentEx = 0;
      exi = drv->voltageEx;
      eoff = 0;
    } else if (drv->currentEx > 0) {
      mode = 1;
      drv->voltageEx = 0;
      exi = drv->currentEx;
      eoff = 0;
    } else {
      mode = 0;
      exi = 5;
      eoff = 1;
    }
    if (drv->range <= 0) {
      if (drv->autoRange) {
        drv->range = 13;
      } else {
        drv->range = 1;
      }
    }
    if (drv->autoRange)
      drv->autoRange = 1;
    snprintf(cmd, sizeof cmd, "RDGRNG 1,%d,%d,%d,%d,%d;RDGRNG?1",
             mode, exi, drv->range, drv->autoRange, eoff);
    EaseWrite(eab, cmd);
    return __LINE__;
  case __LINE__:                 /**********************************/
    goto loop;

  finish:
    EaseWrite(eab, "MODE 0;MODE?");     /* local mode */
    return __LINE__;
  case __LINE__:                 /**********************************/

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

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

  drv = EaseMakeDriv(con, &lsc370Class, argc, argv, dynamic, 7,
                     Lsc370ParDef, LscHandler, Lsc370Start, NULL,
                     Lsc370Read, Lsc370Set);
  if (drv == NULL)
    return 0;
  drv->htrRange = 0;
  setRS232ReplyTerminator(drv->d.b.ser, "\n");
  setRS232SendTerminator(drv->d.b.ser, "\n");
  return 1;
}

/*----------------------------------------------------------------------------*/
void Lsc370Startup(void)
{
  ParMakeClass(&lsc370Class, EaseDrivClass());
  MakeDriver("LSC370", Lsc370Init, 0,
             "LakeShore 370 AC Resistance Bridge");
}
