#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "myc_mem.h"
#include "myc_err.h"
#include "coc_util.h"
#include "coc_server.h"
#include "coc_logfile.h"
#include "myc_str.h"
#include "myc_time.h"
#include "tecs_lsc.h"
#include "tecs_data.h"
#include "logger.h"

/* --- non ANSI signal --- */
#ifndef SIGPIPE
#define SIGPIPE 13
#endif

#define TABLE_FILE "tecs.cfg"
#define DEV_LIST "dev.list"
#define LSC_CODES "lsc.codes"
#define LOGLIFETIME 24*3600

#define CELSIUS 2

static SerChannel *ser=NULL;
static char binDir[256]="";
static char logDir[256]="";
static char loggerDir[256]="";
static int logIt=0, use_stdout=0;
static char serverId[32]="tecs";
static int msecTmo=5000;
static char host[64]="";
static char starthost[64]="";
static int port=9753;

typedef struct {
  char ch[4];
  float t, t0, t1, t2, min, max, band; /* temperatures */
  float scale; /* scale for extreme ranges */
  float kink; /* for T > kink values are scaled
                 kink > RT for thermocouples going to more than 1500 K */
  float lim; /* precise range limit (used when two sensors are present) */
  float alarm;
  int customAlarm;
  int stat1, stat2; /* reading status summary */
  int present; /* 0: sensor inactive, 1: sensor configured, -1: sensor parameters read */
  int readStat; /* reading status */
  int dispfld;
  char curve[32]; /* name of curve file */
  char type[4];
  char dispfmt;
  char typ;
} SensorT;

enum Sensors { A, B, C, D, N_SENSORS };

static SensorT
  sensA={"A"},
  sensB={"B"},
  sensC={"C"},
  sensD={"D"},
  *sensors[N_SENSORS]={&sensA, &sensB, &sensC, &sensD},
  *ctlSens=NULL, /* control sensor */
  *heliumSens=NULL,
  *auxSens=NULL,
  *testSens=NULL,
  *test2Sens=NULL,
  *sens=&sensA;

typedef struct {
  SensorT *sensor1, *sensor2;
  float temp; /* weighted temperature */
  float t1, t2; /* minimum and maximum temperatures since ... */
} Testpoint;

static Testpoint /* C standard guarantees initialization to zero */
  cryo, /* data for heat exchanger, or the only sensors */
  samp, /* data for sensors on sample stick */
  *tpoints[2]={&cryo, &samp},
  *tpoint=&cryo;

typedef struct _Plug {
  SensorT *sensor1, *sensor2;
  int devcmd; /* 1: device configured by command, 0: by cabling code */
  int code, code1, code2; /* device code, buffers for device code */
  int codChanged; /* code has changed */
  int codDefined; /* code is not yet confirmed */
  char device[16]; /* device name */
  char descr[80];
} Plug;

static Plug
  plug0={&sensA, &sensB }, /* data for sensors on plug0 */
  plug1={&sensC, &sensD }, /* data for sensors on plug1 */
  *plugs[2]={&plug0, &plug1},
  *plug=&plug0;

static float
  set, /* set T */
  setH, /* set T on heat exchanger */
  set2, /* set T of second loop in two loop mode */
  htr, power=DATA_UNDEF,   /* heater current percentage, heater power */
  power2=DATA_UNDEF, /* heater2 power */
  tLimit, /* temperature limit */
  tMaxLimit, /* maximal temperature limit */
  maxPowerFlt, /* max. Power */
  maxCurrent, /* maxCurrent (not really reached for resist > 25 Ohm */
  powFact=1, /* power factor (for external power supplies) */
  resist=10, /* heater resistance */
  empty, full, /* empty/full value for helium level */
  prop, integ, deriv, /* pid */
  int2=1200.0, /* integration time (sec) for doublecontrol */
  tShift=0, /* setpoint shift */
  maxShift=10, /* maximal shift for doublecontrol */
  propUp=0.5, /* upper limit coefficient (doublecontrol) */
  propDown=0.8, /* lower limit coefficient (doublecontrol) */
  tm=DATA_UNDEF, /* main temperature */
  ts=DATA_UNDEF, /* sample temperature */
  tx=DATA_UNDEF, /* controlled temperature */
  tr=DATA_UNDEF, /* set temperature (read back) */
  te=DATA_UNDEF, /* test temperature */
  tk=DATA_UNDEF, /* test temperature 2 */
  he=DATA_UNDEF, /* helium level value */
  aux=DATA_UNDEF, /* auxiliary value */
  scanChan=DATA_UNDEF, /* scan channel */
  ramp=0, 
  slope=0,
  smooth=0,
  fbuf, /* float buffer */
  r1, r2, /* temporary values */
  still=0,
  linearPower=0, /* normally 0. Maximum power when loop=2 and the power linear to the output */
  shiftUp, shiftLow, /* upper and lower limit of tShift correction */
  maxpower2=1.0,
  stickhtron=5.0, /* switch stick heater on when sample is more than this value below main */
  stickhtroff=0.0, /* switch stick heater off when sample is less than this value below main */
  state,
  mout;

static int
  logPeriod=5, /* data logging period (sec.) */
  period=5000, /* default read interval (msec.) */
  logTime, /* next logging time */
  settingsFlag, /* settings to be done */
  saveTime, /* time for a CRVSAV command */
  noResp=1, /* no response */
  quit, /* quit server */
  controlMode=0, /* obsolete (now controlSensor/doubleControl) 0: control on heater, 1: control on sample, 2: 2nd loop for difference heater-sample */
  doubleControl=0,
  manual=0,
  remoteMode, /* 1: local, 2: remote */
  local, /* 0: remote, 1: local */
  maxfld, /* last used display field */
  busy, /* busy after CRVSAV */
  relay, relay0, /* relay status */
  num, /* curve number */
  key, /* key status */
  serialNo=340000, /* initialize with a valid, but not existing value */
  lscVersion, /* firmware version date yyyymmdd */
  readTemp, /* client requested readTemp */
  cod1, cod2, out1, out2, /* codes read from digital input/output */
  iRange, iAmp, /* max. range and max. current code */
  jRange, /* range code, jRange=iRange if controller did not switch off */
  ibuf, /* temporary int. buffer */
  htrst, htrst0, /* heater status */
  loop=1, /* active loop */
  twoloops=0, /* two active loops */
  touched, /* user touched keys */
  tim0, /* msec Time */
  per, /* effective period */
  mmInt, mmTime, /* interval and time for next min-max logging */
  alarmListSize=0,
  keepT=0, /* keep control over power-up */
  swRangeOn=60, /* when not happen several times within less than 60 sec. delay,
                   switch heater range on when controller switched it off */
  initMaxPower=0, /* set MaxPower for the first time */
  unit=1, /* 1: Kelvin, 2: Celsius */
  autoconfig=0, /* automatic configuration 1: inside tecs, 0: done by SEA */
  lockAlarm,
  stickhtr=-1, /* -1, no stick heater, 1: use stick heater in parallel */
  parallelhtr=0, /* is parallel heater active ? */
  ramping=0, /* setpoint is ramping */
  cntError;

static int tim, rdTim; /* actual time, read Time */

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

static char
  statusBuf[132], /* phase status */
  error[128], /* error message */
  status[1000], /* status summary */
  pid[128], /* PID summary */
  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[4], /* actual channel */
  alarmStatus[20], /* alarm status */
  config[256], /* special configuration commands */
  helium[80], /* helium level status */
  heUnits[4], /* helium level units */
  alarmChannels[N_SENSORS],
  alarmHistory[N_SENSORS],
  swap[4],
  controlSensor[4], /* '0': main two, '1': sample two, or any of "slmntkabcd" */
  dev[80],
  devHelp[10000],
  update[32], /* update script option */
  lscfg[256], /* lsc commands for configuration */
  devname0[64], /* name of plugs from codes */ 
  devname1[64],
  maxPower[128],
  controlChannel[4]="A";

static char
  *alarmList[N_SENSORS], /* enabled alarms */
  *cache=NULL, /* curve list cache */
  *logfile="<none>";

static int logMask=LOG_MAIN;

static float gradata[COC_RES_LEN/4-100];
static char grapar[128];
static int* grasize;

static char ttp[80]; /* temperature, target and power */

typedef struct {
  char cmd[COC_CMD_LEN];
  int logstart;
  long int logpos;
  char logline[COC_RES_LEN];
} ClientData;

/*
static ClientData clInit;
static ClientData *clData=&clInit;
*/

static ClientData *clData;

void ignore_forever(int signo) {
  signal(signo,ignore_forever);
}

int IdleHdl(int tmo, int fd) {
  int iRet;

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

void *SetClientData(void *data) {
  ClientData *cl;
  if (data==NULL) {
    NEW(cl, ClientData);
    return cl;
  }
  clData = data;
  return NULL;
  OnError:
    return NULL;
}

void ConcatDevice(void) {
  str_copy(device, plug0.device);
  if (0!=strcmp(plug0.device, plug1.device) && (plug1.device[0]!='\0' || plug1.devcmd)) {
    str_append(device, "/");
    str_append(device, plug1.device);
  }
  if (device[0]=='\0') return;
}

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

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

int InstalCurve(SensorT *sensor, char *devArg) {
/*
  install sensor
*/
  char nam[32], buf[256], chead[64], nbuf[256], lbuf[16];
  char *crv, *entry, *points, *start,
    *s, /* start of found entry */
    *e, /* cache part after found entry */
    *t;
  int i, n;
  char used[61];
  FILE *fil;

  sens=sensor;
  str_copy(chan, sens->ch);
  ERR_P(LscCmd(ser, "FILTER [chan]:1,64,10"));
  sens->present=0;
  if (sens->typ=='x' || sens->typ=='h' || sens->typ=='f') {
    if (chan[0]>='C') {
      ERR_P(LscCmd(ser, "INTYPE [chan]:0,1,2,1,13;INSET [chan],1")); /* 7.5 V Range */
    } else {
      ERR_P(LscCmd(ser, "INTYPE [chan]:0,1,2,1,12;INSET [chan],1")); /* 5 V Range */
    }
    if (sens->typ=='f') {
      ERR_P(LscCmd(ser, "MNMX [chan]:1,4;INCRV [chan]:0"));
    } else {
      ERR_P(LscCmd(ser, "MNMX [chan]:1,3;INCRV [chan]:0"));
    }
    if (settingsFlag==0) sens->present=1;
    return 0;
  }

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

  sprintf(nam, "%s_%c", devArg, sens->typ);
  logfileOut(LOG_MAIN+LOG_STAT, "install curve %s\n", nam);

  /* read curve file */
  str_copy(nbuf, binDir);
  if (sensor->curve[0]=='\0') { /* old style file name */
    str_append(nbuf, nam);
  } else {
    str_append(nbuf, sensor->curve);
  }
  str_append(nbuf, ".crv");
  
  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");

    ERR_P(LscCmd(ser, "CRVHDR?[num]>head"));
      
  } else {

    if (sensor->curve[0]!='\0') {
      strcpy(buf, ",");
      str_append(buf, sensor->curve);
      str_append(buf, " ");
      str_upcase(buf, buf);
    } 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");

      ERR_P(LscCmd(ser, "CRVHDR?[num]>head"));

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

  if (num > 20 && strlen(chead) > 10) { /* replace name by a more meaningful */
    for (i=0; i<10 && i<strlen(nam); i++) {
      chead[i]=nam[i];
    }
    for (i=strlen(nam); i<10; i++) {
      chead[i]=' ';
    }
  }

  i=0;
  if (num>20) i=12; /* check only from 12th character (CRC) for user curves */
  if (head[0]!='\0' && LscEqPar(head+i, chead+i)) { /* header matches: select sensor type and curve */

    if (i>0 && !LscEqPar(head, chead)) { /* name does not match */
      str_upcase(head, chead);
      ERR_P(LscCmd(ser, "CRVHDR [num]:[head]"));
    }
    ERR_P(LscCmd(ser, "INTYPE?[chan]>buf1;INCRV?[chan]>buf2;INSET?[chan]>buf3"));
    if (!LscEqPar(buf1, intype) || atoi(buf2)!=num || buf3[0]!='1') {
      ERR_P(LscCmd(ser, "ANALOG 2:0,3;INSET [chan],1"));
      if (chan[1]<='1') ERR_P(LscCmd(ser, "RANGE:0;INTYPE [chan]:[intype]"));
      ERR_P(LscCmd(ser, "INCRV [chan]:[num];MNMX [chan]:1,[unit]"));
      logfileOut(LOG_MAIN, "curve %d on channel %s selected\n", num, chan);
    } else {
      logfileOut(LOG_MAIN, "curve %d on channel %s is already selected\n", num, chan);
    }

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

    logfileOut(LOG_MAIN, "%s\n%s\n", head, chead);
    if (num<=20) ERR_MSG("standard curve does not match");
    if (busy) ERR_MSG("busy");
    logfileOut(LOG_MAIN+LOG_STAT, "download curve %d\n", num);
    ERR_P(LscCmd(ser, "ANALOG 2:0,3;INSET [chan],1"));
    /* select sensor type first */
    if (chan[1]<='1') ERR_P(LscCmd(ser, "RANGE:0;INTYPE [chan]:[intype]"));

    n=3;
    do { /* try to delete curve 3 times */
      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");

    /* store header, but without name (curve format must be determined before loading points) */
    t=strchr(chead, ',');
    if (t==NULL) ERR_MSG("illegal header");
    str_copy(buf2, t);
    ERR_P(LscCmd(ser, "CRVHDR [num]:[buf2]"));

    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));
        if (lscVersion<20030000 || strstr(buf,":0,0")==NULL) {
          ERR_P(LscCmd(ser, buf));
        } else {
          logfileOut(LOG_MAIN, "skip %s\n", buf);
        }
        i++;
        sprintf(statusBuf, "downloading curve %d at line %d", num, i);
      }
      points=t;
    } while (t!=NULL);

    /* write header, select curve */
    str_upcase(head, chead);
    ERR_P(LscCmd(ser, "CRVHDR [num]:[head];INCRV [chan]:[num];MNMX [chan]:1,[unit]"));
    logfileOut(LOG_MAIN, "curve %d selected on channel %s\n", num, chan);
    saveTime = (tim/3600/24 + 1) * 3600*24; /* save at midnight GMT */
  }
  FREE(crv); crv=NULL;

  if (sens->scale != 1.0 || sens->kink != 0.0) { /* scaling for display */
    fbuf = sens->kink * (1.0 - sens->scale);
    ERR_P(LscCmd(ser, "LINEAR [chan]:1,[sens.scale],[unit],1,[fbuf]"));
  }
  if (settingsFlag==0) sens->present=1;
  /*
  if (num<=20) return 0;
  */
   /* standard curve, do not touch cache */

  /* rewrite cache with actual entry at beginning */
  str_copy(nbuf, logDir);
  str_append(nbuf, "lsc.tmp");
  fil=fopen(nbuf, "w+");
  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 */
    if (num>20) {
      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(fputc('\n', fil));
  ERR_SI(fclose(fil));
  str_copy(buf, logDir);
  sprintf(lbuf, "lsc.%d", serialNo);
  str_append(buf, lbuf);
  rename(nbuf, buf); /* rename to permanent version */
  fil=NULL;
  FREE(cache);
  /* re-read it */
  ERR_P(cache=str_read_file(buf));

  /* for vms: */
  str_append(buf, ";-1");
  remove(buf); /* remove old version */
  buf[strlen(buf)-2]='\0'; /* strip off "-1" */
  rename(buf, buf); /* lower version number */

  return 0;

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

void InitSensor(SensorT *s) {
  s->t=DATA_UNDEF;
  s->scale=1;
  s->kink=0;
  s->lim=0;
  s->alarm=0;
  s->customAlarm=0;
  s->present=0;
  s->curve[0]='\0';
  s->type[0]='\0';
  s->dispfld=0;
}

int PrepInput(char *label) {
  char *t;
  char nam[16], typ;
  char buf[256];
  int i, j;
  SensorT *s;
  char *cfg;

  if (label[0]=='\'') {
    str_copy(nam, label+1);
    nam[strlen(nam)-1]='\0'; /* strip off quote */
  } else {
    str_copy(nam, label);
  }
again:
  str_copy(buf, binDir);
  str_append(buf, nam);
  str_append(buf, ".cfg");
  cfg=str_read_file(buf);
  if (cfg==NULL) {
    logfileOut(LOG_MAIN, "%s not found\n", buf);
    ERR_MSG("device not found");
  }
  if (cfg && *cfg=='@') { /* alias */
    t=strchr(cfg,'\n');
    if (t) *t='\0';
    str_copy(nam, cfg+1);
    str_copy(plug->device, nam);
    FREE(cfg);
    goto again;
  }
  if (plug==&plug0) {
    InitSensor(&sensA);
    InitSensor(&sensB);
    slope=0;
    ramp=0;
    controlMode=0;
    doubleControl=0;
    controlSensor[0]='0';
    linearPower=0;
    powFact=1;
    config[0]='\0';
    lscfg[0]='\0';
    tLimit=0;
    tMaxLimit=0;
    keepT=0;
    swRangeOn=60;
    loop=1;
    twoloops=0;
    initMaxPower=1;
    resist=10;
    unit=1;
    empty=0;
    full = -1;
    stickhtr = -1;
  } else {
    InitSensor(&sensC);
    if (strcasecmp(nam, "none") == 0) {
      /* exception: he sensor is not cleared when stick is none */
      if (full < 0 || sensB.type[0]=='\0' || sensD.type[0] != 'h') {
        InitSensor(&sensD);
      }
    } else {
      empty = 0;
      full = -1;
      InitSensor(&sensD);
    }
  }

  dev[0]='\0';
  str_copy(heUnits, "%");

  ERR_P(CocReadVars(cfg, '\0'));
  if (loop!=2) loop=1;

  if (plug==&plug0) {
    j=0;
  } else {
    j=2;
  }
  str_copy(plug->descr, dev);
  if (tLimit==0.0) {
    if (tMaxLimit==0.0) {
      tLimit=310;
      tMaxLimit=350;
    } else {
      tLimit=tMaxLimit-10;
    }
  } else if (tMaxLimit==0.0) {
    tMaxLimit=tLimit+10;
  } else if (tLimit>tMaxLimit) {
    tLimit=tMaxLimit;
  }
  maxPowerFlt=25;
  sscanf(maxPower, "%f", &maxPowerFlt);

  for (i=j; i<j+2; i++) {
    s=sensors[i];
    typ=s->type[0];
    if (typ=='_') typ='\0';
    if (NULL==strchr("mnslhxftk", typ)) ERR_MSG("unknown channel type code");
    if (typ!='\0') {
      s->present=-1;
    }
    s->typ=typ;
    s->band=10;
    if (s->scale==0.0) s->scale=1.0;
    if ((s->alarm==0.0 && typ=='m') || typ=='s') {
      s->alarm=tMaxLimit;
      s->customAlarm=0;
    } else {
      s->customAlarm=1;
    }
  }

  if (!plug->devcmd) { /* set device name */
    str_copy(plug->device, nam);
    ConcatDevice();
  }
  if (cfg) {
    FREE(cfg);
  }
  return 0;
OnError:
  if (cfg) {
    FREE(cfg);
  }
  return -1;
}

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);
    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 an 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 TrueScale(SensorT *s, float val) {
  float tval;
  if (s==NULL) {
    return val;
  } else {
    if (val > s->kink) {
      tval = (val - s->kink) * s->scale + s->kink;
    } else {
      tval = val;
    }
    return tval;
  }
}

float FakeScale(SensorT *s, float val) {
  float fval;
  if (s==NULL) {
    return val;
  } else {
    if (val > s->kink) {
      fval = (val - s->kink) / s->scale + s->kink;
    } else {
      fval = val;
    }
    return fval;
  }
}

float WeightedAverage(float tH, float tL, Testpoint *t) {
  float p,q, tLow, tHigh;
  SensorT *s1, *s2;

  if (tH==DATA_UNDEF) tH=0.0;
  if (tL==DATA_UNDEF) tL=0.0;
  s1=t->sensor1;
  s2=t->sensor2;
  tH = TrueScale(s1, tH);
  tL = TrueScale(s2, tL);
  if (tH!=0.0) {
    if (tL!=0.0) {
      tLow=s1->lim;
      tHigh=s2->lim;
      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 DATA_UNDEF;
}

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

  sprintf(buf, "@");
  if (cryo.sensor1!=NULL) { /* at least one sensor is present */
    for (i=0; i<2; i++) {
      tpoint=tpoints[i];
      s1=tpoint->sensor1;
      s2=tpoint->sensor2;
      tpoint->t1 = WeightedAverage(s1->min, s2->min, tpoint);
      tpoint->t2 = WeightedAverage(s1->max, s2->max, tpoint);
      if (fabsf(tpoint->t1 - tpoint->temp) < fabsf(tpoint->t2 - tpoint->temp)) {
        tbuf = tpoint->t1;
        tpoint->t1 = tpoint->t2;
        tpoint->t2 = tbuf;
      }
      s1->min=0;
      s1->max=0;
      s2->min=0;
      s2->max=0;
    }

    if (cryo.t1!=DATA_UNDEF) {
      sprintf(buf1, " Tm %.5g _ %.5g", cryo.t1, cryo.t2);
      str_append(buf, buf1);
    }
    if (cryo.sensor1!=samp.sensor1 && samp.t1!=DATA_UNDEF) {
      sprintf(buf1, " Ts %.5g _ %.5g", samp.t1, samp.t2);
      str_append(buf, buf1);
    }
    if (tr!=DATA_UNDEF) {
      sprintf(buf1, " Tr %.5g", tr);
      str_append(buf, buf1);
    }
    if (te!=DATA_UNDEF) {
      sprintf(buf1, " Te %.5g", te);
      str_append(buf, buf1);
    }
    if (tk!=DATA_UNDEF) {
      sprintf(buf1, " Tk %.5g", tk);
      str_append(buf, buf1);
    }
  } else {
    return;
  }
  if (power!=DATA_UNDEF) {
    sprintf(buf1, " P %.4g", power);
    str_append(buf, buf1);
  }
  if (he!=DATA_UNDEF) {
    sprintf(buf1, " He %.4g", he);
    str_append(buf, buf1);
  }
  if (aux!=DATA_UNDEF) {
    sprintf(buf1, " Aux %.5g", aux);
    str_append(buf, buf1);
  }
  if (new==2) {
    mmTime=mmTime+30;
    first=1;
  } else {
    if (first==0) {
      logfileOut(LOG_MAIN, "%s\n", buf);
    } else {
      first=0;
    }
    if (new) {
      mmInt=30;
    } else {
      mmInt=60;
    }
    mmTime=((tim+mmInt/2)/mmInt+1)*mmInt-logPeriod;
  }
}

SensorT *findSensor(char txt) {
  int i;
  SensorT *s;

  s = NULL;
  txt = tolower(txt);
  if (strchr("01slmntkabcde", txt) != 0) {
    for (i=0; i<4; i++) {
      if (txt == tolower(sensors[i]->ch[0])
          || txt == sensors[i]->typ) {
        s = sensors[i];
      }
    }
  }
  return s;
}

void SetCtlSens(void) {
  SensorT *s;

  if (controlSensor[0] == '1') {
    tShift=0;
    if (ctlSens==NULL) {
      ctlSens=samp.sensor1;
    } else if (ctlSens==samp.sensor1) {
      if (set < ctlSens->lim) {
        ctlSens=samp.sensor2;
      }
    } else if (ctlSens==samp.sensor2) {
      if (set > ctlSens->lim) {
        ctlSens=samp.sensor1;
      }
    } else {
      ctlSens=samp.sensor1;
    }
    if (controlMode != 2) {
      controlMode = 1;
    }
  } else if (controlSensor[0] == '0') {
    if (!doubleControl) {
      tShift=0;
    }
    if (ctlSens==NULL) {
      ctlSens=cryo.sensor1;
    } else if (ctlSens==cryo.sensor1) {
      if (set < ctlSens->lim) {
        ctlSens=cryo.sensor2;
      }
    } else if (ctlSens==cryo.sensor2) {
      if (set > ctlSens->lim) {
        ctlSens=cryo.sensor1;
      }
    } else {
      ctlSens=cryo.sensor1;
    }
    if (controlMode != 2) {
      controlMode = 0;
    }
  } else {
    s = findSensor(controlSensor[0]);
    if (s) {
      ctlSens = s;
      if (controlMode != 2) {
        if (s->typ == 's' || s->typ == 'l') {
          controlMode = 1;
        } else {
          controlMode = 0;
        }
      }
    } else {
      ctlSens = cryo.sensor1; /* should always be defined */
      if (controlMode != 2) {
        controlMode = 0;
      }
    }
  }
}


int ReadTemp(void) {
  char buf[256], typ, *err, dispfmt;
  int i, doit, stat;
  int tfill, dfill;
  static time_t lastRead=0;
  float hlev;
  SensorT *s;

  readTemp=0;

  doit=0;
  lastRead = 0;
  for (i=0; i<4; i++) {
    s=sensors[i];
    s->stat1=0;
    s->stat2=0;
    if (s->present) {
      if (doit) ERR_P(LscCmd(ser, buf));
      if (i<4) {
        str_substitute(buf
          , "KRDG?#>sens#.t0;MDAT?#>sens#.t1,sens#.t2;MDATST?#>sens#.stat1,sens#.stat2"
          , "#", s->ch);
      } else {
        str_substitute(buf
          , "KRDG?#>sens#.t1;RDGST?#>sens#.stat1;DIOST?>,sens#.stat2"
          , "#", s->ch);
      }
      if (s->typ=='h' || s->typ=='x') {
        buf[0]='S'; /* change KRDG to SRDG */
        typ='S';
      } else if (s->typ=='f') {
        buf[0]='L'; /* change KRDG to LDAT */
        buf[1]='D';
        buf[2]='A';
        buf[3]='T';
      } else if (unit == CELSIUS) {
        buf[0]='C'; /* change KRDG to CRDG */
      }
      doit=1;
    }
  }
  if (doit) {
    if (ramp!=0) {
      str_append(buf, ";SETP?[loop]>fbuf;RAMPST?[loop]>ramping");
    } else {
      ramping = 0;
    }
    str_append(buf, ";MNMXRST");
    ERR_P(LscCmd(ser, buf));
    SetCtlSens();
    if (manual) {
      tr=DATA_UNDEF;
    } else if (ramp!=0) {
      tr=TrueScale(ctlSens, fbuf);
    } else {
      tr=TrueScale(ctlSens, setH);
    }
    if (tr==0) tr=DATA_UNDEF;
  }

  /* check for reading errors, determine legal value */
  for (i=0; i<4; i++) {
    s=sensors[i];
    if (s->present) {

      if (i>=4) {
        if (s->stat2==i-4) { /* ignore reading, if channel was active (may be noisy) */
          s->t1 = s->t;
        }
        s->t2 = s->t1;
        s->stat2 = s->stat1;
      }
      if (set != 63.0 &&
        (s->t1 == 63.0 || s->t2 == 63.0 || s->t0 == 63.0)) {
        /* probably noisy values */
        logfileOut(LOG_MAIN, "magic %s: %g %g %g\n", s->ch, s->t1, s->t2, s->t0);
      }

      stat=(s->stat1 & s->stat2) & (255-3); /* ignore "old reading" and "invalid reading", error must be on min & max */
      if (stat > s->readStat || (stat==0 && s->readStat>0)) {
        err=LscReadStat(stat);
        if (*err=='\0') {
          logfileOut(LOG_MAIN, "reading o.k. %s\n", s->ch);
        } else {
          logfileOut(LOG_MAIN, "%s %s\n", err, s->ch);
        }
        s->readStat=stat;
      }
      if (stat==0) {

        if (s->typ=='h') {
          hlev = s->t1; /* take minimum only */
          if (full!=empty) {
            hlev=(hlev-empty)/(full-empty);
            if (hlev<1.0 && hlev>0.0) {
              he=hlev*100.0;
              s->t=he;
              tfill = tim % (24*3600) / 60; /* min since midnight */
              dfill = mycDate(tim) % 10000; /* fill date without year */
              sprintf(helium, "%.3g %s (%02d.%02d., %02d:%02d)", he, heUnits
                , dfill % 100, dfill / 100, tfill / 60, tfill % 60);
            }
          }
        } else if (s->t2 - s->t1 <= s->band*2) { /* normal case */
          s->t = (s->t1 + s->t2) * 0.5; /* mean of min and max */
          s->band = s->band/2;
        } else {
          if (s->t1 < 0.8 * s->t2) {
            /* probably noisy values */
            logfileOut(LOG_MAIN, "min/max %s: %g %g\n", s->ch, s->t1, s->t2);
          }
          s->t=s->t0;
          s->t1 = s->t;
          s->t2 = s->t;
        }
        if (s->t1 < s->min || s->min==0.0) s->min = s->t1;
        if (s->t2 > s->max) s->max = s->t2;
        if (s->max - s->min > s->band) {
          s->band = s->max - s->min;
        }
        if (s->scale > 1.0 && s->kink != 0) {
          if (s->t > s->kink) {
            dispfmt='4';
          } else if (unit == CELSIUS) {
            dispfmt='2';
          } else {
            dispfmt='1';
          }
          if (dispfmt != s->dispfmt) { /* change display format */
            s->dispfmt = dispfmt;
            sprintf(buf, "DISPFLD %d,%s,%c", s->dispfld, s->ch, dispfmt);
            ERR_P(LscCmd(ser, buf));
          }
        }
      } else {
        s->t=DATA_UNDEF;
      }
    } else {
      s->t=DATA_UNDEF;
    }
  }

  rdTim=mycNow();

  tm=DATA_UNDEF;
  ts=DATA_UNDEF;
  if (cryo.sensor1!=NULL) {
    cryo.temp=WeightedAverage(cryo.sensor1->t, cryo.sensor2->t, &cryo);
    samp.temp=WeightedAverage(samp.sensor1->t, samp.sensor2->t, &samp);
    tm=cryo.temp;
    if (samp.sensor1!=cryo.sensor1) {
      ts=samp.temp;
    }
    if (samp.temp==DATA_UNDEF) {
      samp.temp=cryo.temp;
    }
  } else {
    tm=(rdTim % 3600) * 1.0e-4;
    ts=(rdTim % 60) * 60.0e-4+0.5;
  }
  if (doubleControl) {
    tx=ts;
  } else if (ctlSens) {
    tx=TrueScale(ctlSens, ctlSens->t);
  }
  if (auxSens != NULL) {
    aux=TrueScale(auxSens, auxSens->t);
  } else { 
    aux=DATA_UNDEF;
  }
  if (testSens != NULL) {
    te=TrueScale(testSens, testSens->t);
  } else { 
    te=DATA_UNDEF;
  }
  if (test2Sens != NULL) {
    tk=TrueScale(test2Sens, test2Sens->t);
  } else { 
    tk=DATA_UNDEF;
  }
  if (heliumSens == NULL) {
    he=DATA_UNDEF;
  }
  return 0;
  OnError: return -1;
}

float Percent2Power(float percent) {
  float curr;
  
  if (linearPower > 0) {
    return percent/100*linearPower;
  }
  curr=maxCurrent*percent/100;
  if (linearPower < 0) {
    return curr*sqrt(resist)*powFact;
  }
  return curr*curr*resist*powFact;
}

float Power2Percent(float power) {
  if (linearPower > 0) {
    return power/linearPower*100;
  }
  if (linearPower < 0) {
    return power/powFact/sqrt(resist)/maxCurrent*100;
  }
  return sqrt(power/powFact/resist)/maxCurrent*100;
}

int ReadHeater(int full) {
  static time_t lastOff=0;
  static int cntOff=3;
  time_t now;

  power2 = DATA_UNDEF;
  if (loop==1) {
    if (full) {
      if (relay) {
        ERR_P(LscCmd(ser, "HTRST?>htrst"));
        htr=0;
      } else {
        if (twoloops) {
          ERR_P(LscCmd(ser, "HTR?>htr;HTRST?>htrst;RELAYST?1>relay;RANGE?>ibuf;AOUT?2>fbuf"));
          power2 = fbuf*fbuf*1e-4*maxpower2;
        } else {
          ERR_P(LscCmd(ser, "HTR?>htr;HTRST?>htrst;RELAYST?1>relay;RANGE?>ibuf"));
        }
        if (jRange!=0 && ibuf==0) {
          if (swRangeOn) {
            time(&now);
            if (lastOff > 0) {
              if (now > lastOff + swRangeOn * 2) {
                if (cntOff < 3) cntOff++;
              } else if (now < lastOff + swRangeOn) {
                cntOff--;
              }
            }
            lastOff = now;
            if (cntOff > 0) {
              logfileOut(LOG_MAIN, "controller switched heater off - switch on again (%d more tries)\n", cntOff-1);
              ERR_P(LscCmd(ser, "RANGE:[jRange]"));
            } else {
              logfileOut(LOG_MAIN, "controller switched heater off definitely (happened to often)\n");
              jRange=0;
            }
          } else {
            logfileOut(LOG_MAIN, "controller switched heater off\n");
            jRange=0;
          }
        }
      }
    } else {
      ERR_P(LscCmd(ser, "HTR?>htr"));
    }
  } else {
    ERR_P(LscCmd(ser, "AOUT?2>htr"));
    htrst=0;
  }
  if (htr == 0 && set == 0 && manual == 0) {
    power=DATA_UNDEF;
  } else {
    power=Percent2Power(htr);
    if (power>maxPowerFlt) power=maxPowerFlt;
  }
  return 0;
  OnError: return -1;
}

int SetTemp(int switchOn) {
  int showSet;
  float tc;
  float tLim;
  
  if (set<0 || set>tLimit) {
    set=0;
    logfileOut(LOG_MAIN, "set point not within (0 ... %g), reset to 0\n", tLimit);
  }
  if (switchOn) {
    ERR_I(ReadTemp());
    LogMinMax(switchOn);
  }
  SetCtlSens();
  if (ctlSens==NULL) return 0;

  str_copy(chan, ctlSens->ch);
  if (!doubleControl) {
    tShift=0.0;
  }
  tLim = (tLimit+tMaxLimit)*0.5;
  if (set+tShift <= 0) { /* set + tShift must not be negative */
    tShift = -0.999 * set;
  } else if (set + tShift > tLim) {  /* prevent limit overshoot */
    tShift = tLim - set;
  }
  setH=FakeScale(ctlSens, set+tShift);
  if (setH > FakeScale(ctlSens, tLim)) setH=FakeScale(ctlSens, tLim);
  if (switchOn) {
    /* switch off other loop */
    if (loop==1) {
      if (twoloops==0) {
        ERR_P(LscCmd(ser, "CSET 2:[chan],[unit],0,0"));
      }
    } else {
      ERR_P(LscCmd(ser, "CSET 1:[chan],[unit],0,0"));
    }
    if (ramp > 0 && set != 0) {
      /* start ramp from actual temperature */
      ERR_P(LscCmd(ser, "CSET?[loop]>,,cod1;SETP?[loop]>fbuf"));
      if (loop == 1 && cod1 != 0) {
        ERR_P(LscCmd(ser, "RANGE?>cod1"));
      } /* cod1 == 0  --> heater is off */
      tr=TrueScale(ctlSens, fbuf);
      tc=TrueScale(ctlSens, ctlSens->t);
      if (cod1 == 0 || fabsf(tc - tr) >= 0.1 * tc) {
        fbuf = ctlSens->t;
        tr = TrueScale(ctlSens, fbuf);
        ERR_P(LscCmd(ser, "SETP [loop],[fbuf];RAMP [loop],0,0;SETP [loop],[fbuf]"));
        if (twoloops) {
          ERR_P(LscCmd(ser, "SETP 2,[fbuf];RAMP 2,0,0;SETP 2,[fbuf]"));
        }
      }
    }
  }
  if (set==0) {
    mout=0;
    ERR_P(LscCmd(ser, "CSET [loop]:[chan],[unit],1,[keepT];MOUT [loop],0"));
    if (twoloops) {
      ERR_P(LscCmd(ser, "CSET 2:C,[unit],1,[keepT];MOUT 2,0"));
    }
    if (loop==1) {
      ERR_P(LscCmd(ser, "RANGE:0"));
      jRange=0;
      if (twoloops) {
        ERR_P(LscCmd(ser, "ANALOG 2:0,0"));
      }
    } else {
      ERR_P(LscCmd(ser, "ANALOG 2:0,0"));
    }
    setH=0;
  } else if (switchOn) { /* switch on also when bad heater message and no alarms: || (htrst>=5 && !relay))  */
    ERR_P(LscCmd(ser, "CSET [loop]:[chan],[unit],1,[keepT]"));
    if (loop==1) {
      ERR_P(LscCmd(ser, "RANGE:[iRange]"));
      jRange=iRange;
      if (twoloops) {
        ERR_P(LscCmd(ser, "ANALOG 2:0,3"));
      }
    } else {
      ERR_P(LscCmd(ser, "ANALOG 2:0,3"));
    }
  } else {
    ERR_P(LscCmd(ser, "CSET?[loop]>,buf1,"));
    if (buf1[0] != ('0' + unit)) {
      ERR_P(LscCmd(ser, "CSET [loop]:[chan],[unit]"));
    }
  }
  showSet=(0!=strcmp(ctlSens->ch, controlChannel) || switchOn);
  str_copy(controlChannel, ctlSens->ch);
  if (ramp==0 || set==0) {
    ERR_P(LscCmd(ser, "RAMP?[loop]>buf1,"));
    if (buf1[0] != '0') {
      ERR_P(LscCmd(ser, "RAMP [loop]:0,[ramp]"));
      if (twoloops) {
        ERR_P(LscCmd(ser, "RAMP 2:0,[ramp]"));
      }
    }
    ERR_P(LscCmd(ser, "SETP?[loop]>fbuf"));
    if (fabsf(setH-fbuf) >= (fbuf+setH)*1.0e-5) {
      ERR_P(LscCmd(ser, "SETP [loop]:[setH]"));
      if (!doubleControl && !showSet) {
        logfileOut(LOG_MAIN, "set %g (on %s) (was changed %g -> %g)\n", set, ctlSens->ch, fbuf, setH);
      }
    } else {
      setH=fbuf;
    }
    if (twoloops) {
      ERR_P(LscCmd(ser, "SETP 2:[set2]"));
    }
    tr=TrueScale(ctlSens, setH);
    if (showSet) {
      logfileOut(LOG_MAIN, "set %g (on %s)\n", set, ctlSens->ch);
    }
  } else {
    if (ramp < 0) ramp=-ramp;
    if (ramp < 0.1) ramp=0.1;
    if (showSet) {
      fbuf = ramp;
    } else {
      ERR_P(LscCmd(ser, "SETP?[loop]>fbuf"));
      if (fabs(fbuf-setH) >= ramp*smooth) {
        fbuf=ramp;
      } else {
        fbuf=sqrt(fabs(fbuf-setH)*ramp/smooth);
      }
    }
    fbuf = fbuf / ctlSens->scale;
    if (fbuf<0.1) fbuf=0.1;
    ERR_P(LscCmd(ser, "RAMP [loop]:1,[fbuf];SETP [loop],[setH]"));
    if (twoloops) {
      ERR_P(LscCmd(ser, "RAMP 2:1,[fbuf];SETP 2,[set2]"));
    }
    ERR_P(LscCmd(ser, "SETP?[loop]>fbuf"));
    tr=TrueScale(ctlSens, fbuf);
    if (showSet) {
      logfileOut(LOG_MAIN, "set %g (on %s) %g K/min, starting from %g\n", set, ctlSens->ch, ramp, tr);
    }
  }
  if (tr==0) tr=DATA_UNDEF;
  if (showSet) {
    /* relay=0; */
    ERR_I(ReadHeater(1));
  }
  return 0;
  OnError: return -1;
}

void CalcMaxPower(void) {
  int i, j;
  struct {
    float p;
    int iAmp, iRange;
  } low, high, *best;
  float pa, pr, p, plim;
  float h, maxAt;
  char warning[128];
  
  iAmp=1; iRange=0;
  maxCurrent=0;
  if (maxPowerFlt>0) {
    if (resist<1) resist=10;
    maxCurrent = 1; /* must be 1 for Power2Percent */
    h=Power2Percent(maxPowerFlt)/100;
    p=h*h*resist; /* power before amplifier */
    plim=3025/resist;
    /* power limited by 55 V output. U*U/R=55*55/R */
    if (p>plim) p=plim;
    pa=resist*4; /* max. maxPower R*I*I (I=2 A) */
    low.p = 0;
    low.iRange = 0;
    high.p = resist*6;
    high.iRange = 0;
    for (i=4; i>0; i--) {
      pr=pa;
      for (j=5; j>0; j--) {
        if (pr > p) {
          if (pr < high.p) {
            high.p = pr;
            high.iAmp = i;
            high.iRange = j;
          }
        } else {
          if (pr > low.p) {
            low.p = pr;
            low.iAmp = i;
            low.iRange = j;
          }
        }
        pr=pr/10;
      }
      pa=pa/4;
    }
    if (high.iRange == 0) {
      best = &low;
      if (low.iRange == 0) {
        return;
      }
    } else if (low.iRange == 0) {
      best = &high;
    } else if (p > low.p*1.1) {
      best = &high;
    } else {
      best = &low;
    }
    warning[0]='\0';
    maxAt = 100;
    if (best->p > plim) {
      maxAt = sqrt(plim/best->p)*100;
      maxPowerFlt=Percent2Power(100*sqrt(plim/resist));
    } else {
      maxPowerFlt=Percent2Power(100*sqrt(best->p/resist));
    }
    snprintf(maxPower, sizeof maxPower, "%.5g", maxPowerFlt);
    if (best->p > p * 1.01) {
      if (low.iRange != 0) {
        snprintf(warning, sizeof warning,
          "next lower value: %.4g",
          Percent2Power(100*sqrt(low.p/resist)));
      } else {
        snprintf(warning, sizeof warning,
          "rounded up to minimal value");
      }
    } else if (best->p < p * 0.99) {
      if (high.p > plim) high.p = plim;
      if (high.p < resist * 5) {
        snprintf(warning, sizeof warning,
          "next higher value: %.4g",
          Percent2Power(100*sqrt(high.p/resist)));
      } else {
        snprintf(warning, sizeof warning,
          "rounded down to maximal value");
      }
    } else if (p < h*h*resist * 0.99) {
      snprintf(warning, sizeof warning,
        "rounded down to maximal value");
    }
    if (maxAt < 99.0) {
      str_append(maxPower, " (");
      if (warning[0] != '\0') {
        str_append(maxPower, warning);
        str_append(maxPower, ", ");
      }
      snprintf(warning, sizeof warning,
        "maximum power already at %.1f %%",
        maxAt);
      str_append(maxPower, warning);
      str_append(maxPower, ")");
    } else if (warning[0]!='\0') {
      str_append(maxPower, " (");
      str_append(maxPower, warning);
      str_append(maxPower, ")");
    }
    maxCurrent=sqrt(best->p/resist);
    iRange = best->iRange;
    iAmp = best->iAmp;
    assert(maxCurrent>0);
  }
}

int PutFloat(StrBuf *buf, int prec, float f) {
  char num[32], fmt[32];

  if (f == DATA_UNDEF) {
    return StrPut(buf, "NaN", StrNONE);
  } else {
    sprintf(fmt, "%%.%dg", prec);
    sprintf(num, fmt, f);
    return StrPut(buf, num, StrNONE);
  }
}

int PidSumHdl(int mode, void *base, int fd) {
  StrBuf buf;

  StrLink(&buf, pid);
  StrClear(&buf);
  ERR_I(PutFloat(&buf, 5, prop));
  ERR_I(StrPut(&buf, " ", StrNONE));
  ERR_I(PutFloat(&buf, 5, integ));
  ERR_I(StrPut(&buf, " ", StrNONE));
  ERR_I(PutFloat(&buf, 4, deriv));
  ERR_I(StrPut(&buf, " /", ' '));
  ERR_I(PutFloat(&buf, 4, prop*maxCurrent*resist/600));
  ERR_I(StrPut(&buf, " A/K,", ' '));
  if (integ == 0.0) {
    ERR_I(StrPut(&buf, " inf,", ' '));
  } else {
    ERR_I(PutFloat(&buf, 4, 1000.0/integ));
    ERR_I(StrPut(&buf, " sec,", ' '));
  }
  ERR_I(PutFloat(&buf, 4, deriv));
  ERR_I(StrPut(&buf, " sec/maxCurrent", ' '));
  if (maxCurrent>0.5) {
    ERR_I(PutFloat(&buf, 4, maxCurrent));
    ERR_I(StrPut(&buf, " A", StrNONE));
  } else {
    ERR_I(PutFloat(&buf, 4, maxCurrent*1000));
    ERR_I(StrPut(&buf, " mA", StrNONE));
  }
  return 0;
  OnError:
    return -1;
}

int SetMaxPower(void) {
  /* static float p0; */
  static float lastCurrent, limCurrent, pold;
  /* float plim; */

  if (loop == 1) {
    if (initMaxPower) {
      ERR_P(LscCmd(ser, "CLIMIT?1>,,,iAmp,iRange"));
      pold=0;
    } else {
      iRange=0;
    }
    if (iRange>0) {
      lastCurrent=pow(2.0,iAmp-3)*pow(sqrt(10.0),iRange-5);
      /*
      p0=resist*pow(4.0,iAmp)*pow(10.0,iRange)/6.4e6*powFact;
      plim=2500/resist*powFact;
      pold=p0;
      if (pold>plim) pold=plim;
      */
      if (resist<1) resist=10;
      limCurrent=50/resist;  /* 50 V max. */
      if (limCurrent > lastCurrent) limCurrent=lastCurrent;
      pold=limCurrent*limCurrent*resist;
    }
    CalcMaxPower();
  } else if (linearPower != 0) {
    maxCurrent = 1;
    maxPowerFlt = linearPower;
  } else {
    maxCurrent=10/resist; /* 10 V max. */
    if (maxCurrent>0.1) {
      limCurrent=0.1; /* 0.1 A max. */
    } else {
      limCurrent=maxCurrent;
    }
    maxPowerFlt=limCurrent*limCurrent*resist;
  }
  logfileOut(LOG_MAIN, "maxPower changed from %g to %g\n", pold, maxPowerFlt);
  if (manual) {
    cod1=3;
  } else {
    cod1=1;
  }
  ERR_P(LscCmd(ser, "CDISP 1:[loop],[resist],1;MOUT [loop]:0;CMODE [loop]:[cod1]"));
  if (twoloops) {
    ERR_P(LscCmd(ser, "CDISP 2:3,[resist],1;MOUT 2:0;CMODE 2:[cod1]"));
  }  
  mout=0;
  jRange=0;
  if (slope<0) slope=-slope;
  if (slope!=0 && slope<0.1) slope=0.1;
  SetCtlSens();
  fbuf=FakeScale(ctlSens, (tLimit + tMaxLimit)*0.5);
  if (loop==1) {
    PidSumHdl(COC_RD, NULL, 0);
    if (!initMaxPower && lastCurrent != maxCurrent && lastCurrent != 0) {
      logfileOut(LOG_MAIN, "pid: %s\nlast %f max %f\n", pid, lastCurrent, maxCurrent);
      prop=prop*lastCurrent/maxCurrent;
    }
    ERR_P(LscCmd(ser, "CLIMIT 1:[fbuf],[slope],0,[iAmp],[iRange];PID [loop],[prop]"));
    if (twoloops) {
      ERR_P(LscCmd(ser, "CLIMIT 2:[fbuf],[slope],0"));
    }
  } else {
    ERR_P(LscCmd(ser, "CLIMIT 2:[fbuf],[slope],0"));
  }
  initMaxPower=0;
  lastCurrent=maxCurrent;
  pold=maxPowerFlt;
  PidSumHdl(COC_RD, NULL, 0);
  logfileOut(LOG_MAIN, "pid: %s\n", pid);
  return 0;
  OnError: return -1;
}

int ConfigAlarms(float genLimit) {
  int i, k;
  SensorT *s;

  ERR_P(LscCmd(ser, "RELAY 1:1;BEEP:0"));
  relay=0;
  k=0;
  for (i=0;i<4;i++) {
    s=sensors[i];
    str_copy(buf1, s->ch);
    if (s->customAlarm==0 && genLimit>0.0) s->alarm=genLimit;
    if (s->present==1 && s->alarm>0) {
      r1=FakeScale(s, s->alarm);
      ERR_P(LscCmd(ser, "ALARM [buf1]:1,[unit],[r1],0,1,1"));
      alarmList[k]=s->ch;
      k++;
    } else if (i != 4) { /* do not switch off A0 alarm */
      ERR_P(LscCmd(ser, "ALARM [buf1]:0")); /* switch off unused alarms */
    }
  }
  alarmListSize=k;
  return 0;
  OnError: return -1;
}

int LoadFromLsc(void) {
  ERR_P(LscCmd(ser, "PID?[loop]>prop,integ,deriv"));
  ERR_P(LscCmd(ser, "RAMP?[loop]>,ramp"));
  if (ctlSens != NULL) {
    ramp *= ctlSens->scale;
  }
  ERR_P(LscCmd(ser, "ANALOG?1>,,,,,,fbuf"));
  still=fbuf;
  return 0;
  OnError: return -1;
}

int DisplayFmt(SensorT *s, SensorT *fields[], int *dispfld) {
  if (s==NULL || s->present<=0 || s->typ=='h' || *dispfld>4) return 0;
  if (s->typ == 'x') {
    s->dispfmt='3';
  } else if (s->typ == 'f') {
    s->dispfmt='4';
  } else {
    if (s->scale == 1.0 || s->kink > 0.0) {
      s->dispfmt='0' + unit;
    } else {
      s->dispfmt='4';
    }
  }
  s->dispfld=*dispfld;
  fields[*dispfld]=s;
  *dispfld=*dispfld+1;
  /* logfileOut(LOG_MAIN, "display %d %c\n", *dispfld, s->dispfmt); */
  return 1;
}

void AssignTypes(void) {
  int i;
  char typ;
  SensorT *s;

  cryo.sensor1=NULL;
  cryo.sensor2=NULL;
  samp.sensor1=NULL;
  samp.sensor2=NULL;
  heliumSens=NULL;
  auxSens=NULL;
  testSens=NULL;
  test2Sens=NULL;
  for (i=0; i<4; i++) {
    s=sensors[i];
    if (s->present==1) {
      typ=s->typ;
      if ('m'==typ) {
        if (cryo.sensor2==NULL) cryo.sensor2=s;
        cryo.sensor1=s;
      } else if ('n'==typ) {
        if (cryo.sensor1==NULL) cryo.sensor1=s;
        cryo.sensor2=s;
      } else if ('s'==typ) {
        if (samp.sensor2==NULL) samp.sensor2=s;
        samp.sensor1=s;
      } else if ('l'==typ) {
        if (samp.sensor1==NULL) samp.sensor1=s;
        samp.sensor2=s;
      } else if ('h'==typ) {
        heliumSens=s;
      } else if ('x'==typ || 'f'==typ) {
        auxSens=s;
      } else if ('t'==typ) {
        testSens=s;
      } else if ('k'==typ) {
        test2Sens=s;
      }
      s->band=10;
    }
  }
  if (cryo.sensor1==NULL) {
    if (samp.sensor1==NULL) {
      if (testSens==NULL) {
        cryo.sensor1=test2Sens;
      } else {
        cryo.sensor1=testSens;
      }
    } else {
      cryo.sensor1=samp.sensor1;
    }
    cryo.sensor2=cryo.sensor1;
  }
  if (samp.sensor1==NULL) {
    samp.sensor1=cryo.sensor1;
    samp.sensor2=cryo.sensor2;
  }
}

int Settings(void) {
  char buf[256];
  SensorT *s;
  SensorT *fields[8];
  char *cfg, *p;
  int i,k,l;

  if (remoteMode!=2) {
    remoteMode=2; /* set to remote mode */
    local=0;
    ERR_I(LoadFromLsc());
    ERR_P(LscCmd(ser, "MODE:[remoteMode]"));
  } else {
    ERR_P(LscCmd(ser, "PID [loop],[prop],[integ],[deriv]"));
  }
  for (i=0; i<4; i++) {
    s=sensors[i];
    if (i<4) {
      plug=plugs[i/2];
    } else {
      plug=&plug0;
    }
    if (s->present < 0) {
      if (settingsFlag) return 0;
      ERR_I(InstalCurve(s, plug->device));
    }
  }

  AssignTypes();

  if (settingsFlag) return 0;

  s=cryo.sensor1;
  if (s!=NULL) {
    ERR_I(SetMaxPower());
  }

  if (settingsFlag) return 0;

  str_copy(statusBuf, "alarms");
  ERR_I(ConfigAlarms(0.0));

  if (settingsFlag) return 0;

  str_copy(statusBuf, "display");

  /* display */
  k=1;

  DisplayFmt(cryo.sensor1, fields, &k);
  if (samp.sensor1 != cryo.sensor1) {
    DisplayFmt(samp.sensor1, fields, &k);
  }
  if (cryo.sensor2 != cryo.sensor1) {
    DisplayFmt(cryo.sensor2, fields, &k);
  }
  if (samp.sensor2 != samp.sensor1 && samp.sensor2 != cryo.sensor2) {
    DisplayFmt(samp.sensor2, fields, &k);
  }
  DisplayFmt(testSens, fields, &k);
  DisplayFmt(test2Sens, fields, &k);
  DisplayFmt(auxSens, fields, &k);
  maxfld=k-1;

  /* 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++) {
      assert(l<128);
      sprintf(buf+l, "DISPFLD %d,%s,%c;", k, fields[k]->ch, fields[k]->dispfmt);
      l=strlen(buf);
    }
    str_append(buf, "DISPLAY:[maxfld]");
    ERR_P(LscCmd(ser, buf));
  }
  if (config[0] != '\0') { /* obsolete */
    str_copy(buf, binDir);
    str_append(buf, config);
    cfg=str_read_file(buf);
    if (cfg != NULL) {
      logfileOut(LOG_MAIN, "load %s\n", config);
      p=cfg;
      while (p!=NULL) {
        p=str_split(buf, p, '\n');
        ERR_P(LscCmd(ser, buf));
      }
      FREE(cfg);
    }
  }
  if (lscfg[0] != '\0') {
    ERR_P(LscCmd(ser, lscfg));
  }

  if (settingsFlag) return 0;
  str_copy(statusBuf, "reading temperatures");  

  if (cryo.sensor1!=NULL) {
    ERR_I(SetTemp(2));
  } else {
    ERR_I(ReadTemp());
  }
  logTime=(rdTim / logPeriod + 2) * logPeriod; /* wait before logging "dirty" temperatures */
  return 0;
  OnError:
    return -1;
}

int ConfigByCode(int plugNr) {
  char buf[256], nam[16];
  int c1, c2;
  char *p;
  FILE *fil;
  
  plug=plugs[plugNr];
  if (autoconfig) {
    str_copy(plug->device,"");
  }
  plug->devcmd=0;
  ConcatDevice();
  if (plug->code==0) {
    if (autoconfig) {
      logfileOut(LOG_MAIN ,"reset inputs on plug%d\n", plugNr);
      plug->sensor1->present=0;
      plug->sensor2->present=0;
      settingsFlag=1;
    } else {
      if (plugNr == 0) {
        str_copy(devname0, "none");
      } else {
        str_copy(devname1, "none");
      }
    }
  } else {
    str_copy(buf, binDir);
    str_append(buf, LSC_CODES);
    ERR_SP(fil=fopen(buf, "r"));
    p=fgets(buf, sizeof(buf), fil);
    c1=0; c2=0;
    while (p!=NULL && plug->code != c1 && plug->code != c2) {
      if (*p != '#') {
        c1=0; c2=0;
        sscanf(buf, "%15s %d %d", nam, &c1, &c2);
      }
      p=fgets(buf, sizeof(buf), fil);
    }
    fclose(fil);
    if (plug->code != c1 && plug->code != c2) {
      logfileOut(LOG_MAIN+LOG_STAT ,"unknown code %d on plug%d\n", plug->code, plugNr);
      return 0;
    } else {
      if (autoconfig) {
        logfileOut(LOG_MAIN+LOG_STAT ,"configure plug%d for %s (code %d)\n", plugNr, nam, plug->code);
        str_copy(buf, "'");
        str_append(buf, nam);
        str_append(buf, "'");
        ERR_I(PrepInput(buf));
        settingsFlag=1;
      } else {
        logfileOut(LOG_MAIN+LOG_STAT ,"%s on plug%d (code %d)\n", nam, plugNr,  plug->code);
        if (plugNr == 0) {
          str_copy(devname0, nam);
        } else {
          str_copy(devname1, nam);
        }
      }
    }
  }
  return 0;
  OnError: return -1;
}

int ConfigByName(int plugNr) {
  char buf[20];

  plug=plugs[plugNr];
  plug->devcmd=1;
  logfileOut(LOG_MAIN+LOG_STAT ,"configure plug%d for %s\n", plugNr, plug->device);
  plug->sensor1->present=0;
  plug->sensor2->present=0;
  sprintf(buf, "'%s'", plug->device);
  ERR_I(PrepInput(buf));
  settingsFlag=1;
  return 0;
  OnError:
    plug->device[0]='\0';
    ConcatDevice();
    return -1;
}

#define HISTSIZE 65

typedef struct {
  int init;
  float vals[HISTSIZE];
  float fore;
  float weight;
  float err;
} Hist;

/*
static Hist fore, mFore;
*/

float forecast(Hist *h, float value, int tim, int nmax) {
  int i, n, stp, ne;
  float d1, d2, d, vmean, vsig, val, val1, fact, fact1;
  float emean, esig, errLin, valLin;

  if (h->init  == 0) { /* put a start value */
    h->init = 1;
    for (i=0; i<HISTSIZE; i++) {
      h->vals[i] = value;
    }
    h->err = value;
    return value;
  }
  for (i=HISTSIZE-1; i>0; i--) {
    h->vals[i] = h->vals[i-1];
  }
  h->vals[0] = value;
  h->err = value;
  h->fore = 0;
  errLin = value;
  valLin = value;
  for (stp = 1; stp*4 < HISTSIZE; stp *= 2) {
    vmean = 0;
    vsig = 0; 
    n = 0;
    val1 = 0;
    emean = 0;
    esig = 0;
    ne = 0;
    for (i = 0; i < stp*nmax && i + 2 * stp < HISTSIZE; i += stp) {
      d1 = h->vals[i+stp] - h->vals[i];
      d2 = h->vals[i+2*stp] - h->vals[i+stp];
      if (fabs(d2) > fabsf(d1) && d1 * d2 >= 0 && n >= 0) {
        /* exponential extrapolation */
        fact = d1 / d2;
        val = h->vals[i] - d1 * fact / (1 - fact);
        if (n == 0) {
          fact1 = fact;
          val1 = val;
        }
        n++;
        d = val - vmean;
        vmean += d / n; 
        vsig += d * (val - vmean);
      } else {
        n = -1; /* exponential interpolation not possible */
      }
      /* linear extrapolation */
      val = h->vals[i] - 2 * d1 * tim / stp; 
      ne++;
      d = val - emean;
      emean += d / ne; 
      esig += d * (val - emean);
      val = h->vals[i];
      ne++;
      d = val - emean;
      emean += d / ne; 
      esig += d * (val - emean);
    }
    if (n > 1) {
      vsig = sqrt(vsig / (n - 1));
      if (vsig < h->err) {
        h->err = vsig;
        h->fore = val1;
        if (fact1 > 0.9) {
          h->weight = (1 - fact1) / stp;
        } else {
          h->weight = 1 - pow(fact1, 1.0/stp);
        }
      }
    }
    esig = sqrt(esig / (ne - 1));
    if (esig < errLin) {
      errLin = esig;
      valLin = emean;
    }
  }
  h->init = 1;
  if (h->fore == 0) {
    h->fore = valLin;
    h->err = errLin;
    h->init = -1;
    h->weight = 1.0 / tim;
  }
  return h->fore;
}  

#define MAXTIMES 16

typedef struct {
  int init;
  int ntimes;
  float period;
  float mn[MAXTIMES];
  float mx[MAXTIMES];
  float upper;
  float lower;
} Stat;

static Stat mStat, sStat;

void statInit(Stat *s, float mn, float mx) {
  int i;
  assert(mx >= mn);
  for (i=1; i < MAXTIMES; i++) {
    s->mn[i] = mn;
    s->mx[i] = mx;
  }
}

void statCrop(Stat *s, float mn, float mx) {
  int i;
  float d;
  
  if (mn > s->upper) {
    d = mn - s->upper;
    for (i=1; i < MAXTIMES; i++) {
      s->mx[i] += d;
    }
  }
  if (mx < s->lower) {
    d = mx - s->lower;
    for (i=1; i < MAXTIMES; i++) {
      s->mn[i] += d;
    }
  }
}
  
void statBegin(Stat *s, float period, float value) {
  s->ntimes = 0;
  s->period = period;
  s->mn[0] = value;
  s->mx[0] = value;
  if (s->init  == 0) { /* put a start value */
    s->init = 1;
    statInit(s, value*0.9, value*1.1);
  }
  s->lower = 0;
  s->upper = 0;
}
  
int statTime(Stat *s, float tim1, float tim2) {
  float w1, w2, dif, mn, mx;
  int i;
 
  i = s->ntimes;
  assert(tim1 <= tim2);
  assert(i < MAXTIMES-1);
  
  mn = s->mn[s->ntimes];
  mx = s->mx[s->ntimes];
  s->ntimes++;
  i = s->ntimes;
  s->upper = s->mx[i];
  s->lower = s->mn[i];
  if (tim1 <= 0) {
    w1 = 1;
  } else {
    w1 = 1 - exp(-s->period / tim1);
  }
  if (tim2 <= 0) {
    w2 = 1;
  } else {
    w2 = 1 - exp(-s->period / tim2);
  }
  dif = mx - s->mx[i];
  if (dif > 0) {
    s->mx[i] += w1 * dif;
  } else {
    s->mx[i] += w2 * dif;
  }
  dif = mn - s->mn[i];
  if (dif < 0) {
    s->mn[i] += w1 * dif;
  } else {
    s->mn[i] += w2 * dif;
  }
  return i;
}

void statEnd(Stat *s) {
  int i;
  i = s->ntimes;
  if (s->upper < s->mx[i]) {
    s->upper = s->mx[i];
  }
  if (s->lower > s->mn[i]) {
    s->lower = s->mn[i];
  }
}

typedef struct {
  float slope;
  float last;
  float mx;
  int wasmax;
  time_t lastTim;
} Slope;


void maxSlope(Slope *s, float value) {
  float d;
  if (rdTim <= s->lastTim + per*0.0015) {
    return;
  }
  s->wasmax = 0;
  if (s->slope < 0) {
    s->last = value;
    s->lastTim = rdTim;
    s->slope = 0;
  } else {
    d = (value - s->last) / (rdTim - s->lastTim);
    s->slope = d;
    if (fabsf(d) > s->mx) {
      s->mx = fabsf(d);
      s->wasmax = 1;
    }
  }
  s->last = value;
  s->lastTim = rdTim;
}

static Slope slopeS, slopeU, slopeL;

int PeriodicTask(void) {
  char buf[256], lbuf[16];
  char *next, *alms;
  int i, k, cnt;
  float t, htr0, mstep, fdif, d, dif2, ml;

  static time_t finTime;
  static int lastIntTim;
  static float dif=0;
  static float oldSet=0;
  static float oldShift=0;

/*
  static float shiftTarget=0;
*/  
  if (stickhtr < 0) {
    ERR_P(LscCmd(ser, "DIOST?>cod1,out1;DOUT 3,29;BUSY?>busy"));
  } else {
    if (parallelhtr) {
      out1 = 2;
    } else {
      out1 = 0;
    }
    if (stickhtr > 1) {
      out1 |= stickhtr;
    }
    ERR_P(LscCmd(ser, "DOUT 3,[out1];BUSY?>busy"));
  }
  if ((plug0.codDefined && plug1.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 */
    k=serialNo; /* check serial number */
    ERR_P(LscCmd(ser, "*IDN?>buf1,buf2,buf3,buf4"));

    /* decode firmware version date */
    i=0;
    sscanf(buf4, "%d", &i);
    i=i/100+(i%100+2000)*10000;
    if (i>20900000) i-=1000000; /* 90..99 --> 1990-1999 */
    lscVersion=i;

    sscanf(buf3, "%d", &serialNo);
    if (0!=strcmp(buf1, "LSCI") || 0!=strcmp(buf2, "MODEL340")) return 0;
    if (k!=serialNo) { /* controller exchanged or we are initializing */
      logfileOut(LOG_MAIN, "serialNo: %d (was %d)\n", serialNo, k);
      if (serialNo < 340000 || serialNo > 349999) {
        serialNo=k;
      } else if (k < 340000 || k > 349999) {
        k=serialNo;
      }
    }
    if (k!=serialNo) { /* controller exchanged or we are initializing */
      logfileOut(LOG_MAIN, "controller connected\n");
      set=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 */
      plug1.device[0]='\0';
      plug0.code=0;
      plug1.code=0;
      next=str_split(buf1, buf, '/');
      if (next!=NULL) {
        next=str_split(buf2, next, '/');
        if (next!=NULL) {
          next=str_split(buf3, next, '/');
          plug0.code=atoi(buf3);
          plug0.codChanged=1;
          if (next!=NULL) {
            next=str_split(buf3, next, '\n');
            plug1.code=atoi(buf3);
            plug1.codChanged=1;
          }
        }
      }
      if (buf1[0]=='*') {
        str_copy(plug0.device, buf1+1);
        plug0.codChanged=0;
        ConfigByName(0);
        if (autoconfig == 0) {
          str_copy(devname0, plug0.device);
        }
      } else {
        if (plug0.code!=0) str_copy(plug0.device, buf1);
        plug0.devcmd=0;
        if (autoconfig == 0) {
          ConfigByName(0);
          str_copy(devname0, plug0.device);
        }          
      }
      if (buf2[0]=='*') {
        str_copy(plug1.device, buf2+1);
        plug1.codChanged=0;
        ConfigByName(1);
        /* if (autoconfig == 0) ConfigByCode(1); */
      } else {
        if (plug1.code!=0) str_copy(plug1.device, buf2);
        plug1.devcmd=0;
        if (autoconfig == 0) {
          ConfigByName(1);
          str_copy(devname1, plug1.device);
        }          
      }
      ConcatDevice();

      settingsFlag=1;
    }
    noResp=0;
  }

  ERR_I(ReadHeater(1));

  if (alarmListSize!=0) {
    buf[0]='\0';
    for (k=0;k<alarmListSize;k++) {
      str_append(buf,";ALARMST?");
      str_append(buf, alarmList[k]);
    }
    ERR_P(alms=LscCmd(ser, buf+1)); /* send without leading semicolon */
    if (0!=strcmp(alarmStatus, alms)) {
      str_copy(alarmStatus, alms);
      alarmChannels[0]='\0';
      for (k=0;k<alarmListSize;k++) {
        if (alms[k*4] != '0') {
          str_append(alarmChannels, alarmList[k]);
          str_append(alarmChannels, " ");
          if (NULL==strstr(alarmHistory, alarmList[k])) { /* if not yet in history */
            str_append(alarmHistory, alarmList[k]); /* add to history */
          }
        }
      }
      if (alarmChannels[0]=='\0') {
        if (lockAlarm) {
          logfileOut(LOG_MAIN, "No more active alarms, latching alarms: %s!\n", alarmHistory);
        } else {
          alarmHistory[0]='\0';
          alarmChannels[0]='\0';
          logfileOut(LOG_MAIN, "No more active alarms\n");
          ERR_P(LscCmd(ser, "ALMRST"));
          relay=0;
        }
      } else {
        logfileOut(LOG_MAIN, "Alarm on channel %s\n", alarmChannels);
        if (!relay0 && set!=0) {
          lockAlarm=1;
          logfileOut(LOG_MAIN, "Switch off heater\n");
          jRange=0;
          if (loop==1) { /* turn on high relay manually, switch off heater and reset alarms */
            ERR_P(LscCmd(ser, "RELAY 1:2,1;RANGE:0;ALMRST")); 
          } else {
            ERR_P(LscCmd(ser, "RELAY 1:2,1;ANALOG 2:0,0;ALMRST"));
            relay=1;
          }
        } else {
          ERR_P(LscCmd(ser, "ALMRST"));
        }
      }
    } else {
      ERR_P(LscCmd(ser, "ALMRST"));
    }
  } else if (loop == 1) {
    if (relay && !relay0) logfileOut(LOG_MAIN, "Relay is on!\n");
    alarmStatus[0]='\0';
  }

  relay0=relay;

  ERR_I(ReadTemp());

  snprintf(ttp, sizeof(ttp), "%.6g %.6g %.6g %.6g %.6g", tm, tr, power, tx, set);

  if (htrst!=htrst0) {
    LogMinMax(0);
    if (htrst==0) {
      logfileOut(LOG_MAIN, "heater o.k.\n");
    } else {
      logfileOut(LOG_MAIN, "%s\n", LscHtrStat(htrst));
    }
    htrst0=htrst;
  }

  if (rdTim>=logTime) {
    ERR_I(DataPutAll(NULL, rdTim));
    logTime=(rdTim / logPeriod + 1) * logPeriod;
    if (rdTim>mmTime && (cryo.sensor1 != NULL)) LogMinMax(0);
  }
  if (set!=0 && remoteMode==2) {
    t=ctlSens->t;
    if (htr == 0.0 && mout < 0) {
      mstep=0.2;
      while (htr == 0 && mout < 0) {
        mstep *= 2;
        mout += mstep;
        ERR_P(LscCmd(ser, "MOUT [loop],[mout]"));
        ERR_I(ReadHeater(0));
      }
      cnt=10;
      while (htr >= 0.2 && cnt > 0) {
        mout -= htr - 0.1;
        htr0=htr;
        if (mout > 0) mout=0;
        ERR_P(LscCmd(ser, "MOUT [loop],[mout]"));
        while (htr == htr0 && cnt > 0) {
          ERR_I(ReadHeater(0));
          cnt--;
        }
      }
      logfileOut(LOG_MAIN, "adjusted mout=%.2f\n", mout);
    }
    if (htr == 100.0 && mout > 0) {
      mstep = 0.2;
      while (htr == 100.0 && mout > 0) {
        mstep *= 2;
        mout -= mstep;
        ERR_P(LscCmd(ser, "MOUT [loop],[mout]"));
        ERR_I(ReadHeater(0));
      }
      cnt=10;
      while (htr <= 99.8 && cnt > 0) {
        mout += 99.9 - htr;
        htr0=htr;
        if (mout < 0) mout=0;
        ERR_P(LscCmd(ser, "MOUT [loop],[mout]"));
        while (htr == htr0 && cnt > 0) {
          ERR_I(ReadHeater(0));
          cnt--;
        }
      }
      logfileOut(LOG_MAIN, "adjusted mout=%.2f\n", mout);
    }
    fdif=FakeScale(ctlSens, tr)-t;
    fbuf=htr-mout-prop/6*fdif; /* value of integrator (assume deriv=0) */
    if ((fbuf > 99.8 && mout < 0 && fdif > 0) ||
        (fbuf <  0.2 && mout > 0 && fdif < 0)) { /* probably integrator overflow */
      if (lastIntTim > 0) {
        mout += fdif*prop*integ/3000*(rdTim-lastIntTim); /* use mout for integral */
        if (mout < -100) mout=-100;
        if (mout > 100) mout=100;
        ERR_P(LscCmd(ser, "MOUT [loop],[mout]"));
      } else {
        logfileOut(LOG_MAIN, "possible integrator overflow, mout=%.2f\n", mout);
      }
      lastIntTim=rdTim;
    } else {
      if (lastIntTim>0) {
        logfileOut(LOG_MAIN, "end integrator overflow mout=%.2f\n", mout);
        lastIntTim=0;
      }
    }
    ml = set/100;
    if (ml == 0) ml = 1;
    
    statBegin(&mStat, per*0.001, TrueScale(ctlSens, ctlSens->t));
    statTime(&mStat, 10, 20); 
    statTime(&mStat, int2/16, int2/2); 
    statTime(&mStat, int2/1.5, int2+10);
    statEnd(&mStat);
    
    statBegin(&sStat, per*0.001, samp.temp);
    statTime(&sStat, 10, 20);
    statEnd(&sStat);
    
    maxSlope(&slopeS, samp.temp);
    maxSlope(&slopeU, mStat.upper);
    maxSlope(&slopeL, mStat.lower);
    
    /* statBegin(&sStat, maxShift*0.01, per*0.001, samp.temp); */
     
    d = set - samp.temp;
    shiftLow = mStat.lower - sStat.upper;
    shiftUp = mStat.upper - sStat.lower;
    if (cryo.sensor1!=samp.sensor1 && doubleControl) {
      dif2 = d - dif;
      if (oldSet == 0) oldSet = samp.temp;
      if (oldSet != set || fabsf(d) > 50*ml) {
        if (oldSet != set) {
          float f = fabsf((oldSet - set)/ (oldSet + set));
          oldShift *= exp(-f*f*25);
          tShift = oldShift;
          statCrop(&mStat, sStat.lower+tShift, sStat.upper+tShift);
          state = 1;
        } else {
          state = 0;
        }
        finTime = rdTim + int2 + 30;
        slopeS.mx = -1;
        slopeU.mx = -1;
        slopeL.mx = -1;
      }
      if (ramping) {
        /* do not change tShift when ramping */
      } else if (rdTim < finTime) {
        if (slopeS.wasmax) {
          finTime = rdTim + int2 + 30;
        }
        if (fabs(slopeS.slope) > slopeS.mx * 0.2
            && finTime < rdTim + per * 0.0035) {
          finTime = rdTim + per  * 0.0035;
        }
        if (d > 0) {
          tShift = oldShift + propUp * d;
        } else {
          tShift = oldShift + propDown * d;
        }
      } else {
        if (state != 2) {
          /* tShift = oldShift; */
          state = 2;
          statCrop(&mStat, sStat.lower+tShift, sStat.upper+tShift);
        } else {
          if (shiftLow > tShift) {
            tShift = shiftLow;
            oldShift = tShift;
          } else if (shiftUp < tShift) {
            tShift = shiftUp;
            oldShift = tShift;
          }
        }
      }
      if (tShift > ml * maxShift) {
        tShift = ml * maxShift;
      } else if (tShift < -ml * maxShift) {
        tShift = - ml * maxShift;
      }
      dif = d;
      ERR_I(SetTemp(0));
    } else {
      ERR_I(SetTemp(0));
    }
  } else {
    oldShift = 0;
  }
  oldSet = set;

  if (stickhtr < 0) {
    ERR_P(LscCmd(ser, "KEYST?>key;DIOST?>cod2,out2;DOUT 3,30"));
  } else {
    ERR_P(LscCmd(ser, "KEYST?>key"));
    if (stickhtr == 1) {
      d = set - samp.temp;
      if (parallelhtr) {
        if (d < stickhtroff) {
          parallelhtr = 0;
        }
      } else if (d > stickhtron) {
        parallelhtr=1;
      }
    }
  }
  if (busy==0) {
    if (stickhtr < 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"));
      }
    } else {
      out1=0;
      out2=0;
    }

    if (out1==30 && out2==29) {
      /* code conversion */
      plug0.code1=3*decod[cod2 % 8] ^ 2*decod[cod1 % 8]; /* ^ is exclusive OR */
      plug1.code1=-(3*decod[cod2 / 8] ^ 2*decod[cod1 / 8]);
      for (i=0; i<2; i++) {
        plug=plugs[i];
        if (plug->code1!=plug->code) { /* code has changed -> wait for a confirmation */
          if (plug->code1!=plug->code2) {
            plug->code2=plug->code1;
          } else {
            plug->code=plug->code1;
            plug->codChanged=1;
          }
        } else {
          plug->code2=plug->code;
          if (plug->codChanged) { /* code change confirmed */
            plug->codChanged=0;
            if (plug->code1==0) {
              logfileOut(LOG_MAIN, "plug%d unplugged\n", i);
              str_copy(plug->descr,"unplugged");
            } else {
              logfileOut(LOG_MAIN, "plugged %d on plug%d\n", plug->code1, i);
            }
            set=0;
            ERR_I(ConfigByCode(i));
          }
          plug->codDefined=1;
        }
      }
    }
  }

  if (key!=0) {
    ERR_P(LscCmd(ser, "MODE?>remoteMode"));
    local = (remoteMode == 1);
    if (!touched) {
      touched=1;
      logfileOut(LOG_MAIN ,"user touched keys\n");
    }
    if (remoteMode==2) {
      logfileOut(LOG_MAIN ,"user switched to remote\n");
      touched=0;
      ERR_I(LoadFromLsc());
      if (doubleControl) {
        ERR_P(LscCmd(ser, "RANGE?>iRange"));
        if (iRange==0) set=0;
      } else {
        ERR_P(LscCmd(ser, "RAMP?[loop]>,ramp"));
        ERR_P(LscCmd(ser, "RANGE?>iRange;CSET?[loop]>,cod1,;SETP?[loop]>fbuf"));
        if (ctlSens!=NULL) {
          set=TrueScale(ctlSens,fbuf);
        } else {
          set=0;
        }
        if (cod1!=1) {
          set=0;
          logfileOut(LOG_MAIN, "set point was not in K, set to 0\n");
        }
      }
      settingsFlag=1;
      jRange=iRange;
    }
  }

  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 DeviceHdl(int mode, void *base, int fd) {
  char *t;
  int do0, do1;

  if (mode==COC_WR) {
    set=0;
    t=strchr(device, '/');
    if (t==NULL) {
      if (0==strcmp(device, "0") || 0==strcasecmp(device, "auto")) {
        ERR_I(ConfigByCode(0));
        ERR_I(ConfigByCode(1));
      } else {
        str_copy(plug0.device, device);
        str_copy(plug1.device, device);
        ERR_I(ConfigByName(0));
        ERR_I(ConfigByName(1));
      }
    } else {
      do0=0;
      do1=0;
      if (t!=device) {
        *t='\0';
        str_copy(plug0.device, device);
        *t='/';
        do0=1;
      }
      t++;
      if (*t!='\0') {
        str_copy(plug1.device, t);
        do1=1;
      }
      if (do0) ERR_I(ConfigByName(0));
      if (do1) ERR_I(ConfigByName(1));
    }
    ConcatDevice();
  }
  return 0;
  OnError: return -1;
}

int PidHdl(int mode, void *base, int fd) {
  char oldpid[128];

  if (mode==COC_WR) {
    return COC_DWR;
  } else if (mode==COC_DWR) {
    assert(sizeof oldpid == sizeof pid);
    strcpy(oldpid, pid);
    PidSumHdl(COC_RD, base, fd);
    if (strcmp(oldpid, pid) != 0) {
      ERR_P(LscCmd(ser,"PID [loop]:[prop],[integ],[deriv]"));
      logfileOut(LOG_MAIN, "pid: %s\n", pid);
    }
  }
  return 0;
  OnError: return -1;
}

int AlarmHdl(int mode, void *base, int fd) {
  if (mode==COC_WR) {
    return COC_DWR;
  } else if (mode==COC_DWR) {
    ERR_I(ConfigAlarms(0.0));
  }
  return 0;
  OnError: return -1;
}

int TLimitHdl(int mode, void *base, int fd) {
  if (mode==COC_WR) {
    if (tLimit>tMaxLimit) tLimit=tMaxLimit;
    return COC_DWR;
  } else if (mode==COC_DWR) {
    ERR_I(ConfigAlarms(tLimit));
    ERR_I(SetMaxPower());
  }
  return 0;
  OnError: return -1;
}

int SwapHdl(int mode, void *base, int fd) {
  static char swp;
  static SensorT *s1, *s2;

  if (mode==COC_WR) {
    if (strlen(swap) != 2) ERR_MSG("must be 2 letters");
    str_lowcase(swap, swap);
    s1 = findSensor(swap[0]);
    s2 = findSensor(swap[1]);
    if (s1 == NULL || s2 == NULL) {
      ERR_MSG("no such sensor(s)");
    }
    if (s1 == s2) {
      return 0;
    }
    return COC_DWR;
  } else if (mode==COC_DWR) {
    swp = s1->typ;
    s2->typ = s1->typ;
    s1->typ = swp;
    AssignTypes();
  }
  return 0;
  OnError: return -1;
}

int SetPower(float setpower) {
  float htr1, htr0;
  int cnt;

  logfileOut(LOG_MAIN,"SetPower\n");
  if (setpower<0) return 0;
  if (setpower>maxPowerFlt) setpower=maxPowerFlt;
  if (setpower<0) setpower=0;
  mout=Power2Percent(setpower);
  if (mout>100) mout=100;
  if (!manual && set == 0 && setpower > 0) {
    ERR_P(LscCmd(ser, "RANGE:[iRange]"));
    manual=1;
  }
  ERR_P(LscCmd(ser, "CMODE [loop]:3;MOUT [loop],[mout]")); /* set to open loop */
  if (manual) return 0;
  ERR_I(ReadHeater(0));
  ERR_P(LscCmd(ser, "RANGE:0"));
  htr0=htr;
  ERR_P(LscCmd(ser, "CMODE [loop]:1")); /* pid control*/
  ERR_P(LscCmd(ser, "RANGE:[iRange]"));
  ERR_I(ReadHeater(0));
  cnt=20;
  htr1=htr;
  while (htr1==htr && cnt>0) { /* wait for heater change */
    ERR_I(ReadHeater(0));
    cnt--;
  }
  if (htr != htr0) {
    mout=0;
    cnt=10;
    while (cnt>0) {
      mout+=htr0-htr;
      if (mout > 100) mout=100;
      if (mout < -100) mout =-100;
      ERR_P(LscCmd(ser, "MOUT [loop],[mout]"));
      ERR_I(ReadHeater(0));
      cnt--;
    }
  }
  power=Percent2Power(htr);
  logfileOut(LOG_MAIN, "power: %5f (tried %5f), mout %.1f\n", power, setpower, mout);
  return 0;
  OnError: return -1;
}

int AlarmReset(void) {
  if (relay) {
    ERR_P(LscCmd(ser, "RELAY 1:2,1"));
    if (alarmHistory[0]=='\0') {
      str_copy(alarmHistory, "X");
    }
  } else {
    alarmHistory[0]='\0';
    alarmStatus[0]='\0';
    ERR_P(LscCmd(ser, "ALMRST;RELAY 1:1"));
    logfileOut(LOG_MAIN, "reset alarms\n");
    relay0=0;
    lockAlarm=0;
  }
  OnError: return -1;
}

int SetHdl(int mode, void *base, int fd) {
  if (mode==COC_WR) {
    if (remoteMode!=2) {
      ERR_MSG("controller is in local mode, enter 'remote' first");
    }
    if (relay && set!=0) {
      if (alarmChannels[0]!='\0') {
        ERR_MSG("alarm is active, set prohibited");
      } else {
        ERR_MSG("alarm was active, enter 'reset' first");
      }
    } else if (set<=0) {
      set=0;
    } else if (set>tLimit) {
      set=0;
      ERR_MSG("set point too high");
    }
    return COC_DWR;
  } else if (mode==COC_DWR) {
    if (manual) {
      manual=0;
      mout=0;
      ERR_P(LscCmd(ser, "CMODE [loop],1;MOUT [loop],0"));
    }
    if (cryo.sensor1!=NULL && remoteMode==2) {
      ERR_I(SetTemp(1));
    }
  }
  return 0;
  OnError: return -1;
}

int ControlModeHdl(int mode, void *base, int fd) {
  if (mode==COC_WR) {
    if (controlMode == 1) {
      controlSensor[0]='1';
    } else {
      controlSensor[0]='0';
    }
    if (controlMode >= 2) {
      doubleControl = 1;
      controlMode = 2;
    } else {
      doubleControl = 0;
      if (controlMode < 0) {
        controlMode = 0;
      }
    }
  }
  return SetHdl(mode, base, fd);
}

int MaxPowerHdl(int mode, void *base, int fd) {
  static float setpower;
  if (mode==COC_WR) {
    if (loop!=1) return COC_DWR;
    setpower=power;
    sscanf(maxPower, "%f", &maxPowerFlt);
    CalcMaxPower();
    if (remoteMode!=2) {
      ERR_MSG("controller is in local mode, enter 'remote' first");
    } else if (relay) {
      if (alarmChannels[0]!='\0') {
        ERR_MSG("alarm is active, maxPower prohibited");
      } else {
        ERR_MSG("alarm was active, enter 'reset' first");
      }
    }
    return COC_DWR;
  } else if (mode==COC_DWR && remoteMode==2) {
    ERR_I(SetMaxPower());
    if (maxCurrent>0 && loop==1) {
      ERR_I(SetPower(setpower));
    }
  }
  return 0;
  OnError: return -1;
}

int PowerHdl(int mode, void *base, int fd) {
  static float setpower=0;
  float htr1, diff;
  if (mode==COC_WR) {
    if (power>maxPowerFlt) power=maxPowerFlt;
    htr1=Power2Percent(power);
    diff=htr1-htr;
    if (mout+diff>100) {
      diff=100-mout;
      htr+=diff;
      power=Percent2Power(htr);
    } else if (mout+diff<-100) {
      diff=-100-mout;
      htr+=diff;
      power=Percent2Power(htr);
    }
    if (power<0) power=0;
    setpower=power;
    return COC_DWR;
  } else if (mode==COC_DWR && maxCurrent>0) {
    ERR_I(SetPower(setpower));
  }
  return 0;
  OnError: return -1;
}

int ManualHdl(int mode, void *base, int fd) {
  if (mode == COC_DWR && maxCurrent>0) {
    if (manual == 0 && ctlSens != NULL) {
      set=TrueScale(ctlSens, ctlSens->t);
    }
  }
  return PowerHdl(mode, base, fd);
}

int MoutHdl(int mode, void *base, int fd) {
  if (mode==COC_WR) {
    return COC_DWR;
  } else if (mode==COC_DWR) {
    ERR_P(LscCmd(ser, "MOUT [loop],[mout]"));
  }
  return 0;
  OnError: return -1;
}


int StillHdl(int mode, void *base, int fd) {
  if (mode==COC_WR) {
    return COC_DWR;
  } else if (mode==COC_DWR) {
    fbuf=still;
    ERR_P(LscCmd(ser,"ANALOG 1:1,2,,,,,[fbuf]"));
  }
  return 0;
  OnError: return -1;
}

int SendHdl(int mode, void *base, int fd) {
  char *res;
  ClientData *data;

  if (mode==COC_WR) {
    return COC_DRD;
  } else if (mode==COC_DRD) {
    data=base;
    if (NULL!=strchr(data->cmd, '>') || NULL!=strchr(data->cmd, '['))
        ERR_MSG("no variables allowed");
    ERR_P(res=LscCmd(ser, data->cmd));
    str_copy(data->cmd, res);
  }
  return 0;
  OnError: return -1;
}

int LogHdl(int mode, void *base, int fd) {
  ClientData *data;

  if (mode==COC_RD) {
    data=base;
    if (data->logstart != 0) {
      data->logpos = logfilePos(data->logstart);
      data->logstart = 0;
    }
    data->logpos=logfileGetLines(data->logpos, 25, data->logline, sizeof(data->logline));
  }
  return 0;
}

int PltHdl(int mode, void *base, int fd) {
  if (mode==COC_RD) {
    ERR_MSG("Tecs client version not up to date");
  }
  return 0;
  OnError: return -1;
}

int GraHdl(int mode, void *base, int fd) {
  int l;
  char names[64];
  long startTime, endTime, step;

  if (mode==COC_RD) {
    ERR_SI(sscanf(grapar, "%ld %ld %ld %n", &startTime, &endTime, &step, &l)-2);
    str_copy(names, grapar+l);
    ERR_I(l=DataGetMult(names, startTime, endTime, step, 30, gradata, sizeof(gradata)/sizeof(float)));
    assert(l*sizeof(float)<sizeof(gradata));
    *grasize=l;
  }
  return 0;
  OnError: return -1;
}

int ShowSensor(StrBuf *buf, SensorT *s, char *name, char *units, char lim) {

  if (s->present!=1) return 0;
  ERR_I(StrPut(buf, "\n ", ' '));
  ERR_I(StrPut(buf, s->ch, ':'));
  ERR_I(StrPut(buf, "", ' '));
  if (s->readStat==0) {
    if (s->t==0) {
      ERR_I(StrPut(buf, "not yet read", StrNONE));
    } else {
      ERR_I(StrPut(buf, name, '='));
      ERR_I(PutFloat(buf, 5, TrueScale(s, s->t)));
      ERR_I(StrPut(buf, " ", StrNONE));
      ERR_I(StrPut(buf, units, StrNONE));
    }
  } else {
    ERR_I(StrPut(buf, LscReadStat(s->readStat), StrNONE));
  }
  if (s==ctlSens && !manual) {
    ERR_I(StrPut(buf, ", setpoint", '='));
    if (tr==DATA_UNDEF) {
      ERR_I(PutFloat(buf, 5, 0.0));
    } else {
      ERR_I(PutFloat(buf, 5, tr));
    }
    ERR_I(StrPut(buf, " ", StrNONE));
    ERR_I(StrPut(buf, units, StrNONE));
  }
  if (s->lim > 0 && lim!=0) {
    ERR_I(StrPut(buf, ", for T", lim));
    ERR_I(PutFloat(buf, 5, s->lim));
  }
  return 0;
  OnError: return -1;
}

int ErrorHdl(int mode, void *base, int fd) {
  error[0]='\0';
  if (noResp==3) {
    snprintf(error, sizeof(error), "no connection or controller switched off");
  } else if (statusBuf[0] != '\0') {
    snprintf(error, sizeof(error), "configuring: %s", statusBuf);
  } else if (device[0] == '\0') {
    snprintf(error, sizeof(error), "no device selected");
  }
  return 0;
}

int StatusHdl(int mode, void *base, int fd) {
  int i;
  StrBuf buf;
  float tM, tS;
  char pendAct[256];
  char *p;

  readTemp=1;
  StrLink(&buf, status);
  StrClear(&buf);
  ERR_I(StrPut(&buf, "\n", StrNONE));
  ErrorHdl(mode, base, fd);
  if (error[0] != '\0') {
    ERR_I(StrPut(&buf, error, StrNONE));
    goto EndStatus;
  }
  CocShowHandlers(pendAct, sizeof(pendAct));
  if (pendAct[0]!='\0') {
    if (mode==COC_RD) {
      return COC_DRD;
    } else {
      ERR_I(StrPut(&buf, "pending action(s):", ' '));
      ERR_I(StrPut(&buf, pendAct, StrNONE));
      goto EndStatus;
    }
  }
  if (remoteMode==1) {
    ERR_I(StrPut(&buf, "LOCAL mode, enter 'remote' to enable remote control", '\n'));
  }
  ERR_I(StrPut(&buf, "device:", ' '));
  p=strchr(device, '/');
  if (p!=NULL) {
    *p='\0';
    ERR_I(StrPut(&buf, device, ' '));
    *p='/'; p++;
  } else {
    ERR_I(StrPut(&buf, device, ' '));
  }
  ERR_I(StrPut(&buf, plug0.descr, StrNONE));
  if (plug0.devcmd) {
    ERR_I(StrPut(&buf, " ", '*'));
  }
  if (p!=NULL) {
    ERR_I(StrPut(&buf, "\n       ", ' '));
    ERR_I(StrPut(&buf, p, ' '));
    ERR_I(StrPut(&buf, plug1.descr, StrNONE));
    if (plug1.devcmd) {
      ERR_I(StrPut(&buf, " ", '*'));
    }
  }
  if (set==0) {
    ERR_I(StrPut(&buf, "\nheater off,",' '));
  } else {
    if (manual) {
      ERR_I(StrPut(&buf, "\nmanual power,", ' '));    
    } else {
      ERR_I(StrPut(&buf, "\ntarget", '='));
      ERR_I(PutFloat(&buf, 5, set));
      if (ramp==0 || tr==TrueScale(ctlSens, setH)) {
        ERR_I(StrPut(&buf, " K,", ' '));
      } else {
        ERR_I(StrPut(&buf, " K ramping at", ' '));
        ERR_I(PutFloat(&buf, 4, ramp));
        ERR_I(StrPut(&buf, " K/min,", ' '));
      }
    }
  }
  if (relay) {
    if (alarmChannels[0]!='\0') {
      ERR_I(StrPut(&buf, "alarm on channel",' '));
      ERR_I(StrPut(&buf, alarmChannels, StrNONE));
    } else if (alarmHistory[0]!='\0') {
      ERR_I(StrPut(&buf, "alarm was on channel", ' '));
      ERR_I(StrPut(&buf, alarmHistory, ','));
      ERR_I(StrPut(&buf, " (enter 'reset' to confirm)", StrNONE));
    } else {
      ERR_I(StrPut(&buf, "alarm",' '));
    }
  } else if (htrst!=0) {
    ERR_I(StrPut(&buf, LscHtrStat(htrst), StrNONE));
  } else {
    if (power!=DATA_UNDEF) {
      ERR_I(StrPut(&buf, "htr power", '='));
      if (power>=0.5) {
        ERR_I(PutFloat(&buf, 3, power));
        ERR_I(StrPut(&buf, " W, htr current", '='));
      } else {
        ERR_I(PutFloat(&buf, 3, power*1000));
        ERR_I(StrPut(&buf, " mW, htr current", '='));
      }
      ERR_I(PutFloat(&buf, 4, htr));
      ERR_I(StrPut(&buf, "% ", '('));
      ERR_I(PutFloat(&buf, 4, sqrt(power/resist)));
      ERR_I(StrPut(&buf, " A", ')'));
    } else {
      ERR_I(StrPut(&buf, "set=0", StrNONE));
    }
  }
  tM=0; tS=0;
  if (cryo.sensor1!=NULL) {
    if (cryo.sensor1!=samp.sensor1) {
      ERR_I(StrPut(&buf, "\nheat exchanger", ':'));
      if (cryo.sensor2!=cryo.sensor1) {
        ERR_I(StrPut(&buf, " T", '='));
        ERR_I(PutFloat(&buf, 4, cryo.temp));
        ERR_I(StrPut(&buf, " ", 'K'));
        ERR_I(ShowSensor(&buf, cryo.sensor1, "T", "K", '>'));
      }
      ERR_I(ShowSensor(&buf, cryo.sensor2, "T", "K", '<'));
      ERR_I(StrPut(&buf, "\nsample:", ' '));
    }
    if (samp.sensor2!=samp.sensor1) {
      ERR_I(StrPut(&buf, " T", '='));
      ERR_I(PutFloat(&buf, 4, samp.temp));
      ERR_I(StrPut(&buf, " ", 'K'));
      ERR_I(ShowSensor(&buf, samp.sensor1, "T", "K", '>'));
    }
    ERR_I(ShowSensor(&buf, samp.sensor2, "T", "K", '<'));
  }
  if (testSens!=NULL || test2Sens!=NULL) {
    ERR_I(StrPut(&buf, "\ntest", ':'));
  }
  if (testSens!=NULL) {
    ERR_I(ShowSensor(&buf, testSens, "T", "K", 0));
  }
  if (test2Sens!=NULL) {
    ERR_I(ShowSensor(&buf, test2Sens, "T", "K", 0));
  }
  for (i=5; i<4; i++) {
    ERR_I(ShowSensor(&buf, sensors[i], "T", "K", 0));
  }
  if (auxSens!=NULL) {
    ERR_I(StrPut(&buf, "\nauxilliary", ':'));
    ERR_I(ShowSensor(&buf, auxSens, "U", "V", 0));
  }
  if (heliumSens!=NULL) {
    ERR_I(StrPut(&buf, "\nhelium:", ' '));
    ERR_I(StrPut(&buf, helium, StrNONE));
  }
  if (still!=0) {
    ERR_I(StrPut(&buf, "\nstill:", ' '));
    ERR_I(PutFloat(&buf, 1, still));
    ERR_I(StrPut(&buf, " mW", ' '));
  }
EndStatus:
  ERR_I(StrPut(&buf, "", '\0'));
  return 0;
  OnError:
    return -1;
}

int DevHelpHdl(int mode, void *base, int fd) {
  char nbuf[256];
  char *list;

  str_copy(nbuf, binDir);
  str_append(nbuf, DEV_LIST);
  ERR_P(list=str_read_file(nbuf));
  str_copy(devHelp, list);
  FREE(list);
  return 0;
  OnError: return -1;
}

int UpdateHdl(int mode, void *base, int fd) {
  char cmd[128];
  if (mode==COC_WR) {
    return COC_DRD;
  } else if (mode==COC_DRD) {
    str_copy(cmd, "tecsinstall ");
    str_lowcase(update, update);
    str_append(cmd, update);
    str_append(cmd, " ");
    if (NULL==strstr(" cfg server sync ", cmd+11)) {
      str_copy(update, "unknown");
    } else {
      system(cmd);
    }
  }
  return 0;
}

int RemoteHdl(int mode, void *base, int fd) {
  if (mode==COC_WR) {
    return COC_DWR;
  } else if (mode==COC_DWR) {
    ERR_P(LscCmd(ser, "MODE:[remoteMode]"));
  }
  return 0;
  OnError: return -1;
}

int LocalHdl(int mode, void *base, int fd) {
  if (mode==COC_RD) {
    local = (remoteMode == 1);
    return 0;
  } else if (mode==COC_WR) {
    remoteMode = 1 + (local == 0);
    return RemoteHdl(mode, base, fd);
  } else if (mode==COC_DWR) {
    return RemoteHdl(mode, base, fd);
  }
  return 0;
}

int RelayHdl(int mode, void *base, int fd) {
  if (mode==COC_WR) {
    if (alarmChannels[0]!='\0') ERR_MSG("alarm is still active");
    return COC_DWR;
  } else if (mode==COC_DWR) {
    ERR_I(AlarmReset());
    set = 0;
  }
  return 0;
  OnError: return -1;
}

int TmoHdl(int mode, void *base, int fd) {
  if (mode==COC_WR) {
    ERR_I(SerSetTmo(ser, msecTmo));
  }
  return 0;
  OnError: return -1;
}

int LogfileHdl(int mode, void *base, int fd) {
  char buf[256];
  if (mode==COC_WR) {
    logfileClose();
    str_copy(buf, logDir);
    str_append(buf, serverId);
    logfile=logfileInit(buf, logIt, use_stdout, logIt && use_stdout);
    CocDefStr(logfile, COC_RDONLY);
  }
  return 0;
}

int ExecuteRequest(void) {
  if (readTemp) ERR_I(ReadTemp());
  ERR_I(CocCallHandlers());
  while (settingsFlag==1) {
    settingsFlag=0;
    ERR_I(Settings());
  }
  statusBuf[0]='\0';
  return 0;
  OnError:
    return -1;
}

int MainBody(void) {
  int iret, tdif;

  logfileWrite(logMask);

  while (!quit) {
    tdif = per - mycMsecSince(tim0);
    if (tdif<0) { tim = mycNow(); break; } /* timeout */
    ERR_I(iret=CocHandleRequests(tdif, 0));
    tim=mycNow();
    if (iret==0) { /* timeout */
      break;
    }
    if (ser!=NULL) {
      ERR_I(ExecuteRequest());
    }
    logfileWrite(logMask);
  }
  tdif = mycMsecSince(tim0);
  if (tdif>60000 && cntError < 2) {
    logfileOut(LOG_MAIN ,"%d seconds lost\n", tdif/1000);
  }
  tim0 = mycMsecSince(0)/period*period;

  if (ser!=NULL) {
    ERR_I(PeriodicTask());
    ERR_I(ExecuteRequest());
  }

  return 0;
  OnError:
    if (0==strcmp(ErrMessage, "timeout")) {
      if (noResp==2) { /* this is the second time we have no response */
        per=1000; /* 1 sec */
        cryo.temp=0;
        samp.temp=0;
        noResp=3;
      } else if (noResp < 2) {
        logfileShowErr("no response");
        logfileOut(LOG_ALL ,"no response\n");
        noResp=2;
        per=100; /* try again soon */
      }
      return 0;
    }
    return -1;
}

int main(int argc, char *argv[]) {
  int i, iret;
  char *inistr;
  char buf[256], opt;

  signal(SIGPIPE, ignore_forever);

#define RD COC_RDONLY
#define RW COC_RDWR
#define RA COC_RDWRALL

  CocDefStruct(cryo, Testpoint);
  CocDefStruct(samp, Testpoint);
  
  CocDefPtr(tpoint, Testpoint);
  CocFltFld(Testpoint, temp, RD);
  CocFltFld(Testpoint, t1, RD);
  CocFltFld(Testpoint, t2, RD);

  CocDefStruct(sensA, SensorT);
  CocDefStruct(sensB, SensorT);
  CocDefStruct(sensC, SensorT);
  CocDefStruct(sensD, SensorT);
  
  CocDefPtr(sens, SensorT);
  CocFltFld(SensorT, t, RD);
  CocFltFld(SensorT, t0, RD);
  CocFltFld(SensorT, t1, RD);
  CocFltFld(SensorT, t2, RD);
  CocFltFld(SensorT, lim, RW);
  CocFltFld(SensorT, scale, RD);
  CocFltFld(SensorT, kink, RD);
  CocFltFld(SensorT, alarm, RW); CocHdl(AlarmHdl);
  CocIntFld(SensorT, readStat, RD);
  CocIntFld(SensorT, stat1, RD);
  CocIntFld(SensorT, stat2, RD);
  CocIntFld(SensorT, dispfld, RD);
  CocStrFld(SensorT, ch, RD);
  CocStrFld(SensorT, curve, RD);
  CocStrFld(SensorT, type, RD);

  CocDefStr(maxPower, RW); CocHdl(MaxPowerHdl);
  CocDefFlt(maxPowerFlt, RD);
  CocDefFlt(slope, RW);    CocHdl(MaxPowerHdl);
  CocDefFlt(prop,     RW); CocHdl(PidHdl);
  CocDefFlt(integ,    RW); CocHdl(PidHdl);
  CocDefFlt(deriv,    RW); CocHdl(PidHdl);
  CocDefFlt(set,  RW); CocHdl(SetHdl);
  CocDefFlt(set2, RW); CocHdl(SetHdl);
  CocDefFlt(ramp, RW); CocHdl(SetHdl);
  CocDefFlt(still, RW); CocHdl(StillHdl);
  CocDefFlt(power, RW); CocHdl(PowerHdl);
  CocDefFlt(power2, RD);
  CocDefFlt(mout, RW); CocHdl(MoutHdl);
  CocDefFlt(tLimit, RW); CocHdl(TLimitHdl);
  CocDefFlt(tMaxLimit, RD);
  CocDefFlt(maxCurrent, RD);
  CocDefFlt(smooth, RW);
  CocDefFlt(powFact, RW);
  CocDefFlt(resist, RD);
  CocDefFlt(htr, RD);
  CocDefFlt(setH, RD);
  CocDefFlt(full, RW);
  CocDefFlt(empty, RW);
  CocDefFlt(int2, RW);
  CocDefFlt(propUp, RW);
  CocDefFlt(propDown, RW);
  CocDefFlt(maxShift, RW);
  CocDefFlt(tm, RD);
  CocDefFlt(ts, RD);
  CocDefFlt(tr, RD);
  CocDefFlt(tx, RD);
  CocDefFlt(te, RD);
  CocDefFlt(tk, RD);
  CocDefFlt(aux, RD);
  CocDefFlt(he, RD);
  CocDefFlt(fbuf, RD);
  CocDefFlt(r1, RD);
  CocDefFlt(r2, RD);
  CocDefFlt(tShift, RW);
  CocDefFlt(linearPower, RW);
  CocDefFlt(maxpower2, RW);
  CocDefFlt(scanChan, RW);

  CocDefFlt(shiftUp, RW);
  CocDefFlt(shiftLow, RW);
  CocDefFlt(state, RW);

  CocDefPtr(clData, ClientData);
  CocStrFld(ClientData, cmd, RW); CocHdl(SendHdl);
  CocIntFld(ClientData, logstart, RA);
  CocStrFld(ClientData, logline, RA); CocHdl(LogHdl);

  CocAlias(send, clData.cmd);
  CocAlias(logstart, clData.logstart);
  CocAlias(logline, clData.logline);

  CocDefStr(device, RW); CocHdl(DeviceHdl);
  CocDefStr(buf1, RD);
  CocDefStr(buf2, RD);
  CocDefStr(buf3, RD);
  CocDefStr(buf4, RD);
  CocDefStr(head, RD);
  CocDefStr(chan, RD);
  CocDefStr(helium, RD);
  CocDefStr(heUnits, RD);
  CocDefStr(intype, RD);
  CocDefStr(error, RD); CocHdl(ErrorHdl);
  CocDefStr(status, RD); CocHdl(StatusHdl);
  CocDefStr(pid, RD); CocHdl(PidSumHdl);
  CocDefStr(config, RD);
  CocDefStr(swap, RW); CocHdl(SwapHdl);
  CocDefStr(dev, RD);
  CocDefStr(devHelp, RD); CocHdl(DevHelpHdl);
  CocDefStr(update, RW); CocHdl(UpdateHdl);
  CocDefStr(lscfg, RD);
  CocDefStr(ttp, RD);
  CocDefStr(alarmChannels, RD);
  CocDefStr(alarmHistory, RD);

  CocDefInt(cod1, RD);
  CocDefInt(cod2, RD);
  CocDefInt(out1, RD);
  CocDefInt(out2, RD);
  CocDefInt(num, RD);
  CocDefInt(key, RD);
  CocDefInt(maxfld, RD);
  CocDefInt(iAmp, RD);
  CocDefInt(iRange, RD);
  CocDefInt(jRange, RD);
  CocDefInt(remoteMode, RW); CocHdl(RemoteHdl);
  CocDefInt(local, RW); CocHdl(LocalHdl);
  CocDefInt(relay, RW); CocHdl(RelayHdl);
  CocDefInt(manual, RW); CocHdl(ManualHdl);
  CocDefInt(htrst, RD);
  CocDefInt(loop, RW);
  CocDefInt(twoloops, RW);
  CocDefInt(rdTim, RD);
  CocDefInt(tim0, RD);
  CocDefInt(ibuf, RD);
  CocDefInt(autoconfig, RW);
  CocDefInt(ramping, RD);

  CocDefInt(logMask, RW);
  CocDefInt(logPeriod, RW);
  CocDefInt(readTemp, RW);
  CocDefInt(controlMode, RW); CocHdl(ControlModeHdl);
  CocDefInt(busy, RD);
  CocDefInt(serialNo, RD);
  CocDefInt(quit, RW);
  CocDefInt(stickhtr, RW);
  CocDefInt(parallelhtr, RW);
  CocDefFlt(stickhtron, RW);
  CocDefFlt(stickhtroff, RW);
  CocDefInt(keepT, RW);
  CocDefInt(swRangeOn, RW);
  CocDefInt(doubleControl, RW);
  CocDefInt(unit, RW);

  CocDefStr(controlSensor, RW); CocHdl(SetHdl);

  CocDefStr(grapar, RA);
  CocDefArr(gradata, RD); CocHdl(GraHdl); grasize=CocSizePtr();

  CocDefStr(devname0, RW);
  CocDefStr(devname1, RW);
  CocAlias(tempX,cryo.temp);
  CocAlias(tempP,samp.temp);
  CocAlias(Ta,sensA.t);
  CocAlias(Tb,sensB.t);
  CocAlias(Tc,sensC.t);
  CocAlias(Td,sensD.t);
  CocAlias(P,power);
  CocAlias(tempC, set);
  CocAlias(int,integ);

  CocDefInt(msecTmo, RW); CocHdl(TmoHdl);
  CocDefStr(host, RW);
  CocDefInt(port, RD);
  CocDefInt(use_stdout, RW); CocHdl(LogfileHdl);
  CocDefStr(serverId, RW); CocHdl(LogfileHdl);
  CocDefInt(logIt, RW); CocHdl(LogfileHdl);
  CocDefStr(logDir, RW); CocHdl(LogfileHdl);
  CocDefStr(loggerDir, RW);
  CocDefStr(binDir, RW);

  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 ('a'==opt) {
        autoconfig=1;
        opt=' ';
      } else if ('n'==opt) {
        i++;
        str_copy(serverId, argv[i]);
      } else if ('b'==opt) {
        i++;
        str_copy(binDir, argv[i]);
      } else if ('d'==opt) {
        i++;
        str_copy(logDir, argv[i]);
      } else if ('m'==opt) {
        i++;
        str_copy(loggerDir, argv[i]);
      } else if ('t'==opt) {
        i++;
        msecTmo=atoi(argv[i]);
      } else if ('h'==opt) {
        i++;
        str_copy(host, argv[i]);
        str_copy(starthost, argv[i]);
      } else if ('p'==opt) {
        i++;
        port=atoi(argv[i]);
      } else if ('f'==opt) {
        i++;
        logPeriod=atoi(argv[i]);
      } else {
        if (NULL!=strchr(argv[i], '=')) {
          CocReadVars(argv[i], '\0');
        } else {
          inistr=str_read_file(argv[i]);
          if (inistr==NULL) {
            logfileOut(LOG_MAIN ,"?");
          } else {
            CocReadVars(inistr, '\0');
            FREE(inistr);
          }
        } 
      }
      if (opt!=' ') logfileOut(LOG_MAIN ,"-%c ", opt);
      logfileOut(LOG_MAIN ,"%s ", argv[i]);
    }
  }

  assert(logPeriod>0 && host[0]!='\0' && port!=0 && msecTmo>0);

  str_copy(statusBuf, "starting up");
  logfileStatusBuf(statusBuf);

  if (logDir[0] == '/') {
    str_copy(buf, logDir);
  } else {
    getcwd(buf, sizeof buf);
    str_append(buf, "/");
    ERR_I(str_append(buf, logDir));
  }
  ERR_I(str_append(buf, "tecs"));
  logfile=logfileInit(buf, logIt, use_stdout, logIt && use_stdout);
  
  if (loggerDir[0] != '\0') {
    if (loggerDir[0] == '/') {
      str_copy(buf, loggerDir);
    } else {
      getcwd(buf, sizeof buf);
      str_append(buf, "/");
      ERR_I(str_append(buf, loggerDir));
    }
    str_copy(loggerDir, buf);
    LoggerSetDir(loggerDir);
  }

  CocDefStr(logfile, RD);
  logfileOut(LOG_MAIN ,"\n");
  logfileWrite(logMask);

  ERR_I(CocInitServer(SetClientData, port));

  ser=SerOpen(host, msecTmo, IdleHdl);
  if (ser==NULL) {
    str_copy(statusBuf, "can not connect to serial port server or terminal server");
    logfileShowErr("error in SerOpen");
  }
  ERR_I(iret=CocHandleRequests(100, 0));
  tim0=mycMsecSince(0)/period*period;
  tim=mycNow();

  ERR_P(DataCreateSet(NULL, "Tm", &tm, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "Ts", &ts, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "Tr", &tr, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "Te", &te, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "Tk", &tk, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "He", &he, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "Aux", &aux, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "P", &power, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "power2", &power2, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "ScanChan", &scanChan, logPeriod, LOGLIFETIME, tim));
  DataUndef(0);
  ERR_P(DataCreateSet(NULL, "Set", &set, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "Set2", &set2, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "shiftUp", &shiftUp, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "shiftLow", &shiftLow, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "state", &state, logPeriod, LOGLIFETIME, tim));
  DataUndef(DATA_UNDEF);
  ERR_P(DataCreateSet(NULL, "tShift", &tShift, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "prop", &prop, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "int", &integ, logPeriod, LOGLIFETIME, tim));
  ERR_P(DataCreateSet(NULL, "deriv", &deriv, logPeriod, LOGLIFETIME, tim));
  
  remoteMode=2;
  local = 0;
  prop=50;
  integ=20;
  deriv=0;
  if (ser!=NULL) {
    LscCmd(ser, "MODE?>remoteMode");
    LoadFromLsc();
  }
  per=1; /* advance fast when initializing */
  cntError=0;
  while (!quit) {
    iret=MainBody();
    if (ser!=NULL && 0!=strcmp(host, ser->host)) {
      SerClose(ser);
      ser=NULL;
      noResp=1;
    }
    if (iret<0 || ser==NULL) {
      cntError++;
      ser=SerCheck(ser);
      if (0==strcmp(ErrMessage, "asynsrv error")) {
        if (ser!=NULL) SerClose(ser);
        ser=NULL;
        noResp=1;
      }
      if (cntError<4) {
        if (iret<0) {
          logfileShowErr("error in TecsServer/MainBody");
        } else {
          logfileShowErr("error in SerOpen");
        }
      } else if (cntError==4) {
        logfileOut(LOG_MAIN, "--- too many errors: skip messages ---\n");
      }
      if (ser==NULL) {
        CocHandleRequests(msecTmo, 0);
        if (strcmp(host, "start") == 0) {
          str_copy(host, starthost);
        }
        ser=SerOpen(host, msecTmo, IdleHdl);
        if (ser==NULL) {
          str_copy(statusBuf, "can not connect to serial port server or terminal server");
        } else {
          LscCmd(ser, "MODE?>remoteMode");
          LoadFromLsc();
        }
      }
    } else {
      if (cntError>0) {
        if (cntError>4) {
          cntError=4;
        }
        cntError--;
      }
    }
  }
  LogMinMax(0);
  logfileWrite(logMask);
  if (quit==1) {
    ERR_MSG("restart by quit command");
  } else {
    ERR_MSG("killed by quit command");
  }
  OnError:
    printf("TecsServer exited: %s\n", ErrMessage);
    logfileShowErr("exit TecsServer");
    if (ser!=NULL) SerClose(ser);
    CocCloseServer();
    logfileClose();
    return quit;
}
