#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/timeb.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include "sys_util.h"
#include "err_handling.h"
#include "coc_server.h"
#include "coc_logfile.h"
#include "str_util.h"
#include "tecs_lsc.h"
#include "tecs_dlog.h"

int ftime (struct timeb *__timeptr); /* for some reason not defined in timeb.h with flag -std1 */

#define TABLE_FILE "tecs.tab"
#define Progress(I) if (configuring) { configuring+=I; }
#define undef -65535.

static SerChannel *ser=NULL;
static char *serverId=NULL;
static char *binDir=NULL;
static char *logDir=NULL;

typedef struct {
  float t, t1, t2, min, max, band; /* temperatures */
  int stat1, stat2; /* reading status summary */
  int present; /* sensor is present */
  int readStat; /* reading status */
  char ch[2]; /* channels */
} SensorT;

SensorT
  sens1, sens2, sens3, sens4,
  *sensors[5]={NULL, &sens1, &sens2, &sens3, &sens4 },
  *sensor=&sens1;

typedef struct {
  SensorT *sensor1, *sensor2;
  float temp; /* weighted temperature */
  float tMin, tMax; /* minimum and maximum temperatures since ... */
  int dirty; /* input config to be reloaded */
  int try; /* trial count */
  int manual; /* manual device */
  int code, code1; /* device code, buffer for device code */
  int codChanged; /* code has changed */
  int codDefined; /* code is not yet confirmed */
  float scale; /* scale for extreme ranges */
  char device[16]; /* device name */
  char tname[16];
} Testpoint;

Testpoint /* C standard guarantees initialization to zero */
  cryo={&sens1, &sens2 }, /* data for main sensors (on heat exchanger, or the only sensors) */
  samp={&sens3, &sens4 }, /* data for extra sensors of sample stick */
  *tpoints[2]={&cryo, &samp},
  *tpoint=&cryo;

static float
  tempC, /* set T (for sample) */
  tempH, /* set T on heat exchanger */
  htr,   /* heater current percentage */
  tLimit, maxPower, /* heater parameters */
  tLow=0, tHigh=0, /* lower limit of high-T sensor, upper limit of low-T sensor */
  tShift=0, /* setpoint shift */
  aux, /* auxilliary value, i.e. helium level */
  full, /* full value for helium level */
  prop, integ, deriv, /* pid */
  maxShift=2, /* maximal shift in when controlMode=2 */
  tInt=0; /* integral time (sec.) for setpoint shift */

static int
  logPeriod=0, /* data logging period (sec.) */
  period=5000, /* default read interval (msec.) */
  logTime, /* next logging time */
  setFlag, /* temperature to be set */
  maxPowerFlag, /* maxPower to be set */
  pidFlag, /* pid's to be set */
  saveTime, /* time for a CRVSAV command */
  noResp=2, /* no response */
  quit, /* quit server */
  controlMode=2, /* 0: control on heater, 1: control on sample, 3: 2nd loop for difference heater-sample */
  heliumMode, /* 0: no level meter, 1: constant current, 2: pulsed */
  int2=30, /* inegration time for controlMode 2 */
  remoteMode, /* 1: local, 2: remote */
  maxfld, /* last used display field */
  busy, /* busy after CRVSAV */
  relay, relay0, /* relay status */
  deviceFlag, /* device given via net */
  num, /* curve number */
  fld, /* field number */
  key, /* key status */
  serialNo,
  configuring=1,
  stable, /* stable since 2 min. */
  resist, /* heater resistance */
  readTemp, /* client requested readTemp */
  cod1, cod2, out1, out2, /* codes read from digital input/output */
  iRange, iAmp, /* max. range and max. current code */
  htrst, htrst0, /* heater status */
  per; /* effective period */

static time_t
  tim, /* actual time */
  mmInt, mmTime, /* interval and time for next min-max logging */
  tableTime; /* last time when table was read */

static int decod[8]={21,20,17,16,5,4,1,0}; /* for code conversion */

static char
  status[132], /* status buffer */
  device[64], /* concatenated device names */
  buf1[256], buf2[256], buf3[256], buf4[256], /* buffers for temporary use */
  head[64], /* curve header */
  intype[64], /* input configuration */
  chan[2], /* actual channel */
  alarms[20], /* alarm status */
  alarmList[4], /* alarm list */
  chanS[4], chanM[4], /* channels in input routine */
  helium[80], /* helium level status */
  dlogfile[128],
  controlChannel[2]="A";

static char
  *table=NULL, /* environment devices table */
  *cache=NULL, /* curve list cache */
  *logfile="<none>";

static char
  *heaterStatus[7]={
    "heater o.k.\n",
    "heater supply over V\n",
    "heater supply under V\n",
    "heater output DAC error\n",
    "heater Ilimit DAC error\n",
    "open heater load\n",
    "heater load < 10 Ohm\n",
  };

struct timeb tim0;
int logMask;

void idleHdl(int tmo, int fd) {
  int iRet;

  iRet=CocHandleRequests(tmo, fd);
  if (iRet<0) logfileShowErr("CocHandleRequests");
}

void concatDevice(void) {
  str_copy(device, cryo.device);
  if (0!=strcmp(cryo.device, samp.device) && (samp.device[0]!='\0' || samp.manual)) {
    str_append(device, "/");
    str_append(device, samp.device);
  }
  if (cryo.manual) {
    str_append(device, " (manual");
  } else {
    str_append(device, " (auto");
  }
  if (samp.manual==cryo.manual) {
    str_append(device, ")");
  } else if (samp.manual) {
    str_append(device, "/manual)");
  } else {
    str_append(device, "/auto)");
  }
}

int putPermanentData(FILE *fil) {
  char buf[256];
  char *d1, *d2;

  if (cryo.manual) {
    d1="*";
  } else {
    d1="";
  }
  if (samp.manual) {
    d2="*";
  } else {
    d2="";
  }
  sprintf(buf, "%s%s/%s%s/%d/%d\n", d1, cryo.device, d2, samp.device, cryo.code, samp.code);
  ERR_SI(fputs(buf, fil));
  return(0);
  OnError: return(-1);
}

int instCurve(char *nam, char *channel, int dispFld) {
/*
  install sensor nam on channel
*/
  char buf[256], chead[64], nbuf[256], lbuf[16];
  char *crv, *entry, *points, *start,
    *s, /* start of found entry */
    *e, /* cache part after found entry */
    *res, *t;
  int i, n, c1, c2;
  char used[60];
  FILE *fil;
  int retstat;

  fil=NULL;
  crv=NULL;
  e=NULL;

  str_copy(chan, channel);
  logfileOut(LOG_MAIN, "install curve %s\n", nam);

  /* read curve file */
  str_copy(nbuf, binDir);
  str_append(nbuf, nam);
  retstat=-2; /* an error would be severe */
  ERR_P(crv=str_read_file(nbuf));

  t=str_split(chead, crv, '\n');
  if (t==NULL) {
    points=NULL;
  } else {
    points=str_split(intype, t, '\n');
  }
  if (points==NULL) ERR_MSG("illegal curve file");
  if (cache==NULL) { ERR_SP(cache=MALLOC(1)); *cache='\0'; } /* create empty cache if undefined */

  start=strchr(cache, '\n'); /* skip permanent data */
  if (start==NULL) { start=cache; } else { start++; }

  if (points[0]=='$') { /* standard curve */

    points++; num=atoi(points);
    if (num>20) ERR_MSG("illegal standard curve number");

    retstat=-1; /* an error could be fixed */
    ERR_P(LscCmd(ser, "CRVHDR?[num]>head"));

  } else {

    strcpy(buf, ":");
    str_append(buf, nam);
    str_append(buf, " ");
    str_upcase(buf, buf);

    entry=strstr(start, buf);
    if (entry==NULL) { /* sensor not found in cache */
      entry=start;
      for (i=60;i>20;i--) used[i]=0;
      n=40; num=0;
      while (entry!=NULL && n>0) {
        i=0;
        sscanf(entry, "%d", &i);
        if (i>20 && i<=60) { num=i; s=entry; used[i]=1; n--; }
        entry=strchr(entry, '\n');
        if (entry !=NULL) entry++;
      }
      if (n>0) {
        for (num=60;num>20;num--) { if (used[num]==0) break; }
        s=NULL;
        e=NULL;
      } else {
        e=strchr(s, '\n');
        if (e!=NULL) { *e='\0'; e++; }
        if (s>start) { s--; *s='\0'; }
      }
      head[0]='\0';
    } else {
      s=entry; entry++;
      while (s>start && *s!='\n') s--;
      if (s>start) { *s='\0'; s++; }
      num=0;
      sscanf(s, "%d", &num);
      if (num<21 || num>60) ERR_MSG("illegal curve number");

      retstat=-1; /* an error could be fixed */
      ERR_P(LscCmd(ser, "CRVHDR?[num]>head"));

      e=strchr(entry, '\n');
      if (e!=NULL) { *e='\0'; e++; }
    }
  }
  fld=dispFld;
  if (fld>maxfld) maxfld=fld;

  if (head[0]!='\0' && LscEqPar(head, chead)) { /* header matches: select sensor type and curve */

    retstat=-1; /* an error could be fixed */
    ERR_P(LscCmd(ser, "RANGE:0;INTYPE [chan]:[intype];INCRV [chan]:[num]"));
    ERR_P(LscCmd(ser, "MNMX [chan]:1,1;DISPFLD [fld],[chan],1;DISPLAY [maxfld]"));
    logfileOut(LOG_MAIN, "curve %d on channel %s selected\n", num, chan);
    Progress(100);

  } else { /* header does not match -> download */

    retstat=-2; /* an error would be severe */
    if (num<=20) ERR_MSG("standard curve does not match");
    retstat=-1; /* an error could be fixed */
    if (busy) ERR_MSG("busy");
    logfileOut(LOG_MAIN, "download curve %d\n", num);
    /* select sensor type first to display sensor units */
    ERR_P(LscCmd(ser, "RANGE:0;INTYPE [chan]:[intype];DISPLAY:[maxfld]"));
    Progress(1);

    n=3;
    do {
      ERR_P(LscCmd(ser, "CRVDEL [num];CRVHDR?[num]>buf1,"));
      buf1[4]='\0';
      i=strcmp(buf1, "User");
      n--;
    } while (i!=0 && n!=0);
    if (i!=0) ERR_MSG("can not delete curve");

    sprintf(lbuf, "CRVPT %d", num);
    i=0;
    do { /* download curve */
      t=str_split(nbuf, points, '\n');
      if (nbuf[0]!='\0') {
        ERR_I(str_substitute(buf, nbuf, "#0", lbuf));
        ERR_P(LscCmd(ser, buf));
        i++;
        if (i%10==0) sprintf(status, "downloading curve at line %d", i);
        Progress(1);
      }
      points=t;
    } while (t!=NULL);

    /* write header, select curve */
    str_upcase(head, chead);
    ERR_P(LscCmd(ser, "CRVHDR [num]:[head];INCRV [chan]:[num]"));
    ERR_P(LscCmd(ser, "MNMX [chan]:1,1;DISPFLD [fld],[chan],1;DISPLAY [maxfld]"));
    Progress(1);
    logfileOut(LOG_MAIN, "curve selected on channel %s\n", chan);
    saveTime=tim+30;
  }
  FREE(crv); crv=NULL;

  if (num<=20) return(0); /* standard curve, do not touch cache */

  /* rewrite cache with actual entry at beginning */
  retstat=-2; /* errors in following section are severe */
  sprintf(lbuf, "lsc.%d", serialNo);
  str_copy(nbuf, logDir);
  str_append(nbuf, lbuf);
  fil=fopen(nbuf, "r+");
  if (fil==NULL) ERR_SP(fil=fopen(nbuf, "w"));
  ERR_I(putPermanentData(fil));
  if (num>20) { /* write actual entry */
    sprintf(buf, "%d:%s", num, head);
    ERR_SI(fputs(buf, fil));
  }
  if (start!=s) { /* write content before replaced entry */
    ERR_SI(fputs("\n", fil));
    ERR_SI(fputs(start, fil));
  }
  if (e!=NULL) { /* write content after replaced entry */
    ERR_SI(fputs("\n", fil));
    ERR_SI(fputs(e, fil));
  }
  ERR_SI(fputc('\0', fil));
  ERR_SI(fclose(fil));
  fil=NULL;
  FREE(cache);
  /* re-read it */
  ERR_P(cache=str_read_file(nbuf));
  return(0);

  OnError:
    if (crv!=NULL) FREE(crv);
    if (fil!=NULL) fclose(fil);
    return(retstat);
}

int configInput(void) {
  char *t, *e;
  char buf[80], nam[16], nbuf[256], ch[4];
  int i, l, n, nn, dispFld;
  int retstat;
  char *ext;
  Str_Buf sbuf;

  retstat=-2; /* errors in following section are severe */
  if (tpoint->manual) {
    sprintf(buf, "'%s'", tpoint->device);
  } else {
    sprintf(buf, "%+d,", tpoint->code);
    if (tpoint->code==0) return(0);
  }
  if (table!=NULL && tim>tableTime+60) { FREE(table); table=NULL; }; /* clear old table */
  if (table==NULL) { /* read table */
    str_copy(nbuf, binDir);
    str_append(nbuf, TABLE_FILE);
    ERR_P(table=str_read_file(nbuf));
    tableTime=tim;
  }
  t=strstr(table, buf);
  if (t==NULL) ERR_MSG("device not found");
  e=strchr(t, '\'');
  if (e==NULL || e>strchr(t,'\n')) ERR_MSG("missing ' or device name in table file");
  t=e+1;
  if (tpoint==&samp) {
    sens3.present=0;
    sens4.present=0;
    ext=".s";
    dispFld=2;
  } else {
    sens1.present=0;
    sens2.present=0;
    ext=".x";
    dispFld=1;
    tLow=0; tHigh=0;
    controlMode=0;
    heliumMode=0;
  }
  chanS[0]='\0';
  chanM[0]='\0';

  i=sscanf(t, "%12s%n", nam, &l);
  if (i<1) ERR_MSG("missing device name");
  t+=l;

  /* interprete settings until '+' appeares */
  i=sscanf(t, "%64s%n", buf, &l);
  while (i>0 && buf[0]!='+') {
    t+=l;
    e=strchr(buf,'=');
    if (e==NULL) ERR_MSG("syntax error");
    *e='\0';
    str_link_buf(&sbuf, e+1, 0, ',');
    ERR_I(CocGetVar(serverVarList, &sbuf, buf, 0));
    i=sscanf(t, "%64s%n", buf, &l);
  }

  if (tpoint==&samp) {
    str_copy(ch, chanS);
  } else {
    if (!samp.manual && (NULL!=strchr(chanS,'A') || NULL!=strchr(chanS,'B'))) {
      samp.dirty=1; /* sample and heat exchanger are on main plug */
      samp.code=cryo.code;
    }
    str_copy(ch, chanM);
    if (heliumMode==0) {
      sprintf(helium, "no He-level meter for '%s", nam);
    } else {
      sprintf(helium, "He-level meter not yet read", nam);
    }
  }

  n=strlen(ch);
  if (n==0) return(0);
  if (n>2) ERR_MSG("no more than 2 channels per plug allowed");

  nam[strlen(nam)-1]='\0'; /* strip off quote */
  if (!tpoint->manual) { /* set device name */
    str_copy(tpoint->device, nam);
    concatDevice();
  }
  str_append(nam, ext);
  if (ch[0]<'A' || ch[0]>'D') ERR_MSG("illegal channel");
  tpoint->sensor1->ch[0]=ch[0];
  tpoint->sensor1->ch[1]='\0';
  ERR_I(retstat=instCurve(nam, tpoint->sensor1->ch, dispFld));
  tpoint->sensor1->present=1;
  tpoint->sensor1->band=10;
  if (n==2) {
    if (ch[1]>='a' && ch[1]<='d') {
      tpoint->sensor2->ch[0]=ch[1];
      tpoint->sensor2->ch[1]='\0';
      str_copy(chan, ch+1);
      logfileOut(LOG_MAIN, "auxilliary input on channel %s\n", chan);
      if (ch[0]>'b') {
        ERR_P(LscCmd(ser, "INTYPE [chan]:0,1,2,1,13;MNMX [chan]:1,3"));
      } else {
        ERR_P(LscCmd(ser, "INTYPE [chan]:0,1,2,1,12;MNMX [chan]:1,3"));
      }
      tpoint->sensor2->present=2;
    } else {
      if (ch[1]<'A' || ch[1]>'D') ERR_MSG("illegal channel");
      tpoint->sensor2->ch[0]=ch[1];
      tpoint->sensor2->ch[1]='\0';
      str_append(nam, "l");
      ERR_I(retstat=instCurve(nam, tpoint->sensor2->ch, dispFld+2));
      tpoint->sensor2->present=1;
    }
    tpoint->sensor2->band=10;
  }
  return(0);
  OnError: return(retstat);
}

int loadCache(void) {
  int i, j, k;
  char *res;
  char buf[256], nbuf[256], lbuf[16];
  char *bufi[4];
  FILE *fil;

  fil=NULL;
  sprintf(lbuf, "lsc.%d", serialNo);
  str_copy(nbuf, logDir);
  str_append(nbuf, lbuf);
  ERR_SP(fil=fopen(nbuf, "w"));
  ERR_I(putPermanentData(fil));
  bufi[0]=buf1;
  bufi[1]=buf2;
  bufi[2]=buf3;
  bufi[3]=buf4;
  for (i=60; i>21; i-=4) {
    sprintf(buf, "CRVHDR?%d>buf1;CRVHDR?%d>buf2;CRVHDR?%d>buf3;CRVHDR?%d>buf4", i, i-1, i-2, i-3);
    Progress(1);
    ERR_P(LscCmd(ser, buf));
    k=0;
    for (j=i; j>i-4; j--) {
      res=bufi[k]; k++;
      if (res[1]=='s') { i=0; break;} /* s of "User", must be empty slot, lowercase letters are not programmable */
      sprintf(buf, "%d:", j);
      ERR_SI(fputs(buf, fil));
      ERR_SI(fputs(res, fil));
      ERR_SI(fputs("\n", fil));
    }
  }
  ERR_SI(fclose(fil));
  /* re-read cache */
  ERR_P(cache=str_read_file(nbuf));
  return(0);
  OnError:
    if (fil!=NULL) fclose(fil);
    return(-1);
}

float WeightedAverage(float tH, float tL) {  
  float p,q;

  if (tH!=0.0) {
    if (tL!=0.0) {
      if (tL<tLow) {
        return(tL);
      } else if (tH<tHigh) {
        p=tHigh-tH;
        q=tL-tLow;
        if (p==0.0 && q==0.0) { p=1; q=1; } /* should not be the case */
        return((tL*p*p+tH*q*q)/(p*p+q*q));
      }
    }
    return(tH);
  } else if (tL!=0.0) {
    return(tL);
  }
  return(0.0);
}

void LogMinMax(int new) {
  char buf[256];
  int i;
  SensorT *s1, *s2;

  for (i=0; i<2; i++) {
    tpoint=tpoints[i];
    s1=tpoint->sensor1;
    s2=tpoint->sensor2;
    tpoint->tMin = WeightedAverage(s1->min, s2->min) * tpoint->scale;
    tpoint->tMax = WeightedAverage(s1->max, s2->max) * tpoint->scale;
    s1->min=0;
    s1->max=0;
    s2->min=0;
    s2->max=0;
  }
  if (cryo.tMax>0.0) {
    sprintf(buf, "@%.3f < T < %.3f K", cryo.tMin, cryo.tMax);
    if (samp.tMax>0.0) {
      sprintf(buf1, " (reg), %.3f < T < %.3f K (samp)", samp.tMin, samp.tMax);
      str_append(buf, buf1);
    }
    logfileOut(LOG_MAIN, "%s\n", buf);
  }
  if (new) {
    mmInt=60;
  } else if (mmInt<600) {
    mmInt=mmInt+60;
  }
  mmTime=tim+mmInt;
}

int ReadTemp(void) {
  char buf[256], bufs[256];
  int i, l, ls, stat;
  struct tm *tb;

  readTemp=0;

  l=0;
  ls=0;
  for (i=1; i<=4; i++) {
    sensor=sensors[i];
    sensor->stat1=0;
    sensor->stat2=0;
    if (sensor->present) {
      assert(l<128);
      sprintf(buf+l, "MDAT?[sens%d.ch]>sens%d.t1,sens%d.t2;", i, i, i);
      l=strlen(buf);
      assert(ls<128);
      sprintf(bufs+ls, "MDATST?[sens%d.ch]>sens%d.stat1,sens%d.stat2;", i, i, i);
      ls=strlen(bufs);
    }
  }
  if (ls>0) {
    buf[l-1]='\0'; /* strip off ';' */
    ERR_P(LscCmd(ser, buf));
    str_append(bufs, "MNMXRST");
    ERR_P(LscCmd(ser, bufs));
  }

  /* check for reading errors */
  for (i=1; i<=4; i++) {
    sensor=sensors[i];
    if (sensor->present) {
      stat=(sensor->stat1 | sensor->stat2) & 255;
      if (stat != sensor->readStat) {
        sensor->readStat=stat;
        if (stat &  1) logfileOut(LOG_MAIN, "invalid reading %s\n", sensor->ch);
        if (stat &  2) logfileOut(LOG_MAIN, "old reading %s\n", sensor->ch);
        if (stat & 12) logfileOut(LOG_MAIN, "unknown reading status %s\n", sensor->ch);
        if (stat & 16) logfileOut(LOG_MAIN, "temp underrange %s\n", sensor->ch);
        if (stat & 32) logfileOut(LOG_MAIN, "temp overrange %s\n", sensor->ch);
        if (stat & 64) logfileOut(LOG_MAIN, "units zero %s\n", sensor->ch);
        if (stat &128) logfileOut(LOG_MAIN, "units overrange %s\n", sensor->ch);
        if (stat==0) logfileOut(LOG_MAIN, "reading o.k. %s\n", sensor->ch);
      }
      if (stat==0) {

        if (sensor->present==2 && heliumMode==2) {
          sensor->t = sensor->t1; /* take minimum only */
        } else if (sensor->t2 - sensor->t1 <= sensor->band*2) { /* normal case */
          sensor->t = (sensor->t1 + sensor->t2) * 0.5; /* mean of min and max */
          sensor->band = sensor->band/2;
        } else { /* probably noisy values */
          logfileOut(LOG_MAIN, "min/max %s: %f %f\n", sensor->ch, sensor->t1, sensor->t2);
          if (fabs(sensor->t1 - sensor->t) < fabs(sensor->t2 - sensor->t) ) {
            sensor->t=sensor->t1;
            sensor->t2 = sensor->t1;
          } else {
            sensor->t=sensor->t2;
            sensor->t1 = sensor->t1;
          }
          sensor->t1 = sensor->t;
          sensor->t2 = sensor->t;
        }
        if (sensor->present==2) {
          aux=sensor->t; sensor->t=0;
          if (full>0 && heliumMode>0) {
            tb=localtime(&tim);
            if (aux<full && aux>0) {
              sprintf(helium, "%5.1f %% (%02d.%02d., %02d:%02d)", 100.0*aux/full
                , tb->tm_mday, tb->tm_mon+1, tb->tm_hour, tb->tm_min);
            }
          }
        }
        if (sensor->t1 < sensor->min || sensor->min==0.0) sensor->min = sensor->t1;
        if (sensor->t2 > sensor->max) sensor->max = sensor->t2;
        if (sensor->max - sensor->min > sensor->band) {
          sensor->band = sensor->max - sensor->min;
        }
      } else {
        sensor->t=0;
      }
    } else {
      sensor->t=0;
    }
  }

  cryo.temp=WeightedAverage(sens1.t, sens2.t)*cryo.scale;
  samp.temp=WeightedAverage(sens3.t, sens4.t)*samp.scale;
  if (samp.temp==0.0) samp.temp=cryo.temp;
  if (!deviceFlag
      && !samp.dirty && samp.codDefined && !samp.codChanged
      && !cryo.dirty && cryo.codDefined && !cryo.codChanged) {
    configuring=0;
  } else if (configuring==0 && remoteMode==2) {
    str_copy(status, "configuring");
    configuring=1;
  }
  return(0);
  OnError: return(-1);
}

int SetTemp(int switchOn) {
  char *ch;
  float scale;

  if (tempC>tLimit) {
    tempC=tLimit;
    logfileOut(LOG_MAIN, "set point too high, reset to %f\n", tempC);
  }
  if (switchOn) {
    ERR_I(ReadTemp());
    LogMinMax(1);
  }
  scale=cryo.scale;
  ch=sens1.ch;
  if (sens2.present==1 && tempC<(tLow+tHigh)/2) ch=sens2.ch;
  if (sens3.present) {
    if (controlMode==1) { /* control directly on sample sensor */
      tShift=0;
      if (sens3.t!=0) ch=sens3.ch;
      if (sens4.present==1 && tempC<(tLow+tHigh)/2 && sens4.t!=0) ch=sens4.ch;
      scale=samp.scale;
    } else if (controlMode!=2) {
      tShift=0;
    }
  } else {
    tShift=0;
  }
  str_copy(chan, ch);
  if (scale!=1.0) { /* show set point on display (for rdrn) */
    ERR_P(LscCmd(ser, "LINEAR C,1,0,1,1,[tempC]"));
  }
  if (tShift>maxShift) {
    tShift=maxShift;
  } else if (tShift<-maxShift) {
    tShift=-maxShift;
  }
  tempH=(tempC+tShift)/scale;
  if (tempH>tLimit) tempH=tLimit;
  if (tempC==0) {
    ERR_P(LscCmd(ser, "CSET 1:[chan],1,1,0;RANGE:0;SETP 1:0"));
  } else if (remoteMode==1) { /* in local mode: do not switch on heater */
    ERR_P(LscCmd(ser, "SETP 1:[tempH]"));
    ch=controlChannel;
  } else if (switchOn || (htrst>=5 && relay==0)) { /* switch on also when bad heater message and no alarms */
    ERR_P(LscCmd(ser, "CSET 1:[chan],1,1,0;RANGE:[iRange];SETP 1:[tempH]"));
  } else {
    ERR_P(LscCmd(ser, "CSET 1:[chan],1;SETP 1:[tempH]"));
  }
  if (0!=strcmp(ch, controlChannel) || switchOn) {
    str_copy(controlChannel, ch);
    logfileOut(LOG_MAIN, "set %f (on %s)\n", tempC, ch);
  }
  return(0);
  OnError: return(-1);
}

int PeriodicTask(void) {
  char buf[256], lbuf[16];
  char *next, *alms;
  int i, k;
  time_t putTim;
  float t3[3], p, d, w, t;

  ERR_P(LscCmd(ser, "DIOST?>cod1,out1;DOUT 3,29;HTR?>htr;HTRST?>htrst;RELAYST?1>relay;BUSY?>busy"));
  if (cryo.codDefined && samp.codDefined) {
    per=period; /* no timeout on above command and codes are defined: normal period */
    if (per>logPeriod*1000) per=logPeriod*1000;
  }

  if (noResp) { /* there was no response on an earlier command, or we are initializing */
    if (!configuring) remoteMode=2;
    LscCmd(ser, "MODE:[remoteMode]");
    k=serialNo; /* check serial number */
    ERR_P(LscCmd(ser, "*IDN?>buf1,buf2,serialNo,"));
    if (0!=strcmp(buf1, "LSCI") || 0!=strcmp(buf2, "MODEL340") || serialNo==0) return(0);
    if (k!=serialNo) { /* controller exchanged or we are initializing */
      if (!configuring) {
        logfileOut(LOG_MAIN, "controller connected\n");
      }
      if (remoteMode==2) configuring++;
      tempC=0;
      /* reload curve cache: */
      if (cache!=NULL) FREE(cache);
      sprintf(lbuf, "lsc.%d", serialNo);
      str_copy(buf, logDir);
      str_append(buf, lbuf);
      cache=str_read_file(buf);
      if (cache==NULL && 0==strcmp(ErrMessage, "file not found")) ERR_I(loadCache());
      /* get device names and last codes separated by '/' */
      str_split(buf, cache, '\n'); /* read 1 line */
      samp.device[0]='\0';
      cryo.code=0;
      samp.code=0;
      next=str_split(buf1, buf, '/');
      if (next!=NULL) {
        next=str_split(buf2, next, '/');
        if (next!=NULL) {
          next=str_split(buf3, next, '/');
          cryo.code=atoi(buf3);
          cryo.codChanged=0;
          if (next!=NULL) {
            next=str_split(buf3, next, '\n');
            samp.code=atoi(buf3);
            samp.codChanged=0;
          }
        }
      }
      if (buf1[0]=='*') {
        str_copy(cryo.device, buf1+1);
        cryo.manual=1;
      } else {
        if (cryo.code!=0) str_copy(cryo.device, buf1);
        cryo.manual=0;
      }
      if (buf2[0]=='*') {
        str_copy(samp.device, buf2+1);
        samp.manual=1;
      } else {
        if (samp.code!=0) str_copy(samp.device, buf2);
        samp.manual=0;
      }
      concatDevice();
      if (cryo.manual || cryo.code!=0) { cryo.dirty=1; }
      if (samp.manual || samp.code!=0) { samp.dirty=1; }
    }
    noResp=0;
  }

  if (relay) {
    if (alarmList[0]!='\0') {
      str_copy(buf, "ALARMST?*");
      buf[8]=alarmList[0];
      if (alarmList[1]!='\0') {
        str_append(buf,";ALARMST?*");
        buf[18]=alarmList[1];
      }
      str_append(buf, ";ALMRST");
      ERR_P(alms=LscCmd(ser, buf));
      if (0!=strcmp(alarms, alms)) {
        str_copy(buf, "  ");
        if (alms[ 0]!='0' || alms[ 2]!='0') buf[0]=alarmList[0];
        if (alms[ 4]!='0' || alms[ 6]!='0') buf[1]=alarmList[1];
        if (0==strcmp(buf,"  ")) {
          logfileOut(LOG_MAIN, "No more alarms, but relay is on!\n");
        } else {
          logfileOut(LOG_MAIN, "Alarm on channel %s\n", buf);
        }
        str_copy(alarms, alms);
      }
    } else {
      if (!relay0) logfileOut(LOG_MAIN, "Relay is on!\n");
      alarms[0]='\0';
    }
  } else {
    if (relay0) logfileOut(LOG_MAIN, "No more alarms, relay is off\n");
    alarms[0]='\0';
  }
  relay0=relay;

  ERR_I(ReadTemp());

  if (htrst!=htrst0) {
    LogMinMax(0);
    if (htrst<0 || htrst>6) {
      sprintf(buf, "heater status %d\n", htrst);
      logfileOut(LOG_MAIN, buf);
    } else {
      logfileOut(LOG_MAIN, heaterStatus[htrst]);
    }
    htrst0=htrst;
  }

  if (tim>=logTime) {
    i=0;
    if (sens1.present) {
      t3[0]=cryo.temp;
      i=1;
    } else {
      t3[0]=undef;
    }
    if (sens3.present) {
      t3[1]=samp.temp;
      i=2;
    } else {
      if (sens2.present==1) {
        t3[1]=sens2.t;
        i=2;
      } else {
        t3[1]=undef;
      }
    }
    if (tempC!=0 || htr!=0) {
      t3[2]=htr*htr*maxPower*1e-4;
      i=3;
    } else {
      t3[2]=undef;
    }
    time(&putTim);
    if (i>0) ERR_I(dlog_put_(&putTim, &i, t3));
    logTime=(putTim/logPeriod+1)*logPeriod;
    if (tim>mmTime) LogMinMax(0);
  }
  if (tempC!=0) {
    if (sens1.present && sens3.present && controlMode==2) {
      t=sens1.t;
      if (sens2.present==1 && tempC<(tLow+tHigh)/2) t=sens2.t;
      d=(tempH-t)/t-1.0; /* relative difference */
      w=exp(-d*d*230); /* gaussian */
      if (w<0.1) tInt=0; /* reset when far from setpoint (more than 10 %) */
      if (int2<1) int2=1;
      if (tInt<int2*1000/per) tInt+=w; /* increase integral time until int2 sec. */
      if (tInt>w) {
        p=w/tInt;
      } else {
        p=1.0;
      }
      tShift=tShift*(1.0-p)+p*(cryo.temp-samp.temp);
      ERR_I(SetTemp(0));
    } else if (remoteMode==2 && sens1.present) {
      ERR_I(SetTemp(0));
    }
  }

  ERR_P(LscCmd(ser, "KEYST?>key;DIOST?>cod2,out2;DOUT 3,30"));

  if (busy==0) {
    if (out1!=30) {
      ERR_P(LscCmd(ser, "DOUT:3,30"));
      ERR_P(LscCmd(ser, "DIOST?>cod1,out1"));
    }
    if (out2!=29) {
      ERR_P(LscCmd(ser, "DOUT:3,29"));
      ERR_P(LscCmd(ser, "DIOST?>cod2,out2;DOUT 3,30"));
    }

    if (out1==30 && out2==29) {
      /* code conversion */
      cryo.code1=3*decod[cod2 % 8] ^ 2*decod[cod1 % 8]; /* ^ is exclusive OR */
      samp.code1=-(3*decod[cod2 / 8] ^ 2*decod[cod1 / 8]);
      for (i=0; i<2; i++) {
        tpoint=tpoints[i];
        if (tpoint->code1!=tpoint->code) { /* code has changed -> wait for a confirmation */
          tpoint->code=tpoint->code1;
          tpoint->codChanged=1;
        } else {
          if (tpoint->codChanged) { /* code change confirmed */
            tpoint->codChanged=0;
            Progress(1);
            tpoint->manual=0;
            if (tpoint->code1==0) {
              logfileOut(LOG_MAIN, "%s unplugged\n", tpoint->tname);
            } else {
              logfileOut(LOG_MAIN, "plugged %d on %s\n", tpoint->code1, tpoint->tname);
            }
            tempC=0;
            remoteMode=2; /* set to remote mode */
            LscCmd(ser, "MODE:[remoteMode]");
            tpoint->dirty=1;
            if (!configuring) configuring=1;
          }
          tpoint->codDefined=1;
        }
      }
    }
  }

  if (key!=0) {
    if (!(cryo.dirty || samp.dirty)) {
      logfileOut(LOG_MAIN ,"user touched keys\n");
    }
    if (cryo.manual || cryo.code!=0) cryo.dirty=1;
    if (samp.manual || samp.code!=0) samp.dirty=1;
    remoteMode=1;
    ERR_P(LscCmd(ser, "MODE?>remoteMode"));
    if (remoteMode==2) { /* user switched to remote mode */
      ERR_P(LscCmd(ser, "PID?1>prop,integ,deriv"));
      if (controlMode==2) {
        ERR_P(LscCmd(ser, "RANGE?>iRange"));
        if (iRange==0) tempC=0;
      } else {
        ERR_P(LscCmd(ser, "RANGE?>iRange;CSET?1>,cod1,;SETP?1>tempC"));
        if (cod1!=1) {
          tempC=0;
          logfileOut(LOG_MAIN, "set point was not in K, set to 0\n");
        }
      }
      setFlag=(iRange>0);
    }
  }

  if (saveTime!=0 && tim>saveTime) {
    ERR_P(LscCmd(ser, "CRVSAV;BUSY?>busy"));
    while (!busy) {
      idleHdl(200, 0); /* wait 200 ms */
      ERR_P(LscCmd(ser, "BUSY?>busy"));
    }
    saveTime=0;
  }
  return(0);
  OnError: return(-1);
}

int inputSettings(Testpoint *this) {

  tpoint=this;
  if (tpoint->dirty && samp.codDefined) {
    if (busy==0 || cryo.dirty>=0 && tpoint->dirty>=0) { /* do not enter when busy and cryo.dirty/P indicates error on last time */
      if (tpoint->manual) {
        logfileOut(LOG_MAIN ,"configure %s inputs for %s\n", tpoint->tname, tpoint->device);
      } else {
        if (tpoint->code==0) {
          logfileOut(LOG_MAIN ,"reset %s inputs\n", tpoint->tname);
	  if (cryo.code==0 && samp.code==0) {
            logfileOut(LOG_MAIN, "no more sensor connected\n");
          }
        } else {
          logfileOut(LOG_MAIN ,"configure %s inputs for code %+d\n", tpoint->tname, tpoint->code);
        }
      }
      if (tpoint->dirty>0) tpoint->try=0;
      tpoint->sensor1->present=0;
      tpoint->sensor2->present=0;
      if (!tpoint->manual) { tpoint->device[0]='\0'; concatDevice(); }
      tpoint->dirty=configInput();
      if (tpoint->dirty<0) {
        tpoint->try++;
        if (tpoint->dirty!=-1 || tpoint->try>3) {
          logfileShowErr("fatal error");
          tpoint->dirty=0; tpoint->device[0]='\0'; concatDevice();
        } else {
          logfileShowErr("try again");
        }
      }
    }
  }
  return(0);
  OnError: return(-1);
}

int SetMaxPower(void) {
  int i, j;
  float pa, pr, pw, dif;

  iAmp=1; iRange=0;
  if (maxPower>0) {
    pa=resist*4; /* max. maxPower */
    pw=0; dif=1.0e6;
    for (i=4; i>0; i--) {
      pr=pa;
      for (j=5; j>0; j--) {
        if (pr>maxPower) {
          if (pr/maxPower<dif) { dif=pr/maxPower; pw=pr; iAmp=i; iRange=j; }
        } else {
          if (maxPower/pr<dif) { dif=maxPower/pr; pw=pr; iAmp=i; iRange=j; }
        }
        pr=pr/10;
      }
      pa=pa/4;
    }
  }
  maxPower=pw;  
  logfileOut(LOG_MAIN, "maxPower %f\n", maxPower, iAmp, iRange);
  ERR_P(LscCmd(ser, "CDISP 1:1,[resist],1;MOUT 1:0;CMODE 1:1"));
  ERR_P(LscCmd(ser, "CLIMIT 1:[tLimit],0,0,[iAmp],[iRange]"));
  ERR_I(SetTemp(1));
  return(0);
  OnError: return(-1);
}

int Display(void) {
  char flds[6], fmt[6], buf[256];
  int i,k,l;
  SensorT *s;

  maxfld=0;
  k=1;
  flds[0]='*';
  flds[5]='\0';
  for (i=1; i<=4; i++) { /* fill in kelvin fields */
    s=sensors[i];
    if (s->present) {
      flds[k]=s->ch[0];
      if (s->present==1) { fmt[k]='1'; } else { fmt[k]='3'; } /* special value */
      if (k>maxfld) maxfld=k;
    } else {
      flds[k]='\0';
    }
    k=k+2; if (k>4) k=2;
  }

  for (i=1; i<=4; i++) { /* fill in raw fields */
    s=sensors[i];
    if (s->present) {
      k=strlen(flds); /* find next free field */
      if (k<=4) {
        if (k>maxfld) maxfld=k;
        flds[k]=s->ch[0];
        fmt[k]='3';
      }
    }
  }

  /* fields 5-8 standard raw data */
  ERR_P(LscCmd(ser, "DISPFLD 5,A,3;DISPFLD 6,C,3;DISPFLD 7,B,3;DISPFLD 8,D,3"));
  if (maxfld==0) { /* show raw data */
    ERR_P(LscCmd(ser, "DISPFLD 1,A,3;DISPFLD 2,C,3;DISPFLD 3,B,3;DISPFLD 4,D,3;DISPLAY:4"));
  } else {
    l=0;
    for (k=1; k<=maxfld; k++) {
      if (flds[k]!='\0') { 
        assert(l<128);
        sprintf(buf+l, "DISPFLD %d,%c,%c;", k, flds[k], fmt[k]);
        l=strlen(buf);
      }
    }
    str_append(buf, "DISPLAY:[maxfld]");
    ERR_P(LscCmd(ser, buf));
  }
  return(0);
  OnError: return(-1);
}

int Settings(void) {
  char nbuf[256], buf[256], *cfg, *p;

  cfg=NULL;
  if (cryo.dirty && cryo.codDefined || samp.dirty && samp.codDefined) {
    
    ERR_I(inputSettings(&cryo));
    ERR_I(inputSettings(&samp));

    ERR_P(LscCmd(ser, "ALARM A:0;ALARM B:0;ALARM C:0;ALARM D:0"));

    alarmList[0]='\0';
    alarmList[1]='\0';
    alarmList[2]='\0';
    if (sens1.present) {
      ERR_I(SetMaxPower());
      str_copy(buf, "ALARM [sens1.ch]:1,1,[tLimit],0,1,1;RELAY 1:1;BEEP:0");
      alarmList[0]=sens1.ch[0];
      if (sens3.present) {
        str_append(buf, ";ALARM [sens3.ch]:1,1,[tLimit],0,1,1");
        alarmList[1]=sens3.ch[0];
      }
      ERR_P(LscCmd(ser, buf));
    }

    /* switch of unused channels */
    buf[0]='\0';
    if (NULL==strchr(alarmList, 'A')) str_append(buf, ";ALARM A:0");
    if (NULL==strchr(alarmList, 'B')) str_append(buf, ";ALARM B:0");
    if (NULL==strchr(alarmList, 'C')) str_append(buf, ";ALARM C:0");
    if (NULL==strchr(alarmList, 'D')) str_append(buf, ";ALARM D:0");
    if (buf[0]!='\0') ERR_P(LscCmd(ser, buf+1)); /* send without leading semicolon */

    ERR_I(Display());

    str_copy(nbuf, binDir);
    str_append(nbuf, cryo.device);
    str_append(nbuf, ".cfg");
    cfg=str_read_file(nbuf);
    if (cfg!=NULL) {
      logfileOut(LOG_MAIN, "%s opened\n", nbuf);
      p=str_split(buf, cfg, '\n');
      sscanf(buf, "%f%f", &cryo.scale, &samp.scale);
      while (p!=NULL) {
        p=str_split(buf, p, '\n');
        ERR_P(LscCmd(ser, buf));
      }
      FREE(cfg);
    } else {
      cryo.scale=1;
      samp.scale=1;
    }

    ERR_I(ReadTemp());
  }
  return(0);
  OnError:
    if (cfg!=NULL) FREE(cfg);
    return(-1);
}

int ExecuteRequest(void) {
  char *t, *res;
  struct CocClient *client;
  float p;

  if (readTemp) ERR_I(ReadTemp());
  if (remoteMode==2) ERR_I(Settings());
  if (maxPowerFlag) {
    maxPowerFlag=0;
    ERR_I(SetMaxPower());
  }
  if (pidFlag) {
    pidFlag=0;
    ERR_P(LscCmd(ser,"PID 1:[prop],[integ],[deriv]"));
  }
  if (setFlag) {
    setFlag=0;
    if (sens1.present) {
      tInt=0; /* reset integral time */
      ERR_I(SetTemp(1));
    }
  }
  client=CocGetNextCmd();
  if (client!=NULL) {
    if (NULL!=strchr(client->cmd, '>') || NULL!=strchr(client->cmd, '[')) ERR_MSG("no variables allowed");
    res=LscCmd(ser, client->cmd);
    if (res==NULL && ErrMessage!=NULL) {
      str_copy(client->res, ErrMessage);
    } else {
      str_copy(client->res, res);
    }
    client->cmd[0]='\0';
  }
  if (deviceFlag) {
    tempC=0;
    remoteMode=2; /* set to remote mode */
    ERR_P(LscCmd(ser, "MODE:[remoteMode]"));
    if (!configuring) {
      str_copy(status, "configuring");
      configuring=1;
    }
    t=strchr(device, '/');
    if (t==NULL) {
      if (0==strcmp(device, "0") || 0==strcasecmp(device, "auto")) {
        cryo.manual=0; cryo.dirty=1; cryo.device[0]='\0';
        samp.manual=0; samp.dirty=1; samp.device[0]='\0';
      } else {
        str_copy(cryo.device, device);
        str_copy(samp.device, device);
        cryo.manual=1; cryo.dirty=1;
        samp.manual=1; samp.dirty=1;
      }
    } else {
      if (t!=device) {
        *t='\0';
        str_copy(cryo.device, device);
        *t='/';
        cryo.manual=1; cryo.dirty=1;
      }
      t++;
      if (*t!='\0') {
        str_copy(samp.device, t);
        samp.manual=1; samp.dirty=1;
      }
    }
    concatDevice();
    deviceFlag=0;
  }
  return(0);
  OnError: return(-1);
}

int mainBody(void)
{
  int i, iret, tdif;
  struct timeb tim1;

  logfileWrite(logMask);

  while (!quit) {
    ftime(&tim1);
    tdif=per-((tim1.time-tim0.time)*1000+tim1.millitm-tim0.millitm);
    if (tdif<0) { tim=tim1.time; break; } /* timeout */
    ERR_I(iret=CocHandleRequests(tdif, 0));
    if (iret==0) { time(&tim); break; } /* timeout */
    tim=tim1.time;
    ERR_I(ExecuteRequest());
    logfileWrite(logMask);
  }
  ftime(&tim1);
  tdif=((tim1.time-tim0.time)*1000+tim1.millitm-tim0.millitm)/period;
  i=period*tdif+tim0.millitm;
  tim0.time+=i / 1000;
  tim0.millitm=i % 1000;
  if (tdif>4) {
    logfileOut(LOG_MAIN ,"%d cycles lost\n", tdif-1);
  }

  ERR_I(PeriodicTask());
  if (remoteMode==2) ERR_I(Settings());

  return(0);
  OnError:
    if (0==strcmp(ErrMessage, "timeout")) {
      if (noResp==1) { /* this is the second time we have no response */
        per=period+15000; /* long period if no response */
        cryo.temp=0;
        samp.temp=0;
        remoteMode=1;
        configuring=0;
      } else {
        logfileOut(LOG_ALL ,"no response\n");
        noResp=1;
        per=100; /* try again soon */
      }
      return(0);
    }
    return(-1);
}

int main(int argc, char *argv[])
{ int logIt=0;
  int i, iret, cntError, use_stdout=0;
  char *host;
  char buf[256], opt;
  int port, msecTmo;

  str_copy(cryo.tname,"main");
  str_copy(sens1.ch,"A");
  str_copy(sens2.ch,"B");
  str_copy(samp.tname,"sample stick");
  str_copy(sens3.ch,"C");
  str_copy(sens4.ch,"D");
  cryo.codChanged=1;
  cryo.scale=1.0;
  samp.codChanged=1;
  samp.scale=1.0;

  logMask=LOG_MAIN;
  binDir="bin/";
  logDir="log/";
  serverId="tecs";
  host="lnsp26:4000/0";
  port=0;
  msecTmo=0;
  logfileOut(LOG_MAIN ,"%s ", argv[0]);
  for (i=1;i<argc;i++) {
    if (argv[i]!=NULL) {
      if (argv[i][0]=='-') {
        opt=tolower(argv[i][1]);
      } else {
        opt=' ';
      }
      if ('l'==opt) {
        logIt=1;
        logMask=LOG_ALL;
        opt=' ';
      } else if ('s'==opt) {
        use_stdout=1;
        opt=' ';
      } else if ('n'==opt) {
        i++;
        serverId=argv[i];
      } else if ('b'==opt) {
        i++;
        binDir=argv[i];
      } else if ('d'==opt) {
        i++;
        logDir=argv[i];
      } else if ('t'==opt) {
        i++;
        msecTmo=atoi(argv[i]);
      } else if ('h'==opt) {
        i++;
        host=argv[i];
      } else if ('p'==opt) {
        i++;
        port=atoi(argv[i]);
      } else if ('f'==opt) {
        i++;
        logPeriod=atoi(argv[i]);
      } else {
        logfileOut(LOG_MAIN ,"?");
      }
      if (opt!=' ') logfileOut(LOG_MAIN ,"-%c ", opt);
      logfileOut(LOG_MAIN ,"%s ", argv[i]);
    }
  }
  if (port==0) port=9753;
  if (msecTmo==0) msecTmo=1000;
  if (logPeriod==0) logPeriod=5;

  str_copy(buf, logDir);
  str_append(buf, serverId);

  str_copy(status, "starting up");
  logfileStatusBuf(status);
  logfile=logfileInit(buf, logIt, use_stdout, logIt && use_stdout);
  logfileOut(LOG_MAIN ,"\n");
  logfileWrite(logMask);

  ERR_I(CocInitServer(1024, port));

  CocDefStruct(cryo, Testpoint);
  CocDefStruct(samp, Testpoint);
  CocDefPtr(tpoint, Testpoint);

  CocFltFld(Testpoint, temp, CocRD);
  CocFltFld(Testpoint, scale, CocRD);
  CocFltFld(Testpoint, tMin, CocRD);
  CocFltFld(Testpoint, tMax, CocRD);

  CocDefStruct(sens1, SensorT);
  CocDefStruct(sens2, SensorT);
  CocDefStruct(sens3, SensorT);
  CocDefStruct(sens4, SensorT);
  CocDefPtr(sensor, SensorT);

  CocFltFld(SensorT, t, CocRD);
  CocFltFld(SensorT, t1, CocRD);
  CocFltFld(SensorT, t2, CocRD);
  CocIntFld(SensorT, readStat, CocRD);
  CocIntFld(SensorT, stat1, CocRD);
  CocIntFld(SensorT, stat2, CocRD);
  CocStrFld(SensorT, ch, CocRD);

  CocDefFlt(htr, CocRD);
  CocDefFlt(maxPower, maxPowerFlag);
  CocDefFlt(prop, pidFlag);
  CocDefFlt(integ, pidFlag);
  CocDefFlt(deriv, pidFlag);
  CocDefFlt(tempC, setFlag);
  CocDefFlt(tempH, CocRD);
  CocDefFlt(aux, CocRD);
  CocDefFlt(full, CocWR);
  CocDefFlt(tLimit, CocRD);
  CocDefFlt(tLow, CocRD);
  CocDefFlt(tHigh, CocRD);
  CocDefFlt(maxShift, CocWR);

  CocDefStr(device, deviceFlag);

  CocDefStr(buf1, CocRD);
  CocDefStr(buf2, CocRD);
  CocDefStr(buf3, CocRD);
  CocDefStr(buf4, CocRD);
  CocDefStr(head, CocRD);
  CocDefStr(chan, CocRD);
  CocDefStr(chanS, CocRD);
  CocDefStr(chanM, CocRD);
  CocDefStr(helium, CocRD);
  CocDefStr(intype, CocRD);
  CocDefStr(status, CocRD);
  CocDefStr(dlogfile, CocRD);
  CocDefStr(logfile, CocRD);

  CocDefInt(cod1, CocRD);
  CocDefInt(cod2, CocRD);
  CocDefInt(out1, CocRD);
  CocDefInt(out2, CocRD);
  CocDefInt(num, CocRD);
  CocDefInt(fld, CocRD);
  CocDefInt(key, CocRD);
  CocDefInt(maxfld, CocRD);
  CocDefInt(resist, CocRD);
  CocDefInt(iAmp, CocRD);
  CocDefInt(iRange, CocRD);
  CocDefInt(remoteMode, CocRD);
  CocDefInt(heliumMode, CocRD);
  CocDefInt(htrst, CocRD);

  CocDefInt(logPeriod, CocWR);
  CocDefInt(readTemp, CocWR);
  CocDefInt(controlMode, CocWR);
  CocDefInt(int2, CocWR);
  CocDefInt(busy, CocRD);
  CocDefInt(relay, CocRD);
  CocDefInt(serialNo, CocRD);
  CocDefInt(configuring, CocRD);
  CocDefInt(quit, CocWR);

  CocAlias(tempX,cryo.temp);
  CocAlias(tempP,samp.temp);
  CocAlias(tX,cryo.temp);
  CocAlias(tS,samp.temp);
  CocAlias(t1,sens1.t);
  CocAlias(t2,sens2.t);
  CocAlias(t3,sens3.t);
  CocAlias(t4,sens4.t);
  CocAlias(set,tempC);
  CocAlias(int,integ);

  ERR_P(ser=SerOpen(host, msecTmo, idleHdl));
  ERR_I(iret=CocHandleRequests(100, 0));
  ftime(&tim0);
  tim=tim0.time; 
  if (period>1000) { /* round time */
    tim0.time=tim0.time-(tim0.time % (period/1000));
    tim0.millitm=0;
  }
  str_copy(dlogfile, logDir);
  str_append(dlogfile, serverId);
  str_append(dlogfile, ".dlog");
  logfileOut(LOG_MAIN, "open data log file: %s\n", dlogfile);
  ERR_I(iret=dlog_open_write_(dlogfile));
  if (iret==1) logfileOut(LOG_MAIN, "created new data log file\n");
  logfileWrite(logMask);

  remoteMode=2;
  LscCmd(ser, "MODE?>remoteMode");
  prop=50;
  integ=20;
  deriv=0;
  LscCmd(ser, "PID?1>prop,integ,deriv");
  if (remoteMode!=2) configuring=0;
  per=1; /* advance fast when initializing */
  cntError=0;
  while (!quit) {
    iret=mainBody();
    if (iret<0) {
      cntError++;
      if (cntError>10) ERR_MSG("too many errors - quit");
      logfileShowErr("error in TecsServer/mainBody");
    } else {
      if (cntError>0) cntError--;
    }
  }
  LogMinMax(0);
  logfileWrite(logMask);
  ERR_MSG("got quit command");
  OnError:
    logfileShowErr("exit TecsServer");
    dlog_close_w_();
    SerClose(ser);
    CocCloseServer();
    return(0);
}
