Index: /fact/BIASctrl/BIASctrl.cc
===================================================================
--- /fact/BIASctrl/BIASctrl.cc	(revision 9917)
+++ /fact/BIASctrl/BIASctrl.cc	(revision 9917)
@@ -0,0 +1,106 @@
+/**************************************************************\
+
+  Control program for the fACT G-APD bias supply
+
+  Adapted from previous version 'hvcontrol'.
+  
+  Oliver Grimm, August 2010
+  
+\**************************************************************/
+
+#include <stdio.h>
+
+#include "ProcessIO.h"
+
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#define LOCKFILE "/tmp/CTX_HV_LOCK"
+
+//
+// Remove lock file before running default signal code
+//
+void CrashHandler(int Signal) {
+
+  remove(LOCKFILE);
+  printf("Caught signal number %d. Removing lockfile and performing standard signal action. Good luck.\n",Signal);
+  signal(Signal, SIG_DFL);
+  raise(Signal);
+}
+
+//
+// Dummy handler to return from blocking syscalls
+//
+void DummyHandler(int) {};
+
+//
+// This function will be implicitly called by exit()
+//
+void ExitFunction() {
+
+  if (remove(LOCKFILE) == -1) {
+    printf("Could not remove lock file %s (%s)\n", LOCKFILE, strerror(errno));
+  }
+}
+
+//
+// Main program
+//
+int main() {
+
+  char str[MAX_COM_SIZE], *Command;
+  int Lock;
+
+  // Assure only one instance runs
+  // O_EXCL with O_CREAT assure that the lock file cannot be 
+  // opened by another instance, i.e. there are no parallel write accesses
+  if((Lock = open(LOCKFILE,O_WRONLY|O_CREAT|O_EXCL,S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)) == -1) {
+    if(errno==EEXIST) {
+      printf("Error: Lock file already existing\n");
+      sprintf(str, "paste %s -s -d ' '", LOCKFILE);
+      system(str);
+    }
+    else printf("Could not create lock file %s (%s)\n", LOCKFILE, strerror(errno));
+    exit(EXIT_FAILURE);
+  }
+  close(Lock);
+  sprintf(str,"echo Created >%s; date >>%s; echo by $USER@$HOSTNAME>>%s", LOCKFILE, LOCKFILE, LOCKFILE);
+  system(str);
+  
+  system("clear");                   
+  printf("\n*** Bias control (compiled %s, %s) ***\n\n", __DATE__, __TIME__);
+ 
+  // Install signal handler and set signal SIGUSR1 to interrupt blocking system calls
+  signal(SIGUSR1, &DummyHandler);
+  siginterrupt (SIGUSR1, true);
+
+  // Assure lock file is deleted in case of a program crash or call to exit()
+  signal(SIGILL, &CrashHandler);
+  signal(SIGABRT, &CrashHandler);
+  signal(SIGFPE, &CrashHandler);
+  signal(SIGSEGV, &CrashHandler);
+  signal(SIGBUS, &CrashHandler);
+  atexit(&ExitFunction);
+  
+  // Construct main instance
+  static ProcessIO M;
+  
+  // These signals were set during construction of EvidenceServer
+  signal(SIGQUIT, &CrashHandler);  // CTRL-Backspace
+  signal(SIGINT, &CrashHandler);   // CTRL-C
+  signal(SIGHUP, &CrashHandler);   // Terminal closed
+  signal(SIGTERM, &CrashHandler);
+
+  // Handle command-line input
+  while (!M.ExitRequest) {        
+    Command = readline("\rBias> ");
+	
+	// NULL returned if interrupted by signal
+    if (Command == NULL) continue;
+    if (strlen(Command) > 0) add_history(Command);
+
+    // Process command (via DIM gives automatic thread serialisation)
+	DimClient::sendCommand("Bias/Command", Command);
+    free(Command);
+  }
+}
Index: /fact/BIASctrl/Crate.cc
===================================================================
--- /fact/BIASctrl/Crate.cc	(revision 9917)
+++ /fact/BIASctrl/Crate.cc	(revision 9917)
@@ -0,0 +1,277 @@
+/********************************************************************\
+
+  Interface to FACT bias voltage crate
+                
+\********************************************************************/
+
+#include "Crate.h"
+#include "ProcessIO.h" // Must be not in HV.h to avoid problem with declaring class ProcessIO
+
+using namespace std;
+
+//
+// Constructor
+//
+Crate::Crate(string CrateName, int Number, class ProcessIO *PIO) {
+   
+  struct termios tio;
+
+  // Initialize
+  InitOK = false;
+  m = PIO;
+  Name = new char [CrateName.size()+1];
+  strcpy(Name, CrateName.c_str());
+  CrateNumber = Number;
+  WrapCount = -1;
+
+  for (int i=0; i<MAX_NUM_BOARDS; i++) {
+	for (int j=0; j<NUM_CHANNELS; j++) {
+	  OC[i][j] = false;
+	  Current[i][j] = 0;
+	}
+  }
+  ResetHit = false;
+  WrapOK = true;
+  WrapCount = -1;
+  ErrorCount = 0;
+
+  // Create DIM services
+  stringstream ID;
+  ID << setfill('0') << setw(2) << CrateNumber;
+
+  NameService = new DimService ((SERVER_NAME"/NAME/ID"+ID.str()).c_str(), Name);
+  BiasVolt = new DimService ((char *) (SERVER_NAME"/VOLT/ID"+ID.str()).c_str(), (char *) "D", Volt, MAX_NUM_BOARDS*NUM_CHANNELS*sizeof(double));
+
+  ClearVoltageArrays();
+
+  // Open device
+  if ((fDescriptor = open(("/dev/"+CrateName).c_str(), O_RDWR|O_NOCTTY|O_NDELAY)) == -1) {
+    if(errno != 2) m->PrintMessage("Error: Could not open device %d/%s (%s)\n", CrateNumber, Name, strerror(errno));
+    return;
+  }
+
+  // Get current serial port settings
+  if (tcgetattr(fDescriptor, &tio) == -1) {
+    m->PrintMessage("Error: tcgetattr() failed on device %s (%s)\n", Name, strerror(errno));
+    return;   
+  }
+
+  // Set baudrate and raw mode
+  if (cfsetspeed(&tio, BAUDRATE) == -1) {
+	m->PrintMessage("Error: Could not set baud rate of device %s (%s)\n", Name,  strerror(errno));
+	return;
+  }
+  cfmakeraw(&tio);
+  if (tcsetattr(fDescriptor, TCSANOW, &tio ) == -1) {
+	m->PrintMessage("Error: tcsetattr() failed on device %s (%s)\n", Name, strerror(errno));
+	return;
+  }
+  
+  InitOK = true;
+}
+
+//
+// Destructor (Resets board)
+//
+Crate::~Crate() {
+
+  if(fDescriptor != -1) {
+    SystemReset();
+    close(fDescriptor);
+  }
+
+  delete NameService;
+  delete BiasVolt;
+  delete[] Name;
+}
+
+
+// Communicate: Write and read from HV Board until time-out has been reached 
+//
+// Returns: 0 error, 1 success, -1 time-out exceeded
+int Crate::Communicate(unsigned char* wbuf, int Bytes) {
+
+  unsigned char rbuf[3];
+  int N, Ret = 0;
+  fd_set SelectDescriptor;
+  struct timeval WaitTime = {(long) m->fTimeOut, (long) ((m->fTimeOut-(long) m->fTimeOut)*1e6)};
+  
+  // === Lock file descriptor ===
+  if (lockf(fDescriptor, F_LOCK, 0) == -1) {
+	m->Message(m->ERROR, "Failed to lock file descriptor (%s)", strerror(errno));
+	return 0;
+  }
+
+  // === Write data ===
+  if ((N = write(fDescriptor, wbuf, Bytes)) < Bytes) {
+    if (N == -1) m->Message(m->ERROR, "Could not write data to crate (%s)", strerror(errno));
+    else m->Message(m->ERROR, "Could write only %d of %d bytes to board", N, Bytes);
+    ErrorCount++;
+	goto ExitCommunicate;
+  }
+
+  // === Try to read until time-out ===
+  FD_ZERO(&SelectDescriptor);   FD_SET(fDescriptor, &SelectDescriptor);
+  if (select(fDescriptor+1, &SelectDescriptor, NULL, NULL, &WaitTime)==-1) {
+    m->Message(m->ERROR, "Error with select() (%s)", strerror(errno));
+	goto ExitCommunicate;
+  }
+  // Time-out expired?
+  if (!FD_ISSET(fDescriptor, &SelectDescriptor)) {
+  	Ret = -1;
+	goto ExitCommunicate;
+  }
+
+  // Read data   
+  if ((N = read(fDescriptor, &rbuf, 1)) == -1) {
+    m->Message(m->ERROR, "Read error (%s)", strerror(errno));
+    ErrorCount++;
+	goto ExitCommunicate;
+  }
+
+  // === Update status information 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;
+	  else WrapOK = false;
+	}
+	WrapCount = (rbuf[0]>>4) & 7;
+	Ret = 1;
+  }
+  
+  // === UnLock file descriptor ===
+  ExitCommunicate:
+  if (lockf(fDescriptor, F_LOCK, 0) == -1) {
+	m->Message(m->ERROR, "Failed to lock file descriptor (%s)", strerror(errno));
+	return 0;
+  }
+  
+  return Ret;
+}
+
+//
+// System reset of bias crate
+//
+int Crate::SystemReset() {
+  
+  unsigned char wbuf[] = {0,0,0};
+  int ret;
+  
+  if((ret = Communicate(wbuf, 3)) == 1) {
+    ClearVoltageArrays();
+    ErrorCount = 0;
+  }
+  return ret;
+}
+
+//
+// Read channel status
+//
+int Crate::ReadChannel(unsigned int Board, unsigned int Channel) {
+  
+   // Check limits
+  if (Board > MAX_NUM_BOARDS) {
+    m->PrintMessage("Error: Board number out of range\n"); 
+    return 0;
+  }
+  if (Channel > NUM_CHANNELS) {
+    m->PrintMessage("Error: Channel number out of range\n"); 
+    return 0;
+  }
+
+  // Execute command
+  unsigned char wbuf[] = {1<<5 | Board<<1 | (Channel&16)>>4, Channel<<4, 0};
+  int ret;  
+    
+  if ((ret = Communicate(wbuf, 3)) == 1) {
+	Current[Board][Channel] = LastCurrent;
+	OC[Board][Channel] = LastOC;
+  }
+  return ret;
+}
+
+
+// ***** Global set *****
+int Crate::GlobalSet(unsigned int SetPoint) {
+
+  // Check limit
+  if (SetPoint > 0x0FFF) {
+    m->PrintMessage("Error: Voltage DAC value above 0x0FFF\n"); 
+    return 0;
+  }
+
+  // Execute command
+  unsigned char wbuf[] = {1<<6 , SetPoint>>8, SetPoint};
+  int ret;  
+
+  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;
+	}
+  }
+  return ret;
+}
+
+
+// ***** Channel set *****
+int Crate::ChannelSet(int Board, int Channel, unsigned int SetPoint) {
+  
+  // Check limits
+  if (SetPoint > 0x0FFF) {
+    m->PrintMessage("Error: Voltage DAC value above 0x0FFF\n"); 
+    return 0;
+  }
+  if (Board > MAX_NUM_BOARDS) {
+    m->PrintMessage("Error: Board number out of range\n"); 
+    return 0;
+  }
+  if (Channel > NUM_CHANNELS) {
+    m->PrintMessage("Error: Channel number out of range\n"); 
+    return 0;
+  }
+
+  // Execute command
+  unsigned char wbuf[] = {3<<5 |  Board<<1 | (Channel&16)>>4, Channel<<4 | SetPoint>>8, SetPoint};
+  int ret;  
+    
+  if ((ret = Communicate(wbuf, 3)) == 1) {
+    DAC[Board][Channel] = SetPoint;
+	Current[Board][Channel] = LastCurrent;
+	OC[Board][Channel] = LastOC;
+  }
+  return ret;
+}
+
+
+// ***** Synchronize board *****
+bool Crate::Synch() {
+  
+  unsigned char wbuf = 0;
+  int Trial = 0, ret;
+  
+  while(++Trial <= 3) {
+    if((ret = Communicate(&wbuf, 1)) == 1) return true;
+    if (ret == 0) break;
+  }
+  return false;
+}
+
+
+// ***** Set all voltages of board to zero *****
+void Crate::ClearVoltageArrays() {
+
+  for (int i=0; i<MAX_NUM_BOARDS; i++) {
+    for (int j=0; j<NUM_CHANNELS; j++){
+      DAC[i][j] = 0;
+      //Volt[i][j] = 0.0;      
+    }
+  }
+  // Update DIM services
+  BiasVolt->updateService();
+}
Index: /fact/BIASctrl/Crate.h
===================================================================
--- /fact/BIASctrl/Crate.h	(revision 9917)
+++ /fact/BIASctrl/Crate.h	(revision 9917)
@@ -0,0 +1,58 @@
+#ifndef HV_H_SEEN
+#define HV_H_SEEN
+
+#include <termios.h>		// POSIX terminal control definitions
+#include <fcntl.h>		// File control definitions => fcntl() in fcn OpenPort()
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <string>
+
+#include "dis.hxx"
+
+#define MAX_NUM_BOARDS 13	// Maximum number of boards per crate
+#define NUM_CHANNELS 32		// Channels per bias board
+#define BAUDRATE B115200
+
+class ProcessIO;
+
+class Crate {
+  
+    class ProcessIO *m;
+	int CrateNumber;
+	int fDescriptor; 
+	DimService *NameService;
+	bool LastOC;
+	int LastCurrent;
+
+	int Communicate(unsigned char*, int);
+	void ClearVoltageArrays();
+   
+  public:
+    Crate(std::string, int, class ProcessIO *);
+    ~Crate();
+
+    char *Name;
+	DimService *BiasVolt;
+
+	int Current[MAX_NUM_BOARDS][NUM_CHANNELS];
+	bool OC[MAX_NUM_BOARDS][NUM_CHANNELS];
+	bool ResetHit;
+	bool WrapOK;
+	int WrapCount;
+	int ErrorCount;
+
+	int DAC[MAX_NUM_BOARDS][NUM_CHANNELS];      // Voltage in DAC units
+	double Volt[MAX_NUM_BOARDS][NUM_CHANNELS];  // Voltage in Volt
+
+	bool InitOK;
+
+	int SystemReset();
+	int ReadChannel(unsigned int, unsigned int);
+	int GlobalSet(unsigned int);
+	int ChannelSet(int, int, unsigned int);
+	bool Synch();
+};
+
+#endif
Index: /fact/BIASctrl/History.txt
===================================================================
--- /fact/BIASctrl/History.txt	(revision 9917)
+++ /fact/BIASctrl/History.txt	(revision 9917)
@@ -0,0 +1,3 @@
+13/8/2010	Control program for new G-APD bias supply started, using previous
+			program 'hvcontrol' (revision 9852).
+20/8/2010	Removed the possibility to set DAC values.
Index: /fact/BIASctrl/Makefile
===================================================================
--- /fact/BIASctrl/Makefile	(revision 9917)
+++ /fact/BIASctrl/Makefile	(revision 9917)
@@ -0,0 +1,23 @@
+#  Makefile for BIASctrl
+
+SOURCES = BIASctrl.cc Crate.cc ProcessIO.cc ../pixelmap/Pixel.cc ../pixelmap/PixelMap.cc ../Evidence/Evidence.cc 
+OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) 
+INCDIRS   = -I. -I../Evidence -I./src -I$(DIMDIR)/dim
+
+CPPFLAGS = -O3 -Wall $(INCDIRS)
+LDLIBS = -L/usr/lib/termcap -lstdc++ -lpthread -lfl -lreadline -ltermcap $(DIMDIR)/linux/libdim.a
+
+BIASctrl: $(OBJECTS)
+
+clean:
+	@rm -f $(OBJECTS) *.d *~ BIASctrl
+
+-include Dep.d
+
+# Implicit rules
+
+%.d :
+	@echo "Generating dependencies" $@
+	@$(CC) -MM $(SOURCES) $(INCDIRS) \
+	| sed 's/^\(.*\).o:/$@ \1.o:/' > $@
+
Index: /fact/BIASctrl/ProcessIO.cc
===================================================================
--- /fact/BIASctrl/ProcessIO.cc	(revision 9917)
+++ /fact/BIASctrl/ProcessIO.cc	(revision 9917)
@@ -0,0 +1,533 @@
+//
+// 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)
+++ /fact/BIASctrl/ProcessIO.h	(revision 9917)
@@ -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 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
