 {
 ******************************************************
 GUI script for ALIce PHOS COOLing system.
 ******************************************************
 Next text uses by @Help command. Do not remove it.
 ******************************************************
 [@Help]
 |Command list: StdIn "@cmd=arg" or "@cmd arg"
 |******************************************************
 | @Help          - This help.
 | @DebugEcho=n   - Switch HTTP debug echo page On/Off.
 | @DebugFlags=n  - Set DebugFlags,1/2/4/8=!/:/>/< view
 | @Browse n      - Call HTML browser to explore site n.
 | @HTTP_REQUEST_ACCEPTED=sender,reqtime,request,reply
 |                - HTTP request, see &WebSrv @Help
 | @Click         - handle remote click from DIM
 | @Notify        - print notification from DIM server.
 | @Update t      - update tag with name t.
 | @Shutdown Daq Exit    - Exit    DAQ system
 | @Shutdown Daq Restart - Restart DAQ system
 | @Shutdown Crw Exit    - Exit    CRW-DAQ program
 | @Shutdown Win Exit    - Exit    Windows
 | @Shutdown Win Logout  - Logout  Windows
 | @Shutdown Win Restart - Restart Windows
 |******************************************************
 []
 }
program PHOS_COOL_GUI;
const
 dfTrouble         = 1;       { DebugFlags - Trouble             }
 dfSuccess         = 2;       { DebugFlags - Success             }
 dfViewExp         = 4;       { DebugFlags - ViewExp             }
 dfViewImp         = 8;       { DebugFlags - ViewImp             }
 acc_Deny          = 0;       { Access denied                    }
 acc_Guest         = 1;       { Grant access level "guest"       }
 acc_User          = 2;       { Grant access level "user"        }
 acc_Root          = 3;       { Grant access level "root"        }
 acc_List          = 'Guest,User,Root';
 acc_webList       = 'web-guest,web-user,web-root';
 snd_Click         = 'Click'; { Sound on button click            }
 snd_Fails         = 'Fails'; { Sound on operation failure       }
 snd_Wheel         = 'WinWheel'; { Sound on windows operations   }
 snd_Alert1        = 'Alert1';{ Sound on 1st alert level         }
 snd_Alert2        = 'Alert2';{ Sound on 2nd alert level         }
 DimSrv            = '&DimSrv';    { DIM server device name      }
 winDevMsg         = '@DevMsg';    { Device message window       }
 senDevMsg         = '@DevMsg';    { Device message sensor       }
 AlertPeriod       = 7;       { Alert sound period               }
 WatchDogTimeout   = 5000;    { Watchdog to detect host die      }
 HomeRefresh       = 3;       { Refresh time for Home page       }
 TableRefresh      = 3;       { Refresh time for Table page      }
 PlotTimeOut       = 1000;    { Timeout to make Plot             }
 PlotRefresh       = 10;      { Refresh time for Plot page       }
 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    }
 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         }
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      }
 DebugEcho         : Boolean; { FOR DEBUGGING ONLY               }
 DebugFlags        : Integer; { Debug bit flags                  }
 StdIn_Line        : String;  { Temporary variable               }
 winCoolCtrl       : String;  { Cool control window              }
 winCoolPlot       : String;  { Cool plot window                 }
 winCoolTable      : String;  { Cool table window                }
 winCtrlList       : String;  { List of known windows            }
 devDimSrv         : Integer; { Dim server reference             }
 devCoolCtrl       : Integer; { PHOS.COOL.CTRL device reference  }
 Click_Info        : String;  { Information on last click        }
 Click             : Record   { Click event information:         }
  Cmnd,Data        : String;  { Cmnd or Cmnd=Data                }
  Button           : Integer; { Clicked button                   }
  Sensor           : String;  { Clicked sensor name              }
  Device           : String;  { Clicked sensor device name       }
  Window           : String;  { Clicked sensor window name       }
  Curve            : String;  { Clicked sensor curve  name       }
  Tag              : String;  { Clicked sensor tag    name       }
  Value            : String;  { Click sensor tag old value       }
  Guard            : String;  { Guard:Lock/Guest/User/Root       }
  User             : String;  { User name                        }
  Host             : String;  { Host name                        }
  IP               : String;  { IP address                       }
  MAC              : String;  { MAC address                      }
  newValue         : String;  { Click sensor tag new value       }
 end;
 WEB               : record
  Sender           : Integer; { Web server device reference      }
  ReqTime          : Real;    { HTTP request time, ms            }
  Request          : Integer; { HTTP request text reference      }
  Reply            : Integer; { HTTP reply   text reference      }
  TrustedUsers     : Integer; { List of trusted users            }
  QueryCount       : Integer; { QUERY.COUNT                      }
  QueryItems       : Integer; { List of Query items              }
  CookieCount      : Integer; { COOKIE.COUNT                     }
  CookieItems      : Integer; { List of Cookie items             }
  ContentCount     : Integer; { CONTENT.COUNT                    }
  ContentItems     : Integer; { List of Content items            }
  GatewayInterface : String;  { GATEWAY_INTERFACE                }
  RequestMethod    : String;  { REQUEST_METHOD                   }
  QueryString      : String;  { QUERY_STRING                     }
  Content          : String;  { CONTENT                          }
  ContentLength    : Integer; { CONTENT_LENGTH                   }
  ContentType      : String;  { CONTENT_TYPE                     }
  ServerName       : String;  { SERVER_NAME                      }
  ServerPort       : Integer; { SERVER_PORT                      }
  ServerProtocol   : String;  { SERVER_PROTOCOL                  }
  ServerSoftware   : String;  { SERVER_SOFTWARE                  }
  RemoteHost       : String;  { REMOTE_HOST                      }
  RemoteAddr       : String;  { REMOTE_ADDR                      }
  ScriptName       : String;  { SCRIPT_NAME                      }
  PathInfo         : String;  { PATH_INFO                        }
  PathTranslated   : String;  { PATH_TRANSLATED                  }
  WebSrvName       : String;  { WEBSRV.NAME                      }
  WebSrvSite       : String;  { WEBSRV.SITE                      }
  WebSrvRoot       : String;  { WEBSRV.ROOT                      }
  WebSrvPort       : Integer; { WEBSRV.PORT                      }
  WebSrvIndex      : String;  { WEBSRV.INDEX                     }
  UserName         : String;  { For authentication               }
  Password         : String;  { For authentication               }
  DateTime         : String;  { For current date/time            }
 end;
 tagClick          : Integer; { Sensor click information         }
 tagNotify         : Integer; { Sensor click notification        }
 tagHostTime       : Integer; { Host time, msecnow               }
 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                          }
  WTM              : Integer; { Work time                        }
  POW              : Integer; { Power on/off                     }
  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                          }
  WTM              : Integer; { Work time                        }
  POW              : Integer; { Power on/off                     }
  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                          }
  WTM              : Integer; { Work time                        }
  POW              : Integer; { Power on/off                     }
  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  }
 tagCmdEcv         : Integer; { Command for ECV control program  }
 tagCmdBlk         : Integer; { Command for emergency blocking   }
 tagBlkNum         : Integer; { Number of active blocks          }
 tagBlkOpt         : Integer; { Blocking options                 }
 tagCmdClose       : Integer; { Close menu command               }
 tagEcvIp          : Integer; { ECV IP address                   }
 tagLoadParams     : Integer; { Load control parameters          }
 tagSaveParams     : Integer; { Save control parameters          }
 tagHeatsT4max     : Integer; { Heat T4 high limit               }
 tagCmdParam       : Integer; { Dialog notification              }
 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  }
 HostWatchDog      : Real;    { To check is host online or not   }
 HostLastTime      : Real;    { To check host time               }
 menu              : Integer; { Temporary                        }
 MainMenuOn        : Boolean; { Main menu state                  }
 ToolBarOn         : Boolean; { Toolbar state                    }
 StatusBarOn       : Boolean; { Statusbar state                  }
 DaqSystemOn       : Boolean; { DAQ SYSTEM window state          }
 r                 : Real;    { Temporary                        }
 {
 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 export from program.
 }
 procedure ViewExp(msg:String);
 begin
  if iAnd(DebugFlags,dfViewExp)<>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;
 {
 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;
 {
 Find index of word s in list of string items.
 For example, WordIndex('3','1 2 3 4 5')=3.
 }
 function WordIndex(s,list:String):Integer;
 var i,j,n:Integer;
 begin
  i:=1;
  j:=0;
  n:=WordCount(list);
  while i<=n do begin
   if IsSameText(s,ExtractWord(i,list)) then begin
    j:=i;
    i:=n;
   end;
   i:=i+1;
  end;
  WordIndex:=j;
 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;
 {
 Get string parameter by aName from aText list of "Name=Value" items.
 }
 function GetStringVar(aText:Integer; aName:String; var aValue:String):Boolean;
 var i,p:Integer; Name,Value:String;
 begin
  aValue:='';
  GetStringVar:=False;
  if Length(aName)>0 then
  for i:=0 to text_NumLn(aText)-1 do begin
   Value:='';
   Name:=text_GetLn(aText,i);
   p:=Pos('=',Name);
   if p>0 then begin
    Value:=Copy(Name,p+1);
    Name:=Copy(Name,1,p-1);
   end;
   if IsSameText(Name,aName) then begin
    GetStringVar:=True;
    aValue:=Value;
   end;
  end;
  Name:='';
  Value:='';
 end;
 {
 Clear text: remove all lines, but do not free text instance.
 }
 procedure ClearText(aText:Integer);
 var i,N:Integer; b:Boolean;
 begin
  N:=text_NumLn(aText);
  for i:=N-1 downto 0 do b:=text_DelLn(aText,i);
 end; 
 {
 Dialog with warning message
 }
 procedure Warning(msg:string);
 var b:boolean;
 begin
  if editstate=0 
  then msg:=edit('('+msg)+edit(')Warning')
  else b:=Echo(msg);
 end;
 {
 Initialize dialog to edit tag
 }
 procedure StartEditTag(tag:integer; Caption:string);
 var s:string;
 begin
  s:='';
  if typetag(tag)>0 then begin
   if editstate=0 then begin
    if typetag(tag)=1 then s:=str(igettag(tag)) else
    if typetag(tag)=2 then s:=str(rgettag(tag)) else
    if typetag(tag)=3 then s:=sgettag(tag) else s:='';
    if pos('?',edit('(Редактировать тег '+nametag(tag))
              +edit(' '+Caption+'|'+s)
              +edit(')StringGridEdit EDIT_TAG_'+nametag(tag)))>0
    then Warning('Error starting edit tag "'+nametag(tag)+'"!');
   end else Warning('Could not edit tag "'+nametag(tag)+'" right now!');
  end;
  s:='';
 end;
 {
 Check if tag editing done.
 }
 function CheckEditTag(tag:integer; var newValue:String):Boolean;
 var s,d:string; r:real; b:boolean;
 begin
  r:=0;
  s:='';
  d:='';
  CheckEditTag:=false;
  if editstate=1 then
  if typetag(tag)>0 then begin
   s:=edit('?ans 0');
   if extractword(1,s)='EDIT_TAG_'+nametag(tag) then begin
    if extractword(2,s)='1' then begin
     s:=edit('?ans 1');
     d:=worddelims('|');
     s:=extractword(2,s);
     d:=worddelims(d);
     if typetag(tag)=1 then begin
      r:=eval(s);
      if not isnan(r) then begin
       r:=Round(r);
       newValue:=Str(r);
       CheckEditTag:=true;
      end;
     end;
     if typetag(tag)=2 then begin
      r:=eval(s);
      if not isnan(r) then begin
       newValue:=Str(r);
       CheckEditTag:=true;
      end;
     end;
     if typetag(tag)=3 then begin
      newValue:=s;
      CheckEditTag:=true;
     end;
    end;
    s:=edit('');
   end;
   if isnan(r) then Warning('Invalid input!')
  end;
  s:='';
  d:='';
 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;
 {
 Спрятать\показать основное меню
 }
 procedure DoMainMenu;
 var r:Real;
 begin
  if MainMenuOn
  then r:=eval('@system @async @view show FormCrw32.MainMenu')
  else r:=eval('@system @async @view hide FormCrw32.MainMenu');
  MainMenuOn:=not MainMenuOn;
  b:=voice(snd_Click);
 end;
 {
 Спрятать\показать панель инструментов
 }
 procedure DoToolBar;
 var r:Real;
 begin
  if ToolBarOn
  then r:=eval('@system @async @view show FormCrw32.ToolBar')
  else r:=eval('@system @async @view hide FormCrw32.ToolBar');
  ToolBarOn:=not ToolBarOn;
  b:=voice(snd_Click);
 end;
 {
 Спрятать\показать статусную строку
 }
 procedure DoStatusBar;
 var r:Real;
 begin
  if StatusBarOn
  then r:=eval('@system @async @view show FormCrw32.StatusBar')
  else r:=eval('@system @async @view hide FormCrw32.StatusBar');
  StatusBarOn:=not StatusBarOn;
  b:=voice(snd_Click);
 end;
 {
 Спрятать\показать DAQ SYSTEM
 }
 procedure DoDaqSystem;
 var r:Real;
 begin
  if DaqSystemOn
  then r:=eval('@system @async @view norm FormDaqControlDialog')
  else r:=eval('@system @async @view min  FormDaqControlDialog');
  DaqSystemOn:=not DaqSystemOn;
  b:=voice(snd_Click);
 end;
 {
 Выход из DAQ
 }
 procedure DoExitDaq;
 var r:Real;
 begin
  r:=eval('@system @async SaveGuard=@guard');
  r:=eval('@system @async @guard root');
  r:=eval('@system @async _Daq_Force_Stop_=1');
  r:=eval('@system @async _Daq_Force_Exit_=1');
  r:=eval('@system @async @view max FormCrw32');
  r:=eval('@system @async @view norm FormDaqControlDialog');
  r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqStop');
  r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqDone');
  r:=eval('@system @async @guard %SaveGuard');
  r:=eval('@system @async SaveGuard=');
  b:=voice(snd_Click);
 end;
 {
 Выход из CRW
 }
 procedure DoExitCrw;
 var r:Real;
 begin
  r:=eval('@system @async SaveGuard=@guard');
  r:=eval('@system @async @guard root');
  r:=eval('@system @async _Daq_Force_Stop_=1');
  r:=eval('@system @async _Daq_Force_Exit_=1');
  r:=eval('@system @async @view max FormCrw32');
  r:=eval('@system @async @view norm FormDaqControlDialog');
  r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqStop');
  r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqDone');
  r:=eval('@system @async _Crw_Force_Exit_=1');
  r:=eval('@system @async @view max FormCrw32');
  r:=eval('@system @async @menu run FormCrw32.ActionFileExit');
  r:=eval('@system @async @guard %SaveGuard');
  r:=eval('@system @async SaveGuard=');
  b:=voice(snd_Click);
 end;
 {
 Выход из Windows через 15 сек
 How = l/s/r = logout/stop/restart
 }
 procedure DoExitWin(How:Char);
 var r:Real;
 begin
  r:=eval('@system @async SaveGuard=@guard');
  r:=eval('@system @async @guard root');
  r:=eval('@system @async _Daq_Force_Stop_=1');
  r:=eval('@system @async _Daq_Force_Exit_=1');
  r:=eval('@system @async @view max FormCrw32');
  r:=eval('@system @async @view norm FormDaqControlDialog');
  r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqStop');
  r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqDone');
  r:=eval('@system @async @run -hide '+GetComSpec+' /c '
         +AddBackSlash(ExtractFilePath(GetComSpec))+'shutdown.exe -'+How+' -t 15');
  r:=eval('@system @async _Crw_Force_Exit_=1');
  r:=eval('@system @async @view max FormCrw32');
  r:=eval('@system @async @menu run FormCrw32.ActionFileExit');
  r:=eval('@system @async @guard %SaveGuard');
  r:=eval('@system @async SaveGuard=');
  b:=voice(snd_Click);
 end;
 {
 Перезагрузка DAQ
 Принудительный старт при ForceStart=true
 }
 procedure DoRestartDaq(cfg:String;ForceStart:Boolean);
 var r:Real;
 begin
  r:=eval('@system @async SaveGuard=@guard');
  r:=eval('@system @async @guard root');
  r:=eval('@system @async _Daq_Force_Stop_=1');
  r:=eval('@system @async _Daq_Force_Exit_=1');
  r:=eval('@system @async @view max FormCrw32');
  r:=eval('@system @async @view norm FormDaqControlDialog');
  r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqStop');
  r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqDone');
  r:=eval('@system @async _Daq_Force_Start_='+str(ord(ForceStart)));
  r:=eval('@system @async @run '+ParamStr('ProgName')+' '+cfg);
  r:=eval('@system @async @sleep 100');
  r:=eval('@system @async @async @guard %SaveGuard');
  r:=eval('@system @async @async SaveGuard=');
  b:=voice(snd_Click);
 end;
 {
 Перезагрузка DAQ, другая версия
 Принудительный старт при ForceStart=true
 }
 procedure DoReloadDaq(cfg:String;ForceStart:Boolean);
 var r:Real;
 begin
  r:=eval('@system @async SaveGuard=@guard');
  r:=eval('@system @async @guard root');
  r:=eval('@system @async _Daq_Force_Stop_=1');
  r:=eval('@system @async _Daq_Force_Exit_=1');
  r:=eval('@system @async @view max FormCrw32');
  r:=eval('@system @async @view norm FormDaqControlDialog');
  r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqStop');
  r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqDone');
  if ForceStart then begin
   r:=eval('@system @async @run '+ParamStr('ProgName')+' '+cfg);
   r:=eval('@system @async @sleep 100');
   r:=eval('@system @async @async @view max FormCrw32');
   r:=eval('@system @async @async @view norm FormDaqControlDialog');
   r:=eval('@system @async @async @menu run FormDaqControlDialog.ActionDaqStart');
  end;
  r:=eval('@system @async @async @guard %SaveGuard');
  r:=eval('@system @async @async SaveGuard=');
  b:=voice(snd_Click);
 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 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;
 {
 Update Digital Output n: put (t,y) event.
 }
 procedure UpdateDo(n:Integer; t,y:Real);
 var b:Boolean;
 begin
  if RefDo(n)<>0 then b:=putdo(n,t,y);
 end;
 {
 Send click command to server. Use encryption for network safety.
 }
 procedure Click_Send(data:String);
 begin
  DIM_UpdateTag(tagClick,crypt_encode(data,'crw-daq.ru'));
 end;
 {
 Xor bit on click (local version)
 }
 procedure ClickBitXorLocal(tag,XorMask:Integer);
 var b:Boolean; nv:Integer;
 begin
  if ClickTag=tag then begin
   b:=iSetTag(tag,iXor(iGetTag(tag),XorMask));
   b:=Voice(snd_Click);
  end;
 end;
 {
 Xor bit on click (remote version)
 }
 procedure ClickBitXorRemote(tag,XorMask:Integer);
 var b:Boolean; nv:Integer;
 begin
  if ClickTag=tag then begin
   Click_Send(Click_Info+'NewValue='+Str(iXor(iGetTag(tag),XorMask)));
   b:=Voice(snd_Click);
  end;
 end;
 {
 Encode full sensor click information into long string...
 }
 procedure EncodeClick(var s:String);
 begin
  s:='@Click'+CRLF;
  s:=s+'Button='+ClickParams('Button')+CRLF;
  s:=s+'Sensor='+ClickParams('Sensor')+CRLF;
  s:=s+'Device='+ClickParams('Device')+CRLF;
  s:=s+'Window='+ClickParams('Window')+CRLF;
  s:=s+'Value='+ClickParams('Value')+CRLF;
  s:=s+'Curve='+ClickParams('Curve')+CRLF;
  s:=s+'Tag='+ClickParams('Tag')+CRLF;
  s:=s+'Guard='+ParamStr('Guard')+CRLF;
  s:=s+'User='+ParamStr('UserName')+CRLF;
  s:=s+'Host='+ParamStr('HostName')+CRLF;
  s:=s+'IP='+ParamStr('IPAddress')+CRLF;
  s:=s+'MAC='+ParamStr('MACAddress')+CRLF;
 end;
 {
 Encode full sensor click information into long string...
 }
 function ComposeClick(Button:Integer;
          Device,Window,Sensor,Value,Curve,Tag,NewValue:String):String;
 begin
  ComposeClick:='@Click'+CRLF
               +'Button='+Str(Button)+CRLF
               +'Sensor='+Sensor+CRLF
               +'Device='+Device+CRLF
               +'Window='+Window+CRLF
               +'Value='+Value+CRLF
               +'Curve='+Curve+CRLF
               +'Tag='+Tag+CRLF
               +'Guard='+ParamStr('Guard')+CRLF
               +'User='+ParamStr('UserName')+CRLF
               +'Host='+ParamStr('HostName')+CRLF
               +'IP='+ParamStr('IPAddress')+CRLF
               +'MAC='+ParamStr('MACAddress')+CRLF
               +'NewValue='+NewValue+CRLF;
 end;
 {
 Encode simulated click
 }
 procedure SimulateClick(var s:String;aClickTag:Integer);
 begin
  s:='@Click'+CRLF;
  s:=s+'Button='+ClickParams('Button')+CRLF;
  s:=s+'Sensor='+ClickParams('Sensor')+CRLF;
  s:=s+'Device='+ClickParams('Device')+CRLF;
  s:=s+'Window='+ClickParams('Window')+CRLF;
  if TypeTag(aClickTag)=1 then s:=s+'Value='+Str(iGetTag(aClickTag))+CRLF else
  if TypeTag(aClickTag)=2 then s:=s+'Value='+Str(rGetTag(aClickTag))+CRLF else
  if TypeTag(aClickTag)=3 then s:=s+'Value='+sGetTag(aClickTag)+CRLF else s:=s+'Value='+CRLF;
  s:=s+'Curve='+ClickParams('Curve')+CRLF;
  s:=s+'Tag='+NameTag(aClickTag)+CRLF;
  s:=s+'Guard='+ParamStr('Guard')+CRLF;
  s:=s+'User='+ParamStr('UserName')+CRLF;
  s:=s+'Host='+ParamStr('HostName')+CRLF;
  s:=s+'IP='+ParamStr('IPAddress')+CRLF;
  s:=s+'MAC='+ParamStr('MACAddress')+CRLF;
 end;
 {
 Send local device message...
 }
 procedure LocalDevMsg(Device,Msg:String);
 var b:Boolean;
 begin
  b:=DevMsg(Device+' '+Msg+CRLF)>0;
 end;
 {
 Send remote device message...
 }
 procedure RemoteDevMsg(Device,Msg:String);
 begin
  Click_Send(ComposeClick(1,DevName,winDevMsg,senDevMsg,'','','',Device+' '+Msg));
 end;
 {
 Convert string lines with CRLF delimeters into text.
 }
 function StringToText(s:String):Integer;
 var t,p:Integer; b:Boolean;
 begin
  t:=text_new;
  while Length(s)>0 do begin
   p:=Pos(CRLF,s);
   if p=0 then p:=Length(s)+1;
   if p>1 then b:=text_addln(t,Copy(s,1,p-1));
   s:=Copy(s,p+2);
  end;
  StringToText:=t;
 end;
 {
 Compare guard levels.
 -1 if g1 less then  g2
  0 if g1 equavalent g2
 +1 if g1 more then  g2
 }
 function CompareGuards(g1,g2:String):Integer;
 const GuardList='LOCK,GUEST,USER,ROOT';
 var p,p1,p2:Integer;
 begin
  p1:=pos(UpCaseStr(Trim(g1)),GuardList);
  p2:=pos(UpCaseStr(Trim(g2)),GuardList);
  if (p1=0) or (p2=0) then p:=-1 else p:=sign(p2-p1);
  CompareGuards:=p;
 end;
 {
 Check if given (Guard,User,Host,IP,MAC) set have access rights.
 }
 function GrantAccessDim(Guard,User,Host,IP,MAC:String):Boolean;
 var t,i:Integer; w1,w2,w3,w4,w5:String; g,g1,g2,g3,g4,g5:Boolean;
 begin
  g:=false;
  w1:='';w2:='';w3:='';w4:='';w5:='';
  t:=ReadIniSection(text_new,16,'',ReadIni('TrustedUsers'));
  for i:=0 to text_numln(t)-1 do
  if WordCount(text_getln(t,i))=5 then begin
   w1:=ExtractWord(1,text_getln(t,i));  if w1='.' then w1:=ParamStr('Guard');
   w2:=ExtractWord(2,text_getln(t,i));  if w2='.' then w2:=ParamStr('UserName');
   w3:=ExtractWord(3,text_getln(t,i));  if w3='.' then w3:=ParamStr('HostName');
   w4:=ExtractWord(4,text_getln(t,i));  if w4='.' then w4:=ParamStr('IPAddress');
   w5:=ExtractWord(5,text_getln(t,i));  if w5='.' then w5:=ExtractWord(1,ParamStr('MACAddress'));
   g1:=IsSameText(w1,'*') or (CompareGuards(w1,Guard)>=0);
   g2:=IsSameText(w2,'*') or IsSameText(w2,User);
   g3:=IsSameText(w3,'*') or IsSameText(w3,Host);
   g4:=IsSameText(w4,'*') or IsSameText(w4,IP);
   g5:=IsSameText(w5,'*') or IsSameText(w5,ExtractWord(1,MAC));
   if g1 and g2 and g3 and g4 and g5 then g:=true;
  end;
  b:=text_free(t);
  w1:='';w2:='';w3:='';w4:='';w5:='';
  GrantAccessDim:=g;
 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;
 {
 Handle remote sensor click.
 }
 procedure HandleRemoteClick;
 var i:Integer; r:Real;
  procedure AssignTag(tag:Integer);
  begin
   if IsSameText(Click.Tag,NameTag(tag))
   then b:=DevSend(devCoolCtrl,'@AssignTag '+NameTag(tag)+'='+Click.NewValue+CRLF)>0;
  end;
 begin
  if Click.Button=1 then begin
   if CompareGuards('Guest',Click.Guard)>=0 then begin
    {
    Handle clicks in winCoolCtrl window...
    }
    if IsSameText(Click.Window,winCoolCtrl) then begin
     r:=rVal(Click.NewValue);
     {
     Compressor control settings
     }
     AssignTag(tagCompP1MIN);
     AssignTag(tagCompP1MAX);
     AssignTag(tagCompP2MIN);
     AssignTag(tagCompP2MAX);
     AssignTag(tagCompNBMIN);
     AssignTag(tagCompNBMAX);
     AssignTag(tagCompPOLLTM);
     AssignTag(tagCompDEADTM);
     AssignTag(tagPumpP3MIN);
     AssignTag(tagPumpP3MAX);
     AssignTag(tagBlkOpt);
     {
     Compressors
     }
     for i:=1 to nComps do begin
      AssignTag(tagComp[i].ENB);
      AssignTag(tagComp[i].POW);
     end;
     {
     Pumps
     }
     for i:=1 to nPumps do begin
      AssignTag(tagPump[i].ENB);
      AssignTag(tagPump[i].POW);
     end;
     {
     FAN
     }
     AssignTag(tagFAN.TON);
     AssignTag(tagFan.ENB);
     AssignTag(tagFan.POW);
     {
     StBy
     }
     AssignTag(tagStBy.TSTAB);
     AssignTag(tagStBy.DELTA);
     AssignTag(tagStBy.NBMIN);
     AssignTag(tagStBy.NBMAX);
     AssignTag(tagStBy.POLLTM);
     AssignTag(tagStBy.DEADTM);
     AssignTag(tagStBy.NBSEN);
     AssignTag(tagStBy.DTMAX);
     AssignTag(tagStBy.Q[1]);
     AssignTag(tagStBy.Q[2]);
     AssignTag(tagStBy.Q[3]);
     {
     Cool
     }
     AssignTag(tagCool.TSTAB);
     AssignTag(tagCool.DELTA);
     AssignTag(tagCool.NBMIN);
     AssignTag(tagCool.NBMAX);
     AssignTag(tagCool.POLLTM);
     AssignTag(tagCool.DEADTM);
     AssignTag(tagCool.NBSEN);
     AssignTag(tagCool.DTMAX);
     AssignTag(tagCool.Q[1]);
     AssignTag(tagCool.Q[2]);
     AssignTag(tagCool.Q[3]);
     {
     Warm
     }
     AssignTag(tagWarm.TSTAB);
     AssignTag(tagWarm.DELTA);
     AssignTag(tagWarm.NBMIN);
     AssignTag(tagWarm.NBMAX);
     AssignTag(tagWarm.POLLTM);
     AssignTag(tagWarm.DEADTM);
     AssignTag(tagWarm.NBSEN);
     AssignTag(tagWarm.DTMAX);
     AssignTag(tagWarm.Q[1]);
     AssignTag(tagWarm.Q[2]);
     AssignTag(tagWarm.Q[3]);
     {
     Heats
     }
     for i:=1 to nHeats do begin
      AssignTag(tagHeat[i].ENB);
      AssignTag(tagHeat[i].POW);
      AssignTag(tagHeat[i].Q);
      AssignTag(tagHeat[i].QMAX);
     end;
     {
     Commands
     }
     AssignTag(tagCmdEcv);
     AssignTag(tagCmdBlk);
     AssignTag(tagCmdCtrl);
     AssignTag(tagLiq.ENB);
     AssignTag(tagLiq.VE2);
     AssignTag(tagLiq.TMIN);
     AssignTag(tagLiq.TMAX);
     AssignTag(tagLiq.TSTAB);
     AssignTag(tagLiq.DELTA);
     AssignTag(tagLiq.NBSEN);
     AssignTag(tagLiq.DTMAX);
     AssignTag(tagFreon.ENB);
     AssignTag(tagFreon.VE1);
     AssignTag(tagInterlock);
     AssignTag(tagLoadParams);
     AssignTag(tagSaveParams);
     AssignTag(tagHeatsT4max);
    end;
    {
    Handle clicks in winDevMsg window...
    }
    if IsSameText(Click.Window,winDevMsg) then begin
     {
     Handle RemoteDevMsg
     }
     if IsSameText(Click.Sensor,senDevMsg) then b:=DevMsg(Click.NewValue+CRLF)>0;
    end;
   end else begin
    DIM_UpdateTag(tagNotify,'DENY '+Click.Guard+':'+Click.User+'@'+Click.Host+' ('+Click.IP+')!');
   end;
  end;
 end;
 {
 Decode full sensor click information from long string...
 }
 procedure DecodeClick(s:String);
 var t,i,p:Integer;
  procedure ClearClick;
  begin
   Click.Button:=0;   Click.Cmnd:='';    Click.Data:='';
   Click.Sensor:='';  Click.Device:='';  Click.Window:='';
   Click.Value:='';   Click.Curve:='';   Click.Tag:='';
   Click.Guard:='';   Click.User:='';    Click.Host:='';
   Click.IP:='';      Click.MAC:='';     Click.newValue:='';
  end;
 begin
  ClearClick;
  t:=StringToText(s);
  if IsSameText(text_getln(t,0),'@Click') then begin
   for i:=1 to text_numln(t)-1 do begin
    p:=Pos('=',text_getln(t,i)); if p=0 then p:=Length(text_getln(t,i))+1;
    Click.Cmnd:=Copy(text_getln(t,i),1,p-1);
    Click.Data:=Copy(text_getln(t,i),p+1);
    if IsSameText(Click.Cmnd,'Button')   then Click.Button:=Val(Click.Data);
    if IsSameText(Click.Cmnd,'Sensor')   then Click.Sensor:=Click.Data;
    if IsSameText(Click.Cmnd,'Device')   then Click.Device:=Click.Data;
    if IsSameText(Click.Cmnd,'Window')   then Click.Window:=Click.Data;
    if IsSameText(Click.Cmnd,'Value')    then Click.Value:=Click.Data;
    if IsSameText(Click.Cmnd,'Curve')    then Click.Curve:=Click.Data;
    if IsSameText(Click.Cmnd,'Tag')      then Click.Tag:=Click.Data;
    if IsSameText(Click.Cmnd,'User')     then Click.User:=Click.Data;
    if IsSameText(Click.Cmnd,'Host')     then Click.Host:=Click.Data;
    if IsSameText(Click.Cmnd,'IP')       then Click.IP:=Click.Data;
    if IsSameText(Click.Cmnd,'MAC')      then Click.MAC:=Click.Data;
    if IsSameText(Click.Cmnd,'Guard')    then Click.Guard:=Click.Data;
    if IsSameText(Click.Cmnd,'newValue') then Click.newValue:=Click.Data;
   end;
  end;
  b:=text_free(t);
  if Click.Button<>0 then begin
   if GrantAccessDim(Click.Guard,Click.User,Click.Host,Click.IP,Click.MAC)
   then HandleRemoteClick
   else DIM_UpdateTag(tagNotify,'DENY '+Click.Guard+':'+Click.User+'@'+Click.Host+' ('+Click.IP+')!');
  end;
  ClearClick;
 end;
 {
 Finalize GUI system.
 }
 procedure GUI_Free;
 begin
 end;
 {
 Initialize GUI system.
 }
 procedure GUI_Init;
 begin
  HostWatchDog:=msecnow+WatchDogTimeOut;
  {
  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].WTM, StrReplace(ReadIni('tagCompWTM'),'##',Str(i),3), 2);
   InitTag(tagComp[i].POW, StrReplace(ReadIni('tagCompPOW'),'##',Str(i),3), 1);
   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].WTM, StrReplace(ReadIni('tagPumpWTM'),'##',Str(i),3), 2);
   InitTag(tagPump[i].POW, StrReplace(ReadIni('tagPumpPOW'),'##',Str(i),3), 1);
   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(tagCmdClose,   ReadIni('tagCmdClose'),  1);
  InitTag(tagEcvIp,      ReadIni('tagEcvIp'),     3);
  InitTag(tagLoadParams, ReadIni('tagLoadParams'),1);
  InitTag(tagSaveParams, ReadIni('tagSaveParams'),1);
  InitTag(tagHeatsT4max, ReadIni('tagHeatsT4max'),2);
  InitTag(tagCmdParam,   ReadIni('tagCmdParam'),  1);
  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 windows
  }
  winCoolTable:=ReadIni('winCoolTable');
  if Length(winCoolTable)>0
  then Success('winCoolTable='+winCoolTable)
  else Trouble('winCoolTable=?');
  if RefFind('Window '+winCoolTable)<>0 then begin
   b:=winshow(winCoolTable);
   b:=windraw(winCoolTable+'|Top=0|Left=977');
   b:=winselect(winCoolTable);
  end;
  winCoolPlot:=ReadIni('winCoolPlot');
  if Length(winCoolPlot)>0
  then Success('winCoolPlot='+winCoolPlot)
  else Trouble('winCoolPlot=?');
  if RefFind('Window '+winCoolPlot)<>0 then begin
   b:=winshow(winCoolPlot);
   b:=windraw(winCoolPlot+'|Top=337|Left=20|Width=600|Height=317');
   b:=WinDraw(winCoolPlot+'|Options=-Min,-Max,+Close');
   b:=winselect(winCoolPlot);
   b:=winhide(winCoolPlot);
  end;
  winCoolCtrl:=ReadIni('winCoolCtrl');
  if Length(winCoolCtrl)>0
  then Success('winCoolCtrl='+winCoolCtrl)
  else Trouble('winCoolCtrl=?');
  if RefFind('Window '+winCoolCtrl)<>0 then begin
   b:=WinShow(winCoolCtrl);
   b:=WinDraw(winCoolCtrl+'|Left=167|Top=0|Width=810|Height=440');
   b:=WinDraw(winCoolCtrl+'|Options=-Min,-Max,-Close,-Width,-Height,-HScroll,-VScroll');
   b:=WinSelect(winCoolCtrl);
  end;
  winCtrlList:=winCtrlList+winCoolCtrl+';';
  winCtrlList:=winCtrlList+winCoolPlot+';';
  b:=WinHide(ParamStr('Console &DatSrv'));
 end;
 {
 GUI polling actions
 }
 procedure GUI_Polling;
 var i,n:Integer;
 begin
  {
  Write tags to curves for saving
  }
  n:=0;
  for i:=1 to nComps do begin
   UpdateDo(n,time,iGetTag(tagComp[i].ENB)); n:=n+1;
   UpdateDo(n,time,iGetTag(tagComp[i].POW)); n:=n+1;
   UpdateDo(n,time,rGetTag(tagComp[i].WTM)); n:=n+1;
   UpdateDo(n,time,iGetTag(tagComp[i].THR)); n:=n+1;
   UpdateDo(n,time,iGetTag(tagComp[i].OIL)); n:=n+1;
   UpdateDo(n,time,iGetTag(tagComp[i].LPR)); n:=n+1;
   UpdateDo(n,time,iGetTag(tagComp[i].HPR)); n:=n+1;
  end;
  for i:=1 to nPumps do begin
   UpdateDo(n,time,iGetTag(tagPump[i].ENB)); n:=n+1;
   UpdateDo(n,time,iGetTag(tagPump[i].POW)); n:=n+1;
   UpdateDo(n,time,rGetTag(tagPump[i].WTM)); n:=n+1;
   UpdateDo(n,time,iGetTag(tagPump[i].THR)); n:=n+1;
  end;
  UpdateDo(n,time,iGetTag(tagFREON.ENB)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagFREON.VE1)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagFREON.LPR)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagFREON.HPR)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagLIQ.ENB)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagLIQ.VE2)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagLIQ.HLR)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagLIQ.LLR)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagFAN.ENB)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagFAN.POW)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagFAN.WTM)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagFAN.TON)); n:=n+1;
  for i:=1 to nHeats do begin
   UpdateDo(n,time,iGetTag(tagHEAT[i].ENB)); n:=n+1;
   UpdateDo(n,time,iGetTag(tagHEAT[i].POW)); n:=n+1;
   UpdateDo(n,time,iGetTag(tagHEAT[i].SSR)); n:=n+1;
   UpdateDo(n,time,iGetTag(tagHEAT[i].PER)); n:=n+1;
   UpdateDo(n,time,rGetTag(tagHEAT[i].Q));   n:=n+1;
  end;
  UpdateDo(n,time,rGetTag(tagLIQ.TMIN)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagLIQ.TMAX)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagLIQ.TSTAB)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagLIQ.DELTA)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagLIQ.NBSEN)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagLIQ.DTMAX)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagLIQ.TSENS)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagLIQ.TGOAL)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagLIQ.TAVER)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagCOMPP1MIN)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagCOMPP1MAX)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagCOMPP2MIN)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagCOMPP2MAX)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagCOMPNBMIN)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagCOMPNBMAX)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagCOMPPOLLTM)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagCOMPDEADTM)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagPUMPP3MIN)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagPUMPP3MAX)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagHEATST4MAX)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagDaqBugs)); n:=n+1;
  UpdateDo(n,time,rGetTag(tagComBugs)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagInterlock)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagCmdCtrl)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagCmdBlk)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagBlkNum)); n:=n+1;
  UpdateDo(n,time,iGetTag(tagBlkOpt)); n:=n+1;
 end;
 {
 Clear WEB table.
 }
 procedure WEB_Clear;
 begin
  {
  Do not zero WEB.QueryItems, WEB.CookieItems, WEB.ContentItems there!
  }
  ClearText(WEB.QueryItems);
  ClearText(WEB.CookieItems);
  ClearText(WEB.ContentItems);
  WEB.Sender:=0;
  WEB.ReqTime:=0;
  WEB.Request:=0;
  WEB.Reply:=0;
  WEB.QueryCount:=0;
  WEB.CookieCount:=0;
  WEB.ContentCount:=0;
  WEB.GatewayInterface:='';
  WEB.RequestMethod:='';
  WEB.QueryString:='';
  WEB.Content:='';
  WEB.ContentLength:=0;
  WEB.ContentType:='';
  WEB.ServerName:='';
  WEB.ServerPort:=0;
  WEB.ServerProtocol:='';
  WEB.ServerSoftware:='';
  WEB.RemoteHost:='';
  WEB.RemoteAddr:='';
  WEB.ScriptName:='';
  WEB.PathInfo:='';
  WEB.PathTranslated:='';
  WEB.WebSrvName:='';
  WEB.WebSrvSite:='';
  WEB.WebSrvRoot:='';
  WEB.WebSrvPort:=0;
  WEB.WebSrvIndex:='';
  WEB.UserName:='';
  WEB.Password:='';
  WEB.DateTime:='';
 end;
 {
 Clear all strings
 }
 procedure ClearStrings;
 var i:Integer;
 begin
  s:='';
  WEB_Clear;
  Click_Info:='';
  StdIn_Line:='';
  winCoolCtrl:='';   winCoolPlot:='';   winCoolTable:='';  winCtrlList:='';
  Click.Cmnd:='';    Click.Data:='';    Click.Sensor:='';  Click.Device:='';
  Click.Window:='';  Click.Value:='';   Click.Curve:='';   Click.Tag:='';
  Click.Guard:='';   Click.User:='';    Click.Host:='';    Click.IP:='';
  Click.MAC:='';     Click.newValue:='';
  if runcount=1 then fixmaxavail:=maxavail;
  if isinf(runcount) then
  if maxavail<>fixmaxavail then Trouble('String Manager Leak = '+str(fixmaxavail-maxavail));
 end;
 {
 Call HTML browser.
 }
 procedure WebBrowser(FileName:String);
 var WebViewer:String; p:Integer;
 begin
  FileName:=Trim(FileName);
  if Length(FileName)=0 then FileName:='http://localhost/index.htm';
  WebViewer:=ParamStr('GetExeByFile '+DaqFileRef('..\index.htm',''));
  if FileExists(WebViewer) then begin
   p:=task_init('"'+WebViewer+'" "'+FileName+'"');
   if task_run(p)
   then Success('Start pid '+Str(task_pid(p))+': '+task_ctrl(p,'CmdLine'))
   else Success('Could not browse '+FileName);
   b:=task_Free(p);
  end else begin
   if ShellExecute('open|'+FileName)<=32 then Success('Could not open '+FileName);
  end;
  WebViewer:='';
 end;
 {
 Initialize WEB server.
 }
 procedure WEB_Init;
 begin
  WEB_Clear;
  WEB.QueryItems:=text_New;
  WEB.CookieItems:=text_New;
  WEB.ContentItems:=text_New;
  WEB.TrustedUsers:=text_New;
 end;
 {
 Finalize WEB server.
 }
 procedure WEB_Free;
 var b:Boolean;
 begin
  WEB_Clear;
  b:=text_Free(WEB.QueryItems);
  b:=text_Free(WEB.CookieItems);
  b:=text_Free(WEB.ContentItems);
  b:=text_Free(WEB.TrustedUsers);
  WEB.QueryItems:=0;
  WEB.CookieItems:=0;
  WEB.ContentItems:=0;
  WEB.TrustedUsers:=0;
 end;
 {
 Add string to WEB.Reply text.
 }
 procedure WEB_Reply(s:String);
 var b:Boolean;
 begin
  b:=text_Addln(WEB.Reply,s);
 end;
 {
 Return true if some data posted from HTML form.
 }
 function WEB_FormDataPosted:Boolean;
 begin
  WEB_FormDataPosted:=IsSameText(WEB.RequestMethod,'Post') and 
                      IsSameText(WEB.ContentType,'application/x-www-form-urlencoded');
 end;
 {
 Set meta tags to refresh site.
 }
 procedure WEB_MetaRefresh(Period:Integer; Link:String);
 begin
  WEB_Reply('<meta http-equiv="Cache-Control" content="no-cache">');
  WEB_Reply('<meta http-equiv="Pragma" content="no-cache">');
  if Period>=0 then begin
   if Length(Link)>0 then Link:=','+Link;
   WEB_Reply('<meta http-equiv="Refresh" content="'+Str(Period)+Link+'">');
  end;
 end;
 {
 Set meta tag Set-Cookie Name=Value.
 }
 procedure WEB_MetaSetCookie(Name,Data:String);
 begin
  if Length(Name)>0 then
  WEB_Reply('<meta http-equiv="Set-Cookie" content="'+Name+'='+Data+'">');
 end;
 {
 Show foot note.
 }
 procedure WEB_ShowFootNote;
 begin
  WEB_Reply('<hr>');
  WEB_Reply('<small><i>');
  WEB_Reply('<b>AliPhosCool</b> = <b>ALI</b>ce <b>PHOS</b> <b>COOL</b>ing system.');
  WEB_Reply('2007, Россия, Саров, (83130)-28817,');
  WEB_Reply('<a href="mailto:kouriakine@mail.ru">kouriakine@mail.ru</a>,');
  WEB_Reply('<a href="mailto:vinogr@rol.ru">vinogr@rol.ru</a>.');
  WEB_Reply('</i></small>');
  WEB_Reply('<hr>');
 end;
 {
 Show access and update time.
 }
 procedure WEB_ShowAccessTime(Access:Integer; Time:String);
 begin
  WEB_Reply('<small>');
  WEB_Reply('<b>Host:</b><font color=blue><i>'+ExtractWord(2,sGetTag(tagHostInfo))+'</i></font>');
  if Access>=0 then
  WEB_Reply('&nbsp;<b>Access:</b><font color=blue><i>'+ExtractWord(Access,acc_List)+'</i></font>');
  if Length(Time)>0 then
  WEB_Reply('&nbsp;<b>Time:</b><font color=blue><i>'+Time+'</i></font>');
  WEB_Reply('</small>');
 end;
 {
 Show header "Name: Comment".
 }
 procedure WEB_ShowHeader(Name,Comment:String);
 begin
  WEB_Reply('<hr>');
  WEB_Reply('<font size="+3"><b><font color="Red">'+Name+'</font>: '+Comment+'</b></font>');
  WEB_Reply('<hr>');
 end;
 {
 HTML page to show error.
 }
 procedure WEB_ErrorPage(Msg:String);
 var i:Integer; b:Boolean;
 begin
  WEB_Reply('<html>');
  WEB_Reply('<head>');
  WEB_MetaSetCookie('RemoteUserName','');
  WEB_MetaSetCookie('RemotePassword','');
  WEB_Reply('<title>AliPhosCool: Error!</title>');
  WEB_Reply('</head>');
  WEB_Reply('<body background="../bitmaps/background.jpg" bgcolor="#f0f0ff" text="black" lang="ru">');
  WEB_Reply('<font face="Courier New" size="3">');
  WEB_ShowHeader('AliPhosCool','Error!');
  WEB_Reply(Msg);
  WEB_ShowFootNote;
  WEB_Reply('</font>');
  WEB_Reply('</body>');
  WEB_Reply('</html>');
  WEB_Reply(dump(0));
 end;
 {
 HTML page to show Access Denied page.
 }
 procedure WEB_AccessDeniedPage;
 begin
  WEB_ErrorPage('<font size="+2"><b>AliPhosCool: <font color="Red">Access denied.</font><br>'
               +'You have to <a href="'+Web.ScriptName
               +'?action=login&timestamp='+Str(msecnow)+'">Login</a> first.<b></font>');
 end;
 {
 HTML page to see HTTP echo in DebugEcho mode, for tests only.
 }
 procedure WEB_EchoPage;
 var i:Integer; b:Boolean;
 begin
  WEB_Reply('<html>');
  WEB_Reply('<head>');
  WEB_Reply('<title>HTTP echo</title>');
  WEB_Reply('</head>');
  WEB_Reply('<body background="../bitmaps/background.jpg" bgcolor="#f0f0ff" text="black" lang="ru">');
  WEB_Reply('<font face="Courier New" size="3">');
  WEB_ShowHeader('AliPhosCool','HTTP request echo');
  WEB_Reply('<pre>');
  for i:=0 to text_Numln(WEB.Request)-1 do WEB_Reply('   '+text_Getln(WEB.Request,i));
  WEB_Reply('</pre>');
  WEB_ShowAccessTime(-1,WEB.DateTime);
  WEB_ShowFootNote;
  WEB_Reply('</font>');
  WEB_Reply('</body>');
  WEB_Reply('</html>');
  WEB_Reply(dump(0));
 end;
 {
 Grant access to (User,Host,IP,Password).
 Return 0/1/2/3=Deny/Guest/User/Root access level.
 }
 function GrantAccess(User,Host,IP,Password:String):Integer;
 var i:Integer; w1,w2,w3,w4,w5:String; g,g1,g2,g3,g4,g5:Integer;
 begin
  g:=acc_Deny;
  w1:='';w2:='';w3:='';w4:='';w5:='';
  if text_NumLn(WEB.TrustedUsers)=0 then begin
   if Length(ReadIni('TrustedUsers'))>0 then begin
    i:=ReadIniSection(WEB.TrustedUsers,16,'',ReadIni('TrustedUsers'));
    if text_numln(WEB.TrustedUsers)=0
    then Trouble('TrustedUsers section is empty!');
   end else Trouble('TrustedUsers is not specified!');
  end;
  for i:=0 to text_numln(WEB.TrustedUsers)-1 do
  if WordCount(text_getln(WEB.TrustedUsers,i))=5 then begin
   w1:=ExtractWord(1,text_getln(WEB.TrustedUsers,i));
   g1:=WordIndex(w1,acc_webList);
   if g1>acc_Deny then begin
    w2:=ExtractWord(2,text_getln(WEB.TrustedUsers,i));
    if w2='.' then w2:=ParamStr('UserName');
    g2:=Ord(IsSameText(w2,'*') or IsSameText(w2,User));
    if g2>acc_Deny then begin
     w3:=ExtractWord(3,text_getln(WEB.TrustedUsers,i));
     if w3='.' then w3:=ParamStr('HostName');
     g3:=Ord(IsSameText(w3,'*') or IsSameText(w3,Host));
     if g3>acc_Deny then begin
      w4:=ExtractWord(4,text_getln(WEB.TrustedUsers,i));
      if w4='.' then w4:=ParamStr('IPAddress');
      g4:=Ord(IsSameText(w4,'*') or IsSameText(w4,IP));
      if g4>acc_Deny then begin
       w5:=ExtractWord(5,text_getln(WEB.TrustedUsers,i));
       g5:=Ord(IsSameText(w5,'*') or (w5=Password) or
              (Trim(Crypt_Decode(w5,'crw-daq.ru'))=Password));
      end;
     end;
    end;
   end;
   g:=Round(Max(g,g1*g2*g3*g4*g5));
  end;
  w1:='';w2:='';w3:='';w4:='';w5:='';
  GrantAccess:=g;
 end;
 {
 Grant access for HTTP request.
 Mode bits are:
 1 - Get username & password from Cookie items.
 }
 function AccessGranted(Mode:Integer):Integer;
 begin
  if iAnd(Mode,1)>0 then begin
   {get username&password from cookies}
   if GetStringVar(WEB.CookieItems,'RemoteUserName',WEB.UserName)
   then WEB.UserName:=Trim(Crypt_Decode(WEB.UserName,dump(TimeBase)));
   if GetStringVar(WEB.CookieItems,'RemotePassword',WEB.Password)
   then WEB.Password:=Trim(Crypt_Decode(WEB.Password,dump(TimeBase)));
  end;
  AccessGranted:=GrantAccess(WEB.UserName,WEB.RemoteHost,WEB.RemoteAddr,WEB.Password);
 end;
 {
 HTML page to view main menu.
 }
 procedure WEB_HomePage;
 var i,Access:Integer;
 begin
  Access:=AccessGranted(1);
  if Access>=acc_Guest then begin
   WEB_Reply('<html>');
   WEB_Reply('<head>');
   WEB_MetaRefresh(HomeRefresh,'');
   WEB_Reply('<title>AliPhosCool: Content</title>');
   WEB_Reply('</head>');
   WEB_Reply('<body background="../bitmaps/background.jpg" bgcolor="#f0f0ff" text="black" lang="ru">');
   WEB_Reply('<font face="Courier New" size="3">');
   WEB_ShowHeader('AliPhosCool','Content');
   WEB_Reply('<UL style="font-size:150%">');
   WEB_Reply('<LI><a href="'+WEB.ScriptName+'?Action=Table&Index=COOL.PT&TimeStamp='+Str(msecnow)
            +'" target="_blank"><b>COOL.PT</b> - table of cooling pressures and temperatures</a>');
   if Access>=acc_Root then begin
    for i:=1 to WordCount(winCtrlList) do
    if Length(ExtractWord(i,winCtrlList))>0 then
    WEB_Reply('<LI><a href="'+WEB.ScriptName+'?Action=Plot&Index='
             +URL_Encode(ExtractWord(i,winCtrlList))+'&TimeStamp='+Str(msecnow)
             +'" target="_blank"><b>Plot </b> '+ExtractWord(i,winCtrlList)+'...</a>');
   end;
   WEB_Reply('<LI><a href="/Help/aliphoscool.htm" target="_blank"><b>Help</b> on AliPhosCool facility</a>');
   WEB_Reply('</UL>');
   WEB_Reply('<p>');
   WEB_ShowAccessTime(Access,WEB.DateTime);
   WEB_ShowFootNote;
   WEB_Reply('</font>');
   WEB_Reply('</body>');
   WEB_Reply('</html>');
   WEB_Reply(dump(0));
  end else WEB_AccessDeniedPage;
 end;
 {
 HTML page to view table of current facility state.
 }
 procedure WEB_TablePage;
 var i,Access:Integer; b:Boolean; TableIndex:String;
  procedure AddTableRow3(Col1,Col2,Col3:String);
  begin
   WEB_Reply('<tr>');
   WEB_Reply('<td>'+Col1+'</td>');
   WEB_Reply('<td>'+Col2+'</td>');
   WEB_Reply('<td>'+Col3+'</td>');
   WEB_Reply('</tr>');
  end;
  procedure AddTableRow4(Col1,Col2,Col3,Col4:String);
  begin
   WEB_Reply('<tr>');
   WEB_Reply('<td>'+Col1+'</td>');
   WEB_Reply('<td>'+Col2+'</td>');
   WEB_Reply('<td>'+Col3+'</td>');
   WEB_Reply('<td>'+Col4+'</td>');
   WEB_Reply('</tr>');
  end;
 begin
  TableIndex:='';
  Access:=AccessGranted(1);
  if Access>=acc_Guest then begin
   if not GetStringVar(WEB.QueryItems,'Index',TableIndex) then begin
    WEB_ErrorPage('<font size="+2"><b>AliPhosCool: <font color="Red">Invalid request.</font><br>'
                 +'Please return to authorization site to <a href="'+Web.ScriptName
                 +'?action=login&timestamp='+Str(msecnow)+'">Login</a>.<b></font>')
   end else begin
    WEB_Reply('<html>');
    WEB_Reply('<head>');
    WEB_MetaRefresh(TableRefresh,'');
    WEB_Reply('<title>AliPhosCool: Table '+TableIndex+'</title>');
    WEB_Reply('</head>');
    WEB_Reply('<body background="../bitmaps/background.jpg" bgcolor="#f0f0ff" text="black" lang="ru">');
    WEB_Reply('<font face="Courier New" size="3">');
    WEB_ShowHeader('AliPhosCool','table '+TableIndex);
    //
    // Table COOL.PT
    //
    if IsSameText(TableIndex,'COOL.PT') then begin
     WEB_Reply('<table border=1 cellspacing=2 cellpadding=2>');
     AddTableRow4('<b><big>Name</big></b>',
                  '<b><big>Value</big></b>',
                  '<b><big>Unit</big></b>',
                  '<b><big>Comment</big></b>');
     AddTableRow4(NameTag(tagP[1]),
                  StrReplace(StrFix(rGetTag(tagP[1]),7,2),dump(' '),'&nbsp;',3),
                  '<center>Bar</center>',
                  '<i><small>Pressure in condensing tube</small></i>');
     AddTableRow4(NameTag(tagP[2]),
                  StrReplace(StrFix(rGetTag(tagP[2]),7,2),dump(' '),'&nbsp;',3),
                  '<center>Bar</center>',
                  '<i><small>Pressure in evaporating tube</small></i>');
     AddTableRow4(NameTag(tagP[3]),
                  StrReplace(StrFix(rGetTag(tagP[3]),7,2),dump(' '),'&nbsp;',3),
                  '<center>Bar</center>',
                  '<i><small>Pressure in C<sub>6</sub>F<sub>14</sub> pipe</small></i>');
     AddTableRow4(NameTag(tagTP2),
                  StrReplace(StrFix(rGetTag(tagTP2),7,2),dump(' '),'&nbsp;',3),
                  '<center>&#176;C</center>',
                  '<i><small>Temperature of evaporating (by pressure P2)</small></i>');
     AddTableRow4(NameTag(tagT[1]),
                  StrReplace(StrFix(rGetTag(tagT[1]),7,2),dump(' '),'&nbsp;',3),
                  '<center>&#176;C</center>',
                  '<i><small>Temperature before heat exchange</small></i>');
     AddTableRow4(NameTag(tagT[2]),
                  StrReplace(StrFix(rGetTag(tagT[2]),7,2),dump(' '),'&nbsp;',3),
                  '<center>&#176;C</center>',
                  '<i><small>Temperature of air</small></i>');
     AddTableRow4(NameTag(tagT[3]),
                  StrReplace(StrFix(rGetTag(tagT[3]),7,2),dump(' '),'&nbsp;',3),
                  '<center>&#176;C</center>',
                  '<i><small>Temperature of C<sub>6</sub>F<sub>14</sub> in receiver</small></i>');
     AddTableRow4(NameTag(tagT[4]),
                  StrReplace(StrFix(rGetTag(tagT[4]),7,2),dump(' '),'&nbsp;',3),
                  '<center>&#176;C</center>',
                  '<i><small>Temperature of C<sub>6</sub>F<sub>14</sub> in PHOS input</small></i>');
     AddTableRow4(NameTag(tagT[5]),
                  StrReplace(StrFix(rGetTag(tagT[5]),7,2),dump(' '),'&nbsp;',3),
                  '<center>&#176;C</center>',
                  '<i><small>Temperature of ??</small></i>');
     WEB_Reply('</table>');
    end else begin
     WEB_Reply('Unknown request:'+Web.QueryString);
    end;
    WEB_Reply('<p>');
    WEB_ShowAccessTime(Access,WEB.DateTime);
    WEB_ShowFootNote;
    WEB_Reply('</font>');
    WEB_Reply('</body>');
    WEB_Reply('</html>');
    WEB_Reply(dump(0));
   end;
  end else WEB_AccessDeniedPage;
  TableIndex:='';
 end;
 {
 HTML page to plot curves.
 }
 procedure WEB_PlotPage;
 var i,n,Access:Integer; b:Boolean; ms:Real;  gif,PlotIndex:String;
 begin
  n:=0;
  gif:='';
  PlotIndex:='';
  Access:=AccessGranted(1);
  if Access>=acc_Guest then begin
   if not GetStringVar(WEB.QueryItems,'Index',PlotIndex) then begin
    WEB_ErrorPage('<font size="+2"><b>AliPhosCool: <font color="Red">Invalid request.</font><br>'
                 +'Please return to authorization site to <a href="'+Web.ScriptName
                 +'?action=login&timestamp='+Str(msecnow)+'">Login</a>.<b></font>')
   end else begin
    WEB_Reply('<html>');
    WEB_Reply('<head>');
    WEB_MetaRefresh(PlotRefresh,'');
    WEB_Reply('<title>AliPhosCool: plot '+PlotIndex+'</title>');
    WEB_Reply('</head>');
    WEB_Reply('<body background="../bitmaps/background.jpg" bgcolor="#f0f0ff" text="black" lang="ru">');
    WEB_Reply('<font face="Courier New" size="3">');
    WEB_ShowHeader('AliPhosCool','plot '+PlotIndex);
    for i:=1 to WordCount(winCtrlList) do
    if IsSameText(PlotIndex,ExtractWord(i,winCtrlList)) then begin
     gif:=Hex_Encode(GetMD5FromStr(PlotIndex))+'.gif';
     b:=FileErase(DaqFileRef('..\Temp\'+gif,''));
     b:=WinShow(PlotIndex);
     b:=WinDraw(PlotIndex+'|SaveBmp=4bit,..\Temp\'+gif);
     ms:=msecnow;
     while (msecnow-ms<PlotTimeOut) and not FileExists(DaqFileRef('..\Temp\'+gif,'')) do b:=Sleep(1);
     WEB_Reply('<p>');
     WEB_Reply('<img src="/Temp/'+gif+'" border="1" alt="'+PlotIndex+' plot"><br>');
     n:=n+1;
    end;
    if n=0 then begin
     WEB_Reply('Неизвестный запрос:'+Web.QueryString);
    end;
    WEB_Reply('<p>');
    WEB_ShowAccessTime(Access,WEB.DateTime);
    WEB_ShowFootNote;
    WEB_Reply('</font>');
    WEB_Reply('</body>');
    WEB_Reply('</html>');
    WEB_Reply(dump(0));
   end;
  end else WEB_AccessDeniedPage;
  PlotIndex:='';
  gif:='';
 end;
 {
 HTTP page to make Login.
 }
 procedure WEB_LoginPage;
 var i,access:Integer; b:Boolean;
 begin
  WEB_Reply('<html>');
  WEB_Reply('<head>');
  {If data posted from HTML form...}
  if WEB_FormDataPosted then begin
   {If user sent Login form, save encrypted username&password in cookies for future}
   if GetStringVar(WEB.ContentItems,'RemoteUserName',WEB.UserName) then begin
    s:=WEB.UserName;
    while Length(s)<32 do s:=s+' ';
    WEB_MetaSetCookie('RemoteUserName',Crypt_Encode(s,dump(TimeBase)));
   end;
   if GetStringVar(WEB.ContentItems,'RemotePassword',WEB.Password) then begin
    s:=WEB.Password;
    while Length(s)<32 do s:=s+' ';
    WEB_MetaSetCookie('RemotePassword',Crypt_Encode(s,dump(TimeBase)));
   end;
   {check access, get username&password from content}
   access:=AccessGranted(0);
  end else begin
   {check access, get username&password from cookies}
   access:=AccessGranted(1);
  end;
  WEB_MetaRefresh(-1,'');
  WEB_Reply('<title>AliPhosCool: Login</title>');
  WEB_Reply('</head>');
  WEB_Reply('<body background="../bitmaps/background.jpg" bgcolor="#f0f0ff" text="black" lang="ru">');
  WEB_Reply('<font face="Courier New" size="3">');
  WEB_ShowHeader('AliPhosCool','Authorization');
  if access>acc_Deny then begin
   WEB_Reply('Access level <font color=red>'+ExtractWord(access,acc_List)+'</font> granted.');
   WEB_Reply('<P><font size=+2><b>Now you may <a href="'
            +WEB.ScriptName+'?action=home&timestamp='+Str(msecnow)+'">Come in</a> on site AliPhosCool.</b></font>');
  end else begin
   WEB_Reply('<form action="'+WEB.ScriptName+'?action=login" method="post">');
   WEB_Reply('<table>');
   WEB_Reply('<tr valign=top>');
   WEB_Reply('<td>');
   WEB_Reply('<font color="Blue" size="+1"><b>Stay where you are!</b></font><br>');
   WEB_Reply('<big>Your rank</big>');
   WEB_Reply('<select name="RemoteUserName" style="font-size:110%">');
   WEB_Reply('<option selected value="guest"> Soldier (guest) </option>');
   WEB_Reply('<option          value="user">  Captain (user) </option>');
   WEB_Reply('<option          value="root">  General (root) </option>');
   WEB_Reply('</select>');
   WEB_Reply('<p>');
   WEB_Reply('<big>Password:</big>');
   WEB_Reply('<input type="Password" name="RemotePassword"><br>');
   WEB_Reply('<p>');
   WEB_Reply('<pre style="font-size:100%">');
   WEB_Reply('User <font color=red>guest</font> can come in<br>');
   WEB_Reply('without password, but<br>');
   WEB_Reply('with restricted rights.');
   WEB_Reply('</pre>');
   WEB_Reply('<p>');
   WEB_Reply('<input type="Submit" value="Login">');
   WEB_Reply('<input type="Reset"  value="Clear">');
   WEB_Reply('</td>');
   WEB_Reply('<td>');
   WEB_Reply('<img src="../bitmaps/policeman.jpg" alt="Input password!">');
   WEB_Reply('</td>');
   WEB_Reply('</tr>');
   WEB_Reply('</table>');
   WEB_Reply('</form>');
  end;
  WEB_ShowFootNote;
  WEB_Reply('</font>');
  WEB_Reply('</body>');
  WEB_Reply('</html>');
  WEB_Reply(dump(0));
 end;
 {
 General procedure to process HTTP request.
 All HTTP request data is already located in WEB record when Processing called.
 User should add HTML page via WEB_Reply() and call WEB_Reply(dump(0)) in the end.
 }
 procedure WEB_Processing;
 begin
  if not IsSameText(WEB.RequestMethod,'Get') and not IsSameText(WEB.RequestMethod,'Post')
  then WEB_ErrorPage('Unsupported REQUEST_METHOD='+WEB.RequestMethod) else
  if GetStringVar(WEB.QueryItems,'Action',s) and IsSameText(s,'Login') then WEB_LoginPage else
  if GetStringVar(WEB.QueryItems,'Action',s) and IsSameText(s,'Home')  then WEB_HomePage  else
  if GetStringVar(WEB.QueryItems,'Action',s) and IsSameText(s,'Table') then WEB_TablePage else
  if GetStringVar(WEB.QueryItems,'Action',s) and IsSameText(s,'Plot')  then WEB_PlotPage  else
  if GetStringVar(WEB.QueryItems,'Action',s) and IsSameText(s,'Echo')  then WEB_EchoPage  else
  WEB_ErrorPage('<h3><font color="Red">Unknown action requested.</font></h3><br>'
               +'You should <a href="'+Web.ScriptName+'?action=login&timestamp='+Str(msecnow)+'">Login</a>.');
 end;
 {
 Handle @HTTP_REQUEST_ACCEPTED=ArgList message.
 ArgList is Sender,RequestTime,RequestText,ReplyText.
 Client should analyse RequestText, and put HTML page
 to ReplyText. Last line of ReplyText should be dump(0).
 }
 procedure WEB_HandleRequest(Msg:String);
 const MessageId='@HTTP_REQUEST_ACCEPTED';
 var cmd,arg:String; i,j,p:Integer;
 begin
  cmd:='';
  arg:='';
  WEB_Clear;
  Msg:=Trim(Msg);
  if IsSameText(ExtractWord(1,Msg),MessageId) then begin
   Msg:=Copy(Msg,Length(MessageId)+2);
   WEB.Sender:=RefFind('Device '+ExtractWord(1,Msg));
   WEB.ReqTime:=rVal(ExtractWord(2,Msg));
   WEB.Request:=Val(ExtractWord(3,Msg));
   WEB.Reply:=Val(ExtractWord(4,Msg));
   if IsSameText(RefInfo(WEB.Sender,'Type'),'Device') and
      IsSameText(RefInfo(WEB.Request,'Type'),'Text') and
      IsSameText(RefInfo(WEB.Reply,'Type'),'Text') and
      not IsNaN(WEB.ReqTime) and not IsInf(WEB.ReqTime)
   then begin
    WEB.DateTime:=GetDateTime(msecnow);
    for i:=0 to text_NumLn(WEB.Request)-1 do begin
     p:=Pos('=',text_GetLn(WEB.Request,i));
     cmd:=Copy(text_GetLn(WEB.Request,i),1,p-1);
     arg:=Copy(text_GetLn(WEB.Request,i),p+1);
     if IsSameText(cmd,'QUERY.COUNT') then begin
      WEB.QueryCount:=Val(arg);
      for j:=0 to WEB.QueryCount-1 do b:=text_Addln(WEB.QueryItems,'');
     end;
     for j:=0 to WEB.QueryCount-1 do
     if IsSameText(cmd,'QUERY.ITEM'+Str(j)) then b:=text_PutLn(WEB.QueryItems,j,arg);
     if IsSameText(cmd,'COOKIE.COUNT') then begin
      WEB.CookieCount:=Val(arg);
      for j:=0 to WEB.CookieCount-1 do b:=text_Addln(WEB.CookieItems,'');
     end;
     for j:=0 to WEB.CookieCount-1 do
     if IsSameText(cmd,'COOKIE.ITEM'+Str(j)) then b:=text_PutLn(WEB.CookieItems,j,arg);
     if IsSameText(cmd,'CONTENT.COUNT') then begin
      WEB.ContentCount:=Val(arg);
      for j:=0 to WEB.ContentCount-1 do b:=text_Addln(WEB.ContentItems,'');
     end;
     for j:=0 to WEB.ContentCount-1 do
     if IsSameText(cmd,'CONTENT.ITEM'+Str(j)) then b:=text_PutLn(WEB.ContentItems,j,arg);
     if IsSameText(cmd,'GATEWAY_INTERFACE') then WEB.GatewayInterface:=arg;
     if IsSameText(cmd,'QUERY_STRING') then WEB.QueryString:=arg;
     if IsSameText(cmd,'REQUEST_METHOD') then WEB.RequestMethod:=arg;
     if IsSameText(cmd,'CONTENT') then WEB.Content:=arg;
     if IsSameText(cmd,'CONTENT_LENGTH') then WEB.ContentLength:=Val(arg);
     if IsSameText(cmd,'CONTENT_TYPE') then WEB.ContentType:=arg;
     if IsSameText(cmd,'SERVER_NAME') then WEB.ServerName:=arg;
     if IsSameText(cmd,'SERVER_PORT') then WEB.ServerPort:=Val(arg);
     if IsSameText(cmd,'SERVER_PROTOCOL') then WEB.ServerProtocol:=arg;
     if IsSameText(cmd,'SERVER_SOFTWARE') then WEB.ServerSoftware:=arg;
     if IsSameText(cmd,'REMOTE_HOST') then WEB.RemoteHost:=arg;
     if IsSameText(cmd,'REMOTE_ADDR') then WEB.RemoteAddr:=arg;
     if IsSameText(cmd,'SCRIPT_NAME') then WEB.ScriptName:=arg;
     if IsSameText(cmd,'PATH_INFO') then WEB.PathInfo:=arg;
     if IsSameText(cmd,'PATH_TRANSLATED') then WEB.PathTranslated:=arg;
     if IsSameText(cmd,'WEBSRV.NAME') then WEB.WebSrvName:=arg;
     if IsSameText(cmd,'WEBSRV.SITE') then WEB.WebSrvSite:=arg;
     if IsSameText(cmd,'WEBSRV.ROOT') then WEB.WebSrvRoot:=arg;
     if IsSameText(cmd,'WEBSRV.PORT') then WEB.WebSrvPort:=Val(arg);
     if IsSameText(cmd,'WEBSRV.INDEX') then WEB.WebSrvIndex:=arg;
    end;
    if iAnd(DebugFlags,dfViewImp)<>0 then begin
     ViewImp('CGI: QUERY.COUNT='+Str(WEB.QueryCount));
     for j:=0 to WEB.QueryCount-1 do ViewImp('QUERY.ITEM'+Str(j)+'='+text_GetLn(WEB.QueryItems,j));
     ViewImp('CGI: COOKIE.COUNT='+Str(WEB.CookieCount));
     for j:=0 to WEB.CookieCount-1 do ViewImp('COOKIE.ITEM'+Str(j)+'='+text_GetLn(WEB.CookieItems,j));
     ViewImp('CGI: CONTENT.COUNT='+Str(WEB.ContentCount));
     for j:=0 to WEB.ContentCount do ViewImp('CONTENT.ITEM'+Str(j)+'='+text_GetLn(WEB.ContentItems,j));
     ViewImp('CGI: GATEWAY_INTERFACE='+WEB.GatewayInterface);
     ViewImp('CGI: QUERY_STRING='+WEB.QueryString);
     ViewImp('CGI: REQUEST_METHOD='+WEB.RequestMethod);
     ViewImp('CGI: CONTENT='+WEB.Content);
     ViewImp('CGI: CONTENT_LENGTH='+Str(WEB.ContentLength));
     ViewImp('CGI: CONTENT_TYPE='+WEB.ContentType);
     ViewImp('CGI: SERVER_NAME='+WEB.ServerName);
     ViewImp('CGI: SERVER_PORT='+Str(WEB.ServerPort));
     ViewImp('CGI: SERVER_PROTOCOL='+WEB.ServerProtocol);
     ViewImp('CGI: SERVER_SOFTWARE='+WEB.ServerSoftware);
     ViewImp('CGI: REMOTE_HOST='+WEB.RemoteHost);
     ViewImp('CGI: REMOTE_ADDR='+WEB.RemoteAddr);
     ViewImp('CGI: SCRIPT_NAME='+WEB.ScriptName);
     ViewImp('CGI: PATH_INFO='+WEB.PathInfo);
     ViewImp('CGI: PATH_TRANSLATED='+WEB.PathTranslated);
     ViewImp('CGI: WEBSRV.NAME='+WEB.WebSrvName);
     ViewImp('CGI: WEBSRV.SITE='+WEB.WebSrvSite);
     ViewImp('CGI: WEBSRV.ROOT='+WEB.WebSrvRoot);
     ViewImp('CGI: WEBSRV.PORT='+Str(WEB.WebSrvPort));
     ViewImp('CGI: WEBSRV.INDEX='+WEB.WebSrvIndex);
     ViewImp('CGI: DATETIME='+WEB.DateTime);
    end;
    {
    Now WEB structure ready to use
    We should use Request information, add HTML
    page text and dump(0) as end-marker to Reply.
    }
    if DebugEcho then WEB_EchoPage else WEB_Processing;
   end else WEB_ErrorPage('Invalid HTTP request.');
  end;
  WEB_Clear;
  cmd:='';
  arg:='';
 end;
 {
 Dialog with information message
 }
 procedure InfoBox(msg:string);
 var b:boolean;
 begin
  if editstate=0 
  then msg:=edit('('+msg)+edit(')Information')
  else b:=Echo(devname+' : '+msg);
 end;
 {
 Procedure to show sensor help
 }
 procedure SensorHelp(s:String);
 begin
  if Length(s)>0 then begin
   InfoBox(s);
   Speak(s);
  end;
 end;
 {
 Analyse data coming from standard input.
 }
 procedure StdIn_Process(Data:string);
 var cmd,arg:String; b:Boolean; tag:Integer;
 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,'@DebugEcho') then begin
    if not IsNan(rVal(arg)) then DebugEcho:=(rVal(arg)>0);
    Success(cmd+'='+Str(Ord(DebugEcho)));
    Data:='';
   end;
   {}
   if IsSameText(cmd,'@Browse') then begin
    if DevMsg('&WebSrv @Browse '+arg+CRLF)=0 then WebBrowser(arg);
    Data:='';
   end;
   {}
   if IsSameText(cmd,'@HTTP_REQUEST_ACCEPTED') then begin
    WEB_HandleRequest(Data);
    Data:='';
   end;
   {}
   if IsSameText('@Click',cmd) then begin
    if Length(arg)=0
    then arg:=sGetTag(tagClick)
    else arg:=Trim(mime_decode(arg));
    if Length(arg)>0 then DecodeClick(crypt_decode(arg,'crw-daq.ru'));
    b:=sSetTag(tagClick,'');
    Data:='';
   end;
   {}
   if IsSameText('@Notify',cmd) then begin
    if Length(arg)=0
    then arg:=sGetTag(tagNotify)
    else arg:=Trim(mime_decode(arg));
    if (Length(arg)>0) and not IsSameText(arg,'Ok') then begin
     b:=Echo(devname+' ! SERVER NOTIFY:');
     b:=Echo(devname+' ! '+arg);
     b:=WinSelect(ParamStr('MainConsole'));
    end;
    Data:='';
   end;
   {}
   if IsSameText('@Update',cmd) then begin
    tag:=FindTag(Trim(arg));
    if tag=tagP[1] then UpdateAo(0,time,rGetTag(tagP[1]));
    if tag=tagP[2] then UpdateAo(1,time,rGetTag(tagP[2]));
    if tag=tagP[3] then UpdateAo(2,time,rGetTag(tagP[3]));
    if tag=tagT[1] then UpdateAo(3,time,rGetTag(tagT[1]));
    if tag=tagT[2] then UpdateAo(4,time,rGetTag(tagT[2]));
    if tag=tagT[3] then UpdateAo(5,time,rGetTag(tagT[3]));
    if tag=tagT[4] then UpdateAo(6,time,rGetTag(tagT[4]));
    if tag=tagT[5] then UpdateAo(7,time,rGetTag(tagT[5]));
    if tag=tagTCJ  then UpdateAo(8,time,rGetTag(tagTCJ));
    if tag=tagTP2  then UpdateAo(9,time,rGetTag(tagTP2));
    Data:='';
   end;
   {}
   if IsSameText('@Shutdown',cmd) then begin
    if IsSameText('Daq',ExtractWord(1,arg)) then begin
     if IsSameText('Exit',    ExtractWord(2,arg)) then DoExitDaq;
     if IsSameText('Restart', ExtractWord(2,arg)) then DoRestartDaq(ParamStr('DaqConfigFile'),true);
    end;
    if IsSameText('Crw',ExtractWord(1,arg)) then begin
     if IsSameText('Exit',    ExtractWord(2,arg)) then DoExitCrw;
    end;
    if IsSameText('Win',ExtractWord(1,arg)) then begin
     if IsSameText('Exit',    ExtractWord(2,arg)) then DoExitWin('s');
     if IsSameText('Logout',  ExtractWord(2,arg)) then DoExitWin('l');
     if IsSameText('Restart', ExtractWord(2,arg)) then DoExitWin('r');
    end;
    Data:='';
   end;
   {}
   if Length(Data)>0 then begin
    Trouble(' Unrecognized command "'+Data+'".');
    Data:='';
   end;
  end;
  cmd:='';
  arg:='';
 end;
 {
 Main
 }
begin
 {
 Initialization actions on Start...
 }
 if runcount=1 then begin
  {
  Initialize errors...
  }
  errors:=0;
  errorcode:=registererr(devname);
  {
  Clear and initialize variables...
  }
  ClearStrings;
  DebugEcho:=False;
  DebugFlags:=val(ReadIni('DebugFlags'));
  OpenConsole(Val(ReadIni('OpenConsole')));
  Success('Start at '+GetDateTime(msecnow));
  if not IsSameText(crypt_ctrl('Kind='+ReadIni('EncryptMethod')),ReadIni('EncryptMethod'))
  then Trouble('Could not read EncryptMethod')
  else Success('EncryptMethod='+crypt_ctrl('Kind'));
  {
  Initialize DIM device reference...
  }
  devDimSrv:=RefFind('Device '+DimSrv);
  if devDimSrv=0 then Trouble('Could not find '+DimSrv);
  devCoolCtrl:=RefFind('Device '+ReadIni('devCoolCtrl'));
  if devCoolCtrl<>0 then Success('devCoolCtrl='+RefInfo(devCoolCtrl,'Name'));
  {
  Initialize GUI, Web server and connect to tags.
  }
  WEB_Init;
  GUI_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
  WEB_Free;
  GUI_Free;
  ClearStrings;
  Success('Stop.');
 end else
 {
 Actions on Poll
 }
 if Ok then begin
  {
  Process standard input...
  }
  while StdIn_Readln(StdIn_Line) do StdIn_Process(StdIn_Line);
  {
  GUI polling actions
  }
  GUI_Polling;
  {
  WatchDog: if HOST_TIME don't change for a long time,
  detect "connection lost" and reset state to "unknown".
  }
  if HostLastTime<>rGetTag(tagHostTime) then begin
   HostLastTime:=rGetTag(tagHostTime);
   HostWatchDog:=msecnow;
  end;
  if msecnow-HostWatchDog>WatchDogTimeout then begin
   b:=sSetTag(tagHostInfo,'Server disconnected or network failure.');
   HostWatchDog:=msecnow;
  end;
  {
  ECV control command
  }
  if iGetTag(tagCmdEcv)<>0 then begin
   b:=DevMsg(DevName+' @Browse http://'+Trim(sGetTag(tagEcvIp))+'/'+CRLF)>0;
   b:=iSetTag(tagCmdEcv,0);
  end;
  {
  Load/Save parameters command
  }
  if iGetTag(tagLoadParams)<>0 then begin
   DIM_UpdateTag(tagNotify,'Last saved parameters loaded from INI.');
   b:=DevSend(devCoolCtrl,'@LoadParams'+CRLF)>0;
   b:=iSetTag(tagLoadParams,0);
  end;
  if iGetTag(tagSaveParams)<>0 then begin
   DIM_UpdateTag(tagNotify,'Current control parameters saved to INI.');
   b:=DevSend(devCoolCtrl,'@SaveParams'+CRLF)>0;
   b:=iSetTag(tagSaveParams,0);
  end;
  {
  Close menu command
  }
  if iGetTag(tagCmdClose)<>0 then begin
   if editstate=0 then begin
    if pos('?',edit('(Close menu... ')
              +edit(' What do you want:')
              +edit(' Close '+winCoolCtrl)
              +edit(' Close/open ToolBar')
              +edit(' Close/open MainMenu')
              +edit(' Close/open StatusBar')
              +edit(' Exit     DAQ     on local host')
              +edit(' Restart  DAQ     on local host')
              +edit(' Exit     CRW-DAQ on local host')
              +edit(' Logout   WINDOWS on local host')
              +edit(' Restart  WINDOWS on local host')
              +edit(' Shutdown WINDOWS on local host')
              +edit(' Exit     DAQ     on remote host')
              +edit(' Restart  DAQ     on remote host')
              +edit(' Exit     CRW-DAQ on remote host')
              +edit(' Logout   WINDOWS on remote host')
              +edit(' Restart  WINDOWS on remote host')
              +edit(' Shutdown WINDOWS on remote host')
              +edit(')MenuList MENU_'+NameTag(tagCmdClose)))>0
    then Warning('Error initializing MenuList!');
   end else Warning('Cannot edit right now!');
   b:=iSetTag(tagCmdClose,0);
  end;
  {
  Handle dialog clicks
  }
  if iGetTag(tagCmdParam)<>0 then begin
   if iGetTag(tagCmdParam)=1 then begin
    Click_Send(ComposeClick(1,DevName,winCoolCtrl,
                NameTag(tagCompP1MIN),Str(rGetTag(tagCompP1MIN)),'',
                NameTag(tagCompP1MIN),Str(rGetTag(tagCompP1MIN))));
    Click_Send(ComposeClick(1,DevName,winCoolCtrl,
                NameTag(tagCompP1MAX),Str(rGetTag(tagCompP1MAX)),'',
                NameTag(tagCompP1MAX),Str(rGetTag(tagCompP1MAX))));
    Click_Send(ComposeClick(1,DevName,winCoolCtrl,
                NameTag(tagCompP2MIN),Str(rGetTag(tagCompP2MIN)),'',
                NameTag(tagCompP2MIN),Str(rGetTag(tagCompP2MIN))));
    Click_Send(ComposeClick(1,DevName,winCoolCtrl,
                NameTag(tagCompP2MAX),Str(rGetTag(tagCompP2MAX)),'',
                NameTag(tagCompP2MAX),Str(rGetTag(tagCompP2MAX))));
    Click_Send(ComposeClick(1,DevName,winCoolCtrl,
                NameTag(tagPumpP3MIN),Str(rGetTag(tagPumpP3MIN)),'',
                NameTag(tagPumpP3MIN),Str(rGetTag(tagPumpP3MIN))));
    Click_Send(ComposeClick(1,DevName,winCoolCtrl,
                NameTag(tagPumpP3MAX),Str(rGetTag(tagPumpP3MAX)),'',
                NameTag(tagPumpP3MAX),Str(rGetTag(tagPumpP3MAX))));
    Click_Send(ComposeClick(1,DevName,winCoolCtrl,
                NameTag(tagLiq.TMIN),Str(rGetTag(tagLiq.TMIN)),'',
                NameTag(tagLiq.TMIN),Str(rGetTag(tagLiq.TMIN))));
    Click_Send(ComposeClick(1,DevName,winCoolCtrl,
                NameTag(tagLiq.TMAX),Str(rGetTag(tagLiq.TMAX)),'',
                NameTag(tagLiq.TMAX),Str(rGetTag(tagLiq.TMAX))));
    Click_Send(ComposeClick(1,DevName,winCoolCtrl,
                NameTag(tagHeatsT4MAX),Str(rGetTag(tagHeatsT4MAX)),'',
                NameTag(tagHeatsT4MAX),Str(rGetTag(tagHeatsT4MAX))));
    Click_Send(ComposeClick(1,DevName,winCoolCtrl,
                NameTag(tagBlkOpt),Str(iGetTag(tagBlkOpt)),'',
                NameTag(tagBlkOpt),Str(iGetTag(tagBlkOpt))));
   end;
   b:=iSetTag(tagCmdParam,0);
  end;
  {
  Edit tags...
  }
  if editstate=1 then begin
   {
   Edit tags (remote)...
   }
   if CheckEditTag(tagFAN.TON,s)
   or CheckEditTag(tagLiq.TSTAB,s)
   or CheckEditTag(tagLiq.DELTA,s)
   or CheckEditTag(tagCompNBMIN,s)
   or CheckEditTag(tagCompNBMAX,s)
   or CheckEditTag(tagCompPOLLTM,s)
   or CheckEditTag(tagCompDEADTM,s)
   or CheckEditTag(tagLiq.NBSEN,s)
   or CheckEditTag(tagLiq.DTMAX,s)
   or CheckEditTag(tagHeat[1].Q,s)
   or CheckEditTag(tagHeat[2].Q,s)
   or CheckEditTag(tagHeat[3].Q,s)
   or CheckEditTag(tagStBy.TSTAB,s)
   or CheckEditTag(tagStBy.DELTA,s)
   or CheckEditTag(tagStBy.NBMIN,s)
   or CheckEditTag(tagStBy.NBMAX,s)
   or CheckEditTag(tagStBy.POLLTM,s)
   or CheckEditTag(tagStBy.DEADTM,s)
   or CheckEditTag(tagStBy.NBSEN,s)
   or CheckEditTag(tagStBy.DTMAX,s)
   or CheckEditTag(tagStBy.Q[1],s)
   or CheckEditTag(tagStBy.Q[2],s)
   or CheckEditTag(tagStBy.Q[3],s)
   or CheckEditTag(tagCool.TSTAB,s)
   or CheckEditTag(tagCool.DELTA,s)
   or CheckEditTag(tagCool.NBMIN,s)
   or CheckEditTag(tagCool.NBMAX,s)
   or CheckEditTag(tagCool.POLLTM,s)
   or CheckEditTag(tagCool.DEADTM,s)
   or CheckEditTag(tagCool.NBSEN,s)
   or CheckEditTag(tagCool.DTMAX,s)
   or CheckEditTag(tagCool.Q[1],s)
   or CheckEditTag(tagCool.Q[2],s)
   or CheckEditTag(tagCool.Q[3],s)
   or CheckEditTag(tagWarm.TSTAB,s)
   or CheckEditTag(tagWarm.DELTA,s)
   or CheckEditTag(tagWarm.NBMIN,s)
   or CheckEditTag(tagWarm.NBMAX,s)
   or CheckEditTag(tagWarm.POLLTM,s)
   or CheckEditTag(tagWarm.DEADTM,s)
   or CheckEditTag(tagWarm.NBSEN,s)
   or CheckEditTag(tagWarm.DTMAX,s)
   or CheckEditTag(tagWarm.Q[1],s)
   or CheckEditTag(tagWarm.Q[2],s)
   or CheckEditTag(tagWarm.Q[3],s)
   then begin
    Click_Send(Click_Info+'NewValue='+s+CRLF);
    Click_Info:='';
   end;
   {
   CLOSE menu.
   }
   if IsSameText(ExtractWord(1,edit('?ans 0')),'MENU_'+NameTag(tagCmdClose)) then begin
    if Val(ExtractWord(2,edit('?ans 0')))=1 then begin
     menu:=Val(edit('?ans 1'));
     if menu=0  then b:=WinHide(winCoolCtrl);
     if menu=1  then DoToolBar;
     if menu=2  then DoMainMenu;
     if menu=3  then DoStatusBar;
     if menu=4  then LocalDevMsg(DevName,'@Shutdown Daq Exit');
     if menu=5  then LocalDevMsg(DevName,'@Shutdown Daq Restart');
     if menu=6  then LocalDevMsg(DevName,'@Shutdown Crw Exit');
     if menu=7  then LocalDevMsg(DevName,'@Shutdown Win Logout');
     if menu=8  then LocalDevMsg(DevName,'@Shutdown Win Restart');
     if menu=9  then LocalDevMsg(DevName,'@Shutdown Win Exit');
     if menu=10 then RemoteDevMsg(DevName,'@Shutdown Daq Exit');
     if menu=11 then RemoteDevMsg(DevName,'@Shutdown Daq Restart');
     if menu=12 then RemoteDevMsg(DevName,'@Shutdown Crw Exit');
     if menu=13 then RemoteDevMsg(DevName,'@Shutdown Win Logout');
     if menu=14 then RemoteDevMsg(DevName,'@Shutdown Win Restart');
     if menu=15 then RemoteDevMsg(DevName,'@Shutdown Win Exit');
     Click_Info:='';
    end;
    s:=edit('');
   end;
  end;
  if editstate=1 then begin
   writeln('Unknown tag edition!');
   s:=edit('');
  end;
  if iand(editstate,9)<>0 then begin
   writeln('Dialog error detected!');
   s:=edit('');
  end;
  {
  Handle left button clicks...
  }
  if ClickButton=1 then begin
   {
   Encode click info to send to server later...
   }
   EncodeClick(Click_Info);
   {
   Handle clicks in PHOS.COOL.CTRL window.
   }
   if IsSameText(ClickParams('Window'),winCoolCtrl) then begin
    {
    Select Tx, Px curve on click
    }
    for i:=1 to nTs do
    if ClickTag=tagT[i] then begin
     b:=WinShow(winCoolPlot);
     b:=WinDraw(winCoolPlot+'|SelectCurve='+NameTag(ClickTag));
     b:=WinSelect(winCoolPlot);
     b:=Voice(snd_Wheel);
    end;
    for i:=1 to nPs do
    if ClickTag=tagP[i] then begin
     b:=WinShow(winCoolPlot);
     b:=WinDraw(winCoolPlot+'|SelectCurve='+NameTag(ClickTag));
     b:=WinSelect(winCoolPlot);
     b:=Voice(snd_Wheel);
    end;
    if (ClickTag=tagTP2)
    or (ClickTag=tagLiq.TSENS)
    or (ClickTag=tagLiq.TGOAL)
    or (ClickTag=tagLiq.TAVER)
    or (ClickTag=tagTCJ) then begin
     b:=WinShow(winCoolPlot);
     b:=WinDraw(winCoolPlot+'|SelectCurve='+NameTag(ClickTag));
     b:=WinSelect(winCoolPlot);
     b:=Voice(snd_Wheel);
    end;
    if ClickTag=tagBlkNum then begin
     b:=WinShow(ParamStr('Console '+RefInfo(devCoolCtrl,'Name')));
     b:=WinSelect(ParamStr('Console '+RefInfo(devCoolCtrl,'Name')));
     b:=Voice(snd_Wheel);
    end;
    {
    Compressors
    }
    for i:=1 to nComps do begin
     ClickBitXorRemote(tagComp[i].ENB,1);
     ClickBitXorRemote(tagComp[i].POW,1);
    end;
    {
    Pumps
    }
    for i:=1 to nPumps do begin
     ClickBitXorRemote(tagPump[i].ENB,1);
     ClickBitXorRemote(tagPump[i].POW,1);
    end;
    {
    Fan
    }
    if ClickTag=tagFAN.TON then begin
     StartEditTag(ClickTag,'Fan turn-on temperature, °C');
     b:=Voice(snd_Click);
    end;
    ClickBitXorRemote(tagFan.ENB,1);
    ClickBitXorRemote(tagFan.POW,1);
    {
    Heats
    }
    for i:=1 to nHeats do begin
     ClickBitXorRemote(tagHeat[i].ENB,1);
     ClickBitXorRemote(tagHeat[i].POW,1);
     if ClickTag=tagHeat[i].Q then begin
      if iGetTag(tagCmdCtrl)=cm_StBy then begin
       StartEditTag(tagStBy.Q[i],'Standby Heat '+Str(i)+' power, %');
       SimulateClick(Click_Info,tagStBy.Q[i]);
       b:=Voice(snd_Click);
      end else
      if iGetTag(tagCmdCtrl)=cm_Cool then begin
       StartEditTag(tagCool.Q[i],'Cooling Heat '+Str(i)+' power, %');
       SimulateClick(Click_Info,tagCool.Q[i]);
       b:=Voice(snd_Click);
      end else
      if iGetTag(tagCmdCtrl)=cm_Warm then begin
       StartEditTag(tagWarm.Q[i],'Warming Heat '+Str(i)+' power, %');
       SimulateClick(Click_Info,tagWarm.Q[i]);
       b:=Voice(snd_Click);
      end else
      if iGetTag(tagCmdCtrl)=cm_Hand then begin
       StartEditTag(ClickTag,'Hand Heat '+Str(i)+' power, %');
       b:=Voice(snd_Click);
      end else
      b:=Voice(snd_Fails);
     end;
    end;
    {
    Cooling control
    }
    if ClickTag=tagLiq.TSTAB then begin
     if iGetTag(tagCmdCtrl)=cm_StBy then begin
      StartEditTag(tagStBy.TSTAB,'Standby stabilization temperature, °C');
      SimulateClick(Click_Info,tagStBy.TSTAB);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Cool then begin
      StartEditTag(tagCool.TSTAB,'Cooling stabilization temperature, °C');
      SimulateClick(Click_Info,tagCool.TSTAB);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Warm then begin
      StartEditTag(tagWarm.TSTAB,'Warming stabilization temperature, °C');
      SimulateClick(Click_Info,tagWarm.TSTAB);
      b:=Voice(snd_Click);
     end else
     b:=Voice(snd_Fails);
    end;
    if ClickTag=tagLiq.DELTA then begin
     if iGetTag(tagCmdCtrl)=cm_StBy then begin
      StartEditTag(tagStBy.DELTA,'Standby ± delta T, °C');
      SimulateClick(Click_Info,tagStBy.DELTA);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Cool then begin
      StartEditTag(tagCool.DELTA,'Cooling ± delta T, °C');
      SimulateClick(Click_Info,tagCool.DELTA);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Warm then begin
      StartEditTag(tagWarm.DELTA,'Warming ± delta T, °C');
      SimulateClick(Click_Info,tagWarm.DELTA);
      b:=Voice(snd_Click);
     end else
     b:=Voice(snd_Fails);
    end;
    if ClickTag=tagCompNBMIN then begin
     if iGetTag(tagCmdCtrl)=cm_StBy then begin
      StartEditTag(tagStBy.NBMIN,'Standby min. compressor number, 0..4');
      SimulateClick(Click_Info,tagStBy.NBMIN);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Cool then begin
      StartEditTag(tagCool.NBMIN,'Cooling min. compressor number, 0..4');
      SimulateClick(Click_Info,tagCool.NBMIN);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Warm then begin
      StartEditTag(tagWarm.NBMIN,'Warming min. compressor number, 0..4');
      SimulateClick(Click_Info,tagWarm.NBMIN);
      b:=Voice(snd_Click);
     end else
     b:=Voice(snd_Fails);
    end;
    if ClickTag=tagCompNBMAX then begin
     if iGetTag(tagCmdCtrl)=cm_StBy then begin
      StartEditTag(tagStBy.NBMAX,'Standby max. compressor number, 0..4');
      SimulateClick(Click_Info,tagStBy.NBMAX);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Cool then begin
      StartEditTag(tagCool.NBMAX,'Cooling max. compressor number, 0..4');
      SimulateClick(Click_Info,tagCool.NBMAX);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Warm then begin
      StartEditTag(tagWarm.NBMAX,'Warming max. compressor number, 0..4');
      SimulateClick(Click_Info,tagWarm.NBMAX);
      b:=Voice(snd_Click);
     end else
     b:=Voice(snd_Fails);
    end;
    if ClickTag=tagCompPOLLTM then begin
     if iGetTag(tagCmdCtrl)=cm_StBy then begin
      StartEditTag(tagStBy.POLLTM,'Standby compressor polling period, sec');
      SimulateClick(Click_Info,tagStBy.POLLTM);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Cool then begin
      StartEditTag(tagCool.POLLTM,'Cooling compressor polling period, sec');
      SimulateClick(Click_Info,tagCool.POLLTM);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Warm then begin
      StartEditTag(tagWarm.POLLTM,'Warming compressor polling period, sec');
      SimulateClick(Click_Info,tagWarm.POLLTM);
      b:=Voice(snd_Click);
     end else
     b:=Voice(snd_Fails);
    end;
    if ClickTag=tagCompDEADTM then begin
     if iGetTag(tagCmdCtrl)=cm_StBy then begin
      StartEditTag(tagStBy.DEADTM,'Standby compressor dead time, sec');
      SimulateClick(Click_Info,tagStBy.DEADTM);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Cool then begin
      StartEditTag(tagCool.DEADTM,'Cooling compressor dead time, sec');
      SimulateClick(Click_Info,tagCool.DEADTM);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Warm then begin
      StartEditTag(tagWarm.DEADTM,'Warming compressor dead time, sec');
      SimulateClick(Click_Info,tagWarm.DEADTM);
      b:=Voice(snd_Click);
     end else
     b:=Voice(snd_Fails);
    end;
    if ClickTag=tagLiq.NBSEN then begin
     if iGetTag(tagCmdCtrl)=cm_StBy then begin
      StartEditTag(tagStBy.NBSEN,'Standby speed control sensor, 0..4');
      SimulateClick(Click_Info,tagStBy.NBSEN);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Cool then begin
      StartEditTag(tagCool.NBSEN,'Cooling speed control sensor, 0..4');
      SimulateClick(Click_Info,tagCool.NBSEN);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Warm then begin
      StartEditTag(tagWarm.NBSEN,'Warming speed control sensor, 0..4');
      SimulateClick(Click_Info,tagWarm.NBSEN);
      b:=Voice(snd_Click);
     end else
     b:=Voice(snd_Fails);
    end;
    if ClickTag=tagLiq.DTMAX then begin
     if iGetTag(tagCmdCtrl)=cm_StBy then begin
      StartEditTag(tagStBy.DTMAX,'Standby speed control delta max, °C');
      SimulateClick(Click_Info,tagStBy.DTMAX);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Cool then begin
      StartEditTag(tagCool.DTMAX,'Cooling speed control delta max, °C');
      SimulateClick(Click_Info,tagCool.DTMAX);
      b:=Voice(snd_Click);
     end else
     if iGetTag(tagCmdCtrl)=cm_Warm then begin
      StartEditTag(tagWarm.DTMAX,'Warming speed control delta max, °C');
      SimulateClick(Click_Info,tagWarm.DTMAX);
      b:=Voice(snd_Click);
     end else
     b:=Voice(snd_Fails);
    end;
    {
    Control mode
    }
    for i:=0 to cm_High do
    if ClickTag=tagCmdCtrl then
    if IsSameText(ClickSensor,'CMD.CTRL.'+Str(i)) then begin
     Click_Send(Click_Info+'NewValue='+Str(i));
     b:=Voice(snd_Click);
    end;
    {
    Commands
    }
    ClickBitXorLocal(tagCmdEcv,1);
    ClickBitXorLocal(tagCmdClose,1);
    ClickBitXorRemote(tagCmdBlk, 1);
    ClickBitXorRemote(tagLiq.ENB,1);
    ClickBitXorRemote(tagLiq.VE2,1);
    ClickBitXorRemote(tagFreon.ENB,1);
    ClickBitXorRemote(tagFreon.VE1,1);
    ClickBitXorRemote(tagInterlock,1);
    ClickBitXorRemote(tagLoadParams,1);
    ClickBitXorRemote(tagSaveParams,1);
    {
    Control parameters dialog
    }
    if ClickTag=tagCmdParam then begin
     b:=Action('&'+NameTag(ClickTag));
     b:=Voice(snd_Click);
    end;
   end;
  end;
  {
  Handle right button clicks...
  }
  if ClickButton=2 then begin
   SensorHelp(Url_Decode(ClickParams('Hint')));
  end;
 end;
end.
