/*---------------------------------------------------------------------------
itcdriv.c

Driver for the Oxford Instruments ITC503/ITC4 temperature controller
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"

#define TESLATRON -1
#define ITC_SETHTR 1
#define ITC_SETGAS 2
#define ITC_PIDMODE 3
#define ITC_PROP 4
#define ITC_INTEG 5
#define ITC_DERIV 6
#define ITC_MAXVOLT 7

typedef struct {
  EaseDriv d;
  float t[4];                   /* temperatures (0 is set point) */
  int dig[4];                   /* format for these */
  float htr;
  float gas;
  float autoGasLimit;
  float setGas;
  float travelTime;             /* travel time for needle valve [sec/100%] */
  /* control parameters: for ITC / for TESLATRON */
  float prop;                   /* proportional value / for slow loop [mbar/K] */
  float integ;                  /* integration time / for slow loop [sec] */
  float deriv;                  /* deriv. time / int. for pressure [sec] */
  float prop2;                  /* TESLATRON only: prop. for pressure [%/mbar] */
  float lambdaTarget;           /* TESLATRON only: lambda stage target temperature */
  float power;
  float resist;
  float htrVolt;
  float maxVolt;
  float maxPower;
  int cntVolt;
  int readMaxVolt;
  int pidMode;
  int sampleChan;
  int controlChan;
  int gasMode;
  int htrMode;
  int remote;
  int h;                        /* actual heater channel */
  int a;                        /* actual auto mode */
  time_t lastCtrl;
  float lastTdiff, lastPdiff;
} Itc;

static ParClass itcClass = { "ITC", sizeof(Itc) };

/*----------------------------------------------------------------------------*/
static void ItcParDef(void *object)
{
  Itc *drv = ParCast(&itcClass, object);
  EaseBase *eab = object;

  char fmt[8] = "";
  int i;
  static char *ti[4] = { "setp", "t1", "t2", "t3" };
  static char *modeList[] = { "off", "manual", "auto", NULL };
  static char *progressList[] = { "off", "cooling", "ok", NULL };
  static char *pidList[] = { "default", "manual", "auto", NULL };

  ParName("sampleChan");
  if (eab->syntax != TESLATRON)
    ParAccess(usUser);
  ParInt(&drv->sampleChan, 1);
  if (ParActionIs(PAR_SET) > 0) {
    if (drv->sampleChan < 1 || drv->sampleChan > 3) {
      drv->sampleChan = 1;
    }
  }

  if (eab->syntax != TESLATRON) {
    ParName("controlChan");
    ParAccess(usUser);
    ParInt(&drv->controlChan, 0);
    if (ParActionIs(PAR_SET) > 0) {
      if (drv->controlChan < 0 || drv->controlChan > 3) {
        drv->controlChan = 1;
      }
    }
  }

  ParName("");
  if (eab->syntax == TESLATRON) {
    ParTail("mbar");
  } else {
    ParTail("K");
  }
  i = drv->sampleChan;
  if (drv->dig[i] < 0) {
    snprintf(fmt, sizeof fmt, "%s", "%.3f");
  } else {
    snprintf(fmt, sizeof fmt, "%s.%df", "%", drv->dig[i]);
  }
  ParFmt(fmt);
  ParFloat(&drv->t[drv->sampleChan], PAR_NAN);

  ParName("dig1");
  ParAccess(usUser);
  ParLogAs(NULL);
  ParInt(&drv->dig[1], -1);
  ParName("dig2");
  ParAccess(usUser);
  ParLogAs(NULL);
  ParInt(&drv->dig[2], -1);
  ParName("dig3");
  ParAccess(usUser);
  ParLogAs(NULL);
  ParInt(&drv->dig[3], -1);

  if (eab->syntax != TESLATRON) {
    ParName(ti[0]);
    if (drv->controlChan != 0) {
      snprintf(fmt, sizeof fmt, "%%.%df", drv->dig[drv->controlChan]);
      ParFmt(fmt);
      if (fabsf(drv->d.targetValue - drv->t[0]) < 9e-4) {
        ParList("");
      }
    } else {
      ParList("");
    }
    ParTail("K");
    ParFloat(&drv->t[0], PAR_NAN);
  }

  for (i = 1; i <= 3; i++) {

    ParName(ti[i]);
    if (drv->dig[i] >= 0 && drv->sampleChan != i) {
      snprintf(fmt, sizeof fmt, "%%.%df", drv->dig[i]);
      ParFmt(fmt);
      ParTail("K");
    } else {
      ParList("");
    }
    ParFloat(&drv->t[i], PAR_NAN);

  }

  if (eab->syntax != TESLATRON) {
    ParName("htrMode");
    if (drv->controlChan == 0 && drv->htr == 0) {
      ParList("");
    }
    ParEnum(modeList);
    ParSave(1);
    ParInt(&drv->htrMode, 2);

    ParName("htr");
    ParFmt("%.1f");
    ParTail("%");
    ParSave(1);
    EaseUpdate(ITC_SETHTR);
    ParFloat(&drv->htr, PAR_NAN);

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

    ParName("heaterMode");
    ParInt(&drv->a, 0);

  } else {
    ParName("progress");
    ParAccess(usUser);
    ParEnum(progressList);
    ParList(NULL);
    ParSave(1);
    ParInt(&drv->gasMode, 0);
  }

  ParName("gas");
  ParFmt("%.1f");
  ParTail("%");
  if (drv->gasMode < 1) {
    ParList("");
  }
  ParFloat(&drv->gas, PAR_NAN);

  ParName("setGas");
  ParFmt("%.1f");
  ParTail("%");
  if (drv->gasMode < 1) {
    ParList("");
  }
  EaseUpdate(ITC_SETGAS);
  ParSave(1);
  if (drv->setGas > 99.9)
    drv->setGas = 99.9;
  if (drv->setGas < 0)
    drv->setGas = 0;
  ParFloat(&drv->setGas, 20.0);

  ParName("travelTime");
  ParFmt("%.0f");
  ParTail("sec");
  ParList("");
  ParAccess(usUser);
  ParSave(1);
  ParFloat(&drv->travelTime, 100.0);

  if (eab->syntax != TESLATRON) {
    ParName("autoGasLimit");
    ParAccess(usUser);
    ParFmt("%.1f");
    ParTail("K");
    ParSave(1);
    if (drv->gasMode != 2)
      ParList("");
    ParFloat(&drv->autoGasLimit, 3.0);

    ParName("pidMode");
    ParEnum(pidList);
    EaseUpdate(ITC_PIDMODE);
    ParSave(1);
    ParInt(&drv->pidMode, 0);

    ParName("prop");
    ParFmt("%.3f");
    ParTail("K");
    if (drv->pidMode == 1)
      ParList("");
    EaseUpdate(ITC_PROP);
    ParFloat(&drv->prop, PAR_NAN);

    ParName("int");
    ParFmt("%.1f");
    ParTail("min");
    if (drv->pidMode == 1)
      ParList("");
    EaseUpdate(ITC_INTEG);
    ParFloat(&drv->integ, PAR_NAN);

    ParName("deriv");
    ParFmt("%.2f");
    ParTail("min");
    if (drv->pidMode == 1)
      ParList("");
    EaseUpdate(ITC_DERIV);
    ParFloat(&drv->deriv, PAR_NAN);

    ParName("maxPower");
    ParFmt("%.4g");
    ParTail("W");
    EaseUpdate(ITC_MAXVOLT);
    ParFloat(&drv->maxPower, PAR_NAN);
    if (ParActionIs(PAR_SET) > 0) {
      if (drv->maxPower < 0) {
        drv->readMaxVolt = 1;
        drv->maxPower = PAR_NAN;
        drv->maxVolt = PAR_NAN;
      } else {
        drv->readMaxVolt = 0;
        if (drv->resist < 0) {
          drv->resist = 0;
        }
        drv->maxVolt = sqrt(drv->maxPower * drv->resist);
        if (drv->maxVolt > 47.9) {
          drv->maxVolt = 47.9;
        }
      }
    }

    ParName("resist");
    ParFmt("%g");
    ParAccess(usUser);
    ParTail("Ohm");
    ParList("");
    ParFloat(&drv->resist, 80.0);

    ParName("power");
    ParFmt("%g");
    ParTail("W");
    ParFloat(&drv->power, PAR_NAN);

  } else {

    ParName("lambdaTarget");
    ParFmt("%.2f");
    ParTail("K");
    if (drv->gasMode == 0)
      ParList("");
    ParAccess(usUser);
    ParSave(1);
    ParFloat(&drv->lambdaTarget, 2.2);

    ParName("prop");
    ParFmt("%.1f");
    ParTail("mbar/K");
    ParList("");
    ParAccess(usUser);
    ParSave(1);
    ParFloat(&drv->prop, 300);

    ParName("int");
    ParFmt("%.1f");
    ParTail("mbar/K/min");
    ParList("");
    ParAccess(usUser);
    ParSave(1);
    ParFloat(&drv->integ, 3.0);

    ParName("prop2");
    ParFmt("%.1f");
    ParTail("%/mbar");
    ParList("");
    ParAccess(usUser);
    ParSave(1);
    ParFloat(&drv->prop2, 1.0);

    ParName("int2");
    ParFmt("%.1f");
    ParTail("%/mbar/min");
    ParList("");
    ParAccess(usUser);
    ParSave(1);
    ParFloat(&drv->deriv, 2.0);
  }

  if (drv->controlChan == 0) {
    i = drv->dig[drv->sampleChan];
  } else {
    i = drv->dig[drv->controlChan];
  }
  if (i < 0) {
    snprintf(fmt, sizeof fmt, "%s", "%.3f");
  } else {
    if (eab->syntax == TESLATRON)
      i += 2;
    snprintf(fmt, sizeof fmt, "%s.%df", "%", i);
  }

  EaseBasePar(drv);
  EaseSendPar(drv);
  if (eab->syntax != TESLATRON) {
    EaseDrivPar(drv, fmt, "K");
  } else {
    EaseDrivPar(drv, fmt, "mbar");
  }
  ParStdDef();
  EaseMsgPar(drv);
}

/*----------------------------------------------------------------------------*/
static void ItcLcParDef(void *object)
{
  EaseBase *eab = object;
  eab->syntax = TESLATRON;
  ItcParDef(object);
}

/*----------------------------------------------------------------------------*/
void ItcStatus(Itc * drv)
{
  char *ans;
  int *code;

  if (drv->d.b.state != EASE_read)
    return;
  ans = drv->d.b.ans;
  code = &drv->d.b.errCode;
  if (ans[0] != 'X' || ans[2] != 'A' || ans[4] != 'C' || ans[6] != 'S') {
    ParPrintf(drv, eError, "illegal status response");
    *code = EASE_FAULT;
    return;
  }
  drv->a = ans[3] - '0';
  if (ans[9] == 'H') {
    drv->h = ans[10] - '0';
    if (ans[11] == 'L' && drv->pidMode != 0
        && !EaseGetUpdate(drv, ITC_PIDMODE)) {
      drv->pidMode = ans[12] - '0' + 1;
    }
  } else {
    drv->h = drv->controlChan;
  }
  if (ans[5] != '3' && drv->remote == 2) {
    ParPrintf(drv, eError, "ITC switched to local");
    *code = EASE_FAULT;
    drv->remote = 1;
    return;
  }
}

/*----------------------------------------------------------------------------*/
float controlPropInt(float diff,        /* input (soll-ist for negative loop, ist-soll for positive) */
                     float prop,        /* prop (gain) units: [out]/[inp] */
                     float intd,        /* int*delta (reset) units: [out]/[inp] */
                     float bandP,       /* prop deadband units: [inp] */
                     float bandi,       /* integral deadband units: [inp] */
                     float *buffer)
{                               /* a buffer to store last diff */

  float d, d0;

  bandP = bandP * 0.5;
  d0 = *buffer;
  if (fabs(diff) < bandi * 1.01) {
    intd = 0;
  }
  if (diff > d0 + bandP) {
    d = diff - bandP;
  } else if (diff < d0 - bandP) {
    d = diff + bandP;
  } else {
    d = d0;
  }
  *buffer = d;
  return prop * (d - d0) + intd * diff;
}

/*----------------------------------------------------------------------------*/
void controlLimits(float *inp, float min, float max)
{
  if (*inp > max) {
    *inp = max;
  } else if (*inp < min) {
    *inp = min;
  }
}

/*----------------------------------------------------------------------------*/
static long ItcRead(long pc, void *object)
{
  Itc *drv = ParCast(&itcClass, object);
  EaseBase *eab = object;
  char *p;
  int l;
  time_t now, delta;
  char buf[4];
  float gas, band, v1, v2, h;

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

    if (drv->dig[1] < 0)
      goto skip1;
    EaseWrite(eab, "R1");       /* read sensor 1 */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->t[1] = OxiGet(eab, drv->dig[1], &drv->dig[1], drv->t[1]);

  skip1:
    if (drv->dig[2] < 0)
      goto skip2;
    EaseWrite(eab, "R2");       /* read sensor 2 */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->t[2] = OxiGet(eab, drv->dig[2], &drv->dig[2], drv->t[2]);

  skip2:
    if (drv->dig[3] < 0)
      goto skip3;
    EaseWrite(eab, "R3");       /* read sensor 3 */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->t[3] = OxiGet(eab, drv->dig[3], &drv->dig[3], drv->t[3]);

  skip3:
    if (drv->controlChan == 0 || drv->a == 0) {
      drv->t[0] = drv->d.targetValue;
      goto skip0;
    }
    EaseWrite(eab, "R0");       /* read control T */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->t[0] = OxiGet(eab, drv->dig[drv->controlChan], NULL, drv->t[0]);
    if (drv->gasMode == 2 && eab->syntax != TESLATRON) {
      if (drv->t[drv->controlChan] > drv->autoGasLimit + 1.0) {
        if (drv->a < 2) {
          drv->a |= 2;          /* switch gas to auto */
          ParPrintf(drv, eWarning,
                    "needle valve switched to ITC control (AutoGas)");
        } else {
          goto skip0;
        }
      } else {
        if (drv->a >= 2) {
          if (drv->t[drv->controlChan] < drv->autoGasLimit) {
            drv->a &= 1;        /* switch gas to manual */
            ParPrintf(drv, eWarning,
                      "needle valve switched to software control");
          } else {
            goto skip0;
          }
          if (drv->setGas != drv->gas) {
            EaseSetUpdate(drv, ITC_SETGAS, 1);
          }
        } else {
          if (drv->setGas != drv->gas) {
            EaseSetUpdate(drv, ITC_SETGAS, 1);
          }
          goto skip0;
        }
      }
    } else if (drv->a < 2) {
      goto skip0;
    } else {
      drv->a &= 1;              /* switch gas to manual */
    }
    EaseWrite(eab, "C3");
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->remote = 2;

    snprintf(buf, sizeof buf, "A%d", drv->a);
    EaseWrite(eab, buf);
    return __LINE__;
  case __LINE__:                 /**********************************/
  skip0:

    EaseWrite(eab, "R7");       /* read gas flow */
    return __LINE__;
  case __LINE__:                 /**********************************/
    gas = OxiGet(eab, 1, NULL, drv->gas);

    time(&now);
    delta = now - drv->lastCtrl;
    drv->lastCtrl = now;

    if (drv->gas == PAR_NAN) {
      drv->gas = gas;
    } else {
      if (drv->travelTime < 1)
        drv->travelTime = 1;
      if (drv->gas < gas) {
        drv->gas += delta * 100.0 / drv->travelTime;
        if (drv->gas > gas)
          drv->gas = gas;
      } else {
        drv->gas -= delta * 100.0 / drv->travelTime;
        if (drv->gas < gas)
          drv->gas = gas;
      }
    }

    if (eab->syntax != TESLATRON || drv->gasMode <= 0)
      goto skipFlow;
    if (delta > 10)
      delta = 10;

    if (drv->prop <= 0) {
      band = 0.1;
    } else {
      band = (drv->d.upperLimit - drv->d.lowerLimit) / drv->prop;
    }
    if (drv->t[2] - drv->lambdaTarget > band) {
      drv->gasMode = 1;
    }
    if (drv->gasMode == 1) {
      drv->d.targetValue = drv->d.upperLimit;
      if (drv->t[2] <= drv->lambdaTarget + 0.001) {
        drv->gasMode = 2;
        drv->d.targetValue = drv->d.lowerLimit;
      }
    } else {
      drv->d.targetValue +=
          controlPropInt(drv->t[2] - drv->lambdaTarget,
                         drv->prop, drv->integ * delta / 60.0, 0.01, 0.01,
                         &drv->lastTdiff);
      band = 0.01 * drv->prop;
      controlLimits(&drv->d.targetValue,
                    drv->d.lowerLimit - band, drv->d.upperLimit + band);
    }

    drv->setGas +=
        controlPropInt(drv->d.targetValue - drv->t[1],
                       drv->prop2, drv->deriv * delta / 60.0,
                       drv->d.tolerance, 0.1, &drv->lastPdiff);
    controlLimits(&drv->setGas, 0.0, 99.9);

    if (fabsf(drv->setGas - gas) < 0.1)
      goto skipFlow;
    EaseParHasChanged();

    if (drv->remote == 2)
      goto skipActive;
    EaseWrite(eab, "C3");
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->remote = 2;
  skipActive:

    OxiSet(eab, "G", drv->setGas, 1);   /* cold valve setting */
    return __LINE__;
  case __LINE__:                 /**********************************/
  skipFlow:

    if (!drv->remote || eab->syntax == TESLATRON)
      goto skiprmt;
    EaseWrite(eab, "C0");
    drv->remote = 0;
    return __LINE__;
  case __LINE__:                 /**********************************/
  skiprmt:

    if (eab->syntax == TESLATRON)
      goto skipctrl;

    if (EaseGetUpdate(drv, ITC_MAXVOLT))
      goto skiphtr;
    EaseWrite(eab, "R5");       /* read heater percent */
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (EaseGetUpdate(drv, ITC_MAXVOLT))
      goto skiphtr;
    drv->htr = OxiGet(eab, 1, NULL, drv->htr);

    if (drv->readMaxVolt == 0) {
      drv->htrVolt = drv->htr * 0.01 * drv->maxVolt;
      goto skiphtr;
    }
    EaseWrite(eab, "R6");       /* read heater voltage */
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->htrVolt = OxiGet(eab, 1, NULL, drv->htrVolt);

    if (drv->readMaxVolt) {
      h = drv->htr;
      if (h > 1.0) {
        v1 = 0.1 * (int) (drv->htrVolt * 1000 / (h + 0.099) - 0.5);
        v2 = 0.1 * (int) (drv->htrVolt * 1000 / (h - 0.05) + 0.99);
        if (drv->maxVolt > v2) {
          drv->cntVolt++;
          if (drv->cntVolt > drv->readMaxVolt) {
            drv->maxVolt = v2;
            drv->cntVolt = 0;
            drv->readMaxVolt++;
          }
        } else if (drv->maxVolt < v1) {
          drv->cntVolt--;
          if (drv->cntVolt < -drv->readMaxVolt) {
            drv->maxVolt = v1;
            drv->cntVolt = 0;
            drv->readMaxVolt++;
          }
        } else {
          drv->cntVolt = 0;
        }
      }
    }
    if (drv->maxVolt != PAR_NAN) {
      drv->htrVolt = h * 0.01 * drv->maxVolt;
    }

  skiphtr:
    if (drv->resist <= 1)
      drv->resist = 80.0;
    if (drv->maxVolt != PAR_NAN) {
      drv->maxPower = drv->maxVolt * drv->maxVolt / drv->resist;
    }
    if (drv->htrVolt != PAR_NAN) {
      drv->power = drv->htrVolt * drv->htrVolt / drv->resist;
    }

    if (EaseGetUpdate(drv, ITC_PROP))
      goto skipprop;
    EaseWrite(eab, "R8");       /* read prop */
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (EaseGetUpdate(drv, ITC_PROP))
      goto skipprop;
    drv->prop = OxiGet(eab, 1, NULL, drv->prop);
  skipprop:

    if (EaseGetUpdate(drv, ITC_INTEG))
      goto skipint;
    EaseWrite(eab, "R9");       /* read int */
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (EaseGetUpdate(drv, ITC_INTEG))
      goto skipint;
    drv->integ = OxiGet(eab, 1, NULL, drv->integ);
  skipint:

    if (EaseGetUpdate(drv, ITC_DERIV))
      goto skipderiv;
    EaseWrite(eab, "R10");      /* read deriv */
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (EaseGetUpdate(drv, ITC_DERIV))
      goto skipderiv;
    drv->deriv = OxiGet(eab, 1, NULL, drv->deriv);
  skipderiv:

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

/*----------------------------------------------------------------------------*/
static long ItcStart(long pc, void *object)
{
  Itc *drv = ParCast(&itcClass, object);
  EaseBase *eab = object;
  char msg[256];
  
  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(drv);
    EaseWrite(eab, "V");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (eab->syntax == TESLATRON) {
      if (0 != strncmp(eab->version, "TESLATRON", 9)) {
        snprintf(msg, sizeof msg,
                 "unknown teslatron version: %s", eab->version);
        EaseStop(eab, msg);
        goto quit;
      }
    } else {
      if (0 == strncmp(eab->version, "ITC503", 6)) {
        eab->syntax = 3;
      } else if (0 == strncmp(eab->version, "ITC4", 4)) {
        eab->syntax = 0;
      } else {
        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(ItcRead);
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (drv->controlChan == 0 && drv->h >= 1 && drv->h <= 3) {
      drv->controlChan = drv->h;
    }
    drv->d.targetValue = drv->t[0];

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

/*----------------------------------------------------------------------------*/
static long ItcSetTemp(long pc, void *object)
{
  Itc *drv = ParCast(&itcClass, object);
  EaseBase *eab = object;
  char buf[4];
  int a;

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(drv);
    if (drv->controlChan == 0) {
      ParPrintf(drv, eError, "no control channel selected");
      goto quit;
    }
    if (drv->h == drv->controlChan)
      goto skiph;
    EaseWrite(eab, "A0");       /* heater off  */
    return __LINE__;
  case __LINE__:                 /**********************************/
    snprintf(buf, sizeof buf, "H%d", drv->controlChan);
    EaseWrite(eab, buf);        /* set heater to channel */
    return __LINE__;
  case __LINE__:                 /**********************************/

  skiph:
    OxiSet(eab, "T", drv->d.targetValue, drv->dig[drv->controlChan]);   /* set point */
    return __LINE__;
  case __LINE__:                 /**********************************/

    if (drv->pidMode != 0)
      goto skipPidMode;
    EaseWrite(eab, "L1");       /* 'auto' pid on  */
    return __LINE__;
  case __LINE__:                 /**********************************/
  skipPidMode:

    a = 1;                      /* auto heater */
    if (drv->a >= 2)
      a = 3;                    /* auto gas & heater */
    if (drv->d.targetValue == 0.0) {
      drv->htrMode = 0;
      a = 0;
      if (drv->setGas != drv->gas) {
        EaseSetUpdate(drv, ITC_SETGAS, 1);
      }
    } else {
      drv->htrMode = 2;         /* heater auto */
    }
    if (drv->h == drv->controlChan && drv->a == a)
      goto skipa;
    snprintf(buf, sizeof buf, "A%d", a);
    drv->a = a;
    EaseWrite(eab, buf);
    return __LINE__;
  case __LINE__:                 /**********************************/
  skipa:

    if (drv->a != 0)
      goto quit;
    EaseWrite(eab, "O0");       /* switch off heater */
    return __LINE__;
  case __LINE__:                 /**********************************/

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

/*----------------------------------------------------------------------------*/
static long ItcSetGas(long pc, void *object)
{
  Itc *drv = ParCast(&itcClass, object);
  EaseBase *eab = object;
  char buf[4];

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(drv);
    if (drv->gasMode != 1 && drv->gasMode != 2) {
      ParPrintf(drv, eError, "gasMode must be set to manual or auto");
      goto quit;
    }
    if (drv->a == 2) {
      EaseWrite(eab, "A0");
    } else if (drv->a == 3) {
      EaseWrite(eab, "A1");
    } else {
      goto skipmode;
    }
    return __LINE__;
  case __LINE__:                 /**********************************/
  skipmode:
    OxiSet(eab, "G", drv->setGas, 1);   /* cold valve setting */
    return __LINE__;
  case __LINE__:                 /**********************************/
    EaseWrite(eab, "R7");       /* read gas flow */
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (!EaseGetUpdate(drv, ITC_SETGAS)) {
      drv->setGas = OxiGet(eab, 1, NULL, drv->setGas);
    }
    if (drv->a < 2)
      goto quit;
    snprintf(buf, sizeof buf, "A%d", drv->a);
    EaseWrite(eab, buf);
    return __LINE__;
  case __LINE__:                 /**********************************/
  quit:
    return 0;
  }                             /* FSM END ******************************************* */
}

/*----------------------------------------------------------------------------*/
static long ItcSetHtr(long pc, void *object)
{
  Itc *drv = ParCast(&itcClass, object);
  EaseBase *eab = object;
  char buf[4];

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(drv);
    if (drv->a == 0)
      goto skipmode;
    EaseWrite(eab, "A0");
    return __LINE__;
  case __LINE__:                 /**********************************/
  skipmode:
    OxiSet(eab, "O", drv->htr, 1);      /* manual heater setting */
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (drv->a == 0)
      goto quit;
    snprintf(buf, sizeof buf, "A%d", drv->a);
    EaseWrite(eab, buf);
    return __LINE__;
  case __LINE__:                 /**********************************/
  quit:
    return 0;
  }                             /* FSM END ******************************************* */
}

/*----------------------------------------------------------------------------*/
static long ItcSet(long pc, void *object)
{
  Itc *drv = ParCast(&itcClass, object);
  EaseBase *eab = object;
  int upd;

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(drv);
    EaseWrite(eab, "C3");
  loop:
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->remote = 2;
    upd = EaseNextUpdate(drv);
    if (eab->syntax != TESLATRON) {
      switch (upd) {
      case EASE_RUN:
        FsmCall(ItcSetTemp);
        goto loop;
      case ITC_SETHTR:
        FsmCall(ItcSetHtr);
        goto loop;
      case ITC_SETGAS:
        FsmCall(ItcSetGas);
        goto loop;
      case ITC_PIDMODE:
        if (drv->pidMode == 1) {
          EaseWrite(eab, "L0");
        } else {
          EaseWrite(eab, "L1");
          drv->pidMode = 2;
        }
        goto loop;
      case ITC_PROP:
        OxiSet(eab, "P", drv->prop, 1);
        goto loop;
      case ITC_INTEG:
        OxiSet(eab, "I", drv->integ, 1);
        goto loop;
      case ITC_DERIV:
        OxiSet(eab, "D", drv->deriv, 1);
        goto loop;
      case ITC_MAXVOLT:
        OxiSet(eab, "M", drv->maxVolt, 1);
        goto loop;
      default:
        break;
      }
    }
    if (eab->syntax == TESLATRON && drv->gasMode > 0)
      goto quit;
    EaseWrite(eab, "C0");
    return __LINE__;
  case __LINE__:                 /**********************************/
    drv->remote = 0;
  quit:
    return 0;
  }                             /* FSM END ******************************************* */
}

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

  drv = EaseMakeDriv(con, &itcClass, argc, argv, dynamic, 7,
                     ItcParDef, OxiHandler, ItcStart, NULL, ItcRead,
                     ItcSet);
  if (drv == NULL)
    return 0;
  drv->d.b.syntax = 0;
  drv->cntVolt = 0;
  drv->maxVolt = PAR_NAN;
  drv->htrVolt = PAR_NAN;
  drv->readMaxVolt = 1;
  return 1;
}

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

  drv = EaseMakeDriv(con, &itcClass, argc, argv, dynamic, 7,
                     ItcLcParDef, OxiHandler, ItcStart, NULL, ItcRead,
                     ItcSet);
  if (drv == NULL)
    return 0;
  drv->d.b.syntax = TESLATRON;
  drv->lastCtrl = 0;
  drv->lastTdiff = 0;
  drv->lastPdiff = 0;
  return 1;
}

/*----------------------------------------------------------------------------*/
void ItcStartup(void)
{
  ParMakeClass(&itcClass, EaseDrivClass());
  MakeDriver("ITC", ItcInit, 0, "OI Temperature Controller");
  MakeDriver("LC", ItcInitLc, 0, "OI Lambda Controller");
}
