Index: /Evidence/Alarm/Alarm.cc
===================================================================
--- /Evidence/Alarm/Alarm.cc	(revision 141)
+++ /Evidence/Alarm/Alarm.cc	(revision 142)
@@ -5,8 +5,10 @@
   - Checks periodically if all required servers are up
     (later it should try to start them if not)
-  - Listens to the 'Status' service of each server. The server state 
-    is published as a DIM service.
+  - Listens to the 'Status' service of each server.
+  - A text describing the state of all servers is published as DIM service.
+    The states are described in StateString[].
+  - A master alarm (indicating most severe of individual alarms) is published. 
     
-  Oliver Grimm, September 2009
+  Oliver Grimm, January 2010
 
 \********************************************************************/
@@ -14,4 +16,8 @@
 #define SERVER_NAME "Alarm"
 #include "../Evidence.h"
+
+#define SUMMARYSIZE 10000	// Bytes for alarm summary text
+
+const char* StateString[] = {"OK", "WARN", "ERROR", "FATAL", "UNAVAILABLE"};
 
 //
@@ -21,4 +27,5 @@
     
     DimStampedInfo **StatusService;
+
     void infoHandler();
 
@@ -26,9 +33,15 @@
     AlarmHandler();
     ~AlarmHandler();
-    
+
+	DimService *Summary, *Master;
+	
+	char *AlarmSummary;
+	int MasterAlarm;
+	int *State;    
     char **Server;
     unsigned int NumServers;
-    char *StateString;
-    char *ServerList;  
+    char *ServerList; 
+	
+	void UpdateAlarmSummary();
 }; 
 
@@ -36,5 +49,12 @@
 AlarmHandler::AlarmHandler(): EvidenceServer(SERVER_NAME) {
 
+  AlarmSummary = new char [SUMMARYSIZE];
+  MasterAlarm = 0;
+  
   char *ServerNames = GetConfig(SERVER_NAME " servers");
+
+  // Create DIM services
+  Summary = new DimService(SERVER_NAME"/Summary", AlarmSummary);
+  Master = new DimService(SERVER_NAME"/MasterAlarm", MasterAlarm);
 
   // Copy original list of servers to observe
@@ -53,4 +73,5 @@
   // Subscribe with handler to 'Status' service of all servers
   StatusService = new DimStampedInfo* [NumServers];
+  State = new int [NumServers];
   
   for (int i=0; i<NumServers; i++) {
@@ -58,12 +79,9 @@
     strcpy(Buffer, Server[i]);
     strcat(Buffer, "/Status");
-    StatusService[i] = new DimStampedInfo(Buffer, 0, this);
-    printf("Subscribed to %s\n", Buffer);
-    delete[] Buffer; 
+    StatusService[i] = new DimStampedInfo(Buffer, NO_LINK, this);
+    delete[] Buffer;
+	
+	State[i] = 0;
   }
-  
-  StateString = new char [NumServers+1];
-  for (int i=0; i<NumServers; i++) StateString[i] = '1';
-  StateString[NumServers] = '\0';
 }
 
@@ -73,28 +91,52 @@
   for (int i=0; i<NumServers; i++) delete StatusService[i];
   delete[] StatusService;
+  delete Master;
+  delete Summary;
+  delete[] State;
   delete[] Server;
   delete[] ServerList;
+  delete[] AlarmSummary;
 }
 
-// Print messages of status changes to screen
+// Print messages of status changes to screen and update status string
 void AlarmHandler::infoHandler() {
 
-  // Ignore empty messages
-  if (strlen(getInfo()->getString()) == 0) return;
-  
-  // Print message
-  time_t RawTime = getInfo()->getTimestamp();
-  struct tm *TM = localtime(&RawTime);
-  printf("%s (%02d:%02d:%02d): %s\n", getInfo()->getName(), TM->tm_hour,
-	TM->tm_min, TM->tm_sec, getInfo()->getString());
+  // Identify status service
+  for (int i=0; i<NumServers; i++) if (getInfo() == StatusService[i]) {
 
-  // Update status string
-  for (int i=0; i<NumServers; i++) {
-    if (strcmp(getInfo()->getName(),Server[i]) == 0) {
-      StateString[i] = '2';
-    }
+	// Ignore DIS_DNS (has no status service)
+	if (strcmp(getInfo()->getName(),"DIS_DNS/Status") == 0) return;
+	
+	// Update State if server is unavailable or with current severity of status  
+	if (getInfo()->getSize()==strlen(NO_LINK)+1 &&
+		strcmp(getInfo()->getString(), NO_LINK)==0) State[i] = 4;
+	else {
+	  State[i] = *(getInfo()->getString()+strlen(getInfo()->getString())+2);
+
+	  // Print message
+	  time_t RawTime = getInfo()->getTimestamp();
+	  struct tm *TM = localtime(&RawTime);
+	  printf("%s (%02d:%02d:%02d): %s\n", getInfo()->getName(), TM->tm_hour,
+		TM->tm_min, TM->tm_sec, getInfo()->getString());	  
+	}
+	UpdateAlarmSummary();
   }  
 }
 
+
+// Update alarm status summary
+void AlarmHandler::UpdateAlarmSummary() {
+  
+  int Offset = 0;
+  MasterAlarm = 0;
+   
+  for (int i=0; i<NumServers; i++) {
+    snprintf(AlarmSummary+Offset, SUMMARYSIZE-Offset, "%s: %s\n", Server[i], StateString[State[i]]);
+	Offset += strlen(AlarmSummary+Offset);
+	if (State[i] > MasterAlarm) MasterAlarm = State[i];
+  }
+  Summary->updateService();
+  Master->updateService();
+}
 
 //	    
@@ -113,8 +155,4 @@
   unsigned int Period = atoi(Alarm.GetConfig(SERVER_NAME " period"));
 
-  // Create DIM services
-  static DimService ServerState(SERVER_NAME"/State", Alarm.StateString);
-  static DimService ServerList(SERVER_NAME"/Servers", Alarm.ServerList);
-
   // Check periodically if servers are up
   while(!EvidenceServer::ExitRequest) {
@@ -125,12 +163,8 @@
         if (strcmp(ServerName, Alarm.Server[i]) == 0) Exists = true;
       }
-      if (!Exists) {
-        Alarm.Msg(Alarm.WARN, "Server %s unavailable", Alarm.Server[i]);
-	Alarm.StateString[i] = '0';
-      }
-      else if (Alarm.StateString[i] == '0') Alarm.StateString[i] = '1';
+      if (!Exists) Alarm.State[i] = 4;
     }
     
-    ServerState.updateService(Alarm.StateString);
+    Alarm.UpdateAlarmSummary();
     sleep(Period);
   }
Index: /Evidence/Config/Config.cc
===================================================================
--- /Evidence/Config/Config.cc	(revision 141)
+++ /Evidence/Config/Config.cc	(revision 142)
@@ -5,8 +5,10 @@
   - The configuration file is opened without buffering to catch changes
     without closing/opening.
-  - If a configuration file change is detected through inotify, the service "Config/Modified"
-    is updated (it contains no data) to inform applications. 
-  - The employed line buffer has conservatively at least the size of the configuration file. If
-    needed, it will be enlarged.
+  - 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" is updated with the current UNIX time to inform applications.
+	The initial value of the server is the last file modification time. 
+  - The employed line buffer has conservatively at least the size of the
+    configuration file. If needed, it will be enlarged.
          
   Oliver Grimm, November 2009
@@ -14,8 +16,9 @@
 \********************************************************************/
 
-#define CONFIG_FILE "configuration.txt"
+#define DEFAULT_CONFIG "configuration.txt"
 #define SERVER_NAME "Config"
 
 #include "../Evidence.h"
+#include <ctype.h>
 #include <sys/stat.h>
 #include <sys/inotify.h>
@@ -31,5 +34,6 @@
     unsigned int BufferLength;
     DimService *ConfigModified;
-
+    int ModifyTime;
+	
     void rpcHandler();
   
@@ -45,15 +49,21 @@
 	DimRpc("ConfigRequest", "C", "C"), EvidenceServer(SERVER_NAME) {
 
-  // Create DIM service to indicate changes of configuration file
-  ConfigModified = new DimService (SERVER_NAME"/Modified", (char *) "");
-
   // Open configuration file
   if ((File = fopen(Filename, "r")) == NULL) {
-    Msg(FATAL, "Could not open configuration file '%s' (%s)\n", Filename, strerror(errno));
+    State(FATAL, "Could not open configuration file '%s' (%s)\n", Filename, strerror(errno));
   }
 
+  // Create DIM service to indicate changes of configuration file
+  struct stat Stat;
+  if (stat(Filename, &Stat) == -1) {
+    State(WARN, "Could not read last modification time of configuration file '%s' (%s)", Filename, strerror(errno));
+	ModifyTime = 0;
+  }
+  else ModifyTime = Stat.st_mtime;   
+  ConfigModified = new DimService (SERVER_NAME"/ModifyTime", ModifyTime);
+
   // Disable buffering, so file modifications are immediately seen
-  if(setvbuf(File, NULL, _IONBF, 0) != 0) {
-    Msg(WARN, "Error setting configuration file '%s' to unbuffered mode.\n", Filename);
+  if (setvbuf(File, NULL, _IONBF, 0) != 0) {
+    State(WARN, "Error setting configuration file '%s' to unbuffered mode", Filename);
   }
     
@@ -77,5 +87,5 @@
   // Check if Buffer[] is large enough to hold full file, enlarge if necessary
   if (fstat(fileno(File), &FileStatus) == -1) {
-     Msg(ERROR, "Could not determine size of configuration file to allocate buffer (%s)\n", strerror(errno));
+     State(FATAL, "Could not determine size of configuration file to allocate buffer (%s)", strerror(errno));
   }
   else if(BufferLength < FileStatus.st_size) {
@@ -105,11 +115,16 @@
     if(Token1==NULL || Token2==NULL || Token3==NULL) continue;
 
-    // Check for match and then send data
+    // Check for match and then send data (removing trainlin whitespace)
     if (strstr(Request, Token1)!=NULL && strstr(Request, Token2)!=NULL) {
+	  while (isspace(*Token3) != 0) Token3++;
       setData(Token3);
       break;
     }
   }
-  Msg(INFO, "Client '%s' (ID %d) requested '%s'. Send '%s'.",
+  
+  // If configuration data not found, send empty string
+  if (feof(File)!=0) setData((char *) "");
+  
+  State(INFO, "Client '%s' (ID %d) requested '%s'. Send '%s'.",
 		DimServer::getClientName(),
 		DimServer::getClientId(), 
@@ -120,4 +135,5 @@
 void EvidenceConfig::ConfigChanged() {
 
+  ModifyTime = time(NULL);
   ConfigModified->updateService();
 }
@@ -126,7 +142,7 @@
 // Declaring class static ensures destructor is called when exit() is invoked 
 //
-int main() {
+int main(int argc, char *argv[]) {
         
-  static EvidenceConfig Config(CONFIG_FILE);
+  static EvidenceConfig Config(argc<2 ? DEFAULT_CONFIG : argv[1]);
 
   int Notify;
@@ -134,8 +150,8 @@
   
   if ((Notify = inotify_init()) == -1) {
-    Config.Msg(EvidenceConfig::WARN, "inotify_init() failed, cannot monitor changes of configuration file (%s)\n", strerror(errno));
+    Config.State(EvidenceConfig::WARN, "inotify_init() failed, cannot monitor changes of configuration file (%s)\n", strerror(errno));
   }
-  else if (inotify_add_watch(Notify, CONFIG_FILE, IN_MODIFY) == -1) { 
-      Config.Msg(EvidenceConfig::WARN, "Could not set inotify watch on configuration file (%s)\n", strerror(errno));
+  else if (inotify_add_watch(Notify, argc<2 ? DEFAULT_CONFIG : argv[1], IN_MODIFY) == -1) { 
+      Config.State(EvidenceConfig::WARN, "Could not set inotify watch on configuration file (%s)\n", strerror(errno));
 	  close(Notify);
 	  Notify = -1;
Index: /Evidence/Config/Makefile
===================================================================
--- /Evidence/Config/Makefile	(revision 141)
+++ /Evidence/Config/Makefile	(revision 142)
@@ -2,5 +2,5 @@
 
 PROG=Config
-CPPFLAGS += -I../DIM/ 
+CPPFLAGS += -I../DIM/
 LDLIBS += -lpthread
 
Index: /Evidence/DColl/DColl.cc
===================================================================
--- /Evidence/DColl/DColl.cc	(revision 141)
+++ /Evidence/DColl/DColl.cc	(revision 142)
@@ -5,7 +5,10 @@
   - DColl subscribes to all services given by the configuration
     server and writes these to the data file at every update.
-  - For each service, it creates a new service with '.hist' appended that
-    contains a history of events kept within a ring buffer. Each entry will
-	be the result of a conversion to double of the text written to the data file.
+  - One data file per day is generated, with roll-over at 13:00 local time.
+  - For each service, it creates a new service with '.hist' appended
+	that contains a history of events kept within a ring buffer. Each
+	entry will be the result of a conversion to double of the text 
+	written to the data file. Only if the new value has changed by a
+	minimum amout it will be added to the ring buffer.
   - The command 'DColl/Log' writes the associated string to the log 
     file specified in the configuration.
@@ -17,31 +20,50 @@
 #define SERVER_NAME "DColl"
 
+#define DATE_ROLLOVER 12 // localtime hour after which next date is used
+
 #include "../Evidence.h"
-
-#define NO_LINK "__&DIM&NOLINK&__" // for checking if DIMserver is alive
+#include <math.h>
+#include <float.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <regex.h>
 
 //
 // Class declaration
 //
-class DataHandler:	public DimClient, public DimCommand,
-					public DimBrowser, public EvidenceServer {
-  private:
-    pthread_mutex_t DataMutex;
-    pthread_mutex_t LogMutex;
-
+class DataHandler:	public DimClient, public DimBrowser,
+					public EvidenceServer {
+
+	struct Item {
+	  DimStampedInfo *DataItem;
+      DimService *HistService;
+	  struct EvidenceHistoryItem *HistBuffer;
+	  int HistPointer;
+	  double LastValue;
+	  double MinAbsChange;
+	} *List;
+
+	DimCommand *LogCommand;
+		
     unsigned int NumItems;
     FILE *DataFile;
     FILE *LogFile;
-    char *Filename;
-
-    DimStampedInfo **DataItem;
-    DimService **HistService;	    
+	float DataSizekB, LogSizekB;
+	int DataSizeLastUpdate, LogSizeLastUpdate;
+	char *DataDir;
+    DimService *LogSizeService, *DataSizeService;
     int HistSize;
-    struct EvidenceHistoryItem **HistBuffer;
-	int *HistPointer;
-    	
+	int SizeUpdateDelay;
+	int TimeForNextFile;
+	
+	int RegExCount;
+	regex_t *RegEx;
+	double *MinChange;
+	
     void infoHandler();
     void commandHandler();
-    bool ReallocMem(void *&, int);
+	void AddService(char *);
+	float FileSize(FILE *);
 	    
   public:
@@ -53,92 +75,75 @@
 // Constructor
 //
-DataHandler::DataHandler(): DimCommand((char *) "DColl/Log", (char *) "C"), EvidenceServer(SERVER_NAME) {
+DataHandler::DataHandler(): EvidenceServer(SERVER_NAME) {
 
   // Initialization to prevent freeing unallocated memory 
   DataFile = NULL;
-  Filename = NULL;
-
-  DataItem = NULL;
-  HistService = NULL;
-  HistBuffer = NULL;
-  HistPointer = NULL;
-  
+  LogFile = NULL;
+  List = NULL;
+  LogSizeService = NULL;
+  DataSizeService = NULL;
+  
+  DataSizeLastUpdate = 0;
+  LogSizeLastUpdate = 0;
+  TimeForNextFile = 0;
+
   // Request configuration data
-  char *Items = GetConfig(SERVER_NAME " items");
-  char *DataDir = GetConfig(SERVER_NAME " datadir");
+  char *Change = GetConfig(SERVER_NAME " minchange");
+  DataDir = GetConfig(SERVER_NAME " datadir");
   char *Logname = GetConfig(SERVER_NAME " logfile");
+  SizeUpdateDelay = atoi(GetConfig(SERVER_NAME " sizeupdate"));
   HistSize = atoi(GetConfig(SERVER_NAME " histsize"));
   if (HistSize < 1) HistSize = 1; // Minimum one items
    
-  // Create mutex for thread synchronization 
-  if ((errno=pthread_mutex_init(&DataMutex, NULL)) != 0) {
-    Msg(FATAL, "pthread_mutex_init() failed for data file (%s)", strerror(errno));
-  }
-  if ((errno=pthread_mutex_init(&LogMutex, NULL)) != 0) {
-    Msg(FATAL, "pthread_mutex_init() failed for log file (%s)", strerror(errno));
-  }
-
-  // Interpret DIM services to observe and prepare history buffer
-  char *Buf, *ServiceName, *Format;
-  int NumServices;
-  NumItems = 0;
-  char *NextToken = strtok(Items, " \t");
-  while (NextToken != NULL) {
-    NumServices = getServices(NextToken);
-    for (int j=0; j<NumServices; j++)  {
-	  if (getNextService(ServiceName, Format) == DimSERVICE) {
-
-	    // Check if already subsubed to this service
-		for (int i=0; i<NumItems; i++) {
-		  if(strcmp(ServiceName, DataItem[i]->getName()) == 0) continue;
-		}
-		
-	    // Increase capacity of arrays
-	    if (!ReallocMem((void *&) DataItem, NumItems+1)) continue;
-	    if (!ReallocMem((void *&) HistService, NumItems+1)) continue;
-	    if (!ReallocMem((void *&) HistBuffer, NumItems+1)) continue;
-	    if (!ReallocMem((void *&) HistPointer, NumItems+1)) continue;
-
-  		// Subscribe to service
-    	DataItem[NumItems] = new DimStampedInfo(ServiceName, (char *) NO_LINK, this);
-
-    	// Create history service
-    	HistBuffer[NumItems] = new struct EvidenceHistoryItem [HistSize];
-		memset(HistBuffer[NumItems], 0, HistSize*sizeof(EvidenceHistoryItem));
-		HistPointer[NumItems] = 0;
-
-    	if (asprintf(&Buf, "%s.hist", ServiceName) == -1) {
-    	  Msg(ERROR, "Could not create Hist service for %s because asprintf() failed", ServiceName);
-    	}
-    	else {
-		  HistService[NumItems] = new DimService (Buf, (char *) "C",
-	  							HistBuffer[NumItems], HistSize*sizeof(EvidenceHistoryItem));
-    	  free(Buf);
-		}
-		NumItems++;
-	  }
-	}
-    NextToken = strtok(NULL, " \t");
-  }
-
-  // Open data file
-  time_t rawtime = time(NULL);
-  struct tm * timeinfo = gmtime(&rawtime);
-  if(timeinfo->tm_hour >= 13) rawtime += 12*60*60;
-  timeinfo = gmtime(&rawtime);
-
-  if (asprintf(&Filename, "%s/%d%02d%02d.slow", DataDir, timeinfo->tm_year+1900, timeinfo->tm_mon+1, timeinfo->tm_mday) == -1) {
-    Filename = NULL;
-    Msg(FATAL, "Could not create filename with asprintf()(%s)", strerror(errno));
-  }
-  if ((DataFile = fopen(Filename, "a")) == NULL) {
-    Msg(FATAL, "Could not open data file '%s' (%s)", Filename, strerror(errno));
-  }
-  
   // Open log file
   if ((LogFile = fopen(Logname, "a")) == NULL) {
-    Msg(FATAL, "Could not open log file '%s' (%s)", Logname, strerror(errno));
-  }
-
+    State(FATAL, "Could not open log file '%s' (%s)", Logname, strerror(errno));
+  }
+
+  // Provide logging command   
+  LogCommand = new DimCommand("DColl/Log", (char *) "C", this);
+              
+  // Create services for file sizes
+  DataSizekB = 0;
+  DataSizeService = new DimService(SERVER_NAME "/DataSizekB", DataSizekB);
+  
+  LogSizekB = FileSize(LogFile);
+  LogSizeService = new DimService(SERVER_NAME "/LogSizekB", LogSizekB);
+
+  // Count how many minimum change value regular expressions are present
+  RegExCount = 0;
+  char *Token = strtok(Change, "\t ");
+  while (Token != NULL) {
+	RegExCount++;
+	Token = strtok(NULL, "\t ");
+  }
+  
+  // Allocate memory for regular expressions and minimum change values
+  RegEx = new regex_t[RegExCount];
+  MinChange = new double [RegExCount];
+
+  // Compile regular expressions
+  int Pos = 0;
+  for (int i=0; i<RegExCount; i++) {
+    int Len = strlen(Change+Pos) + 1;
+    Token = strtok(Change + Pos, ": \t");
+
+    int Ret = regcomp(&RegEx[i], Token, REG_EXTENDED|REG_NOSUB);
+	if (Ret != 0) {
+	  char ErrMsg[200];
+	  regerror(Ret, &RegEx[i], ErrMsg, sizeof(ErrMsg));
+	  State(WARN, "Error compiling regular expression '%s' (%s)", Token, ErrMsg);
+	}
+	else {
+	  if ((Token=strtok(NULL, "")) != NULL) MinChange[i] = atof(Token);
+	  else MinChange[i] = 0;
+	}
+	Pos += Len;
+  }
+
+  // Subscribe to list of servers at DIS_DNS 
+  AddService((char *) "DIS_DNS/SERVER_LIST");
+
+  DimClient::sendCommand("DColl/Log", SERVER_NAME" *** Logging started ***");
 }
 
@@ -148,27 +153,31 @@
 DataHandler::~DataHandler() {
 
+  // Delete DIM services and command first so handlers and not called anymore
+  for (int i=0; i<NumItems; i++) {
+	delete List[i].HistService;
+    delete List[i].DataItem;
+    delete[] List[i].HistBuffer;
+  }
+  free(List);
+
+  //delete LogSizeService; // These create segmentation faults?!
+  //delete DataSizeService;
+
+  delete LogCommand;
+
   // Close files
-  if (LogFile != NULL && fclose(LogFile) != 0) {
-	Msg(ERROR, "Could not close log file (%s)", strerror(errno));
+  if (LogFile != NULL) if (fclose(LogFile) != 0) {
+	State(ERROR, "Could not close log file (%s)", strerror(errno));
   }
   if (DataFile != NULL && fclose(DataFile) != 0) {
-	Msg(ERROR, "Error: Could not close data file (%s)", strerror(errno));
-  }	
-
-  // Free all memory
-  for (int i=0; i<NumItems; i++) {
-    delete[] HistBuffer[i];
-    delete DataItem[i];
-  }
-
-  free(Filename);
-  free(HistService);
-  free(HistPointer);
-  free(HistBuffer);
-  free(DataItem);
-  
-  // Destroy mutex
-  pthread_mutex_destroy (&LogMutex);
-  pthread_mutex_destroy (&DataMutex);
+	State(ERROR, "Error: Could not close data file (%s)", strerror(errno));
+  }
+
+  // Free memory for regular expressions handling
+  for (int i=0; i<RegExCount; i++) {
+    regfree(&RegEx[i]);
+  }
+  delete[] MinChange;
+  delete[] RegEx;
 }
 
@@ -176,21 +185,84 @@
 // Implementation of data handling
 //
-// More than one infoHandler() might run in parallel, therefore
-// the mutex mechanism is used to serialize writing to the file
+// DIM ensures infoHandler() is called serialized, therefore
+// no mutex is needed to serialize writing to the file
 void DataHandler::infoHandler() {
-  
+
   DimInfo *Info = getInfo();
 
-  // Check if service actually available, if it contains data and if data file is open
+  // Check if service available
   if (Info->getSize()==strlen(NO_LINK)+1 && strcmp(Info->getString(), NO_LINK)==0) return;
-  if (Info->getSize() == 0 || DataFile == NULL) return;
-
+
+  // If service is DIS_DNS/SERVER_LIST, subscribe to all SERVICE_LIST services
+  if (strcmp(Info->getName(), "DIS_DNS/SERVER_LIST") == 0) {	
+	char *Token = strtok(Info->getString(), "+-!|@");	
+	while (Token != NULL) {
+	  char *Buf;
+	  if (MakeString(&Buf, "%s/SERVICE_LIST", Token) != -1) {
+	    AddService(Buf);
+		free(Buf);
+	  }
+	  else State(ERROR, "MakeString() failed for server %s", Token);
+	  
+  	  Token = strtok(NULL, "|");
+	  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(Info->getName(), "/SERVICE_LIST") != NULL) {
+	char *Name = strtok(Info->getString(), "+-!|");
+	while (Name != NULL) {
+	  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);
+	  }
+	  Name = strtok(NULL, "+-!|");
+	}
+	return;
+  }
+
+  // If it is time to open new data file, close the current one 
+  if (time(NULL) >= TimeForNextFile) {
+	if (DataFile != NULL && fclose(DataFile) != 0) {
+	  State(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);
+	
+	if(T->tm_hour >= DATE_ROLLOVER) T->tm_mday++;
+	if (mktime(T) == -1) State(ERROR, "mktime() failed, check filename");
+
+	char *Filename;
+	if (MakeString(&Filename, "%s/%d%02d%02d.slow", DataDir, T->tm_year+1900, T->tm_mon+1, T->tm_mday) == -1) State(FATAL, "Could not create filename, MakeString() failed");
+	if ((DataFile = fopen(Filename, "a")) == NULL) {
+      State(FATAL, "Could not open data file '%s' (%s)", Filename, strerror(errno));
+	}
+	else State(INFO, "Opened data file '%s'", Filename);
+	free(Filename);
+	
+	// Calculate time for next file opening
+	T->tm_sec = 0;
+	T->tm_min = 0;
+	T->tm_hour = DATE_ROLLOVER;
+	TimeForNextFile = mktime(T);
+  }
+   
   // Identify index of service
   int Service;  
-  for (Service=0; Service<NumItems; Service++) if (Info == DataItem[Service]) break;
-  if (Service == NumItems) return;  // Not found: should never happen
-
-  pthread_mutex_lock(&DataMutex);
-  
+  for (Service=0; Service<NumItems; Service++) if (Info == List[Service].DataItem) break;
+  if (Service == NumItems) return;  // Service not found
+
+  // If negative value for absolute change, ignore this entry
+  if (List[Service].MinAbsChange < 0) return;
+
   // Write data header
   time_t RawTime = Info->getTimestamp();
@@ -199,15 +271,22 @@
   fprintf(DataFile, "%s %d %d %d %d %d %d %d %lu ", Info->getName(), TM->tm_year+1900, TM->tm_mon+1, TM->tm_mday, TM->tm_hour, TM->tm_min, TM->tm_sec, Info->getTimestampMillisecs(), Info->getTimestamp());
 
-  // Translate info data into ASCII and write to file and to history buffer
+  // Translate data into ASCII
   char *Text = EvidenceServer::ToString(Info);
   if (Text != NULL) {
+	// Replace all control characters by white space
+	for (int i=0; i<strlen(Text); i++) if (iscntrl(Text[i])) Text[i] = ' ';
+	
+	// Write to file
     fprintf(DataFile, "%s\n", Text);
 	
-	HistBuffer[Service][HistPointer[Service]].Seconds = Info->getTimestamp();
-    HistBuffer[Service][HistPointer[Service]].Value = atof(Text);
-    HistService[Service]->updateService();
-	HistPointer[Service]++;
-	if (HistPointer[Service] >= HistSize) HistPointer[Service] = 0;
-
+	// Add to history buffer if change large enough
+	if ((fabs(atof(Text)-List[Service].LastValue) > List[Service].MinAbsChange)) {
+	  List[Service].HistBuffer[List[Service].HistPointer].Seconds = Info->getTimestamp();
+      List[Service].HistBuffer[List[Service].HistPointer].Value = atof(Text);
+      List[Service].HistService->updateService();
+	  List[Service].HistPointer++;
+	  if (List[Service].HistPointer >= HistSize) List[Service].HistPointer = 0;
+	  List[Service].LastValue = atof(Text);
+	}
 	free(Text);
   }
@@ -215,18 +294,35 @@
  
   fflush(DataFile);
-  if(ferror(DataFile)) Msg(ERROR, "Error writing to data file (%s)", strerror(errno));
-
-  pthread_mutex_unlock(&DataMutex);
-}
-
-//
-// Implementation of log writing (the only command is 'DColl/Log')
+
+  // Terminate if error because otherwise infinite loop might result as
+  // next call to this infoHandler() will try to (re-)open file
+  if(ferror(DataFile)) {
+    fclose(DataFile);
+	DataFile = NULL;
+	State(FATAL, "Error writing to data file, closed file (%s)", strerror(errno));
+  }
+
+  // Update datafile size service
+  if (time(NULL) - DataSizeLastUpdate > SizeUpdateDelay) {
+	DataSizekB = FileSize(DataFile);
+	DataSizeService->updateService();
+	DataSizeLastUpdate = time(NULL);
+  }
+}
+
+//
+// Implementation of log writing
 //
 void DataHandler::commandHandler() {
-
-  if (LogFile == NULL) return;  // Handler might be called before file is opened
  
-  pthread_mutex_lock(&LogMutex);
-
+  if (getCommand() != LogCommand || LogFile == NULL) return;
+
+  // Replace all carriage-return by line feed and non-printable characters
+  char *Text = getCommand()->getString();
+  for (unsigned int i=0; i<strlen(Text); 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);
@@ -234,27 +330,90 @@
   fprintf(LogFile, "%d/%d/%d %d:%d:%d: %s\n",
   		TM->tm_mday, TM->tm_mon+1, TM->tm_year+1900,
-		TM->tm_hour, TM->tm_min, TM->tm_sec, getString());
+		TM->tm_hour, TM->tm_min, TM->tm_sec, Text);
 
   fflush(LogFile);
-  if(ferror(LogFile)) Msg(ERROR, "Error writing to log file (%s)", strerror(errno));
-  pthread_mutex_unlock(&LogMutex);
-}
-
-
-//
-// Implementation of memory re-allocation for pointer arrays
-//
-bool DataHandler::ReallocMem(void *&Memory, int Size) {
-
-  void *NewMem = realloc(Memory, Size*sizeof(void *));
-
-  if (NewMem != NULL) {
-    Memory = NewMem;
-	return true;
-  }
+  
+  // If error close file (otherwise infinite loop because State() also writes to log) 
+  if(ferror(LogFile)) {
+    fclose(LogFile);
+	LogFile = NULL;
+    State(ERROR, "Error writing to log file, closing file (%s)", strerror(errno));
+  }
+    
+  // Update logfile size service
+  if (time(NULL) - LogSizeLastUpdate > SizeUpdateDelay) {
+	LogSizekB = FileSize(LogFile);
+	LogSizeService->updateService();
+	LogSizeLastUpdate = time(NULL);
+  }
+}
+
+
+//
+// Add service to watch list
+//
+void DataHandler::AddService(char *Name) {
+
+  // Do not subscribe to history services (otherwise infinite loop)
+  if (strstr(Name, ".hist") != NULL) return;
+
+  // Check if already subscribed to this service
+  for (int i=0; i<NumItems; i++) {
+	if(strcmp(Name, List[i].DataItem->getName()) == 0) return;
+  }
+
+  // Increase capacity of item list			
+  struct Item *New = (struct Item *) realloc(List, (NumItems+1)*sizeof(struct Item));
+  if (New != NULL) List = New;
   else {
-	Msg(ERROR, "Could not allocate memory ()", strerror(errno));
-	return false;
-  }
+	State(ERROR, "Could not allocate memory for item list, service '' not added (%s)", Name, strerror(errno));
+	return;
+  }
+  
+  // Set minimum required change by comparing to regular expressions
+  List[NumItems].MinAbsChange = 0;
+  for (int i=0; i<RegExCount; i++) {
+    if (regexec(&RegEx[i], Name, (size_t) 0, NULL, 0) == 0) {
+	  List[NumItems].MinAbsChange = MinChange[i];
+	}
+  }
+  
+  List[NumItems].LastValue = DBL_MAX;
+		
+  // Create history service
+  List[NumItems].HistBuffer = new struct EvidenceHistoryItem [HistSize];
+  memset(List[NumItems].HistBuffer, 0, HistSize*sizeof(EvidenceHistoryItem));
+  List[NumItems].HistPointer = 0;
+
+  char *Buf;
+  if (MakeString(&Buf, "%s.hist", Name) == -1) {
+    State(ERROR, "Could not create history service for '%s', MakeString() failed", Name);
+  }
+  else {
+	List[NumItems].HistService = new DimService (Buf, (char *) "C",
+  					  List[NumItems].HistBuffer, HistSize*sizeof(EvidenceHistoryItem));
+    free(Buf);
+  }
+
+  // Subscribe to service
+  List[NumItems].DataItem = new DimStampedInfo(Name, NO_LINK, this);
+
+  // Increase number only after all set-up
+  NumItems++;
+}
+
+//
+// Determine size of file in kB
+//
+float DataHandler::FileSize(FILE *File) {
+
+  struct stat FileStatus;
+
+  if (fstat(fileno(File), &FileStatus) == -1) {
+     State(WARN, "Could not determine size of file (%s)", strerror(errno));
+	 return -1;
+  }
+
+  return (float) FileStatus.st_size/1024;
 }
 
@@ -267,4 +426,5 @@
   static DataHandler Data;
   
-  pause();	  // Sleep until signal caught
-}
+  // Sleep until signal caught
+  pause();
+}
Index: /Evidence/Edd/Edd.cc
===================================================================
--- /Evidence/Edd/Edd.cc	(revision 141)
+++ /Evidence/Edd/Edd.cc	(revision 142)
@@ -1,2 +1,3 @@
+
 /* ============================================================ 
 
@@ -19,4 +20,6 @@
 	Qt::gray, Qt::darkGray, Qt::lightGray};
 
+class GUI *Handler;
+
 //////////////////////////////////////////
 // Text display for arbitary DIM service//
@@ -26,17 +29,21 @@
 Edd_Indicator::Edd_Indicator(QString DIMService, QWidget *P): QLineEdit(P) {
 
-  ServiceName = qstrdup(DIMService.toAscii().data());
-  
   // Widget properties
   setReadOnly(true);
   setMaximumWidth(100);
-  connect(this, SIGNAL(YEP(QString)), this, SLOT(setText(QString)));
+  ShowAsTime = false;
+  
+  // Connect to DIM handler
+  if (connect(Handler, SIGNAL(YEP(DimInfo*, int, QString, QByteArray, QString)), SLOT(Update(DimInfo*, int, QString, QByteArray, QString))) == false) {
+    printf("Failed connection for %s\n", DIMService.toAscii().data());
+  }
 
   // Context menu
   Menu = new QMenu(this);
+  Menu->addAction("Open history", this, SLOT(MenuOpenHistory()));
   Menu->addAction("Copy service", this, SLOT(MenuCopyService()));
 
   // DIM client
-  Data = new DimStampedInfo(ServiceName, INT_MAX, (char *) NO_LINK, this);
+  Data = new DimStampedInfo(DIMService.toAscii().data(), INT_MAX, (char *) NO_LINK, Handler);
 }
 
@@ -44,54 +51,40 @@
 Edd_Indicator::~Edd_Indicator() {
   delete Data;
-  delete[] ServiceName;
-}
-
-// Handling of DIM service update
-void Edd_Indicator::infoHandler() {
-
+}
+
+// Update widget
+void Edd_Indicator::Update(DimInfo *Info, int Time, QString Format, QByteArray Data, QString Text) {
+
+  if (Info != this->Data) return;
+    
   QPalette Pal = palette();  
-  QString S;
 
   // Check if service available
-  if (getInfo()->getSize() == strlen(NO_LINK)+1 && strcmp(getInfo()->getString(), NO_LINK) == 0) {
-    emit(YEP(QString("n/a")));
-    setStatusTip(QString("%1:  unavailable").arg(ServiceName));
-    Pal.setColor(backgroundRole(), Qt::red);
-    setPalette(Pal);
-    return;
-  }
-  Pal.setColor(backgroundRole(), Qt::white);
-
-  // Translate data into ASCII
-  char *Text = EvidenceServer::ToString(getInfo());
-
-  // If this is a status indicator, adapt background colour
-  if (getInfo()->getSize() == (int) strlen(Text)+3) {
-    switch (*((char *) getInfo()->getData() + strlen(Text) + 2)) {
-      case 0:  Pal.setColor(backgroundRole(), Qt::white); break;
-      case 1:  Pal.setColor(backgroundRole(), Qt::cyan); break;
-      case 2:  Pal.setColor(backgroundRole(), Qt::red); break;
-      case 3:  Pal.setColor(backgroundRole(), Qt::red); break;
-      default: break;
-    }
-  }
+  if (Time == -1) {
+    setText("n/a");
+	setStatusTip(QString("%1:  unavailable").arg(Info->getName()));
+    Pal.setColor(QPalette::Base, Qt::lightGray);
+  }
+  else {
+    // If this is a status indicator, adapt background colour
+    if (Data.size() == Text.size()+2) {
+      switch (Data[Text.size() + 2]) {
+        case 0:  Pal.setColor(QPalette::Base, Qt::white); break;
+        case 1:  Pal.setColor(QPalette::Base, Qt::cyan); break;
+        case 2:  Pal.setColor(QPalette::Base, Qt::red); break;
+        case 3:  Pal.setColor(QPalette::Base, Qt::red); break;
+        default: break;
+      }
+    }
+    else Pal.setColor(QPalette::Base, Qt::white);
+	
+	if (!ShowAsTime) setText(Text);
+	else setText(QDateTime::fromTime_t(Text.toInt()).toString());
+	
+    // Update status tip
+    setStatusTip(QString("%1:  Last update %2  Format '%3'").arg(Info->getName(), QDateTime::fromTime_t(Time).toString()).arg(Format));
+  }
+  
   setPalette(Pal);
-  
-  if (Text != NULL) {
-    QTextStream(&S) << Text;
-	free(Text);
-  }
-  else QTextStream(&S) << "Cannot interpret format identifier";
-    
-  if (strlen(getInfo()->getFormat()) > 1) {
-    QTextStream(&S) << " (DIM format string longer)";
-  }
-  
-  // Trigger display update
-  emit(YEP(S));
-  
-  // Update status tip
-  QDateTime Time = QDateTime::fromTime_t(getInfo()->getTimestamp()); 
-  setStatusTip(QString("%1:  Last update %2  Format '%3'").arg(ServiceName, Time.toString()).arg(getInfo()->getFormat()));
 }
 
@@ -111,6 +104,5 @@
 
   // If not, open new plot
-  LastPlot = new Edd_Plot(ServiceName);
-  LastPlot->show();
+  Edd_Indicator::MenuOpenHistory();
 }
 
@@ -129,5 +121,5 @@
   QDrag *Drag = new QDrag(this);
   QMimeData *MimeData = new QMimeData;
-  MimeData->setText(QString(ServiceName));
+  MimeData->setText(QString(Data->getName()));
   Drag->setMimeData(MimeData);
   Drag->exec();
@@ -142,8 +134,15 @@
 }
 
+// Open history plot
+void Edd_Indicator::MenuOpenHistory() {
+  
+  LastPlot = new Edd_Plot(Data->getName());
+  LastPlot->show();
+}
+
 // Copy service name
 void Edd_Indicator::MenuCopyService() {
   
-  QApplication::clipboard()->setText(QString(ServiceName));
+  QApplication::clipboard()->setText(QString(Data->getName()));
 }
 
@@ -161,10 +160,10 @@
   
   // Graph properties
-  QwtText XAxisTitle("Time (RJD-55000)");
+  setAutoReplot(false);
+  QwtText XAxisTitle("Time (RJD-55200)");
   XAxisTitle.setFont(QFont("Helvetica", 10));
   setAxisTitle(QwtPlot::xBottom, XAxisTitle);
-  setAutoReplot(false);
   setCanvasBackground(QColor(Qt::yellow));
-  
+
   Zoomer = new QwtPlotZoomer(QwtPlot::xBottom,QwtPlot::yLeft,canvas());
   connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(HandleZoom(const QwtDoubleRect &)));
@@ -176,7 +175,9 @@
   Legend = new QwtLegend();
   insertLegend(Legend, QwtPlot::TopLegend);
-    
-  // Threads may not call replot directly, but only through this signal
-  connect(this, SIGNAL(YEP()), this, SLOT(UpdatePlot()));
+
+  // Connect to DIM handler
+  if (connect(Handler, SIGNAL(YEP(DimInfo *, int, QString, QByteArray, QString)), SLOT(Update(DimInfo *, int, QString, QByteArray, QString))) == false) {
+    printf("Failed connection for %s\n", DIMService.toAscii().data());
+  }
 
   // Context menu
@@ -222,5 +223,5 @@
 
   QString HistName = Name+".hist";
-  
+
   // Lock before accessing Items list
   QMutexLocker Locker(&Mutex);
@@ -233,5 +234,5 @@
     }
   }  
-
+  
   // Generate new curve and subscribe to service
   struct PlotItem New;
@@ -239,23 +240,20 @@
   New.Signal->attach(this);
   New.Signal->setTitle(HistName);
-  New.Signal->setPen(QColor(LineColors[Items.size()%(sizeof(LineColors)/sizeof(Qt::GlobalColor))]));
+  New.Signal->setPen(QColor(LineColors[Items.size() % (sizeof(LineColors)/sizeof(Qt::GlobalColor))]));
   New.x = NULL;
   New.y = NULL;
   New.Count = 0;
-  New.Data = new DimStampedInfo(HistName.toAscii(), NO_LINK, this); 
-  New.LiveData = new DimStampedInfo(Name.toAscii(), NO_LINK, this); 
+  New.Data = new DimStampedInfo(HistName.toAscii().data(), (char *) NO_LINK, Handler);
+  New.LiveData = new DimStampedInfo(Name.toAscii().data(), (char *) NO_LINK, Handler); 
 
   Items.append(New);
 }
 
-//
-// Handle update of DIM service
-//
-void Edd_Plot::infoHandler() {
+// Update widget (must happen in GUI thread)
+void Edd_Plot::Update(DimInfo *Info, int Time, QString Format, QByteArray Data, QString Text) {
 
   // Check if service available
-  if (getInfo()->getSize() == strlen(NO_LINK)+1 && strcmp(getInfo()->getString(), NO_LINK) == 0) {
-    setStatusTip(QString("%1:  unavailable").arg(getInfo()->getName()));
-    return;
+  if (Time == -1) {
+	setStatusTip(QString("%1:  unavailable").arg(Info->getName()));
   }
 
@@ -265,8 +263,8 @@
   // Determine which plot item this call belongs to
   int ItemNo;
-  for (ItemNo=0; ItemNo<Items.size(); ItemNo++) if (Items[ItemNo].Data == getInfo()) {
+  for (ItemNo=0; ItemNo<Items.size(); ItemNo++) if (Info == Items[ItemNo].Data) {
     // This is a history service  
-    EvidenceHistoryItem *Curr = (EvidenceHistoryItem *) getInfo()->getData();
-    int Count=0, DataPoints = getInfo()->getSize()/sizeof(struct EvidenceHistoryItem);
+    EvidenceHistoryItem *Curr = (EvidenceHistoryItem *) Data.data();
+    int Count=0, DataPoints = Data.size()/sizeof(struct EvidenceHistoryItem);
 
     delete[] Items[ItemNo].x;
@@ -302,8 +300,8 @@
 
     // Update status tip
-    QDateTime Time = QDateTime::fromTime_t(getInfo()->getTimestamp()); 
-    setStatusTip(QString("%1:  Last update %2  Format '%3'").arg(getInfo()->getName(), Time.toString()).arg(getInfo()->getFormat()));
-
-  } else if (Items[ItemNo].LiveData == getInfo()) {
+    QDateTime Timex = QDateTime::fromTime_t(Time); 
+    StatusTip = QString("%1:  Last update %2  Format '%3'").arg(Info->getName(), Timex.toString()).arg(Format);
+
+  } else if (Info == Items[ItemNo].LiveData) {
     // This is a live service 
 
@@ -313,8 +311,8 @@
     // Append data
     struct EvidenceHistoryItem Data;
-    Data.Seconds = getInfo()->getTimestamp();
-    Data.Value = atof(EvidenceServer::ToString(getInfo()));            
+    Data.Seconds = Time;
+    Data.Value = atof(Text.toAscii().data());            
     Items[ItemNo].Live.append(Data);
-    
+
     // Update largest and smallest value    
     if (Data.Value > Items[ItemNo].Largest) Items[ItemNo].Largest = Data.Value;
@@ -324,8 +322,6 @@
   Locker.unlock();
 
-  // Do not call replot() directly from this thread!
-  emit(YEP());
-}
-
+  UpdatePlot();
+}
 
 //
@@ -333,4 +329,8 @@
 //
 void Edd_Plot::UpdatePlot() {
+
+  static QwtSymbol Symbol, Sym1;
+  Symbol.setStyle(QwtSymbol::Ellipse);
+  Symbol.setSize(4);
 
   if (!YLogAction->isChecked()) {
@@ -342,23 +342,26 @@
   QMutexLocker Locker(&Mutex);
 
+  setStatusTip(StatusTip);
+  
   for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
 
-    if (Items[ItemNo].Count == 0) continue;
-
-    if (StyleAction->isChecked()) Items[ItemNo].Signal->setStyle(QwtPlotCurve::Dots);
-    else Items[ItemNo].Signal->setStyle(QwtPlotCurve::Lines);
-
-    int DataPoints = Items[ItemNo].Count + Items[ItemNo].Live.size();      
+    if (StyleAction->isChecked()) Items[ItemNo].Signal->setSymbol(Symbol);
+    else Items[ItemNo].Signal->setSymbol(Sym1);
+
+    int DataPoints = Items[ItemNo].Count + Items[ItemNo].Live.size();
+	
+	if (DataPoints == 0) continue;
+   
     double *x = new double [DataPoints];
     double *y = new double [DataPoints];
-    
+ 
     // Adapt time scale and normalize y scale if requested
     for (int i=0; i<DataPoints; i++) {
       if (i < Items[ItemNo].Count) {
-        x[i] = Items[ItemNo].x[i] / 86400.0 + 40587.5 - 55000;
+        x[i] = Items[ItemNo].x[i] / 86400.0 + 40587.5 - 55200;
         y[i] = Items[ItemNo].y[i];
       }
       else {
-        x[i]= Items[ItemNo].Live[i-Items[ItemNo].Count].Seconds / 86400.0 + 40587.5 - 55000;
+        x[i]= Items[ItemNo].Live[i-Items[ItemNo].Count].Seconds / 86400.0 + 40587.5 - 55200;
         y[i] = Items[ItemNo].Live[i-Items[ItemNo].Count].Value;
       }
@@ -498,12 +501,66 @@
 
 
+//////////////////
+// Text display //
+//////////////////
+
+// Constructor
+Edd_Textout::Edd_Textout(QString DIMService, QWidget *P): QTextEdit(P) {
+
+  // Widget properties
+  setReadOnly(true);
+  setAutoFillBackground(true);
+  document()->setMaximumBlockCount(1000);
+  Accumulate = true;
+  
+  // Connect to DIM handler
+  if (connect(Handler, SIGNAL(YEP(DimInfo*, int, QString, QByteArray, QString)), SLOT(Update(DimInfo*, int, QString, QByteArray, QString))) == false) {
+    printf("Failed connection for %s\n", DIMService.toAscii().data());
+  }
+
+  // DIM client
+  Data = new DimStampedInfo(DIMService.toAscii().data(), INT_MAX, (char *) NO_LINK, Handler);
+}
+
+// Destructor
+Edd_Textout::~Edd_Textout() {
+
+  delete Data;
+}
+
+// Handling of DIM service update
+void Edd_Textout::Update(DimInfo *Info, int Time, QString Format, QByteArray, QString Text) {
+
+  if (Info != this->Data) return;
+  
+  QPalette Pal = palette();  
+
+  // Check if service available
+  if (Time == -1) {
+	setStatusTip(QString("%1:  unavailable").arg(Info->getName()));
+    Pal.setColor(QPalette::Base, Qt::lightGray);
+  }
+  else {
+    Pal.setColor(QPalette::Base, Qt::white);
+
+	// Clear display in case text should not accumulate
+	if (Accumulate == false) clear();
+	
+    // Add if service contains only a string
+    if (Format == "C") insertPlainText(Text);
+
+    // Update status tip
+    setStatusTip(QString("%1:  Last update %2  Format '%3'").arg(Info->getName(), QDateTime::fromTime_t(Time).toString()).arg(Format));
+  }
+  setPalette(Pal);
+}
+
+
 //
 // Main GUI (all widgets have ultimately Central as parent)
 //
 GUI::GUI() {
-
-  Edd_Indicator *Value;
-  Edd_Plot *Graph;
-  QString Text;
+ 
+  Handler = this;
   
   // Set features of main window
@@ -511,5 +568,12 @@
   setCentralWidget(Central);
   setStatusBar(new QStatusBar(this));
-  
+  setGeometry(100, 100, 800, 650);
+  setWindowTitle("Edd - Evidence Data Display");
+
+  Edd_Indicator *Value;
+  Edd_Plot *Graph;
+  Edd_Textout *Textout; 
+  QString Text;
+
    // TextBox for value
   //Value = new Edd_Indicator((char *) "SQM/NSB", Central);
@@ -532,4 +596,22 @@
   MainWidget = new QWidget();
   MainLayout = new QGridLayout(MainWidget);
+
+  Value = new Edd_Indicator("DColl/DataSizekB");
+  MainLayout->addWidget(Value, 3, 5, 1, 1);
+
+  Value = new Edd_Indicator("DColl/LogSizekB");
+  MainLayout->addWidget(Value, 4, 5, 1, 1);
+
+  Value = new Edd_Indicator("Config/ModifyTime");
+  Value->setMaximumWidth(200);
+  Value->ShowAsTime = true;
+  MainLayout->addWidget(Value, 5, 5, 1, 1);
+
+  Value = new Edd_Indicator("Alarm/MasterAlarm");
+  MainLayout->addWidget(Value, 2, 0, 1, 1);
+
+  Textout = new Edd_Textout("Alarm/Summary");
+  Textout->Accumulate = false;
+  MainLayout->addWidget(Textout, 3, 0, 2, 1);
 
   // Layout of all widgets
@@ -557,4 +639,5 @@
     Graph->AddService(Text);
   }
+
   BiasLayout->addWidget(Graph, 0, 4, 12, 3);
   Value = new Edd_Indicator("BIAS/Status");
@@ -562,4 +645,8 @@
   BiasLayout->addWidget(Value, 0, 0, 1, 3);      
 
+  Textout = new Edd_Textout("BIAS/Textout");
+  Textout->setFixedWidth(400);
+  BiasLayout->addWidget(Textout, 10, 0, 4, 4);      
+  
   // Environment page
   EnvironmentWidget = new QWidget();
@@ -580,5 +667,5 @@
   Value = new Edd_Indicator("SQM/NSB");
   EnvironmentLayout->addWidget(Value, 6, 0, 1, 1);      
-    
+
   // Tab widget
   TabWidget = new QTabWidget(Central);
@@ -595,4 +682,7 @@
   QAction* QuitAction = Menu->addAction("Quit", qApp, SLOT(quit()));
   QuitAction->setShortcut(Qt::CTRL + Qt::Key_Q);
+
+  // Show main window
+  show();
 }  
     
@@ -601,12 +691,13 @@
 }
 
+
 void GUI::MenuAbout() {
   QString Rev(SVN_REVISION);
-  Rev.remove(0,1).chop(1);
+  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.\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.");
@@ -639,4 +730,19 @@
 }
 
+// Handling of DIM service update
+void GUI::infoHandler() {
+
+  // Check if service available
+  if (getInfo()->getSize() == strlen(NO_LINK)+1 && strcmp(getInfo()->getString(), NO_LINK) == 0) {
+    YEP(getInfo(), -1);
+  }
+  else {
+    char *Txt = EvidenceServer::ToString(getInfo());
+
+    YEP(getInfo(), getInfo()->getTimestamp(), getInfo()->getFormat(), QByteArray((char *) getInfo()->getData(), getInfo()->getSize()), QString(Txt));
+		free(Txt);
+  }
+}
+
 //---------------------------------------------------------------------
 //************************ All functions ****************************
@@ -654,11 +760,8 @@
 
 int main(int argc, char *argv[]) {
-  QApplication app(argc, argv);
-  
+
+  QApplication app(argc, argv); 
   GUI MainWindow;
-  MainWindow.setGeometry(100, 100, 800, 650);
-  MainWindow.setWindowTitle("Edd - Evidence Data Display");
-  MainWindow.show();
-  
+
   return app.exec();
 }
Index: /Evidence/Edd/Edd.h
===================================================================
--- /Evidence/Edd/Edd.h	(revision 141)
+++ /Evidence/Edd/Edd.h	(revision 142)
@@ -15,4 +15,5 @@
 #include <qwt_legend.h>
 #include <qwt_legend_item.h>
+#include <qwt_symbol.h>
 
 #include <limits.h>
@@ -22,19 +23,16 @@
 #include "Evidence.h"
 
-#define NO_LINK "__&DIM&NOLINK&__" // for checking if DIMserver is alive
 #define SVN_REVISION "$Revision$"
 		  
 // General indicator for DIM service
-class Edd_Indicator: public QLineEdit, public DimClient, public DimBrowser {
+class Edd_Indicator: public QLineEdit, public DimClient {
     Q_OBJECT
-
-    char *ServiceName;
-    DimStampedInfo *Data;
 
     QMenu *Menu;
     QPoint dragStart;
     QwtPlot *LastPlot;
-    	
-    void infoHandler();
+	
+    DimStampedInfo *Data;
+	
     void mousePressEvent(QMouseEvent *); 
     void mouseReleaseEvent(QMouseEvent *); 
@@ -45,10 +43,11 @@
     ~Edd_Indicator();
 
+	bool ShowAsTime;
+	
   private slots:
+	void Update(DimInfo *, int, QString, QByteArray, QString);
     void contextMenuEvent(QContextMenuEvent *);    
+    void MenuOpenHistory();
     void MenuCopyService();
-
-  signals:
-    void YEP(QString);
 };
 
@@ -72,4 +71,6 @@
     QMutex Mutex;
     
+	QString StatusTip;
+	
     QMenu *Menu;
     QAction *YLogAction;
@@ -82,5 +83,4 @@
     QwtLegend *Legend;
     
-    void infoHandler();    
     void dragEnterEvent(QDragEnterEvent *);
     void dropEvent(QDropEvent *);
@@ -93,4 +93,6 @@
   private slots:
     void UpdatePlot();
+	void Update(DimInfo* Info, int, QString, QByteArray, QString);
+
     void HandleZoom(const QwtDoubleRect &);
     void contextMenuEvent(QContextMenuEvent *);    
@@ -101,12 +103,24 @@
     void MenuPrint();
     void MenuPasteService();
- 
- signals:
-     void YEP();
+};
 
+// Textout indicator for DIM service
+class Edd_Textout: public QTextEdit, public DimClient {
+    Q_OBJECT
+
+    DimStampedInfo *Data;
+
+  public:
+    Edd_Textout(QString, QWidget * = NULL);
+    ~Edd_Textout();
+	
+	bool Accumulate;
+	
+  private slots:
+	void Update(DimInfo* Info, int, QString, QByteArray, QString);
 };
 
 // Main window class
-class GUI: public QMainWindow, public DimBrowser {
+class GUI: public QMainWindow, public DimBrowser, public DimInfo {
     Q_OBJECT
 
@@ -118,6 +132,7 @@
     QTabWidget *TabWidget;
             
-    void closeEvent(QCloseEvent *); 
-
+    void closeEvent(QCloseEvent *);
+	void infoHandler();
+	
   public:
     GUI();
@@ -127,4 +142,7 @@
     void MenuAbout();
     void MenuNewHistory();
+	
+  signals:
+    void YEP(DimInfo *, int, QString = QString(), QByteArray = QByteArray(), QString = QString());
 };
 
Index: /Evidence/Evidence.cc
===================================================================
--- /Evidence/Evidence.cc	(revision 141)
+++ /Evidence/Evidence.cc	(revision 142)
@@ -5,6 +5,8 @@
   - The server is started with the given name.
   - DIM exit and error handlers are implemented.
-  - The Status service is published.
-    It can be updated with the Msg() method. The text will also be logged.
+  - The Status service is published (special format, see below).
+    It can be updated with the State() method. The text will also be logged.
+  - If the severity of a State() call is FATAL, exit() will be called (with
+    this severity, the call to State() is guranteed not to return).
   - Configuration data can be requested by GetConfig(). 
   - Signal handlers to ignore common signals are installed.
@@ -30,5 +32,4 @@
   // Initialize
   Status = NULL;
-  MsgBuffer = NULL;
   ConfigList = NULL;
   ConfigNum = 0;
@@ -41,26 +42,23 @@
   
   // Create server name
-  if (asprintf(&ServerName, "%s/Status", Name) == -1) {
-    ServerName = NULL;
-    Msg(FATAL, "Could not generate service name, asprintf() failed");
+  if (MakeString(&StatusName, "%s/Status", Name) == -1) {
+    State(FATAL, "Could not generate service name, asprintf() failed");
   }
   
   // Start server
-  static char InitMsg[] = "OK" "\0" "0";
-  Status = new DimService(ServerName, (char *) "C", InitMsg, sizeof(InitMsg));
-  DimServer::start(Name);
-  DimServer::addExitHandler(this);
-  
-  Msg(INFO, "Server started");
-}
-
-// Destructor: Frees all buffers
+  Status = new DimService(StatusName, (char *) "Server started");
+
+  start(Name);
+  addExitHandler(this);
+}
+
+// Destructor: Free memory
 EvidenceServer::~EvidenceServer() {
 
-  free(ServerName);
-  free(MsgBuffer);
+  free(StatusName);
   
   for (unsigned int i=0; i<ConfigNum; i++) {
-    delete *((DimRpcInfo **) ConfigList + i);
+  	delete[] ConfigList[i].Name;
+	delete[] ConfigList[i].Value;
   }
   free(ConfigList);
@@ -69,5 +67,5 @@
 // DIM exit handler
 void EvidenceServer::exitHandler(int Code) {
-  Msg(INFO, "Server stopped (DIM exit code %d)", Code);
+  State(INFO, "Server stopped (DIM exit code %d)", Code);
   exit(EXIT_SUCCESS);
 }
@@ -75,48 +73,37 @@
 // DIM error handler
 void EvidenceServer::errorHandler(int Severity, int Code, char *Message) {   
-  Msg(ERROR, "%s (DIM Error, code %d, severity %d)\n", Message, Code, Severity);
-}
-
-// Print message to screen, to log and to status
-//
-// The message format is special: after the string-terminating '\0'
-// the Severity is given, terminated by another '\0' 
-// The buffer must have the same lifetime as the DIM service.
-// If Severity is FATAL, exit() will be invoked.
-void EvidenceServer::Msg(MsgType Severity, const char *Format, ...) {
-
-  char *TmpBuffer;
-  int Size;
-  
-  static char DefaultMsg[]="vasprintf() or asprintf() failed, cannot process message" "\0" "2";   // Severity 'Error'
-   
+  State(ERROR, "%s (DIM error code %d, severity %d)\n", Message, Code, Severity);
+}
+
+// Set status of server
+//
+// The message format is special: after the string-terminating '\0' the Severity 
+// is given, terminated by another '\0'  The buffer for the DIM service must have
+// the same lifetime as the DIM service. If Severity is FATAL, exit() will be invoked.
+void EvidenceServer::State(StateType Severity, const char *Format, ...) {
+
+  static const char* StateString[] = {"Info", "Warn", "Error", "Fatal"};
+  static char ErrorString[] = "vasprintf() failed in State()";
+  static char SBuf[STATUS_SIZE];
+  char TBuf[STATUS_SIZE];
+  char *Tmp;
+  
   // Assemble message from application
   va_list ArgumentPointer;
   va_start(ArgumentPointer, Format);
-  if (vasprintf(&TmpBuffer, Format, ArgumentPointer) == -1) TmpBuffer = NULL;
+  if (vasprintf(&Tmp, Format, ArgumentPointer) == -1) Tmp = ErrorString;
   va_end(ArgumentPointer);
 
-  // Free previous message buffer if allocated dynamically
-  if (MsgBuffer != DefaultMsg) free(MsgBuffer);
-  
-  // Assemble final message including headers and Severity encoding
-  if (TmpBuffer == NULL) Size = -1;
-  else Size = asprintf(&MsgBuffer, "%s: %s%s*%d", ServerName,
-		(Severity==FATAL) ? "(Fatal) ":"", TmpBuffer,
-		(unsigned char) Severity);
-  free(TmpBuffer);
-
-  // If memory could not be allocated, show default message,
-  // otherwise replace '*' by '0' for severity encoding		
-  if (Size == -1) {
-    MsgBuffer = DefaultMsg;
-	Size = sizeof(DefaultMsg) - 1;
-  }
-  else *(strrchr(MsgBuffer, '*')) = '\0';
-
-  // Send message to console, log file and update DIM status service
-  printf("%s\n", MsgBuffer);
-  DimClient::sendCommand("DColl/Log", MsgBuffer);
-  if (Status != NULL) Status->updateService(MsgBuffer, Size+1);
+  snprintf(TBuf, sizeof(TBuf), "%s (%s): %s", StatusName, StateString[Severity], Tmp);
+  snprintf(SBuf, sizeof(SBuf), "%s\0%d", Tmp);
+
+  if (Tmp != ErrorString) free(Tmp);
+  
+  // Send message to console and log file
+  printf("%s\n", TBuf);
+  if (Severity != INFO) DimClient::sendCommand("DColl/Log", TBuf);
+
+  // Update DIM status service (including severity encoding)
+  if (Status != NULL) Status->updateService(SBuf, strlen(SBuf)+2);
 
   // Terminate if message type is fatal
@@ -129,26 +116,54 @@
 // the destructor.
 char* EvidenceServer::GetConfig(const char *Item) {
-
-  // Enlarge memory to hold new pointer
-  DimRpcInfo **Config = (DimRpcInfo **) realloc(ConfigList, sizeof(void *) * (++ConfigNum));
-  if (Config == NULL) {
-    Msg(WARN, "Could not realloc() memory for DimRpcInfo list (%s)", strerror(errno));
-    ConfigNum--;  
-  }
-  else ConfigList = (void *) Config;
-  
-  // Advance array pointer to position for new DimRpcInfo pointer 
-  Config += ConfigNum - 1;
+  
+  // Determine configuration file update time
+  DimCurrentInfo ModifyTime("Config/ModifyTime", 0);
+  int Time = ModifyTime.getInt(), ItemNo = -1;
+  
+  // Check if configuration request already in list
+  for (int i=0; i<ConfigNum; i++) {
+    if (strcmp(ConfigList[i].Name, Item) == 0) {
+	  // Return original value if still up to date
+	  if (ConfigList[i].Time >= Time) return ConfigList[i].Value;
+
+	  // Otherwise, free memory of old value
+	  delete[] ConfigList[i].Name;
+	  delete[] ConfigList[i].Value;	  
+	  ItemNo = i;
+	  break;
+	}
+  } 
   
   // Make configuration request
-  *Config = new DimRpcInfo((char *) "ConfigRequest", (char *) "");
-  (*Config)->setData((char *) Item);
+  DimRpcInfo Config((char *) "ConfigRequest", (char *) "");
+  Config.setData((char *) Item);
 
   // Terminate if not successful
-  if (strlen((*Config)->getString()) == 0) {
-    Msg(FATAL, "Missing configuration data '%s'", Item);
-  }
-
-  return (*Config)->getString();
+  if (strlen(Config.getString()) == 0) {
+    State(FATAL, "Missing configuration data '%s'", Item);
+  }
+
+  // Enlarge memory to hold new pointer if necessary
+  if (ItemNo == -1) {
+	void *N = realloc(ConfigList, sizeof(struct ConfigItem)*(++ConfigNum));
+	if (N == NULL) {
+	  State(WARN, "Could not realloc() memory for configuration, will lose memory (%s)", strerror(errno));
+	  ConfigNum--;  
+	}
+	else ConfigList = (struct ConfigItem *) N;
+	
+	ItemNo = ConfigNum-1;
+  }
+  
+  // Allocate memory for strings, and copy data to this memory
+  ConfigList[ItemNo].Value = new char [strlen(Config.getString())+1];
+  ConfigList[ItemNo].Name = new char [strlen(Item)+1];
+  strcpy(ConfigList[ItemNo].Name, Item);
+  strcpy(ConfigList[ItemNo].Value, Config.getString());
+
+  ConfigList[ItemNo].Time = Time;
+  
+  // Return address to configuration value  
+  return ConfigList[ItemNo].Value;
 }
 
@@ -156,31 +171,47 @@
 // ====== Static methods ======
 
-// Signal handler (causes pause() to return)
+// Signal handler (causes pause() and other syscalls to return)
 void EvidenceServer::SignalHandler(int) {
+
   EvidenceServer::ExitRequest = true;
 }
 
 // Translates DIMInfo to string (memory has to be freed by caller)
+// No DIM structures are supported (only a single number or string is converted)
 char *EvidenceServer::ToString(DimInfo *Item) {
 
   char *Text;
-  int R;
-  
-  // Cannot handle complex data formats
+  
   if (strlen(Item->getFormat()) != 1) return NULL;
   
-  // Interpret format
   switch (*(Item->getFormat())) {
-    case 'I':  R = asprintf(&Text, "%d", Item->getInt());   break;
-    case 'C':  R = asprintf(&Text, "%s", Item->getString());   break;
-    case 'S':  R = asprintf(&Text, "%hd", Item->getShort());   break;
-    case 'F':  R = asprintf(&Text, "%.3f", Item->getFloat());   break;
-    case 'D':  R = asprintf(&Text, "%.3f", Item->getDouble());   break;
-    case 'X':  R = asprintf(&Text, "%lld", Item->getLonglong());   break;
+    case 'I':  MakeString(&Text, "%d", Item->getInt());   break;
+    case 'C':  MakeString(&Text, "%s", Item->getString());   break;
+    case 'S':  MakeString(&Text, "%hd", Item->getShort());   break;
+    case 'F':  MakeString(&Text, "%.5f", Item->getFloat());   break;
+    case 'D':  MakeString(&Text, "%.5f", Item->getDouble());   break;
+    case 'X':  MakeString(&Text, "%lld", Item->getLonglong());   break;
     default: return NULL;
   }
 
-  // Check if aprintf() worked OK
-  if (R != -1) return Text;
-  else return NULL;
-}
+  return Text;
+}
+
+//
+// Generate string with vasprintf()
+//
+// The pointer will be set to NULL in case of error, so can always safely passed to free().
+// In case vasprintf() is not available on a particular system, the functionality can
+// be manually implemented in this routine. 
+//
+int EvidenceServer::MakeString(char **Pointer, const char *Format, ...) {
+
+  int Ret;
+  va_list ArgumentPointer;  
+
+  va_start(ArgumentPointer, Format);  
+  if ((Ret = vasprintf(Pointer, Format, ArgumentPointer)) == -1) Pointer = NULL;
+  va_end(ArgumentPointer);
+  
+  return Ret;
+}
Index: /Evidence/Evidence.h
===================================================================
--- /Evidence/Evidence.h	(revision 141)
+++ /Evidence/Evidence.h	(revision 142)
@@ -9,4 +9,6 @@
 #include "dic.hxx"
 
+#define NO_LINK (char *) "__&DIM&NOLINK&__" // Data if no link available
+#define STATUS_SIZE 1000					// Bytes for status service string
 
 // Declaration of item for history buffer (see DColl.cc)
@@ -17,11 +19,15 @@
 
 // Class declation of Evidence server
-class EvidenceServer: public DimExitHandler, public DimErrorHandler {
+class EvidenceServer: public DimServer {
 
   private:
-    char *ServerName;
-	char *MsgBuffer;
-	void *ConfigList;
+	struct ConfigItem {
+	  char *Name;
+	  char *Value;
+	  int Time;
+	} *ConfigList;
 	unsigned int ConfigNum;
+
+    char *StatusName;
     DimService *Status;
 
@@ -34,9 +40,11 @@
 	~EvidenceServer();
 	
-	enum MsgType {INFO=0, WARN=1, ERROR=2, FATAL=3};
+	enum StateType {INFO=0, WARN=1, ERROR=2, FATAL=3};
 
-	void Msg(MsgType, const char *, ...);
+	void State(StateType, const char *, ...);
+
 	char* GetConfig(const char *);
 	static char *ToString(DimInfo *);
+	static int MakeString(char **Pointer, const char *Format, ...);
 	
     static bool ExitRequest;
