Changeset 10280 for fact/Evidence


Ignore:
Timestamp:
04/01/11 09:39:25 (14 years ago)
Author:
ogrimm
Message:
Separated Edd into generic Evidence part and FACT-specific part
Location:
fact/Evidence
Files:
1 deleted
1 edited
3 copied

Legend:

Unmodified
Added
Removed
  • fact/Evidence/Doc/Evidence.tex

    r262 r10280  
    1111\usepackage{url}
    1212\usepackage{listings}
    13 \usepackage[light]{draftcopy}
     13%\usepackage[light]{draftcopy}
    1414\usepackage{longtable}
    1515
     
    3939\maketitle
    4040
    41 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$}
     41This 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.
    4242
    4343\tableofcontents
     
    4848\label{Overview}
    4949
    50 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}). 
     50The \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,And11}). 
    5151
    5252An 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.
     
    130130\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.
    131131\item Provides a method for safely translating DIM service data into text.
    132 \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.
     132\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. The handler will issue a message with severity \lstinline{INFO}, including the received integer, and then calls \lstinline{exit(EXIT_SUCCESS)}. The user application can override this handler.
    133133\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.
    134134\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.
     
    153153    ~EvidenceServer();
    154154
    155     enum MessageType {INFO=0, WARN=1, ERROR=2, FATAL=3};
    156 
    157     void Message(MessageType, const char *, ...);
     155    enum MessageType {INFO=0, WARN=10, ERROR=20, FATAL=30};
     156
     157    void Message(int, const char *, ...);
    158158    void SendToLog(const char *, ...);
    159159    string GetConfig(string, string = string());
     
    164164    static bool ServiceOK(DimInfo *);
    165165    static bool ServiceOK(DimRpcInfo *);
    166         static bool ServiceOK(DimCurrentInfo *);
     166    static bool ServiceOK(DimCurrentInfo *);
    167167    static vector<string> Tokenize(const string &, const string & = " ");
    168168
     
    181181\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).
    182182
    183 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.
     183The 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 immediately updated configuration data. See Sect.\,\ref{ConfigHandling} for more details.
    184184
    185185The 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.
     
    265265\subsection{\lstinline{Alarm} --- Handling of error conditions}
    266266
    267 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.
     267The alarm server maintains a list of \emph{alarm levels} for a given set of servers. The standard alarm levels are defined as \lstinline{OK} (0), \lstinline{WARN} (10), \lstinline{ERROR} (20), \lstinline{FATAL} (30), and \lstinline{UNAVAILABLE} (40). 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}. Only if a server changes from \lstinline{UNAVAILABLE} to \lstinline{OK} (by restarting) or through the command \lstinline|ResetAlarm| can the alarm level be lowered.
    268268
    269269A 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.
     
    277277\lstinline|period| & Interval in seconds to check for server availability.\\[1ex]
    278278\multicolumn{2}{l}{\textbf{Commands}} \\
    279 \lstinline|ResetAlarm xyz| & Reset alarm level of server \lstinline|xyz|.\\[1ex]
     279\lstinline|ResetAlarm xyz| & Reset alarm level of server \lstinline|xyz|.\\
     280\lstinline|Switch on/off| & Switch alarm server on or off.\\[1ex]
    280281\multicolumn{2}{l}{\textbf{Services}} \\
    281282\lstinline|Alarm/Summary| & Text listing all observed servers and their alarm level.\\
     
    360361As 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.
    361362
    362 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.
     363The remote procedure call is non-blocking (using an \lstinline{rpcInfoHandler()}) when invoked from the DIM handler thread and blocking otherwise. 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 an internal handler later when the data arrived. This procedure is necessary since a blocking remote procedure call from the DIM handler thread (e.g. from an \lstinline{infoHandler()}) will result in a dead-lock.
    363364
    364365In 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()}.
     
    368369An 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()}.
    369370
    370 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.   
     371Running in a separate thread requires suitable protection by the programmer when accessing common data structures. To ease that, the \lstinline{EvidenceServer} class contains the 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 instead the program will terminate with a \lstinline{FATAL} message.   
    371372
    372373
     
    450451A 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.
    451452
    452 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.
     453The 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.
    453454
    454455The 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. 
     
    466467\label{DIMDetails}
    467468
    468 \subsection{Format of \lstinline{DIM_DNS/SERVER_LIST} and \lstinline{xyz/SERVICE_LIST}}
     469\subsection{Format of \lstinline{DIS_DNS/SERVER_LIST} and \lstinline{xyz/SERVICE_LIST}}
    469470\label{ServiceFormats}
    470471 
     
    497498If 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.
    498499
    499 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}.
     500In 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}. This also applies to the \lstinline{exitHandler()}. Calling \lstinline{exit()} from this handler will run destructors of static objects within the DIM thread. This might result in undesirable behaviour if DIM services are deleted in those destructors.
    500501
    501502If DIM is compiled without threads, it uses internally the signals SIGIO and SIGALRM for communication.
     
    538539\begin{thebibliography}{xxxx00}
    539540
    540 \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
    541 \bibitem[Bra09]{Bra09} I. Braun et al., Nucl. Inst. and Methods A 610, 400 (2009)
    542 \bibitem[And10]{And10} H. Anderhub et al., Nucl. Inst. and Methods, to be published (2010)
    543 
     541\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 \textbf{140}, 102 (2001)
     542\bibitem[Bra09]{Bra09} I. Braun et al., Nucl. Inst. and Methods \textbf{A 610}, 400 (2009)
     543\bibitem[And11]{And11} H. Anderhub et al., Nucl. Inst. and Methods, \textbf{A 628}, 107 (2011)
    544544\end{thebibliography}
    545545
  • fact/Evidence/Doc/INSTALL_Edd

    r10262 r10280  
    3636and DIMDIR to the DIM installation.
    3737
    38 The subversion repository should preferably be checked out fully, as
    39 the class describing the M0 raw data format is currently needed.
    40 
    4138Issue 'qmake', then 'make' will build the executable Edd.
    4239
  • fact/Evidence/GUI.cc

    r10262 r10280  
    1313============================================================ */
    1414
    15 #include "Edd.h"
    16 
    17 Qt::GlobalColor LineColors[] = {Qt::black, Qt::blue, Qt::red, Qt::green, Qt::white,
    18         Qt::darkRed, Qt::darkGreen, Qt::darkBlue, Qt::cyan,
    19         Qt::darkCyan, Qt::magenta, Qt::darkMagenta,
    20         Qt::gray, Qt::darkGray, Qt::lightGray};
    21 
     15#include "GUI.h"
    2216
    2317class EddDim *Handler;
    24 QString DRSBoard = "drsdaq";
    25 std::string PixelMapText;
    26 
     18
     19//
    2720// History chooser function (opens plot for numeric data, TextHist for all other)
     21//
    2822QWidget *OpenHistory(char *Service, int Index) {
    2923
     
    5448}
    5549
     50//
    5651// Set status tip (returns true if service was available)
     52//
    5753bool SetStatus(QWidget *W, QString Name, int Time, QString Format, int Index) {
    5854
     
    589585QwtPlotCurve *EddBasePlot::NewCurve(QwtText Title) {
    590586
     587  static Qt::GlobalColor LineColors[] = {Qt::black, Qt::blue, Qt::red, Qt::green, Qt::white,
     588        Qt::darkRed, Qt::darkGreen, Qt::darkBlue, Qt::cyan, Qt::darkCyan, Qt::magenta, Qt::darkMagenta,
     589        Qt::gray, Qt::darkGray, Qt::lightGray};
    591590  struct PlotItem N;
    592591
     
    10061005}
    10071006
    1008 //
    1009 //
    1010 // ====== FACT specific part ======
    1011 //
    1012 //
    1013 
    1014 ////////////////////////
    1015 // Event oscilloscope //
    1016 ////////////////////////
     1007
     1008/////////////////////
     1009// Open new window //
     1010/////////////////////
    10171011
    10181012// Constructor
    1019 EventScope::EventScope(QWidget *P): EddBasePlot(P), PixelMap(PixelMapText, false) {
    1020 
    1021   Name = DRSBoard+"/EventData";
    1022 
    1023   Tmpfile = tmpfile();
    1024   if(Tmpfile == NULL) {
    1025     QMessageBox::warning(this, "Edd Message", "Could not open temporary file.", QMessageBox::Ok);
    1026   }
    1027 
    1028   // Open file with RawDataCTX
    1029   RD = new RawDataCTX(true);
    1030   ErrCode = CTX_NOTOPEN;
    1031 
    1032   // Context menu
    1033   PhysPipeAction = new QAction("Physical pipeline", this);
    1034   PhysPipeAction->setCheckable(true);
    1035   connect(PhysPipeAction, SIGNAL(triggered()), SLOT(PlotTraces()));
    1036   Menu->insertAction(StripAction, PhysPipeAction);
    1037 
    1038   PersistanceAction = new QAction("Persistance", this);
    1039   PersistanceAction->setCheckable(true);
    1040   Menu->insertAction(StripAction, PersistanceAction);
    1041   Menu->removeAction(StripAction);
    1042 
    1043   // Initial trace
    1044   AddTrace(0,0,0);
    1045 
    1046   // Connect to DIM handler
    1047   if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
    1048     printf("Failed connection for %s\n", Name.toAscii().data());
    1049   }
    1050   SetActive(true);
    1051 }
    1052 
    1053 // Destructor (items with parent widget are automatically deleted)
    1054 EventScope::~EventScope() {
    1055 
    1056   SetActive(false);
    1057   while (!List.isEmpty()) DeleteCurve(List.last().Signal);
    1058   delete RD;
    1059   if (Tmpfile != NULL) fclose(Tmpfile);
    1060 
    1061 
    1062 // Add trace
    1063 void EventScope::AddTrace(int Board, int Chip, int Channel) {
    1064 
    1065   struct ItemDetails N;
    1066  
    1067   N.Signal = NewCurve(QString::number(Board)+","+QString::number(Chip)+","+ QString::number(Channel)+ " (" + ToPixel(0, Board, Chip, Channel) + ")");
    1068   N.Board = Board;
    1069   N.Chip = Chip;
    1070   N.Channel = Channel;
    1071   N.Trigger = new QwtPlotMarker();
    1072   N.Trigger->setSymbol(QwtSymbol(QwtSymbol::Diamond, QBrush(N.Signal->pen().color()), N.Signal->pen(), QSize(10,10)));
    1073   N.Trigger->attach(this);
    1074 
    1075   if (List.isEmpty()) {
    1076     QPen Pen = N.Signal->pen();
    1077         Pen.setWidth(2);
    1078         N.Signal->setPen(Pen);
    1079   }
    1080   List.append(N);
    1081    
    1082   PlotTraces();
    1083 }
    1084 
    1085 // Update last trace (to reflect current setting of spin boxes in DAQ page)
    1086 void EventScope::UpdateFirst(int Board, int Chip, int Channel) {
    1087 
    1088   if (List.isEmpty()) return;
    1089  
    1090   // Clear in case persistance was activated
    1091   ClearCurve(0);
    1092 
    1093   List.first().Signal->setTitle(QString::number(Board)+","+QString::number(Chip)+","+ QString::number(Channel) + " (" + ToPixel(0, Board, Chip, Channel) + ")");
    1094   List.first().Board = Board;
    1095   List.first().Chip = Chip;
    1096   List.first().Channel = Channel;
    1097    
    1098   PlotTraces();
    1099 }
    1100  
    1101 // Update event buffer
    1102 void EventScope::Update(QString Name, int Time, QByteArray Data, QString Format, QString) {
    1103  
    1104   if (Name != this->Name) return;
    1105 
    1106   // Check if service available
    1107   if (!SetStatus(this, Name, Time, Format)) return;
    1108   if (Data.size() < (int) sizeof(RunHeader)) return;
    1109  
    1110   // Disconnect while processing to avoid queing of events
    1111   disconnect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), this, SLOT(Update(QString, int, QByteArray, QString, QString)));
    1112  
    1113   // Open tempory file and write event data to this file
    1114   QTemporaryFile File;
    1115   if (!File.open()) {
    1116         QMessageBox::warning(this, "Edd Message","Could not open temporary file.",QMessageBox::Ok);
    1117         return;
    1118   }
    1119   if (File.write(Data) == -1) {
    1120         QMessageBox::warning(this, "Edd Message","Could not write data to temporary file.",QMessageBox::Ok);
    1121         return;
    1122   }
    1123 
    1124   // Prepare temporary file for run header 
    1125   ftruncate(fileno(Tmpfile), 0);
    1126   rewind(Tmpfile);
    1127 
    1128   // Open file with RawDataCTX
    1129   switch (ErrCode = RD->OpenDataFile(File.fileName().toAscii().data(), Tmpfile)) {
    1130     case CTX_FOPEN:             QMessageBox::warning(this, "Edd Message","Could not open file.",QMessageBox::Ok);
    1131                                                 return;
    1132     case CTX_RHEADER:   QMessageBox::warning(this, "Edd Message","Could not read run header.",QMessageBox::Ok);
    1133                                         return;
    1134     case CTX_BSTRUCT:   QMessageBox::warning(this, "Edd Message","Could not read board structures.",QMessageBox::Ok);
    1135                                                 return;
    1136         default: break;
    1137   }
    1138 
    1139   // Emit signal containing run header
    1140   rewind(Tmpfile);
    1141   QTextStream Stream(Tmpfile);
    1142   emit(RunHeaderChanged(Stream.readAll()));
    1143 
    1144   // Prepare temporary file for event header 
    1145   ftruncate(fileno(Tmpfile), 0);
    1146   rewind(Tmpfile);
    1147 
    1148   // Write event header text to file
    1149   if (RD->ReadEvent(0, Tmpfile) != CTX_OK) {
    1150     QMessageBox::warning(this, "Edd Warning","Could not read event.",QMessageBox::Ok);
    1151     return;
    1152   }
    1153 
    1154   // Add trigger cells to file
    1155   fprintf(Tmpfile, "\nTrigger cells:");
    1156   int *TrigCells = (int *) RD->Data;
    1157   for (unsigned int i=0; i<RD->RHeader->NBoards; i++) {
    1158     fprintf(Tmpfile, "\n Board %d   ", i);
    1159         for (unsigned int j=0; j<RD->RHeader->NChips; j++) fprintf(Tmpfile, "%d ", *(TrigCells++));
    1160   }
    1161 
    1162   // Emit signal containing run header
    1163   rewind(Tmpfile);
    1164   emit(EventHeaderChanged(Stream.readAll()));
    1165 
    1166   // Update display
    1167   PlotTraces();
    1168  
    1169   // Reconnect after processing
    1170   connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString)));
    1171 }
    1172 
    1173 // Update curves
    1174 void EventScope::PlotTraces() {
    1175 
    1176   double x,y;
    1177   unsigned int Cell, Trig;
    1178   static int Last = 0;
    1179  
    1180   // Only process if valid data in RawDataCTX class
    1181   if (ErrCode != CTX_OK) return;
    1182  
    1183   // Set x axis title
    1184   if (PhysPipeAction->isChecked()) setAxisTitle(QwtPlot::xBottom, "Time from start of pipeline (ns)");
    1185   else setAxisTitle(QwtPlot::xBottom, "Time from trigger minus one revolution (ns)");
    1186 
    1187   // Loop through event data to update event scope
    1188   RunHeader *R = RD->RHeader;
    1189   for (int i=0; i<List.size(); i++) {
    1190 
    1191         if (PersistanceAction->isChecked()) List[i].Signal->setStyle(QwtPlotCurve::Dots);
    1192         else {
    1193           ClearCurve(i);
    1194           List[i].Signal->setStyle(QwtPlotCurve::Lines);
    1195         }
    1196 
    1197         // Check if current event contains requested trace
    1198     if (List[i].Board>=R->NBoards || List[i].Chip>=R->NChips || List[i].Channel>=R->NChannels) continue;
    1199 
    1200         // Set trigger marker visibility
    1201         List[i].Trigger->setVisible(PhysPipeAction->isChecked());
    1202        
    1203         // Determine trigger cell
    1204         Trig = *((int *) RD->Data + List[i].Board*R->NChips + List[i].Chip);
    1205 
    1206         // Calulate point of curve
    1207         for (unsigned int j=0; j<R->Samples; j++) {
    1208 
    1209           if (PhysPipeAction->isChecked()) Cell = (j - Trig) % 1024;
    1210           else Cell = j;
    1211 
    1212           x = j / RD->BStruct[List[i].Board].NomFreq;
    1213           y = *((short *) (RD->Data + R->NBoards*R->NChips*sizeof(int)) +
    1214                 List[i].Board*R->NChips*R->NChannels*R->Samples + List[i].Chip*R->NChannels*R->Samples +
    1215                 List[i].Channel*R->Samples + Cell) * RD->BStruct[List[i].Board].ScaleFactor;
    1216 
    1217           AddPoint(i, x, y);
    1218          
    1219           // Set trigger point indicator
    1220           if (Trig == j) List[i].Trigger->setValue(x, y);       
    1221     }
    1222   }
    1223 
    1224   // Limit update rate in persistance mode
    1225   if (!PersistanceAction->isChecked() || time(NULL) > Last) {
    1226     UpdatePlot();
    1227     Last = time(NULL);
    1228   }
    1229 
    1230 
    1231   // Loop through event data for pixel display
    1232   QVector<double> Pixel(R->NBoards*R->NChips*R->NChannels);
    1233   int Count = 0;
    1234 
    1235   for (unsigned int Board=0; Board<R->NBoards; Board++) {
    1236   for (unsigned int Chip=0; Chip<R->NChips; Chip++) {
    1237   for (unsigned int Channel=0; Channel<R->NChannels; Channel++) {
    1238     Pixel[Count] = DBL_MIN;
    1239 
    1240         for (unsigned int i=0; i<R->Samples; i++) {
    1241           y = *((short *) (RD->Data + R->NBoards*R->NChips*sizeof(int)) +
    1242                 Board*R->NChips*R->NChannels*R->Samples + Chip*R->NChannels*R->Samples +
    1243                 Channel*R->Samples + i) * RD->BStruct[Board].ScaleFactor;
    1244 
    1245           if (y > Pixel[Count]) Pixel[Count] = y;
    1246     }
    1247         Count++;         
    1248   }}}
    1249  
    1250   emit(PixelData(Pixel));
    1251 }
    1252 
    1253 // Remove list entry
    1254 void EventScope::DeleteCurve(QwtPlotCurve *Curve) {
    1255 
    1256   for (int i=0; i<List.size(); i++) if (List[i].Signal == Curve) {
    1257         delete List[i].Trigger;
    1258     List.removeAt(i);
    1259   }
    1260 }
    1261 
    1262 // Set display active (if inactive, disconnect from server)
    1263 void EventScope::SetActive(bool State) {
    1264 
    1265   static bool Active = false;
    1266 
    1267   if (State && !Active) Handler->Subscribe(DRSBoard+"/EventData");
    1268   if (!State && Active) Handler->Unsubscribe(DRSBoard+"/EventData");
    1269   Active = State;
    1270 }
    1271 
    1272 // Translate FPA ID to Pixel ID (use '-' instead of PM_ERROR_CODE)
    1273 QString EventScope::ToPixel(unsigned int Crate, unsigned int Board, unsigned int Patch, unsigned int Pixel) {
    1274  
    1275   if (FPA_to_Pixel(Crate, Board, Patch, Pixel) == PM_ERROR_CODE) return "-";
    1276   else return QString::number(FPA_to_Pixel(Crate, Board, Patch, Pixel));
    1277 }
    1278  
    1279 //------------------------------------------------------------------
    1280 //**************************** Tab pages ***************************
    1281 //------------------------------------------------------------------
    1282 
    1283 //
    1284 // Environment page
    1285 //
    1286 TP_Environment::TP_Environment() {
    1287 
    1288   QGridLayout *Layout = new QGridLayout(this);
    1289   setAttribute(Qt::WA_DeleteOnClose);
    1290 
    1291   // Status display
    1292   EddLineDisplay *Line = new EddLineDisplay("ARDUINO/Message");
    1293   Line->setMaximumWidth(200);
    1294   Layout->addWidget(Line, 0, 0, 1, 2);     
    1295 
    1296   // Generate plot and data displays
    1297   EddPlot *Plot = new EddPlot();
    1298   for (int i=0; i<10; i++) {
    1299     Line = new EddLineDisplay("ARDUINO/Data", i);
    1300     Layout->addWidget(Line, i%5+1, i/5, 1, 1);
    1301     Plot->AddService("ARDUINO/Data", i);
    1302   }
    1303   Layout->addWidget(Plot, 0, 2, 9, 7);     
    1304 
    1305   // Night sky monitor
    1306   Line = new EddLineDisplay("SQM/Message");
    1307   Line->setMaximumWidth(200);
    1308   Layout->addWidget(Line, 6, 0, 1, 2);     
    1309 
    1310   Line = new EddLineDisplay("SQM/NSB");
    1311   Layout->addWidget(Line, 7, 0, 1, 1);         
    1312 }
    1313 
    1314 //
    1315 // Bias page
    1316 //
    1317 TP_Bias::TP_Bias() {
    1318 
    1319   QGridLayout *Layout = new QGridLayout(this);
    1320   setAttribute(Qt::WA_DeleteOnClose);
    1321   EddLineDisplay *Line;
    1322  
    1323   EddPlot *Plot = new EddPlot();
    1324   Plot->setMinimumWidth(400);
    1325   for (int i=0; i<18; i++) {
    1326     Line = new EddLineDisplay("Bias/VOLT/ID00", i+64);
    1327     Layout->addWidget(Line, i%9+1, 0+i/9, 1, 1);
    1328     Plot->AddService("Bias/VOLT/ID00", i+64);
    1329 
    1330     Line = new EddLineDisplay("Bias/VOLT/ID00", i+96);
    1331     Layout->addWidget(Line, i%9+1, 2+i/9, 1, 1);
    1332     Plot->AddService("Bias/VOLT/ID00",i+96);
    1333   }
    1334 
    1335   Layout->addWidget(Plot, 0, 4, 12, 3);
    1336   Line = new EddLineDisplay("Bias/Message");
    1337   Line->setMaximumWidth(200);
    1338   Layout->addWidget(Line, 0, 0, 1, 3);     
    1339 
    1340   EddCommand *Command = new EddCommand("Bias/Command");
    1341   Layout->addWidget(Command, 10, 0, 1, 4);   
    1342 
    1343   EddText *Text = new EddText("Bias/ConsoleOut", true);
    1344   Text->setFixedWidth(400);
    1345   Layout->addWidget(Text, 11, 0, 4, 4);
    1346  
    1347   QWidget *Button = new QPushButton("Currents");
    1348   Layout->addWidget(Button, 13, 4, 1, 1);     
    1349   connect(Button, SIGNAL(pressed()), SLOT(BiasCurrents()));
    1350 }
    1351 
    1352 void TP_Bias::BiasCurrents() {
    1353 
    1354   QMainWindow *M = new QMainWindow;
     1013EddWindow::EddWindow(QString ButtonName, QString WindowName): QPushButton(ButtonName) {
     1014 
     1015  M = new QMainWindow;
    13551016  M->setCentralWidget(new QWidget);
    13561017  M->setStatusBar(new QStatusBar(M));
    1357   M->setWindowTitle("Edd - Bias currents");
    1358   M->setAttribute(Qt::WA_DeleteOnClose);
    1359 
    1360   QGridLayout *Layout = new QGridLayout(M->centralWidget());
    1361   EddLineDisplay *Line;
    1362   EddPlot *Plot = new EddPlot();
    1363 
    1364   for (int i=0; i<36; i++) {
    1365     Line = new EddLineDisplay("Bias/MICROAMP/ID00", i+64);
    1366         Line->setMaximumWidth(60);
    1367     Layout->addWidget(Line, i%9, 0+i/9, 1, 1);
    1368     Plot->AddService("Bias/MICROAMP/ID00", i+64);       
    1369   }
    1370   Layout->addWidget(Plot, 0, 4, 30, 12);
    1371  
    1372   M->show();
    1373 }
    1374 
    1375 //
    1376 // Feedback page
    1377 //
    1378 TP_Feedback::TP_Feedback() {
    1379 
    1380   setAttribute(Qt::WA_DeleteOnClose);
    1381   QGridLayout *Layout = new QGridLayout(this);
    1382   EddLineDisplay *Line;
    1383 
    1384   EddPlot *Plot = new EddPlot();
    1385   for (int i=0; i<36; i++) {
    1386     Line = new EddLineDisplay("Feedback/Average", i);
    1387         Line->setMaximumWidth(60);
    1388     Layout->addWidget(Line, i%9+2, 0+i/9, 1, 1);
    1389     Plot->AddService("Feedback/Average", i);
    1390   }
    1391   Layout->addWidget(Plot, 0, 4, 12, 10);
    1392 
    1393   Line = new EddLineDisplay("Feedback/Message");
    1394   Line->setMaximumWidth(200);
    1395   Layout->addWidget(Line, 0, 0, 1, 2);     
    1396 
    1397   Line = new EddLineDisplay("Feedback/State");
    1398   Line->setMaximumWidth(150);
    1399   Layout->addWidget(Line, 1, 0, 1, 2);     
    1400   Line = new EddLineDisplay("Feedback/Count");
    1401   Line->setMaximumWidth(60);
    1402   Layout->addWidget(Line, 1, 2);     
    1403 
    1404   QWidget *Button = new QPushButton("Details");
    1405   Layout->addWidget(Button, 12, 0, 1, 1);     
    1406   connect(Button, SIGNAL(pressed()), SLOT(FeedbackDetails()));
    1407 }
    1408 
    1409 void TP_Feedback::FeedbackDetails() {
    1410 
    1411   QMainWindow *M = new QMainWindow;
    1412   M->setCentralWidget(new QWidget);
    1413   M->setStatusBar(new QStatusBar(M));
    1414   M->setWindowTitle("Edd - Feedback Details");
    1415   M->setAttribute(Qt::WA_DeleteOnClose);
    1416 
    1417   QGridLayout *Layout = new QGridLayout(M->centralWidget());
    1418   EddLineDisplay *Line;
    1419   EddPlot *Plot = new EddPlot();
    1420 
    1421   for (int i=0; i<36; i++) {
    1422     Line = new EddLineDisplay("Feedback/Sigma", i);
    1423         Line->setMaximumWidth(60);
    1424     Layout->addWidget(Line, i%9, 0+i/9, 1, 1);
    1425     Plot->AddService("Feedback/Sigma", i);
    1426        
    1427     Line = new EddLineDisplay("Feedback/Target", i);
    1428         Line->setMaximumWidth(60);
    1429     Layout->addWidget(Line, i%9+10, 0+i/9, 1, 1);
    1430        
    1431         Line = new EddLineDisplay("Feedback/Response", i);
    1432         Line->setMaximumWidth(60);
    1433     Layout->addWidget(Line, i%9+20, 0+i/9, 1, 1);
    1434   }
    1435   Layout->addWidget(Plot, 0, 4, 30, 12);
    1436  
    1437   M->show();
    1438 }
    1439 
    1440 //
    1441 // FADctrl page
    1442 //
    1443 TP_FADctrl::TP_FADctrl() {
    1444 
    1445   QString Board;
    1446  
    1447   QGridLayout *Layout = new QGridLayout(this);
    1448   setAttribute(Qt::WA_DeleteOnClose);
    1449   EddLineDisplay *Line;
    1450  
    1451   Line = new EddLineDisplay("FADctrl/Message");
    1452   Line->setMaximumWidth(200);
    1453   Layout->addWidget(Line, 0, 0, 1, 3);     
    1454 
    1455   EddCommand *Command = new EddCommand("FADctrl/Command");
    1456   Layout->addWidget(Command, 1, 0, 1, 4);   
    1457 
    1458   EddText *Text = new EddText("FADctrl/ConsoleOut", true);
    1459   Text->setFixedWidth(400);
    1460   Layout->addWidget(Text, 2, 0, 4, 4);
    1461 
    1462   for (int i=0; i<10; i++) {
    1463     Board = Board.sprintf("FADctrl/Board%.2d/", i);
    1464     Line = new EddLineDisplay(Board+"Server");
    1465     Layout->addWidget(Line, i+7, 0, 1, 1);
    1466     Line = new EddLineDisplay(Board+"BoardID");
    1467     Layout->addWidget(Line, i+7, 1, 1, 1);
    1468     Line = new EddLineDisplay(Board+"Temperature", 0);
    1469     Layout->addWidget(Line, i+7, 2, 1, 1);
    1470     Line = new EddLineDisplay(Board+"Temperature", 1);
    1471     Layout->addWidget(Line, i+7, 3, 1, 1);
    1472     Line = new EddLineDisplay(Board+"Temperature", 2);
    1473     Layout->addWidget(Line, i+7, 4, 1, 1);
    1474     Line = new EddLineDisplay(Board+"Temperature", 3);
    1475     Layout->addWidget(Line, i+7, 5, 1, 1);
    1476   }
    1477 }
    1478 
    1479 //
    1480 // Event scope page
    1481 //
    1482 TP_DAQ::TP_DAQ() {
    1483 
    1484   EddLineDisplay *Line;
    1485 
    1486   setAttribute(Qt::WA_DeleteOnClose);
    1487   QGridLayout *Layout = new QGridLayout(this);
    1488 
    1489   // Run-related information
    1490   Line = new EddLineDisplay("drsdaq/RunNumber");
    1491   Line->setMaximumWidth(100);
    1492   Layout->addWidget(Line, 0, 1, 1, 1);     
    1493   Line = new EddLineDisplay(DRSBoard+"/EventNumber");
    1494   Line->setMaximumWidth(100);
    1495   Layout->addWidget(Line, 0, 2, 1, 1);     
    1496   Line = new EddLineDisplay("drsdaq/RunSizeMB");
    1497   Line->setMaximumWidth(100);
    1498   Layout->addWidget(Line, 0, 3, 1, 1);     
    1499   Line = new EddLineDisplay("drsdaq/FileSizeMB");
    1500   Line->setMaximumWidth(100);
    1501   Layout->addWidget(Line, 0, 4, 1, 1);     
    1502   Line = new EddLineDisplay("drsdaq/FileName");
    1503   Line->setMaximumWidth(200);
    1504   Layout->addWidget(Line, 0, 5, 1, 1);     
    1505 
    1506   // Message service
    1507   Line = new EddLineDisplay(DRSBoard+"/Message");
    1508   Line->setMaximumWidth(200);
    1509   Layout->addWidget(Line, 1, 1, 1, 2);     
    1510 
    1511   // Event scope
    1512   Scope = new EventScope;
    1513   Scope->setMinimumWidth(700);
    1514 
    1515   // Text boxes for run and event header
    1516   RunHeaderDisplay = new QPlainTextEdit();
    1517   EventHeaderDisplay = new QPlainTextEdit();
    1518   RunHeaderDisplay->setReadOnly(true);
    1519   EventHeaderDisplay->setReadOnly(true);
    1520 
    1521   // Tab widget
    1522   QTabWidget *TabWidget = new QTabWidget();
    1523   TabWidget->addTab(Scope, "&Signals");
    1524   TabWidget->addTab(RunHeaderDisplay, "&Run Header");
    1525   TabWidget->addTab(EventHeaderDisplay, "&Event Header");
    1526   Layout->addWidget(TabWidget, 2, 1, 6, 5);
    1527 
    1528   // Channel number
    1529   Channel = new QSpinBox;
    1530   connect(Channel, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int)));
    1531   Channel->setToolTip("DRS channel number");
    1532 
    1533   // Chip number
    1534   Chip = new QSpinBox;
    1535   connect(Chip, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int)));
    1536   Chip->setToolTip("DRS chip number");
    1537 
    1538   // Board number
    1539   Board = new QSpinBox;
    1540   connect(Board, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int)));
    1541   Board->setToolTip("DRS board number");
    1542 
    1543   // Pixel ID
    1544   PixelID = new QSpinBox;
    1545   PixelID->setMaximumWidth(60);
    1546   PixelID->setRange(-1, 9999);
    1547   PixelID->setSpecialValueText("n/a");
    1548   connect(PixelID, SIGNAL(valueChanged(int)), SLOT(TranslatePixelID(int)));
    1549   PixelID->setToolTip("Pixel identification");
    1550  
    1551   // Layout of pixel addressing widgets
    1552   QFormLayout *FormLayout = new QFormLayout();
    1553   FormLayout->setRowWrapPolicy(QFormLayout::WrapAllRows);
    1554   FormLayout->addRow("Board", Board);
    1555   FormLayout->addRow("Chip", Chip);
    1556   FormLayout->addRow("Channel", Channel);
    1557   FormLayout->addRow("Pixel ID", PixelID);
    1558   Layout->addLayout(FormLayout, 0, 0, 4, 1);
    1559  
    1560   // Add trace permanently
    1561   QPushButton *Button = new QPushButton("Keep trace");
    1562   Button->setToolTip("Keep trace in display");
    1563   Button->setMaximumWidth(80);
    1564   Layout->addWidget(Button, 4, 0);
    1565   connect(Button, SIGNAL(clicked()), SLOT(KeepCurrent()));
    1566 
    1567   // Stop/start
    1568   StartStopButton = new QPushButton("Stop");
    1569   StartStopButton->setToolTip("Start/stop display");
    1570   StartStopButton->setMaximumWidth(80);
    1571   StartStopButton->setCheckable(true);
    1572   QPalette Palette = StartStopButton->palette();
    1573   Palette.setColor(QPalette::ButtonText, Qt::blue);
    1574   Palette.setColor(QPalette::Button, Qt::green);
    1575   StartStopButton->setPalette(Palette);
    1576   StartStopButton->setFont(QFont("Times", 10, QFont::Bold));
    1577   Layout->addWidget(StartStopButton, 6, 0);
    1578   connect(StartStopButton, SIGNAL(toggled(bool)), SLOT(StartStop(bool)));
    1579 
    1580   // Button to show event display
    1581   QPushButton *PixDisplay = new QPushButton("Pixel display");
    1582   PixDisplay->setFont(QFont("Times", 10, QFont::Bold));
    1583   PixDisplay->setToolTip("Show event display window");
    1584   PixDisplay->setMaximumWidth(80);
    1585   Layout->addWidget(PixDisplay, 7, 0);
    1586   connect(PixDisplay, SIGNAL(clicked()), SLOT(ShowPixelDisplay()));
    1587 
    1588   // Event display window
    1589   Display = new QWidget();
    1590   Display->setWindowTitle("Edd - Event display");
    1591  
    1592   Pixel = new QPushButton *[MAXPIXEL];
    1593   int Count = 0;
    1594   double x,y;
    1595  
    1596   for (int ix=-22; ix<=22; ix++) for (int iy=-19; iy<=20; iy++) { 
    1597     if (Count == MAXPIXEL) break;
    1598        
    1599         x = ix*0.866;
    1600         y = iy - (ix%2==0 ? 0.5:0);
    1601     if ((pow(x,2)+pow(y,2) >= 395) && !(abs(ix)==22 && iy==7)) continue;
    1602        
    1603     Pixel[Count] = new QPushButton(Display);
    1604         Pixel[Count]->setAutoFillBackground(true);
    1605         Pixel[Count]->setGeometry((int) (x*12.5 + 250), (int) (y*12.5 + 250), 10, 10);
    1606         Pixel[Count]->show();
    1607         Count++;
    1608   }
    1609 
    1610   connect(Scope, SIGNAL(RunHeaderChanged(QString)), RunHeaderDisplay, SLOT(setPlainText(QString)));
    1611   connect(Scope, SIGNAL(EventHeaderChanged(QString)), EventHeaderDisplay, SLOT(setPlainText(QString)));
    1612 
    1613   StartStop(false);
    1614   connect(Scope, SIGNAL(PixelData(QVector<double>)), SLOT(SetPixelData(QVector<double>)));
    1615 
    1616   // Call to get initial pixel ID correct
    1617   UpdateScope(0);
    1618 }
    1619 
    1620 TP_DAQ::~TP_DAQ() {
    1621 
    1622   delete[] Pixel;
    1623 }
    1624 
    1625 // Translate pixel ID to board, chip, channel
    1626 void TP_DAQ::TranslatePixelID(int ID) {
    1627 
    1628   // setValue() below will call UpdateScope() through signal, therefore need to store numbers here
    1629   unsigned int BoardNo = Scope->Pixel_to_FPAboard(ID);
    1630   unsigned int PatchNo = Scope->Pixel_to_FPApatch(ID);
    1631   unsigned int PixelNo = Scope->Pixel_to_FPApixel(ID);
    1632  
    1633   if (BoardNo == Scope->PM_ERROR_CODE) PixelID->setValue(-1);
    1634   else {
    1635     Board->setValue(BoardNo);
    1636     Chip->setValue(PatchNo);
    1637     Channel->setValue(PixelNo);
    1638   }
    1639 }
    1640 
    1641 // Keep current trace
    1642 void TP_DAQ::KeepCurrent() {
    1643 
    1644   Scope->AddTrace(Board->value(), Chip->value(), Channel->value());
    1645 }
    1646 
    1647 // Start/stop event acquisition
    1648 void TP_DAQ::StartStop(bool State) {
    1649 
    1650   Scope->SetActive(!State);
    1651   StartStopButton->setText(State ? "Start" : "Stop");
    1652   if (!State) connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), Scope, SLOT(Update(QString, int, QByteArray, QString, QString)));
    1653   else disconnect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), Scope, SLOT(Update(QString, int, QByteArray, QString, QString)));
    1654 }
    1655 
    1656 // Update event scope
    1657 void TP_DAQ::UpdateScope(int) {
    1658 
    1659   // Update pixel ID
    1660   PixelID->setValue(Scope->FPA_to_Pixel(0, Board->value(), Chip->value(), Channel->value()));
    1661   if (PixelID->value() == (int) Scope->PM_ERROR_CODE) PixelID->setValue(-1);
    1662  
    1663   // Update first trace
    1664   Scope->UpdateFirst(Board->value(), Chip->value(), Channel->value());
    1665 }
    1666 
    1667 // Show/hide pixel display
    1668 void TP_DAQ::ShowPixelDisplay() {
    1669 
    1670   Display->show();
    1671   Display->raise();
    1672 }
    1673 
    1674 void TP_DAQ::SetPixelData(QVector<double> Data) {
    1675 
    1676   QwtLinearColorMap Map;
    1677 
    1678   for (int i=0; i<Data.size(); i++) {
    1679         Pixel[i]->setPalette(QPalette(Map.color(QwtDoubleInterval(300, 400), Data[i])));
    1680   }
    1681 }
    1682 
    1683 
    1684 //
    1685 // Evidence page
    1686 //
    1687 TP_Evidence::TP_Evidence() {
    1688 
    1689   setAttribute(Qt::WA_DeleteOnClose);
    1690   QGridLayout *Layout = new QGridLayout(this);
    1691   EddLineDisplay *Line;
    1692   EddText *Text;
    1693  
    1694   Line = new EddLineDisplay("Alarm/Message");
    1695   Line->setMaximumWidth(200);
    1696   Layout->addWidget(Line, 0, 0, 1, 2);     
    1697 
    1698   QPushButton *Button = new QPushButton();
    1699   Button->setText("ON/OFF");
    1700   Button->setCheckable(true);
    1701   connect(Button, SIGNAL(toggled(bool)), SLOT(ToggleAlarm(bool)));
    1702   Layout->addWidget(Button, 0, 3, 1, 1);
    1703 
    1704   Line = new EddLineDisplay("Alarm/MasterAlarm");
    1705   Layout->addWidget(Line, 0, 1, 1, 1);
    1706 
    1707   Text = new EddText("Alarm/Summary", true);
    1708   Text->Accumulate = false;
    1709   Text->setMaximumWidth(200);
    1710   Text->setMaximumHeight(150);
    1711   Layout->addWidget(Text, 1, 0, 1, 2);
    1712 
    1713   Line = new EddLineDisplay("DColl/Message");
    1714   Line->setMaximumWidth(200);
    1715   Layout->addWidget(Line, 3, 0, 1, 2);     
    1716 
    1717   Line = new EddLineDisplay("DColl/DataSizeMB");
    1718   Layout->addWidget(Line, 4, 0, 1, 1);
    1719 
    1720   Line = new EddLineDisplay("DColl/LogSizeMB");
    1721   Layout->addWidget(Line, 4, 1, 1, 1);
    1722 
    1723   Line = new EddLineDisplay("DColl/CurrentFile");
    1724   Line->setMaximumWidth(400);
    1725   Layout->addWidget(Line, 5, 0, 1, 3);
    1726 
    1727   Line = new EddLineDisplay("Config/Message");
    1728   Line->setMaximumWidth(200);
    1729   Layout->addWidget(Line, 6, 0, 1, 2);     
    1730 
    1731   Line = new EddLineDisplay("Config/ModifyTime");
    1732   Line->setMaximumWidth(200);
    1733   Line->ShowAsTime = true;
    1734   Layout->addWidget(Line, 7, 0, 1, 1);
    1735 
    1736   Button = new QPushButton();
    1737   Button->setText("eLogBook");
    1738   connect(Button, SIGNAL(released()), SLOT(StartELog()));
    1739   Layout->addWidget(Button, 6, 1, 1, 1);
    1740 
    1741   Button = new QPushButton();
    1742   Button->setText("Start DIM browser");
    1743   connect(Button, SIGNAL(released()), SLOT(StartDIMBrowser()));
    1744   Layout->addWidget(Button, 7, 1, 1, 1);
    1745 
    1746   Line = new EddLineDisplay("Edd/Rate_kBSec");
    1747   Layout->addWidget(Line, 8, 0, 1, 1);
    1748 }
    1749 
    1750 // Toggle state of Alarm server
    1751 void TP_Evidence::ToggleAlarm(bool State) {
    1752 
    1753   if (State) DimClient::sendCommandNB((char *) "Alarm/Switch", (char *) "off");
    1754   else DimClient::sendCommandNB((char *) "Alarm/Switch", (char *) "on");
    1755 }
    1756  
    1757 // Start DIM Browser
    1758 void TP_Evidence::StartDIMBrowser() {
    1759 
    1760   QProcess::startDetached("did", QStringList(), QString(getenv("DIMDIR"))+"/linux/");
    1761 }
    1762 
    1763 // Start eLogBook
    1764 void TP_Evidence::StartELog() {
    1765 
    1766   QProcess::startDetached("firefox http://fact.ethz.ch/FACTelog/index.jsp");
    1767 }
    1768 
    1769  
    1770 //--------------------------------------------------------------------
    1771 //*************************** Main GUI *******************************
    1772 //--------------------------------------------------------------------
    1773 //
    1774 // All widgets have ultimately Central as parent.
    1775 //
    1776 GUI::GUI() {
    1777 
    1778   Handler = new EddDim();
    1779  
    1780   // Set features of main window
    1781   Central = new QWidget(this);
    1782   setCentralWidget(Central);
    1783   setStatusBar(new QStatusBar(this));
    1784   setWindowTitle("Edd - Evidence Data Display");
    1785 
    1786   // Arrangement in tabs
    1787   TabWidget = new QTabWidget(Central);
    1788   TabWidget->setTabsClosable(true);
    1789   connect(TabWidget, SIGNAL(tabCloseRequested(int)), SLOT(DetachTab(int)));
    1790   TabWidget->addTab(new TP_DAQ, "Event scope " + DRSBoard);
    1791   TabWidget->addTab(new TP_FADctrl, "FADctrl");
    1792   TabWidget->addTab(new TP_Bias, "Bias");
    1793   TabWidget->addTab(new TP_Feedback, "Feedback");
    1794   TabWidget->addTab(new TP_Environment, "Environment");
    1795   TabWidget->addTab(new TP_Evidence, "Evidence");
    1796 
    1797   // Menu bar
    1798   QMenu* Menu = menuBar()->addMenu("&Menu");
    1799   Menu->addAction("New history plot", this, SLOT(MenuNewHistory()));
    1800   Menu->addSeparator();
    1801   Menu->addAction("About", this, SLOT(MenuAbout()));
    1802   Menu->addSeparator();
    1803   QAction* QuitAction = Menu->addAction("Quit", qApp, SLOT(quit()));
    1804   QuitAction->setShortcut(Qt::CTRL + Qt::Key_Q);
    1805 
    1806   // Show main window
    1807   resize(TabWidget->sizeHint()*1.1);
    1808   show();
    1809 
    1810   // Set timer to regularly check the master alarm
    1811   QTimer *Timer = new QTimer(this);
    1812   connect(Timer, SIGNAL(timeout()), this, SLOT(CheckAlarm()));
    1813   Timer->start(5000);
    1814 
    1815    
    1816 GUI::~GUI() {
    1817   delete Central;
    1818 }
    1819 
    1820 
    1821 void GUI::MenuAbout() {
    1822   QString Rev(SVN_REVISION);
    1823   Rev.remove(0,1).chop(2);
    1824  
    1825   QMessageBox::about(this, "About Edd","Evidence Data Display\n\n"
    1826     "Written by Oliver Grimm, IPP, ETH Zurich\n"
    1827     "This version compiled "__DATE__" ("+Rev+")\n\n"
    1828     "Graphical user interface implemented with Qt and Qwt.\n"
    1829     "Evidence control system based on DIM (http://dim.web.cern.ch).\n\n"
    1830     "Comments to oliver.grimm@phys.ethz.ch.");
    1831 }
    1832 
    1833 // Open request for new history plot
    1834 void GUI::MenuNewHistory() {
    1835 
    1836   QStringList List;
    1837   char *Name, *Format;
    1838   int Type;
    1839   bool OK;
    1840 
    1841   // Find all DIM services and sort
    1842   getServices("*");
    1843   while ((Type = getNextService(Name, Format)) != 0) {
    1844     if (Type==DimSERVICE) List.append(Name);
    1845   }
    1846   List.sort();
    1847 
    1848   // Open dialog and open history window
    1849   QString Result = QInputDialog::getItem(this, "Edd Request",
    1850     "Enter DIM service name", List, 0, true, &OK);
    1851   if (OK && !Result.isEmpty()) {
    1852     Result = Result.trimmed();
    1853     QWidget *Hist = OpenHistory(Result.toAscii().data(), 0);
    1854     if (Hist != NULL) Hist->show();
    1855   }
    1856 }
    1857 
    1858 // Open tab as separate window
    1859 void GUI::DetachTab(int Tab) {
    1860 
    1861   QWidget *W = NULL;
    1862   QMainWindow *M = new QMainWindow;
    1863 
    1864   M->setCentralWidget(new QWidget(M));
    1865   M->setStatusBar(new QStatusBar(M));
    1866 
    1867   switch(Tab) {
    1868         case 0: W = new TP_DAQ; break;
    1869         case 1: W = new TP_Bias; break;
    1870         case 2: W = new TP_Feedback; break;
    1871         case 3: W = new TP_Environment; break;
    1872         case 4: W = new TP_Evidence; break;
    1873         default: break;
    1874   }
    1875 
    1876   if (W == NULL) {
    1877     delete M->centralWidget();
    1878         delete M;
    1879         return;
    1880   }
    1881 
    1882   W->setParent(M);
    1883   M->resize(size());
    1884   M->setWindowTitle("Edd - " + TabWidget->tabText(Tab));
    1885   M->show();
    1886 }
    1887 
    1888 // Check alarm level and if Alarm server is alive
    1889 void GUI::CheckAlarm() {
    1890 
    1891   static int WarnedLevel = 0;
    1892   static bool AlarmServerWarned = false;
    1893 
    1894   // === Check service Alarm/MasterAlarm ===
    1895   DimCurrentInfo MasterAlarm("Alarm/MasterAlarm", -1);
    1896 
    1897   if (MasterAlarm.getInt() > WarnedLevel) {
    1898         QSound::play(QApplication::applicationDirPath() + "/Error.wav");
    1899 
    1900     // Construct warning message box
    1901         QMessageBox Box;
    1902         Box.setWindowTitle("Edd Alarm");
    1903         Box.setText("Service 'Alarm/MasterAlarm' is at " + QString::number(MasterAlarm.getInt()));
    1904         Box.setInformativeText("Warn again for the same alarm level?");
    1905         Box.setIcon(QMessageBox::Warning);
    1906         Box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
    1907         Box.setDefaultButton(QMessageBox::No);
    1908 
    1909         // Check if user wants to reset warned level
    1910         if (Box.exec() == QMessageBox::Yes) WarnedLevel = 0;
    1911         else WarnedLevel = MasterAlarm.getInt();
    1912   }
    1913 
    1914   // If MasterAlam decreased, lower also warned level
    1915   if (MasterAlarm.getInt() < WarnedLevel && MasterAlarm.getInt() >=0 ) WarnedLevel = MasterAlarm.getInt();
    1916  
    1917   // === Check Alarm server ===
    1918   DimCurrentInfo ServerList("DIS_DNS/SERVER_LIST", NO_LINK);
    1919   std::string Result = EvidenceServer::ToString((char *) "C", ServerList.getData(), ServerList.getSize());
    1920 
    1921   // Warn if SERVER_LIST does not contain alarm server
    1922   if (Result.find("Alarm@") == std::string::npos && !AlarmServerWarned) {
    1923         QMessageBox Box;
    1924         Box.setWindowTitle("Edd Alarm");
    1925         Box.setText("Alarm server is unavailable or in error");
    1926         Box.setIcon(QMessageBox::Critical);
    1927         Box.setStandardButtons(QMessageBox::Ok);
    1928         Box.setDefaultButton(QMessageBox::Ok);
    1929         Box.exec();
    1930 
    1931         AlarmServerWarned = true;
    1932   }
    1933  
    1934   if (Result.find("Alarm@") != std::string::npos) AlarmServerWarned = false;
    1935 }
    1936 
    1937 // Quit application when clicking close button on window
    1938 void GUI::closeEvent(QCloseEvent *) {
    1939 
    1940   qApp->quit();
    1941 }
    1942 
    1943 
    1944 //**************************** Main program ***************************
    1945 int main(int argc, char *argv[]) {
    1946 
    1947   if (argc > 1) DRSBoard = "FADctrl";
    1948 
    1949   // Make RPC to get pixelmap
    1950   DimRpcInfo RPC((char *) "ConfigRequest", (char *) "");
    1951   RPC.setData((char *) "Misc PixelMap");
    1952   PixelMapText = std::string(RPC.getString(), RPC.getSize());
    1953 
    1954   QApplication app(argc, argv);
    1955   GUI MainWindow;
    1956 
    1957   return app.exec();
    1958 }
     1018  M->setWindowTitle(WindowName);
     1019
     1020  L = new QGridLayout(M->centralWidget());
     1021
     1022  connect(this, SIGNAL(pressed()), SLOT(Toggle()));
     1023}
     1024
     1025// Show or hide window
     1026void EddWindow::Toggle() {
     1027
     1028  if (M->isVisible()) M->hide();
     1029  else M->show();
     1030}
     1031
     1032// Return layout
     1033QGridLayout *EddWindow::Layout() {
     1034
     1035  return L;
     1036}
     1037
     1038// Return window
     1039QMainWindow *EddWindow::Window() {
     1040
     1041  return M;
     1042}
  • fact/Evidence/GUI.h

    r10262 r10280  
    1 #ifndef EDD_H_SEEN
    2 #define EDD_H_SEEN
     1#ifndef GUI_H_SEEN
     2#define GUI_H_SEEN
    33
    44#include <QtGui>
     
    2626#include "dic.hxx"
    2727#include "Evidence.h"
    28 #include "RawDataCTX.h"
    29 #include "PixelMap.h"
    30 
    31 #define SVN_REVISION "$Revision$"
    3228
    3329const QColor EddPlotBackgroundColor(Qt::yellow);
     
    258254};
    259255
    260 //
    261 //
    262 // ====== FACT specific part ======
    263 //
    264 //
    265 
    266 // Event oscilloscope
    267 class EventScope: public EddBasePlot, public PixelMap {
    268   Q_OBJECT
    269 
    270   private:
    271     struct ItemDetails {
    272           unsigned int Board, Chip, Channel;
    273       QwtPlotCurve *Signal;
    274           QwtPlotMarker *Trigger;
    275     };
    276     QList<struct ItemDetails> List;
    277 
    278     QString Name;
    279         RawDataCTX *RD;
    280         CTX_ErrCode ErrCode;
    281         QAction *PhysPipeAction;
    282         QAction *PersistanceAction;
    283         FILE *Tmpfile;
    284 
    285   public:
    286     EventScope(QWidget * = NULL);
    287     ~EventScope();
    288        
    289         void UpdateFirst(int, int, int);
    290         void AddTrace(int, int, int);
    291         void SetActive(bool);
    292         QString ToPixel(unsigned int, unsigned int, unsigned int, unsigned int);
    293 
    294   private slots:
    295         void Update(QString, int, QByteArray, QString, QString);
    296         void PlotTraces();
    297         void DeleteCurve(QwtPlotCurve *);
    298        
    299   signals:
    300         void RunHeaderChanged(QString);
    301         void EventHeaderChanged(QString);
    302         void PixelData(QVector<double>);
    303 };
    304 
    305 
    306 // Tab page classes
    307 class TP_Environment: public QWidget {
    308   Q_OBJECT
    309 
    310   public:
    311     TP_Environment();
    312 };
    313 
    314 class TP_Bias: public QWidget {
    315   Q_OBJECT
    316 
    317   private slots:
    318         void BiasCurrents();
    319 
    320   public:
    321     TP_Bias();
    322 };
    323 
    324 class TP_FADctrl: public QWidget {
    325   Q_OBJECT
    326 
    327   public:
    328     TP_FADctrl();
    329 };
    330 
    331 class TP_Feedback: public QWidget {
    332   Q_OBJECT
    333 
    334   private slots:
    335         void FeedbackDetails();
    336 
    337   public:
    338     TP_Feedback();
    339 };
    340 
    341 class TP_DAQ: public QWidget {
    342   Q_OBJECT
    343   static const int MAXPIXEL = 1440;
    344 
    345   private:
    346         EventScope *Scope;
    347     QPlainTextEdit *RunHeaderDisplay, *EventHeaderDisplay;
    348 
    349         QSpinBox *Channel, *Chip, *Board, *PixelID;
    350         QFormLayout *FormLayout;
    351         QWidget *Display;
    352         QPushButton **Pixel;
    353         QPushButton *StartStopButton;
    354 
    355   private slots:
    356         void TranslatePixelID(int);
    357         void UpdateScope(int);
    358         void KeepCurrent();
    359         void StartStop(bool);
    360         void ShowPixelDisplay();
    361         void SetPixelData(QVector<double>);
    362 
    363   public:
    364         TP_DAQ();       
    365         ~TP_DAQ();     
    366 };
    367 
    368 class TP_Evidence: public QWidget {
    369   Q_OBJECT
    370 
    371   private slots:
    372         void ToggleAlarm(bool);
    373         void StartDIMBrowser();
    374         void StartELog();
    375 
    376   public:
    377         TP_Evidence(); 
    378 };
    379 
    380 
    381 // Main window class
    382 class GUI: public QMainWindow, public DimBrowser {
    383   Q_OBJECT
    384 
    385   private:
    386     QWidget *Central;
    387     QTabWidget *TabWidget;
    388            
    389     void closeEvent(QCloseEvent *);
    390        
    391   public:
    392     GUI();
    393     ~GUI();
    394    
    395   private slots:
    396     void MenuAbout();
    397     void MenuNewHistory();
    398         void DetachTab(int);
    399         void CheckAlarm();
     256// Open new window
     257class EddWindow: public QPushButton {
     258  Q_OBJECT
     259
     260  QMainWindow *M;
     261  QGridLayout *L;
     262
     263  public:
     264    EddWindow(QString, QString);
     265        QGridLayout *Layout();
     266        QMainWindow *Window();
     267  private slots:
     268        void Toggle();
    400269};
    401270
Note: See TracChangeset for help on using the changeset viewer.