#include <math.h>
#include <assert.h>
#include <sys/timeb.h>
#include <string.h>
#include <ctype.h>
#include "errhdl.h"
#include "server.h"
#include "logfile.h"
#include "util.h"
#include "lsc.h"
#include "dlog.h"

#define TABLE_FILE "lsc.tab"
#define NO_CODE 999

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

typedef struct {
  float temp, t1, t2; /* calc, high, low temperature */
  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 nSens; /* number of sensors */  
  int codChanged; /* code has changed */
  int codDefined; /* code is not yet confirmed */
  char ch1[2], ch2[2]; /* channels for high/low T */
  char device[16]; /* device name */
} Part;

Part /* C standard guarantees initialization to zero */
  cryo, /* data for main sensors (on heat exchanger, or the only sensors) */
  samp, /* data for extra sensors of sample stick */
  *parts[2]={&cryo, &samp},
  *part=&cryo;

static float
  tempC, /* set T */
  tempH, /* set T on heater */
  htr,   /* heat power */
  tLimit, power, /* heater parameters */
  tLow=0, tHigh=0, /* lower limit of high-T sensor, upper limit of low-T sensor */
  tShift=0, /* setpoint shift */
  tInt=0; /* integral time (sec.) for setpoint shift */

static int
  logPeriod=10, /* data logging period (sec.) */
  period=5000, /* default read interval (msec.) */
  logTime, /* next logging time */
  setFlag, /* temperature to be set */
  remoteFlag, /* to be set to remote mode */
  saveTime, /* time for a CRVSAV command */
  noResp=1, /* no response */
  quit, /* quit server */
  controlMode=2, /* 0: control on heater, 1: control on sample, 3: 2nd loop for difference heater-sample */
  mode, /* 0: local, 2: remote */
  maxfld, /* last used display field */
  busy, /* busy after CRVSAV */
  deviceFlag, /* device given via net */
  num, /* curve number */
  fld, /* field number */
  key, /* key status */
  serialNo,
  resist,
  cod1, cod2, out1, out2, /* codes read from digital input/output */
  iRange, iAmp, /* max. range and max. current code */
  per; /* effective period */

static time_t
  auto_remote_time, /* time for automatic reload */
  tim, /* actual time */
  tableTime; /* last time when table was read */

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

static char
  device[32], /* 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 */
  xxxx;

static char
  *table=NULL, /* environment devices table */
  *cache=NULL; /* curve list cache */

struct timeb tim0;
int logMask;

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

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

void concatDevice() {
  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);
  }
}

int instCurve(char *nam, char *channel) {
/*
  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;
  char used[60];
  FILE *fil;
  int retstat;

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

  fil=NULL;
  crv=NULL;

  /* 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 (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);

    if (cache==NULL) { cache=malloc(1); *cache='\0'; } /* create empty cache if undefined */
    start=strchr(cache, '\n'); /* skip device names */
    if (start==NULL) { start=cache; } else { start++; }
    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=chan[0]-'A'+1;
  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, "INTYPE [chan]:[intype];INCRV [chan]:[num];DISPFLD [fld],[chan],1;DISPLAY:[maxfld]"));
    logfileOut(LOG_MAIN, "curve %d on channel %s selected\n", num, chan);

  } 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, "INTYPE [chan]:[intype];DISPFLD [fld],[chan],3;DISPLAY:[maxfld]"));

    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);
    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));
      }
      points=t;
    } while (t!=NULL);

    /* write header, select curve */
    str_upcase(head, chead);
    ERR_P(LscCmd(ser, "CRVHDR [num]:[head];INCRV [chan]:[num];DISPFLD [fld],[chan],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"));
  if (cryo.manual) ERR_SI(fputs(cryo.device, fil));
  ERR_SI(fputs("/", fil));
  if (samp.manual) ERR_SI(fputs(samp.device, fil));
  ERR_SI(fputs("\n", fil));
  sprintf(buf, "%d:%s", num, head);
  ERR_SI(fputs(buf, fil)); /* write actual entry */
  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(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() {
  char *t;
  char buf[80], nam[16], nbuf[256];
  int i, n, nn;
  int retstat;
  char *ext;

  retstat=-2; /* errors in following section are severe */
  if (part->manual) {
    sprintf(buf, ".%s.", part->device);
  } else {
    sprintf(buf, "%+d ", part->code);
    if (part->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;
    str_replace_char(table, 9, ' '); /* replace TAB (9) by space in order to find codes terminated by tab */
  }
  t=strstr(table, buf);
  if (t==NULL) ERR_MSG("device not found");
  i=sscanf(t, "%79[^\n!]", buf); /* read line */
  t=strchr(buf, '.');
  if (t==NULL) ERR_MSG("missing '.' in table file");
  t++;
  n=1;
  if (part==&samp) {
    samp.nSens=0;
    i=sscanf(t, "%12s%d%d", nam, &nn, &n);
    if (i<1) ERR_MSG("missing sensor name");
    ext="s";
  } else {
    cryo.nSens=0;
    tLow=0; tHigh=0;
    controlMode=0;
    i=sscanf(t, "%12s%d%d%d%f%d%f%f%f", nam, &n, &nn, &controlMode, &tLimit, &resist, &power, &tLow, &tHigh);
    if (i<7) ERR_MSG("missing some sensor parameters");
    ext="x";
  }
  if (n<0 || n>2) ERR_MSG("illegal value for nsensor");
  if (n==0) return(0);
  if (!part->manual) { /* set device name */
    str_copy(part->device, nam);
    part->device[strlen(part->device)-1]='\0'; /* strip off '.' */
    concatDevice();
  }
  str_append(nam, ext);

  ERR_I(retstat=instCurve(nam, part->ch1));
  if (n==2) {
    str_append(nam, "l");
    ERR_I(retstat=instCurve(nam, part->ch2));
  }
  part->nSens=n;
  return(0);
  OnError: return(retstat);
}

int loadCache() {
  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"));
  if (cryo.manual) ERR_SI(fputs(cryo.device, fil));
  ERR_SI(fputs("/", fil));
  if (samp.manual) ERR_SI(fputs(samp.device, fil));
  ERR_SI(fputs("\n", 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);
    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(int n, float t1, float t2) {  
  float p,q;
  if (n==0) {
    return(0.0);
  } else if (n<2) {
    return(t1);
  } else {
    if (t2<tLow) {
      return(t2);
    } else if (t1<tHigh) {
      p=tHigh-t1;
      q=t2-tLow;
      return((t1*p*p+t2*q*q)/(p*p+q*q));
    } else {
      return(t1);
    }
  }
}

int SetTemp(int switchOn) {
  char *ch;

  if (tempC==0) {
    ERR_P(LscCmd(ser, "RANGE:0;SETP 1:0"));
    return(0);
  }
  ch=cryo.ch1;
  if (cryo.nSens>1 && tempC<(tLow+tHigh)/2) ch=cryo.ch2;
  if (samp.nSens>0) {
    if (controlMode==1) { /* control directly on sample sensor */
      tShift=0;
      ch=samp.ch1;
      if (cryo.nSens>1 && tempC<(tLow+tHigh)/2) ch=samp.ch2;
    } else if (controlMode!=2) {
      tShift=0;
    }
  } else {
    tShift=0;
  }
  str_copy(chan, ch);
  tempH=tempC+tShift;
  if (switchOn) {
    ERR_P(LscCmd(ser, "CSET 1:[chan],1;RANGE:5;SETP 1:[tempH]"));
  } else {
    ERR_P(LscCmd(ser, "CSET 1:[chan],1;SETP 1:[tempH]"));
  }
  return(0);
  OnError: return(-1);
}

int ReadTemp() {
  char buf[256], lbuf[16];
  char *res;
  int i, k;
  float t2[2], p, d;

  ERR_P(LscCmd(ser, "DIOST?>cod1,out1;DOUT 3,29;HTR?>htr;BUSY?>busy"));
  if (cryo.codDefined) {
    per=period;
  } else {
    per=1; /* advance fast when initializing */
  }

  if (noResp) { /* check serial number */
    k=serialNo;
    ERR_P(LscCmd(ser, "*IDN?>buf1,buf2,serialNo"));
    if (0!=strcmp(buf1, "LSCI") || 0!=strcmp(buf2, "MODEL340") || serialNo==0) return(0);
    if (k!=serialNo) { 
      if (cryo.manual || cryo.code!=0) cryo.dirty=1;
      if (samp.manual || samp.code!=0) samp.dirty=1;
      ERR_P(LscCmd(ser, "RANGE:0")); /* switch off heater */
      /* 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());
      cryo.device[0]='\0';
      samp.device[0]='\0';
      sscanf(cache, "%15[^/!]%*c%15[^\n!]", cryo.device, samp.device); /* read device names separated by '/' */
      if (cryo.device[0]!='\0') cryo.manual=1;
      if (samp.device[0]!='\0') samp.manual=1;
      concatDevice();
    }
    noResp=0;
  }

  ERR_P(LscCmd(ser,
    "KRDG?[cryo.ch1]>cryo.t1;KRDG?[cryo.ch2]>cryo.t2;KRDG?[samp.ch1]>samp.t1;KRDG?[samp.ch2]>samp.t2"));

  cryo.temp=WeightedAverage(cryo.nSens, cryo.t1, cryo.t2);
  samp.temp=WeightedAverage(samp.nSens, samp.t1, samp.t2);
  if (samp.temp==0.0) samp.temp=cryo.temp;
  if (cryo.dirty==0 && samp.dirty==0 && noResp==0 && tim>logTime) {
    t2[0]=cryo.temp;
    t2[1]=samp.temp;
    DlogPut(&dset, tim, 2, t2); DlogUpd(&dset);
    logTime=(tim/logPeriod+1)*logPeriod;
  }
  if (samp.nSens>0 && cryo.nSens>0 && controlMode==2 && tempC!=0) {
    d=tempH-cryo.temp; d=exp(-d*d); /* if d is small, we are far from setpoint */
    if (tInt<60000/period) tInt+=d; /* increase integral time until 60 sec. */
    if (tInt>d) {
      p=d/tInt;
    } else {
      p=1.0;
    }
    tShift=tShift*(1.0-p)+p*(cryo.temp-samp.temp);
    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++) {
        part=parts[i];
        if (part->code1!=part->code) {
          part->code=part->code1;
          part->codChanged=1;
        } else {
          if (part->codChanged) {
            part->codChanged=0;
            if (part->code1==0) {
              logfileOut(LOG_MAIN, "unplugged X\n");
            } else {
              logfileOut(LOG_MAIN, "plugged (%d)\n", part->code1);
            }
            if (part->codDefined) {
              part->manual=0;
            }
            part->dirty=1;
          }
          part->codDefined=1;
        }
      }
    }
  }

  if (key!=0) {
    auto_remote_time=tim+600;
    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;
    mode=0;
    ERR_P(LscCmd(ser, "MODE?>mode"));
    if (mode==2) auto_remote_time=tim; /* user switched to remote mode */
  }
  return(0);
  OnError: return(-1);
}

int inputSettings(Part *this) {

  part=this;
  if (part->dirty && samp.codDefined) {
    if (busy==0 || cryo.dirty>=0 && part->dirty>=0) { /* do not enter when busy and cryo.dirty/P indicates error on last time */
      logfileOut(LOG_MAIN ,"configure inputs for ");
      if (part->manual) {
        logfileOut(LOG_MAIN ,"%s\n", part->device);
      } else {
        logfileOut(LOG_MAIN ,"%+d\n", part->code);
      }
      if (part->dirty>0) part->try=0;
      part->nSens=0;
      if (!part->manual) { part->device[0]='\0'; concatDevice(); }
      part->dirty=configInput();
      if (part->dirty<0) {
        part->try++;
        if (part->dirty!=-1 || part->try>3) {
          logfileShowErr("fatal error P");
          part->dirty=0; part->device[0]='\0'; concatDevice();
        } else {
          logfileShowErr("try again P");
        }
      } else {
        ERR_P(LscCmd(ser, "ALARM [part.ch1]:[part.nSens],1,[tLimit],0,0,1;ALARM [part.ch2]:0;RELAY 1:1;BEEP:0"));
      }
    }
  }
  return(0);
  OnError: return(-1);
}

int Settings() {
  int i, j, k;
  float pa, pr, pw, dif;

  if (cryo.dirty && cryo.codDefined || samp.dirty && samp.codDefined) {
    
    for (i=0; i<2; i++) {
      part=parts[i];
      if (part->dirty) {
        ERR_P(LscCmd(ser, "DISPFLD 2,[part.ch1],1;DISPFLD 4,[part.ch2],1"));
      }
    }
    inputSettings(&cryo);
    inputSettings(&samp);

    if (cryo.nSens>0) {
      /* control settings */
      ERR_P(LscCmd(ser, "CDISP 1:1,[resist],1;MOUT 1:0;CMODE 1:1"));
      iAmp=1; iRange=0;
      if (power>0) {
        pa=resist*4; /* max. power */
        pw=0; dif=1.0e6;
        for (i=4; i>0; i--) {
          pr=pa;
          for (j=5; j>0; j--) {
            if (pr>power) {
              if (pr/power<dif) { dif=pr/power; pw=pr; iAmp=i; iRange=j; }
            } else {
              if (power/pr<dif) { dif=power/pr; pw=pr; iAmp=i; iRange=j; }
            }
            pr=pr/10;
          }
          pa=pa/4;
        }
      }
      power=pw;
      ERR_P(LscCmd(ser, "CLIMIT 1:[tLimit],0,0,[iAmp],[iRange];CSET 1:[cryo.ch1],1,1,0")); /* some times .ch2? */
    }
    if (samp.nSens>=cryo.nSens) {
      maxfld=2*samp.nSens;
    } else {
      maxfld=2*cryo.nSens-1;
    }
    if (maxfld>0) {
      ERR_P(LscCmd(ser, "MODE:2;DISPLAY:[maxfld]"));
    } else {
      maxfld=1;
      ERR_P(LscCmd(ser, "MODE:2;DISPLAY:1;DISPFLD 1,A,3"));
    }
    mode=2;

    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 ExecuteRequest() {
  char *t, *res;
  struct CocClient *client;

  if (tim>auto_remote_time || setFlag) ERR_I(Settings());
  if (setFlag) {
    if (cryo.nSens>0) {
      tInt=0; /* reset integral time */
      ERR_I(SetTemp(1));
    }
    setFlag=0;
  }
  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) {
    t=strchr(device, '/');
    if (t==NULL) {
      if (0==strcmp(device, "0")) {
        cryo.manual=0; cryo.dirty=1;
        samp.manual=0; samp.dirty=1;
      } 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;

  /* read & control temp */
  ERR_I(ReadTemp());
  if (tim>=auto_remote_time) ERR_I(Settings());
  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>1) {
    logfileOut(LOG_MAIN ,"%d cycles lost\n", tdif-1);
  }
  return(0);
  OnError:
    if (0==strcmp(ErrMessage, "timeout")) {
      if (!noResp) logfileOut(LOG_MAIN ,"no response\n");
      per=period+10000; /* long period if no response */
      noResp=1;
      return(0);
    }
    return(-1);
}

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

  str_copy(cryo.ch1,"A");
  str_copy(cryo.ch2,"B");
  str_copy(samp.ch1,"C");
  str_copy(samp.ch2,"D");
  cryo.codChanged=1;
  samp.codChanged=1;

  logMask=LOG_MAIN+LOG_WARN;
  binDir="bin/";
  logDir="log/";
  serverId="tecs";
  host="lnsp26";
  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 {
        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;

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

  logfileInit(buf, logIt, use_stdout, logIt);
  logfileOut(LOG_MAIN ,"\n");
  logfileWrite(logMask);

  ERR_I(CocInitServer(1024, port));

  CocDefStruct(cryo, Part);
  CocDefStruct(samp, Part);
  CocDefPtr(part, Part);

  CocFltFld(Part, temp, CocRD);
  CocFltFld(Part, t1, CocRD);
  CocFltFld(Part, t2, CocRD);

  CocStrFld(Part, ch1, CocRD);
  CocStrFld(Part, ch2, CocRD);

  CocDefFlt(htr, CocRD);
  CocDefFlt(tempC, setFlag);
  CocDefFlt(tempH, CocRD);
  CocDefFlt(tLimit, CocRD);

  CocAlias(tempX,cryo.temp);
  CocAlias(tempP,samp.temp);

  CocDefStr(device, deviceFlag);

  CocDefStr(buf1, CocRD);
  CocDefStr(buf2, CocRD);
  CocDefStr(buf3, CocRD);
  CocDefStr(buf4, CocRD);
  CocDefStr(head, CocRD);
  CocDefStr(chan, CocRD);
  CocDefStr(intype, 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(mode, CocRD);

  CocDefInt(controlMode, CocWR);
  CocDefInt(busy, CocRD);
  CocDefInt(serialNo, CocRD);
  CocDefInt(quit, CocWR);

  ERR_P(ser=SerOpen(host, msecTmo, idleHdl));
  ERR_I(iret=CocHandleRequests(100, 0));
  ftime(&tim0);
  tim=tim0.time; 
  str_copy(buf, logDir);
  str_append(buf, serverId);
  str_append(buf, ".dlog");
  iret=DlogOpen(&dset, buf, 1);
  if (iret<0) {
    logfileOut(LOG_MAIN, "create data log file: %s\n", buf);
    ERR_I(DlogCreate(&dset, buf, tim0.time, 2, 7*24*60*60/logPeriod, logPeriod, 0.0));
  } else {
    logfileOut(LOG_MAIN, "opened data log file: %s\n", buf);
  }
  logfileWrite(logMask);
  per=period;
  while (!quit) {
    iret=mainBody();
    if (iret<0) logfileShowErr("error in TecsServer");
  }
  ERR_MSG("got quit command");
  OnError:
    logfileShowErr("exit TecsServer");
    DlogClose(&dset);
    SerClose(ser);
    CocCloseServer();
    return(0);
}
