Index: fact/Evidence/Alarm.cc
===================================================================
--- fact/Evidence/Alarm.cc	(revision 9834)
+++ fact/Evidence/Alarm.cc	(revision 9834)
@@ -0,0 +1,235 @@
+/********************************************************************\
+
+  Alarm handler of the Evidence Control System
+
+  - Checks periodically if all required servers are up
+  - Listens to the 'Message' service of each server and generates new service for
+    each observed server indicating the maximum Severity in the past.
+  - Maximum severity may be reset by a command 'Alarm/ResetAlarm' for a server.
+  - A text describing the current state of all servers is published as DIM service.
+    The states are described in LevelStr[].
+  - A master alarm (indicating most severe of individual alarms) is published. 
+    
+  A mutex is used because UpdateAlarmSummary() may be called from DIM handler thread and
+  from main thread.
+
+  Oliver Grimm, June 2010
+
+\********************************************************************/
+
+#define SERVER_NAME "Alarm"
+#include "Evidence.h"
+
+#include <sstream>
+
+using namespace std;
+
+const char* LevelStr[] = {"OK", "WARN", "ERROR", "FATAL", "UNAVAILABLE"};
+
+//
+// Class declaration
+//
+class AlarmHandler: public DimClient, public EvidenceServer {
+    
+	DimCommand *Command;
+	DimService *Summary, *Master;
+	char *AlarmText;
+	int MasterAlarm;
+
+    void infoHandler();
+	void commandHandler();
+
+  public:
+    AlarmHandler();
+    ~AlarmHandler();
+
+	struct Item {
+	  string Server;
+	  string Email;
+	  DimStampedInfo *Subscription;
+	  DimService *AlarmLevel;
+	  int WarnedLevel;
+	  int Level;
+	};
+	vector<struct Item> List;
+
+	void UpdateAlarmSummary();
+}; 
+
+// Constructor
+AlarmHandler::AlarmHandler(): EvidenceServer(SERVER_NAME) {
+
+  struct Item N;
+  static int InitLevel = -1; // static for DIM service below
+
+  // Initialise
+  MasterAlarm = 0;
+  AlarmText = NULL;
+  
+  // Handling of servies will only start after start()
+  autoStartOff();
+
+  // Create DIM services
+  Summary = new DimService(SERVER_NAME"/Summary", (char *) "not yet available");
+  Master = new DimService(SERVER_NAME"/MasterAlarm", MasterAlarm);
+
+  // Get DIM servers to observe
+  vector<string> Token = Tokenize(GetConfig("servers"));
+
+  for (int i=0; i<Token.size(); i++) {
+	// Extract server name and email
+	vector<string> A = Tokenize(Token[i], ":");
+	N.Server = A[0];
+	if (A.size() == 2) N.Email = A[1];
+	else N.Email = string();
+
+	// DIS_DNS has no Message service
+	if (N.Server == "DIS_DNS") N.Subscription = NULL;
+	else N.Subscription = new DimStampedInfo((N.Server+"/Message").c_str(), NO_LINK, this);
+
+	// Alarm service for server (reference to variable will be updated in UpdateAlarmSummary())
+	N.WarnedLevel = 0;
+	N.Level = -1;
+	N.AlarmLevel = new DimService((N.Server+"/AlarmLevel").c_str(), InitLevel);
+
+	List.push_back(N);
+  }
+
+  // Provide command to reset Level   
+  Command = new DimCommand("ResetAlarm", (char *) "C", this);
+  
+  // List set up, can start handling
+  start(SERVER_NAME);
+}
+
+
+// Destructor
+AlarmHandler::~AlarmHandler() {
+
+  delete Command;
+
+  for (int i=0; i<List.size(); i++) {
+    delete List[i].Subscription;
+    delete List[i].AlarmLevel;
+  }	
+  delete Master;
+  delete Summary;
+  delete[] AlarmText;
+}
+
+
+// Print messages of status changes to screen and update status string
+void AlarmHandler::infoHandler() {
+
+  // Identify status service
+  for (int i=0; i<List.size(); i++) if (getInfo() == List[i].Subscription) {
+	// Update level: unavailable or current severity of status (safely extracted)  
+	if (!ServiceOK(getInfo())) List[i].Level = 4;
+	else {
+	  int Severity = atoi(ToString(getInfo()->getFormat(), getInfo()->getData(), getInfo()->getSize()).c_str());
+	  if (Severity > List[i].Level) List[i].Level = Severity;
+	}
+  }
+
+  UpdateAlarmSummary();
+}
+
+
+// Reset alarm level of given server
+void AlarmHandler::commandHandler() {
+
+  // Safety check
+  string Server = ToString((char *) "C", getCommand()->getData(), getCommand()->getSize());
+  if (getCommand() != Command || Server.empty()) return;
+ 
+  // Reset alarm level, publish/log action and reset server message severity
+  for (int i=0; i<List.size(); i++) if (List[i].Server == Server) {
+    Message(INFO, "Alarm level of server %s reset by %s (ID %d)", Server.c_str(), getClientName(), getClientId());
+	List[i].Level = 0;
+	List[i].WarnedLevel = 0;
+	sendCommandNB((Server+"/ResetMessage").c_str(), (int) 0);
+  }
+  
+  UpdateAlarmSummary();
+}
+
+
+// Update alarm status summary (locking since access can be from main thread and DIM handler threads)
+void AlarmHandler::UpdateAlarmSummary() {
+
+  ostringstream Buf;
+  int Alarm = 0, Ret;  
+
+  Lock();
+
+  for (int i=0; i<List.size(); i++) {
+	// Alarm level description
+	Buf << List[i].Server << ": " << (List[i].Level>=0 && List[i].Level<=4 ? LevelStr[List[i].Level] : "unknown");
+	Buf << " (" << List[i].Level << ")" << endl;
+
+	// Adjust master alarm and update server alarm level
+	if (List[i].Level > Alarm) Alarm = List[i].Level;
+	List[i].AlarmLevel->updateService(List[i].Level);
+
+	// Check if alarm level raised, then send alarm message once
+	if (List[i].WarnedLevel < List[i].Level && !List[i].Email.empty()) {
+	  List[i].WarnedLevel = List[i].Level;
+	  
+	  // Prepare email message
+	  char *Text;
+	  time_t Time = time(NULL);
+	  if (asprintf(&Text, "echo \"Server alarm level '%s' at %s\"|"
+			"mail -s \"Evidence Alarm for '%s'\" %s",
+			List[i].Level>=0 && List[i].Level<=4 ? LevelStr[List[i].Level] : "unknown",
+	  		ctime(&Time), List[i].Server.c_str(), List[i].Email.c_str()) != -1) {
+		system(Text); // Return value depending on OS
+		free(Text);
+	  }
+	  else Message(ERROR, "Could not send alarm email, asprintf() failed");
+	}
+  }
+  
+  // Update master alarm services
+  MasterAlarm = Alarm;   
+  Master->updateService();
+  
+  // Update alarm description (DIM requires variables to be valid until update)
+  char *Tmp = new char[Buf.str().size()+1];
+  strcpy(Tmp, Buf.str().c_str());  
+  Summary->updateService(Tmp);
+
+  delete[] AlarmText;
+  AlarmText = Tmp;
+  
+  Unlock();
+}
+
+//	    
+// Main program
+//
+int main() {
+    
+  DimBrowser Browser;
+  char *Server, *Node;
+  bool Exist;
+  
+  // Static declaration ensures calling of destructor by exit()
+  static AlarmHandler Alarm; 
+  
+  // Check periodically if servers are up
+  while(!Alarm.ExitRequest) {
+
+    for (int i=0; i<Alarm.List.size(); i++) {
+      Exist = false;
+      Browser.getServers();
+      while (Browser.getNextServer(Server, Node) == 1) {
+        if (Alarm.List[i].Server == Server) Exist = true;
+      }
+	  if (!Exist) Alarm.List[i].Level = 4;
+	  else if (Alarm.List[i].Level == -1) Alarm.List[i].Level = 0;
+    }
+    
+    Alarm.UpdateAlarmSummary();
+    sleep(atoi(Alarm.GetConfig("period").c_str()));
+  }
+}
Index: fact/Evidence/Bridge.cc
===================================================================
--- fact/Evidence/Bridge.cc	(revision 9834)
+++ fact/Evidence/Bridge.cc	(revision 9834)
@@ -0,0 +1,281 @@
+/********************************************************************\
+
+  Bridge between two networks
+
+  Subscription to top-level server list DIS_DNS/SERVER_LIST not via
+  AddService() ensures AddService() only called from infoHandler(),
+  thus serialized by DIM and no Mutex is necessary.
+
+  Remote procedure calls are bridged through their underlying services
+  and commands.
+  
+  Configuraton changes are automatically tracked through ConfigChanged()
+    
+  Oliver Grimm, June 2010
+
+\********************************************************************/
+
+#define SERVER_NAME "Bridge"
+#include "Evidence.h"
+
+#include <string>
+#include <vector>
+#include <regex.h>
+
+const int DEFAULT_PORT = 2505;
+
+using namespace std;
+
+// Class declaration
+class Bridge: public DimClient, public EvidenceServer {
+
+	struct Item {
+	  DimCommand *Command;
+	  DimStampedInfo *DataItem;
+	  DimService *Service;
+	  char *Data;
+	};
+
+	map<string, struct Item> Map;
+	vector<regex_t> RegEx;
+	DimInfo *ServerList;
+	
+    void infoHandler();
+    void commandHandler();
+	void AddService(string, char *, int);
+	void RemoveService(string);
+	void ConfigChanged();
+	void BugFix();
+	void Undo();
+
+  public:
+    Bridge();
+    ~Bridge();
+}; 
+
+// Constructor (ConfigChanged() is automatically called at start-up)
+Bridge::Bridge(): EvidenceServer(SERVER_NAME) {
+
+  // Initialise
+  ServerList = NULL;
+  GetConfig("cmdallow", " ");
+  
+  ConfigChanged();
+}
+
+
+// Destructor
+Bridge::~Bridge() {
+
+  Undo();
+}
+
+
+// Undo: Delete all subscriptions and regular expressions
+void Bridge::Undo() {
+
+  while (Map.size() != 0) RemoveService((*Map.begin()).first);  
+  delete ServerList;
+  for (int i=0; i<RegEx.size(); i++) regfree(&RegEx[i]);
+  RegEx.clear();
+}
+
+
+// Called by signal handler in case configuration changes and by constructor
+void Bridge::ConfigChanged() {
+
+  static string ExcludeString;
+
+  // Check if configuratiomn changed
+  string NewExcludeString = GetConfig("exclude");
+
+  Lock();
+  if (ExcludeString != NewExcludeString) ExcludeString.swap(NewExcludeString);
+  Unlock();
+  if (ExcludeString == NewExcludeString) return;
+    
+  // Compile regular expressions
+  regex_t R;
+  vector<regex_t> RegEx;
+
+  vector<string> Exclude = Tokenize(ExcludeString, " \t");
+  for (int i=0; i<Exclude.size(); i++) {
+	int Ret = regcomp(&R, Exclude[i].c_str(), REG_EXTENDED|REG_NOSUB);
+	if (Ret != 0) {
+	  char Err[200];
+	  regerror(Ret, &R, Err, sizeof(Err));
+	  Message(ERROR, "Error compiling regular expression '%s' (%s)", Exclude[i].c_str(), Err);
+	}
+	else RegEx.push_back(R);
+  }
+
+  // Remove previous data, set new regular expressions and subscribe to top-level server list
+  Lock();
+  Undo();
+  Bridge::RegEx = RegEx;
+  ServerList = new DimInfo((char *) "DIS_DNS/SERVER_LIST", NO_LINK, this);
+  Unlock();
+}
+
+
+// Service subscription and repeating
+void Bridge::infoHandler() {
+
+  DimInfo *I = getInfo();
+
+  // Check if service available
+  if (!ServiceOK(I)) return;
+
+  // If service is DIS_DNS/SERVER_LIST, subscribe to all SERVICE_LIST services
+  if (strcmp(I->getName(), "DIS_DNS/SERVER_LIST") == 0) {
+
+	char *Token = strtok(I->getString(), "+-!@");	
+	while (Token != NULL) {
+	  if (*I->getString() == '-' || *I->getString() == '!') {
+		RemoveService(string(Token)+"/SERVICE_LIST");
+	  }
+	  else AddService(string(Token)+"/SERVICE_LIST", (char *) "C", DimSERVICE);
+
+	  // Skip server IP address and process ID
+	  Token = strtok(NULL, "|");
+	  Token = strtok(NULL, "@");
+	}	
+	return;
+  }
+
+  // If service is SERVICE_LIST, scan and subscribe/unsubscribe to services
+  if (strstr(I->getName(), "/SERVICE_LIST") != NULL) {
+
+	// Bug fix for empty SERVICE_LIST
+	if (strlen(I->getString()) == 0) {
+	  string Tmp(I->getName());
+	  RemoveService(I->getName());
+	  AddService(Tmp.c_str(), (char *) "C", DimSERVICE);
+	  return;
+	}
+
+	char *Format, *Name = strtok(I->getString(), "+-!|");
+	while (Name != NULL) {
+      if ((Format = strtok(NULL, "\n")) != NULL) {
+	    // Check if service added or removed/unavailable
+	    if (*I->getString() == '-' || *I->getString() == '!') {
+		  if (strstr(Format, "|RPC") != NULL) {
+	  		RemoveService(string(Name)+"/RpcIn");
+	  		RemoveService(string(Name)+"/RpcOut");
+		  }
+		  else RemoveService(Name);
+		}
+		else {
+		  // Determine type of service
+		  if (strstr(Format, "|CMD") != NULL) {
+			*(strstr(Format, "|CMD")) = '\0';
+			AddService(Name, Format, DimCOMMAND);
+		  }
+		  else if (strstr(Format, "|RPC") != NULL) {
+			*(strstr(Format, "|RPC")) = '\0';
+		    if (strchr(Format, ',') != NULL) {
+	  		  *strchr(Format, ',') = '\0';
+	  		  AddService(string(Name)+"/RpcIn", Format, DimCOMMAND);
+	  		  AddService(string(Name)+"/RpcOut", Format+strlen(Format)+1, DimSERVICE);
+			}
+			
+		  }
+		  else {
+			Format[strlen(Format)-1] = '\0';
+			AddService(Name, Format, DimSERVICE);
+		  }
+		}
+	  }
+	  Name = strtok(NULL, "|");
+	}
+	return;
+  }
+
+  // Check if service known and repeat to secondary DNS
+  if (Map.count(I->getName()) == 0) return;
+
+  // Copy service data
+  delete[] Map[I->getName()].Data;
+  Map[I->getName()].Data = new char [I->getSize()];
+  memcpy(Map[I->getName()].Data, I->getData(), I->getSize());
+
+  // Set new service properties and update service
+  Map[I->getName()].Service->setQuality(I->getQuality());
+  Map[I->getName()].Service->setTimestamp(I->getTimestamp(), I->getTimestampMillisecs());	
+  Map[I->getName()].Service->updateService(Map[I->getName()].Data, I->getSize());
+}
+
+
+// Command repeating (also handles requests for remote procedure calls)
+void Bridge::commandHandler() {
+
+  // Check if client allowed to send commands
+  vector<string> Client = Tokenize(getClientName(), "@");
+  if (Client.size() == 2 && GetConfig("cmdallow").find(Client[1]) == string::npos) {
+	Message(INFO, "Rejected command/rpc from %s (ID %d)", getClientName(), getClientId());
+	return;  
+  } 
+
+  // Send command to server
+  sendCommandNB(getCommand()->getName(), getCommand()->getData(), getCommand()->getSize());
+}
+
+
+// Service subscription
+void Bridge::AddService(string Name, char *Format, int Type) {
+
+  // Check if already subscribed to this service
+  if (Map.count(Name) != 0) return;
+
+  // Should service be ignored?
+  for (int i=0; i<RegEx.size(); i++) {
+    if (regexec(&RegEx[i], Name.c_str(), (size_t) 0, NULL, 0) == 0) return;
+  }
+
+  // Create subscription and service to secondary DNS or new command
+  Map[Name].Command = NULL;
+  Map[Name].DataItem = NULL;
+  Map[Name].Service = NULL;
+  Map[Name].Data = NULL;
+  
+  if  (Type == DimSERVICE) {  
+	Map[Name].Service = new DimService(Name.c_str(), (char *) Format, Map[Name].Data, 0);
+	Map[Name].DataItem = new DimStampedInfo(Name.c_str(), NO_LINK, this);
+  }
+  else if (Type == DimCOMMAND) Map[Name].Command = new DimCommand(Name.c_str(), Format, this);
+}
+
+
+// Remove service from watch list (unused pointer are NULL)
+void Bridge::RemoveService(string Name) {
+
+  // Check if actually subscribed to service  
+  if (Map.count(Name) == 0) return;
+
+  delete Map[Name].DataItem;
+  delete Map[Name].Service;
+  delete[] Map[Name].Data;
+  delete Map[Name].Command;
+
+  Map.erase(Name);
+}
+
+
+// Main program
+int main(int argc, char *argv[]) {
+
+  // Check command line argument
+  if (argc == 1) {
+	printf("Usage: %s <address of primary DNS> [port] (default port %d)\n", argv[0], DEFAULT_PORT);
+	exit(EXIT_FAILURE);
+  }
+
+  // Set primary DIM network to subscribe from
+  DimClient::setDnsNode(argv[1], argc>2 ? atoi(argv[2]) : DEFAULT_PORT);
+
+  // Static ensures calling of destructor by exit()
+  static Bridge Class;
+  
+  // Sleep until signal caught
+  while (!Class.ExitRequest) pause();
+}
Index: fact/Evidence/Config.cc
===================================================================
--- fact/Evidence/Config.cc	(revision 9834)
+++ fact/Evidence/Config.cc	(revision 9834)
@@ -0,0 +1,258 @@
+/********************************************************************\
+
+  Configuration server for the Evidence Control System
+
+  - The name of a configuration file can be given as command line argument
+  - If a configuration file change is detected through inotify, the service
+    "Config/ModifyTime" contains the last modification UNIX time.
+  - The contents of the configuration file is available through Config/ConfigData.
+  - The parser removes all tabs, multiple, leading and trailing spaces, and
+    concatenates all lines until the next item.
+
+  A mutex is used for preventing concurrent access to the configuration data from
+  the methods rpcHandler() and ReadConfig().
+         
+  Oliver Grimm, July 2010
+
+\********************************************************************/
+
+#define SERVER_NAME "Config"
+
+#include "Evidence.h"
+
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/inotify.h>
+#include <sstream>
+
+using namespace std;
+
+//
+// Class derived from DimRpc
+//
+class EvidenceConfig: public DimRpc, public EvidenceServer {
+
+  private:
+	map<string, string> Map;
+    FILE *File;	
+	char *FileContent;
+    DimService *ConfigModified;
+	DimService *ConfigContent;
+
+    void rpcHandler();
+	void AddItem(string, string, string);
+	string RemoveSpaces(string &);
+
+  public:
+    EvidenceConfig(const char *);
+    ~EvidenceConfig();
+	
+	void ReadConfig(); 
+};
+
+
+// Constructor
+EvidenceConfig::EvidenceConfig(const char *Filename):
+	DimRpc("ConfigRequest", "C", "C"), EvidenceServer(SERVER_NAME) {
+
+  ConfigModified = NULL;	// Will be allocated in ReadConfig()
+  ConfigContent = NULL;
+  FileContent = NULL;
+
+  // Open configuration file
+  if ((File = fopen(Filename, "r")) == NULL) {
+    Message(FATAL, "Could not open configuration file '%s' (%s)", Filename, strerror(errno));
+  }
+
+  // Disable buffering, so file modifications are immediately seen
+  if (setvbuf(File, NULL, _IONBF, 0) != 0) {
+    Message(WARN, "Error setting configuration file '%s' to unbuffered mode", Filename);
+  }
+
+  // Create DIM services
+  ReadConfig();
+}
+
+// Destructor
+EvidenceConfig::~EvidenceConfig() {
+
+  if (File != NULL && fclose(File) != 0) Message(ERROR, "Error closing configuration file (%s)", strerror(errno));
+
+  delete ConfigModified;
+  delete ConfigContent;
+  delete[] FileContent;
+}
+
+
+// Implementation of response to configuration request
+void EvidenceConfig::rpcHandler() {
+
+  string Response, Item = ToString((char *) "C", getData(), getSize());
+  
+  // Search for config data in list (default response is empty string)
+  // Lock because ConfigChange() might also access concurrently
+  Lock();
+  if (Map.count(Item) != 0) Response = Map[Item];
+  Unlock();
+
+  // Send data and update Status
+  setData((char *) Response.c_str());
+    
+  Message(INFO, "Client '%s' (ID %d) requested '%s'. Send '%s'.",
+		DimServer::getClientName(),	DimServer::getClientId(), 
+		Item.c_str(), Response.c_str());
+}
+
+
+// Signalisation of configuration change
+void EvidenceConfig::ReadConfig() {
+
+  static int ModifyTime;
+
+  //
+  // Part A: Handle updates to DIM services
+  //
+  
+  // Access status information for configuration file (if fileno() returns -1, fstat() reports error)
+  struct stat Stat;
+  if (fstat(fileno(File), &Stat) == -1) {
+    Message(ERROR, "Error with stat() system call for configuration file, cannot update configuration file information (%s)", strerror(errno));
+	delete ConfigModified;
+	delete ConfigContent;
+	ConfigModified = NULL;
+	ConfigContent = NULL;
+	return;
+  }
+
+  // Create/update DIM service to indicate changes of configuration file
+  ModifyTime = Stat.st_mtime;   
+  if (ConfigModified == NULL) ConfigModified = new DimService (SERVER_NAME"/ModifyTime", ModifyTime);
+  else ConfigModified->updateService();
+  
+  // Read new configuration file (enfore \0 termination)
+  char *NewContent = new char [Stat.st_size+1];
+  rewind(File);
+  if (fread(NewContent, sizeof(char), Stat.st_size, File) != Stat.st_size) {
+    Message(FATAL, "Could not read configuration file");
+  }
+  NewContent[Stat.st_size] = '\0';
+
+  // Create/update DIM service that contains the current configuration file
+  if (ConfigContent == NULL )ConfigContent = new DimService(SERVER_NAME"/ConfigData", NewContent);
+  else ConfigContent->updateService(NewContent);
+
+  delete[] FileContent;
+  FileContent = NewContent;
+
+  //
+  // Part B: Interpret configuration list
+  //
+
+  stringstream In(FileContent), Out;
+  string Section, Item, Line, Data;
+  
+  // First clean up and concatenate lines
+  while (getline(In, Line).good()) {
+    // Remove comments
+	if (Line.find('#') != string::npos) Line.erase(Line.find('#'));
+    // Replace all tabs by spaces
+    while (Line.find("\t") != string::npos) Line[Line.find("\t")] = ' ';
+	// Remove empty lines
+	if (RemoveSpaces(Line).empty()) continue;
+	// Concatenate if line ends with '\'
+    if (Line[Line.size()-1] != '\\') Out << Line << endl;
+	else Out << Line.erase(Line.size()-1);
+  };
+  
+  In.str(Out.str());
+  In.clear();
+
+  // Interpret data
+  while (getline(In, Line).good()) {
+
+	// Check if current line is section heading (contains only [xxx])
+	if (Line.find('[')==0 && Line.find(']')==Line.size()-1) {
+	  // Add previous item to list (if any)
+	  AddItem(Section, Item, Data);
+	  Item.clear();
+	  Data.clear();
+	  // Extract new section name
+	  Section = Line.substr(1, Line.size()-2);
+	  continue;
+	}
+
+	// Check if current line contains equal sign (defines new item name)
+	if((Line.find('=')) != string::npos) {
+	  // Add previous item to list
+	  AddItem(Section, Item, Data);
+	  // Extract parameter name and data
+	  Item = string(Line, 0, Line.find('=')-1);
+	  Data = string(Line, Line.find('=')+1, string::npos);
+	}
+	else Data += ' ' + Line; // Concatenate lines
+  }
+  // Add last item
+  AddItem(Section, Item, Data);
+}
+
+// Add item to configuration list
+void EvidenceConfig::AddItem(string Section, string Parameter, string Data) {
+
+  // Clean up strings
+  RemoveSpaces(Parameter);
+  RemoveSpaces(Data);
+  if (Section.empty() || Parameter.empty() || Data.empty()) return;
+
+  // Add to configuration list
+  Lock();
+  Map[Section + " " + Parameter] = Data;
+  Unlock();
+}
+
+// Removes whitespace
+string EvidenceConfig::RemoveSpaces(string &Text) {
+
+  // Remove leading spaces
+  while (!Text.empty() && isspace(Text[0])) Text.erase(0, 1);
+  // Remove trailing spaces
+  while (!Text.empty() && isspace(Text[Text.size()-1])) Text.erase(Text.size()-1);
+  // Remove multiple spaces
+  while (Text.find("  ") != string::npos) Text.erase(Text.find("  "), 1);
+  
+  return Text;
+}
+
+//	    
+// Declaring class static ensures destructor is called when exit() is invoked 
+//
+int main(int argc, char *argv[]) {
+
+  if (argc != 2) {
+	printf("Usage: %s <Configuration-File>\n", argv[0]);
+	exit(EXIT_FAILURE);
+  }
+  
+  static EvidenceConfig Config(argv[1]);
+  int Notify;
+  struct inotify_event Event;
+  
+  if ((Notify = inotify_init()) == -1) {
+    Config.Message(EvidenceConfig::WARN, "inotify_init() failed, cannot monitor changes of configuration file (%s)\n", strerror(errno));
+  }
+  else if (inotify_add_watch(Notify, argv[1], IN_MODIFY) == -1) { 
+      Config.Message(EvidenceConfig::WARN, "Could not set inotify watch on configuration file (%s)\n", strerror(errno));
+	  close(Notify);
+	  Notify = -1;
+  }
+
+  // Sleep until file changes or signal caught 
+  while (!Config.ExitRequest) {
+    if (Notify != -1) {
+	  if (read(Notify, &Event, sizeof(Event)) == sizeof(Event)) Config.ReadConfig();
+	}
+    else pause();	  
+  }
+  
+  // Closing will also free all inotify watches
+  if (Notify != -1) close(Notify);
+}
Index: fact/Evidence/DColl.cc
===================================================================
--- fact/Evidence/DColl.cc	(revision 9834)
+++ fact/Evidence/DColl.cc	(revision 9834)
@@ -0,0 +1,401 @@
+/********************************************************************\
+
+  Central data collector of the Evidence Control System
+  
+  - DColl subscribes to all services given by the configuration
+    server and writes these to the data file at every update.
+  - One data file per day is generated.
+  - The command 'DColl/Log' writes the associated string to the log file
+  
+  Oliver Grimm, June 2010
+
+\********************************************************************/
+
+#define SERVER_NAME "DColl"
+#define LOG_FILENAME "Evidence.log"
+
+#include "Evidence.h"
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include <vector>
+#include <sys/stat.h>
+#include <regex.h>
+
+using namespace std;
+
+//
+// Class declaration
+//
+class DataHandler:	public DimClient, public EvidenceServer {
+
+	struct Item {
+	  DimStampedInfo *DataItem;
+	  bool Exclude;
+	};
+	vector<struct Item> List;
+	
+	DimCommand *LogCommand;
+	DimInfo *ServerList;
+		
+    FILE *DataFile;
+    FILE *LogFile;
+	char *Filename;
+	float DataSizeMB, LogSizeMB;
+	int DataSizeLastUpdate, LogSizeLastUpdate;
+    DimService *LogSizeService, *DataSizeService, *DataFilename;
+	int TimeForNextFile;
+
+	vector<regex_t> RegEx;
+
+    void infoHandler();
+    void commandHandler();
+	void AddService(string);
+	void RemoveService(string);
+	off_t FileSize(FILE *);
+
+  public:
+    DataHandler();
+    ~DataHandler();
+}; 
+
+//
+// Constructor
+//
+DataHandler::DataHandler(): EvidenceServer(SERVER_NAME) {
+
+  // Initialization to prevent freeing unallocated memory 
+  DataFile = NULL;
+  LogFile = NULL;
+  Filename = NULL;
+  
+  LogSizeService = NULL;
+  DataSizeService = NULL;
+  
+  DataSizeLastUpdate = 0;
+  LogSizeLastUpdate = 0;
+  TimeForNextFile = 0;
+
+  // Request configuration data / initialise for later non-blocking updates
+  GetConfig("basedir");
+  GetConfig("sizeupdate");
+  GetConfig("rollover");
+
+  // Open log file
+  if ((LogFile = fopen((GetConfig("basedir")+"/"+LOG_FILENAME).c_str(), "a")) == NULL) {
+    Message(FATAL, "Could not open log file (%s)", strerror(errno));
+  }
+             
+  // Create services for file sizes and data file name
+  DataSizeMB = 0;
+  DataSizeService = new DimService(SERVER_NAME "/DataSizeMB", DataSizeMB);
+  
+  LogSizeMB = FileSize(LogFile)/1024.0/1024.0;
+  LogSizeService = new DimService(SERVER_NAME "/LogSizeMB", LogSizeMB);
+
+  DataFilename = new DimService(SERVER_NAME "/CurrentFile", (char *) "");
+
+  // Compile regular expressions
+  regex_t R;
+  vector<string> Exclude = Tokenize(GetConfig("exclude"), " \t");
+  for (int i=0; i<Exclude.size(); i++) {
+	int Ret = regcomp(&R, Exclude[i].c_str(), REG_EXTENDED|REG_NOSUB);
+	if (Ret != 0) {
+	  char Err[200];
+	  regerror(Ret, &R, Err, sizeof(Err));
+	  Message(ERROR, "Error compiling regular expression '%s' (%s)", Exclude[i].c_str(), Err);
+	}
+	else RegEx.push_back(R);
+  }
+
+  // Provide logging command   
+  LogCommand = new DimCommand("DColl/Log", (char *) "C", this);
+
+  // Subsribe to top-level server list (not via AddService() due to thread issue)
+  ServerList = new DimInfo((char *) "DIS_DNS/SERVER_LIST", NO_LINK, this);
+}
+
+//
+// Destructor: Close files and free memory
+//
+DataHandler::~DataHandler() {
+
+  // Delete all DIM subscriptions
+  delete ServerList;
+  while (List.size() != 0) RemoveService(List[0].DataItem->getName());
+  
+  delete LogCommand;
+  delete DataFilename;
+  delete[] Filename;
+  
+  delete LogSizeService;
+  delete DataSizeService;
+
+  // Close files
+  if (LogFile != NULL) if (fclose(LogFile) != 0) {
+	Message(ERROR, "Could not close log file (%s)", strerror(errno));
+  }
+  if (DataFile != NULL && fclose(DataFile) != 0) {
+	Message(ERROR, "Error: Could not close data file (%s)", strerror(errno));
+  }
+
+  // Free memory for regular expressions handling
+  for (int i=0; i<RegEx.size(); i++) regfree(&RegEx[i]);
+}
+
+//
+// Implementation of data handling
+//
+// DIM ensures infoHandler() is called serialized, therefore
+// no mutex is needed to serialize writing to the file
+void DataHandler::infoHandler() {
+
+  // Check if service available
+  DimInfo *I = getInfo();
+  if (!ServiceOK(I)) return;
+
+  //
+  // ====== Part A: Handle service subscriptions ===
+  //
+  // Services are added here, removal only in destructor.
+  
+  // If service is DIS_DNS/SERVER_LIST, subscribe to all SERVICE_LIST services
+  if (strcmp(I->getName(), "DIS_DNS/SERVER_LIST") == 0) {	
+	char *Token = strtok(I->getString(), "+-!@");	
+	while (Token != NULL) {
+	  AddService(string(Token)+"/SERVICE_LIST"); // 'add' also for '-' and '!'
+	  Token = strtok(NULL, "|"); // Skip server IP address
+	  Token = strtok(NULL, "@");
+	}	
+	return;
+  }
+
+  // If service is SERVICE_LIST of any server, scan all services.
+  // Subscribe to all services (but not to commands and RPCs)
+  if (strstr(I->getName(), "/SERVICE_LIST") != NULL) {
+
+	// Bug fix for empty SERVICE_LIST
+	if (strlen(I->getString()) == 0) {
+	  string Tmp(I->getName());
+	  RemoveService(I->getName());
+	  AddService(Tmp.c_str());
+	  return;
+	}
+
+	char *Name = strtok(I->getString(), "+-!|");
+	while (Name != NULL) {
+	  // Check if item is a service
+	  char *Type = strtok(NULL, "\n");
+	  if (Type == NULL) return; // for safety, should not happen
+      if (strstr(Type, "|CMD")==NULL && strstr(Type, "|RPC")==NULL) AddService(Name); // 'add' also for '-' and '!'
+	  Name = strtok(NULL, "|");
+	}
+	return;
+  }
+
+  //
+  // ====== Part B: Handle opening of data files ===
+  //
+
+  // If it is time to open new data file, close the current one 
+  if (time(NULL) >= TimeForNextFile) {
+	if (DataFile != NULL && fclose(DataFile) != 0) {
+	  Message(ERROR, "Error: Could not close data file (%s)", strerror(errno));
+	}
+	DataFile = NULL;
+  }
+
+  // Open new data file if necessary
+  if (DataFile == NULL) {
+	time_t Time = time(NULL);
+	struct tm *T = localtime(&Time);
+
+	// Get time structure with date rollover
+	if (T->tm_hour >= atoi(GetConfig("rollover").c_str())) T->tm_mday++;
+	if (mktime(T) == -1) Message(ERROR, "mktime() failed, check filename");
+
+	// Create direcory if not existing (ignore error if already existing)
+	char *Dir;
+	if (asprintf(&Dir, "%s/%d", GetConfig("basedir").c_str(), T->tm_year+1900) == -1) {
+	  Message(FATAL, "asprintf() failed, could not create direcory name");	
+	}
+	if(mkdir(Dir, S_IRWXU|S_IRWXG)==-1 && errno!=EEXIST) {
+	  Message(FATAL, "Could not create directory '%s' (%s)", Dir, strerror(errno));
+	}
+
+	// Create filename
+	free(Filename);
+	if (asprintf(&Filename, "%s/%d%02d%02d.slow", Dir, T->tm_year+1900, T->tm_mon+1, T->tm_mday) == 1) {
+	  Message(FATAL, "asprintf() failed, could not create filename");		  
+	}
+	free(Dir);
+	
+	// Open file
+	if ((DataFile = fopen(Filename, "a")) == NULL) {
+      Message(FATAL, "Could not open data file '%s' (%s)", Filename, strerror(errno));
+	}
+	else Message(INFO, "Opened data file '%s'", Filename);
+	DataFilename->updateService(Filename);
+	
+	// Calculate time for next file opening
+	T->tm_sec = 0;
+	T->tm_min = 0;
+	T->tm_hour = atoi(GetConfig("rollover").c_str());
+	TimeForNextFile = mktime(T);
+  }
+
+  //
+  // ====== Part C: Handle writing to data file ===
+  //
+
+  // Identify index of service
+  for (int Service=0; Service<List.size(); Service++) if (I == List[Service].DataItem) {
+
+	// Service excluded from writing or contains no data?
+	if (List[Service].Exclude || I->getSize()==0) return;
+	
+	// Write data header
+	time_t RawTime = I->getTimestamp();
+	struct tm *TM = localtime(&RawTime);
+
+	fprintf(DataFile, "%s %d %d %d %d %d %d %d %d %lu ", I->getName(), I->getQuality(), TM->tm_year+1900, TM->tm_mon+1, TM->tm_mday, TM->tm_hour, TM->tm_min, TM->tm_sec, I->getTimestampMillisecs(), I->getTimestamp());
+
+	// Translate data into ASCII
+	string Text = EvidenceServer::ToString(I->getFormat(), I->getData(), I->getSize());
+
+	if (!Text.empty()) {
+	  // Replace new line by '\' and all other control characters by white space
+	  for (int i=0; i<Text.size(); i++) {
+		if (Text[i] == '\n') Text[i] = '\\';
+		else if (iscntrl(Text[i])) Text[i] = ' ';
+	  }
+	  // Write to file
+      fprintf(DataFile, "%s\n", Text.c_str());
+	}
+	else fprintf(DataFile, "Cannot interpret service format\n");
+
+	// Terminate if error because otherwise infinite loop might result as
+	// next call to this infoHandler() will try to (re-)open file
+	if(ferror(DataFile) != 0) {
+      if (fclose(DataFile) != 0) Message(ERROR, "Error closing data file (%s)", strerror(errno));
+	  DataFile = NULL;
+	  Message(FATAL, "Error writing to data file, closed file (%s)", strerror(errno));
+	}
+
+	// Update datafile size service (not every time to avoid infinite loop)
+	if (time(NULL) - DataSizeLastUpdate > max(atoi(GetConfig("sizeupdate").c_str()), 1)) {
+      fflush(DataFile); // not continuously to reduce load
+
+	  DataSizeMB = FileSize(DataFile)/1024.0/1024.0;
+	  DataSizeService->updateService();
+	  DataSizeLastUpdate = time(NULL);
+	}  
+  } // Check for disk writing
+}
+
+//
+// Implementation of log writing
+//
+void DataHandler::commandHandler() {
+
+  // Translate text safely to string
+  string Text = ToString((char *) "C", getCommand()->getData(), getCommand()->getSize());
+
+  // Safety check
+  if (getCommand() != LogCommand || LogFile == NULL || Text.empty()) return;
+  
+  // Replace all carriage-return by line feed and non-printable characters
+  for (unsigned int i=0; i<Text.size(); i++) {
+    if (Text[i] == '\r') Text[i] = '\n';
+	if(isprint(Text[i])==0 && isspace(Text[i])==0) Text[i] = ' ';
+  }
+ 
+  time_t RawTime = time(NULL);
+  struct tm *TM = localtime(&RawTime);
+
+  fprintf(LogFile, "%.2d/%.2d/%4d %.2d:%.2d:%2.d %s (ID %d): %s\n",
+  		TM->tm_mday, TM->tm_mon+1, TM->tm_year+1900,
+		TM->tm_hour, TM->tm_min, TM->tm_sec, getClientName(), getClientId(), Text.c_str());
+  fflush(LogFile);
+  
+  // If error close file (otherwise infinite loop because Message() also writes to log) 
+  if(ferror(LogFile)) {
+    fclose(LogFile);
+	LogFile = NULL;
+    Message(ERROR, "Error writing to log file, closing file (%s)", strerror(errno));
+  }
+    
+  // Update logfile size service (not every time to avoid infinite loop)
+  if (time(NULL) - LogSizeLastUpdate > atoi(GetConfig("sizeupdate").c_str())) {
+	LogSizeMB = FileSize(LogFile)/1024.0/1024.0;
+	LogSizeService->updateService();
+	LogSizeLastUpdate = time(NULL);
+  }
+}
+
+
+//
+// Add service to watch list
+//
+void DataHandler::AddService(string Name) {
+
+  struct Item New;
+
+  // Check if already subscribed to this service
+  for (int i=0; i<List.size(); i++) {
+	if (Name == List[i].DataItem->getName()) return;
+  }
+  
+  // Should service be ignored?
+  New.Exclude = false;
+  for (int i=0; i<RegEx.size(); i++) {
+    if (regexec(&RegEx[i], Name.c_str(), (size_t) 0, NULL, 0) == 0) New.Exclude = true;
+  }
+
+  // Subscribe to service
+  New.DataItem = new DimStampedInfo(Name.c_str(), NO_LINK, this);
+
+  List.push_back(New);
+}
+
+
+//
+// Remove service from watch list
+//
+void DataHandler::RemoveService(string Name) {
+
+  // Find service index
+  vector<struct Item>::iterator E;
+  for (E=List.begin(); E<List.end(); ++E) if (Name == (*E).DataItem->getName()) {
+	delete (*E).DataItem;	
+	List.erase(E);
+  }
+}
+
+//
+// Determine size of file in kB
+//
+off_t DataHandler::FileSize(FILE *File) {
+
+  struct stat FileStatus;
+
+  if (fstat(fileno(File), &FileStatus) == -1) {
+     Message(WARN, "Could not determine size of file (%s)", strerror(errno));
+	 return -1;
+  }
+  return FileStatus.st_size;
+}
+
+
+//	    
+// Main program
+//
+int main() {
+        
+  // Static ensures calling of destructor by exit()
+  static DataHandler DataInstance;
+  
+  // Sleep until exit requested
+  while (!DataInstance.ExitRequest) pause();
+}
Index: fact/Evidence/Doc/Evidence.tex
===================================================================
--- fact/Evidence/Doc/Evidence.tex	(revision 9834)
+++ fact/Evidence/Doc/Evidence.tex	(revision 9834)
@@ -0,0 +1,547 @@
+\documentclass[10pt,twoside,fleqn,a4paper]{article}
+
+\usepackage[bf]{caption}
+\usepackage{listings}
+\usepackage{graphicx}
+\usepackage{subfigure}
+\usepackage{amssymb}
+\usepackage{textcomp}
+\usepackage{units}
+\usepackage{exscale}
+\usepackage{url}
+\usepackage{listings} 
+\usepackage[light]{draftcopy}
+\usepackage{longtable}
+
+
+\newcommand{\E}{\lstinline{Evidence} }
+
+\renewcommand{\textfraction}{.15}
+\renewcommand{\topfraction}{.85}
+\renewcommand{\bottomfraction}{.85}
+
+\setlength{\oddsidemargin}{2.5cm}
+\addtolength{\oddsidemargin}{-1in} % because of strange definition of origin
+\setlength{\evensidemargin}{2.5cm}
+\addtolength{\evensidemargin}{-1in}
+\setlength{\topmargin}{2.5cm}
+\addtolength{\topmargin}{-1in}
+\setlength{\textheight}{23.2cm}
+\setlength{\textwidth}{16.0cm}
+
+\frenchspacing
+\lstset{basicstyle=\ttfamily,breaklines=true}
+
+\begin{document}
+
+\title{\E --- A control system for laboratory application and small-scale experiments}
+\author{Oliver Grimm, ETH Z\"{u}rich}
+\maketitle
+
+This report describes the design and basic functionality of the \E control system. This essentially is a C++ class and a set of programs running on Linux for controlling small scale experiments. It is based on CERN's DIM library for interprocess communication over TCP/IP connections. \lstinline{$Rev$}
+
+\tableofcontents
+
+% ===================================================
+
+\section{Overview of \E}
+\label{Overview}
+
+The \E control system has been developed for application in small-scale experiments, for which established and comprehensive control systems like EPICS, DOOCS or PVS-II are too large and impose too much overburden.\footnote{Information on these systems can be found at \url{http://www.aps.anl.gov/epics/}, \url{http://tesla.desy.de/doocs}, \url{http://www.etm.at/}.} The development of \E has been started within the FACT project (\emph{First G-APD Cherenkov Telescope} \cite{Bra09,And10}).  
+
+An experiment control system often comprises several individual programs that require configuration information, produce data that should be stored and easily visualized, and at least partly need to exchange information between each other. The intention of \E is to a) integrate this with a minimum of extra coding on part of the applications, b) to achieve this using as far as reasonable established tools, c) to be centralized\footnote{This is a design choice that partly defines the range of applicable experiments. For large systems decentralization can be a better approach.}, and d) to be lean. 
+The common functionality is collected in a small C++ class. Its main objective is to allow easy surveillance of the system by a central alarm server and aiding the programmer by providing a few standard routines. It supports easy message logging as well as the distribution of warning and error conditions by the individual programs, but otherwise only little definite structure is imposed on the programmer
+ 
+\E uses the DIM (Distributed Information Management) system\footnote{http://dim.web.cern.ch/dim} as communication layer \cite{Gas01}. This system is developed and actively maintained at CERN for more than 17 years. DIM itself can be compiled on many platforms, but \E is developed on Linux and uses several functions specific to this operating system.
+
+Sections \ref{Overview} and \ref{DIM-Basics} give a general overview of \E and its components, the remaining sections contain more technical details.
+
+
+\subsection{Main programs}
+
+The functionality of the main programs of the the \E control system\footnote{The denomination \emph{control system} for the core programs of \E is exaggerated, as without further servers that provide experiment-specific functionality, \E would only control itself. Lacking a better term, this report sticks to it.} are briefly listed here. More details, including all required configuration items and services provided, can be found in Sect.\,\ref{ServerDetails}.
+
+\begin{itemize}
+\item \lstinline{Config}\\
+The configuration server distributes text data read from a configuration file to clients via remote procedure calls. The configuration file is watched for modifications and clients are informed on updates via a DIM service.
+ 
+\item \lstinline{DColl}\\
+The data collector dynamically subscribes to all DIM services (except those explicitely excluded) and writes the corresponding data at every service update to a file. It also provides a central logging facility via a DIM command.
+
+\item \lstinline{Alarm}\\
+The alarm server subscribes to the standardized message service of a given list of servers and monitors their severity. It generates a master alarm and can send email notifications in case of server warnings, errors or unavailability. An alarm has to be explicitely acknowledged through a DIM command to be reset.
+ 
+\item \lstinline{History}\\
+The history server keeps a number of updates of all services in a memory ring buffer and can send the buffer to a client if requested through a DIM remote procedure call. This can be used by user interfaces to provide a quick visualization of the past behavior of a given service without accessing the data stored on disk.
+
+\item \lstinline{Bridge}\\
+The bridge connects two DIM networks, repeating services, commands and remote procedure calls from one to the other. It allows commands only from a given set of IP addresses, thus providing some limited access control (but no authentication).
+\end{itemize}
+
+A graphical user interface has been developed for the FACT project, see Sect.\,\ref{User-Interface}. It consists of a set of Qt widgets adapted to display the contents of DIM services and history buffers, as well as sending commands.
+ 
+ 
+\section{Basic DIM functionality}
+\label{DIM-Basics}
+
+As DIM defines the basic operation principle of the control system, its functionality is briefly sketched here. Details explained in the DIM manual are not reported, but features and caveats not properly documented are summarized in Appendix \ref{DIMDetails}.
+
+DIM uses the client/server approach. DIM services are known to the user only by their name\footnote{The name is a standard C string.}, not by the location of the corresponding server on the network. A central name server brings clients and servers in contact. If a client wants to subscribe to a particular service, he first contacts the name server and then establishes, using the obtained information, a direct TCP/IP connection to the server. The name server is then not involved further and could even go down without affecting the established connections.
+
+There is no access control implemented within DIM. Authentication should be done via other methods that allow to restrict access to IP addresses and ports. An example configuration for the Linux \lstinline{IPtables} package is given in Appendix \ref{FirewallExample}.
+
+
+\subsection{Server and client}
+
+A server provides, via unique names, services, commands and remote procedure calls. A DIM service contains data that a client can subscribe to, a DIM command is a one way transfer of data from a client to a server\footnote{The client can optionally be informed if the command arrived at the server, but not if it was actually processed.}, and a DIM remote procedure call in addition will sent back data to the client.
+
+Subscription in this context means that the client can be informed automatically if updated data becomes available or if the service became unavailable. A client can also choose to be updated at regular intervals.\footnote{Also in this case the update is initiated by the server, to which the requested periodic update rate is send.} Subscribing to a not yet existing service is possible. The connection will then be established automatically as soon as it becomes available.
+
+A dedicated TCP/IP connection is established between each server and client.\footnote{That also means that if 10 clients subscribe to a particular service, the data will be send 10 times over the network. This eventually will limit the scalability of the system.} A server takes at startup the first free port in the range between 5100 and 6000 to respond to clients.
+
+The overhead imposed by DIM on a TCP/IP connection is small. The throughput can almost reach the line speed, but depends to some extend on the amount of data transmitted per service update. A non-negligible load is generated at service reception by the thread-based approach of DIM as data arrival normally triggers the execution of a handler in a separate thread. This requires a context switch that constrains even on fast computers the rate of updates and, for small service sizes, the throughput.
+
+\subsection{Name server}
+
+The name server keeps a list of all services, commands and remote procedure calls available in the system and their corresponding server addresses. It distributes this information to clients wishing to make a connection. This is done by the DIM library in the background and requires no user action. The information is also accessible to a client via a DIM service (see Appendix \ref{ServiceFormats}).
+
+\subsection{Implementation in an application}
+
+Implementing the DIM server functionality requires only little additional coding in an existing program, typically a few lines of code and inclusion of the DIM library. The library handles all communication transparently in the background (see Sect.\,\ref{Implementation} for more details).
+
+DIM provides C++, C, Fortran, Java and Python interfaces, and can be compiled for various variants of Linux and Windows on 32 and 64 bit architectures.
+
+At several places, handlers (also called call-back routines) can be implemented by the user application that are then invoked automatically by the DIM library when a request from a client or an answer from a server arrives. These handlers are executed in a separate thread, and care has to be taken by the programmer in accessing data structures from a handler. All calls to handlers are strictly serialized by DIM, thus an access protection mechanism is not needed if data is only accessed from within these handlers.
+
+
+\section{Detailed description of the \E class}
+
+In principle, there is no need to use a particular collection of functions beyond those defined by DIM itself to interconnect programs with DIM. A certain number of tasks, however, are repetitive, and some standardization is beneficial for supervising the server functionality.
+
+The C++ class \lstinline{EvidenceServer} is used by all the main programs of \E to provide the common functionality that is needed for all of them. It is suggested that also user applications use this class. \lstinline{EvidenceServer} can simply be inherited by the user class, as shown in Sect.\,\ref{Server-Programming}.
+
+There is no corresponding \lstinline{EvidenceClient} class. Those methods that can be used also by a client are declared \lstinline{static} in \lstinline{EvidenceServer} and can thus be invoked without instantiation.
+
+\subsection{Class functionality}
+ 
+\begin{itemize}
+\item Starts the DIM server.
+\item Provides a standard text message service \lstinline{SvrName/Message}, including encoding of the message severity (INFO, WARN, ERROR, FATAL) and automatic logging of it. The initial message published (and thus registered by the data collector) contains the subversion revision number and built time of the server. The DIM service format is \lstinline{"I:1;C"}.
+\item Provides a method for configuration requests. If the configuration data is not available, the application terminates with a  message of FATAL severity unless default data is given.
+\item Provides a method for safely translating DIM service data into text.
+\item Implements the virtual DIM methods \lstinline{exitHandler()}. It can be called through a standard DIM command \lstinline{SvrName/EXIT}, taking a single integer as argument. Upon first invocation, the handler just sets the flag \lstinline{ExitRequest} which should be handled by the application. Upon second invocation, it will call \lstinline{exit()}. The user application can override this handler.
+\item Provides a DIM command \lstinline{SvrName/ResetMessage}. It will set the message service to INFO severity with the information which client issued the command. This can be used to remove a WARN, ERROR or FATAL serverity once the problem has been fixed. The \lstinline{Alarm} server uses this command if it is instructed to reset an alarm level. The command takes no arguments.
+\item Implements the virtual DIM methods \lstinline{errorHandler()}. The error handler will issue a message with ERROR severity that contains the DIM error code. The user application can override this handler.
+\item Installs signal handler for SIGQUIT (ctrl-backspace), SIGTERM, SIGINT (ctrl-c), and SIGHUP (terminal closed). The signal handler sets first \lstinline{ExitRequest}, and on second invocation calls \lstinline{exit()}. After instantiating the class, the programmer may override the handlers.
+\item Catches un-handled C++ exceptions and extracts as much information from the exception as possible.\footnote{This termination handler is taken directly from the code of the \lstinline{g++} compiler and is thus compiler specific.} That information is also published as a message.
+\item Subscribes to the service \lstinline{Config/ModifyTime}. Upon updates to the configuration file, a call-back routine of the user application can be automatically invoked, see Sect.\,\ref{ConfigHandling} for more details.
+\end{itemize}
+
+
+\subsection{\lstinline{public} class methods}
+\label{EvidenceServer-Methods}
+
+The \lstinline{public} part of the header file \lstinline{Evidence.h} is as follows. The namespace designation \lstinline{std} has been left out for clarity in this listing.
+
+\begin{lstlisting}[numbers=left,numberstyle=\tiny,stepnumber=2,numbersep=5pt]
+#define NO_LINK (char *) "__&DIM&NOLINK&__"
+
+class EvidenceServer: public DimServer {
+
+  public:
+    EvidenceServer(string);
+    ~EvidenceServer();
+
+    enum MessageType {INFO=0, WARN=1, ERROR=2, FATAL=3};
+
+    void Message(MessageType, const char *, ...);
+    void SendToLog(const char *, ...);
+    string GetConfig(string, string = string());
+	virtual void ConfigChanged() {};
+    void Lock();
+    void Unlock();
+    static string ToString(char *, void *, int);
+    static bool ServiceOK(DimInfo *);
+    static bool ServiceOK(DimRpcInfo *);
+	static bool ServiceOK(DimCurrentInfo *);
+    static vector<string> Tokenize(const string &, const string & = " ");
+
+    bool ExitRequest;
+};
+\end{lstlisting}
+
+The class methods are thread safe as they either use only local data or lock access if necessary. \lstinline{NO_LINK} is used for service subscription by clients, see the method \lstinline{ServiceOK()} below and Sect.\,\ref{Client-Programming}
+
+The constructor \underline{\lstinline{EvidenceServer()}} takes the server name as argument which is subsequently automatically added to logging and message texts and also used for configuration requests with \lstinline{GetConfig()}.
+
+\underline{\lstinline{Message()}} updates the standard message service with the given text and severity. Formatting is as for \lstinline{printf()}. The text is also sent to the console and to the log file with \lstinline{SendToLog()}. In case of FATAL severity \lstinline{exit()} is invoked, so that the application can safely assume the call will not return. The permanent buffer for the DIM service is automatically allocated and freed.
+
+\underline{\lstinline{SendToLog()}} sends the text to the central log file via a non-blocking command. That method can be called also in a termination or crash handler.
+
+\underline{\lstinline{GetConfig()}} issues, on first invocation, a DIM remote procedure call to the configuration server to retrieve the required data and returns it as a string. The second argument gives the data to be returned in case the server is unavailable or cannot provide the requested data. If in this case the second string is empty, the program terminates with a FATAL message. Using the service \lstinline{Config/ModifyTime}, the server keeps track of changes to the configuration file in the background. Upon subsequent requests for the same configuration data, it only issues a remote procedure call again if the file changed in the meantime. If not, the same data already retrieved is returned. This way, this function can be repeatedly called, even at high rate, without generating unnecessary load to the configuration server (as the configuration file does not change frequently).
+
+The virtual method \underline{\lstinline{ConfigChanged()}} is executed in a separate thread when the configuration file changes. It can be reimplemented by the application. Calls to \lstinline{GetConfig()} from this method will be blocking and thus result in updated configuration data.
+
+The methods \underline{\lstinline{Lock()}} and \underline{\lstinline{Unlock()}} work on an internal mutex.\footnote{Its type is \lstinline{PTHREAD_MUTEX_ERRORCHECK}. In case an already locked mutex is re-locked, the corresponding system call will therefore return a error and thus avoid dead-locking. Error messages from \lstinline{Lock()} and \lstinline{Unlock()} are written to the console and to the log file. They are not published using \lstinline{Message()} since this method itself uses locking and calling it would result in an infinite recursion.} They are used by \lstinline{GetConfig()} but are also available for the user application to serialize access from multiple threads. Calling functions in the locked state should be avoided as it might result in re-locking.
+
+The static method \underline{\lstinline{ToString()}} translates the contents of a DIM service safely into a string that is returned. As no consistency between a service format and the contained data is guaranteed by DIM, precautions are necessary to avoid buffer overruns. The method currently handles the standardized message format \lstinline{"I:1;C"}, arrays of numbers and strings. All other formats are translated into a hex representation. The arguments are the DIM service format, a pointer to the service data and the data size in bytes. It is thread safe as it uses only the arguments and dynamically allocated storage.
+
+The static methods \underline{\lstinline{ServiceOK()}} take a pointer to a received service update or result of a remote procedure call (as available in the respective handlers) and safely checks if its contents is identical to the constant \lstinline{NO_LINK}. If so, they return false. If using the same constant in the service declaration, this provides a safe way of being informed if a particular service becomes unavailable. Then, the handler is called once for that service with the data content \lstinline{NO_LINK}.
+
+\underline{\lstinline{Tokenize()}} takes the string from the first argument, tokenizes it using the characters contained the second argument as delimeters, and returns a vector of strings containing all tokens.
+
+The boolean \underline{\lstinline{ExitRequest}} is set to \lstinline{true} by the signal handler when the program should terminate. The application should check that variable and react accordingly.\footnote{The reception of a signal usually makes system calls return with an error \lstinline{EINTR}. That behaviour can be used by the application to honour \lstinline{ExitRequest} without continuous polling (e.g by using the \lstinline{pause()} system call).}
+ 
+\subsection{Note on program termination}
+
+If the application does not react to \lstinline{ExitRequest} or an immediate program termination is required, several methods may invoke the \lstinline{exit()} system call. Orderly termination is then still possible in most cases if the application uses \lstinline{atexit()} to register a termination function that will be called by the operating system during execution of \lstinline{exit()}. That function can the do clean up work. If a class instance is declared \lstinline{static}, its destructor will also be called by \lstinline{exit()}. 
+
+However, due to the nature of an exception or a signal resulting from an error condition, correct execution of the termination routines cannot always be guaranteed.
+
+
+\section{Main servers of \E}
+\label{ServerDetails}
+
+Starting a server requires that the environment variable \lstinline{DIM_DNS_NODE} contains the Internet address of the name server. Optionally, \lstinline{DIM_DNS_PORT} may be set if the name server answers on a port different from the standard 2505.
+
+\subsection{\lstinline{Config} --- Distribution of configuration information}
+
+The configuration server accesses a text file, formatted in the INI style, and responds to requests for configuration data from a client. To this end, it provides a remote procedure call with the name \lstinline{ConfigRequest}. The data send along by the client is interpreted as a C string in the format \lstinline{SVR_NAME ITEM}. The configuration server searches for a section \lstinline{SERVER_NAME} and then for a line starting with \lstinline{ITEM =} in the configuration file. It then return the text following the equal sign up to the next item line as a string, removing all leading, trailing and multiple white space and all comments.
+
+The file containing the configuration data is watched for modification using the Linux \lstinline{inotify} mechanism, so always the latest data is distributed. For this purpose, the file is also accessed with all buffering disabled. The configuration file is given as command-line option at start-up of the server. Usually, this should be the first server to be started.
+
+The configuration file format is illustrated here with an example from FACT.
+\begin{lstlisting}
+[SQM]     # Sky Quality Monitor
+
+address =  sqm.ethz.ch
+port    =  10001
+period  =  30
+
+
+[DColl]   # Central Data Collector
+
+exclude     =  DIS_DNS/SERVER_INFO Alarm/Summary Bias/ConsoleOut
+               drsdaq/Count drsdaq/EventData drsdaq/ConsoleOut 
+sizeupdate  =  30   # Min delay in seconds between file size updates
+rollover    =  12   # Hour of day for change of date
+\end{lstlisting}
+
+\noindent
+\begin{longtable}{lp{0.7\textwidth}}
+\multicolumn{2}{l}{\textbf{Invocation}} \\
+\multicolumn{2}{l}{\lstinline|Config <Name of configuration file>|} \\[1ex]
+\multicolumn{2}{l}{\textbf{Remote procedure call}} \\
+\lstinline|ConfigRequest| & Interpret data send along as C string in the form \lstinline|SERVER_NAME ITEM| and return the applicable configuration data as text or an empty response if data could not be found.\\[1ex]
+\multicolumn{2}{l}{\textbf{Services}} \\
+\lstinline|Config/ConfigData| & Contains the full text of the configuration file. If this service is not excluded in the data collector, the latest version is automatically written to the slow data stream at every update.\\
+\lstinline|Config/ModifyTime| & Contains the unix time of the last modification of the configuration file.
+\end{longtable}
+
+ 
+\subsection{\lstinline{DColl} --- Data collector}
+
+The data collector subscribes to all DIM services (except those excluded by the configuration item \lstinline{exclude}) and writes at every update of a service the data to a text file. A new file is generated daily, written to a directory that changes yearly. It translates the service data to text using the method \lstinline{ToText()} from the \lstinline{EvidenceServer} class and then replaces all non printable characters by spaces.
+
+This server provides a command to log information in a single log file. To copy or truncate this file, standard Linux tools like \lstinline{logrotate} can be used. Using a non-blocking DIM command data can be send for logging even as part of a crash handler, aiding in debugging.
+
+All files are opened in append mode, thus preventing overwriting of existing data.
+
+\noindent
+\begin{longtable}{lp{0.7\textwidth}}
+\multicolumn{2}{l}{\textbf{Configuration section \lstinline|[DColl]|}} \\
+\lstinline|exclude| & Services not to write to the data file. Regular expressions can be used.\\
+\lstinline|basedir| & Directory where to root the data file structure and where the log file resides.\\
+\lstinline|sizeupdate| & Minimum delay in seconds between updates to the file size services. Updates will never occur more frequently than once per second.\\
+\lstinline|rollover| & Hour of day in local time when to start a new data file.\\[1ex]
+\multicolumn{2}{l}{\textbf{Commands}} \\
+\lstinline|DColl/Log Text| & Interprets \lstinline|Text| as a C string and writes it to the log file, including information on the sender and a time stamp.\\[1ex]
+\multicolumn{2}{l}{\textbf{Services}} \\
+\lstinline|DColl/DataSizeMB| & Size of current data file in MByte.\\
+\lstinline|DColl/CurrentFile| & Name of current data file.\\
+\lstinline|DColl/LogSizeMB| & Size of log file in MByte.\\
+\end{longtable}
+
+\subsection{\lstinline{Alarm} --- Handling of error conditions}
+
+The alarm server maintains a list of \emph{alarm levels} for a given set of servers. The alarm levels are defined as \lstinline{OK} (0), \lstinline{WARN} (1), \lstinline{ERROR} (2), \lstinline{FATAL} (3), and \lstinline{UNAVAILABLE} (4). The first four result from the corresponding severities of the message services, to which the alarm server subscribes. The alarm level does not decrease if, for example, a server issues a message with severity \lstinline{WARN} after one with \lstinline{ERROR}. It is only reset by command or by restarting the alarm server.
+
+A master alarm is generated from the highest server alarm level. The alarm server also periodically checks if all required servers are up (searching for them with the DIM browser). It can send an email in case a server is down or in error. One email will be send with each increase of alarm level for each server.
+
+The alarm server itself could be monitored, if desired, using a Linux watch dog and/or from a remote operators panel.
+ 
+\noindent
+\begin{longtable}{lp{0.7\textwidth}}
+\multicolumn{2}{l}{\textbf{Configuration section \lstinline|[Alarm]|}} \\
+\lstinline|servers| & List of servers to check. An email address can be added to a server name by colon.\\
+\lstinline|period| & Interval in seconds to check for server availability.\\[1ex]
+\multicolumn{2}{l}{\textbf{Commands}} \\
+\lstinline|ResetAlarm xyz| & Reset alarm level of server \lstinline|xyz|.\\[1ex]
+\multicolumn{2}{l}{\textbf{Services}} \\
+\lstinline|Alarm/Summary| & Text listing all observed servers and their alarm level.\\
+\lstinline|Alarm/MasterAlarm| & The highest alarm level of all servers watched.\\
+\lstinline|xyz/AlarmLevel| & Highest alarm level of server \lstinline|xyz| since the start of the \lstinline|Alarm| server or the last reset command.
+\end{longtable}
+
+\subsection{\lstinline{History} --- Service histories}
+
+Data written by \lstinline{DColl} usually resides on a hard disk and is thus not quickly accessible to remote clients. A recently started user interfaces, for example, might want to provide a display to show the past behaviour of a DIM service. The \lstinline{History} server facilitates this by subscribing to all services and keeping their recent updates in a ring buffer in memory. The ring buffer entries are time stamped and contain exactly the data that was contained in the DIM service. The server provides a remote procedure call to retrieve that data. The ring buffers are written to files in case the history server is terminated, and re-read at next startup. The directory where the buffers are stored is given as command line parameter.
+
+The ring buffer implementation needs to store entries of variable size in a continuous memory region to allow transmission with DIM. This requires pointer manipulations that are more error prone than code using only standard C++ containers. This is one reason why the history functionality, which is not an essential component of the control system but a convenience function, is separated from the data collector.
+
+A request for a history buffer also implies a non-negligible transfer of data, thus if there are bandwidth issues, history servers can be installed at several places along a network chain that are separated by a bridge, see below.
+
+\noindent
+\begin{longtable}{lp{0.7\textwidth}}
+\multicolumn{2}{l}{\textbf{Invocation}} \\
+\multicolumn{2}{l}{\lstinline|History <Directory for storing history buffers>|} \\[1ex]
+\multicolumn{2}{l}{\textbf{Configuration section \lstinline|[History]|}} \\
+\lstinline|minchange| & Minimum absolute change necessary for a service to be added to the history buffer. The format is \lstinline|ServiceName:MinChange|. This is only meaningful for services that represent numbers or number arrays. For an array, the difference of the sum of the absolute values of all elements is compared to \lstinline|MinChange|.\\
+\lstinline|maxsize_kb| & Maximum size of a single history buffer in kByte. Default value is 2000.\\
+\lstinline|numentries| & Numer of entries that a history buffer should hold, provided its size does not exceed the defined maximum. Default value is 1000. For DIM services of varying size, buffer sizes are recalculated at each update and never shrink.\\[1ex]
+\multicolumn{2}{l}{\textbf{Remote procedure call}} \\
+\lstinline|ServiceHistory Srvc| & Returns the history buffer of the given service if available, otherwise the response will be empty (zero bytes). If the buffer is not currently in memory because the corresponding service is not available, it will be searched for on disk.
+\end{longtable}
+
+To safely retrieve the data from a history buffer, a class \lstinline{EvidenceHistory} is available. Its \lstinline{public} part is as follows.
+
+\begin{lstlisting}[numbers=left,numberstyle=\tiny,stepnumber=2,numbersep=5pt]
+class EvidenceHistory {
+
+  public:
+    struct Item {
+      int Time;
+      int Size;
+      char Data[]; // Size bytes follow
+    } __attribute__((packed));
+
+    EvidenceHistory(std::string);
+    ~EvidenceHistory();	
+
+    bool GetHistory();
+    char *GetFormat();
+    const struct Item *Next();
+    void Rewind();
+};
+\end{lstlisting}
+
+The constructor takes as argument the name of a DIM service. Calling \lstinline{GetHistory()} will request a history buffer from the server and returns \lstinline{true} if successful. A blocking remote procedure call is used. \lstinline{Next()} will then iterate through the entries of the history buffer, starting at the oldest entry, and returns a pointer to a \lstinline{struct Item}, through which the time, size and data of the entry can be accessed. If no more data is in the buffer, \lstinline{NULL} is returned. To read the buffer again, \lstinline{Rewind()} can be called. The DIM service format is returned by \lstinline{GetFormat()}.
+
+The structure attribute ensures that no padding bytes are added by the compiler. That is directive specific to the \lstinline{g++} compiler.
+
+Accessing the history buffer through this class is recommended, as the format of the buffer might change at a later time.
+
+\subsection{\lstinline{Bridge} --- Connecting two DIM networks}
+
+The \lstinline{Bridge} server appears as a client on the primary DIM network (IP address and optionally port of the primary name server are given as command-line arguments), subscribes to all services not excluded in the configuration and forwards them to the secondary network where it acts as server (secondary name server given as usual by the environment variable \lstinline{DIM_DNS_NODE}). Services originating from various servers on the primary side will appear to be provided by the \lstinline{Bridge} server on the secondary side. The name, time stamp and service quality number are unchanged.
+
+The bridge also creates commands with the same name as seen on the primary side and forwards them to the actual servers. They are send non-blocking. Confirmation of reception at a client on the secondary side only indicates that the bridge has received the command. Remote procedure calls are internally handled by DIM as a command/service pair, thus no special handling for this case is required.
+
+All service updates and commands pass through the bridge server, thus it should run on a sufficiently powerful computer. An application of the bridge is to facilitate access control: a firewall between the two DIM networks can prohibit any connection except by the bridge where, for example, the origin of commands can be checked. The bridge can also help if the server load due to too many clients would become too high. A bridge between the server and the clients will allow to have only one direct connection to the server by the bridge, while all other clients only load the bridge.
+
+\noindent
+\begin{longtable}{lp{0.7\textwidth}}
+\multicolumn{2}{l}{\textbf{Invocation}} \\
+\multicolumn{2}{l}{\lstinline|Bridge <Primary name server node> [Primary name server port]|} \\[1ex]
+\multicolumn{2}{l}{\textbf{Configuration section \lstinline|[Bridge]|}} \\
+\lstinline|cmdallow| & IP addresses that are allowed to pass a command over the bridge. All other commands will be rejected. An \lstinline|INFO| entry in the message service will be made in this case. The address is compared to the result of the DimServer:: getClientName() call.\\[1ex]
+\lstinline|exclude| & Services not to bridge. This must always contain \lstinline|DIS_DNS/*| as a DIM name server exists on both sides and service names must be unique.\footnotemark It can also be desirable to not forward History services, and run instead a separate history server on the secondary side. Regular expressions are allowed.
+\end{longtable}
+
+\footnotetext{If needed, it would be easy to implement a renaming of services on the secondary side.}
+
+\section{Details on handling configuration updates}
+\label{ConfigHandling}
+
+An application typically requests configuration data at start-up and then uses this data repeatedly during its execution flow. The configuration file can be modified during that time and it might not always be clear to the user that the program still uses outdated information. A method to keep the application up-to-date with respect to its configuration data is thus desirable.
+
+\subsection{\lstinline{GetConfig()}}
+
+As a first step in achieving this, the application should not store the obtained configuration data internally, but always re-request it using the method \lstinline{GetConfig()} described in Sect.\,\ref{EvidenceServer-Methods}. This method will only issue a remote procedure call to the \lstinline{Config} server if the configuration file has been modified since the last invocation. So calling this method even at high rate will not load the configuration server at all if the configuraton file is unchanged, but will yield up-to-date information if it did change.
+
+The remote procedure call is blocking when called from the main thread or from the method \lstinline{ConfigChanged()} (which runs in a separate thread). It is non-blocking, using an \lstinline{rpcInfoHandler()}, when called from any other thread, especially also from the DIM handler thread. Blocking execution means that the remote procedure call will wait until the data has arrived from the server before returning to the application, whereas non-blocking execution will return immediately and invoke a handler later when the data arrived. This procedure is necessary since a blocking remote procedure call from \lstinline{infoHandler()} will result in a dead-lock.
+
+In the non-blocking case, the call to \lstinline{GetConfig()} returns still the previous, non-updated data even if the configuration file changed. The result of the non-blocking remote procedure call can only be processed by DIM once the current and all queued handler invocations have finished. When this is done, updated data will be returned by subsequent calls to \lstinline{GetConfig()}.
+
+\subsection{\lstinline{ConfigChanged()}}
+
+An alternative, albeit for the programmer more demanding, procedure for semi-automatic updates on configuration information is to reimplement the virtual method \lstinline{ConfigChanged()} in the user class. This method is invoked as a separate thread by the \lstinline{EvidenceServer} class whenever the service \lstinline{Config/ModifyTime} changes (and also at program start-up). As it is not running within the DIM handler thread, \lstinline{GetConfig()} will use blocking connections to get immediately up-to-date data when called from \lstinline{ConfigChanged()}.
+
+Running in a separate thread requires suitable protection by the programmer when accessing common data structures. To ease that, the \lstinline{EvidenceServer} class contains the pair of methods \lstinline{Lock()} and \lstinline{Unlock()} that work on an class internal mutex. The mutex type is \lstinline{PTHREAD_MUTEX_ERRORCHECK} and therefore includes error checking: no dead-lock will occur if double locking, but the program will terminate with a \lstinline{FATAL} message.   
+
+
+\section{Implementing and compiling \E}
+\label{Implementation}
+
+\subsection{Makefile}
+
+If the environment variable \lstinline{DIM_DIR} is pointing to the DIM installation, a \lstinline{make} file for a program \lstinline{MyProg} can be as follows.
+
+\begin{verbatim}
+CC=g++
+PROG=MyProg
+
+CPPFLAGS += -I$(DIMDIR)/dim/
+LDLIBS += -lpthread $(DIMDIR)/linux/libdim.a
+
+all: $(PROG)
+	 
+$(PROG): $(PROG).o Evidence.o
+\end{verbatim}
+   
+Instead of the static version, a shared library \lstinline{libdim.so} is also available. The \lstinline{EvidenceServer} class is linked (and compiled, if not done yet) upon generation of \lstinline{MyProg}. It should reside in the same directory as \lstinline{MyProg.cc}, or else the correct path to \lstinline{Evidence.o} should be added .
+
+\subsection{Server}
+\label{Server-Programming}
+
+For a server, the following skeleton can be used.
+
+\begin{lstlisting}[numbers=left,numberstyle=\tiny,stepnumber=2,numbersep=5pt]
+#include "Evidence.h"
+
+// Class declaration
+class MyClass: public EvidenceServer { ... }
+
+// Constructor
+MyClass::MyClass(): EvidenceServer(SERVER_NAME) { ... }
+
+// Request configuration data
+std::string Data = GetConfig("config_item_name");
+
+// Create service
+static int Service = 0;
+NewService = new DimService(SERVER_NAME "/ServiceName", Service);
+
+// Remove service
+delete NewService;
+\end{lstlisting}
+
+Note that the contents of the service might be requested by a client at any time. As the service variable is passed by reference, it is mandatory that the lifetime of that variable is as long as that of the DIM service.
+
+\subsection{Client}
+\label{Client-Programming}
+
+A client can use the same header file and access the static methods and the constant \lstinline{NO_LINK} defined without instantiating the server class.
+
+\begin{lstlisting}[numbers=left,numberstyle=\tiny,stepnumber=2,numbersep=5pt]
+#include "Evidence.h"
+
+// Class declaration
+class MyClass: public DimClient { ... }
+
+// Subscribe to service using infoHandler()
+DataItem = new DimStampedInfo(ServiceName, NO_LINK, this);
+
+void MyClass::infoHandler() { 
+
+  // Check if service became unavailable
+  if (!ServiceOK(getInfo())) { ... }
+
+  ...
+}
+\end{lstlisting}
+
+The various versions of \lstinline{DimInfo} are described in the DIM manual. At subscription and then at every update, the method \lstinline{infoHandler()} is executed. If the method \lstinline{ServiceOK()} returns false, the corresponding service became unavailable, otherwise the service data can be evaluated within the handler. The handler applies to all service subscriptions made within this class.
+
+
+\section{User interface}    
+\label{User-Interface}
+
+A graphical user interface (GUI), implemented using the Qt and Qwt frameworks\footnote{Information on these frameworks is available at \url{http://qt.nokia.com/} and \url{http://qwt.sourceforge.net/}.}, is available. It derives from standard widget classes extended versions that can display the contents of DIM services and history buffers. A widget to send generic text commands is also available. Qwt is used to display graphs which is not supported by Qt.
+
+The GUI is called \emph{Evidence Data Display} (\lstinline{EDD}). It has a single point interface to the DIM system and distributes received service updates to its widgets using the Qt signal/slot mechanism. This is necessary since the DIM \lstinline{infoHandler()} receiving the updates runs in a separate thread, but manipulations of GUI elements within Qt may only be done by the main thread. This mechanism also guarantees that one GUI instance subscribes not more than once to a particular service, even if the same data is shown by multiple widgets.
+
+The GUI implementation is designed to be easily portable and does not use operating-system specifics. It sticks to standard C++ code, to Qt capabilities and to DIM. Qt is explicitely designed for cross-platform applications.  
+
+
+\section*{Acknowledgments}
+\addcontentsline{toc}{section}{Acknowledgments}
+
+The help of Clara Gaspar from CERN was indispensable for understanding the DIM system and making good use of it. Manwoo Lee from Kyungpook National University kindly checked the main programs for bugs and missing error handling.  
+
+
+\begin{appendix}
+
+\section{DIM features not documented in the manual}
+\label{DIMDetails}
+
+\subsection{Format of \lstinline{DIM_DNS/SERVER_LIST} and \lstinline{xyz/SERVICE_LIST}}
+\label{ServiceFormats}
+ 
+The name server \lstinline{DIS_DNS} provides a service \lstinline{DIS_DNS/SERVER_LIST} containing a C string. Subscribing to it, the client gets at first update a list of all currently existing servers in the format \lstinline{Servername@node}. Individual entries are separated by the \lstinline{|} character. Further updates will indicate additions (\lstinline{+}), deletions (\lstinline{-}) or error states (\lstinline{!}) of servers, with a list of the same format as before following. The error state indicates that the sever did not send its regular watchdog message. It can be treated as deleted by the client, as it will reappear with \lstinline{+} in case it sends the messages again.
+
+Each server \lstinline{xyz} has a service \lstinline{xyz/SERVICE_LIST}, also a C string. It contains line-feed separated entries listing all services, commands and remote procedure calls available from it. For a service, the entry is \lstinline{Name|Format|}, for a command \lstinline{Name|Format|CMD} (\lstinline{Format} may also be empty), and for a remote procedure call \lstinline{Name|Format_in,Format_out|RPC}. Additions and deletion of services are handled as above.
+
+Sequentially subscribing to \lstinline{DIS_DNS/SERVER_LIST} and then to all corresponding service lists allows a client to be kept up-to-date of all existing services, commands and remote procedure calls within the system. This mechanism ist used by the components \lstinline{DColl}, \lstinline{History} and \lstinline{Bridge} of \E. The \lstinline{DimBrowser} class is provided by DIM with similar functionality.
+
+\subsection{Miscellaneous}
+
+A standard service \lstinline{xyz/CLIENT_LIST} is provided by all DIM servers, with a structure similar to \lstinline{xyz/SERVICE_LIST}.
+
+If a command could not be delivered to the server it is discarded and not delivered later. This prevents the reception of spurious, delayed commands. In case the blocking version of \lstinline{sendCommand()} was used by the client, it receives in this case the return value 0. 
+
+If the received value in the \lstinline{infoHandler()} indicates the service is unavailable (\lstinline{NO_LINK}), then \lstinline{getFormat()} should not be used as its result may point to an arbitrary memory location.
+
+For updating a service, the same version of the function as used for its creation must be used, otherwise the call will be ignored. If a service is created using \lstinline{Service = DimService(char *Name, char *Format, void *Data, int Size)}, then it is not possible to use \lstinline{Service->update(char* Newdata)}, even if the original format was \lstinline{"C"}.
+
+The data received from a service or a remote procedure call is not guaranteed by DIM to comply to the format of that service (e.g. format \lstinline{"C"} does not assure a \lstinline{'\0'} terminated C string). The data content is up to the sender. If in doubt, the receiver must make sure it does not access more than \lstinline{getSize()} bytes starting at the memory location given by \lstinline{getData()}. Function like \lstinline{getInt()} or \lstinline{getString()} are only casting the pointer \lstinline{getData()}.
+
+The main purpose of the format identifier of DIM services is to allow DIM to translate structure padding from the server architecture to the client. A format like \lstinline{"C:3;I:2;F"} is intended to correspond to a variable sized structure in C,
+\begin{verbatim}
+struct A {
+  char a[3];
+  int b[2];
+  float c[];
+}
+\end{verbatim}
+If server and client use the same structure definition, a cast like \lstinline{struct A *Pnt = getData()} will guarantee correct access to elements. For example, \lstinline{Pnt->b[1]} will contain the second integer, even if client and server pad structures differently. Padding can also be disabled for a server if desired.
+
+In general, no blocking functions should be called from within a DIM handler. Specifically, making a blocking remote procedure call in an \lstinline{infoHandler()} will dead lock.\footnote{Non-blocking reception using an \lstinline{rpcInfoHandler()} is possible}.
+
+If DIM is compiled without threads, it uses internally the signals SIGIO and SIGALRM for communication.
+
+Only a single DIM server can be started by a process. Even when publishing to two different DIM networks, the service names on both sides combined have to be unique (since internally, a single hash table is used).
+
+
+\section{Example for access control using IPtables}
+\label{FirewallExample}
+
+This is an example for an IPtables firewall setting taken from one of the FACT computers. Rule 4 is to allow \lstinline{ssh} connections, rule 5 is for the DIM servers, and rule 6 for \lstinline{X11} connections. Rule 2 is an example how direct connections from a particular IP address can be prohibited. That can be used to force all connection to go through the Bridge.
+
+\footnotesize
+\begin{verbatim}
+Chain RH-Firewall-1-INPUT (2 references)
+num  target     prot opt source               destination
+1    ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           icmp type 255
+2    REJECT     all  --  192.33.97.201        0.0.0.0/0           reject-with icmp-port-unreachable
+3    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED
+4    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:22
+5    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpts:5100:6000 state NEW
+6    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpts:6000:6063 state NEW
+7    REJECT     all  --  0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited
+\end{verbatim}
+\normalsize
+Rule 2 prohibits connections to servers running on this computer. If the name server would be at another IP address, a client could still see the existence of the local servers and access also the list of services they provide, but connection attempts would be denied.
+
+%\section{Glossary}
+
+%DIM service
+%subversion (svn)
+%thread
+%widget
+
+
+\end{appendix}
+
+% ===================================================
+
+\begin{thebibliography}{xxxx00}
+
+\bibitem[Gas01]{Gas01} C. Gaspar, M. D\"{o}nszelmann and Ph. Charpentier, \emph{DIM, a portable, light weight package for information publishing, data transfer and inter-process communication}, Computer Physics Communications 140 1+2 102-9, 2001
+\bibitem[Bra09]{Bra09} I. Braun et al., Nucl. Inst. and Methods A 610, 400 (2009)
+\bibitem[And10]{And10} H. Anderhub et al., Nucl. Inst. and Methods, to be published (2010)
+
+\end{thebibliography}
+
+
+\end{document}
Index: fact/Evidence/Edd/Edd.cc
===================================================================
--- fact/Evidence/Edd/Edd.cc	(revision 9834)
+++ fact/Evidence/Edd/Edd.cc	(revision 9834)
@@ -0,0 +1,1664 @@
+
+/* ============================================================ 
+
+Edd - Evidence Data Display
+
+Qt-based graphical user interface for the Evidence contron system
+
+EddLineDisplay changes its background colour in case it display
+a DIM status service
+
+April 2010, Oliver Grimm
+
+============================================================ */
+
+#include "Edd.h"
+
+Qt::GlobalColor LineColors[] = {Qt::black, Qt::blue, Qt::red, Qt::green, Qt::white,
+   	Qt::darkRed, Qt::darkGreen, Qt::darkBlue, Qt::cyan,
+    	Qt::darkCyan, Qt::magenta, Qt::darkMagenta,
+	Qt::gray, Qt::darkGray, Qt::lightGray};
+
+
+class EddDim *Handler;
+
+
+// History chooser function (opens plot for numeric data, TextHist for all other)
+QWidget *OpenHistory(char *Service, int Index) {
+
+  QString Format;
+  DimBrowser Browser;
+  class EvidenceHistory *Hist = Handler->GetHistory(Service);
+
+  // Check if history service available
+  if (Hist == NULL || Hist->GetFormat() == NULL) {
+	QMessageBox::warning(NULL, "Edd Message", QString("Could not retrieve history for service ") + Service ,QMessageBox::Ok);
+
+	// If service currently available, take its format
+	char *Name, *Fmt;
+
+	Browser.getServices(Service);
+	if (Browser.getNextService(Name, Fmt) != 0) Format = QString(Fmt);
+	else {
+	  Handler->DropHistory(Service);
+	  return NULL;
+	}
+  }
+
+  if (Format.isEmpty()) Format = Hist->GetFormat();
+  Handler->DropHistory(Service);
+  
+  if (Format.size() == 1 && Format[0] != 'C') return new EddPlot(Service, Index);
+  else return new EddText(Service);
+}
+
+// Set status tip (returns true if service was available)
+bool SetStatus(QWidget *W, QString Name, int Time, QString Format, int Index) {
+
+  QString Status;
+
+  if (Index != -1) Name = Name + "(" + QString::number(Index) + ")";
+
+  if (Time == -1) Status = QString("%1:  unavailable").arg(Name);
+  else Status = QString("%1:  Last update %2   Format '%3'").arg(Name).arg(QDateTime::fromTime_t(Time).toString()).arg(Format); 
+
+  W->setStatusTip(Status);
+  
+  return(Time != -1);
+}
+
+
+//////////////////////////////////////////
+// Text display for arbitary DIM service//
+//////////////////////////////////////////
+
+EddLineDisplay::EddLineDisplay(QString Name, int Index, QWidget *P):
+	QLineEdit(P), ServiceName(Name), Index(Index) {
+
+  LastHist = NULL;
+ 
+  // Widget properties
+  setReadOnly(true);
+  setMaximumWidth(100);
+  ShowAsTime = false;
+  setFrame(false);
+  setAttribute(Qt::WA_DeleteOnClose);
+  
+  // Connect to DIM handler
+  if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
+    printf("Failed connection for %s\n", Name.toAscii().data());
+  }
+
+  // Context menu
+  Menu = new QMenu(this);
+  Menu->addAction("Open new history", this, SLOT(MenuOpenHistory()));
+  Menu->addAction("Copy service", this, SLOT(MenuCopyService()));
+  Menu->addAction("Copy data", this, SLOT(MenuCopyData()));
+
+  // Subscribe to service
+  Handler->Subscribe(Name);
+}
+
+// Destructor
+EddLineDisplay::~EddLineDisplay() {
+
+  Handler->Unsubscribe(ServiceName);
+}
+
+// Update widget
+void EddLineDisplay::Update(QString Name, int Time, QByteArray, QString Format, QString Text) {
+
+  if (ServiceName != Name) return;
+
+  // Check if service available
+  QPalette Pal = palette();  
+  if (!SetStatus(this, Name, Time, Format, Index)) {
+    setText("n/a");
+    Pal.setColor(QPalette::Base, Qt::lightGray);
+	setPalette(Pal);
+	return;
+  }
+  else Pal.setColor(QPalette::Base, Qt::white);
+
+  // Message service backgound colour determined by severity 
+  if (Name.endsWith("/Message")) {
+    switch (Text.section(' ', 0, 0).toInt()) {
+      case 0:  Pal.setColor(QPalette::Base, Qt::white); break;
+      case 1:  Pal.setColor(QPalette::Base, Qt::yellow); break;
+      case 2:  Pal.setColor(QPalette::Base, Qt::red); break;
+      case 3:  Pal.setColor(QPalette::Base, Qt::red); break;
+      default: break;
+    }
+	Text = Text.section(' ', 1);
+  }
+  else if (Format[0].toUpper() != 'C' && Format != "I:1;C") Text = Text.section(' ', Index, Index);
+
+  if (!ShowAsTime) setText(Text);
+  else setText(QDateTime::fromTime_t(Text.toInt()).toString());
+
+  setCursorPosition(0);  
+  setPalette(Pal);
+}
+
+// Open plot if mouse release within widget
+void EddLineDisplay::mouseReleaseEvent(QMouseEvent *Event) {
+
+  if (Event->button()!=Qt::LeftButton || !contentsRect().contains(Event->pos())) return;
+
+  // Check if last history plot still open, then raise
+  foreach (QWidget *Widget, QApplication::allWidgets()) {
+    if (Widget == LastHist) {
+      Widget->activateWindow();
+      Widget->raise();
+      return;
+    }
+  }
+
+  // If not, open new plot
+  EddLineDisplay::MenuOpenHistory();
+}
+
+// Handling of mouse press event: Register start position for drag
+void EddLineDisplay::mousePressEvent(QMouseEvent *Event) {
+
+  if (Event->button() == Qt::LeftButton) dragStart = Event->pos();
+}
+
+// Handling of dragging (Drag and MimeData will be deleted by Qt)
+void EddLineDisplay::mouseMoveEvent(QMouseEvent *Event) {
+
+  if ((Event->buttons() & Qt::LeftButton) == 0) return;
+  if ((Event->pos()-dragStart).manhattanLength() < QApplication::startDragDistance()) return;
+
+  QDrag *Drag = new QDrag(this);
+  QMimeData *MimeData = new QMimeData;
+  QByteArray Data;
+  MimeData->setData("Edd/Service", Data.append(ServiceName + " " + QString::number(Index)));
+  Drag->setMimeData(MimeData);
+  Drag->exec();
+}
+
+//
+// Opening context menu
+//
+void EddLineDisplay::contextMenuEvent(QContextMenuEvent *Event) {
+
+  Menu->exec(Event->globalPos());
+}
+
+// Menu: Open history plot
+void EddLineDisplay::MenuOpenHistory() {
+  
+  LastHist = OpenHistory(ServiceName.toAscii().data(), Index);
+  if (LastHist != NULL) LastHist->show();
+}
+
+// Menu: Copy service name
+void EddLineDisplay::MenuCopyService() {
+  
+  QMimeData *MimeData = new QMimeData;
+  QByteArray Data;
+  MimeData->setData("Edd/Service", Data.append(ServiceName + " " + QString::number(Index)));
+  QApplication::clipboard()->setMimeData(MimeData);
+}
+
+// Menu: Copy data
+void EddLineDisplay::MenuCopyData() {
+
+  QApplication::clipboard()->setText(text());
+}
+
+
+//////////////////////////////////////////
+// Sending string command to DIM server //
+//////////////////////////////////////////
+
+EddCommand::EddCommand(QString Name, QWidget *P): QLineEdit(P), Name(Name) {
+
+  setToolTip("Send command "+Name);  
+  connect(this, SIGNAL(returnPressed()), SLOT(SendCommand()));
+}
+
+// Send command
+void EddCommand::SendCommand() {
+
+  DimClient::sendCommand(Name.toAscii().data(), text().toAscii().data());
+  clear();
+}
+
+
+//////////////////////////////////
+// History plot for DIM service //
+//////////////////////////////////
+
+EddPlot::EddPlot(QString Service, int Index, QWidget *P): EddBasePlot(P) {
+
+  // Widget properties
+  setAcceptDrops(true);
+  setAxisScaleDraw(QwtPlot::xBottom, new EddTimeScale);
+	
+  // Update time range on plot when axis change	(update() results in paintEvent())
+  connect(axisWidget(QwtPlot::xBottom), SIGNAL(scaleDivChanged()), SLOT(update()));
+
+  legend()->setItemMode(QwtLegend::ClickableItem);
+  connect(this, SIGNAL(legendClicked (QwtPlotItem *)), SLOT(LegendClicked(QwtPlotItem *)));
+  
+  // Connect to DIM handler
+  if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
+    printf("Failed connection for %s\n", Service.toAscii().data());
+  }
+
+  // Additonal context menu items
+  QAction* Action = Menu->addAction("Paste service", this, SLOT(MenuPasteService()));
+  Menu->removeAction(Action);
+  Menu->insertAction(Menu->actions().value(1), Action);
+  
+  // Maximum number if points in curve (will be increased in Update())
+  SizeLimit = 0;
+
+  // DIM client
+  if (!Service.isEmpty()) AddService(Service, Index);
+}
+
+// Destructor (items with parent widget are automatically deleted)
+EddPlot::~EddPlot() {
+
+  while (!List.isEmpty()) DeleteCurve(List.last().Signal);
+}
+
+// Add history service to plot
+void EddPlot::AddService(QString Name, int Index) {
+
+  // Check if curve already present on plot
+  for (int i=0; i<List.size(); i++) {
+    if (Name == List[i].Name && Index == List[i].Index) {
+      QMessageBox::warning(this, "Edd Message",Name+" ("+QString::number(Index)+") already present",QMessageBox::Ok);
+      return;
+    }
+  }  
+  
+  // Generate new curve and subscribe to service
+  struct ItemDetails N;
+
+  N.Name = Name;
+  N.Signal = NewCurve(Name+"("+QString::number(Index)+")");
+  N.Index = Index;
+  List.append(N);
+
+  Handler->Subscribe(Name);
+}
+
+// Update widget (must happen in GUI thread)
+void EddPlot::Update(QString Name, int Time, QByteArray, QString Format, QString Text) {
+
+  // Determine which plot item this call belongs to
+  int ItemNo;
+  for (ItemNo=0; ItemNo<List.size(); ItemNo++) if (List[ItemNo].Name == Name) {
+
+	// If size limit reached, clear buffer
+    if (List[ItemNo].Signal->dataSize() > SizeLimit) List[ItemNo].Signal->setData(QPolygonF());
+
+	// If buffer empty, request new history buffer
+    if (List[ItemNo].Signal->dataSize() == 0) {
+	  int Count=0;
+	  const struct EvidenceHistory::Item *R;
+	  class EvidenceHistory *Hist;
+
+	  if ((Hist = Handler->GetHistory(List[ItemNo].Name)) != NULL) {
+		double Number=0;
+		while ((R=Hist->Next()) != NULL) {
+		  switch (*(Hist->GetFormat())) {
+    		case 'I':
+			case 'L':  Number = *((int *) R->Data + List[ItemNo].Index);   break;
+    		case 'S':  Number = *((short *) R->Data + List[ItemNo].Index);   break;
+    		case 'F':  Number = *((float *) R->Data + List[ItemNo].Index);   break;
+    		case 'D':  Number = *((double *) R->Data + List[ItemNo].Index);   break;
+    		case 'X':  Number = *((long long *) R->Data + List[ItemNo].Index);   break;
+    		default: break;
+		  }
+		  AddPoint(ItemNo, R->Time, Number);
+		  Count++;
+		}
+
+		// Local buffer at least twice as large as longest history
+		if (SizeLimit < 2*Count) SizeLimit = 2*Count;
+	  }
+	  Handler->DropHistory(List[ItemNo].Name);
+	}
+
+ 	// Append data only if service available
+	if (SetStatus(this, Name, Time, Format)) {
+	  QString Txt = Text;
+	  Txt = Txt.section(' ', List[ItemNo].Index, List[ItemNo].Index);
+      AddPoint(ItemNo, Time, atof(Txt.toAscii().data()));
+	}
+  }
+
+  UpdatePlot();
+}
+
+
+// Add text indicating time range to plot
+void EddPlot::paintEvent(QPaintEvent *) {
+
+  QString Text;
+  QFont Font;
+  QPainter Painter(this);
+  
+  Text = QDateTime::fromTime_t((int) axisScaleDiv(QwtPlot::xBottom)->lowerBound()).toString("d-MMM-yyyy hh:mm:ss") + " to " + QDateTime::fromTime_t((int) axisScaleDiv(QwtPlot::xBottom)->upperBound()).toString("d-MMM-yyyy hh:mm:ss");
+
+  Font.setPointSize(6);  
+  Painter.setFont(Font);
+  Painter.drawText(0, height(), Text);
+}
+
+// Drag and drop methods
+void EddPlot::dragEnterEvent(QDragEnterEvent *Event) {
+    
+  if (Event->mimeData()->hasFormat("Edd/Service")) Event->acceptProposedAction();
+}
+
+void EddPlot::dropEvent(QDropEvent *Event) {
+
+  QByteArray D(Event->mimeData()->data("Edd/Service"));
+  AddService(D.left(D.lastIndexOf(' ')), D.right(D.size()-D.lastIndexOf(' ')).toInt());
+}
+
+void EddPlot::LegendClicked(QwtPlotItem *Item) {
+
+  QString D(Item->title().text());
+  D.replace('(',' ').chop(1);
+  
+  QDrag *Drag = new QDrag(this);
+  QMimeData *MimeData = new QMimeData;
+  QByteArray Data;
+  MimeData->setData("Edd/Service", Data.append(D));
+  Drag->setMimeData(MimeData);
+  Drag->exec();
+}
+
+// Add new service by pasting name
+void EddPlot::MenuPasteService() {
+
+  const QMimeData *D = QApplication::clipboard()->mimeData();
+  if (!D->hasFormat("Edd/Service")) return;
+  
+  QByteArray E(D->data("Edd/Service"));
+  AddService(E.left(E.lastIndexOf(' ')), E.right(E.size()-E.lastIndexOf(' ')).toInt());
+}
+
+// Remove list entry
+void EddPlot::DeleteCurve(QwtPlotCurve *Curve) {
+
+  for (int i=0; i<List.size(); i++) if (List[i].Signal == Curve) {
+    Handler->Unsubscribe(List[i].Name);
+    List.removeAt(i);
+  }
+}
+
+
+//////////////////
+// General plot //
+//////////////////
+
+// Constructor (all slots called in GUI thread, therefore no mutex necessary)
+EddBasePlot::EddBasePlot(QWidget *P): QwtPlot(P) {
+
+  // Widget properties
+  setAttribute(Qt::WA_DeleteOnClose);
+  setAutoReplot(false);
+  setCanvasBackground(EddPlotBackgroundColor);
+  setMargin(15);
+
+  // Plot navigation
+  Zoomer = new QwtPlotZoomer(QwtPlot::xBottom,QwtPlot::yLeft,canvas());
+  connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(HandleZoom(const QwtDoubleRect &)));
+
+  Magnifier = new QwtPlotMagnifier(canvas());
+  Magnifier->setMouseButton(Qt::NoButton,Qt::NoButton);
+  Magnifier->setZoomInKey(Qt::Key_M, Qt::NoModifier);
+  Magnifier->setZoomOutKey(Qt::Key_M, Qt::ShiftModifier);
+
+  Panner = new QwtPlotPanner(canvas());
+  Panner->setMouseButton(Qt::LeftButton, Qt::ShiftModifier);
+
+  Picker = new QwtPicker(QwtPicker::CornerToCorner|QwtPicker::RectSelection, QwtPicker::RectRubberBand, QwtPicker::AlwaysOff, this);
+  connect(Picker, SIGNAL(selected(const QwtPolygon &)), SLOT(MouseSelection(const QwtPolygon &)));
+
+  // Grid and legend
+  Grid = new QwtPlotGrid;
+  Grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine));
+  Grid->attach(this);
+  
+  insertLegend(new QwtLegend(), QwtPlot::TopLegend, 0.3);
+
+  // Context menu
+  Menu = new QMenu(this);
+  StripAction = Menu->addAction("Stripchart", this, SLOT(UpdatePlot()));
+  StripAction->setCheckable(true);
+  Menu->addSeparator();
+  YLogAction = Menu->addAction("y scale log", this, SLOT(UpdatePlot()));
+  YLogAction->setCheckable(true);
+  NormAction = Menu->addAction("Normalize", this, SLOT(UpdatePlot()));
+  NormAction->setCheckable(true);
+  StyleAction = Menu->addAction("Draw dots", this, SLOT(UpdatePlot()));
+  StyleAction->setCheckable(true);
+  Menu->addAction("Zoom out", this, SLOT(MenuZoomOut()));
+  Menu->addAction("Single trace", this, SLOT(MenuSingleTrace()));
+  Menu->addSeparator();
+  Menu->addAction("Save as ASCII", this, SLOT(MenuSaveASCII()));
+  Menu->addAction("Save plot", this, SLOT(MenuSave()));
+  Menu->addAction("Print plot", this, SLOT(MenuPrint()));
+  Menu->addAction("Plot help", this, SLOT(MenuPlotHelp()));
+}
+
+// Destructor (items with parent widget are automatically deleted)
+EddBasePlot::~EddBasePlot() {
+
+  for (int i=0; i<Items.size(); i++) delete Items[i].Signal;
+  delete Grid;
+}
+
+// Update all curves in plot
+void EddBasePlot::UpdatePlot() {
+
+  double Lower = axisScaleDiv(QwtPlot::xBottom)->lowerBound();
+  double Upper = axisScaleDiv(QwtPlot::xBottom)->upperBound();
+  double MaxTime = DBL_MIN;
+  static QwtSymbol Symbol, Sym1;
+  Symbol.setStyle(QwtSymbol::Ellipse);
+  Symbol.setSize(4);
+
+  // Select engine for linear or logarithmic scale
+  if (!YLogAction->isChecked()) {
+    setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine);
+  }
+  else setAxisScaleEngine(QwtPlot::yLeft, new QwtLog10ScaleEngine);
+
+  for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
+	// Determine current maximum value for strip chart plotting
+	if (Items[ItemNo].Signal->boundingRect().right() > MaxTime) {
+	  MaxTime = Items[ItemNo].Signal->boundingRect().right();
+	}
+
+	// Set symbol if requested
+    if (StyleAction->isChecked()) Items[ItemNo].Signal->setSymbol(Symbol);
+    else Items[ItemNo].Signal->setSymbol(Sym1);
+
+	// Determine number of data points
+    int DataPoints = Items[ItemNo].x.size();	
+	if (DataPoints == 0) continue;
+ 
+    // Normalize y scale if requested
+    double *y = new double [DataPoints];
+    for (int i=0; i<DataPoints; i++) {
+      y[i] = Items[ItemNo].y[i];
+
+      if (NormAction->isChecked()) {
+	    if (Items[ItemNo].Smallest != Items[ItemNo].Largest) {
+		  y[i] = (y[i] - Items[ItemNo].Smallest)/(Items[ItemNo].Largest-Items[ItemNo].Smallest);
+		}
+	    else y[i] = 1;
+      }  
+    }
+
+    // Plot data
+    Items[ItemNo].Signal->setData(Items[ItemNo].x.data(), y, DataPoints);
+    Items[ItemNo].Signal->show();
+	delete[] y;
+
+	// Generate bounding box of all curves for zoomer
+	BBox = BBox.unite(Items[ItemNo].Signal->boundingRect());
+  }
+
+  // Reset zoom base to include all data
+  Zoomer->setZoomBase(BBox);
+
+  // If plot is strip char, only move axis but keep range
+  if (StripAction->isChecked()) {
+    setCanvasBackground(EddPlotBackgroundColor.lighter(90));
+    setAxisScale(QwtPlot::xBottom, Lower+ BBox.right() - MaxTime, Upper + BBox.right() - MaxTime);
+  }
+  else setCanvasBackground(EddPlotBackgroundColor);
+
+  replot();
+}
+
+// Append curve to plot
+QwtPlotCurve *EddBasePlot::NewCurve(QwtText Title) {
+
+  struct PlotItem N;
+
+  N.Signal = new QwtPlotCurve;
+  N.Signal->attach(this);
+  N.Signal->setTitle(Title);
+  N.Signal->setPen(QColor(LineColors[Items.size() % (sizeof(LineColors)/sizeof(Qt::GlobalColor))]));
+  N.Largest = DBL_MIN;
+  N.Smallest = DBL_MAX;  
+  Items.append(N);
+  
+  return N.Signal;
+}
+
+// Clear curve data
+void EddBasePlot::ClearCurve(unsigned int Item) {
+
+  if (Item >= (unsigned int) Items.size()) return;
+  
+  Items[Item].x.clear();
+  Items[Item].y.clear();
+}
+
+// Append data point
+void EddBasePlot::AddPoint(unsigned int Item, double x, double y) {
+
+  if (Item >= (unsigned int) Items.size()) return;
+  
+  Items[Item].x.append(x);
+  Items[Item].y.append(y);
+
+  if (y > Items[Item].Largest) Items[Item].Largest = y;
+  if (y < Items[Item].Smallest) Items[Item].Smallest = y;    
+}
+
+// Rescale plot in case selection has been made outside the canvas
+void EddBasePlot::MouseSelection(const QPolygon &P) {
+ 
+  QwtDoubleInterval xPlot, xMouse, yPlot, yMouse; 
+
+  // Shift selected rectangle so that upper left corner is 0/0 on canvas
+  QRect R = P.boundingRect().translated(-plotLayout()->canvasRect().topLeft());
+
+  // Current axis intervals
+  xPlot = axisScaleDiv(QwtPlot::xBottom)->interval();
+  yPlot = axisScaleDiv(QwtPlot::yLeft)->interval();
+  
+  // Selected axis intervals
+  xMouse = QwtDoubleInterval(invTransform(QwtPlot::xBottom, R.left()),
+  		invTransform(QwtPlot::xBottom, R.right()));
+  yMouse = QwtDoubleInterval(invTransform(QwtPlot::yLeft, R.bottom()),
+		invTransform(QwtPlot::yLeft, R.top()));
+
+  // Selection region outside all axis?
+  if (R.right() < 0 && R.top() > plotLayout()->canvasRect().height()) return;
+  
+  // Rescale both axis if selected rectangle encompasses canvas completely
+  if (P.boundingRect().contains(plotLayout()->canvasRect())) {
+	yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
+	yMouse.setMinValue(yMouse.minValue() - yPlot.width());
+	xMouse.setMinValue(xMouse.minValue() - xPlot.width());
+	xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
+
+	setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
+	setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
+  }
+
+  // Rescale y axis (increase range if selected rectangle larger than axis area)
+  if (R.right() < 0) {
+	if (yMouse.maxValue() > axisScaleDiv(QwtPlot::yLeft)->upperBound()) {
+	  yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
+	}
+	if (yMouse.minValue() < axisScaleDiv(QwtPlot::yLeft)->lowerBound()) {
+	  yMouse.setMinValue(yMouse.minValue() - yPlot.width());
+	}
+	
+	setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
+  }
+
+  // Rescale x axis (increase range if selected rectangle larger than axis area)
+  if (R.top() > plotLayout()->canvasRect().height()) {
+	if (xMouse.maxValue() > axisScaleDiv(QwtPlot::xBottom)->upperBound()) {
+	  xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
+	}
+	if (xMouse.minValue() < axisScaleDiv(QwtPlot::xBottom)->lowerBound()) {
+	  xMouse.setMinValue(xMouse.minValue() - xPlot.width());
+	}
+
+	setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
+  }
+
+  // Draw new scales
+  replot();
+} 
+
+// Reset graph axes to autoscale when fully unzoomed
+void EddBasePlot::HandleZoom(const QwtDoubleRect &) {
+
+  if(Zoomer->zoomRectIndex() == 0) {
+    setAxisAutoScale(QwtPlot::xBottom);
+    setAxisAutoScale(QwtPlot::yLeft);
+  }
+}
+    
+// Opening context menu
+void EddBasePlot::contextMenuEvent(QContextMenuEvent *Event) {
+
+  Menu->exec(Event->globalPos());
+}
+
+// Zoom completely out
+void EddBasePlot::MenuZoomOut() {
+
+  Zoomer->zoom(0);
+  UpdatePlot();
+}
+
+// Remove all items except one
+void EddBasePlot::MenuSingleTrace() {
+
+  while (Items.size() > 1) {  
+	DeleteCurve(Items.last().Signal);
+    delete Items.last().Signal;
+    Items.takeLast();
+  }
+  UpdatePlot();
+}
+
+// Save data of plot as test 
+void EddBasePlot::MenuSaveASCII() {
+  QString Filename = QFileDialog::getSaveFileName(this,
+     "Filename", ".", "Text files (*.txt *.ascii *.asc);;All files (*)");
+  if (Filename.length() <= 0) return;
+  
+  QFile File(Filename);
+  if (!File.open(QFile::WriteOnly | QIODevice::Text | QFile::Truncate)) {
+    QMessageBox::warning(this, "Edd Message","Could not open file for writing.",QMessageBox::Ok);
+    return;
+  }
+
+   // Write x and y data for all signals to file
+  QTextStream Stream(&File);
+
+  for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
+    Stream << QString("# ") + Items[ItemNo].Signal->title().text() + ".hist" << endl;
+    for (int i=0; i<Items[ItemNo].Signal->dataSize(); i++) {
+      Stream << (int) Items[ItemNo].x.at(i) << " " << Items[ItemNo].Signal->y(i) << endl;
+    }
+  }
+}
+
+// Print plot
+void EddBasePlot::MenuPrint() {
+
+  QPrinter *Printer = new QPrinter;
+  QPrintDialog *PrintDialog = new QPrintDialog(Printer, this);
+  if (PrintDialog->exec() == QDialog::Accepted) {
+    QPainter Painter(Printer);
+    QPixmap Pixmap = QPixmap::grabWidget(this);
+    Painter.drawPixmap(0, 0, Pixmap);
+  }
+  delete Printer;	delete PrintDialog;
+}
+
+// Save plot as image
+void EddBasePlot::MenuSave() {
+
+  QString Filename = QFileDialog::getSaveFileName(this,
+     "Filename of image", "/home/ogrimm/ddd", "Image files (*.bmp *.jpg *.png *.ppm *.tiff *.xbm *.xpm);;All files (*)");
+  if (Filename.length()>0) {
+    QPixmap Pixmap = QPixmap::grabWidget(this);
+    if(!Pixmap.save(Filename)) {
+      QMessageBox::warning(this, "Edd Message","Could not write image file.",QMessageBox::Ok);
+      remove(Filename.toAscii().data());
+    }
+  }
+}
+
+// Help text
+void EddBasePlot::MenuPlotHelp() {
+
+  QMessageBox::about(this, "Edd - Plot navigation",
+    "Zoom\tMouse wheel\n"
+	"\tKeys m and shift-m\n"
+	"\tSelecting region with left mouse button\n"
+	"\tMiddle button zooms out one level\n"
+	"\tSelecting a range on an axis\n"
+	"\tSelecting whole canvas\n\n"
+    "Pan\tShift and left mouse button\n\n"
+	"ESC cancels selection\n"
+    "Cursor keys move mouse");
+}
+
+//////////////////////////////////////
+// History text box for DIM service //
+//////////////////////////////////////
+
+// Constructor
+EddText::EddText(QString Name, bool Pure, QWidget *P):
+	QTextEdit(P), Name(Name), Pure(Pure) {
+  
+  // Widget properties
+  setReadOnly(true);
+  setAttribute(Qt::WA_DeleteOnClose);
+  setAutoFillBackground(true);
+  document()->setMaximumBlockCount(1000);
+  Accumulate = true;
+  
+  // Connect to DIM handler
+  if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
+    printf("Failed connection for %s\n", Name.toAscii().data());
+  }
+
+  if (!Pure) {
+	// Get history for this service
+	const struct EvidenceHistory::Item *R;
+	class EvidenceHistory *Hist;
+
+	if ((Hist = Handler->GetHistory(Name)) != NULL) {
+	  while ((R=Hist->Next()) != NULL) {
+		moveCursor (QTextCursor::Start);
+		insertPlainText(QString("(")+QDateTime::fromTime_t(R->Time).toString()+") " +
+		  QString::fromStdString(EvidenceServer::ToString(Hist->GetFormat(), (void *) R->Data, R->Size)) + "\n"); 
+	  }	
+	}
+	Handler->DropHistory(Name);
+  }
+
+  // DIM client
+  Handler->Subscribe(Name);
+}
+
+// Destructor
+EddText::~EddText() {
+
+  Handler->Unsubscribe(Name);
+}
+
+
+// Update widget (must happen in GUI thread)
+void EddText::Update(QString Name, int Time, QByteArray, QString Format, QString Text) {
+
+  if (this->Name != Name) return;
+  QPalette Pal = palette();  
+
+  // Check if service available
+  if (!SetStatus(this, Name, Time, Format)) {
+    Pal.setColor(QPalette::Base, Qt::lightGray);
+	setPalette(Pal);
+	return;
+  }
+
+  Pal.setColor(QPalette::Base, Qt::white);
+  setPalette(Pal);
+  QDateTime Timex = QDateTime::fromTime_t(Time); 
+
+  // Clear display in case text should not accumulate
+  if (Accumulate == false) clear();
+
+  if (!Pure) {
+    moveCursor(QTextCursor::Start);
+    insertPlainText(QString("(")+Timex.toString()+QString(") "));	  
+    insertPlainText(Text + "\n");	  
+  }
+  else 	if (Format == "C") insertPlainText(Text);
+}
+
+
+/////////////////////////////
+// Interface to Dim system //
+/////////////////////////////
+EddDim::EddDim() {
+
+  Mutex = new QMutex(QMutex::Recursive);
+  Volume = 0;
+
+  // Timer to calculate data rates
+  QTimer *Timer = new QTimer(this);
+  Timer->connect(Timer, SIGNAL(timeout()), this, SLOT(UpdateStatistics()));
+  Timer->start(10000);
+
+  // Connect to DIM handler
+  if (connect(this, SIGNAL(INT(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
+    printf("Failed connection in EddDim()\n");
+  }
+}
+
+// Destructor
+EddDim::~EddDim() {
+
+  QList<QString> L = HistoryList.keys();
+  for(int i=0; i<L.size(); i++) delete HistoryList[L[i]].HistClass;
+
+  delete Mutex;
+}
+
+// Subscribe to DIM service
+void EddDim::Subscribe(QString Name) {
+
+  // Lock before accessing list
+  QMutexLocker Locker(Mutex);
+
+  // If already subscribed to service, increase usage count and reemit data for new subscriber
+  if (ServiceList.contains(Name)) {
+	ServiceList[Name].Count++;
+	YEP(Name, ServiceList[Name].TimeStamp, ServiceList[Name].ByteArray, ServiceList[Name].Format, ServiceList[Name].Text);
+	return;
+  }
+
+  // Create new entry in service list
+  ServiceList[Name].ByteArray = QByteArray();
+  ServiceList[Name].TimeStamp = -1;
+  ServiceList[Name].Count = 1;
+  ServiceList[Name].DIMService = new DimStampedInfo(Name.toAscii().data(), INT_MAX, NO_LINK, this);
+  return;
+}
+
+// Unsubsribe from DIM service
+void EddDim::Unsubscribe(QString Name) {
+
+  // Lock before accessing list
+  QMutexLocker Locker(Mutex);
+
+  if (ServiceList.contains(Name)) ServiceList[Name].Count--;
+
+  if (ServiceList[Name].Count == 0) {
+	delete ServiceList[Name].DIMService;
+	ServiceList.remove(Name);
+	return;
+  }
+}
+
+// Get history buffer
+class EvidenceHistory *EddDim::GetHistory(QString Name) {
+
+  // History already available (only request again if too old)
+  if (HistoryList.contains(Name)) {
+	HistoryList[Name].Count++;
+
+	if (time(NULL)-HistoryList[Name].LastUpdate < 5) {
+	  HistoryList[Name].HistClass->Rewind();
+	  return HistoryList[Name].HistClass;
+	}
+	HistoryList[Name].LastUpdate = time(NULL);
+	if (HistoryList[Name].HistClass->GetHistory()) return HistoryList[Name].HistClass;
+	else return NULL;
+  }
+
+  // Create new history class
+  HistoryList[Name].HistClass = new EvidenceHistory(Name.toStdString());
+  HistoryList[Name].Count = 1;
+  HistoryList[Name].LastUpdate = time(NULL);
+
+  if (HistoryList[Name].HistClass->GetHistory()) return HistoryList[Name].HistClass;
+  else return NULL;
+}
+
+// Reduce history usage counter
+void EddDim::DropHistory(QString Name) {
+
+  if (HistoryList.contains(Name)) HistoryList[Name].Count--;
+}
+
+// Update throughput statistics and clear up history memory
+void EddDim::UpdateStatistics() {
+
+  // Lock before accessing internal variables
+  QMutexLocker Locker(Mutex);
+
+  // Remove unused histories after not less than 5 seconds
+  QList<QString> L = HistoryList.keys();
+  for(int i=0; i<L.size(); i++) {
+	if ((HistoryList[L[i]].Count <= 0) && (time(NULL)-HistoryList[L[i]].LastUpdate) > 5) { 
+	  delete HistoryList[L[i]].HistClass;
+	  HistoryList.remove(L[i]);
+	}
+  }
+  
+  float Rate = Volume/1024.0/10;
+  Volume = 0;
+  YEP("Edd/Rate_kBSec", time(NULL), QByteArray::number(Rate), "F", QString::number(Rate));
+}
+
+// Store service information for usage by Subscribe(), update statistics and emit signal to widgets
+void EddDim::Update(QString Name, int Time, QByteArray Data, QString Format, QString Text) {
+
+  // Lock before accessing list
+  QMutexLocker Locker(Mutex);
+
+  // Store service data
+  if (ServiceList.contains(Name)) {
+	  ServiceList[Name].TimeStamp = Time;
+	  ServiceList[Name].ByteArray = Data;
+	  ServiceList[Name].Format = Format;
+	  ServiceList[Name].Text = Text;
+  }
+
+  // Update statistics only for actual Dim services
+  if (!Name.startsWith("Edd/")) Volume += Data.size();
+  
+  // Emit signal to all widgets
+  YEP(Name, Time, Data, Format, Text);
+}
+
+// Handling of DIM service update
+// No locking allowed. Signal triggers only EddDim::Update() when the main event loop is idle.
+void EddDim::infoHandler() {
+
+  if (!EvidenceServer::ServiceOK(getInfo())) INT(getInfo()->getName(), -1);
+  else {
+	INT(getInfo()->getName(), getInfo()->getTimestamp(), QByteArray((char *) getInfo()->getData(), 
+		getInfo()->getSize()), getInfo()->getFormat(), QString::fromStdString(EvidenceServer::ToString(getInfo()->getFormat(), getInfo()->getData(), getInfo()->getSize())));
+  }
+}
+
+//
+//
+// ====== FACT specific part ======
+//
+//
+
+////////////////////////
+// Event oscilloscope //
+////////////////////////
+
+// Constructor
+EventScope::EventScope(QWidget *P): EddBasePlot(P), PixelMap("../../config/PixelMap.txt", false) {
+
+  Name = "drsdaq/EventData";
+
+  Tmpfile = tmpfile();
+  if(Tmpfile == NULL) {
+    QMessageBox::warning(this, "Edd Message", "Could not open temporary file.", QMessageBox::Ok);
+  }
+
+  // Open file with RawDataCTX
+  RD = new RawDataCTX(true);
+  ErrCode = CTX_NOTOPEN;
+
+  // Context menu
+  PhysPipeAction = new QAction("Plot physical pipeline", this);
+  PhysPipeAction->setCheckable(true);
+  connect(PhysPipeAction, SIGNAL(triggered()), SLOT(PlotTraces()));
+  Menu->insertAction(StripAction, PhysPipeAction);
+  Menu->removeAction(StripAction);
+
+  // Initial trace
+  AddTrace(0,0,0);
+
+  // Connect to DIM handler
+  if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
+    printf("Failed connection for %s\n", Name.toAscii().data());
+  }
+  Handler->Subscribe(Name);
+}
+
+// Destructor (items with parent widget are automatically deleted)
+EventScope::~EventScope() {
+
+  Handler->Unsubscribe(Name);
+  while (!List.isEmpty()) DeleteCurve(List.last().Signal);
+  delete RD;
+  if (Tmpfile != NULL) fclose(Tmpfile);
+}  
+
+// Add trace
+void EventScope::AddTrace(int Board, int Chip, int Channel) {
+
+  struct ItemDetails N;
+  
+  N.Signal = NewCurve(QString::number(Board)+","+QString::number(Chip)+","+ QString::number(Channel)+ " (" + DRS_to_Pixel(Board, Chip, Channel).c_str() + ")");
+  N.Board = Board;
+  N.Chip = Chip;
+  N.Channel = Channel;
+  N.Trigger = new QwtPlotMarker();
+  N.Trigger->setSymbol(QwtSymbol(QwtSymbol::Diamond, QBrush(N.Signal->pen().color()), N.Signal->pen(), QSize(10,10)));
+  N.Trigger->attach(this);
+
+  if (List.isEmpty()) {
+    QPen Pen = N.Signal->pen();
+	Pen.setWidth(2);
+	N.Signal->setPen(Pen);
+  }
+  List.append(N);
+    
+  PlotTraces();
+}
+
+// Update last trace (to reflect current setting of spin boxes in DAQ page)
+void EventScope::UpdateFirst(int Board, int Chip, int Channel) {
+
+  if (List.isEmpty()) return;
+  
+  List.first().Signal->setTitle(QString::number(Board)+","+QString::number(Chip)+","+ QString::number(Channel) + " (" + DRS_to_Pixel(Board, Chip, Channel).c_str() + ")");
+  List.first().Board = Board;
+  List.first().Chip = Chip;
+  List.first().Channel = Channel;
+    
+  PlotTraces();
+}
+  
+// Update event buffer
+void EventScope::Update(QString Name, int Time, QByteArray Data, QString Format, QString) {
+
+  if (Name != this->Name) return;
+
+  // Check if service available
+  if (!SetStatus(this, Name, Time, Format)) return;
+  if (Data.size() < (int) sizeof(RunHeader)) return;
+  
+  // Open tempory file and write event data to this file
+  QTemporaryFile File;
+  if (!File.open()) {
+	QMessageBox::warning(this, "Edd Message","Could not open temporary file.",QMessageBox::Ok);
+	return;
+  }
+  if (File.write(Data) == -1) {
+	QMessageBox::warning(this, "Edd Message","Could not write data to temporary file.",QMessageBox::Ok);
+	return;	
+  }
+
+  // Prepare temporary file for run header  
+  ftruncate(fileno(Tmpfile), 0);
+  rewind(Tmpfile);
+
+  // Open file with RawDataCTX
+  switch (ErrCode = RD->OpenDataFile(File.fileName().toAscii().data(), Tmpfile)) {
+    case CTX_FOPEN:		QMessageBox::warning(this, "Edd Message","Could not open file.",QMessageBox::Ok);
+						return;
+    case CTX_RHEADER:	QMessageBox::warning(this, "Edd Message","Could not read run header.",QMessageBox::Ok);
+		      			return;
+    case CTX_BSTRUCT:	QMessageBox::warning(this, "Edd Message","Could not read board structures.",QMessageBox::Ok);
+						return;
+	default: break;
+  }
+
+  // Emit signal containing run header
+  rewind(Tmpfile);
+  QTextStream Stream(Tmpfile);
+  emit(RunHeaderChanged(Stream.readAll()));
+
+  // Prepare temporary file for event header  
+  ftruncate(fileno(Tmpfile), 0);
+  rewind(Tmpfile);
+
+  if (RD->ReadEvent(0, Tmpfile) != CTX_OK) {
+    QMessageBox::warning(this, "Edd Warning","Could not read event.",QMessageBox::Ok);
+    return;
+  }
+
+  // Emit signal containing run header
+  rewind(Tmpfile);
+  emit(EventHeaderChanged(Stream.readAll()));
+
+  PlotTraces();
+}
+
+// Update curves
+void EventScope::PlotTraces() {
+
+  double x,y;
+  unsigned int Cell, Trig;
+  
+  // Only process if valid data in RawDataCTX class
+  if (ErrCode != CTX_OK) return;
+  
+  // Set x axis title
+  if (PhysPipeAction->isChecked()) setAxisTitle(QwtPlot::xBottom, "Time from start of pipeline (ns)");
+  else setAxisTitle(QwtPlot::xBottom, "Time from trigger minus one revolution (ns)");
+  
+  // Loop through event data to update event scope
+  RunHeader *R = RD->RHeader;
+  for (int i=0; i<List.size(); i++) {
+
+	ClearCurve(i);
+
+ 	// Check if current event contains requested trace
+    if (List[i].Board>=R->NBoards || List[i].Chip>=R->NChips || List[i].Channel>=R->NChannels) continue;
+
+	// Set trigger marker visibility
+	List[i].Trigger->setVisible(PhysPipeAction->isChecked());
+	
+	// Determine trigger cell
+	Trig = *((int *) RD->Data + List[i].Board*R->NChips + List[i].Chip);
+
+	for (unsigned int j=0; j<R->Samples; j++) {
+
+	  if (PhysPipeAction->isChecked()) Cell = (j + Trig) % 1024;
+	  else Cell = j;
+
+	  x = j / RD->BStruct[List[i].Board].NomFreq;
+	  y = *((short *) (RD->Data + R->NBoards*R->NChips*sizeof(int)) +
+	  	List[i].Board*R->NChips*R->NChannels*R->Samples + List[i].Chip*R->NChannels*R->Samples +
+		List[i].Channel*R->Samples + Cell) * RD->BStruct[List[i].Board].ScaleFactor;
+
+	  AddPoint(i, x, y);
+	  
+	  // Set trigger point indicator
+	  if (Trig == j/*+R->Offset*/) List[i].Trigger->setValue(x, y);	
+    }
+  }
+
+  UpdatePlot();
+
+  // Loop through event data for pixel display
+  QVector<double> Pixel(R->NBoards*R->NChips*R->NChannels);
+  int Count = 0;
+
+  for (unsigned int Board=0; Board<R->NBoards; Board++) {
+  for (unsigned int Chip=0; Chip<R->NChips; Chip++) {
+  for (unsigned int Channel=0; Channel<R->NChannels; Channel++) {
+    Pixel[Count] = DBL_MIN;
+
+	for (unsigned int i=0; i<R->Samples; i++) {
+	  y = *((short *) (RD->Data + R->NBoards*R->NChips*sizeof(int)) +
+		Board*R->NChips*R->NChannels*R->Samples + Chip*R->NChannels*R->Samples +
+		Channel*R->Samples + i) * RD->BStruct[Board].ScaleFactor;
+
+	  if (y > Pixel[Count]) Pixel[Count] = y;
+    }
+	Count++;	  
+  }}}
+  
+  emit(PixelData(Pixel));
+}
+
+// Remove list entry
+void EventScope::DeleteCurve(QwtPlotCurve *Curve) {
+
+  for (int i=0; i<List.size(); i++) if (List[i].Signal == Curve) {
+	delete List[i].Trigger;
+    List.removeAt(i);
+  }
+}
+
+//------------------------------------------------------------------
+//**************************** Tab pages ***************************
+//------------------------------------------------------------------
+
+//
+// Environment page
+//
+TP_Environment::TP_Environment() {
+
+  QGridLayout *Layout = new QGridLayout(this);
+  setAttribute(Qt::WA_DeleteOnClose);
+
+  // Status display
+  EddLineDisplay *Line = new EddLineDisplay("ARDUINO/Message");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 0, 0, 1, 2);      
+
+  // Generate plot and data displays
+  EddPlot *Plot = new EddPlot();
+  for (int i=0; i<10; i++) {
+    Line = new EddLineDisplay("ARDUINO/Data", i);
+    Layout->addWidget(Line, i%5+1, i/5, 1, 1);
+    Plot->AddService("ARDUINO/Data", i);
+  }
+  Layout->addWidget(Plot, 0, 2, 9, 7);      
+
+  // Night sky monitor
+  Line = new EddLineDisplay("SQM/Message");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 6, 0, 1, 2);      
+
+  Line = new EddLineDisplay("SQM/NSB");
+  Layout->addWidget(Line, 7, 0, 1, 1);          
+}
+
+//
+// Bias page
+//
+TP_Bias::TP_Bias() {
+
+  QGridLayout *Layout = new QGridLayout(this);
+  setAttribute(Qt::WA_DeleteOnClose);
+  EddLineDisplay *Line;
+  
+  EddPlot *Plot = new EddPlot();
+  Plot->setMinimumWidth(400);
+  for (int i=0; i<18; i++) {
+    Line = new EddLineDisplay("Bias/VOLT/ID00", i);
+    Layout->addWidget(Line, i%9+1, 0+i/9, 1, 1);
+    Plot->AddService("Bias/VOLT/ID00", i);
+
+    Line = new EddLineDisplay("Bias/VOLT/ID00", i+32);
+    Layout->addWidget(Line, i%9+1, 2+i/9, 1, 1);
+    Plot->AddService("Bias/VOLT/ID00",i+32);
+  }
+
+  Layout->addWidget(Plot, 0, 4, 12, 3);
+  Line = new EddLineDisplay("Bias/Message");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 0, 0, 1, 3);      
+
+  EddCommand *Command = new EddCommand("Bias/Command");
+  Layout->addWidget(Command, 10, 0, 1, 4);    
+
+  EddText *Text = new EddText("Bias/ConsoleOut", true);
+  Text->setFixedWidth(400);
+  Layout->addWidget(Text, 11, 0, 4, 4);
+}
+
+//
+// Feedback page
+//
+TP_Feedback::TP_Feedback() {
+
+  setAttribute(Qt::WA_DeleteOnClose);
+  QGridLayout *Layout = new QGridLayout(this);
+  EddLineDisplay *Line;
+
+  EddPlot *Plot = new EddPlot();
+  for (int i=0; i<36; i++) {
+    Line = new EddLineDisplay("Feedback/Average", i);
+	Line->setMaximumWidth(60);
+    Layout->addWidget(Line, i%9+2, 0+i/9, 1, 1);
+    Plot->AddService("Feedback/Average", i);
+  }
+  Layout->addWidget(Plot, 0, 4, 12, 10);
+
+  Line = new EddLineDisplay("drsdaq/Message");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 0, 0, 1, 2);      
+
+  Line = new EddLineDisplay("Feedback/State");
+  Line->setMaximumWidth(150);
+  Layout->addWidget(Line, 1, 0, 1, 2);      
+  Line = new EddLineDisplay("Feedback/Count");
+  Line->setMaximumWidth(60);
+  Layout->addWidget(Line, 1, 2);      
+
+  QWidget *Button = new QPushButton("Details");
+  Layout->addWidget(Button, 12, 0, 1, 1);      
+  connect(Button, SIGNAL(pressed()), SLOT(FeedbackDetails()));
+
+}
+
+void TP_Feedback::FeedbackDetails() {
+
+  QMainWindow *M = new QMainWindow;
+  M->setCentralWidget(new QWidget);
+  M->setStatusBar(new QStatusBar(M));
+  M->setWindowTitle("Edd - Feedback Details");
+  M->setAttribute(Qt::WA_DeleteOnClose);
+
+  QGridLayout *Layout = new QGridLayout(M->centralWidget());
+  EddLineDisplay *Line;
+  EddPlot *Plot = new EddPlot();
+
+  for (int i=0; i<36; i++) {
+    Line = new EddLineDisplay("Feedback/Sigma", i);
+	Line->setMaximumWidth(60);
+    Layout->addWidget(Line, i%9, 0+i/9, 1, 1);
+    Plot->AddService("Feedback/Sigma", i);
+	
+    Line = new EddLineDisplay("Feedback/Target", i);
+	Line->setMaximumWidth(60);
+    Layout->addWidget(Line, i%9+10, 0+i/9, 1, 1);
+	
+	Line = new EddLineDisplay("Feedback/Response", i);
+	Line->setMaximumWidth(60);
+    Layout->addWidget(Line, i%9+20, 0+i/9, 1, 1);
+  }
+  Layout->addWidget(Plot, 0, 4, 30, 12);
+  
+  M->show();
+}
+
+//
+// Event scope page
+//
+TP_DAQ::TP_DAQ() {
+
+  EddLineDisplay *Line;
+
+  setAttribute(Qt::WA_DeleteOnClose);
+  QGridLayout *Layout = new QGridLayout(this);
+
+  // Run-related information 
+  Line = new EddLineDisplay("drsdaq/RunNumber");
+  Line->setMaximumWidth(100);
+  Layout->addWidget(Line, 0, 1, 1, 1);      
+  Line = new EddLineDisplay("drsdaq/EventNumber");
+  Line->setMaximumWidth(100);
+  Layout->addWidget(Line, 0, 2, 1, 1);      
+  Line = new EddLineDisplay("drsdaq/RunSizeMB");
+  Line->setMaximumWidth(100);
+  Layout->addWidget(Line, 0, 3, 1, 1);      
+  Line = new EddLineDisplay("drsdaq/FileSizeMB");
+  Line->setMaximumWidth(100);
+  Layout->addWidget(Line, 0, 4, 1, 1);      
+  Line = new EddLineDisplay("drsdaq/FileName");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 0, 5, 1, 1);      
+
+  // Event scope
+  Scope = new EventScope;
+  Scope->setMinimumWidth(700);
+
+  // Text boxes for run and event header
+  RunHeaderDisplay = new QPlainTextEdit();
+  EventHeaderDisplay = new QPlainTextEdit();
+  RunHeaderDisplay->setReadOnly(true);
+  EventHeaderDisplay->setReadOnly(true);
+
+  // Tab widget
+  QTabWidget *TabWidget = new QTabWidget();
+  TabWidget->addTab(Scope, "&Signals");
+  TabWidget->addTab(RunHeaderDisplay, "&Run Header");
+  TabWidget->addTab(EventHeaderDisplay, "&Event Header");
+  Layout->addWidget(TabWidget, 1, 1, 5, 5);
+
+  connect(Scope, SIGNAL(RunHeaderChanged(QString)), RunHeaderDisplay, SLOT(setPlainText(QString)));
+  connect(Scope, SIGNAL(EventHeaderChanged(QString)), EventHeaderDisplay, SLOT(setPlainText(QString)));
+
+  // Channel number 
+  Channel = new QSpinBox;
+  connect(Channel, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int)));
+  Channel->setToolTip("DRS channel number");
+
+  // Chip number 
+  Chip = new QSpinBox;
+  connect(Chip, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int)));
+  Chip->setToolTip("DRS chip number");
+
+  // Board number 
+  Board = new QSpinBox;
+  connect(Board, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int)));
+  Board->setToolTip("DRS board number");
+
+  // Pixel ID
+  PixelID = new QLineEdit;
+  PixelID->setMaximumWidth(60);
+  connect(PixelID, SIGNAL(returnPressed()), SLOT(TranslatePixelID()));
+  PixelID->setToolTip("Pixel identification");
+  
+  // Layout of pixel addressing widgets
+  QFormLayout *FormLayout = new QFormLayout();
+  FormLayout->setRowWrapPolicy(QFormLayout::WrapAllRows);
+  FormLayout->addRow("Channel", Channel);
+  FormLayout->addRow("Chip", Chip);
+  FormLayout->addRow("Board", Board);
+  FormLayout->addRow("Pixel ID", PixelID);
+  Layout->addLayout(FormLayout, 0, 0, 2, 1);
+  
+  // Add trace permanently
+  QPushButton *Button = new QPushButton("Keep trace");
+  Button->setToolTip("Keep trace in display");
+  Button->setMaximumWidth(80);
+  Layout->addWidget(Button, 2, 0);
+  connect(Button, SIGNAL(clicked()), SLOT(KeepCurrent()));
+
+  // Button to show event display
+  QPushButton *PixDisplay = new QPushButton("Pixel display");
+  PixDisplay->setFont(QFont("Times", 10, QFont::Bold));
+  PixDisplay->setToolTip("Show event display window");
+  PixDisplay->setMaximumWidth(80);
+  Layout->addWidget(PixDisplay, 4, 0);
+  connect(PixDisplay, SIGNAL(clicked()), SLOT(ShowPixelDisplay()));
+
+  // Event display window
+  Display = new QWidget();
+  Display->setWindowTitle("Edd - Event display");
+  
+  Pixel = new QPushButton *[MAXPIXEL];
+  int Count = 0;
+  double x,y;
+  
+  for (int ix=-22; ix<=22; ix++) for (int iy=-19; iy<=20; iy++) {  
+    if (Count == MAXPIXEL) break;
+	
+	x = ix*0.866;
+	y = iy - (ix%2==0 ? 0.5:0);
+    if ((pow(x,2)+pow(y,2) >= 395) && !(abs(ix)==22 && iy==7)) continue;
+	
+    Pixel[Count] = new QPushButton(Display);
+	Pixel[Count]->setAutoFillBackground(true);
+	Pixel[Count]->setGeometry((int) (x*12.5 + 250), (int) (y*12.5 + 250), 10, 10);
+	Pixel[Count]->show();
+	Count++;
+  }
+
+  connect(Scope, SIGNAL(PixelData(QVector<double>)), SLOT(SetPixelData(QVector<double>)));
+}
+
+TP_DAQ::~TP_DAQ() {
+
+  delete[] Pixel;
+}
+
+// Translate pixel ID to board, chip, channel
+void TP_DAQ::TranslatePixelID() {
+  
+  if (Scope->Pixel_to_DRSboard(PixelID->text().toStdString()) == 999999999) {
+	QMessageBox::warning(this, "Edd Message","Pixel ID unknown.",QMessageBox::Ok);
+  }
+  else {
+    Board->setValue(Scope->Pixel_to_DRSboard(PixelID->text().toStdString()));
+    Chip->setValue(Scope->Pixel_to_DRSchip(PixelID->text().toStdString()));
+    Channel->setValue(Scope->Pixel_to_DRSchannel(PixelID->text().toStdString()));
+  }
+}
+
+// Update event scope
+void TP_DAQ::KeepCurrent() {
+
+  Scope->AddTrace(Board->value(), Chip->value(), Channel->value());
+}
+
+// Update event scope
+void TP_DAQ::UpdateScope(int) {
+
+  // Update pixel ID
+  PixelID->setText(Scope->DRS_to_Pixel(Board->value(), Chip->value(), Channel->value()).c_str());
+  // Update first trace
+  Scope->UpdateFirst(Board->value(), Chip->value(), Channel->value());
+}
+
+// Show/hide pixel display
+void TP_DAQ::ShowPixelDisplay() {
+
+  Display->show();
+  Display->raise();
+}
+
+void TP_DAQ::SetPixelData(QVector<double> Data) {
+
+  QwtLinearColorMap Map;
+
+  for (int i=0; i<Data.size(); i++) {
+	Pixel[i]->setPalette(QPalette(Map.color(QwtDoubleInterval(300, 400), Data[i])));
+  }
+}
+
+
+//
+// Evidence page
+//
+TP_Evidence::TP_Evidence() {
+
+  setAttribute(Qt::WA_DeleteOnClose);
+  QGridLayout *Layout = new QGridLayout(this);
+  EddLineDisplay *Line;
+  EddText *Text;
+  
+  Line = new EddLineDisplay("Alarm/Message");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 0, 0, 1, 2);      
+
+  Line = new EddLineDisplay("Alarm/MasterAlarm");
+  Layout->addWidget(Line, 0, 1, 1, 1);
+
+  Text = new EddText("Alarm/Summary", true);
+  Text->Accumulate = false;
+  Text->setMaximumWidth(200);
+  Text->setMaximumHeight(150);
+  Layout->addWidget(Text, 1, 0, 1, 2);
+
+  Line = new EddLineDisplay("DColl/Message");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 3, 0, 1, 2);      
+
+  Line = new EddLineDisplay("DColl/DataSizeMB");
+  Layout->addWidget(Line, 4, 0, 1, 1);
+
+  Line = new EddLineDisplay("DColl/LogSizeMB");
+  Layout->addWidget(Line, 4, 1, 1, 1);
+
+  Line = new EddLineDisplay("DColl/CurrentFile");
+  Line->setMaximumWidth(400);
+  Layout->addWidget(Line, 5, 0, 1, 3);
+
+  Line = new EddLineDisplay("Config/Message");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 6, 0, 1, 2);      
+
+  Line = new EddLineDisplay("Config/ModifyTime");
+  Line->setMaximumWidth(200);
+  Line->ShowAsTime = true;
+  Layout->addWidget(Line, 7, 0, 1, 1);
+
+  QPushButton *Button = new QPushButton();
+  Button->setText("eLogBook");
+  connect(Button, SIGNAL(released()), SLOT(StartELog()));
+  Layout->addWidget(Button, 6, 1, 1, 1);
+
+  Button = new QPushButton();
+  Button->setText("Start DIM browser");
+  connect(Button, SIGNAL(released()), SLOT(StartDIMBrowser()));
+  Layout->addWidget(Button, 7, 1, 1, 1);
+
+  Line = new EddLineDisplay("Edd/Rate_kBSec");
+  Layout->addWidget(Line, 8, 0, 1, 1);
+}
+ 
+// Start DIM Browser
+void TP_Evidence::StartDIMBrowser() {
+
+  QProcess::startDetached("did", QStringList(), QString(getenv("DIMDIR"))+"/linux/");
+}
+
+// Start eLogBook
+void TP_Evidence::StartELog() {
+
+  QProcess::startDetached("firefox http://fact.ethz.ch/FACTelog/index.jsp");
+}
+
+ 
+//--------------------------------------------------------------------
+//*************************** Main GUI *******************************
+//--------------------------------------------------------------------
+//
+// All widgets have ultimately Central as parent.
+//
+GUI::GUI() {
+
+  Handler = new EddDim();
+  
+  // Set features of main window
+  Central = new QWidget(this);
+  setCentralWidget(Central);
+  setStatusBar(new QStatusBar(this));
+  setWindowTitle("Edd - Evidence Data Display");
+
+  // Arrangement in tabs
+  TabWidget = new QTabWidget(Central);
+  TabWidget->setTabsClosable(true);
+  connect(TabWidget, SIGNAL(tabCloseRequested(int)), SLOT(DetachTab(int)));
+  TabWidget->addTab(new TP_DAQ, "Event scope");
+  TabWidget->addTab(new TP_Bias, "Bias");
+  TabWidget->addTab(new TP_Feedback, "Feedback");
+  TabWidget->addTab(new TP_Environment, "Environment");
+  TabWidget->addTab(new TP_Evidence, "Evidence");
+
+  // Menu bar
+  QMenu* Menu = menuBar()->addMenu("&Menu");
+  Menu->addAction("New history plot", this, SLOT(MenuNewHistory()));
+  Menu->addSeparator();
+  Menu->addAction("About", this, SLOT(MenuAbout()));
+  Menu->addSeparator();
+  QAction* QuitAction = Menu->addAction("Quit", qApp, SLOT(quit()));
+  QuitAction->setShortcut(Qt::CTRL + Qt::Key_Q);
+
+  // Show main window
+  resize(TabWidget->sizeHint()*1.1);
+  show();
+}  
+    
+GUI::~GUI() {
+  delete Central;
+}
+
+
+void GUI::MenuAbout() {
+  QString Rev(SVN_REVISION);
+  Rev.remove(0,1).chop(2);
+  
+  QMessageBox::about(this, "About Edd","Evidence Data Display\n\n"
+    "Written by Oliver Grimm, IPP, ETH Zurich\n"
+    "This version compiled "__DATE__" ("+Rev+")\n\n"
+    "Graphical user interface implemented with Qt and Qwt.\n"
+    "Evidence control system based on DIM (http://dim.web.cern.ch).\n\n"
+    "Comments to oliver.grimm@phys.ethz.ch.");
+}
+
+// Open request for new history plot
+void GUI::MenuNewHistory() {
+
+  QStringList List;
+  char *Name, *Format;
+  int Type;
+  bool OK;
+
+  // Find all DIM services and sort
+  getServices("*");
+  while ((Type = getNextService(Name, Format)) != 0) {
+    if (Type==DimSERVICE) List.append(Name);
+  }
+  List.sort();
+
+  // Open dialog and open history window
+  QString Result = QInputDialog::getItem(this, "Edd Request",
+    "Enter DIM service name", List, 0, true, &OK);
+  if (OK && !Result.isEmpty()) {
+    Result = Result.trimmed();
+    QWidget *Hist = OpenHistory(Result.toAscii().data(), 0);
+    if (Hist != NULL) Hist->show();
+  }
+}
+
+// Open tab as separate window
+void GUI::DetachTab(int Tab) {
+
+  QWidget *W = NULL;
+  QMainWindow *M = new QMainWindow;
+
+  M->setCentralWidget(new QWidget(M));
+  M->setStatusBar(new QStatusBar(M));
+
+  switch(Tab) {
+	case 0:	W = new TP_DAQ; break;
+	case 1: W = new TP_Bias; break;
+	case 2: W = new TP_Feedback; break;
+	case 3: W = new TP_Environment; break;
+	case 4: W = new TP_Evidence; break;
+	default: break;
+  }
+
+  if (W == NULL) {
+    delete M->centralWidget();
+	delete M;
+	return;
+  }
+
+  W->setParent(M);
+  M->resize(size());
+  M->setWindowTitle("Edd - " + TabWidget->tabText(Tab));
+  M->show();
+}
+
+// Quit application when clicking close button on window
+void GUI::closeEvent(QCloseEvent *) {
+  qApp->quit();
+}
+
+
+//**************************** Main program ***************************
+int main(int argc, char *argv[]) {
+
+  QApplication app(argc, argv); 
+  GUI MainWindow;
+
+  return app.exec();
+}
Index: fact/Evidence/Edd/Edd.h
===================================================================
--- fact/Evidence/Edd/Edd.h	(revision 9834)
+++ fact/Evidence/Edd/Edd.h	(revision 9834)
@@ -0,0 +1,381 @@
+#ifndef EDD_H_SEEN
+#define EDD_H_SEEN
+
+#include <QtGui>
+ 
+#include <qwt_plot.h>
+#include <qwt_plot_curve.h>
+#include <qwt_plot_grid.h>
+#include <qwt_plot_zoomer.h>
+#include <qwt_plot_magnifier.h>
+#include <qwt_plot_panner.h>
+#include <qwt_scale_engine.h>
+#include <qwt_analog_clock.h>
+#include <qwt_scale_widget.h>
+#include <qwt_plot_layout.h>
+#include <qwt_legend.h>
+#include <qwt_legend_item.h>
+#include <qwt_symbol.h>
+#include <qwt_plot_marker.h>
+#include <qwt_data.h>
+#include <qwt_color_map.h>
+
+#include <limits.h>
+#include <float.h>
+
+#include "dic.hxx"
+#include "Evidence.h"
+#include "RawDataCTX.h"
+#include "PixelMap.h"
+
+#define SVN_REVISION "$Revision$"
+
+const QColor EddPlotBackgroundColor(Qt::yellow);
+
+QWidget *OpenHistory(char *, int);
+bool SetStatus(QWidget *, QString, int, QString, int = -1);
+
+
+// Base class for Edd plot
+// DeleteCurve() is pure virtual and needs to be implemented iin the application class
+class EddBasePlot: public QwtPlot {
+  Q_OBJECT
+
+  protected:    
+    QMenu *Menu;
+    QAction *StripAction;
+
+  private:
+    struct PlotItem {
+      QwtPlotCurve *Signal;
+	  QVector<double> x;
+	  QVector<double> y;
+      double Smallest;
+      double Largest;	  
+    };
+    QList<struct PlotItem> Items;
+
+    QAction *YLogAction;
+    QAction *NormAction;
+    QAction *StyleAction;
+    
+    QwtPlotPanner *Panner;
+    QwtPlotGrid *Grid;
+    QwtPlotZoomer *Zoomer;
+	QwtPlotMagnifier *Magnifier;
+    QwtPicker *Picker;
+	QwtDoubleRect BBox;
+	 	
+  public:
+    EddBasePlot(QWidget * = NULL);
+    ~EddBasePlot();
+
+	QwtPlotCurve *NewCurve(QwtText);
+	void ClearCurve(unsigned int);
+	void AddPoint(unsigned int, double, double);
+	virtual void DeleteCurve(QwtPlotCurve *) = 0;
+
+  protected slots:
+    void UpdatePlot();
+
+  private slots:
+    void HandleZoom(const QwtDoubleRect &);
+	void MouseSelection(const QwtPolygon &);
+	void contextMenuEvent(QContextMenuEvent *);
+    void MenuSingleTrace();        
+    void MenuZoomOut();
+    void MenuSaveASCII();
+    void MenuSave();
+    void MenuPrint();
+	void MenuPlotHelp();
+};
+		  
+// General indicator for DIM service
+class EddLineDisplay: public QLineEdit {
+    Q_OBJECT
+
+    QMenu *Menu;
+    QPoint dragStart;
+    QWidget *LastHist;
+	
+	QString ServiceName;
+	int Index;
+	
+    void mousePressEvent(QMouseEvent *); 
+    void mouseReleaseEvent(QMouseEvent *); 
+    void mouseMoveEvent(QMouseEvent *); 
+	
+  public:
+    EddLineDisplay(QString, int=0, QWidget * = NULL);
+    ~EddLineDisplay();
+
+	bool ShowAsTime;
+	
+  private slots:
+	void Update(QString, int, QByteArray, QString, QString);
+    void contextMenuEvent(QContextMenuEvent *);    
+    void MenuOpenHistory();
+    void MenuCopyService();
+    void MenuCopyData();
+};
+
+// Sending command to DIM server
+class EddCommand: public QLineEdit {
+    Q_OBJECT
+
+	QString Name;
+	
+  public:
+    EddCommand(QString, QWidget * = NULL);
+
+  private slots:
+	void SendCommand();	
+};
+
+// Graph class for history display 
+class EddPlot: public EddBasePlot {
+    Q_OBJECT
+
+	// Time scale for axis
+	class EddTimeScale: public QwtScaleDraw {
+
+	  public:
+		EddTimeScale() {}
+
+		virtual QwtText label(double v) const {
+		  // Adapt text format to time span 
+		  QString Format;
+		  if (scaleDiv().range() < 60*60) Format = "hh' h\n'mm:ss";
+		  else if (scaleDiv().range() < 24*60*60) Format = "hh:mm";
+		  else if (scaleDiv().range() < 30*24*60*60) Format = "h' h\n'd-MMM";
+		  else Format = "d-MMM'\n'yyyy";
+
+		  // Generate text
+		  QwtText Text = QDateTime::fromTime_t((int) v).toString(Format);
+		  QFont Font = Text.font();
+		  Font.setPointSize(7);
+		  Text.setFont(Font);
+
+    	  return Text;
+		}
+	};
+
+    struct ItemDetails {
+      QString Name;
+	  int Index;
+      QwtPlotCurve *Signal;	  
+    };
+    QList<struct ItemDetails> List;
+
+  private:
+	QwtLegend *Legend;
+	int SizeLimit;
+
+    void dragEnterEvent(QDragEnterEvent *);
+    void dropEvent(QDropEvent *);
+	void paintEvent(QPaintEvent *);
+	 	
+  public:
+    EddPlot(QString = QString(), int = 0, QWidget * = NULL);
+    ~EddPlot();
+    void AddService(QString, int = 0);
+	void DeleteCurve(QwtPlotCurve *);
+
+  private slots:
+	void Update(QString, int, QByteArray, QString, QString);
+	void LegendClicked(QwtPlotItem *);
+    void MenuPasteService();
+};
+
+
+// Text history and output class
+class EddText: public QTextEdit {
+  Q_OBJECT
+
+  private:
+	QString Name;
+	bool Pure;
+	
+  public:
+    EddText(QString, bool = false, QWidget * = NULL);
+    ~EddText();
+
+	bool Accumulate;
+
+  private slots:
+	void Update(QString, int, QByteArray, QString, QString);
+};
+
+
+// Interface to DIM system
+class EddDim: public QObject, public DimInfo {
+  Q_OBJECT
+
+  private:
+	struct Item {
+	  DimStampedInfo *DIMService;
+	  int Count;
+	  int TimeStamp;
+	  QByteArray ByteArray;
+	  QString Format;
+	  QString Text;
+	};
+    QMap<QString, struct Item> ServiceList;
+    QMutex *Mutex;
+
+	struct HistItem {
+	  int Count;
+	  int LastUpdate;
+	  class EvidenceHistory *HistClass;
+	};
+    QMap<QString, struct HistItem> HistoryList;
+
+	long long Volume;
+
+	void infoHandler();
+
+  private slots:
+	void Update(QString, int, QByteArray, QString, QString);
+	void UpdateStatistics();
+
+  public:
+    EddDim();
+    ~EddDim();
+
+	void Subscribe(QString);
+	void Unsubscribe (QString);
+	class EvidenceHistory *GetHistory(QString);
+	void DropHistory(QString);
+
+  signals:
+    void YEP(QString, int, QByteArray = QByteArray(), QString = QString(), QString = QString());
+    void INT(QString, int, QByteArray = QByteArray(), QString = QString(), QString = QString());
+};
+
+//
+//
+// ====== FACT specific part ======
+//
+//
+
+// Event oscilloscope
+class EventScope: public EddBasePlot, public PixelMap {
+  Q_OBJECT
+
+  private:
+    struct ItemDetails {
+	  unsigned int Board, Chip, Channel;
+      QwtPlotCurve *Signal;
+	  QwtPlotMarker *Trigger;
+    };
+    QList<struct ItemDetails> List;
+
+    QString Name;
+	RawDataCTX *RD;
+	CTX_ErrCode ErrCode;
+	QAction *PhysPipeAction;
+	FILE *Tmpfile;
+
+  public:
+    EventScope(QWidget * = NULL);
+    ~EventScope();
+	
+	void UpdateFirst(int, int, int);
+	void AddTrace(int, int, int);
+
+  private slots:
+	void Update(QString, int, QByteArray, QString, QString);
+	void PlotTraces();
+	void DeleteCurve(QwtPlotCurve *);
+	
+  signals:
+	void RunHeaderChanged(QString);
+	void EventHeaderChanged(QString);
+	void PixelData(QVector<double>);
+};
+
+
+// Tab page classes
+class TP_Environment: public QWidget {
+  Q_OBJECT
+
+  public:
+    TP_Environment();
+};
+
+class TP_Bias: public QWidget {
+  Q_OBJECT
+
+  public:
+    TP_Bias();
+};
+
+class TP_Feedback: public QWidget {
+  Q_OBJECT
+
+  private slots:
+	void FeedbackDetails();
+
+  public:
+    TP_Feedback();
+};
+
+class TP_DAQ: public QWidget {
+  Q_OBJECT
+  static const int MAXPIXEL = 1440;
+
+  private:
+	EventScope *Scope;
+    QPlainTextEdit *RunHeaderDisplay, *EventHeaderDisplay;
+
+ 	QSpinBox *Channel, *Chip, *Board;
+ 	QLineEdit *PixelID;
+ 	QFormLayout *FormLayout;
+	QWidget *Display;
+	QPushButton **Pixel;
+
+  private slots:
+	void TranslatePixelID();
+	void UpdateScope(int);
+	void KeepCurrent();
+	void ShowPixelDisplay();
+	void SetPixelData(QVector<double>);
+
+  public:
+	TP_DAQ();	
+	~TP_DAQ();	
+};
+
+class TP_Evidence: public QWidget {
+  Q_OBJECT
+
+  private slots:
+	void StartDIMBrowser();
+	void StartELog();
+
+  public:
+	TP_Evidence();	
+};
+
+
+// Main window class
+class GUI: public QMainWindow, public DimBrowser {
+  Q_OBJECT
+
+  private:
+    QWidget *Central;
+    QTabWidget *TabWidget;
+            
+    void closeEvent(QCloseEvent *);
+	
+  public:
+    GUI();
+    ~GUI();
+    
+  private slots:
+    void MenuAbout();
+    void MenuNewHistory();
+	void DetachTab(int);
+};
+
+#endif
Index: fact/Evidence/Edd/Edd.pro
===================================================================
--- fact/Evidence/Edd/Edd.pro	(revision 9834)
+++ fact/Evidence/Edd/Edd.pro	(revision 9834)
@@ -0,0 +1,13 @@
+######################################################################
+# Automatically generated by qmake (2.01a) Mon Sep 28 16:32:39 2009
+######################################################################
+
+TEMPLATE = app
+TARGET = 
+DEPENDPATH += .
+INCLUDEPATH += . .. $(QWTDIR)/include $(DIMDIR)/dim ../../drsdaq ../../pixelmap
+
+# Input
+HEADERS += Edd.h
+SOURCES += Edd.cc ../Evidence.cc ../../drsdaq/RawDataCTX.cc ../../pixelmap/Pixel.cc ../../pixelmap/PixelMap.cc
+LIBS += -L$(QWTDIR)/lib -lqwt $(DIMDIR)/linux/libdim.a
Index: fact/Evidence/Edd/INSTALL
===================================================================
--- fact/Evidence/Edd/INSTALL	(revision 9834)
+++ fact/Evidence/Edd/INSTALL	(revision 9834)
@@ -0,0 +1,44 @@
+Installation instructions for the Evidence Data Display Edd
+
+A) The packages Qt, Qwt and DIM are needed
+
+1. Qt can be downloaded from http://qt.nokia.com/. 
+
+   Test have been made with version 4.6.2 which can be downloaded aat
+   ftp://ftp.qt.nokia.com/qt/source/qt-everywhere-opensource-src-4.6.2.tar.gz.
+   It should well work with later version as well. 
+   
+   Installation instruction are given in the package. Usually, they amount on
+   Linux to executing './configure', then 'gmake all' followed by 
+   'gmake install', all done within the unpacked directory.
+ 
+   Before the following steps, it should be assured that the right version
+   of qmake is used, as many Linux installations include an older version
+   of Qt. Use 'qmake -version' to check and adjust your PATH variable if
+   necessary.
+    
+2. Qwt can be downloaded from http://sourceforge.net/projects/qwt/.
+   
+   Tests were made with version 5.2.1. Installation should simply be done
+   by executing 'qmake', then 'make', and finally 'make install'.
+
+3. DIM can be downloaded from http://dim.web.cern.ch/dim/.
+
+   The latest available version should be fine. Follow the installation
+   instruction on the web site given at the download link. Note that the
+   command line option in 'unzip -a' is mandatory. Compiling is done
+   with 'gmake all'.
+
+
+B) Compiling Edd
+
+An environment variable QWTDIR should point to the Qwt installation,
+and DIMDIR to the DIM installation.
+
+The subversion repository should preferably be checked out fully, as
+the class describing the M0 raw data format is currently needed.
+
+Issue 'qmake', then 'make' will build the executable Edd.
+
+To start, the environment variable LD_LIBRARY_PATH needs to include the
+location of the Qwt shared library. 
Index: fact/Evidence/Evidence.cc
===================================================================
--- fact/Evidence/Evidence.cc	(revision 9834)
+++ fact/Evidence/Evidence.cc	(revision 9834)
@@ -0,0 +1,586 @@
+/********************************************************************\
+
+  General code to start a server of the Evidence Control System
+  
+  - The server is started with the given name.
+  - DIM exit and error handlers are implemented.
+  - A Message service is published with severity encoding. It can be updated
+    with the Message() method. The text will also be logged.
+  - If the severity of a Message() call is FATAL, exit() will be called (with
+    this severity, the call to Message() is guranteed not to return).
+  - Configuration data can be requested by GetConfig() and non-blocking by GetConfigNB().
+  - If the configuration file changes a signal can be emitted which is caught by the standard
+    signal handler. The handler invokes ConfigChanged() which can be redefined by the user application.
+	The signal is delivered only to the main thread (where the constructor is executed) and thus
+	blocking rpc can be made from it. The signal is set using ActivateSignal().
+  - Signal handlers to ignore common signals are installed.
+    These signals will then cause pause() to return which can be used
+	by the application to terminate gracefully.
+  - The static method ToString() converts the contents of a DIM service into text
+  - A terminate-handler is installed for catching unhandled C++ exceptions.
+
+  Oliver Grimm, June 2010
+ 
+\********************************************************************/
+
+#include "Evidence.h"
+using namespace std;
+
+//
+// Internal configuration class of EvidenceServer
+//
+
+// Constructor: Subscribe to be informed on configuration file change
+EvidenceServer::Config::Config():	DimCommand((This->Name+"/ResetMessage").c_str(), (char *) ""),
+									DimRpcInfo("ConfigRequest", NO_LINK) {
+
+  CurrentItem = string();
+  ConfigTimeStamp = 0;
+  Service = new DimInfo("Config/ModifyTime", NO_LINK, this);
+}
+
+// Destructor
+EvidenceServer::Config::~Config() {
+  
+  delete Service;
+}
+
+// Reset message and severity
+void EvidenceServer::Config::commandHandler() {
+
+  This->Message(INFO, "Message reset by %s (ID %d)", getClientName(), getClientId());
+}
+
+// Track last modification time of configuration file
+void EvidenceServer::Config::infoHandler() {
+
+  pthread_t Thread;
+  int Ret;
+
+  if (!ServiceOK(DimInfo::getInfo())) return;
+return;
+  This->Lock();
+  ConfigTimeStamp = getInfo()->getInt();
+  This->Unlock();
+
+  // Launch ConfigChanged() as detached thread
+  if ((Ret = pthread_create(&Thread, NULL, (void * (*)(void *)) EvidenceServer::CallConfigChanged, (void *) NULL)) != 0) {
+    This->Message(ERROR, "pthread_create() failed in EvidenceServer::Config::infoHandler() (%s)\n", strerror(Ret));
+  }
+  else if ((Ret = pthread_detach(Thread)) != 0) {
+	This->Message(ERROR, "pthread_detach() failed in EvidenceServer::Config::infoHandler() (%s)\n", strerror(Ret));
+  }  
+}
+
+// Answer to remote procedure call: Update map
+void EvidenceServer::Config::rpcInfoHandler(){
+
+  This->Lock();
+  This->List[CurrentItem].Value = string(DimRpcInfo::getString(), DimRpcInfo::getSize()-1);
+  This->List[CurrentItem].Time = ConfigTimeStamp;
+
+  // Clear to allow new rpc call
+  CurrentItem.clear(); 
+  This->Unlock();
+}
+
+// Request configuration data possible only when answer to previous request received
+void EvidenceServer::Config::RequestNB(string Item) {
+
+  This->Lock();
+  if (CurrentItem.empty()) {
+	CurrentItem = Item;
+	setData(((char *) Item.c_str()));
+  }
+  This->Unlock();
+}
+
+
+//////////////////////////
+// EvidenceServer Class //
+//////////////////////////
+
+// Initialise
+EvidenceServer *EvidenceServer::This = NULL;
+pthread_mutex_t EvidenceServer::Mutex;
+set<pthread_t> EvidenceServer::Threads;
+
+
+// Constructor
+EvidenceServer::EvidenceServer(string ServerName): Name(ServerName) {
+
+  // Initialize
+  MessageService = NULL;
+  MessageData = NULL;
+  ExitRequest = false;
+  This = this;
+  Threads.insert(pthread_self());
+
+  // Initialise mutex
+  int Ret;
+  pthread_mutexattr_t Attr;
+
+  if ((Ret = pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_ERRORCHECK)) != 0) {
+    Message(FATAL, "pthread_mutex_settype() failed (%s)", strerror(Ret));
+  }
+  if ((Ret = pthread_mutex_init(&Mutex, &Attr)) != 0) {
+    Message(FATAL, "pthread_mutex_init() failed (%s)", strerror(Ret));
+  }
+
+  // Catch some signals
+  signal(SIGQUIT, &SignalHandler);  // CTRL-Backspace
+  signal(SIGTERM, &SignalHandler);  // Termination signal
+  signal(SIGINT, &SignalHandler);   // CTRL-C
+  signal(SIGHUP, &SignalHandler);   // Terminal closed
+  
+  // Catch C++ unhandled exceptions
+  set_terminate(Terminate);
+
+  // Message service and initial message
+  MessageService = new DimService((Name+"/Message").c_str(), (char *) "I:1;C", NULL, 0);
+
+  string Rev(EVIDENCE_REVISION); 
+  Message(INFO, "Server started (%s, compiled %s %s)", (Rev.substr(1, Rev.size()-3)).c_str(),__DATE__, __TIME__);
+
+  // Configuration class
+  ConfClass = new class Config();
+
+  // Start server
+  start(ServerName.c_str());
+  addExitHandler(this);
+}
+
+
+// Destructor: Free memory
+EvidenceServer::~EvidenceServer() {
+
+  Message(INFO, "Server stopped");
+
+  delete ConfClass;
+
+  int Ret;
+  if ((Ret = pthread_mutex_destroy(&Mutex)) != 0) {
+	Message(ERROR, "pthread_mutex_destroy() failed (%s)", strerror(Ret));
+  }
+  
+  delete MessageService;
+  delete[] MessageData;
+}
+
+
+// DIM exit handler
+void EvidenceServer::exitHandler(int Code) {
+
+  Message(INFO, "Exit handler called (DIM exit code %d)", Code);
+  exit(EXIT_SUCCESS);
+}
+
+// DIM error handler
+void EvidenceServer::errorHandler(int Severity, int Code, char *Text) {
+
+  Message(ERROR, "%s (DIM error code %d, DIM severity %d)\n", Text, Code, Severity);
+}
+
+
+// Set server message (if Severity is FATAL, exit() will be invoked)
+void EvidenceServer::Message(MessageType Severity, const char *Format, ...) {
+
+  static const char* StateString[] = {"Info", "Warn", "Error", "Fatal"};
+  static char ErrorString[] = "vasprintf() failed in Message()";
+  char *Text;
+  
+  // Assemble message from application
+  va_list ArgumentPointer;
+  va_start(ArgumentPointer, Format);
+  if (vasprintf(&Text, Format, ArgumentPointer) == -1) {
+	Text = ErrorString;
+	Severity = ERROR;
+  }
+  va_end(ArgumentPointer);
+
+  // Generate new Message structure and free text
+  struct Message *NewMsg = (struct Message *) new char [sizeof(struct Message)+strlen(Text)+1];
+  NewMsg->Severity = Severity;
+  strcpy(NewMsg->Text, Text);
+  if (Text != ErrorString) free(Text);
+  
+  // Send message to console and log file
+  printf("%s (%s): %s\n", MessageService->getName(), StateString[Severity], NewMsg->Text);
+  SendToLog("%s (%s): %s", MessageService->getName(), StateString[Severity], NewMsg->Text);
+
+  // Update DIM message service
+  if (MessageService != NULL) {
+	MessageService->updateService(NewMsg, sizeof(struct Message)+strlen(NewMsg->Text)+1);
+  }
+
+  // Delete old message
+  Lock();
+  delete[] MessageData;
+  MessageData = NewMsg;
+  Unlock();  
+
+  // Terminate if severity if FATAL  
+  if (Severity == FATAL) exit(EXIT_FAILURE);
+}
+
+
+// Send to central logging server with non-blocking command
+void EvidenceServer::SendToLog(const char *Format, ...) {
+
+  static char ErrorString[] = "vasprintf() failed in SendToLog()";
+  char *Buffer;
+  int Ret;
+
+  // Evaluate variable argument list
+  va_list ArgumentPointer;
+  va_start(ArgumentPointer, Format);
+  Ret = vasprintf(&Buffer, Format, ArgumentPointer);
+  va_end(ArgumentPointer);
+
+  // Send to logger
+  if (Ret != -1) {
+	DimClient::sendCommandNB("DColl/Log", Buffer);
+	free (Buffer);
+  }
+  else DimClient::sendCommandNB("DColl/Log", ErrorString);
+}
+
+
+// Get configuration data
+//
+// Program terminates if data is missing and no default given. Actual configuration
+// request will be made only if config file has modification since last request.
+// If called from infoHandler(), a non-blocking request will be made
+string EvidenceServer::GetConfig(string Item, string Default) {
+
+  string Result;
+  bool Blocking = false;
+  
+  // If up-to-date data in configuration list available, return this
+  Lock();
+  if ((List.count(Item) > 0) && (List[Item].Time >= ConfClass->ConfigTimeStamp)) Result = List[Item].Value;
+  if (Threads.count(pthread_self()) != 0) Blocking = true;
+  Unlock();
+  if (!Result.empty()) return Result;
+
+  // Blocking configuration request
+  if (Blocking) {
+	DimRpcInfo Config((char *) "ConfigRequest", NO_LINK);
+	Config.setData((char *) (Name + " " + Item).c_str());
+
+	// Check if successful
+	if (!EvidenceServer::ServiceOK(&Config)) {
+      if (Default.empty()) {
+		Message(FATAL, "Configuration server unreachable, can't retrieve '%s'", Item.c_str());
+	  }
+	  else Result = Default;
+	}
+	else {
+	  if (Config.getSize() <= 1) Result = Default;
+	  else Result = string(Config.getString(), Config.getSize()-1); // Retrieve string safely
+    }
+
+	// Update configuration map	
+	if (!Result.empty()) {
+	  Lock();
+	  List[Item].Value = Result;
+	  List[Item].Time = ConfClass->ConfigTimeStamp;
+	  Unlock();
+	}	
+  }
+
+  // Non-blocking configuration request
+  if (!Blocking) {
+ 	Lock();
+	if (List.count(Item) > 0) {
+	  ConfClass->RequestNB(Name + " " + Item);
+	  Result = List[Item].Value;
+	}
+	Unlock();
+  }
+
+  // Terminate if no configuration information found
+  if (Result.empty()) Message(FATAL, "Missing configuration data '%s'", Item.c_str());
+
+  return Result;
+}
+
+
+// Locking and unlocking functions.
+// Message() is not used to avoid infinite recursion
+void EvidenceServer::Lock() {
+
+  int Ret;
+
+  if ((Ret = pthread_mutex_lock(&EvidenceServer::Mutex)) != 0) {
+	printf("pthread_mutex_lock() failed in Lock() (%s)", strerror(Ret));
+	SendToLog("pthread_mutex_lock() failed in Lock() (%s)", strerror(Ret));
+	exit(EXIT_FAILURE);
+  }
+}
+
+void EvidenceServer::Unlock() {
+
+  int Ret;
+
+  if ((Ret = pthread_mutex_unlock(&EvidenceServer::Mutex)) != 0) {
+	printf("pthread_mutex_unlock() failed in Unlock() (%s)", strerror(Ret));
+	SendToLog("pthread_mutex_unlock() failed in Unlock() (%s)", strerror(Ret));
+	exit(EXIT_FAILURE);
+  }  
+}
+
+
+// ====== Static methods ======
+
+// Stub to call ConfigChanged() method of class as separate thread
+// Thread set is used to determine if blocking or non-blocking rpc is to be used
+void EvidenceServer::CallConfigChanged() {
+
+  EvidenceServer::Lock();
+  EvidenceServer::Threads.insert(pthread_self());
+  EvidenceServer::Unlock();
+
+  This->ConfigChanged();
+  
+  EvidenceServer::Lock();
+  EvidenceServer::Threads.erase(pthread_self());
+  EvidenceServer::Unlock();
+}
+
+
+// Signal handler (causes pause() and other syscalls to return)
+void EvidenceServer::SignalHandler(int Signal) {
+
+  static bool Called = false;
+
+  // At first invocation just request exit
+  if (!Called) {
+	Called = true;
+	This->ExitRequest = true;
+	return;
+  }
+
+  // If invoked twice, call exit()
+  This->Message(WARN, "Signal handler called again, invoking exit() (signal %d)", Signal);
+  exit(EXIT_FAILURE);
+}
+
+
+// C++ exception handler (derived from gcc __verbose_terminate_handler())
+void EvidenceServer::Terminate() {
+
+  ostringstream Msg;
+  static bool Terminating = false;
+
+  if (Terminating) {
+	Msg << This->Name << ": Terminate() called recursively, calling abort()";
+	printf("%s\n", Msg.str().c_str());
+	This->SendToLog(Msg.str().c_str());
+	abort();
+  }
+
+  Terminating = true;
+
+  // Make sure there was an exception; terminate is also called for an
+  // attempt to rethrow when there is no suitable exception.
+  type_info *Type = abi::__cxa_current_exception_type();
+  if (Type != NULL) {
+	int Status = -1;
+	char *Demangled = NULL;
+
+	Demangled = abi::__cxa_demangle(Type->name(), 0, 0, &Status);
+	Msg << "Terminate() called after throwing an instance of '" << (Status==0 ? Demangled : Type->name()) << "'";
+	free(Demangled);
+
+	// If exception derived from std::exception, more information.
+	try { __throw_exception_again; }
+	catch (exception &E) {
+	  Msg << " (what(): " << E.what() << ")";	  
+	}
+	catch (...) { }
+  }
+  else Msg << "Terminate() called without an active exception";
+
+  This->Message(FATAL, Msg.str().c_str());
+}
+
+
+// Translates DIM data safely to string (assures no invalid memory accesses are made)
+string EvidenceServer::ToString(char *Format, void *Data, int Size) {
+
+  ostringstream Text;
+  
+  // 'Message' service format handled here
+  if (strcmp(Format, "I:1;C") == 0 && Size >= (int) sizeof(struct Message)) {
+	struct Message *Msg = (struct Message *) Data;
+	// Safely extract string and limit to length of C string (padding might make it longer)
+	string MsgText(Msg->Text, Size-sizeof(struct Message));
+	Text << Msg->Severity << " " << MsgText.erase(strlen(MsgText.c_str()));
+
+	return Text.str();
+  }
+  
+  // Structure: print hex representation 
+  if (strlen(Format) != 1) {
+	for (int i=0; i<Size; i++) {
+	  Text << setw(2) << hex << *((char *) Data + i) << " ";
+	} 
+	return Text.str();
+  }
+
+  // String if format "C" and terminated with \0
+  if (strcmp(Format, "C") == 0 && Size > 0 && *((char *) Data+Size-1)=='\0') {
+	return string((char *) Data);
+  }
+
+  // Number array
+  int ElementSize;
+  switch (*Format) {
+    case 'C': ElementSize = sizeof(char);		break;
+    case 'I':
+    case 'L': ElementSize = sizeof(int);		break;
+    case 'S': ElementSize = sizeof(short);		break;
+    case 'F': ElementSize = sizeof(float);		break;
+    case 'D': ElementSize = sizeof(double);		break;
+    case 'X': ElementSize = sizeof(long long);	break;
+    default: return string();
+  }
+
+  for (int i=0; i<Size/ElementSize; i++) {
+	// Space between entries
+    if (i != 0) Text << " ";
+
+	// Translate data
+	switch (*Format) {
+      case 'C': Text << *((char *) Data + i);		break;
+      case 'I':
+      case 'L': Text << *((int *) Data + i);		break;
+      case 'S': Text << *((short *) Data + i);		break;
+      case 'F': Text << *((float *) Data + i);		break;
+      case 'D': Text << *((double *) Data + i);		break;
+      case 'X': Text << *((long long *) Data + i);	break;
+	}
+  }
+  
+  return Text.str();
+}
+
+
+// Checks if service contents indicates not available
+bool EvidenceServer::ServiceOK(DimInfo *Item) {
+
+  return !((Item->getSize() == strlen(NO_LINK)+1) &&  
+	  (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
+}
+
+bool EvidenceServer::ServiceOK(DimRpcInfo *Item) {
+
+  return !((Item->getSize() == strlen(NO_LINK)+1) &&  
+	  (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
+}
+
+bool EvidenceServer::ServiceOK(DimCurrentInfo *Item) {
+
+  return !((Item->getSize() == strlen(NO_LINK)+1) &&  
+	  (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
+}
+
+
+// Tokenize std::string using given delimeter list
+vector<string> EvidenceServer::Tokenize(const string &String, const string &Delimiters) {
+
+  vector<string> Tokens;
+  string::size_type Next, EndNext=0;
+    
+  while (EndNext != string::npos) {
+    // Find next token
+    Next = String.find_first_not_of(Delimiters, EndNext);
+    EndNext = String.find_first_of(Delimiters, Next);
+
+	// Stop if end of string reached
+	if (Next == string::npos) break;
+
+    // Add token to vector.
+    Tokens.push_back(String.substr(Next, EndNext - Next));
+  }
+  
+  return Tokens;
+}
+
+
+///////////////////////////
+// EvidenceHistory Class //
+///////////////////////////
+
+// Organisaztion of history buffer
+// F | T S D | T S D | 0 0 ......  | T S D | T S D | 0 -1
+//
+// F: Offset of oldest entry  T: Time  S: Size  D: Data
+// F, T, S: int
+
+// Marker for history buffer
+const struct EvidenceHistory::Item EvidenceHistory::WrapMark = {0, -1, {}};
+const struct EvidenceHistory::Item EvidenceHistory::EndMark = {0, 0, {}};
+
+// Constructor
+EvidenceHistory::EvidenceHistory(std::string Name):	Name(Name) {
+
+  Buffer = NULL;
+}
+
+// Destructor
+EvidenceHistory::~EvidenceHistory() {
+
+  delete[] Buffer;
+}
+
+// Requests service history (returns true if data received OK)
+bool EvidenceHistory::GetHistory() {
+
+  DimRpcInfo R((char *) "ServiceHistory", NO_LINK);
+  R.setData((char *) Name.c_str());
+
+  // Check if data OK
+  if (!EvidenceServer::ServiceOK(&R)) return false;
+  if (R.getSize() == 0) return false;
+  
+  // Copy data to buffer
+  delete[] Buffer;
+  BufferSize = R.getSize();
+  Buffer = new char [BufferSize];
+
+  memcpy(Buffer, R.getData(), BufferSize);
+  DataStart = Buffer + strlen(Buffer) + 1;
+  Rewind();
+  
+  return true;
+}
+
+// Returns next item in history buffer
+const struct EvidenceHistory::Item *EvidenceHistory::Next() {
+
+  if (Buffer == NULL) return NULL;
+
+  // Check for wrap around
+  if (memcmp(Pointer, &WrapMark, sizeof(WrapMark)) == 0) Pointer = (struct Item *) (DataStart + sizeof(int));
+  // Check if at end of ring buffer
+  if (memcmp(Pointer, &EndMark, sizeof(EndMark)) == 0) return NULL;
+
+  const struct Item *Ret = Pointer;
+  Pointer = (struct Item *) ((char *) (Ret + 1) + Ret->Size);
+
+  return Ret;
+}
+
+// Reset to start of buffer
+void EvidenceHistory::Rewind() {
+
+  if (Buffer != NULL) Pointer = (struct Item *) (DataStart + (*(int *) DataStart));
+}
+
+// Return DIM format string of service (NULL if no data)
+char *EvidenceHistory::GetFormat() {
+
+  return Buffer;
+}
Index: fact/Evidence/Evidence.h
===================================================================
--- fact/Evidence/Evidence.h	(revision 9834)
+++ fact/Evidence/Evidence.h	(revision 9834)
@@ -0,0 +1,121 @@
+#ifndef EVIDENCE_H_SEEN
+#define EVIDENCE_H_SEEN
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include <errno.h>
+#include <vector>
+#include <map>
+#include <set>
+
+#include <exception>
+#include <cxxabi.h>
+
+#include "dis.hxx"
+#include "dic.hxx"
+
+#define NO_LINK (char *) "__&DIM&NOLINK&__" // Data if no link available
+#define EVIDENCE_REVISION "$Revision$"
+
+void ConfigChanged();
+
+// Class declation of Evidence server
+class EvidenceServer: public DimServer {
+
+	// Internal class for configuration requests
+	class Config: public DimInfo, public DimCommand, public DimRpcInfo {
+
+		DimInfo *Service;
+		std::string CurrentItem;
+
+		void rpcInfoHandler();
+		void commandHandler();
+		void infoHandler();
+
+	  public:
+		Config();
+		~Config();
+
+		int ConfigTimeStamp;
+		void RequestNB(std::string);
+	};
+
+	struct Item {
+	  std::string Value;
+	  int Time;
+	};
+	std::map<std::string, struct Item> List;
+
+	struct Message {
+      int Severity;
+	  char Text[];
+	};
+
+	std::string Name;
+    DimService *MessageService;
+	struct Message *MessageData;
+	class Config *ConfClass;
+	static pthread_mutex_t Mutex;
+	static std::set<pthread_t> Threads;
+	static EvidenceServer *This;
+
+    static void SignalHandler(int); // static for signal()
+    static void Terminate();  		// static for set_terminate()
+	virtual void errorHandler(int, int, char *);
+	virtual void exitHandler(int);
+    static void CallConfigChanged();	// static for phread_create()
+
+  public:
+    EvidenceServer(std::string);
+	~EvidenceServer();
+
+	enum MessageType {INFO=0, WARN=1, ERROR=2, FATAL=3};
+
+	void Message(MessageType, const char *, ...);
+	static void SendToLog(const char *, ...);
+	std::string GetConfig(std::string, std::string = std::string());
+	virtual void ConfigChanged() {};
+	static void Lock();
+	static void Unlock();
+	static std::string ToString(char *, void *, int);
+	static bool ServiceOK(DimInfo *);
+	static bool ServiceOK(DimRpcInfo *);
+	static bool ServiceOK(DimCurrentInfo *);
+	static std::vector<std::string> Tokenize(const std::string &, const std::string & = " ");
+
+    bool ExitRequest;
+};
+
+// Class declaration of EvidenceHistory
+class EvidenceHistory {
+
+  public:  
+  	struct Item {
+	  int Time;
+	  int Size;
+	  char Data[]; // Size bytes follow
+	} __attribute__((packed));
+
+	static const struct Item WrapMark;
+	static const struct Item EndMark;
+
+  private:
+	std::string Name;
+	char *Buffer;
+	int BufferSize;
+	char *DataStart;
+	struct Item *Pointer;
+	
+  public:
+	EvidenceHistory(std::string);
+	~EvidenceHistory();	
+
+	bool GetHistory();
+	char *GetFormat();
+	const struct Item *Next();
+	void Rewind();
+};
+#endif
Index: fact/Evidence/History.cc
===================================================================
--- fact/Evidence/History.cc	(revision 9834)
+++ fact/Evidence/History.cc	(revision 9834)
@@ -0,0 +1,384 @@
+/********************************************************************\
+
+  History server of the Evidence Control System
+  
+  - Subscribes to all services and keeps a ring buffer for each service. 
+  - Data added to the buffer only if changed by minimum amount given by
+    configuration information. 
+  - The history is available via an rpc call.
+  - The history buffers are written to disk at program termination and
+    are tried to be read when adding a service.
+  - The buffer should hold at least REQUEST_NUM entries (of the size currently
+    handled in infoHandler()), but should not be larger than MAX_SIZE_KB. 
+  Oliver Grimm, June 2010
+
+\********************************************************************/
+
+#define SERVER_NAME "History"
+#include "Evidence.h"
+
+#include <string>
+#include <sstream>
+#include <map>
+#include <math.h>
+#include <float.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+using namespace std;
+
+const int MIN_SIZE_KB = 50;					// Min and max buffersize in kByte (> 3*sizeof(int) !)
+const string DEFAULT_MAX_SIZE_KB = "2000";
+const string DEFAULT_NUM_ENTRIES = "1000";	// Number of entries in each history buffer
+
+//
+// Class declaration
+//
+class History:	public DimRpc, public DimClient, public EvidenceServer {
+
+	struct Item {
+	  DimStampedInfo *DataItem;
+	  vector <char> Buffer;
+	  int Next;
+	  double LastValue;
+	  double MinAbsChange;
+	  string Format;
+	};
+	map<string, struct Item> Map;
+	
+	DimInfo *ServerList;
+	char *Directory;
+	string Change;
+
+    void infoHandler();
+    void rpcHandler();
+	void AddService(string, const char *);
+	void RemoveService(string);
+	off_t FileSize(FILE *);
+	FILE *OpenFile(string, const char *);
+   
+  public:
+    History(char *);
+    ~History();
+}; 
+
+
+// Constructor
+History::History(char *Dir): DimRpc("ServiceHistory", "C", "C"),
+							 EvidenceServer(SERVER_NAME),
+							 Directory(Dir) {
+
+  // Get/initialize configuration
+  Change = GetConfig("minchange", " ");
+  GetConfig("maxsize_kb", DEFAULT_MAX_SIZE_KB);
+  GetConfig("numentries", DEFAULT_NUM_ENTRIES);
+
+  // Subscribe to top-level server list
+  ServerList = new DimInfo((char *) "DIS_DNS/SERVER_LIST", NO_LINK, this);
+}
+
+
+// Destructor deletes all DIM subscriptions
+History::~History() {
+
+  delete ServerList;
+  while (Map.size() != 0) RemoveService((*Map.begin()).first);
+}
+
+
+// Implementation of data handling
+void History::infoHandler() {
+
+  DimInfo *I = getInfo();
+
+  // Check if service available
+  if (!ServiceOK(I)) return;
+
+  // ====== Part A: Handle service subscriptions ===
+  
+  // If service is DIS_DNS/SERVER_LIST, subscribe to all SERVICE_LIST services
+  if (strcmp(I->getName(), "DIS_DNS/SERVER_LIST") == 0) {	
+	char *Token = strtok(I->getString(), "+-!@");	
+	while (Token != NULL) {
+	  AddService(string(Token)+"/SERVICE_LIST", "C"); // 'add' also for '-' and '!'
+	  Token = strtok(NULL, "|"); // Skip server IP address
+	  Token = strtok(NULL, "@");
+	}	
+	return;
+  }
+
+  // If service is SERVICE_LIST, scan and subscribe/unsubscribe to services
+  if (strstr(I->getName(), "/SERVICE_LIST") != NULL) {
+
+	// Bug fix for empty SERVICE_LIST
+	if (strlen(I->getString()) == 0) {
+	  string Tmp(I->getName());
+	  RemoveService(I->getName());
+	  AddService(Tmp.c_str(), (char *) "C");
+	  return;
+	}
+
+	char *Type, *Name = strtok(I->getString(), "+-!|");
+	while (Name != NULL) {
+	  // Only consider DIM services (not commands and RPCs)
+      if (((Type = strtok(NULL, "\n")) != NULL) &&
+		  (strstr(Type, "|CMD") == NULL) && (strstr(Type, "|RPC") == NULL)) {
+	    if (*I->getString() == '-' || *I->getString() == '!') RemoveService(Name);
+		else {
+		  Type[strlen(Type)-1] = '\0'; // Isolate service format
+		  AddService(Name, Type);
+		}
+	  }
+	  Name = strtok(NULL, "|");
+	}
+	return;
+  }
+
+  // ====== Part B: Handle history service ===
+
+  char *Service = I->getName();
+
+  // Check if service known and ignore empty or illegal time stamped service
+  if (Map.count(Service) == 0 || I->getSize()==0 || I->getTimestamp()<=0) return;
+
+  // Resize buffer if necessary
+  int NEntries = atoi(GetConfig("numentries").c_str());
+  if (Map[Service].Buffer.size() < NEntries*I->getSize()) {
+	if (NEntries*I->getSize() < atoi(GetConfig("maxsize_kb").c_str())*1024) {
+	  Map[Service].Buffer.resize(NEntries*I->getSize());
+	}
+  }
+
+  // If data is number of single type, check minumum change before adding to history
+  if (strcmp(I->getFormat(), "C") != 0) {
+	// Calculate sum of all number in array
+	istringstream Text(EvidenceServer::ToString(I->getFormat(), I->getData(), I->getSize()));
+	double Num, Sum = 0;
+	while (Text.good()) {
+	  Text >> Num;
+	  Sum += fabs(Num);
+	}
+
+	// Minimum change?
+	if (fabs(Sum-Map[Service].LastValue) < fabs(Map[Service].MinAbsChange)) return;
+	Map[Service].LastValue = Sum;
+  }
+  
+  // Check if data fits into buffer
+  if (Map[Service].Buffer.size() < I->getSize() + sizeof(int)+ 2*sizeof(EvidenceHistory::Item)) return;
+
+  int Size = I->getSize() + 2*sizeof(EvidenceHistory::Item), Next = Map[Service].Next;
+  void *WrapPos = NULL;
+  char *Buffer = &Map[Service].Buffer[0];
+  int Oldest = *(int *) Buffer;
+
+  // Check if buffer wrap-around (write wrap mark after Oldest is adjusted)
+  if (Next + Size >= Map[Service].Buffer.size()) {
+    WrapPos = Buffer + Next;
+    Next = sizeof(int);
+  }
+
+  // Adapt pointer to oldest entry
+  while ((Oldest < Next + Size) && 
+  		 (Oldest + *((int *) (Buffer + Oldest) + 1) + 2*sizeof(int) > Next)) {
+	// Check for wrap-around
+	if (memcmp(Buffer + Oldest, &EvidenceHistory::WrapMark, sizeof(EvidenceHistory::WrapMark)) == 0) {
+	  Oldest = sizeof(int);
+	  continue;
+	}
+	// Check if end marker reached, then only one event fits buffer
+  	if (memcmp(Buffer + Oldest, &EvidenceHistory::EndMark, sizeof(EvidenceHistory::EndMark)) == 0) {
+	  Oldest = Next;
+	  break;
+	}
+	// Move to next entry
+    Oldest += *((int *) (Buffer + Oldest) + 1) + 2*sizeof(int);
+  }
+  // Update pointer in buffer
+  *(int *) Buffer = Oldest;
+
+  // Write wrap mark if necessary
+  if (WrapPos != NULL) memcpy(WrapPos, &EvidenceHistory::WrapMark, sizeof(EvidenceHistory::WrapMark));
+
+  // Copy data into ring buffer
+  *((int *) (Buffer + Next)) = I->getTimestamp();
+  *((int *) (Buffer + Next + sizeof(int))) = I->getSize();
+  memcpy(Buffer + Next + 2*sizeof(int), I->getData(), I->getSize());
+
+  // Adjust pointer for next entry and write end marker to buffer
+  Next += I->getSize() + sizeof(EvidenceHistory::Item);
+  memcpy(Buffer + Next, &EvidenceHistory::EndMark, sizeof(EvidenceHistory::EndMark));
+  
+  Map[Service].Next = Next;
+}
+
+
+// Implementation of history buffer distribution
+void History::rpcHandler() {
+
+  string Name = ToString((char *) "C", getData(), getSize());
+  
+  // Search for history buffer in memory
+  if (Map.count(Name) == 1) {
+	char *Buffer = new char [Map[Name].Format.size()+1+Map[Name].Buffer.size()];
+	strcpy(Buffer, Map[Name].Format.c_str());
+	memcpy(Buffer+Map[Name].Format.size()+1, &Map[Name].Buffer[0], Map[Name].Buffer.size());
+	setData((void *) Buffer, Map[Name].Format.size()+1+Map[Name].Buffer.size());
+	delete[] Buffer;
+	return;
+  }
+
+  // Try to open history file if not found in memory
+  FILE *File = OpenFile(Name, "rb");
+  if (File == NULL) {
+    setData(NULL, 0);
+	return;
+  }
+
+  // Read history file and send to client (data will contain format string and history)
+  off_t Size = FileSize(File);
+  if (Size != -1) {
+	char *Buffer = new char [Size-sizeof(int)];
+	fseek(File, sizeof(int), SEEK_SET); // Skip 'Next' pointer
+	if ((fread(Buffer, sizeof(char), Size-sizeof(int), File) != Size-sizeof(int)) || (ferror(File) != 0)) {
+	  Message(WARN, "Error reading history file '%s' in rpcHandler()", Name.c_str());
+	  setData(NULL, 0);		// Default response
+	}
+	else setData((void *) Buffer, Size);
+	delete[] Buffer;
+  }
+  
+  if (fclose(File) != 0) Message(WARN, "Error closing history file '%s' in rpcHandler()", Name.c_str());
+}
+
+
+//
+// Add service to watch list
+//
+void History::AddService(string Name, const char *Format) {
+
+  // Return if already subscribed to this service
+  if (Map.count(Name) != 0) return;
+
+  // Create new service subscription
+  Map[Name].LastValue =  DBL_MAX;
+  Map[Name].Format = Format;
+  Map[Name].MinAbsChange = 0.0;
+  
+  // Set minimum required change if given in configuratrion
+  size_t Pos = Change.find(Name+":");
+  if (Pos != string::npos) Map[Name].MinAbsChange = atof(Change.c_str() + Pos + Name.size() + 1);
+
+  // Load history buffer from file if existing
+  FILE *File = OpenFile(Name, "rb");
+  off_t Size;
+
+  if (File != NULL && (Size = FileSize(File)) != -1) {
+
+    // If current buffer too small, resize
+	if (Size > Map[Name].Buffer.size()) Map[Name].Buffer.resize(Size);
+
+	// Read next pointer
+    fread(&Map[Name].Next, sizeof(Map[Name].Next), 1, File);
+	// Skip format string
+	while (fgetc(File) != 0 && feof(File) == 0) {}
+	// Read buffer
+    fread(&Map[Name].Buffer[0], sizeof(char), Size, File);
+
+	if (ferror(File) != 0) {
+	  Message(WARN, "Error reading history file '%s' in AddService()", Name.c_str());
+	  Map[Name].Buffer.clear();
+	}
+    if (fclose(File) != 0) Message(WARN, "Error closing history file '%s' in AddService()", Name.c_str());;
+  }
+
+  // If no buffer loaded, allocate empty buffer
+  if (Map[Name].Buffer.empty()) {
+	Map[Name].Buffer.resize(MIN_SIZE_KB*1024);
+	memset(&Map[Name].Buffer[0], 0, Map[Name].Buffer.size());
+	*(int *) &Map[Name].Buffer[0] = 4;
+	Map[Name].Next = 4;
+  }
+  
+  // Subscribe to service
+  Map[Name].DataItem = new DimStampedInfo(Name.c_str(), NO_LINK, this);
+}
+
+
+//
+// Remove service from watch list
+//
+void History::RemoveService(string Name) {
+
+  // Check if actually subscribed to service  
+  if (Map.count(Name) == 0) return;
+
+  // Delete subscription first so handler and not called anymore
+  delete Map[Name].DataItem;
+
+  // Save history buffer
+  FILE *File = OpenFile(Name, "wb");
+  if (File != NULL) {
+    fwrite(&Map[Name].Next, sizeof(Map[Name].Next), 1, File);					// Next pointer
+    fwrite(Map[Name].Format.c_str(), Map[Name].Format.size()+1, 1, File);		// Format
+    fwrite(&Map[Name].Buffer[0], sizeof(char), Map[Name].Buffer.size(), File);	// Buffer
+
+	// If error, try to delete (possibly erroneous) file 
+	if (ferror(File) != 0) {
+	  if (remove(Name.c_str()) == -1) Message(WARN, "Error writing history file '%s' in RemoveService(), could also not delete file", Name.c_str());
+	  else Message(WARN, "Error writing history file '%s' in RemoveService(), deleted file", Name.c_str());
+	}
+    if (fclose(File) != 0) Message(WARN, "Error closing history file '%s' in RemoveService()", Name.c_str());;
+  }
+
+  Map.erase(Name);
+}
+
+//
+// Determine size of file in kB
+//
+off_t History::FileSize(FILE *File) {
+
+  struct stat FileStatus;
+
+  if (fstat(fileno(File), &FileStatus) == -1) {
+     Message(WARN, "Could not determine size of file (%s)", strerror(errno));
+	 return -1;
+  }
+
+  return FileStatus.st_size;
+}
+
+//
+// Open file for service history
+//
+FILE *History::OpenFile(string Service, const char *Mode) {
+
+  // Create directory if not yet existing
+  if(mkdir(Directory, S_IRWXU|S_IRWXG)==-1 && errno!=EEXIST) return NULL;
+
+  // Replace all '/' and non-graphical characters by '_' in string and open file
+  for (int i=0; i<Service.size(); i++) {
+	if (Service[i] == '/') Service[i] = '_';
+	if (isgraph(Service[i]) == 0) Service[i] = '_';
+  }
+
+  return fopen((string(Directory) + "/" + Service).c_str(), Mode);
+}
+
+//	    
+// Main program
+//
+int main(int argc, char *argv[]) {
+
+  if (argc != 2) {
+	printf("Usage: %s <History-Directory>\n", argv[0]);
+	exit(EXIT_FAILURE);
+  }
+
+  // Static ensures calling of destructor by exit()
+  static History Hist(argv[1]);
+  
+  // Sleep until signal caught
+  while (!Hist.ExitRequest) pause();
+}
Index: fact/Evidence/Makefile
===================================================================
--- fact/Evidence/Makefile	(revision 9834)
+++ fact/Evidence/Makefile	(revision 9834)
@@ -0,0 +1,30 @@
+CC=g++
+
+PROG1=Config
+PROG2=DColl
+PROG3=Alarm
+PROG4=Bridge
+PROG5=History
+
+CPPFLAGS += -I$(DIMDIR)/dim/
+LDLIBS += -lpthread $(DIMDIR)/linux/libdim.a
+
+all: $(PROG1) $(PROG2) $(PROG3) $(PROG4) $(PROG5)
+	 
+$(PROG1): $(PROG1).o Evidence.o
+
+$(PROG2): $(PROG2).o Evidence.o
+
+$(PROG3): $(PROG3).o Evidence.o
+
+$(PROG4): $(PROG4).o Evidence.o
+
+$(PROG5): $(PROG5).o Evidence.o
+
+clean:
+	@rm -f $(PROG1) $(PROG1).o
+	@rm -f $(PROG2) $(PROG2).o
+	@rm -f $(PROG3) $(PROG3).o
+	@rm -f $(PROG4) $(PROG4).o
+	@rm -f $(PROG5) $(PROG5).o
+	@rm -f Evidence.o	
Index: fact/Evidence/readme.txt
===================================================================
--- fact/Evidence/readme.txt	(revision 9834)
+++ fact/Evidence/readme.txt	(revision 9834)
@@ -0,0 +1,49 @@
+
+This directory contains the backbone of the control system. See directory Doc for documentation.
+
+
+Version history
+---------------
+
+19/5/2010	Service histories now available via DimRpc from DColl, not via .hist service
+			When regular expression compiling results in error, State is set to ERROR, not
+			FATAL. The erroneous expression is ignored in the following.
+25/5/2010	Service history remains available if service itself become unavailable. If not
+			yet in memory, reading from file is tried. Improved error handling of
+			history files.
+28/5/2010	Changed name of 'State' service to 'Message' to better reflect its functionality.
+			Added client information to log file entries.			
+30/5/2010	Created Bridge server that repeats services from one DNS to another.
+			Service quality now also written to slow data file.
+31/5/2010	Configuration file format follows semi-standard INI format.
+7/6/2010	Separated History service from DColl. History format changed, now includes
+			service format (allows history access also when service is unavailable).
+11/6/2010	Bridge does not forward history service
+17/6/2010	Added SendToLog() method. Changed severity encoding of Message service to
+			use standard DIM structure of format "I:1;C"
+18/6/2010	Alarm server configuration accepts now one email address per server. A new
+			service for each observed server SERVERNAME/AlarmLevel contains the highest
+			level that occurred in the past. Reset of alarm level only via a DIM command.
+19/6/2010	ToString() now returns std::string
+23/6/2010	GetConfig() returns std::string. Non-blocking configuration request in case
+			GetConfig() not called from main thread. Access to configuration information
+			internally mutex protected.
+24/6/2010	Workaround for erroneous /SERVICE_LIST updates. Added static tokenize method to
+			Evidence class.
+30/6/2010	Made Lock()/Unlock() publicly available (mutex of type PTHREAD_MUTEX_ERRORCHECK),
+			in case signal is set with ActivateSignal() this signal will be blocked while locked.
+			Implemented experimental automatic full configuration tracking for Bridge.
+7/7/2010	All commandHandler() and rpcHandler() safely translates data into string
+			using ToString(). EvidenceServer class constructor now takes std::string as argument.
+			Removed default configuration file from Config.
+19/7/2010	Added documentation. Replaced std::vector by std::map at several locations. Access to
+			class-wide pointer in Message() protected by mutex (currently the memory of the
+			second-last message will not be freed correctly if severity is FATAL). Added
+			History server configuration parameters to adjust buffer size.
+20/7/2010	SendToLog() does not call Message() in case of error, but writes error message to log.
+21/7/2010	Lock()/Unlock() do not report errors via Message(), but print to console and use 
+			SendToLog(). That avoids a recursion problem since Message() also uses locking.
+26/7/2010	General command '/ResetMessage' will reset message text and severity. This feature is
+			used by Alarm if it receives a command to reset an alarm level.
+			ConfigChanged() is called as separate thread when configuration file changes. Thread ID
+			is checked in GetConfig() and also from this thread it will make blocking requests.
