 {
 Control driver and DIM & Web CGI script for AliPhosCool.
 [@Help]
 |Command list: StdIn "@cmd=arg" or "@cmd arg"
 |******************************************************
 | @Help          - This help.
 | @DebugFlags=n  - Set DebugFlags,1/2/4/8=!/:/>/< view
 | @AssignTag t=v - Assign value v to tag with name t.
 | @LoadParams    - Load control parameters from INI.
 | @SaveParams    - Save control parameters to INI.
 |******************************************************
 []
 }
program PHOS_COOL_CTRL;
const
 dfTrouble         = 1;       { DebugFlags - Trouble             }
 dfSuccess         = 2;       { DebugFlags - Success             }
 dfViewExp         = 4;       { DebugFlags - ViewExp             }
 dfViewImp         = 8;       { DebugFlags - ViewImp             }
 DimSrv            = '&DimSrv'; { DIM server device name         }
 updEmpty          = -1e300;  { Marker for empty update          }
 nComps            = 4;       { Number of compressors            }
 nPumps            = 2;       { Number of pumps                  }
 nHeats            = 3;       { Number of heats                  }
 maxPwo            = 4;       { Max PWO number                   }
 nPs               = 3;       { Number of Pressure sensors       }
 nTs               = 5;       { Number of Temperature sensors    }
 aiP1              = 0;       { P1 input index                   }
 aiP2              = 1;       { P2 input index                   }
 aiP3              = 2;       { P3 input index                   }
 aiT1              = 3;       { T1 input index                   }
 aiT2              = 4;       { T2 input index                   }
 aiT3              = 5;       { T3 input index                   }
 aiT4              = 6;       { T4 input index                   }
 aiT5              = 7;       { T5 input index                   }
 aiTCJ             = 8;       { TCJ input index                  }
 aiTP2             = 9;       { TP2 input index                  }
 aiHPR             = 0;       { Condensing  pressure index       }
 aiLPR             = 1;       { Evaporating pressure index       }
 aiPPR             = 2;       { Pump pressure index              }
 aiFAN             = 7;       { Fan control temperature index    }
 aiHEAT            = 3;       { Heat control temperature index   }
 aiTLIQ            = 3;       { Temperature of liquid            }
 wdtCompTHR        = 1;       { Watchdog on compressor THR       }
 wdtCompOIL        = 45000;   { Watchdog on compressor OIL       }
 wdtCompLPR        = 1;       { Watchdog on comp. low  pr. relay }
 wdtCompHPR        = 1;       { Watchdog on comp. high pr. relay }
 wdtCompP1MIN      = 1;       { Watchdog on comp. low  pressure  }
 wdtCompP1MAX      = 1;       { Watchdog on comp. high pressure  }
 wdtCompP2MIN      = 1;       { Watchdog on comp. low  pressure  }
 wdtCompP2MAX      = 1;       { Watchdog on comp. high pressure  }
 wdtPumpP3MIN      = 1;       { Watchdog on pump  low  pressure  }
 wdtPumpP3MAX      = 1;       { Watchdog on pump  high pressure  }
 wdtPumpTMIN       = 1;       { Watchdog on pump  low  temper-re }
 wdtPumpTMAX       = 1;       { Watchdog on pump  high temper-re }
 wdtPumpTHR        = 1;       { Watchdog on pump thermo relay    }
 wdtFreonLPR       = 1;       { Watchdog on freon low  pr. relay }
 wdtFreonHPR       = 1;       { Watchdog on freon high pr. relay }
 wdtLiqHLR         = 1;       { Watchdog on C6F14 lo level relay }
 wdtLiqLLR         = 1;       { Watchdog on C6F14 hi level relay }
 wdtHeatsT4MAX     = 1;       { Watchdog on high temperature of heater  }
 cm_Halt           = 0;       { Control mode: Halt,emergency OFF }
 cm_Hand           = 1;       { Control mode: Hand, i.e. manual  }
 cm_Stby           = 2;       { Control mode: Standby            }
 cm_Cool           = 3;       { Control mode: Cooling            }
 cm_Warm           = 4;       { Control mode: Warming            }
 cm_High           = 4;       { Control mode: high limit         }
 blk_VE1_LPR       = 0;       { Block VE1  by LPR                }
 blk_VE1_HPR       = 1;       { Block VE1  by LPR                }
 blk_PUMP_THR      = 2;       { Block PUMP by THR                }
 blk_PUMP_VE2      = 3;       { Block PUMP by VE2                }
 blk_PUMP_LLR      = 4;       { Block PUMP by LLR                }
 blk_PUMP_HLR      = 5;       { Block PUMP by HLR                }
 blk_PUMP_P3MIN    = 6;       { Block PUMP by PMIN               }
 blk_PUMP_P3MAX    = 7;       { Block PUMP by PMAX               }
 blk_PUMP_TMIN     = 8;       { Block PUMP by TMIN               }
 blk_PUMP_TMAX     = 9;       { Block PUMP by TMAX               }
 blk_COMP_THR      = 10;      { Block COMP by THR                }
 blk_COMP_OIL      = 11;      { Block COMP by OIL                }
 blk_COMP_LPR      = 12;      { Block COMP by LPR                }
 blk_COMP_HPR      = 13;      { Block COMP by HPR                }
 blk_COMP_P1MIN    = 14;      { Block COMP by P1MIN              }
 blk_COMP_P1MAX    = 15;      { Block COMP by P1MAX              }
 blk_COMP_P2MIN    = 16;      { Block COMP by P2MIN              }
 blk_COMP_P2MAX    = 17;      { Block COMP by P2MAX              }
 blk_COMP_VE1      = 18;      { Block COMP by VE1                }
 blk_COMP_PUMP     = 19;      { Block COMP by PUMP               }
 blk_HEAT_VE2      = 20;      { Block HEAT by VE2                }
 blk_HEAT_PUMP     = 21;      { Block HEAT by PUMP               }
 blk_HEAT_T4MAX    = 22;      { Block HEAT by T4MAX              }
var
 s                 : String;  { Temporary                        }
 i                 : Integer; { Temporary                        }
 b                 : Boolean; { Temporary                        }
 Ok                : Boolean; { Program initialization is Ok?    }
 errors            : Integer; { Program error counter            }
 errorcode         : Integer; { Error code for this device       }
 fixmaxavail       : Integer; { String manager leak control      }
 DebugFlags        : Integer; { Debug bit flags                  }
 StdIn_Line        : String;  { Temporary variable               }
 HostId            : String;  { Pid@HostName string              }
 devDimSrv         : Integer; { Dim server reference             }
 tagClick          : Integer; { Sensor click information         }
 tagNotify         : Integer; { Sensor click notification        }
 tagHostTime       : Integer; { Host time, masecnow              }
 tagHostInfo       : Integer; { Host information string          }
 tagP              : array[1..nPs] of Integer; {Pressure    tags }
 tagT              : array[1..nTs] of Integer; {Temperature tags }
 tagTP2            : Integer; { Temperature calculated by P2     }
 tagTCJ            : Integer; { Temperature of cold junction     }
 tagCompP1MIN      : Integer; { Min  pressure    bound           }
 tagCompP1MAX      : Integer; { Max  pressure    bound           }
 tagCompP2MIN      : Integer; { Min  pressure    bound           }
 tagCompP2MAX      : Integer; { Max  pressure    bound           }
 tagCompNBMIN      : Integer; { Min  number of compressors       }
 tagCompNBMAX      : Integer; { Max  number of compressors       }
 tagCompPOLLTM     : Integer; { Period to turn compressor on/off }
 tagCompDEADTM     : Integer; { Deadtime after compressor on/off }
 tagPumpP3MIN      : Integer; { Min  pressure    bound           }
 tagPumpP3MAX      : Integer; { Max  pressure    bound           }
 tagComp           : array[1..nComps] of record
  ENB              : Integer; { Enabled                          }
  POW              : Integer; { Power on/off                     }
  WTM              : Integer; { Work time                        }
  THR              : Integer; { Thermo relay                     }
  OIL              : Integer; { Oil level                        }
  LPR              : Integer; { Low  pressure                    }
  HPR              : Integer; { High pressure                    }
 end;
 tagPump           : array[1..nPumps] of record
  ENB              : Integer; { Enabled                          }
  POW              : Integer; { Power on/off                     }
  WTM              : Integer; { Work time                        }
  THR              : Integer; { Thermo relay                     }
 end;
 tagHeat           : array[1..nHeats] of record
  ENB              : Integer; { Enabled                          }
  POW              : Integer; { Power on/off                     }
  SSR              : Integer; { Solid state relay                }
  PER              : Integer; { Period                           }
  Q                : Integer; { Power, % of max                  }
  QMAX             : Integer; { Max power                        }
 end;
 tagFreon          : record
  ENB              : Integer; { Enabled                          }
  VE1              : Integer; { Solenoid vent                    }
  LPR              : Integer; { Low  pressure                    }
  HPR              : Integer; { High pressure                    }
 end;
 tagLiq            : record
  ENB              : Integer; { Enabled                          }
  VE2              : Integer; { Solenoid vent                    }
  HLR              : Integer; { High level relay                 }
  LLR              : Integer; { Low  level relay                 }
  TMIN             : Integer; { Liquid temperature low  level    }
  TMAX             : Integer; { Liquid temperature high level    }
  TSTAB            : Integer; { Liquid stabilization temperature }
  DELTA            : Integer; { On/Off temperature= STAB ± DELTA }
  NBSEN            : Integer; { Sensor for cooling speed control }
  DTMAX            : Integer; { Max dT for cooling speed control }
  TSENS            : Integer; { Tem-re for cooling speed control }
  TGOAL            : Integer; { Goal temperature                 }
  TAVER            : Integer; { Average temperature              }
 end;
 tagFan            : record
  ENB              : Integer; { Enabled                          }
  POW              : Integer; { Power on/off                     }
  WTM              : Integer; { Work time                        }
  TON              : Integer; { Turn-on temperature              }
 end;
 tagStBy,
 tagCool,
 tagWarm           : record
  TSTAB            : Integer; { Liquid stabilization temperature }
  DELTA            : Integer; { On/Off temperature= STAB ± DELTA }
  NBMIN            : Integer; { Min  number of compressors       }
  NBMAX            : Integer; { Max  number of compressors       }
  POLLTM           : Integer; { Period to turn compressor on/off }
  DEADTM           : Integer; { Deadtime after compressor on/off }
  NBSEN            : Integer; { Sensor for cooling speed control }
  DTMAX            : Integer; { Max dT for cooling speed control }
  Q                : array[1..nHeats] of Integer; { Heats Q      }
 end;
 tagDaqBugs        : Integer; { DAQ    error count               }
 tagHddBugs        : Integer; { DatSrv error count               }
 tagComBugs        : Integer; { ADAM   error count               }
 tagInterlock      : Integer; { Interlock relay                  }
 tagCmdCtrl        : Integer; { Command for manual/auto control  }
 tagCmdBlk         : Integer; { Command for emergency blocking   }
 tagBlkNum         : Integer; { Number of active blocks          }
 tagBlkOpt         : Integer; { Blocking options                 }
 tagCmdEcv         : Integer; { Command for ECV control program  }
 tagEcvIp          : Integer; { ECV IP address                   }
 tagLoadParams     : Integer; { Load control parameters          }
 tagSaveParams     : Integer; { Save control parameters          }
 tagHeatsT4max     : Integer; { Heat T4 high limit               }
 tagFsmFsm         : Integer; { Fsm control enable               }
 tagFsmDim         : Integer; { FSM control via DIM enable       }
 tagFsmSay         : Integer; { FSM speaking enable              }
 tagFsmState       : Integer; { Current FSM state                }
 tagPwoN           : array[0..maxPwo] of Integer; { PWO sensors  }
 tagFeeN           : array[0..maxPwo] of Integer; { FEE sensors  }
 tagPwoT           : array[0..maxPwo] of Integer; { PWO sensors  }
 tagFeeT           : array[0..maxPwo] of Integer; { FEE sensors  }
 CmdCtrl           : Integer; { Control command state            }
 updP              : array[1..nPs] of Real;    { Update          }
 updT              : array[1..nTs] of Real;    { Update          }
 updTP2            : Real;                     { Update          }
 updTCJ            : Real;                     { Update          }
 updCompP1MIN      : Real;    { Update                           }
 updCompP1MAX      : Real;    { Update                           }
 updCompP2MIN      : Real;    { Update                           }
 updCompP2MAX      : Real;    { Update                           }
 updCompNBMIN      : Real;    { Update                           }
 updCompNBMAX      : Real;    { Update                           }
 updCompPOLLTM     : Real;    { Update                           }
 updCompDEADTM     : Real;    { Update                           }
 updPumpP3MIN      : Real;    { Update                           }
 updPumpP3MAX      : Real;    { Update                           }
 updComp           : array[1..nComps] of record
  ENB              : Real;    { Update                           }
  WTM              : Real;    { Update                           }
  POW              : Real;    { Update                           }
  THR              : Real;    { Update                           }
  OIL              : Real;    { Update                           }
  LPR              : Real;    { Update                           }
  HPR              : Real;    { Update                           }
 end;
 updPump           : array[1..nPumps] of record
  ENB              : Real;    { Update                           }
  POW              : Real;    { Update                           }
  WTM              : Real;    { Update                           }
  THR              : Real;    { Update                           }
 end;
 updHeat           : array[1..nHeats] of record
  ENB              : Real;    { Update                           }
  POW              : Real;    { Update                           }
  SSR              : Real;    { Update                           }
  PER              : Real;    { Update                           }
  Q                : Real;    { Update                           }
  QMAX             : Real;    { Update                           }
 end;
 updFreon          : record
  ENB              : Real;    { Update                           }
  VE1              : Real;    { Update                           }
  LPR              : Real;    { Update                           }
  HPR              : Real;    { Update                           }
 end;
 updLiq            : record
  ENB              : Real;    { Update                           }
  VE2              : Real;    { Update                           }
  HLR              : Real;    { Update                           }
  LLR              : Real;    { Update                           }
  TMIN             : Real;    { Update                           }
  TMAX             : Real;    { Update                           }
  TSTAB            : Real;    { Update                           }
  DELTA            : Real;    { Update                           }
  NBSEN            : Real;    { Update                           }
  DTMAX            : Real;    { Update                           }
  TSENS            : Real;    { Update                           }
  TGOAL            : Real;    { Update                           }
  TAVER            : Real;    { Update                           }
 end;
 updFan            : record
  ENB              : Real;    { Update                           }
  POW              : Real;    { Update                           }
  WTM              : Real;    { Update                           }
  TON              : Real;    { Update                           }
 end;
 updStBy,
 updCool,
 updWarm           : record
  TSTAB            : Real;    { Update                           }
  DELTA            : Real;    { Update                           }
  NBMIN            : Real;    { Update                           }
  NBMAX            : Real;    { Update                           }
  POLLTM           : Real;    { Update                           }
  DEADTM           : Real;    { Update                           }
  NBSEN            : Real;    { Update                           }
  DTMAX            : Real;    { Update                           }
  Q                : array[1..nHeats] of Real; { Update          }
 end;
 updDaqBugs        : Real;    { Update                           }
 updHddBugs        : Real;    { Update                           }
 updComBugs        : Real;    { Update                           }
 updInterlock      : Real;    { Update                           }
 updCmdCtrl        : Real;    { Update                           }
 updCmdBlk         : Real;    { Update                           }
 updBlkNum         : Real;    { Update                           }
 updBlkOpt         : Real;    { Update                           }
 updHeatsT4max     : Real;    { Update                           }
 HeatBase          : array[1..nHeats] of Real; { Heat base time  }
 CompWtm           : array[1..nComps] of Real; { Comp watchdog   }
 PumpWtm           : array[1..nPumps] of Real; { Pump watchdog   }
 FanWtm            : Real;                     { Fan  watchdog   }
 PumpOrder         : array[1..nPumps] of Integer; { Enumeration  }
 CompOrder         : array[1..nComps] of Integer; { Enumeration  }
 PumpCount         : Integer; { Number of working pumps          }
 CompCount         : Integer; { Number of working compressors    }
 CompTimer         : Real;    { Compressors switch time          }
 AverTimer         : Real;    { Average temperature timer        }
 wdtComp           : array[1..nComps] of record
  THR              : Real;    { Thermo        relay              }
  OIL              : Real;    { Oil level     relay              }
  LPR              : Real;    { Low  pressure relay              }
  HPR              : Real;    { High pressure relay              }
  P1MIN            : Real;    { Low  pressure                    }
  P1MAX            : Real;    { High pressure                    }
  P2MIN            : Real;    { Low  pressure                    }
  P2MAX            : Real;    { High pressure                    }
 end;
 wdtPump           : array[1..nPumps] of record
  THR              : Real;    { Thermo relay                     }
  P3MIN            : Real;    { Pump Low  pressure               }
  P3MAX            : Real;    { Pump High pressure               }
  TMIN             : Real;    { Pump Low  temperature            }
  TMAX             : Real;    { Pump High temperature            }
 end;
 wdtFreon          : record
  LPR              : Real; { Low  pressure                    }
  HPR              : Real; { High pressure                    }
 end;
 wdtHeat           : array[1..nHeats] of record
  T4MAX            : Real; { High temperature               }
 end;
 wdtLiq            : record
  HLR              : Real; { High level relay                 }
  LLR              : Real; { Low  level relay                 }
 end;
 HostTimeTick      : Real; { To update host time & date       }
 {
 Report on trouble.
 }
 procedure Trouble(msg:String);
 var b:Boolean;
 begin
  if iand(DebugFlags,dfTrouble)<>0 then
  if length(msg)>0 then writeln(devname+' ! '+msg);
  if runcount=1 then errors:=errors+1 else b:=fixerror(errorcode);
 end;
 {
 Report on success.
 }
 procedure Success(msg:String);
 begin
  if iand(DebugFlags,dfSuccess)<>0 then
  if length(msg)>0 then writeln(devname+' : '+msg);
 end;
 {
 Report on data import to program.
 }
 procedure ViewImp(msg:String);
 begin
  if iand(DebugFlags,dfViewImp)<>0 then
  if length(msg)>0 then writeln(devname+' < '+msg);
 end;
 {
 Report on data export from program.
 }
 procedure ViewExp(msg:String);
 begin
  if iand(DebugFlags,dfViewExp)<>0 then
  if length(msg)>0 then writeln(devname+' > '+msg);
 end;
 {
 Check I/O status.
 }
 function IoError:Boolean;
 begin
  IoError:=false;
  if ioresult<>0 then begin
   Trouble('I/O error.');
   IoError:=true;
  end;
 end;
 {
 Read string line from standard input.
 }
 function StdIn_Readln(var Data:string):boolean;
 begin
  Data:='';
  if not IoError then
  if not Eof then Readln(Data);
  if IoError then Data:='';
  StdIn_Readln:=Length(Data)>0;
 end;
 {
 Initialize and check tag
 }      
 procedure InitTag(var tag:integer; name:string; typ:integer);
 begin
  tag:=findtag(name);
  if (typ>0) and (typetag(tag)<>typ)
  then Trouble('Could not init tag: '+name);
 end;
 {
 Show/hide device console.
 }
 procedure OpenConsole(Mode:Integer);
 var b:Boolean;
  procedure ShowWin(WinName:String);
  begin
   b:=WinShow(WinName);
   b:=WinDraw(WinName+'|top=317|left=0|width=600|height=317');
   if Mode=1 then b:=WinSelect(WinName) else b:=WinHide(WinName);
  end;
 begin
  if Mode>0 then ShowWin(ParamStr('Console '+DevName))
 end;
 {
 Show help in device console and echo to Main console if AllowEcho.
 Help text should be placed in program comment is [@Help] section.
 First symbol of help block should be | and will be ignored.
 }
 procedure ShowHelp(AllowEcho:Boolean);
 var i,p,sect:Integer; b:Boolean;
 begin
  sect:=ReadIniSection(text_New,12,DaqFileRef(ReadIni('ProgramSource'),'.pas'),'[@Help]');
  for i:=0 to text_NumLn(sect)-1 do begin
   if Copy(text_GetLn(sect,i),1,1)='|' then p:=2 else p:=1;
   if AllowEcho then b:=echo(devname+' : '+Copy(text_GetLn(sect,i),p));
   Success(Copy(text_GetLn(sect,i),p));
  end;
  b:=text_Free(sect);
  if AllowEcho then b:=WinSelect(ParamStr('MainConsole'));
 end;
 {
 Get string like 2006.09.21-00:12:30
 }
 function GetDateTime(ms:Real):String;
 var s:String;
 begin
  s:='';
  s:=Str(ms2sec(ms))+s;   while Length(s)<2  do s:='0'+s; s:=':'+s;
  s:=Str(ms2min(ms))+s;   while Length(s)<5  do s:='0'+s; s:=':'+s;
  s:=Str(ms2hour(ms))+s;  while Length(s)<8  do s:='0'+s; s:='-'+s;
  s:=Str(ms2day(ms))+s;   while Length(s)<11 do s:='0'+s; s:='.'+s;
  s:=Str(ms2month(ms))+s; while Length(s)<14 do s:='0'+s; s:='.'+s;
  s:=Str(ms2year(ms))+s;  while Length(s)<19 do s:='0'+s;
  GetDateTime:=s;
  s:='';
 end;
 {
 Replace all "a" to "b" in string "s".
 Flag = 1 - Replace All, 2 - Not Case sensitive
 }
 function StrReplace(s,a,b:String; Flags:Integer):String;
 var p:Integer;
 begin
  if iAnd(Flags,1)=0
  then p:=Pos(a,s)
  else p:=Pos(UpCaseStr(a),UpCaseStr(s));
  if p=0 then StrReplace:=s else begin
   if iAnd(Flags,2)=0
   then StrReplace:=Copy(s,1,p-1)+b+Copy(s,p+Length(a))
   else StrReplace:=Copy(s,1,p-1)+b+StrReplace(Copy(s,p+Length(a)),a,b,Flags);
  end;
 end;
 {
 Speak, i.e. send message to speech server.
 }
 procedure Speak(msg:String);
 var b:Boolean; ref:Integer;
 begin
  msg:=Trim(msg);
  if Length(msg)>0 then begin
   ref:=RefFind('Device &SpeakSrv');
   if ref<>0 then b:=DevSend(ref,'@speak='+msg+CRLF)>0;
  end;
 end;
 {
 Send a message to DIM server.
 }
 procedure DIM_Send(msg:String);
 begin
  if Length(msg)>0 then
  if DevSend(devDimSrv,msg)=0
  then Trouble('Could not send message to '+DimSrv);
 end;
 {
 Send message to DIM server to update tag.
 Send also new tag value if data specified.
 }
 procedure DIM_UpdateTag(tag:Integer; data:String);
 begin
  if TypeTag(tag)>0 then begin
   if Length(data)=0
   then DIM_Send('##'+Str(tag)+CRLF)
   else DIM_Send('##'+Str(tag)+'='+mime_encode(data)+CRLF);
  end;
 end;
 {
 Update tag with new value and with range checking.
 }
 procedure UpdateTag(tag:Integer; newValue:String; min,max:Real);
 var rValue:Real;
 begin
  rValue:=0;
  if TypeTag(tag)=1 then begin
   rValue:=rVal(newValue);
   if rValue<min then rValue:=_Nan;
   if rValue>max then rValue:=_Nan;
   if not IsNan(rValue) then b:=iSetTag(tag,Round(rValue));
  end else
  if TypeTag(tag)=2 then begin
   rValue:=rVal(newValue);
   if rValue<min then rValue:=_Nan;
   if rValue>max then rValue:=_Nan;
   if not IsNan(rValue) then b:=rSetTag(tag,rValue);
  end else
  if TypeTag(tag)=3 then begin
   b:=sSetTag(tag,newValue);
  end;
 end;
 {
 Update Analog Output n: put (t,y) event.
 }
 procedure UpdateAo(n:Integer; t,y:Real);
 var b:Boolean;
 begin
  if RefAo(n)<>0 then b:=putao(n,t,y);
 end;
 {
 Check if refresh needed.
 }
 function ShouldRefresh(var upd:Real; val:Real):Integer;
 begin
  ShouldRefresh:=0;
  if upd<>val then begin
   ShouldRefresh:=1;
   upd:=val;
  end;
 end;
 {
 Thread safe function to get last point Y of curve.
 }
 function crvGetLastY(ref:Integer):Real;
 var b:Boolean;
 begin
  b:=CrvLock(ref);
  crvGetLastY:=crvY(ref,crvLen(ref));
  b:=CrvUnlock(ref);
 end;
 {
 Inverted calibration
 }
 function invCalibr(n:Integer; y,z,a,b:Real):Real;
 const MaxIter=128;
 var i,k,ia,ib,it:Integer; t:Real;
 begin
  k:=0;
  if a>b then begin t:=a; a:=b; b:=t; end;
  ia:=Sign(Calibr(n,a,z)-y); k:=k+1;
  if ia=0 then t:=a else begin
   ib:=Sign(Calibr(n,b,z)-y); k:=k+1;
   if ib=0 then t:=b else begin
    i:=0;
    while i<MaxIter do begin
     t:=a+(b-a)*0.5;
     it:=Sign(Calibr(n,t,z)-y); k:=k+1;
     if t=a then i:=MaxIter else
     if t=b then i:=MaxIter else
     if it=0 then i:=MaxIter else
     if it*ia<0 then begin b:=t; ib:=it; end else
     if it*ib<0 then begin a:=t; ia:=it; end else begin t:=_NaN; i:=MaxIter; end;
     i:=i+1;
    end;
   end;
  end;
  invCalibr:=t;
 end;
 {
 Readout DIO state, i.e. I7053 data
 }
 procedure DioReadout;
 begin
  {
  Readout compressor state
  }
  b:=iSetTag(tagComp[1].THR, Round(DiWord(0,1)));
  b:=iSetTag(tagComp[1].OIL, Round(DiWord(1,1)));
  b:=iSetTag(tagComp[1].LPR, Round(DiWord(2,1)));
  b:=iSetTag(tagComp[1].HPR, Round(DiWord(3,1)));
  b:=iSetTag(tagComp[2].THR, Round(DiWord(4,1)));
  b:=iSetTag(tagComp[2].OIL, Round(DiWord(5,1)));
  b:=iSetTag(tagComp[2].LPR, Round(DiWord(6,1)));
  b:=iSetTag(tagComp[2].HPR, Round(DiWord(7,1)));
  b:=iSetTag(tagComp[3].THR, Round(DiWord(8,1)));
  b:=iSetTag(tagComp[3].OIL, Round(DiWord(9,1)));
  b:=iSetTag(tagComp[3].LPR, Round(DiWord(10,1)));
  b:=iSetTag(tagComp[3].HPR, Round(DiWord(11,1)));
  b:=iSetTag(tagComp[4].THR, Round(DiWord(12,1)));
  b:=iSetTag(tagComp[4].OIL, Round(DiWord(13,1)));
  b:=iSetTag(tagComp[4].LPR, Round(DiWord(14,1)));
  b:=iSetTag(tagComp[4].HPR, Round(DiWord(15,1)));
  {
  Readout Freon,Pump,Liq state
  }
  b:=iSetTag(tagFreon.LPR,   Round(DiWord(16+0,1)));
  b:=iSetTag(tagFreon.HPR,   Round(DiWord(16+1,1)));
  b:=iSetTag(tagPump[1].THR, Round(DiWord(16+2,1)));
  b:=iSetTag(tagPump[2].THR, Round(DiWord(16+3,1)));
  b:=iSetTag(tagLiq.HLR,     Round(DiWord(16+4,1)));
  b:=iSetTag(tagLiq.LLR,     Round(DiWord(16+5,1)));
 end;
 {
 DIO control, i.e. calculate bits of 7043 control word
 }
 procedure DioControl;
 var b:Boolean; cw:Integer;
 begin
  cw:=0;
  cw:=iOr(cw, iShift(Ord(iGetTag(tagComp[1].POW)), 0));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagComp[2].POW)), 1));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagComp[3].POW)), 2));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagComp[4].POW)), 3));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagPump[1].POW)), 4));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagPump[2].POW)), 5));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagFreon.VE1)),   6));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagLiq.VE2)),     7));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagFan.POW)),     8));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagHeat[1].POW)), 9));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagHeat[2].POW)), 10));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagHeat[3].POW)), 11));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagHeat[1].SSR)), 12));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagHeat[2].SSR)), 13));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagHeat[3].SSR)), 14));
  cw:=iOr(cw, iShift(Ord(iGetTag(tagInterlock)),   15));
  b:=PutDo(0,time,cw);
 end;
 {
 Calculate pump order with cyclic shift every h hours
 }
 procedure CalcPumpOrder(h:Real);
 var i,cycles:Integer;
 begin
  cycles:=Round(secnow/(h*3600));
  for i:=1 to nPumps do PumpOrder[i]:=1+((cycles+i) mod (nPumps));
 end;
 {
 Calculate compressor order with cyclic shift every h hours
 }
 procedure CalcCompOrder(h:Real);
 var i,cycles:Integer;
 begin
  cycles:=Round(secnow/(h*3600));
  for i:=1 to nComps do CompOrder[i]:=1+((cycles+i) mod (nComps));
 end;
 {
 Calculate pump states.
 PumpCount pumps should be ON.
 }
 procedure CalcPumpState(PumpCount:Integer);
 var i,j,n:Integer;
 begin
  n:=0;
  for i:=1 to nPumps do begin
   j:=PumpOrder[i];
   if n<PumpCount then begin
    if iGetTag(tagPump[j].ENB)<>0 then begin
     b:=iSetTag(tagPump[j].POW,1);
     n:=n+1;
    end else begin
     b:=iSetTag(tagPump[j].POW,0);
    end;
   end else begin
    b:=iSetTag(tagPump[j].POW,0);
   end;
  end;
 end;
 {
 Calculate compressor states.
 CompCount compressors should be ON.
 }
 procedure CalcCompState(CompCount:Integer);
 var i,j,n:Integer;
 begin
  n:=0;
  for i:=1 to nComps do begin
   j:=CompOrder[i];
   if n<CompCount then begin
    if iGetTag(tagComp[j].ENB)<>0 then begin
     b:=iSetTag(tagComp[j].POW,1);
     n:=n+1;
    end else begin
     b:=iSetTag(tagComp[j].POW,0);
    end;
   end else begin
    b:=iSetTag(tagComp[j].POW,0);
   end;
  end;
 end;
 {
 Calculate compressor count delta:
 +1 - one compressor should be turned on
 -1 - one compressor should be turned off
  0 - change nothing
 }
 function CalcCompDelta(T,TSTAB,DELTA:Real):Integer;
 var n:Integer;
 begin
  if IsNan(T)      then n:=0  else
  if IsNan(TSTAB)  then n:=0  else
  if IsNan(DELTA)  then n:=0  else
  if T<TSTAB-DELTA then n:=-1 else
  if T>TSTAB+DELTA then n:=+1 else n:=0;
  CalcCompDelta:=n;
 end;
 {
 Calculate time since tag switch on
 }
 procedure CalcWdtTime(tag:Integer; var wdt:Real);
 begin
  if iGetTag(tag)<>0 then begin
   if wdt<=0 then wdt:=msecnow;
  end else begin
   wdt:=0;
  end;
 end;
 {
 Calculate time since tag switch on
 }
 function GetWdtTime(wdt:Real):Real;
 begin
  if wdt>0 then GetWdtTime:=msecnow-wdt else GetWdtTime:=0;
 end;
 {
 ------------------------------------------------
 Device control, i.e. compressors, pumps etc
 ------------------------------------------------
 }
 procedure DevControl;
 const MaxPeriods = 10000;
 var b:Boolean; i,n,Hit,nBlocks,CtrlCmd:Integer;
     r,tp,Period,LPR,HPR,PPR,TLIQ,T4,Qi,Ts,Tg,dT,ta,tb:Real;
  procedure CalcWorkTime(tagPOW,tagWTM:Integer; var WTM:Real);
  var t:Real;
  begin
   t:=msecnow;
   if iGetTag(tagPOW)<>0 then begin
    if IsNan(rGetTag(tagWTM)) then b:=rSetTag(tagWTM,0);
    if IsInf(rGetTag(tagWTM)) then b:=rSetTag(tagWTM,0);
    if WTM=0 then WTM:=t;
    if t-WTM>1000 then begin
     b:=rSetTag(tagWTM,rGetTag(tagWTM)+(t-WTM)/3600000);
     WTM:=t;
    end;
   end else begin
    if WTM>0 then begin
     b:=rSetTag(tagWTM,rGetTag(tagWTM)+(t-WTM)/3600000);
     WTM:=0;
    end;
   end;
  end;
  procedure Warn(msg:String);
  begin
   nBlocks:=nBlocks+1;
   writeln(devname+' ! '+msg);
  end;
  function CompPollTime:Real;
  begin
   CompPollTime:=Max(10,1000*rGetTag(tagCompPOLLTM));
  end;
  function CompDeadTime:Real;
  begin
   CompDeadTime:=Max(10,1000*rGetTag(tagCompDEADTM));
  end;
 begin
  {
  Update HOST_TIME
  }
  if msecnow-HostTimeTick>=1000 then begin
   b:=sSetTag(tagHostInfo,GetDateTime(msecnow)+', '+HostId);
   b:=rSetTag(tagHostTime,msecnow);
   DIM_UpdateTag(tagHostTime,'');
   HostTimeTick:=msecnow;
  end;
  {
  Extract P,T tags from corresponded curves
  }
  b:=rSetTag(tagP[1],getai_yn(aiP1));
  b:=rSetTag(tagP[2],getai_yn(aiP2));
  b:=rSetTag(tagP[3],getai_yn(aiP3));
  b:=rSetTag(tagT[1],getai_yn(aiT1));
  b:=rSetTag(tagT[2],getai_yn(aiT2));
  b:=rSetTag(tagT[3],getai_yn(aiT3));
  b:=rSetTag(tagT[4],getai_yn(aiT4));
  b:=rSetTag(tagT[5],getai_yn(aiT5));
  b:=rSetTag(tagTCJ,getai_yn(aiTCJ));
  {
  Calculate evaporating pressure LPR and condensing HPR
  Note that LPR and HPR is like P2 and P1, but with smoothing
  }
  LPR:=getai(aiLPR,getai_xn(aiLPR));
  HPR:=getai(aiHPR,getai_xn(aiHPR));
  PPR:=getai(aiPPR,getai_xn(aiPPR));
  TLIQ:=getai(aiTLIQ,getai_xn(aiTLIQ));
  T4:=getai(aiT4,getai_xn(aiT4));
  {
  Calculate TP2 by P2
  }
  b:=rSetTag(tagTP2,invCalibr(0,rGetTag(tagP[2]),0,-45,45));
  {
  Update DIM services
  }
  if ShouldRefresh(updP[1],getai_xn(aiP1))>0 then DIM_UpdateTag(tagP[1],'');
  if ShouldRefresh(updP[2],getai_xn(aiP2))>0 then begin
   DIM_UpdateTag(tagP[2],'');
   UpdateAo(0,getai_xn(aiP2),rGetTag(tagTP2));
  end;
  if ShouldRefresh(updP[3],getai_xn(aiP3))>0 then DIM_UpdateTag(tagP[3],'');
  if ShouldRefresh(updT[1],getai_xn(aiT1))>0 then DIM_UpdateTag(tagT[1],'');
  if ShouldRefresh(updT[2],getai_xn(aiT2))>0 then DIM_UpdateTag(tagT[2],'');
  if ShouldRefresh(updT[3],getai_xn(aiT3))>0 then DIM_UpdateTag(tagT[3],'');
  if ShouldRefresh(updT[4],getai_xn(aiT4))>0 then DIM_UpdateTag(tagT[4],'');
  if ShouldRefresh(updT[5],getai_xn(aiT5))>0 then DIM_UpdateTag(tagT[5],'');
  if ShouldRefresh(updTCJ,getai_xn(aiTCJ))>0 then DIM_UpdateTag(tagTCJ,'');
  if ShouldRefresh(updTP2,getai_xn(aiP2))>0  then DIM_UpdateTag(tagTP2,'');
  if ShouldRefresh(updCompP1MIN,rGetTag(tagCompP1MIN))+
     ShouldRefresh(updCompP1MAX,rGetTag(tagCompP1MAX))+
     ShouldRefresh(updCompP2MIN,rGetTag(tagCompP2MIN))+
     ShouldRefresh(updCompP2MAX,rGetTag(tagCompP2MAX))+
     ShouldRefresh(updCompNBMIN,iGetTag(tagCompNBMIN))+
     ShouldRefresh(updCompNBMAX,iGetTag(tagCompNBMAX))+
     ShouldRefresh(updCompPOLLTM,rGetTag(tagCompPOLLTM))+
     ShouldRefresh(updCompDEADTM,rGetTag(tagCompDEADTM))>0
  then DIM_UpdateTag(tagCompP1MIN,'');
  if ShouldRefresh(updPumpP3MIN,rGetTag(tagPumpP3MIN))+
     ShouldRefresh(updPumpP3MAX,rGetTag(tagPumpP3MAX))>0
  then DIM_UpdateTag(tagPumpP3MIN,'');
  for i:=1 to nComps do
  if ShouldRefresh(updComp[i].ENB,iGetTag(tagComp[i].ENB))+
     ShouldRefresh(updComp[i].POW,iGetTag(tagComp[i].POW))+
     ShouldRefresh(updComp[i].WTM,rGetTag(tagComp[i].WTM))+
     ShouldRefresh(updComp[i].THR,iGetTag(tagComp[i].THR))+
     ShouldRefresh(updComp[i].OIL,iGetTag(tagComp[i].OIL))+
     ShouldRefresh(updComp[i].LPR,iGetTag(tagComp[i].LPR))+
     ShouldRefresh(updComp[i].HPR,iGetTag(tagComp[i].HPR))>0
  then DIM_UpdateTag(tagComp[i].ENB,'');
  for i:=1 to nPumps do
  if ShouldRefresh(updPump[i].ENB,iGetTag(tagPump[i].ENB))+
     ShouldRefresh(updPump[i].POW,iGetTag(tagPump[i].POW))+
     ShouldRefresh(updPump[i].WTM,rGetTag(tagPump[i].WTM))+
     ShouldRefresh(updPump[i].THR,iGetTag(tagPump[i].THR))>0
  then DIM_UpdateTag(tagPump[i].ENB,'');
  for i:=1 to nHeats do
  if ShouldRefresh(updHeat[i].ENB,iGetTag(tagHeat[i].ENB))+
     ShouldRefresh(updHeat[i].POW,iGetTag(tagHeat[i].POW))+
     ShouldRefresh(updHeat[i].SSR,iGetTag(tagHeat[i].SSR))+
     ShouldRefresh(updHeat[i].PER,iGetTag(tagHeat[i].PER))+
     ShouldRefresh(updHeat[i].Q,  rGetTag(tagHeat[i].Q))+
     ShouldRefresh(updHeat[i].QMAX,rGetTag(tagHeat[i].QMAX))>0
  then DIM_UpdateTag(tagHeat[i].ENB,'');
  if ShouldRefresh(updFreon.ENB,iGetTag(tagFreon.ENB))+
     ShouldRefresh(updFreon.VE1,iGetTag(tagFreon.VE1))+
     ShouldRefresh(updFreon.LPR,iGetTag(tagFreon.LPR))+
     ShouldRefresh(updFreon.HPR,iGetTag(tagFreon.HPR))>0
  then DIM_UpdateTag(tagFreon.ENB,'');
  if ShouldRefresh(updLiq.ENB,iGetTag(tagLiq.ENB))+
     ShouldRefresh(updLiq.VE2,iGetTag(tagLiq.VE2))+
     ShouldRefresh(updLiq.HLR,iGetTag(tagLiq.HLR))+
     ShouldRefresh(updLiq.LLR,iGetTag(tagLiq.LLR))+
     ShouldRefresh(updLiq.TMIN,rGetTag(tagLiq.TMIN))+
     ShouldRefresh(updLiq.TMAX,rGetTag(tagLiq.TMAX))+
     ShouldRefresh(updLiq.TSTAB,rGetTag(tagLiq.TSTAB))+
     ShouldRefresh(updLiq.DELTA,rGetTag(tagLiq.DELTA))+
     ShouldRefresh(updLiq.NBSEN,iGetTag(tagLiq.NBSEN))+
     ShouldRefresh(updLiq.DTMAX,rGetTag(tagLiq.DTMAX))>0
  then DIM_UpdateTag(tagLiq.ENB,'');
  if ShouldRefresh(updLiq.TSENS,rGetTag(tagLiq.TSENS))+
     ShouldRefresh(updLiq.TGOAL,rGetTag(tagLiq.TGOAL))+
     ShouldRefresh(updLiq.TAVER,rGetTag(tagLiq.TAVER))>0
  then DIM_UpdateTag(tagLiq.TSENS,'');
  if ShouldRefresh(updFan.ENB,iGetTag(tagFan.ENB))+
     ShouldRefresh(updFan.POW,iGetTag(tagFan.POW))+
     ShouldRefresh(updFan.WTM,rGetTag(tagFan.WTM))+
     ShouldRefresh(updFan.TON,rGetTag(tagFan.TON))>0
  then DIM_UpdateTag(tagFan.ENB,'');
  r:=GetErrCount(-2);
  if ShouldRefresh(updDaqBugs,r)>0 then DIM_UpdateTag(tagDaqBugs,Dump(r));
  r:=0; for i:=100 to 104 do r:=r+GetErrCount(256+i);
  if ShouldRefresh(updComBugs,r)>0 then DIM_UpdateTag(tagComBugs,Dump(r));
  if ShouldRefresh(updHddBugs,rGetTag(tagHddBugs))>0 then DIM_UpdateTag(tagHddBugs,'');
  if ShouldRefresh(updInterlock,iGetTag(tagInterlock))>0 then DIM_UpdateTag(tagInterlock,'');
  if ShouldRefresh(updCmdCtrl,iGetTag(tagCmdCtrl))>0 then DIM_UpdateTag(tagCmdCtrl,'');
  if ShouldRefresh(updCmdBlk,iGetTag(tagCmdBlk))>0 then DIM_UpdateTag(tagCmdBlk,'');
  if ShouldRefresh(updBlkNum,iGetTag(tagBlkNum))>0 then DIM_UpdateTag(tagBlkNum,'');
  if ShouldRefresh(updBlkOpt,iGetTag(tagBlkOpt))>0 then DIM_UpdateTag(tagBlkOpt,'');
  if ShouldRefresh(updHeatsT4max,rGetTag(tagHeatsT4max))>0
  then DIM_UpdateTag(tagHeatsT4max,'');
  if ShouldRefresh(updStBy.TSTAB,rGetTag(tagStBy.TSTAB))+
     ShouldRefresh(updStBy.DELTA,rGetTag(tagStBy.DELTA))+
     ShouldRefresh(updStBy.NBMIN,iGetTag(tagStBy.NBMIN))+
     ShouldRefresh(updStBy.NBMAX,iGetTag(tagStBy.NBMAX))+
     ShouldRefresh(updStBy.POLLTM,rGetTag(tagStBy.POLLTM))+
     ShouldRefresh(updStBy.DEADTM,rGetTag(tagStBy.DEADTM))+
     ShouldRefresh(updStBy.NBSEN,iGetTag(tagStBy.NBSEN))+
     ShouldRefresh(updStBy.DTMAX,rGetTag(tagStBy.DTMAX))+
     ShouldRefresh(updStBy.Q[1],rGetTag(tagStBy.Q[1]))+
     ShouldRefresh(updStBy.Q[2],rGetTag(tagStBy.Q[2]))+
     ShouldRefresh(updStBy.Q[3],rGetTag(tagStBy.Q[3]))>0
  then DIM_UpdateTag(tagStBy.TSTAB,'');
  if ShouldRefresh(updCool.TSTAB,rGetTag(tagCool.TSTAB))+
     ShouldRefresh(updCool.DELTA,rGetTag(tagCool.DELTA))+
     ShouldRefresh(updCool.NBMIN,iGetTag(tagCool.NBMIN))+
     ShouldRefresh(updCool.NBMAX,iGetTag(tagCool.NBMAX))+
     ShouldRefresh(updCool.POLLTM,rGetTag(tagCool.POLLTM))+
     ShouldRefresh(updCool.DEADTM,rGetTag(tagCool.DEADTM))+
     ShouldRefresh(updCool.NBSEN,iGetTag(tagCool.NBSEN))+
     ShouldRefresh(updCool.DTMAX,rGetTag(tagCool.DTMAX))+
     ShouldRefresh(updCool.Q[1],rGetTag(tagCool.Q[1]))+
     ShouldRefresh(updCool.Q[2],rGetTag(tagCool.Q[2]))+
     ShouldRefresh(updCool.Q[3],rGetTag(tagCool.Q[3]))>0
  then DIM_UpdateTag(tagCool.TSTAB,'');
  if ShouldRefresh(updWarm.TSTAB,rGetTag(tagWarm.TSTAB))+
     ShouldRefresh(updWarm.DELTA,rGetTag(tagWarm.DELTA))+
     ShouldRefresh(updWarm.NBMIN,iGetTag(tagWarm.NBMIN))+
     ShouldRefresh(updWarm.NBMAX,iGetTag(tagWarm.NBMAX))+
     ShouldRefresh(updWarm.POLLTM,rGetTag(tagWarm.POLLTM))+
     ShouldRefresh(updWarm.DEADTM,rGetTag(tagWarm.DEADTM))+
     ShouldRefresh(updWarm.NBSEN,iGetTag(tagWarm.NBSEN))+
     ShouldRefresh(updWarm.DTMAX,rGetTag(tagWarm.DTMAX))+
     ShouldRefresh(updWarm.Q[1],rGetTag(tagWarm.Q[1]))+
     ShouldRefresh(updWarm.Q[2],rGetTag(tagWarm.Q[2]))+
     ShouldRefresh(updWarm.Q[3],rGetTag(tagWarm.Q[3]))>0
  then DIM_UpdateTag(tagWarm.TSTAB,'');
  {
  Power off disabled channels
  }
  if iGetTag(tagFan.ENB)=0 then b:=iSetTag(tagFan.POW,0);
  if iGetTag(tagLiq.ENB)=0 then b:=iSetTag(tagLiq.VE2,0);
  if iGetTag(tagFreon.ENB)=0 then b:=iSetTag(tagFreon.VE1,0);
  for i:=1 to nComps do if iGetTag(tagComp[i].ENB)=0 then b:=iSetTag(tagComp[i].POW,0);
  for i:=1 to nPumps do if iGetTag(tagPump[i].ENB)=0 then b:=iSetTag(tagPump[i].POW,0);
  for i:=1 to nHeats do if iGetTag(tagHeat[i].ENB)=0 then b:=iSetTag(tagHeat[i].POW,0);
  {
  Heater meandr
  }
  for i:=1 to nHeats do begin
   Hit:=0;
   if iGetTag(tagHeat[i].ENB)<>0 then
   if iGetTag(tagHeat[i].POW)<>0 then begin
    Period:=Max(1,iGetTag(tagHeat[i].PER));
    if msecnow>HeatBase[i]+MaxPeriods*Period then HeatBase[i]:=msecnow;
    tp:=(msecnow-HeatBase[i])/Max(1,iGetTag(tagHeat[i].PER));
    Qi:=Min(rGetTag(tagHeat[i].Q),rGetTag(tagHeat[i].QMAX));
    Qi:=Max(0,Min(1,0.01*Max(0,Min(100,Qi))));
    Hit:=Ord(Frac(tp)<Qi);
   end;
   if Qi=0 then b:=iSetTag(tagHeat[i].POW,0);
   b:=iSetTag(tagHeat[i].SSR,Hit);
  end;
  {
  Calculate work times
  }
  CalcWorkTime(tagFan.POW,tagFan.WTM,FanWTM);
  for i:=1 to nComps do CalcWorkTime(tagComp[i].POW,tagComp[i].WTM,CompWTM[i]);
  for i:=1 to nPumps do CalcWorkTime(tagPump[i].POW,tagPump[i].WTM,PumpWTM[i]);
  {
  Calculate watchdog times by tags
  }
  if HPR<rGetTag(tagCompP1MIN)
  then for i:=1 to nComps do CalcWdtTime(tagComp[i].ENB,wdtComp[i].P1MIN)
  else for i:=1 to nComps do wdtComp[i].P1MIN:=0;
  if HPR>rGetTag(tagCompP1MAX)
  then for i:=1 to nComps do CalcWdtTime(tagComp[i].ENB,wdtComp[i].P1MAX)
  else for i:=1 to nComps do wdtComp[i].P1MAX:=0;
  if LPR<rGetTag(tagCompP2MIN)
  then for i:=1 to nComps do CalcWdtTime(tagComp[i].ENB,wdtComp[i].P2MIN)
  else for i:=1 to nComps do wdtComp[i].P2MIN:=0;
  if LPR>rGetTag(tagCompP2MAX)
  then for i:=1 to nComps do CalcWdtTime(tagComp[i].ENB,wdtComp[i].P2MAX)
  else for i:=1 to nComps do wdtComp[i].P2MAX:=0;
  if PPR<rGetTag(tagPumpP3MIN)
  then for i:=1 to nPumps do CalcWdtTime(tagPump[i].ENB,wdtPump[i].P3MIN)
  else for i:=1 to nPumps do wdtPump[i].P3MIN:=0;
  if PPR>rGetTag(tagPumpP3MAX)
  then for i:=1 to nPumps do CalcWdtTime(tagPump[i].ENB,wdtPump[i].P3MAX)
  else for i:=1 to nPumps do wdtPump[i].P3MAX:=0;
  if TLIQ<rGetTag(tagLiq.TMIN)
  then for i:=1 to nPumps do CalcWdtTime(tagPump[i].ENB,wdtPump[i].TMIN)
  else for i:=1 to nPumps do wdtPump[i].TMIN:=0;
  if TLIQ>rGetTag(tagLiq.TMAX)
  then for i:=1 to nPumps do CalcWdtTime(tagPump[i].ENB,wdtPump[i].TMAX)
  else for i:=1 to nPumps do wdtPump[i].TMAX:=0;
  if T4>rGetTag(tagHeatsT4max)
  then for i:=1 to nHeats do CalcWdtTime(tagHeat[i].ENB,wdtHeat[i].T4MAX)
  else for i:=1 to nHeats do wdtHeat[i].T4MAX:=0;
  for i:=1 to nComps do CalcWdtTime(tagComp[i].THR,wdtComp[i].THR);
  for i:=1 to nComps do CalcWdtTime(tagComp[i].OIL,wdtComp[i].OIL);
  for i:=1 to nComps do CalcWdtTime(tagComp[i].LPR,wdtComp[i].LPR);
  for i:=1 to nComps do CalcWdtTime(tagComp[i].HPR,wdtComp[i].HPR);
  for i:=1 to nPumps do CalcWdtTime(tagPump[i].THR,wdtPump[i].THR);
  CalcWdtTime(tagFreon.LPR,wdtFreon.LPR);
  CalcWdtTime(tagFreon.HPR,wdtFreon.HPR);
  CalcWdtTime(tagLiq.HLR,wdtLiq.HLR);
  CalcWdtTime(tagLiq.LLR,wdtLiq.LLR);
  {
  Calculate pump & compressor order
  Change order every 8 hours
  }
  CalcPumpOrder(8);
  CalcCompOrder(8);
  {
  Control command state
  }
  CtrlCmd:=iGetTag(tagCmdCtrl);
  if CtrlCmd<cm_Halt then CtrlCmd:=cm_Halt;
  if CtrlCmd>cm_High then CtrlCmd:=cm_Halt;
  {
  cm_Halt - emergency stop all
  }
  if CtrlCmd=cm_Halt then begin
   b:=iSetTag(tagCmdCtrl,cm_Halt);
   PumpCount:=0;
   CompCount:=0;
   CompTimer:=0;
   AverTimer:=0;
   CalcCompState(CompCount);
   CalcPumpState(PumpCount);
   b:=iSetTag(tagFAN.POW,0);
   b:=iSetTag(tagLIQ.VE2,0);
   b:=iSetTag(tagFREON.VE1,0);
   b:=rSetTag(tagLiq.TSTAB,0);
   b:=rSetTag(tagLiq.DELTA,0);
   b:=iSetTag(tagCompNBMIN,0);
   b:=iSetTag(tagCompNBMAX,0);
   b:=rSetTag(tagCompPOLLTM,0);
   b:=rSetTag(tagCompDEADTM,0);
   b:=iSetTag(tagLiq.NBSEN,0);
   b:=rSetTag(tagLiq.DTMAX,0);
   b:=rSetTag(tagLiq.TSENS,0);
   b:=rSetTag(tagLiq.TGOAL,0);
   b:=rSetTag(tagLiq.TAVER,0);
   for i:=1 to nHeats do b:=rSetTag(tagHeat[i].Q,0);
   for i:=1 to nHeats do b:=iSetTag(tagHeat[i].POW,0);
  end;
  {
  cm_Hand - manual control
  }
  if CtrlCmd=cm_Hand then begin
   PumpCount:=0;
   CompCount:=0;
   CompTimer:=0;
   AverTimer:=0;
   b:=rSetTag(tagLiq.TSTAB,0);
   b:=rSetTag(tagLiq.DELTA,0);
   b:=iSetTag(tagCompNBMIN,0);
   b:=iSetTag(tagCompNBMAX,0);
   b:=rSetTag(tagCompPOLLTM,0);
   b:=rSetTag(tagCompDEADTM,0);
   b:=iSetTag(tagLiq.NBSEN,0);
   b:=rSetTag(tagLiq.DTMAX,0);
   b:=rSetTag(tagLiq.TSENS,0);
   b:=rSetTag(tagLiq.TGOAL,0);
   b:=rSetTag(tagLiq.TAVER,0);
  end;
  {
  cm_StBy - Standby mode
  cm_Cool - Cooling mode
  cm_Warm - Warming mode
  }
  if (CtrlCmd=cm_StBy) or (CtrlCmd=cm_Cool) or (CtrlCmd=cm_Warm) then begin
   {Liquid,Freon,Fan...}
   b:=iSetTag(tagCmdBlk,1);
   b:=iSetTag(tagLIQ.VE2,iGetTag(tagLIQ.ENB));
   b:=iSetTag(tagFREON.VE1,iGetTag(tagFREON.ENB));
   b:=iSetTag(tagFAN.POW,Ord((iGetTag(tagFAN.ENB)<>0) and (getai(aiFAN,getai_xn(aiFAN))>rGetTag(tagFAN.TON))));
   {Activate parameters...}
   if CtrlCmd=cm_StBy then begin
    b:=rSetTag(tagLiq.TSTAB,rGetTag(tagStBy.TSTAB));
    b:=rSetTag(tagLiq.DELTA,rGetTag(tagStBy.DELTA));
    b:=iSetTag(tagCompNBMIN,iGetTag(tagStBy.NBMIN));
    b:=iSetTag(tagCompNBMAX,iGetTag(tagStBy.NBMAX));
    b:=rSetTag(tagCompPOLLTM,rGetTag(tagStBy.POLLTM));
    b:=rSetTag(tagCompDEADTM,rGetTag(tagStBy.DEADTM));
    b:=iSetTag(tagLiq.NBSEN,iGetTag(tagStBy.NBSEN));
    b:=rSetTag(tagLiq.DTMAX,rGetTag(tagStBy.DTMAX));
    for i:=1 to nHeats do b:=rSetTag(tagHeat[i].Q,rGetTag(tagStBy.Q[i]));
   end;
   if CtrlCmd=cm_Cool then begin
    b:=rSetTag(tagLiq.TSTAB,rGetTag(tagCool.TSTAB));
    b:=rSetTag(tagLiq.DELTA,rGetTag(tagCool.DELTA));
    b:=iSetTag(tagCompNBMIN,iGetTag(tagCool.NBMIN));
    b:=iSetTag(tagCompNBMAX,iGetTag(tagCool.NBMAX));
    b:=rSetTag(tagCompPOLLTM,rGetTag(tagCool.POLLTM));
    b:=rSetTag(tagCompDEADTM,rGetTag(tagCool.DEADTM));
    b:=iSetTag(tagLiq.NBSEN,iGetTag(tagCool.NBSEN));
    b:=rSetTag(tagLiq.DTMAX,rGetTag(tagCool.DTMAX));
    for i:=1 to nHeats do b:=rSetTag(tagHeat[i].Q,rGetTag(tagCool.Q[i]));
   end;
   if CtrlCmd=cm_Warm then begin
    b:=rSetTag(tagLiq.TSTAB,rGetTag(tagWarm.TSTAB));
    b:=rSetTag(tagLiq.DELTA,rGetTag(tagWarm.DELTA));
    b:=iSetTag(tagCompNBMIN,iGetTag(tagWarm.NBMIN));
    b:=iSetTag(tagCompNBMAX,iGetTag(tagWarm.NBMAX));
    b:=rSetTag(tagCompPOLLTM,rGetTag(tagWarm.POLLTM));
    b:=rSetTag(tagCompDEADTM,rGetTag(tagWarm.DEADTM));
    b:=iSetTag(tagLiq.NBSEN,iGetTag(tagWarm.NBSEN));
    b:=rSetTag(tagLiq.DTMAX,rGetTag(tagWarm.DTMAX));
    for i:=1 to nHeats do b:=rSetTag(tagHeat[i].Q,rGetTag(tagWarm.Q[i]));
   end;
   {Pumps & Compressors}
   PumpCount:=Ord((iGetTag(tagCompNBMAX)>0));
   if iGetTag(tagCmdCtrl)<>CmdCtrl then begin
    CompTimer:=msecnow+CompDeadTime;
    CompCount:=0;
    AverTimer:=0;
   end;
   if msecnow>CompTimer+CompPollTime then begin
    CompTimer:=msecnow;
    Ts:=_NAN;
    i:=iGetTag(tagLiq.NBSEN);
    Tg:=rGetTag(tagLiq.TSTAB);
    dT:=rGetTag(tagLiq.DTMAX);
    if (i>=0) and (i<=maxPwo) then
    if TypeTag(tagPwoT[i])=2 then Ts:=rGetTag(tagPwoT[i]);
    if IsNan(Ts) then Ts:=0 else
    if (dT>0) and (Ts>=rGetTag(tagLiq.TMIN)) and (Ts<=rGetTag(tagLiq.TMAX)) then begin
     Tg:=Max(Tg,Ts-dT);
     Tg:=Min(Tg,Ts+dT);
     Tg:=Max(Tg,rGetTag(tagLiq.TMIN));
     Tg:=Min(Tg,rGetTag(tagLiq.TMAX));
    end;
    b:=rSetTag(tagLiq.TSENS,Ts);
    b:=rSetTag(tagLiq.TGOAL,Tg);
    n:=CalcCompDelta(rGetTag(tagT[1]),rGetTag(tagLiq.TGOAL),rGetTag(tagLiq.DELTA));
    n:=CompCount+n;
    n:=Round(Min(n,iGetTag(tagCompNBMAX)));
    n:=Round(Max(n,iGetTag(tagCompNBMIN)));
    n:=Round(Min(n,nComps));
    n:=Round(Max(n,0));
    if n<>CompCount then begin
     if (n<CompCount) and (msecnow>AverTimer+5*CompDeadTime) then begin
      if AverTimer>0 then begin
       tb:=(msecnow-TimeBase)/TimeUnits;
       ta:=(AverTimer-TimeBase)/TimeUnits;
       b:=rSetTag(tagLiq.TAVER,CrvInteg(RefAi(aiT1),ta,tb)/(tb-ta));
      end;
      AverTimer:=msecnow;
     end;
     CompTimer:=msecnow+CompDeadTime;
     CompCount:=n;
    end;
   end;
   CalcCompState(CompCount);
   CalcPumpState(PumpCount);
   {Heats}
   for i:=1 to nHeats do begin
    b:=(iGetTag(tagHEAT[i].ENB)<>0);
    b:=b and (iGetTag(tagCompNBMAX)>0);
    b:=b and (rGetTag(tagHEAT[i].Q)>0);
    b:=b and (rGetTag(tagHEAT[i].QMAX)>0);
    b:=iSetTag(tagHEAT[i].POW,Ord(b));
   end;
  end;
  {
  Update Command states
  }
  CmdCtrl:=iGetTag(tagCmdCtrl);
  {
  Blocks
  }
  nBlocks:=0;
  if iGetTag(tagCmdBlk)<>0 then begin
   {
   Block VE1 on freon LPR, HPR
   }
   if iGetTag(tagFREON.VE1)<>0 then begin
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_VE1_LPR))<>0 then
    if GetWdtTime(wdtFREON.LPR)>=wdtFreonLPR then begin
     Warn('VE1  BLOCKED ON LPR.');
     b:=iSetTag(tagFREON.VE1,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_VE1_HPR))<>0 then
    if GetWdtTime(wdtFREON.HPR)>=wdtFreonHPR then begin
     Warn('VE1  BLOCKED ON HPR.');
     b:=iSetTag(tagFREON.VE1,0);
    end;
   end;
   {
   Block pumps on THR, VE2, HLR, LLR, TMIN, TMAX
   }
   for i:=1 to nPumps do
   if iGetTag(tagPUMP[i].POW)<>0 then begin
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_PUMP_THR))<>0 then
    if GetWdtTime(wdtPUMP[i].THR)>=wdtPumpTHR then begin
     Warn('PUMP'+Str(i)+' BLOCKED ON THR.');
     b:=iSetTag(tagPUMP[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_PUMP_VE2))<>0 then
    if iGetTag(tagLIQ.VE2)=0 then begin
     Warn('PUMP'+Str(i)+' BLOCKED ON VE2.');
     b:=iSetTag(tagPUMP[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_PUMP_HLR))<>0 then
    if GetWdtTime(wdtLIQ.HLR)>=wdtLiqHLR then begin
     Warn('PUMP'+Str(i)+' BLOCKED ON LIQ.HLR.');
     b:=iSetTag(tagPUMP[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_PUMP_LLR))<>0 then
    if GetWdtTime(wdtLIQ.LLR)>=wdtLiqLLR then begin
     Warn('PUMP'+Str(i)+' BLOCKED ON LIQ.LLR.');
     b:=iSetTag(tagPUMP[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_PUMP_P3MIN))<>0 then
    if GetWdtTime(wdtPump[i].P3MIN)>=wdtPumpP3MIN then begin
     Warn('PUMP'+Str(i)+' BLOCKED ON PUMP.P3MIN.');
     b:=iSetTag(tagPUMP[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_PUMP_P3MAX))<>0 then
    if GetWdtTime(wdtPump[i].P3MAX)>=wdtPumpP3MAX then begin
     Warn('PUMP'+Str(i)+' BLOCKED ON PUMP.P3MAX.');
     b:=iSetTag(tagPUMP[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_PUMP_TMIN))<>0 then
    if GetWdtTime(wdtPump[i].TMIN)>=wdtPumpTMIN then begin
     Warn('PUMP'+Str(i)+' BLOCKED ON PUMP.TMIN.');
     b:=iSetTag(tagPUMP[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_PUMP_TMAX))<>0 then
    if GetWdtTime(wdtPump[i].TMAX)>=wdtPumpTMAX then begin
     Warn('PUMP'+Str(i)+' BLOCKED ON PUMP.TMAX.');
     b:=iSetTag(tagPUMP[i].POW,0);
    end;
   end;
   {
   Block compressors on THR, OIL, LPR, HPR, PLO, PHI
   }
   for i:=1 to nComps do
   if iGetTag(tagComp[i].POW)<>0 then begin
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_COMP_THR))<>0 then
    if GetWdtTime(wdtComp[i].THR)>=wdtCompTHR then begin
     Warn('COMP'+Str(i)+' BLOCKED ON THR.');
     b:=iSetTag(tagComp[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_COMP_OIL))<>0 then
    if GetWdtTime(wdtComp[i].OIL)>=wdtCompOIL then begin
     Warn('COMP'+Str(i)+' BLOCKED ON OIL.');
     b:=iSetTag(tagComp[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_COMP_LPR))<>0 then
    if GetWdtTime(wdtComp[i].LPR)>=wdtCompLPR then begin
     Warn('COMP'+Str(i)+' BLOCKED ON LPR.');
     b:=iSetTag(tagComp[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_COMP_HPR))<>0 then
    if GetWdtTime(wdtComp[i].HPR)>=wdtCompHPR then begin
     Warn('COMP'+Str(i)+' BLOCKED ON HPR.');
     b:=iSetTag(tagComp[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_COMP_P1MIN))<>0 then
    if GetWdtTime(wdtComp[i].P1MIN)>=wdtCompP1MIN then begin
     Warn('COMP'+Str(i)+' BLOCKED ON P1MIN.');
     b:=iSetTag(tagComp[i].POW,0);
     CompTimer:=0;
     CompCount:=0;
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_COMP_P1MAX))<>0 then
    if GetWdtTime(wdtComp[i].P1MAX)>=wdtCompP1MAX then begin
     Warn('COMP'+Str(i)+' BLOCKED ON P1MAX.');
     b:=iSetTag(tagComp[i].POW,0);
     CompTimer:=0;
     CompCount:=0;
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_COMP_P2MIN))<>0 then
    if GetWdtTime(wdtComp[i].P2MIN)>=wdtCompP2MIN then begin
     Warn('COMP'+Str(i)+' BLOCKED ON P2MIN.');
     b:=iSetTag(tagComp[i].POW,0);
     CompTimer:=0;
     CompCount:=0;
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_COMP_P2MAX))<>0 then
    if GetWdtTime(wdtComp[i].P2MAX)>=wdtCompP2MAX then begin
     Warn('COMP'+Str(i)+' BLOCKED ON P2MAX.');
     b:=iSetTag(tagComp[i].POW,0);
     CompTimer:=0;
     CompCount:=0;
    end;
   end;
   {
   Block compressors on VE1, FAN(?), PUMP[1]+PUMP[2]
   }
   for i:=1 to nComps do
   if iGetTag(tagComp[i].POW)<>0 then begin
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_COMP_VE1))<>0 then
    if iGetTag(tagFREON.VE1)=0 then begin
     Warn('COMP'+Str(i)+' BLOCKED ON VE1.');
     b:=iSetTag(tagComp[i].POW,0);
     CompTimer:=0;
     CompCount:=0;
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_COMP_PUMP))<>0 then
    if iGetTag(tagPUMP[1].POW)+iGetTag(tagPUMP[2].POW)=0 then begin
     Warn('COMP'+Str(i)+' BLOCKED ON PUMP1+PUMP2.');
     b:=iSetTag(tagComp[i].POW,0);
     CompTimer:=0;
     CompCount:=0;
    end;
   end;
   {
   Block heats on VE2, PUMP[1]+PUMP[2], T4MAX
   }
   for i:=1 to nHeats do
   if iGetTag(tagHeat[i].POW)<>0 then begin
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_HEAT_VE2))<>0 then
    if iGetTag(tagLIQ.VE2)=0 then begin
     Warn('HEAT'+Str(i)+' BLOCKED ON VE2.');
     b:=iSetTag(tagHeat[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_HEAT_PUMP))<>0 then
    if iGetTag(tagPUMP[1].POW)+iGetTag(tagPUMP[2].POW)=0 then begin
     Warn('HEAT'+Str(i)+' BLOCKED ON PUMP1+PUMP2.');
     b:=iSetTag(tagHeat[i].POW,0);
    end;
    if iAnd(iGetTag(tagBlkOpt),iShift(1,blk_HEAT_T4MAX))<>0 then
    if GetWdtTime(wdtHeat[i].T4MAX)>=wdtHeatsT4Max then begin
     Warn('HEAT'+Str(i)+' BLOCKED ON T4MAX.');
     b:=iSetTag(tagHeat[i].POW,0);
    end;
   end;
   b:=iSetTag(tagBlkNum,nBlocks);
  end;
 end;
 {
 Save [CustomParameters]
 }
 procedure SaveCustom;
 var f:String; i:Integer;
  procedure SaveTag(tag:Integer);
  begin
   if TypeTag(tag)=1 then Writeln(NameTag(tag),' = ',Str(iGetTag(tag)));
   if TypeTag(tag)=2 then Writeln(NameTag(tag),' = ',Str(rGetTag(tag)));
   if TypeTag(tag)=3 then Writeln(NameTag(tag),' = ',sGetTag(tag));
  end;
 begin
  f:=ReadIni('CustomFile');
  if Length(f)>0 then begin
   i:=Rewrite(DaqFileRef(f,''));
   writeln(ReadIni('CustomSection'));
   for i:=1 to nComps do SaveTag(tagComp[i].WTM);
   for i:=1 to nPumps do SaveTag(tagPump[i].WTM);
   for i:=1 to nHeats do SaveTag(tagHeat[i].Q);
   for i:=1 to nHeats do SaveTag(tagHeat[i].QMAX);
   SaveTag(tagFan.WTM);
   SaveTag(tagFan.TON);
   SaveTag(tagCompP1MIN);
   SaveTag(tagCompP1MAX);
   SaveTag(tagCompP2MIN);
   SaveTag(tagCompP2MAX);
   SaveTag(tagCompNBMIN);
   SaveTag(tagCompNBMAX);
   SaveTag(tagCompPOLLTM);
   SaveTag(tagCompDEADTM);
   SaveTag(tagLiq.TMIN);
   SaveTag(tagLiq.TMAX);
   SaveTag(tagLiq.TSTAB);
   SaveTag(tagLiq.DELTA);
   SaveTag(tagLiq.NBSEN);
   SaveTag(tagLiq.DTMAX);
   SaveTag(tagPumpP3MIN);
   SaveTag(tagPumpP3MAX);
   SaveTag(tagHEATST4MAX);
   SaveTag(tagCmdCtrl);
   SaveTag(tagCmdBlk);
   SaveTag(tagBlkOpt);
   SaveTag(tagStBy.TSTAB);
   SaveTag(tagStBy.DELTA);
   SaveTag(tagStBy.NBMIN);
   SaveTag(tagStBy.NBMAX);
   SaveTag(tagStBy.POLLTM);
   SaveTag(tagStBy.DEADTM);
   SaveTag(tagStBy.NBSEN);
   SaveTag(tagStBy.DTMAX);
   SaveTag(tagStBy.Q[1]);
   SaveTag(tagStBy.Q[2]);
   SaveTag(tagStBy.Q[3]);
   SaveTag(tagCool.TSTAB);
   SaveTag(tagCool.DELTA);
   SaveTag(tagCool.NBMIN);
   SaveTag(tagCool.NBMAX);
   SaveTag(tagCool.POLLTM);
   SaveTag(tagCool.DEADTM);
   SaveTag(tagCool.NBSEN);
   SaveTag(tagCool.DTMAX);
   SaveTag(tagCool.Q[1]);
   SaveTag(tagCool.Q[2]);
   SaveTag(tagCool.Q[3]);
   SaveTag(tagWarm.TSTAB);
   SaveTag(tagWarm.DELTA);
   SaveTag(tagWarm.NBMIN);
   SaveTag(tagWarm.NBMAX);
   SaveTag(tagWarm.POLLTM);
   SaveTag(tagWarm.DEADTM);
   SaveTag(tagWarm.NBSEN);
   SaveTag(tagWarm.DTMAX);
   SaveTag(tagWarm.Q[1]);
   SaveTag(tagWarm.Q[2]);
   SaveTag(tagWarm.Q[3]);
   SaveTag(tagFsmFsm);
   SaveTag(tagFsmDim);
   SaveTag(tagFsmSay);
   SaveTag(tagFsmState);
   for i:=0 to maxPwo do SaveTag(tagPwoN[i]);
   for i:=0 to maxPwo do SaveTag(tagFeeN[i]);
   i:=Append('');
  end;
  f:='';
 end;
 {
 Load [CustomParameters]
 }
 procedure LoadCustom;
 var f:String;
  procedure LoadTag(tag:Integer);
  var s:String;
  begin
   s:=ReadIni(f+' '+NameTag(tag));
   if Length(s)>0 then begin
    if TypeTag(tag)=1 then b:=iSetTag(tag,Val(s));
    if TypeTag(tag)=2 then b:=rSetTag(tag,rVal(s));
    if TypeTag(tag)=3 then b:=sSetTag(tag,s);
   end;
  end;
 begin
  f:=ReadIni('CustomFile');
  if Length(f)>0 then begin
   f:=DaqFileRef(f,'')+' '+ReadIni('CustomSection');
   for i:=1 to nComps do LoadTag(tagComp[i].WTM);
   for i:=1 to nPumps do LoadTag(tagPump[i].WTM);
   for i:=1 to nHeats do LoadTag(tagHeat[i].Q);
   for i:=1 to nHeats do LoadTag(tagHeat[i].QMAX);
   LoadTag(tagFan.WTM);
   LoadTag(tagFan.TON);
   LoadTag(tagCompP1MIN);
   LoadTag(tagCompP1MAX);
   LoadTag(tagCompP2MIN);
   LoadTag(tagCompP2MAX);
   LoadTag(tagCompNBMIN);
   LoadTag(tagCompNBMAX);
   LoadTag(tagCompPOLLTM);
   LoadTag(tagCompDEADTM);
   LoadTag(tagLiq.TMIN);
   LoadTag(tagLiq.TMAX);
   LoadTag(tagLiq.TSTAB);
   LoadTag(tagLiq.DELTA);
   LoadTag(tagLiq.NBSEN);
   LoadTag(tagLiq.DTMAX);
   LoadTag(tagPumpP3MIN);
   LoadTag(tagPumpP3MAX);
   LoadTag(tagHEATST4MAX);
   LoadTag(tagCmdCtrl);
   LoadTag(tagCmdBlk);
   LoadTag(tagBlkOpt);
   LoadTag(tagStBy.TSTAB);
   LoadTag(tagStBy.DELTA);
   LoadTag(tagStBy.NBMIN);
   LoadTag(tagStBy.NBMAX);
   LoadTag(tagStBy.POLLTM);
   LoadTag(tagStBy.DEADTM);
   LoadTag(tagStBy.NBSEN);
   LoadTag(tagStBy.DTMAX);
   LoadTag(tagStBy.Q[1]);
   LoadTag(tagStBy.Q[2]);
   LoadTag(tagStBy.Q[3]);
   LoadTag(tagCool.TSTAB);
   LoadTag(tagCool.DELTA);
   LoadTag(tagCool.NBMIN);
   LoadTag(tagCool.NBMAX);
   LoadTag(tagCool.POLLTM);
   LoadTag(tagCool.DEADTM);
   LoadTag(tagCool.NBSEN);
   LoadTag(tagCool.DTMAX);
   LoadTag(tagCool.Q[1]);
   LoadTag(tagCool.Q[2]);
   LoadTag(tagCool.Q[3]);
   LoadTag(tagWarm.TSTAB);
   LoadTag(tagWarm.DELTA);
   LoadTag(tagWarm.NBMIN);
   LoadTag(tagWarm.NBMAX);
   LoadTag(tagWarm.POLLTM);
   LoadTag(tagWarm.DEADTM);
   LoadTag(tagWarm.NBSEN);
   LoadTag(tagWarm.DTMAX);
   LoadTag(tagWarm.Q[1]);
   LoadTag(tagWarm.Q[2]);
   LoadTag(tagWarm.Q[3]);
   LoadTag(tagFsmFsm);
   LoadTag(tagFsmDim);
   LoadTag(tagFsmSay);
   LoadTag(tagFsmState);
   for i:=0 to maxPwo do LoadTag(tagPwoN[i]);
   for i:=0 to maxPwo do LoadTag(tagFeeN[i]);
  end;
  f:='';
 end;
 {
 Finalize PHOS system.
 }
 procedure PHOS_Free;
 begin
  SaveCustom;
 end;
 {
 Initialize PHOS system.
 }
 procedure PHOS_Init;
 var i:Integer; b:Boolean;
 begin
  {
  Initialize tags...
  }
  InitTag(tagClick,    ReadIni('tagClick'),    3);
  InitTag(tagNotify,   ReadIni('tagNotify'),   3);
  InitTag(tagHostTime, ReadIni('tagHostTime'), 2);
  InitTag(tagHostInfo, ReadIni('tagHostInfo'), 3);
  for i:=1 to nPs do InitTag(tagP[i],StrReplace(ReadIni('tagP'),'##',Str(i),3),2);
  for i:=1 to nTs do InitTag(tagT[i],StrReplace(ReadIni('tagT'),'##',Str(i),3),2);
  for i:=1 to nComps do begin
   InitTag(tagComp[i].ENB, StrReplace(ReadIni('tagCompENB'),'##',Str(i),3), 1);
   InitTag(tagComp[i].POW, StrReplace(ReadIni('tagCompPOW'),'##',Str(i),3), 1);
   InitTag(tagComp[i].WTM, StrReplace(ReadIni('tagCompWTM'),'##',Str(i),3), 2);
   InitTag(tagComp[i].THR, StrReplace(ReadIni('tagCompTHR'),'##',Str(i),3), 1);
   InitTag(tagComp[i].OIL, StrReplace(ReadIni('tagCompOIL'),'##',Str(i),3), 1);
   InitTag(tagComp[i].LPR, StrReplace(ReadIni('tagCompLPR'),'##',Str(i),3), 1);
   InitTag(tagComp[i].HPR, StrReplace(ReadIni('tagCompHPR'),'##',Str(i),3), 1);
  end;
  for i:=1 to nPumps do begin
   InitTag(tagPump[i].ENB, StrReplace(ReadIni('tagPumpENB'),'##',Str(i),3), 1);
   InitTag(tagPump[i].POW, StrReplace(ReadIni('tagPumpPOW'),'##',Str(i),3), 1);
   InitTag(tagPump[i].WTM, StrReplace(ReadIni('tagPumpWTM'),'##',Str(i),3), 2);
   InitTag(tagPump[i].THR, StrReplace(ReadIni('tagPumpTHR'),'##',Str(i),3), 1);
  end;
  for i:=1 to nHeats do begin
   InitTag(tagHeat[i].ENB, StrReplace(ReadIni('tagHeatENB'),'##',Str(i),3), 1);
   InitTag(tagHeat[i].POW, StrReplace(ReadIni('tagHeatPOW'),'##',Str(i),3), 1);
   InitTag(tagHeat[i].SSR, StrReplace(ReadIni('tagHeatSSR'),'##',Str(i),3), 1);
   InitTag(tagHeat[i].PER, StrReplace(ReadIni('tagHeatPER'),'##',Str(i),3), 1);
   InitTag(tagHeat[i].Q,   StrReplace(ReadIni('tagHeatQ'),  '##',Str(i),3), 2);
   InitTag(tagHeat[i].QMAX,StrReplace(ReadIni('tagHeatQmax'),'##',Str(i),3),2);
  end;
  InitTag(tagTP2,        ReadIni('tagTP2'),       2);
  InitTag(tagTCJ,        ReadIni('tagTCJ'),       2);
  InitTag(tagCompP1MIN,  ReadIni('tagCompP1MIN'), 2);
  InitTag(tagCompP1MAX,  ReadIni('tagCompP1MAX'), 2);
  InitTag(tagCompP2MIN,  ReadIni('tagCompP2MIN'), 2);
  InitTag(tagCompP2MAX,  ReadIni('tagCompP2MAX'), 2);
  InitTag(tagCompNBMIN,  ReadIni('tagCompNBMIN'), 1);
  InitTag(tagCompNBMAX,  ReadIni('tagCompNBMAX'), 1);
  InitTag(tagCompPOLLTM, ReadIni('tagCompPOLLTM'),2);
  InitTag(tagCompDEADTM, ReadIni('tagCompDEADTM'),2);
  InitTag(tagPumpP3MIN,  ReadIni('tagPumpP3MIN'), 2);
  InitTag(tagPumpP3MAX,  ReadIni('tagPumpP3MAX'), 2);
  InitTag(tagFreon.ENB,  ReadIni('tagFreonENB'),  1);
  InitTag(tagFreon.VE1,  ReadIni('tagFreonVE1'),  1);
  InitTag(tagFreon.LPR,  ReadIni('tagFreonLPR'),  1);
  InitTag(tagFreon.HPR,  ReadIni('tagFreonHPR'),  1);
  InitTag(tagLiq.ENB,    ReadIni('tagLiqENB'),    1);
  InitTag(tagLiq.VE2,    ReadIni('tagLiqVE2'),    1);
  InitTag(tagLiq.HLR,    ReadIni('tagLiqHLR'),    1);
  InitTag(tagLiq.LLR,    ReadIni('tagLiqLLR'),    1);
  InitTag(tagLiq.TMIN,   ReadIni('tagLiqTMin'),   2);
  InitTag(tagLiq.TMAX,   ReadIni('tagLiqTMax'),   2);
  InitTag(tagLiq.TSTAB,  ReadIni('tagLiqTStab'),  2);
  InitTag(tagLiq.DELTA,  ReadIni('tagLiqDelta'),  2);
  InitTag(tagLiq.NBSEN,  ReadIni('tagLiqNbSen'),  1);
  InitTag(tagLiq.DTMAX,  ReadIni('tagLiqdTMax'),  2);
  InitTag(tagLiq.TSENS,  ReadIni('tagLiqTSens'),  2);
  InitTag(tagLiq.TGOAL,  ReadIni('tagLiqTGoal'),  2);
  InitTag(tagLiq.TAVER,  ReadIni('tagLiqTAver'),  2);
  InitTag(tagFan.ENB,    ReadIni('tagFanENB'),    1);
  InitTag(tagFan.WTM,    ReadIni('tagFanWTM'),    2);
  InitTag(tagFan.POW,    ReadIni('tagFanPOW'),    1);
  InitTag(tagFan.TON,    ReadIni('tagFanTON'),    2);
  InitTag(tagDaqBugs,    ReadIni('tagDaqBugs'),   2);
  InitTag(tagHddBugs,    ReadIni('tagHddBugs'),   2);
  InitTag(tagComBugs,    ReadIni('tagComBugs'),   2);
  InitTag(tagInterlock,  ReadIni('tagInterlock'), 1);
  InitTag(tagCmdCtrl,    ReadIni('tagCmdCtrl'),   1);
  InitTag(tagCmdEcv,     ReadIni('tagCmdEcv'),    1);
  InitTag(tagCmdBlk,     ReadIni('tagCmdBlk'),    1);
  InitTag(tagBlkNum,     ReadIni('tagBlkNum'),    1);
  InitTag(tagBlkOpt,     ReadIni('tagBlkOpt'),    1);
  InitTag(tagEcvIp,      ReadIni('tagEcvIp'),     3);
  InitTag(tagLoadParams, ReadIni('tagLoadParams'),1);
  InitTag(tagSaveParams, ReadIni('tagSaveParams'),1);
  InitTag(tagHeatsT4max, ReadIni('tagHeatsT4max'),2);
  InitTag(tagStBy.TSTAB, ReadIni('tagStByTStab'), 2);
  InitTag(tagStBy.DELTA, ReadIni('tagStByDelta'), 2);
  InitTag(tagStBy.NBMIN, ReadIni('tagStByNbMin'), 1);
  InitTag(tagStBy.NBMAX, ReadIni('tagStByNbMax'), 1);
  InitTag(tagStBy.POLLTM,ReadIni('tagStByPollTm'),2);
  InitTag(tagStBy.DEADTM,ReadIni('tagStByDeadTm'),2);
  InitTag(tagStBy.NBSEN, ReadIni('tagStByNbSen'), 1);
  InitTag(tagStBy.DTMAX, ReadIni('tagStBydTMax'), 2);
  InitTag(tagStBy.Q[1],  ReadIni('tagStByQ1'),    2);
  InitTag(tagStBy.Q[2],  ReadIni('tagStByQ2'),    2);
  InitTag(tagStBy.Q[3],  ReadIni('tagStByQ3'),    2);
  InitTag(tagCool.TSTAB, ReadIni('tagCoolTStab'), 2);
  InitTag(tagCool.DELTA, ReadIni('tagCoolDelta'), 2);
  InitTag(tagCool.NBMIN, ReadIni('tagCoolNbMin'), 1);
  InitTag(tagCool.NBMAX, ReadIni('tagCoolNbMax'), 1);
  InitTag(tagCool.POLLTM,ReadIni('tagCoolPollTm'),2);
  InitTag(tagCool.DEADTM,ReadIni('tagCoolDeadTm'),2);
  InitTag(tagCool.NBSEN, ReadIni('tagCoolNbSen'), 1);
  InitTag(tagCool.DTMAX, ReadIni('tagCooldTMax'), 2);
  InitTag(tagCool.Q[1],  ReadIni('tagCoolQ1'),    2);
  InitTag(tagCool.Q[2],  ReadIni('tagCoolQ2'),    2);
  InitTag(tagCool.Q[3],  ReadIni('tagCoolQ3'),    2);
  InitTag(tagWarm.TSTAB, ReadIni('tagWarmTStab'), 2);
  InitTag(tagWarm.DELTA, ReadIni('tagWarmDelta'), 2);
  InitTag(tagWarm.NBMIN, ReadIni('tagWarmNbMin'), 1);
  InitTag(tagWarm.NBMAX, ReadIni('tagWarmNbMax'), 1);
  InitTag(tagWarm.POLLTM,ReadIni('tagWarmPollTm'),2);
  InitTag(tagWarm.DEADTM,ReadIni('tagWarmDeadTm'),2);
  InitTag(tagWarm.NBSEN, ReadIni('tagWarmNbSen'), 1);
  InitTag(tagWarm.DTMAX, ReadIni('tagWarmdTMax'), 2);
  InitTag(tagWarm.Q[1],  ReadIni('tagWarmQ1'),    2);
  InitTag(tagWarm.Q[2],  ReadIni('tagWarmQ2'),    2);
  InitTag(tagWarm.Q[3],  ReadIni('tagWarmQ3'),    2);
  InitTag(tagFsmFsm,     ReadIni('[&PHOS.FSM.GUI] tagFsmFsm'),  0);
  InitTag(tagFsmDim,     ReadIni('[&PHOS.FSM.GUI] tagFsmDim'),  0);
  InitTag(tagFsmSay,     ReadIni('[&PHOS.FSM.GUI] tagFsmSay'),  0);
  InitTag(tagFsmState,   ReadIni('[&PHOS.FSM.GUI] tagFsmState'),0);
  for i:=0 to maxPwo do begin
   InitTag(tagPwoN[i], ReadIni('[&PHOS.MATR.GUI] tagPwo'+Str(i)+'N'),0);
   InitTag(tagFeeN[i], ReadIni('[&PHOS.MATR.GUI] tagFee'+Str(i)+'N'),0);
   InitTag(tagPwoT[i], ReadIni('[&PHOS.MATR.GUI] tagPwo'+Str(i)+'T'),0);
   InitTag(tagFeeT[i], ReadIni('[&PHOS.MATR.GUI] tagFee'+Str(i)+'T'),0);
  end;
  {
  Initialize update markers...
  }
  for i:=1 to nPs do updP[i]:=updEmpty;
  for i:=1 to nTs do updT[i]:=updEmpty;
  updTP2:=updEmpty;
  updTCJ:=updEmpty;
  updCompP1MIN:=updEmpty;
  updCompP1MAX:=updEmpty;
  updCompP2MIN:=updEmpty;
  updCompP2MAX:=updEmpty;
  updCompNBMIN:=updEmpty;
  updCompNBMAX:=updEmpty;
  updCompPOLLTM:=updEmpty;
  updCompDEADTM:=updEmpty;
  updPumpP3MIN:=updEmpty;
  updPumpP3MAX:=updEmpty;
  for i:=1 to nComps do begin
   updComp[i].ENB:=updEmpty;
   updComp[i].POW:=updEmpty;
   updComp[i].WTM:=updEmpty;
   updComp[i].THR:=updEmpty;
   updComp[i].OIL:=updEmpty;
   updComp[i].LPR:=updEmpty;
   updComp[i].HPR:=updEmpty;
  end;
  for i:=1 to nPumps do begin
   updPump[i].ENB:=updEmpty;
   updPump[i].WTM:=updEmpty;
   updPump[i].POW:=updEmpty;
   updPump[i].THR:=updEmpty;
  end;
  for i:=1 to nHeats do begin
   updHeat[i].ENB:=updEmpty;
   updHeat[i].POW:=updEmpty;
   updHeat[i].SSR:=updEmpty;
   updHeat[i].PER:=updEmpty;
   updHeat[i].Q:=updEmpty;
   updHeat[i].QMAX:=updEmpty;
  end;
  updFreon.ENB:=updEmpty;
  updFreon.VE1:=updEmpty;
  updFreon.LPR:=updEmpty;
  updFreon.HPR:=updEmpty;
  updLiq.ENB:=updEmpty;
  updLiq.VE2:=updEmpty;
  updLiq.HLR:=updEmpty;
  updLiq.LLR:=updEmpty;
  updLiq.TMIN:=updEmpty;
  updLiq.TMAX:=updEmpty;
  updLiq.TSTAB:=updEmpty;
  updLiq.DELTA:=updEmpty;
  updLiq.NBSEN:=updEmpty;
  updLiq.DTMAX:=updEmpty;
  updLiq.TSENS:=updEmpty;
  updLiq.TGOAL:=updEmpty;
  updLiq.TAVER:=updEmpty;
  updFan.ENB:=updEmpty;
  updFan.POW:=updEmpty;
  updFan.WTM:=updEmpty;
  updFan.TON:=updEmpty;
  updDaqBugs:=updEmpty;
  updHddBugs:=updEmpty;
  updComBugs:=updEmpty;
  updInterlock:=updEmpty;
  updCmdCtrl:=updEmpty;
  updCmdBlk:=updEmpty;
  updBlkNum:=updEmpty;
  updBlkOpt:=updEmpty;
  updHeatsT4max:=updEmpty;
  updStBy.TSTAB:=updEmpty;
  updStBy.DELTA:=updEmpty;
  updStBy.NBMIN:=updEmpty;
  updStBy.NBMAX:=updEmpty;
  updStBy.POLLTM:=updEmpty;
  updStBy.DEADTM:=updEmpty;
  updStBy.NBSEN:=updEmpty;
  updStBy.DTMAX:=updEmpty;
  updStBy.Q[1]:=updEmpty;
  updStBy.Q[2]:=updEmpty;
  updStBy.Q[3]:=updEmpty;
  updCool.TSTAB:=updEmpty;
  updCool.DELTA:=updEmpty;
  updCool.NBMIN:=updEmpty;
  updCool.NBMAX:=updEmpty;
  updCool.POLLTM:=updEmpty;
  updCool.DEADTM:=updEmpty;
  updCool.NBSEN:=updEmpty;
  updCool.DTMAX:=updEmpty;
  updCool.Q[1]:=updEmpty;
  updCool.Q[2]:=updEmpty;
  updCool.Q[3]:=updEmpty;
  updWarm.TSTAB:=updEmpty;
  updWarm.DELTA:=updEmpty;
  updWarm.NBMIN:=updEmpty;
  updWarm.NBMAX:=updEmpty;
  updWarm.POLLTM:=updEmpty;
  updWarm.DEADTM:=updEmpty;
  updWarm.NBSEN:=updEmpty;
  updWarm.DTMAX:=updEmpty;
  updWarm.Q[1]:=updEmpty;
  updWarm.Q[2]:=updEmpty;
  updWarm.Q[3]:=updEmpty;
  for i:=1 to nHeats do HeatBase[i]:=0;
  for i:=1 to nComps do CompWtm[i]:=0;
  for i:=1 to nPumps do PumpWtm[i]:=0;
  FanWtm:=0;
  PumpCount:=0;
  CompCount:=0;
  CompTimer:=0;
  AverTimer:=0;
  CalcPumpOrder(8);
  CalcCompOrder(8);
  CmdCtrl:=iGetTag(tagCmdCtrl);
  HostTimeTick:=0;
  HostId:=GetEnv('CRW_DAQ_SYS_EXE_PID')+'@'+ParamStr('HostName 1');
  {
  Initialize watchdogs...
  }
  for i:=1 to nComps do wdtComp[i].THR:=0;
  for i:=1 to nComps do wdtComp[i].OIL:=0;
  for i:=1 to nComps do wdtComp[i].LPR:=0;
  for i:=1 to nComps do wdtComp[i].HPR:=0;
  for i:=1 to nComps do wdtComp[i].P1MIN:=0;
  for i:=1 to nComps do wdtComp[i].P1MAX:=0;
  for i:=1 to nComps do wdtComp[i].P2MIN:=0;
  for i:=1 to nComps do wdtComp[i].P2MAX:=0;
  for i:=1 to nPumps do wdtPump[i].THR:=0;
  for i:=1 to nPumps do wdtPump[i].P3MIN:=0;
  for i:=1 to nPumps do wdtPump[i].P3MAX:=0;
  for i:=1 to nPumps do wdtPump[i].TMAX:=0;
  wdtFreon.LPR:=0;
  wdtFreon.HPR:=0;
  wdtLiq.HLR:=0;
  wdtLiq.LLR:=0;
 end;
 {
 Clear all strings
 }
 procedure ClearStrings;
 begin
  s:='';
  HostId:='';
  StdIn_Line:='';
  if runcount=1 then fixmaxavail:=maxavail;
  if isinf(runcount) then
  if maxavail<>fixmaxavail then Trouble('String Manager Leak = '+str(fixmaxavail-maxavail));
 end;
 {
 Analyse data coming from standard input.
 }
 procedure StdIn_Process(Data:string);
 var cmd,arg:String; b:Boolean; tag:Integer;
  procedure Refresh(TheTag:Integer; min,max:Real);
  begin
   if TheTag=tag then begin
    UpdateTag(tag,ExtractWord(2,arg),min,max);
    Success(cmd+' '+arg);
    Data:='';
   end;
  end;
 begin
  if iAnd(DebugFlags,dfViewImp)<>0 then ViewImp('CON: '+Data);
  {
  "@cmd=arg" or "@cmd args" commands:
  }
  cmd:='';
  arg:='';
  if Length(Data)>0 then
  if Data[1]='@' then begin
   cmd:=ExtractWord(1,Data);
   arg:=Copy(Data,Pos(cmd,Data)+Length(cmd)+1);
   {}
   if IsSameText(cmd,'@Help') then begin
    ShowHelp(true);
    Data:='';
   end;
   {}
   if IsSameText(cmd,'@DebugFlags') then begin
    if not IsNan(rVal(arg)) then DebugFlags:=Round(rVal(arg));
    Success(cmd+'='+Str(DebugFlags));
    Data:='';
   end;
   {}
   if IsSameText(cmd,'@AssignTag') then begin
    tag:=FindTag(ExtractWord(1,arg));
    if TypeTag(tag)>0 then begin
     {
     Compressor control settings
     }
     Refresh(tagCompP1MIN, _MinusInf, _PlusInf);
     Refresh(tagCompP1MAX, _MinusInf, _PlusInf);
     Refresh(tagCompP2MIN, _MinusInf, _PlusInf);
     Refresh(tagCompP2MAX, _MinusInf, _PlusInf);
     Refresh(tagCompNBMIN, 0,         nComps);
     Refresh(tagCompNBMAX, 0,         nComps);
     Refresh(tagCompPOLLTM,0,         _PlusInf);
     Refresh(tagCompDEADTM,0,         _PlusInf);
     Refresh(tagPumpP3MIN, _MinusInf, _PlusInf);
     Refresh(tagPumpP3MAX, _MinusInf, _PlusInf);
     Refresh(tagBlkOpt,    _MinusInf, _PlusInf);
     {
     Compressors
     }
     for i:=1 to nComps do begin
      Refresh(tagComp[i].ENB,0,1);
      Refresh(tagComp[i].POW,0,1);
     end;
     {
     Pumps
     }
     for i:=1 to nPumps do begin
      Refresh(tagPump[i].ENB,0,1);
      Refresh(tagPump[i].POW,0,1);
     end;
     {
     Liquid
     }
     Refresh(tagLiq.TMIN,_MinusInf,_PlusInf);
     Refresh(tagLiq.TMAX,_MinusInf,_PlusInf);
     Refresh(tagLiq.TSTAB,rGetTag(tagLiq.TMIN),rGetTag(tagLiq.TMAX));
     Refresh(tagLiq.DELTA,0,(rGetTag(tagLiq.TMAX)-rGetTag(tagLiq.TMIN))/2);
     Refresh(tagLiq.NBSEN,0,4);
     Refresh(tagLiq.DTMAX,0,100);
     {
     FAN
     }
     Refresh(tagFAN.TON, _MinusInf, _PlusInf);
     Refresh(tagFan.ENB,0,1);
     Refresh(tagFan.POW,0,1);
     {
     StBy
     }
     Refresh(tagStBy.TSTAB, rGetTag(tagLiq.TMIN),rGetTag(tagLiq.TMAX));
     Refresh(tagStBy.DELTA, 0, (rGetTag(tagLiq.TMAX)-rGetTag(tagLiq.TMIN))/2);
     Refresh(tagStBy.NBMIN, 0, nComps);
     Refresh(tagStBy.NBMAX, 0, nComps);
     Refresh(tagStBy.POLLTM,0, _PlusInf);
     Refresh(tagStBy.DEADTM,0, _PlusInf);
     Refresh(tagStBy.NBSEN, 0, 4);
     Refresh(tagStBy.DTMAX, 0, _PlusInf);
     Refresh(tagStBy.Q[1],  0, rGetTag(tagHeat[1].QMAX));
     Refresh(tagStBy.Q[2],  0, rGetTag(tagHeat[2].QMAX));
     Refresh(tagStBy.Q[3],  0, rGetTag(tagHeat[3].QMAX));
     {
     Cool
     }
     Refresh(tagCool.TSTAB, rGetTag(tagLiq.TMIN),rGetTag(tagLiq.TMAX));
     Refresh(tagCool.DELTA, 0, (rGetTag(tagLiq.TMAX)-rGetTag(tagLiq.TMIN))/2);
     Refresh(tagCool.NBMIN, 0, nComps);
     Refresh(tagCool.NBMAX, 0, nComps);
     Refresh(tagCool.POLLTM,0, _PlusInf);
     Refresh(tagCool.DEADTM,0, _PlusInf);
     Refresh(tagCool.NBSEN, 0, 4);
     Refresh(tagCool.DTMAX, 0, _PlusInf);
     Refresh(tagCool.Q[1],  0, rGetTag(tagHeat[1].QMAX));
     Refresh(tagCool.Q[2],  0, rGetTag(tagHeat[2].QMAX));
     Refresh(tagCool.Q[3],  0, rGetTag(tagHeat[3].QMAX));
     {
     Warm
     }
     Refresh(tagWarm.TSTAB, rGetTag(tagLiq.TMIN),rGetTag(tagLiq.TMAX));
     Refresh(tagWarm.DELTA, 0, (rGetTag(tagLiq.TMAX)-rGetTag(tagLiq.TMIN))/2);
     Refresh(tagWarm.NBMIN, 0, nComps);
     Refresh(tagWarm.NBMAX, 0, nComps);
     Refresh(tagWarm.POLLTM,0, _PlusInf);
     Refresh(tagWarm.DEADTM,0, _PlusInf);
     Refresh(tagWarm.NBSEN, 0, 4);
     Refresh(tagWarm.DTMAX, 0, _PlusInf);
     Refresh(tagWarm.Q[1],  0, rGetTag(tagHeat[1].QMAX));
     Refresh(tagWarm.Q[2],  0, rGetTag(tagHeat[2].QMAX));
     Refresh(tagWarm.Q[3],  0, rGetTag(tagHeat[3].QMAX));
     {
     Heats
     }
     for i:=1 to nHeats do begin
      Refresh(tagHeat[i].ENB,0,1);
      Refresh(tagHeat[i].POW,0,1);
      Refresh(tagHeat[i].QMAX,0,100);
      Refresh(tagHeat[i].Q,0,rGetTag(tagHeat[i].Qmax));
     end;
     {
     Commands
     }
     Refresh(tagCmdEcv,0,1);
     Refresh(tagCmdBlk,0,1);
     Refresh(tagCmdCtrl,0,cm_High);
     Refresh(tagLiq.ENB,0,1);
     Refresh(tagLiq.VE2,0,1);
     Refresh(tagFreon.ENB,0,1);
     Refresh(tagFreon.VE1,0,1);
     Refresh(tagInterlock,0,1);
     Refresh(tagLoadParams,0,1);
     Refresh(tagSaveParams,0,1);
     Refresh(tagHeatsT4max,0,_PlusInf);
    end;
   end;
   {}
   if IsSameText(cmd,'@SaveParams') then begin
    SaveCustom;
    Success(cmd);
    Data:='';
   end;
   {}
   if IsSameText(cmd,'@LoadParams') then begin
    LoadCustom;
    Success(cmd);
    Data:='';
   end;
   {}
   if Length(Data)>0 then begin
    Trouble(' Unrecognized command "'+Data+'".');
    Data:='';
   end;
  end;
  cmd:='';
  arg:='';
 end;
begin
 {
 Initialization actions on Start...
 }
 if runcount=1 then begin
  {
  Initialize errors...
  }
  errors:=0;
  errorcode:=registererr(devname);
  {
  Clear and initialize variables...
  }
  ClearStrings;
  DebugFlags:=Val(ReadIni('DebugFlags'));
  OpenConsole(Val(ReadIni('OpenConsole')));
  Success(ProgName+' script initialization:');
  {
  Initialize DIM device reference...
  }
  devDimSrv:=RefFind('Device '+DimSrv);
  if devDimSrv=0 then Trouble('Could not find '+DimSrv);
  {
  Initialize PHOS and connect to tags.
  }
  PHOS_Init;
  {
  Is it Ok?
  }
  if errors=0 then Success('Start Ok.') else Trouble('Start Fails.');
  if errors<>0 then b:=fixerror(errorcode);
  Ok:=(errors=0);
 end else
 {
 Finalization actions on Stop...
 }
 if isinf(runcount) then begin
  PHOS_Free;
  ClearStrings;
  Success('Stop.');
 end else
 {
 Actions on Poll
 }
 if Ok then begin
  {
  Load params on startup...
  }
  if RunCount=2 then LoadCustom;
  {
  Process standard input...
  }
  while StdIn_Readln(StdIn_Line) do StdIn_Process(StdIn_Line);
  {
  Digital I/O control
  }
  DioReadout;         // Readout I7053 state
  DevControl;         // Device  control: compressors, pumps etc
  DioControl;         // Write   I7043 control word
 end;
end.
