Index: fact/BIASctrl/BIASctrl.cc
===================================================================
--- fact/BIASctrl/BIASctrl.cc	(revision 9917)
+++ fact/BIASctrl/BIASctrl.cc	(revision 10049)
@@ -11,5 +11,5 @@
 #include <stdio.h>
 
-#include "ProcessIO.h"
+#include "User.h"
 
 #include <readline/readline.h>
@@ -84,5 +84,5 @@
   
   // Construct main instance
-  static ProcessIO M;
+  static User M;
   
   // These signals were set during construction of EvidenceServer
Index: fact/BIASctrl/Crate.cc
===================================================================
--- fact/BIASctrl/Crate.cc	(revision 9917)
+++ fact/BIASctrl/Crate.cc	(revision 10049)
@@ -6,5 +6,5 @@
 
 #include "Crate.h"
-#include "ProcessIO.h" // Must be not in HV.h to avoid problem with declaring class ProcessIO
+#include "User.h" // Must not be in header file to avoid problem with declaring class User
 
 using namespace std;
@@ -13,5 +13,5 @@
 // Constructor
 //
-Crate::Crate(string CrateName, int Number, class ProcessIO *PIO) {
+Crate::Crate(string CrateName, int Number, class User *PIO) {
    
   struct termios tio;
@@ -78,5 +78,5 @@
   if(fDescriptor != -1) {
     SystemReset();
-    close(fDescriptor);
+    if (close(fDescriptor) == -1) m->PrintMessage("Error closing device %s (%s)\n", Name, strerror(errno));;
   }
 
@@ -92,5 +92,4 @@
 int Crate::Communicate(unsigned char* wbuf, int Bytes) {
 
-  unsigned char rbuf[3];
   int N, Ret = 0;
   fd_set SelectDescriptor;
@@ -117,4 +116,5 @@
 	goto ExitCommunicate;
   }
+
   // Time-out expired?
   if (!FD_ISSET(fDescriptor, &SelectDescriptor)) {
@@ -124,5 +124,5 @@
 
   // Read data   
-  if ((N = read(fDescriptor, &rbuf, 1)) == -1) {
+  if ((N = read(fDescriptor, ReadBuffer, sizeof(ReadBuffer))) == -1) {
     m->Message(m->ERROR, "Read error (%s)", strerror(errno));
     ErrorCount++;
@@ -130,17 +130,11 @@
   }
 
-  // === Update status information if three bytes were returned ===
+  // === Check wrap counter if three bytes were returned ===
   if (N == 3) {
-    // This data only valid for channel set or channel read
-	LastCurrent = rbuf[1] + (rbuf[0]&15)*256;
-	LastOC = rbuf[0] & 128;
-	ResetHit = rbuf[2] & 128;
-
-	// Check wrap counter
 	if (WrapCount != -1) {
-	  if ((WrapCount+1)%8 == ((rbuf[0]>>4) & 7)) WrapOK = true;
+	  if ((WrapCount+1)%8 == ((ReadBuffer[0]>>4) & 7)) WrapOK = true;
 	  else WrapOK = false;
 	}
-	WrapCount = (rbuf[0]>>4) & 7;
+	WrapCount = (ReadBuffer[0]>>4) & 7;
 	Ret = 1;
   }
@@ -148,6 +142,6 @@
   // === UnLock file descriptor ===
   ExitCommunicate:
-  if (lockf(fDescriptor, F_LOCK, 0) == -1) {
-	m->Message(m->ERROR, "Failed to lock file descriptor (%s)", strerror(errno));
+  if (lockf(fDescriptor, F_ULOCK, 0) == -1) {
+	m->Message(m->ERROR, "Failed to unlock file descriptor (%s)", strerror(errno));
 	return 0;
   }
@@ -191,6 +185,7 @@
     
   if ((ret = Communicate(wbuf, 3)) == 1) {
-	Current[Board][Channel] = LastCurrent;
-	OC[Board][Channel] = LastOC;
+	Current[Board][Channel] = ReadBuffer[1] + (ReadBuffer[0]&15)*256;
+	OC[Board][Channel] = ReadBuffer[0] & 128;
+	ResetHit = ReadBuffer[2] & 128;
   }
   return ret;
@@ -213,5 +208,8 @@
   if ((ret = Communicate(wbuf, 3)) == 1) {
 	for (int i=0; i<MAX_NUM_BOARDS; i++) {
-      for (int j=0; j<NUM_CHANNELS; j++) DAC[i][j] = SetPoint;
+      for (int j=0; j<NUM_CHANNELS; j++) {
+	    DAC[i][j] = SetPoint;
+		Volt[i][j] = (double) SetPoint / 0xfff * 90;
+	  }
 	}
   }
@@ -243,6 +241,5 @@
   if ((ret = Communicate(wbuf, 3)) == 1) {
     DAC[Board][Channel] = SetPoint;
-	Current[Board][Channel] = LastCurrent;
-	OC[Board][Channel] = LastOC;
+	Volt[Board][Channel] = (double) SetPoint / 0xfff * 90;
   }
   return ret;
@@ -270,5 +267,5 @@
     for (int j=0; j<NUM_CHANNELS; j++){
       DAC[i][j] = 0;
-      //Volt[i][j] = 0.0;      
+      Volt[i][j] = 0.0;      
     }
   }
Index: fact/BIASctrl/Crate.h
===================================================================
--- fact/BIASctrl/Crate.h	(revision 9917)
+++ fact/BIASctrl/Crate.h	(revision 10049)
@@ -16,14 +16,13 @@
 #define BAUDRATE B115200
 
-class ProcessIO;
+class User;
 
 class Crate {
   
-    class ProcessIO *m;
+    class User *m;
 	int CrateNumber;
 	int fDescriptor; 
 	DimService *NameService;
-	bool LastOC;
-	int LastCurrent;
+	char ReadBuffer[3];
 
 	int Communicate(unsigned char*, int);
@@ -31,5 +30,5 @@
    
   public:
-    Crate(std::string, int, class ProcessIO *);
+    Crate(std::string, int, class User *);
     ~Crate();
 
Index: fact/BIASctrl/Makefile
===================================================================
--- fact/BIASctrl/Makefile	(revision 9917)
+++ fact/BIASctrl/Makefile	(revision 10049)
@@ -1,5 +1,5 @@
 #  Makefile for BIASctrl
 
-SOURCES = BIASctrl.cc Crate.cc ProcessIO.cc ../pixelmap/Pixel.cc ../pixelmap/PixelMap.cc ../Evidence/Evidence.cc 
+SOURCES = BIASctrl.cc Crate.cc User.cc ../pixelmap/Pixel.cc ../pixelmap/PixelMap.cc ../Evidence/Evidence.cc 
 OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) 
 INCDIRS   = -I. -I../Evidence -I./src -I$(DIMDIR)/dim
Index: fact/BIASctrl/ProcessIO.cc
===================================================================
--- fact/BIASctrl/ProcessIO.cc	(revision 9917)
+++ 	(revision )
@@ -1,533 +1,0 @@
-//
-// Class processing user input
-//
-
-#include "ProcessIO.h"
-
-using namespace std;
-
-// Branch table for command evaluation
-static const struct CL_Struct { const char *Name;    
-								void (ProcessIO::*CommandPointer)();
-								unsigned int MinNumParameter;
-								const char *Parameters;
-								const char *Help;
-  } CommandList[] = 
-   {{"hv", &ProcessIO::cmd_hv, 2, "<id>|<ch>|<all> <v>", "Change bias of pixel or (all) chan. of active boards"},
-	{"status", &ProcessIO::cmd_status, 0, "[dac]", "Show status information (DAC values if requested)"},
-	{"load", &ProcessIO::cmd_load, 1, "<file>", "Load and set bias settings from file"},
-	{"save", &ProcessIO::cmd_save, 1, "<file>", "Save current bias settings to file"},
-	{"exit", &ProcessIO::cmd_exit, 0, "", "Exit program"},
-	{"rate", &ProcessIO::cmd_rate, 1, "<rate>", "Set refresh rate in Hz"},
-	{"timeout", &ProcessIO::cmd_timeout, 1, "<time>", "Set timeout to return from read in seconds"},
-	{"reset", &ProcessIO::cmd_reset, 1, "<crates>", "Reset crates"},
-	{"help", &ProcessIO::cmd_help, 0, "", "Print help"}};
-
-
-//
-// Constructor
-//
-ProcessIO::ProcessIO(): EvidenceServer(SERVER_NAME) {
-
-  // DIM console service used in PrintMessage()
-  ConsoleText = NULL;
-  ConsoleOut = new DimService(SERVER_NAME"/ConsoleOut", (char *) "");
-
-  // Get configuration data
-  vector<string> Boards = Tokenize(GetConfig("Boards"), " \t");
-  fTimeOut = atof(GetConfig("TimeOut").c_str());
-  fStatusRefreshRate = atof(GetConfig("StatusRefreshRate").c_str());
-  fMaxDiff = atoi(GetConfig("HVMaxDiff").c_str());
-
-  if (fStatusRefreshRate < MIN_RATE || fStatusRefreshRate > MAX_RATE)  fStatusRefreshRate = 1;
-
-  // Open devices
-  for (unsigned int i=0; i<Boards.size(); i++) {
-  
-    class Crate *New = new class Crate(Boards[i], Crates.size(), this);
-
-    if (New->InitOK && New->Synch()) {
-       PrintMessage("Synchronized and reset board %s (#%d)\n", Boards[i].c_str(), Crates.size());
-       Crates.push_back(New);
-    }
-    else {
-      Message(WARN, "Failed to synchronize board %s", Boards[i].c_str());
-	  delete New;
-    }
-  }
-
-  // Create instances
-  pm 	 = new PixelMap(GetConfig("PixMapTable"));
-  
-  // Install DIM command (after all initialized)
-  DIMCommand = new DimCommand((char *) SERVER_NAME"/Command", (char *) "C", this);
-
-  // Create monitor thread and make accessible for sending signal
-  if ((pthread_create(&Thread, NULL, (void * (*)(void *)) LaunchMonitor,(void *) this)) != 0) {
-    Message(FATAL, "pthread_create() failed with Monitor thread");
-  }
-}
-
-
-//
-// Destructor
-//
-ProcessIO::~ProcessIO() {
-  
-  // Wait for thread to quit
-  if (pthread_join(Thread, NULL) != 0) {
-    PrintMessage("pthread_join() failed");
-  }
-
-  // Delete all crates
-  for (unsigned int i=0; i<Crates.size(); i++) delete Crates[i];
-
-  delete DIMCommand;    
-  delete pm;
-  delete ConsoleOut;	
-  free(ConsoleText);  
-}
-
-//
-// Process user input
-//
-void ProcessIO::commandHandler() {
-
-  // Build string safely
-  string Command = string(getCommand()->getString(), getCommand()->getSize());
-
-  // Check if command is legal and ignore empty commands 
-  if (getCommand() != DIMCommand || Command.size() < 2) return;
-
-  // Shell command
-  if(Command[0]=='.') {
-    system(Command.c_str()+1);
-    return;
-  }
-
-  // Parse command into tokens
-  Parameter = Tokenize(Command, " ");
-
-  // Search for command in command list
-  for(unsigned int CmdNumber=0; CmdNumber<sizeof(CommandList)/sizeof(CL_Struct); CmdNumber++) {
-    if (Match(Parameter[0], CommandList[CmdNumber].Name)) {
-      if(Parameter.size()-1 < CommandList[CmdNumber].MinNumParameter) {
-		PrintMessage("Usage: %s %s\n", CommandList[CmdNumber].Name, CommandList[CmdNumber].Parameters);
-		return;
-	  }
-
-	  // Jump to command function
-	  (this->*CommandList[CmdNumber].CommandPointer)();
-	  return;  
-    }
-  }
-  PrintMessage("Unknown command '%s'\n", Parameter[0].c_str());
-}
-
-
-// Print help
-void ProcessIO::cmd_help() {
-
-  char Buffer[MAX_COM_SIZE];
-  for(unsigned int i=0; i<sizeof(CommandList)/sizeof(CL_Struct); i++) {
-    snprintf(Buffer, sizeof(Buffer), "%s %s", CommandList[i].Name, CommandList[i].Parameters);
-    PrintMessage("%-28s%s\n", Buffer, CommandList[i].Help);
-  }
-  
-  PrintMessage(".<command>                  Execute shell command\n\n"
-  	"Items in <> are mandatory, in [] optional, | indicates mutual exclusive or.\n");
-} 
-
-//
-// Set new bias voltage
-//
-void ProcessIO::cmd_hv() {
-
-  unsigned int DACValue, Errors=0, B, C;
-  double Double;
-  struct Range Crt, Chan;
-  
-  // Loop over all parameters
-  for (unsigned int n=1; n < Parameter.size()-1; n++) {
-
-	// Extract channel identification
-	if (pm->Pixel_to_HVboard(Parameter[n]) != 999999999) {
-      Crt.Min = Crt.Max = pm->Pixel_to_HVboard(Parameter[n]);
-      Chan.Min = Chan.Max = pm->Pixel_to_HVchain(Parameter[n])*NUM_CHANNELS + pm->Pixel_to_HVchannel(Parameter[n]);
-	}
-	else {
-      vector<string> T = Tokenize(Parameter[n], "/");
-	  if (T.size() == 2) {
-		Crt = ConvertToRange(T[0]);
-		Chan = ConvertToRange(T[1]);
-	  }
-	  else {
-		Crt.Min = Crt.Max = 0;
-		Chan = ConvertToRange(T[0]);
-	  }
-	}
-  
-	// Check validity of ranges
-    if (Crt.Min < 0 || Chan.Min < 0 || Crt.Max >= (int) Crates.size() || Chan.Max >= MAX_NUM_BOARDS*NUM_CHANNELS) {
-	  PrintMessage("Numeric conversion or out-of-range error for parameter %d, skipping channel\n", n);
-	  continue;
-	}
-
-	// Convert voltage value and check format 
-	if (!ConvertToDouble(Parameter[n+1], &Double)) {
-	  PrintMessage("Error: Wrong number format for voltage setting\n");
-	  continue;
-	}
-	
-	// Loop over given crates and channels
-	for (int i=Crt.Min; i<=Crt.Max; i++) for (int j=Chan.Min; j<=Chan.Max; j++) {
-	  // Board and channel number
-	  B = j / NUM_CHANNELS;
-	  C = j % NUM_CHANNELS;
-
-	  // Voltage change (number starts with + oder -) ignored if current DAC value is zero
-	  if (isdigit(Parameter[n+1][0])==0 && Crates[i]->DAC[B][C] == 0) continue;
-
-	  // Relative or absolute change?
-	  if (isdigit(Parameter[n+1][0]) == 0) DACValue = Crates[i]->DAC[B][C] + Double/90*0x0fff;
-	  else DACValue = Double/90*0x0fff;
-
-	  // Set new voltage
-	  if (!RampVoltage(DACValue, i, B, C)) Errors++;
-
-	} // Channels
-  } // Loop over command argument
-
-  // Update DIM service
-  for (unsigned int i=0; i<=Crates.size(); i++) Crates[i]->BiasVolt->updateService();
-  
-  if (Errors > 0) PrintMessage("Errors on %d channel(s) occurred\n", Errors);
-}
-
-//
-// Load bias settings from file
-//
-void ProcessIO::cmd_load() {
-
-  char Buffer[MAX_COM_SIZE];
-  int Errors = 0, Board, Channel;
-  unsigned int DACValue, NBoards = 0;
-  FILE *File;
-
-  // Open file
-  if ((File=fopen(Parameter[1].c_str(), "r")) == NULL) {
-    PrintMessage("Error: Could not open file '%s' (%s)\n", Parameter[1].c_str(), strerror(errno));
-    return;
-  }
-
-  // Scan through file line by line
-  while (fgets(Buffer, sizeof(Buffer), File) != NULL) {
-    for (unsigned int Crate=0; Crate<Crates.size(); Crate++) if (Match(Crates[Crate]->Name, Buffer)) {
-
-	  PrintMessage("Found bias settings for board %s (#%d)\n\r", Crates[Crate]->Name, Crate);
-
-	  Board = 0;  Channel = 0;
-	  while (fscanf(File, "%u", &DACValue)==1 && Board<MAX_NUM_BOARDS) {
-	    // Ramp channel to new voltage
-    	if (!RampVoltage(DACValue, Crate, Board, Channel)) {
-	      Errors++;
-	      PrintMessage("Error: Could not ramp board %d, channel %d\n", Board, Channel);
-    	}
-		else {
-	      PrintMessage("Ramped board %d, channel %d to %u (%.2f V)                         \r",
-	    	 Board, Channel, DACValue, (double) DACValue/0x0fff*90);
-		}
-
-		if(++Channel == NUM_CHANNELS) {
-	      Board++;
-	      Channel = 0;
-		}
-	  }
-
-      // Update DIM service
-	  Crates[Crate]->BiasVolt->updateService();
-
-	  if (ferror(File) != 0) {
-		PrintMessage("Error reading DAC value from file, terminating. (%s)\n",strerror(errno));
-    	return;
-	  }
-	  else PrintMessage("\nFinished updating board\n");
-      NBoards++;
-    } // Loop over boards
-  } // while()
-    	    
-  if (NBoards != Crates.size()) PrintMessage("Warning: Could not load bias settings for all connected crates\n");
-  else if (Errors == 0) PrintMessage("Success: Read bias settings for all connected crates\n");
-
-  if (Errors != 0) PrintMessage("Warning: Errors on %d channel(s) occurred\n", Errors);
-
-  if (fclose(File) != 0) PrintMessage("Error: Could not close file '%s'\n", Parameter[1].c_str());
-}
-	   
-//
-// Set refresh rate
-//
-void ProcessIO::cmd_rate() {
-
-  double Rate;
-
-  if (!ConvertToDouble(Parameter[1], &Rate)) {
-     PrintMessage("Error: Wrong number format\n");
-     return;   
-  }
-
-  // Check limits
-  if (Rate<MIN_RATE || Rate>MAX_RATE) {
-    PrintMessage("Refresh rate out of range (min: %.2f Hz, max: %.2f Hz)\n", MIN_RATE, MAX_RATE);
-    return;
-  }
-
-  fStatusRefreshRate = Rate;
-  PrintMessage("Refresh rate set to %.2f Hz\n", fStatusRefreshRate);
-}
-  
-//
-// Reset crates
-//
-void ProcessIO::cmd_reset() {
-
-  struct Range R = ConvertToRange(Parameter[1]);
-  
-  for (int i=0; i<(int) Crates.size(); i++) if (i>= R.Min && i<=R.Max) {
-	if (Crates[i]->SystemReset() == 1) PrintMessage("System reset of crate %s (#%d)\n", Crates[i]->Name, i);
-	else PrintMessage("Error: Could not reset board %s (#%d)\n", Crates[i]->Name, i);
-  }
-}
-
-//
-// Save bias settings of all boards
-//
-void ProcessIO::cmd_save() {
-
-  FILE *File;
-  time_t Time = time(NULL);
-
-  if ((File = fopen(Parameter[1].c_str(), "w")) == NULL) {
-    PrintMessage("Error: Could not open file '%s' (%s)\n", Parameter[1].c_str(), strerror(errno));
-    return;
-  }
-
-  fprintf(File,"********** Bias settings of %s **********\n\n", ctime(&Time));
-
-  for (unsigned int i=0; i<Crates.size(); i++) {
-    fprintf(File, "%s\n\n", Crates[i]->Name);
-
-    for (int j=0; j<MAX_NUM_BOARDS; j++) {
-	  for (int k=0; k<NUM_CHANNELS; k++) fprintf(File,"%5d ",Crates[i]->DAC[j][k]);
-    }
-    fprintf(File, "\n");
-  }
-
-  if (fclose(File) != 0) {
-    PrintMessage("Error: Could not close file '%s' (%s)\n", Parameter[1].c_str(), strerror(errno));  
-  }
-}
-
-//
-// Print status
-//
-void ProcessIO::cmd_status() {
-
-  PrintMessage(" Refresh rate:      %.2f Hz\n", fStatusRefreshRate);
-  PrintMessage(" Number of crates:  %d\n", Crates.size());
-  PrintMessage(" Time out:          %.2f s\n\n", fTimeOut);
-  PrintMessage(" MaxDiff :          %u\n", fMaxDiff);
-
-  for (unsigned int i=0; i<Crates.size(); i++) {
-    PrintMessage(" CRATE %d (%s)   Wrap counter: %s (%d)  Manual reset: %s\n    Error count: %d\n\n",
-		i, Crates[i]->Name,	Crates[i]->WrapOK ? "ok":"error", Crates[i]->WrapCount, 
-		Crates[i]->ResetHit ? "yes" : "no", Crates[i]->ErrorCount);
-
-    for (int j=0; j<MAX_NUM_BOARDS*NUM_CHANNELS; j++) {
-	  if (j%8 == 0) PrintMessage("\n%3.1d:  ", j);
-	  if (Parameter.size() == 2) PrintMessage("%5d ", Crates[i]->DAC[j/NUM_CHANNELS][j%NUM_CHANNELS]);
-      else PrintMessage("%#5.2f ",Crates[i]->Volt[j/NUM_CHANNELS][j%NUM_CHANNELS]);
-	  PrintMessage(" (%#5.2f %s)  ", Crates[i]->Current[j/NUM_CHANNELS][j%NUM_CHANNELS], Crates[i]->OC[j/NUM_CHANNELS][j%NUM_CHANNELS] ? "OC":"");
-	  PrintMessage("\n");
-    }
-  }
-} 
-
-//
-// Set timeout to return from read
-//
-void ProcessIO::cmd_timeout() {
-
-  double Timeout;
-
-  if (!ConvertToDouble(Parameter[1], &Timeout)) {
-     PrintMessage("Error: Wrong number format\n");
-     return;   
-  }
-
-  fTimeOut = Timeout;
-}
-    
-//
-// Exit program
-//
-void ProcessIO::cmd_exit() {
-
-  ExitRequest = true;
-  pthread_kill(Thread, SIGUSR1); 
-}
-  
-
-//
-// Print message to screen and to DIM text service
-//
-void ProcessIO::PrintMessage(const char *Format, ...) {
-
-  static char Error[] = "vasprintf() failed in PrintMessage()";
-  char *Text;
-
-  // Evaluate arguments    
-  va_list ArgumentPointer;
-  va_start(ArgumentPointer, Format);
-  if (vasprintf(&Text, Format, ArgumentPointer) == -1) Text = Error;
-  va_end(ArgumentPointer);
- 
-  // Print to console
-  if(strlen(Text)>0 && Text[strlen(Text)-1]=='\n') printf("\r%s%s", Text, "\rBias> "); // New prompt
-  else printf("%s", Text);
-  fflush(stdout);
-
-  // Send to DIM text service
-  ConsoleOut->updateService(Text); 
-
-  // Free old text
-  if (ConsoleText != Error) free(ConsoleText);
-  ConsoleText = Text; 
-}
-
-
-// Ramp to new voltage with maximum step size given in fMaxDiff
-// No ramping when decreasing voltage
-bool ProcessIO::RampVoltage(unsigned int Target, int Crate, int Board, int Channel) {
-
-  while (Crates[Crate]->DAC[Board][Channel] != (int) Target) {	  
-    int Diff = Target - Crates[Crate]->DAC[Board][Channel];
-    if (Diff > (int) fMaxDiff) Diff = fMaxDiff;
-
-    if (Crates[Crate]->ChannelSet(Board, Channel, Crates[Crate]->DAC[Board][Channel]+Diff) != 1) {
-      Message(ERROR, "Could not set bias of crate %d, board %d, channel %d. Skipping channel\n", Crate, Board, Channel);
-      return false;
-    }
-  }
-
-  return true;
-}
-
-
-//
-// Check status
-//
-void ProcessIO::Monitor() {
-
-  static bool Warned = false;
-
-  while (!ExitRequest) {
-	for (unsigned int i=0; i<Crates.size(); i++) {
-      if (Crates[i]->ErrorCount > 10) {
-    	if (!Warned) {
-          Warned = true;
-          Message(WARN, "Warning: Crate %d has many read/write errors, further error reporting disabled", i);
-    	}
-    	continue;
-      }
-
-      if (Crates[i]->ResetHit) {
-    	Message(INFO, "Manual reset of board %d", i);
-		Crates[i]->SystemReset();
-      }
-
-      if (!Crates[i]->WrapOK) {
-    	Message(ERROR, "Wrap counter mismatch of board %d", i);
-      }
-
-      for (int j=0; j<MAX_NUM_BOARDS*NUM_CHANNELS; j++) {
-    	if (Crates[i]->ReadChannel(j/NUM_CHANNELS, j%NUM_CHANNELS) != 1) {
-    	  Message(ERROR, "Monitor could not read status of crate %d, board %d, channel %d", i, j/NUM_CHANNELS, j%NUM_CHANNELS);
-		  continue;
-    	}
-    	if (Crates[i]->OC[j/NUM_CHANNELS][j%NUM_CHANNELS]) {
-		  Message(WARN, "Overcurrent on crate %d, board %d, channel %d, resetting board", i, j/NUM_CHANNELS, j%NUM_CHANNELS);
-		  Crates[i]->SystemReset();
-    	}
-      }
-	} // for
-
-	// Wait
-	usleep((unsigned long) floor(1000000./fStatusRefreshRate));
-  } // while
-}
-
-// Call monitor loop inside class
-void ProcessIO::LaunchMonitor(ProcessIO *m) {
-
-  m->Monitor();
-}
-
-
-//
-// Check if two strings match (min 1 character must match)
-//
-bool Match(string str, const char *cmd) {
-
-  return strncasecmp(str.c_str(),cmd,strlen(str.c_str())==0 ? 1:strlen(str.c_str())) ? false:true;
-}
-
-//
-// Conversion function from string to double or int
-//
-// Return false if conversion did not stop on whitespace or EOL character
-bool ConvertToDouble(string String, double *Result) {
-
-  char *EndPointer;
-  
-  *Result = strtod(String.c_str(), &EndPointer);
-  if(!isspace(*EndPointer) && *EndPointer!='\0') return false;
-  return true;
-}
-
-bool ConvertToInt(string String, int *Result) {
-
-  char *EndPointer;
-  
-  *Result = (int) strtol(String.c_str(), &EndPointer, 0);
-  if(!isspace(*EndPointer) && *EndPointer!='\0') return false;
-  return true;
-}
-
-//
-// Interprets a range
-//
-struct Range ConvertToRange(string String) {
-
-  struct Range R;
-
-  // Full range
-  if (Match(String, "all")) {
-    R.Min = 0;
-	R.Max = std::numeric_limits<int>::max();
-	return R;
-  }
-
-  // Single number
-  if (ConvertToInt(String, &R.Min)) {
-	R.Max = R.Min;
-	return R;
-  }
-  
-  // Range a-b
-  vector<string> V = EvidenceServer::Tokenize(String, "-");
-  if (V.size() == 2 && ConvertToInt(V[0], &R.Min) && ConvertToInt(V[1], &R.Max)) return R;
-  
-  R.Min = R.Max = -1;  
-  return R;
-}
Index: fact/BIASctrl/ProcessIO.h
===================================================================
--- fact/BIASctrl/ProcessIO.h	(revision 9917)
+++ 	(revision )
@@ -1,68 +1,0 @@
-
-#ifndef PROCESSIO_H_SEEN
-#define PROCESSIO_H_SEEN
-
-#include <stdarg.h>
-#include <errno.h>
-#include <math.h>
-#include <signal.h>
-#include <string>
-#include <pthread.h>
-#include <limits>
-
-#define SERVER_NAME "Bias"       // Name to use in DIM
-#include "Evidence.h"
-
-#include "Crate.h"
-#include "../pixelmap/PixelMap.h"
-
-#define MAX_COM_SIZE 5000
-
-#define MIN_RATE 0.01
-#define MAX_RATE 50.0
-
-class ProcessIO: public EvidenceServer {
-
-	PixelMap *pm;
-	DimCommand *DIMCommand;
-	DimService *ConsoleOut;
-	char *ConsoleText;
-	pthread_t Thread;
-	std::vector<std::string> Parameter;
-
-	std::vector<class Crate *> Crates;
-
-	void commandHandler();
-
- public:
-	double fTimeOut;
-	float fStatusRefreshRate;
-	unsigned int fMaxDiff;
-
-	ProcessIO();
-	~ProcessIO();
-
-	void PrintMessage(const char *, ...);
-	bool RampVoltage(unsigned int, int, int, int);
-	void Monitor();
-	static void LaunchMonitor(ProcessIO *);
-	
-	void cmd_hv();
-	void cmd_status();
-	void cmd_load();	void cmd_save();
-	void cmd_exit();	void cmd_rate();
-	void cmd_timeout();	void cmd_reset();
-	void cmd_help();
-};
-
-struct Range {
-  int Min;
-  int Max;
-};
-
-bool Match(std::string, const char *);
-bool ConvertToDouble(std::string, double *);
-bool ConvertToInt(std::string, int *);
-struct Range ConvertToRange(std::string);
-
-#endif
Index: fact/BIASctrl/User.cc
===================================================================
--- fact/BIASctrl/User.cc	(revision 10049)
+++ fact/BIASctrl/User.cc	(revision 10049)
@@ -0,0 +1,560 @@
+//
+// Class processing user input
+//
+
+#include "User.h"
+
+using namespace std;
+
+// Branch table for command evaluation
+static const struct CL_Struct { const char *Name;    
+								void (User::*CommandPointer)();
+								unsigned int MinNumParameter;
+								const char *Parameters;
+								const char *Help;
+  } CommandList[] = 
+   {{"synch", &User::cmd_synch, 0, "", "Synchronize board"},
+    {"hv", &User::cmd_hv, 2, "<id>|<ch>|<all> <v>", "Change bias of pixel or (all) chan. of active boards"},
+    {"gs", &User::cmd_gs, 1, "[crate] <volt>", "Global voltage set"},
+	{"status", &User::cmd_status, 0, "[dac]", "Show status information (DAC values if requested)"},
+	{"load", &User::cmd_load, 1, "<file>", "Load and set bias settings from file"},
+	{"save", &User::cmd_save, 1, "<file>", "Save current bias settings to file"},
+	{"exit", &User::cmd_exit, 0, "", "Exit program"},
+	{"rate", &User::cmd_rate, 1, "<rate>", "Set refresh rate in Hz"},
+	{"timeout", &User::cmd_timeout, 1, "<time>", "Set timeout to return from read in seconds"},
+	{"reset", &User::cmd_reset, 1, "<crates>", "Reset crates"},
+	{"help", &User::cmd_help, 0, "", "Print help"}};
+
+
+//
+// Constructor
+//
+User::User(): EvidenceServer(SERVER_NAME) {
+
+  // DIM console service used in PrintMessage()
+  ConsoleText = NULL;
+  ConsoleOut = new DimService(SERVER_NAME"/ConsoleOut", (char *) "");
+
+  // Get configuration data
+  vector<string> Boards = Tokenize(GetConfig("Boards"), " \t");
+  Boards = Tokenize("FTE00FOH", " \t");
+  fTimeOut = atof(GetConfig("TimeOut").c_str());
+  fStatusRefreshRate = atof(GetConfig("StatusRefreshRate").c_str());
+  fMaxDiff = atoi(GetConfig("HVMaxDiff").c_str());
+
+  if (fStatusRefreshRate < MIN_RATE || fStatusRefreshRate > MAX_RATE)  fStatusRefreshRate = 1;
+
+  // Open devices
+  for (unsigned int i=0; i<Boards.size(); i++) {
+  
+    class Crate *New = new class Crate(Boards[i], Crates.size(), this);
+
+    if (New->InitOK && New->Synch()) {
+       PrintMessage("Synchronized and reset board %s (#%d)\n", Boards[i].c_str(), Crates.size());
+       Crates.push_back(New);
+    }
+    else {
+      Message(WARN, "Failed to synchronize board %s", Boards[i].c_str());
+	  delete New;
+    }
+  }
+
+  // Create instances
+  pm 	 = new PixelMap(GetConfig("PixMapTable"));
+  
+  // Install DIM command (after all initialized)
+  DIMCommand = new DimCommand((char *) SERVER_NAME"/Command", (char *) "C", this);
+
+  // Create monitor thread and make accessible for sending signal
+  if ((pthread_create(&Thread, NULL, (void * (*)(void *)) LaunchMonitor,(void *) this)) != 0) {
+    Message(FATAL, "pthread_create() failed with Monitor thread");
+  }
+}
+
+
+//
+// Destructor
+//
+User::~User() {
+  
+  // Wait for thread to quit
+  if (pthread_join(Thread, NULL) != 0) {
+    PrintMessage("pthread_join() failed");
+  }
+
+  // Delete all crates
+  for (unsigned int i=0; i<Crates.size(); i++) delete Crates[i];
+
+  delete DIMCommand;    
+  delete pm;
+  delete ConsoleOut;	
+  free(ConsoleText);  
+}
+
+//
+// Process user input
+//
+void User::commandHandler() {
+
+  // Build string safely
+  string Command = string(getCommand()->getString(), getCommand()->getSize());
+
+  // Check if command is legal and ignore empty commands 
+  if (getCommand() != DIMCommand || Command.size() < 2) return;
+
+  // Shell command
+  if(Command[0]=='.') {
+    system(Command.c_str()+1);
+    return;
+  }
+
+  // Parse command into tokens
+  Parameter = Tokenize(Command, " ");
+
+  // Search for command in command list
+  for(unsigned int CmdNumber=0; CmdNumber<sizeof(CommandList)/sizeof(CL_Struct); CmdNumber++) {
+    if (Match(Parameter[0], CommandList[CmdNumber].Name)) {
+      if(Parameter.size()-1 < CommandList[CmdNumber].MinNumParameter) {
+		PrintMessage("Usage: %s %s\n", CommandList[CmdNumber].Name, CommandList[CmdNumber].Parameters);
+		return;
+	  }
+
+	  // Jump to command function
+	  (this->*CommandList[CmdNumber].CommandPointer)();
+	  return;  
+    }
+  }
+  PrintMessage("Unknown command '%s'\n", Parameter[0].c_str());
+}
+
+
+// Print help
+void User::cmd_help() {
+
+  char Buffer[MAX_COM_SIZE];
+  for(unsigned int i=0; i<sizeof(CommandList)/sizeof(CL_Struct); i++) {
+    snprintf(Buffer, sizeof(Buffer), "%s %s", CommandList[i].Name, CommandList[i].Parameters);
+    PrintMessage("%-28s%s\n", Buffer, CommandList[i].Help);
+  }
+  
+  PrintMessage(".<command>                  Execute shell command\n\n"
+  	"Items in <> are mandatory, in [] optional, | indicates mutual exclusive or.\n");
+} 
+
+//
+// Synchronize boards
+//
+void User::cmd_synch() {
+
+  if (Crates[0]->Synch()) PrintMessage("Synchronized board %d\n", 0);
+  else PrintMessage("Failed to synchronize board %d\n", 0);
+}
+
+//
+// Set new bias voltage
+//
+void User::cmd_hv() {
+
+  unsigned int DACValue, Errors=0, B, C;
+  double Double;
+  struct Range Crt, Chan;
+  
+  // Loop over all parameters
+  for (unsigned int n=1; n < Parameter.size()-1; n++) {
+
+	// Extract channel identification
+	if (pm->Pixel_to_HVboard(Parameter[n]) != 999999999) {
+      Crt.Min = Crt.Max = pm->Pixel_to_HVboard(Parameter[n]);
+      Chan.Min = Chan.Max = pm->Pixel_to_HVchain(Parameter[n])*NUM_CHANNELS + pm->Pixel_to_HVchannel(Parameter[n]);
+	}
+	else {
+      vector<string> T = Tokenize(Parameter[n], "/");
+	  if (T.size() == 2) {
+		Crt = ConvertToRange(T[0]);
+		Chan = ConvertToRange(T[1]);
+	  }
+	  else {
+		Crt.Min = Crt.Max = 0;
+		Chan = ConvertToRange(T[0]);
+	  }
+	}
+  
+	// Check validity of ranges
+    if (Crt.Min < 0 || Chan.Min < 0 || Crt.Max >= (int) Crates.size() || Chan.Max >= MAX_NUM_BOARDS*NUM_CHANNELS) {
+	  PrintMessage("Numeric conversion or out-of-range error for parameter %d, skipping channel\n", n);
+	  continue;
+	}
+
+	// Convert voltage value and check format 
+	if (!ConvertToDouble(Parameter[n+1], &Double)) {
+	  PrintMessage("Error: Wrong number format for voltage setting\n");
+	  continue;
+	}
+	
+	// Loop over given crates and channels
+	for (int i=Crt.Min; i<=Crt.Max; i++) for (int j=Chan.Min; j<=Chan.Max; j++) {
+	  // Board and channel number
+	  B = j / NUM_CHANNELS;
+	  C = j % NUM_CHANNELS;
+
+	  // Voltage change (number starts with + oder -) ignored if current DAC value is zero
+	  if (isdigit(Parameter[n+1][0])==0 && Crates[i]->DAC[B][C] == 0) continue;
+
+	  // Relative or absolute change?
+	  if (isdigit(Parameter[n+1][0]) == 0) DACValue = Crates[i]->DAC[B][C] + (unsigned int) (Double/90*0x0fff);
+	  else DACValue = (unsigned int) (Double/90*0x0fff);
+
+	  // Set new voltage
+	  if (!RampVoltage(DACValue, i, B, C)) Errors++;
+
+	} // Channels
+  } // Loop over command argument
+
+  // Update DIM service
+  for (unsigned int i=0; i<Crates.size(); i++) Crates[i]->BiasVolt->updateService();
+  
+  if (Errors > 0) PrintMessage("Errors on %d channel(s) occurred\n", Errors);
+}
+
+//
+// Load bias settings from file
+//
+void User::cmd_load() {
+
+  char Buffer[MAX_COM_SIZE];
+  int Errors = 0, Board, Channel;
+  unsigned int DACValue, NBoards = 0;
+  FILE *File;
+
+  // Open file
+  if ((File=fopen(Parameter[1].c_str(), "r")) == NULL) {
+    PrintMessage("Error: Could not open file '%s' (%s)\n", Parameter[1].c_str(), strerror(errno));
+    return;
+  }
+
+  // Scan through file line by line
+  while (fgets(Buffer, sizeof(Buffer), File) != NULL) {
+    for (unsigned int Crate=0; Crate<Crates.size(); Crate++) if (Match(Crates[Crate]->Name, Buffer)) {
+
+	  PrintMessage("Found bias settings for board %s (#%d)\n\r", Crates[Crate]->Name, Crate);
+
+	  Board = 0;  Channel = 0;
+	  while (fscanf(File, "%u", &DACValue)==1 && Board<MAX_NUM_BOARDS) {
+	    // Ramp channel to new voltage
+    	if (!RampVoltage(DACValue, Crate, Board, Channel)) {
+	      Errors++;
+	      PrintMessage("Error: Could not ramp board %d, channel %d\n", Board, Channel);
+    	}
+		else {
+	      PrintMessage("Ramped board %d, channel %d to %u (%.2f V)                         \r",
+	    	 Board, Channel, DACValue, (double) DACValue/0x0fff*90);
+		}
+
+		if(++Channel == NUM_CHANNELS) {
+	      Board++;
+	      Channel = 0;
+		}
+	  }
+
+      // Update DIM service
+	  Crates[Crate]->BiasVolt->updateService();
+
+	  if (ferror(File) != 0) {
+		PrintMessage("Error reading DAC value from file, terminating. (%s)\n",strerror(errno));
+    	return;
+	  }
+	  else PrintMessage("\nFinished updating board\n");
+      NBoards++;
+    } // Loop over boards
+  } // while()
+    	    
+  if (NBoards != Crates.size()) PrintMessage("Warning: Could not load bias settings for all connected crates\n");
+  else if (Errors == 0) PrintMessage("Success: Read bias settings for all connected crates\n");
+
+  if (Errors != 0) PrintMessage("Warning: Errors on %d channel(s) occurred\n", Errors);
+
+  if (fclose(File) != 0) PrintMessage("Error: Could not close file '%s'\n", Parameter[1].c_str());
+}
+	   
+//
+// Set refresh rate
+//
+void User::cmd_rate() {
+
+  double Rate;
+
+  if (!ConvertToDouble(Parameter[1], &Rate)) {
+     PrintMessage("Error: Wrong number format\n");
+     return;   
+  }
+
+  // Check limits
+  if (Rate<MIN_RATE || Rate>MAX_RATE) {
+    PrintMessage("Refresh rate out of range (min: %.2f Hz, max: %.2f Hz)\n", MIN_RATE, MAX_RATE);
+    return;
+  }
+
+  fStatusRefreshRate = Rate;
+  PrintMessage("Refresh rate set to %.2f Hz\n", fStatusRefreshRate);
+}
+  
+//
+// Reset crates
+//
+void User::cmd_reset() {
+
+  struct Range R = ConvertToRange(Parameter[1]);
+  
+  for (int i=0; i<(int) Crates.size(); i++) if (i>= R.Min && i<=R.Max) {
+	if (Crates[i]->SystemReset() == 1) PrintMessage("System reset of crate %s (#%d)\n", Crates[i]->Name, i);
+	else PrintMessage("Error: Could not reset board %s (#%d)\n", Crates[i]->Name, i);
+  }
+}
+
+//
+// Read channel
+//
+void User::cmd_gs() {
+
+  double Voltage;
+
+  if (!ConvertToDouble(Parameter[1], &Voltage)) return;
+
+  if (Crates[0]->GlobalSet((int) (Voltage/90*0xfff)) != 1) {
+    printf("Error: Could not global set board %d\n", 0);
+  }    
+}
+
+//
+// Save bias settings of all boards
+//
+void User::cmd_save() {
+
+  FILE *File;
+  time_t Time = time(NULL);
+
+  if ((File = fopen(Parameter[1].c_str(), "w")) == NULL) {
+    PrintMessage("Error: Could not open file '%s' (%s)\n", Parameter[1].c_str(), strerror(errno));
+    return;
+  }
+
+  fprintf(File,"********** Bias settings of %s **********\n\n", ctime(&Time));
+
+  for (unsigned int i=0; i<Crates.size(); i++) {
+    fprintf(File, "%s\n\n", Crates[i]->Name);
+
+    for (int j=0; j<MAX_NUM_BOARDS; j++) {
+	  for (int k=0; k<NUM_CHANNELS; k++) fprintf(File,"%5d ",Crates[i]->DAC[j][k]);
+    }
+    fprintf(File, "\n");
+  }
+
+  if (fclose(File) != 0) {
+    PrintMessage("Error: Could not close file '%s' (%s)\n", Parameter[1].c_str(), strerror(errno));  
+  }
+}
+
+//
+// Print status
+//
+void User::cmd_status() {
+
+  PrintMessage(" Number of crates:  %d\n", Crates.size());
+  PrintMessage(" Refresh rate:      %.2f Hz\n", fStatusRefreshRate);
+  PrintMessage(" Time out:          %.2f s\n\n", fTimeOut);
+  PrintMessage(" MaxDiff :          %u\n", fMaxDiff);
+
+  for (unsigned int i=0; i<Crates.size(); i++) {
+    PrintMessage(" CRATE %d (%s)\n   Wrap counter: %s (%d)  Reset: %s  Error count: %d\n ",
+		i, Crates[i]->Name,	Crates[i]->WrapOK ? "ok":"error", Crates[i]->WrapCount, 
+		Crates[i]->ResetHit ? "yes" : "no", Crates[i]->ErrorCount);
+
+    for (int j=0; j<MAX_NUM_BOARDS*NUM_CHANNELS; j++) {
+	  if (j%12 == 0) PrintMessage("\n%3.1d:  ", j);
+	  if (Parameter.size() == 2) PrintMessage("%5d ", Crates[i]->DAC[j/NUM_CHANNELS][j%NUM_CHANNELS]);
+      else PrintMessage("%#5.2f ",Crates[i]->Volt[j/NUM_CHANNELS][j%NUM_CHANNELS]);
+	  //PrintMessage(" (%#5.2f %s)  ", Crates[i]->Current[j/NUM_CHANNELS][j%NUM_CHANNELS], Crates[i]->OC[j/NUM_CHANNELS][j%NUM_CHANNELS] ? "OC":"");
+    }
+	PrintMessage("\n");
+  }
+} 
+
+//
+// Set timeout to return from read
+//
+void User::cmd_timeout() {
+
+  double Timeout;
+
+  if (!ConvertToDouble(Parameter[1], &Timeout)) {
+     PrintMessage("Error: Wrong number format\n");
+     return;   
+  }
+
+  fTimeOut = Timeout;
+  PrintMessage("Timeout set to %.2f s\n", fTimeOut);
+}
+    
+//
+// Exit program
+//
+void User::cmd_exit() {
+
+  ExitRequest = true;
+  pthread_kill(Thread, SIGUSR1); 
+}
+  
+
+//
+// Print message to screen and to DIM text service
+//
+void User::PrintMessage(const char *Format, ...) {
+
+  static char Error[] = "vasprintf() failed in PrintMessage()";
+  char *Text;
+
+  // Evaluate arguments    
+  va_list ArgumentPointer;
+  va_start(ArgumentPointer, Format);
+  if (vasprintf(&Text, Format, ArgumentPointer) == -1) Text = Error;
+  va_end(ArgumentPointer);
+ 
+  // Print to console
+  if(strlen(Text)>0 && Text[strlen(Text)-1]=='\n') printf("\r%s%s", Text, "\rBias> "); // New prompt
+  else printf("%s", Text);
+  fflush(stdout);
+
+  // Send to DIM text service
+  ConsoleOut->updateService(Text); 
+
+  // Free old text
+  if (ConsoleText != Error) free(ConsoleText);
+  ConsoleText = Text; 
+}
+
+
+// Ramp to new voltage with maximum step size given in fMaxDiff
+// No ramping when decreasing voltage
+bool User::RampVoltage(unsigned int Target, int Crate, int Board, int Channel) {
+
+  while (Crates[Crate]->DAC[Board][Channel] != (int) Target) {	  
+    int Diff = Target - Crates[Crate]->DAC[Board][Channel];
+    if (Diff > (int) fMaxDiff) Diff = fMaxDiff;
+
+    if (Crates[Crate]->ChannelSet(Board, Channel, Crates[Crate]->DAC[Board][Channel]+Diff) != 1) {
+      Message(ERROR, "Could not set bias of crate %d, board %d, channel %d. Skipping channel\n", Crate, Board, Channel);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+
+//
+// Check status
+//
+void User::Monitor() {
+
+  static bool Warned = false;
+return;
+  while (!ExitRequest) {
+	for (unsigned int i=0; i<Crates.size(); i++) {
+      if (Crates[i]->ErrorCount > 10) {
+    	if (!Warned) {
+          Warned = true;
+          Message(WARN, "Warning: Crate %d has many read/write errors, further error reporting disabled", i);
+    	}
+    	continue;
+      }
+
+      if (Crates[i]->ResetHit) {
+    	Message(INFO, "Manual reset of board %d", i);
+		Crates[i]->SystemReset();
+      }
+
+      if (!Crates[i]->WrapOK) {
+    	Message(ERROR, "Wrap counter mismatch of board %d", i);
+      }
+
+      for (int j=0; j<MAX_NUM_BOARDS*NUM_CHANNELS; j++) {
+    	if (Crates[i]->ReadChannel(j/NUM_CHANNELS, j%NUM_CHANNELS) != 1) {
+    	  Message(ERROR, "Monitor could not read status of crate %d, board %d, channel %d", i, j/NUM_CHANNELS, j%NUM_CHANNELS);
+		  continue;
+    	}
+    	if (Crates[i]->OC[j/NUM_CHANNELS][j%NUM_CHANNELS]) {
+		  Message(WARN, "Overcurrent on crate %d, board %d, channel %d, resetting board", i, j/NUM_CHANNELS, j%NUM_CHANNELS);
+		  Crates[i]->SystemReset();
+    	}
+      }
+	} // for
+
+	// Wait
+	usleep((unsigned long) floor(1000000./fStatusRefreshRate));
+  } // while
+}
+
+// Call monitor loop inside class
+void User::LaunchMonitor(User *m) {
+
+  m->Monitor();
+}
+
+
+//
+// Check if two strings match (min 1 character must match)
+//
+bool Match(string str, const char *cmd) {
+
+  return strncasecmp(str.c_str(),cmd,strlen(str.c_str())==0 ? 1:strlen(str.c_str())) ? false:true;
+}
+
+//
+// Conversion function from string to double or int
+//
+// Return false if conversion did not stop on whitespace or EOL character
+bool ConvertToDouble(string String, double *Result) {
+
+  char *EndPointer;
+  
+  *Result = strtod(String.c_str(), &EndPointer);
+  if(!isspace(*EndPointer) && *EndPointer!='\0') return false;
+  return true;
+}
+
+bool ConvertToInt(string String, int *Result) {
+
+  char *EndPointer;
+  
+  *Result = (int) strtol(String.c_str(), &EndPointer, 0);
+  if(!isspace(*EndPointer) && *EndPointer!='\0') return false;
+  return true;
+}
+
+//
+// Interprets a range
+//
+struct Range ConvertToRange(string String) {
+
+  struct Range R;
+
+  // Full range
+  if (Match(String, "all")) {
+    R.Min = 0;
+	R.Max = std::numeric_limits<int>::max();
+	return R;
+  }
+
+  // Single number
+  if (ConvertToInt(String, &R.Min)) {
+	R.Max = R.Min;
+	return R;
+  }
+  
+  // Range a-b
+  vector<string> V = EvidenceServer::Tokenize(String, "-");
+  if (V.size() == 2 && ConvertToInt(V[0], &R.Min) && ConvertToInt(V[1], &R.Max)) return R;
+  
+  R.Min = R.Max = -1;  
+  return R;
+}
Index: fact/BIASctrl/User.h
===================================================================
--- fact/BIASctrl/User.h	(revision 10049)
+++ fact/BIASctrl/User.h	(revision 10049)
@@ -0,0 +1,68 @@
+
+#ifndef PROCESSIO_H_SEEN
+#define PROCESSIO_H_SEEN
+
+#include <stdarg.h>
+#include <errno.h>
+#include <math.h>
+#include <signal.h>
+#include <string>
+#include <pthread.h>
+#include <limits>
+
+#define SERVER_NAME "Bias"       // Name to use in DIM
+#include "Evidence.h"
+
+#include "Crate.h"
+#include "../pixelmap/PixelMap.h"
+
+#define MAX_COM_SIZE 5000
+
+#define MIN_RATE 0.01
+#define MAX_RATE 50.0
+
+class User: public EvidenceServer {
+
+	PixelMap *pm;
+	DimCommand *DIMCommand;
+	DimService *ConsoleOut;
+	char *ConsoleText;
+	pthread_t Thread;
+	std::vector<std::string> Parameter;
+
+	std::vector<class Crate *> Crates;
+
+	void commandHandler();
+
+ public:
+	double fTimeOut;
+	float fStatusRefreshRate;
+	unsigned int fMaxDiff;
+
+	User();
+	~User();
+
+	void PrintMessage(const char *, ...);
+	bool RampVoltage(unsigned int, int, int, int);
+	void Monitor();
+	static void LaunchMonitor(User *);
+	
+	void cmd_hv();		void cmd_synch();
+	void cmd_status();	void cmd_gs();
+	void cmd_load();	void cmd_save();
+	void cmd_exit();	void cmd_rate();
+	void cmd_timeout();	void cmd_reset();
+	void cmd_help();
+};
+
+struct Range {
+  int Min;
+  int Max;
+};
+
+bool Match(std::string, const char *);
+bool ConvertToDouble(std::string, double *);
+bool ConvertToInt(std::string, int *);
+struct Range ConvertToRange(std::string);
+
+#endif
