/********************************************************************\

  DAQReadout.cc

  Main DAQ routines.
   
  Sebastian Commichau, Oliver Grimm
  
\********************************************************************/

#include "DAQReadout.h"

static char* daq_state_str[] = {"active", "stopped"};
static char* daq_runtype_str[] = {"data", "pedestal", "test"};

static struct CL_Struct { char *Name;    
                	  void (DAQReadout::*CommandPointer)();
			  bool NeedNotBusy;
			  char *Parameters;
			  char *Help;
  } CommandList[] = 
  {{"board", &DAQReadout::cmd_board, true, "<i> [j] | <all>" ,"Address board i, boards i-j, all boards"},
   {"status", &DAQReadout::cmd_status, false, "[daq|drs]", "Show DAQ/DRS status information"},
   {"freq", &DAQReadout::cmd_freq, true, "<GHz>", "Set DRS sampling frequency"},
   {"calib", &DAQReadout::cmd_calib, true, "<t_f> <c_f> [dir]", "Response calibration"},
   {"trigger", &DAQReadout::cmd_trigger, true, "<on|off>", "Hardware trigger on or off"},
   {"delayed", &DAQReadout::cmd_delayed, true, "<on|off>", "Switch delayed start on or off"},
   {"wmode", &DAQReadout::cmd_wmode, true, "<0|1>", "Set DRS wave mode"},
   {"rmode", &DAQReadout::cmd_rmode, true, "<0|1>", "Set DRS readout mode"},
   {"mode", &DAQReadout::cmd_mode, true, "<0|1>", "Set DRS mode: 0 = single shot, 1 = continuous"},
   {"read", &DAQReadout::cmd_read, true, "<brd> <chip> <chan>", "Read current data from board, chip, channel"},
   {"take", &DAQReadout::cmd_take, false, "<d|p|t> [n] [source]", "Start run (data, pedestal or test) with n events"},
   {"events", &DAQReadout::cmd_events, false, "", "Number of events in current run"},
   {"start", &DAQReadout::cmd_start, true, "", "Start domino wave"},
   {"stop", &DAQReadout::cmd_stop, false, "", "Issue soft trigger and stop DAQ"},
   {"test", &DAQReadout::cmd_test, true, "[2e]<blt32|blt64> [n]", "Test read access of VMEbus (n blocks)"},
   {"regtest", &DAQReadout::cmd_regtest, true, "", "DRS register test"},
   {"ramtest", &DAQReadout::cmd_ramtest, true, "", "DRS RAM integrity and speed test"},
   {"led", &DAQReadout::cmd_led, true, "<on|off>", "Turn LED on or off"},
   {"config", &DAQReadout::cmd_config, false, "", "Print drsdaq configuration"},
   {"serial", &DAQReadout::cmd_serial, true, "<i> <n>", "Set serial# of board <i> to <n> (experts only)"},
   {"disk", &DAQReadout::cmd_disk, false, "" ,"Remaining disk space"},
   {"uptime", &DAQReadout::cmd_uptime, false, "", "Get DAQ uptime"},		  
   {"exit", &DAQReadout::cmd_exit, false, "", "Exit program"},
   {"fmode", &DAQReadout::cmd_fmode, false, "[off|active|targ]", "Set or get feedback mode"},
   {"faverage", &DAQReadout::cmd_faverage, false, "[n]", "Set ot get number of averages for feedback"},
   {"fgain", &DAQReadout::cmd_fgain, false, "[gain]", "Set ot get feedback gain"},
   {"ftarget", &DAQReadout::cmd_ftarget, false, "[brd chip chan]", "Set or get target value"},
   {"fresponse", &DAQReadout::cmd_fresponse, false, "[V1 V2]", "Start response measurement with voltages V1 and V2"},
   {"fconfig", &DAQReadout::cmd_fconfig, false, "", "Print feedback configuration"},
   {"help", &DAQReadout::cmd_help, false, "", "Print help"}};


// -----------------------------------------------
// *****  Constructor: Class initialisation  *****
// -----------------------------------------------
//
// Note that constructor cannot report error and should not fail
 
DAQReadout::DAQReadout(const char *Configfile) {
   
  time(&StartTime);  // Start time of DAQ
  Rawfile = NULL;

  // Initialize status structure
  daq_state	= stopped;
  daq_runtype	= data;
  Socket	= -1;
  Exit		= false;
  NumEvents		= 0;
  NumEventsRequested	= 100;
  NumCMCBoards		= 0;
  FirstBoard		= 0;
  LastBoard		= -1;
  sprintf(Source,"DUMMY");
  
  // Read configuration file
  FILE *File;
  if ((File = fopen(Configfile,"r")) == NULL) {
    printf("Error: Could not open drsdaq configuration file '%s'\n", Configfile);
  }
  else {
    printf("Reading drsdaq configuration file %s\n", Configfile);
    ReadCard("LogFile",            fLogFile,            's', File);
    ReadCard("RawDataPath",        fRawDataPath,        's', File);
    ReadCard("RotateWave",         &fRotateWave,        'I', File);
    ReadCard("FirstSample",        &fFirstSample,       'I', File);
    ReadCard("LastSample",         &fLastSample,        'I', File);
    ReadCard("MinDiskSpaceMB",     &fMinDiskSpaceMB,    'U', File);
    ReadCard("MaxFileSizeMB",      &fMaxFileSizeMB,     'I', File);
    ReadCard("CCPort",             &fCCPort,            'I', File);
    ReadCard("FirstVMESlot",       &fFirstVMESlot,      'I', File);
    ReadCard("LastVMESlot",        &fLastVMESlot,       'I', File);
    ReadCard("HVFeedbackConfig",    fHVFeedbackConfig,  's', File);
    fclose(File);
  }
  if (fFirstSample < 0 || fFirstSample >= kNumberOfBins || fLastSample < 0 || fLastSample >= kNumberOfBins) {
    PrintMessage("Warning: Sample range in configuration beyond limits, setting to full range\n");
    fFirstSample = 0;
    fLastSample = kNumberOfBins;
  }

  // Open log file
  if ((Logfile = fopen(fLogFile, "a")) == NULL)
    fprintf(stderr,"Warning: Could not open log file '%s'\n", fLogFile);
  else PrintMessage(MsgToLog,"********** Logging started **********\n");
 
  cmd_config();

  // Create DRS instance and perform initial scan
  drs     = new DRS();
  drs->SetFirstVMESlot(fFirstVMESlot);
  drs->SetLastVMESlot(fLastVMESlot);
  drs->InitialScan();

  RHeader  = new RunHeader;
  EHeader  = new EventHeader;
  DRSFreq  = new float [drs->GetNumberOfBoards()];

  // Scan for DRS boards
  if (drs->GetNumberOfBoards()==0)
    PrintMessage("No DRS boards found - check VME crate and configuration file!\n");

  for (int i=0; i<drs->GetNumberOfBoards(); i++) {
    PrintMessage("Init. mezz. board %2d on VME slot %2d %s, serial #%d, firmware revision %d\n", 
      i, (drs->GetBoard(i)->GetSlotNumber() >> 1)+2, ((drs->GetBoard(i)->GetSlotNumber() & 1) == 0) ? "upper" : "lower", 
      drs->GetBoard(i)->GetCMCSerialNumber(), drs->GetBoard(i)->GetFirmwareVersion());
    NumCMCBoards++;
    LastBoard++;
    drs->GetBoard(i)->Init();
    drs->GetBoard(i)->SetRotation(fRotateWave);
    DRSFreq[i] = 0;
  }
  BStruct  = new BoardStructure [NumCMCBoards == 0 ? 1:drs->GetNumberOfBoards()];
  WaveForm = new short [NumCMCBoards == 0 ? 1:NumCMCBoards][kNumberOfChips][kNumberOfChannels][kNumberOfBins];
  
  // Create instance of HV feedback (must be after CMC board detection)
  HVFB	  = new HVFeedback(this, fHVFeedbackConfig);
}

// ------------------------
// *****  Destructor  *****
// ------------------------

DAQReadout::~DAQReadout() {
  delete RHeader;     delete EHeader;
  delete drs;	      delete HVFB;
  delete[] DRSFreq;   delete[] BStruct;
  delete[] WaveForm;
  
  PrintMessage(MsgToLog,"********** Logging ended **********\n\n");
  if(Logfile) {
    if(!fclose(Logfile)) printf("Closing logfile\n");
    else perror("Error closing logfile");
  }
}

// --------------------------------
// *****  Command evaluation  *****
// --------------------------------

int DAQReadout::CommandControl(char *Command, bool FromSocket) {

  if (strlen(Command) < 2 ) return 0;  // Ignore commands with only '/n'
  if (Command[strlen(Command)-1]=='\n') Command[strlen(Command)-1]='\0';  // Remove '/n'
  
  if(Command[0]=='.') {   // Shell command
    system(&(Command[1]));
    return 0;
  }

  for(int i=0; i<MAX_NUM_TOKEN; i++) Param[i] = "";  // All pointers point initially to empty string
  NParam = ParseInput(Command, Param);
	
  CmdFromSocket = FromSocket;
  for(CmdNumber=0; CmdNumber<sizeof(CommandList)/sizeof(CL_Struct); CmdNumber++)
    if (Match(Param[0], CommandList[CmdNumber].Name)) {
      if(CommandList[CmdNumber].NeedNotBusy && IsDAQBusy()) PrintMessage("DAQ is busy\n");
      else if(CommandList[CmdNumber].NeedNotBusy && NumCMCBoards==0) PrintMessage("No mezzanine boards available\n");
      else (this->*CommandList[CmdNumber].CommandPointer)();
      return 0;  
    }
  PrintMessage("Unknown command: %s\n",Param[0]);
  return 0;
}
  	  
// Get uptime
void DAQReadout::cmd_uptime() {
  time_t ActualT;
  time (&ActualT);
  PrintMessage("%d:%02d:%02d\n", (int) difftime(ActualT, StartTime)/3600, ((int) difftime(ActualT, StartTime)/60)%60, (int) difftime(ActualT, StartTime)%60);
} 

// Print disk space
void DAQReadout::cmd_disk() {
  PrintMessage("Free disk space (%s) [MB]: %lu\n", fRawDataPath, CheckDisk(fRawDataPath));
} 

// Print current number of events
void DAQReadout::cmd_events() {
  if(daq_state != active) PrintMessage("DAQ not active.\n");
  else PrintMessage("Current number of events: %d\n", NumEvents);
} 

// Print DAQ configuration
void DAQReadout::cmd_config() {
  PrintMessage("LogFile: %s\tRawDataPath: %s\n"
      	       "RotateWave: %d\t\tFirstSample: %d\t\tLastSample: %d\n"
     	       "MinDiskSpaceMB: %u\tMaxFileSizeMB: %d\tCCPort: %d\n"
      	       "FirstVMESlot: %d\t\tLastVMESlot: %d\n"
	       "HVFeedbackConfig: \t%s\n",
    fLogFile,fRawDataPath,fRotateWave,fFirstSample,fLastSample,fMinDiskSpaceMB,
    fMaxFileSizeMB,fCCPort,fFirstVMESlot,fLastVMESlot,fHVFeedbackConfig);
}

// Start DAQ 
void DAQReadout::cmd_take() {
  
  if(!Match(Param[1],"test") && (IsDAQBusy() || NumCMCBoards==0)) {
    PrintMessage("DAQ is busy or no boards available.\n");
    return;
  }
  if (!IsDRSFreqSet()) return;
  if (!IsCalibrationRead()) 
    if(!ReadCalibration()) {
      PrintMessage("Problem with response calibration!\n");
      return;
    }
    
  if (Match(Param[1],"data")) {
    HWTrigger(1);
    daq_runtype = data;
  } 
  else if (Match(Param[1],"pedestal")) {
    HWTrigger(0);
    daq_runtype = pedestal;	
  } 
  else if (Match(Param[1],"test")) {
    daq_runtype = test;	
  }
  else {
    PrintUsage();
    return;
  }

  if (NParam==3 && atoi(Param[2])) NumEventsRequested = atoi(Param[2]);
  if (NParam==4) {
    if(atoi(Param[2])) NumEventsRequested = atoi(Param[2]);
    strcpy(Source, Param[3]);
  }

  if ((pthread_create(&thread_DAQ, NULL, (void * (*)(void *)) DAQ,(void *) this)) != 0)
    perror("pthread_create failed with DAQ thread");
  else {
    daq_state = active;
    Stop = false;
    pthread_detach(thread_DAQ);
  }  
}
  
// Start DRS
void DAQReadout::cmd_start() {
  if (IsDRSFreqSet()) {
    StartDRS();
    PrintMessage("Domino wave started\n");
  }
}

// RAM test
void DAQReadout::cmd_ramtest() {
  for (int i=FirstBoard; i<=LastBoard; i++) {
    PrintMessage("RAM integrity and speed test (board #%d):\n",i);
    (drs->GetBoard(i))->RAMTest(3);
  }
} 

// Register test
void DAQReadout::cmd_regtest() {
  for (int i=FirstBoard; i<=LastBoard; i++) {
    PrintMessage("Register test (board #%d):\n",i);
    (drs->GetBoard(i))->RegisterTest();
  }
}

// Test VME transfer
void DAQReadout::cmd_test() {
  int Type=-1, i;
  
  if (Match(Param[1], "2eblt64")) Type = 2;
  else if (Match(Param[1], "blt32")) Type = 0;
  else if (Match(Param[1], "blt64")) Type = 1;
  else {
    PrintMessage("Unknown type for testing.\n");
    return;
  }
  
  if (NumCMCBoards) 
    for (i=FirstBoard; i<=LastBoard; i++) {
      PrintMessage("BLT test started (board #%d)\n",i);
      (drs->GetBoard(i))->TestRead(Param[2][0] && atoi(Param[2])<=10000 && atoi(Param[2])>0 ? atoi(Param[2]):1, Type);
    }
  else PrintMessage("No DRS boards available\n");
} 

// Stop DAQ 
void DAQReadout::cmd_stop() {
  if(!IsDAQBusy() && !IsDRSBusy()) PrintMessage("Nothing to stop\n");
  if (IsDAQBusy()) StopRun();
  if (IsDRSBusy()) {
    StopDRS();
    PrintMessage("Domino wave stopped\n");
  }
} 
  
// Read data 
void DAQReadout::cmd_read() {
  if (IsDRSBusy()) {
    PrintMessage("Domino wave is running, issue \"stop first\"\n");
    return;
  } 
  if (Param[1][0] && Param[2][0] && Param[3][0]) {
    if (IsDRSFreqSet()&& !IsCalibrationRead()) ReadCalibration();
    ReadandPrintDRSData(atoi(Param[1]),atoi(Param[2]),atoi(Param[3]));
  }
  else PrintUsage();  
} 

// Set Domino mode
void DAQReadout::cmd_mode() {
  if (Match(Param[1],"continuous")) SetDOMINOMode(1);
  else if (Match(Param[1],"single")) SetDOMINOMode(0);
  else PrintUsage();
} 

// Set Domino readout mode
void DAQReadout::cmd_rmode() {
  if (Match(Param[1],"1")) SetDOMINOReadMode(1);
  else if (Match(Param[1],"0")) SetDOMINOReadMode(0);
  else PrintUsage();
} 

// Set Domino wave mode
void DAQReadout::cmd_wmode() {
  if (Match(Param[1],"1")) SetDOMINOWaveMode(1);
  else if (Match(Param[1],"0")) SetDOMINOWaveMode(0);
  else PrintUsage();
} 

// Switch delayed start on/off
void DAQReadout::cmd_delayed() {
  if (Match(Param[1],"on")) SetDelayedStart(1);
  else if (Match(Param[1],"off")) SetDelayedStart(0);
  else PrintUsage();
} 

// Set trigger mode
void DAQReadout::cmd_trigger() {
  if (Match(Param[1],"on")) HWTrigger(1);
  else if (Match(Param[1],"off")) HWTrigger(0);
  else PrintUsage();
} 

// Set serial number of board
void DAQReadout::cmd_serial() {    
  if (NParam==4 && Match(Param[3], "expert")) {
    if ((atoi(Param[1]) < FirstBoard) || (atoi(Param[1]) > LastBoard))
      PrintMessage("Board number out of range (%d...%d)!\n",FirstBoard,LastBoard);
    else if (atoi(Param[2]) < 100 || atoi(Param[2]) >= 1000) 
      PrintMessage("Serial number out of range (100...999)!\n");
    else {
      PrintMessage("Flashing EEPROM of board %d...\n", atoi(Param[1]));
      (drs->GetBoard(atoi(Param[1])))->FlashEEPROM(atoi(Param[2]));
    }
  }
  else PrintMessage("You are not allowed to change the serial number!\n");
} 

// Do internal calibration
void DAQReadout::cmd_calib() {
  if (NParam==4 && atof(Param[1]) && atof(Param[2]))
    CalibrateDRS(Param[3],atof(Param[1]),atof(Param[2])); 
  else if (NParam==3 && atof(Param[1]) && atof(Param[2]))
    CalibrateDRS(NULL,atof(Param[1]),atof(Param[2])); 
  else PrintUsage();
}

// Set DRS sampling frequency
void DAQReadout::cmd_freq() {    
  if (NParam==3 && atof(Param[1]) && atoi(Param[2]))
    SetRegulatedDRSFrequency(atof(Param[1]));
  else if (NParam==2 && atof(Param[1]))
    SetDRSFrequency(atof(Param[1]));
  else PrintUsage();
} 

// Set LED
void DAQReadout::cmd_led() {
  if (Match(Param[1], "on") || Match(Param[1], "off"))
    for (int i=FirstBoard; i<=LastBoard; i++)
      (drs->GetBoard(i))->SetLED(Match(Param[1], "on") ? 1 : 0);     
  else PrintUsage();
}

// Print status
void DAQReadout::cmd_status() {
  
  double freq;
  
  if(NParam==1 || Match(Param[1],"daq")) {
    PrintMessage("********** DAQ STATUS **********\n"
 	        " DAQ: %s\n"
  	        " Run number: %d\n"
 	        " Run type: %c\n"
	        " Event: %d\n"
      	        " Requested events per run: %d\n"
 	        " Storage directory: %s\n"
 	        " Disk space: %lu MByte\n"
 	      	" Socket state: %s\n"                              
              	" Total number of CMC boards: %d\n"
	        " Active CMC boards: %d\n",
      daq_state_str[daq_state], daq_state==active ? (int) RunNumber:-1,
      daq_runtype_str[daq_runtype][0], NumEvents,
      NumEventsRequested, fRawDataPath,
      CheckDisk(fRawDataPath), Socket==-1 ? "disconnected":"connected",
      NumCMCBoards, LastBoard - FirstBoard + 1);

    for (int i=FirstBoard;i<=LastBoard;i++)
      PrintMessage(" Frequency of board %d set: %s\n",i,(DRSFreq[i]!=0 ? "yes":"no"));
  }
  
  if(NParam==1 || Match(Param[1],"drs")) {
    PrintMessage("\n********** DRS STATUS **********\n");
    if (NumCMCBoards) {
      for (int i=FirstBoard; i<=LastBoard; i++) {

	PrintMessage(" Mezz. board index:    %d\n"
                    " Slot:                 %d %s\n",i,((drs->GetBoard(i))->GetSlotNumber() >> 1)+2,((drs->GetBoard(i))->GetSlotNumber() & 1)==0 ? "upper":"lower");
	PrintMessage(" Chip version:         DRS%d\n"
                    " Board version:        %d\n"
                    " Serial number:        %d\n"
                    " Firmware revision:    %d\n"
                    " Temperature:          %1.1lf C\n"
                    " Status reg.:          0X%08X\n", 
		    (drs->GetBoard(i))->GetChipVersion(),
		    (drs->GetBoard(i))->GetCMCVersion(),
		    (drs->GetBoard(i))->GetCMCSerialNumber(),
		    (drs->GetBoard(i))->GetFirmwareVersion(),
		    (drs->GetBoard(i))->GetTemperature(),
		    (drs->GetBoard(i))->GetStatusReg());


	if ((drs->GetBoard(i))->GetStatusReg() & BIT_RUNNING)
	  PrintMessage("   Domino wave running\n");
	if ((drs->GetBoard(i))->GetStatusReg() & BIT_NEW_FREQ1)
	  PrintMessage("   New Freq1 ready\n");
	if ((drs->GetBoard(i))->GetStatusReg() & BIT_NEW_FREQ2)
	  PrintMessage("   New Freq2 ready\n");

	PrintMessage(" Control reg.:         0X%08X\n", (drs->GetBoard(i))->GetCtrlReg());
	if ((drs->GetBoard(i))->GetCtrlReg() & BIT_AUTOSTART)
	  PrintMessage("   AUTOSTART enabled\n");
	if ((drs->GetBoard(i))->GetCtrlReg() & BIT_DMODE)
	  PrintMessage("   DMODE circular\n");
	else
	  PrintMessage("   DMODE single shot\n");
	if ((drs->GetBoard(i))->GetCtrlReg() & BIT_LED)
          PrintMessage("   LED\n");
	if ((drs->GetBoard(i))->GetCtrlReg() & BIT_TCAL_EN)
	  PrintMessage("   TCAL enabled\n");
	if ((drs->GetBoard(i))->GetCtrlReg() & BIT_ZERO_SUPP)
	  PrintMessage("   ZERO_SUPP enabled\n");
	if ((drs->GetBoard(i))->GetCtrlReg() & BIT_FREQ_AUTO_ADJ)
	  PrintMessage("   FREQ_AUTO_ADJ enabled\n");
	if ((drs->GetBoard(i))->GetCtrlReg() & BIT_ENABLE_TRIGGER)
	  PrintMessage("   ENABLE_TRIGGER\n");
	if ((drs->GetBoard(i))->GetCtrlReg() & BIT_LONG_START_PULSE)
	  PrintMessage("   LONG_START_PULSE\n");
	if ((drs->GetBoard(i))->GetCtrlReg() & BIT_DELAYED_START)
	  PrintMessage("   DELAYED_START\n");
	if ((drs->GetBoard(i))->GetCtrlReg() & BIT_ACAL_EN)
	  PrintMessage("   ACAL enabled\n");
	PrintMessage(" Trigger bus:          0X%08X\n", (drs->GetBoard(i))->GetTriggerBus());
	if ((drs->GetBoard(i))->IsBusy()) {
	  (drs->GetBoard(i))->ReadFrequency(0, &freq);
	  PrintMessage(" Frequency0:           %1.4lf GHz\n", freq);
	  (drs->GetBoard(i))->ReadFrequency(1, &freq);
	  PrintMessage(" Frequency1:           %1.4lf GHz\n", freq);
	} 
	else PrintMessage(" Domino wave stopped\n");
      }
    }
    else PrintMessage("No DRS boards available!\n\n");
  }
}

// Adress DRS boards
void DAQReadout::cmd_board() {
  if (Match(Param[1],"all")) {
    FirstBoard = 0;
    LastBoard = drs->GetNumberOfBoards()-1;
  } 
  else if (NParam==2 && atoi(Param[1]) >= 0 && atoi(Param[1]) < NumCMCBoards) {
    FirstBoard = atoi(Param[1]);
    LastBoard = FirstBoard;
  }
  else if (NParam==3 && atoi(Param[1])>=0 && atoi(Param[1])<NumCMCBoards && 
           atoi(Param[2])>0 && atoi(Param[2])<NumCMCBoards) {
    FirstBoard = atoi(Param[1]);
    LastBoard = atoi(Param[2]);
  }
  else PrintMessage("Cannot address board(s), out of range.\n");
} 

// Print help
void DAQReadout::cmd_help() {
  char Buffer[MAX_COM_SIZE];
  for(unsigned int i=0; i<sizeof(CommandList)/sizeof(CL_Struct); i++) {
    sprintf(Buffer, "%s %s", CommandList[i].Name, CommandList[i].Parameters);
    PrintMessage(MsgToConsole|MsgToSocket,"%-28s%s\n", Buffer, CommandList[i].Help);
  }     
  PrintMessage(MsgToConsole|MsgToSocket,".<command>                  Execute shell command\n\n"
   "Items in <> are mandatory, in [] optional, | indicates mutual exclusive or.\n"
   "Test data can also be written if no DRS boards are available.\n"
   "Strings containing spaces have to be enclosed in \"double quotes\".\n"); 
}

// Exit programm
void DAQReadout::cmd_exit() {
  if (CmdFromSocket) {
     PrintMessage("Exit command not allowed over socket.\n");
     return;
  }  	
  if (IsDAQBusy()) PrintMessage("Issue \"stop\" first to stop daq\n");
  else {
    Exit = true;
    if(SocketThread != NULL) pthread_kill(*SocketThread, SIGUSR1);
  }
}

// Set/get mode of feedback
void DAQReadout::cmd_fmode() {
  if(NParam==1) HVFB->GetFBMode();
  else if(Match(Param[1],"off")) HVFB->SetFBMode(FB_Off);
  else if(Match(Param[1],"active")) HVFB->SetFBMode(FB_Active);
  else if(Match(Param[1],"targets")) HVFB->SetFBMode(FB_Targets);
  else PrintUsage();
}

// Set/get current number of events
void DAQReadout::cmd_faverage() {
  if(NParam==1) printf("Current number of feedback events: %u   (acting when %u events are reached)\n",
                       HVFB->GetCurrentCount(), HVFB->GetNumAverages());
  else if(atoi(Param[1])>=0) HVFB->SetNumAverages(atoi(Param[1]));
  else PrintUsage();
}

// Set/get feedback gain
void DAQReadout::cmd_fgain() {
  if(NParam==1) printf("Feedback gain is %.2f\n", HVFB->GetGain());
  else if(NParam==2) HVFB->SetGain(atof(Param[1]));
  else PrintUsage();
}

// Set/get target value
void DAQReadout::cmd_ftarget() {
  if(NParam==1) HVFB->GetTargets();
  else if(NParam!=5) PrintUsage();
  else {
    if(atoi(Param[1])>=0 && atoi(Param[1])<NumCMCBoards && atoi(Param[2])>=0 && 
       atoi(Param[2])<kNumberOfChips && atoi(Param[3])>=0 && atoi(Param[3])<kNumberOfChannels)
      	 HVFB->SetTarget(atoi(Param[1]),atoi(Param[2]),atoi(Param[3]),atoi(Param[4]));
    else PrintMessage("Board, chip or channel number out of range.\n");
  }
}

// Start response measurement
void DAQReadout::cmd_fresponse() {
  if(NParam==1) HVFB->GetResponse();
  else if(atof(Param[1]) && atof(Param[2]))
    HVFB->MeasureResponse(atof(Param[1]),atof(Param[2]));
  else PrintUsage();
}

// Print feedback configuration
void DAQReadout::cmd_fconfig() {
  HVFB->PrintConfig();
}

// ----------------------------------------------
// *****  Utility function for DRS control  *****
// ----------------------------------------------

// Start domino wave
void DAQReadout::StartDRS() {
  for (int i=FirstBoard; i<=LastBoard; i++) drs->GetBoard(i)->StartDomino();
}

// Stop domino wave
void DAQReadout::StopDRS() {
  for (int i=FirstBoard; i<=LastBoard; i++) drs->GetBoard(i)->SoftTrigger();
}

// Read current data
void DAQReadout::ReadandPrintDRSData(int board, int chip, int channel) {

  if (board>LastBoard || board<FirstBoard) {
    PrintMessage("Error: Board %d does not exist\n",board);
    return;
  }
  if (channel<0 || channel>kNumberOfChannels-1) {
    PrintMessage("Error: Select channel between %d and %d\n",0,kNumberOfChannels-1);
    return;
  }
  if (chip<0 || chip>1) {
    PrintMessage("Error: Select chip index between 0 and 1\n");
    return;
  }
  PrintMessage("Waveform from board %d, chip %d, channel %d\n",board,chip,channel);
  PrintMessage("Note: The first number is the number of numbers that follows.\n");

  ReadCalibratedDRSData();
  
  // Note that all numbers must be separated by exactly one whitespace
  // to allow reading from client socket  
  PrintMessage("==START== %d ",kNumberOfBins);
  for (int k=0; k<kNumberOfBins; k++) PrintMessage("%.1f ", (float) WaveForm[board][chip][channel][k]);
  PrintMessage("==END==");
  PrintMessage("\n");
}

// Transfer data to memory
void DAQReadout::ReadCalibratedDRSData() {

  for (int i=FirstBoard; i<=LastBoard; i++) {
    (drs->GetBoard(i))->TransferWaves(kNumberOfChannels*kNumberOfChips); 
    for (int ch=0; ch<kNumberOfChannels; ch++) {
       (drs->GetBoard(i))->GetWave(0, ch, WaveForm[i][0][ch], true); // Chip #1
       (drs->GetBoard(i))->GetWave(1, ch, WaveForm[i][1][ch], true); // Chip #2
    }
  }
}

// Read calibration file
bool DAQReadout::ReadCalibration() {

  char dir[MAX_COM_SIZE];

  getcwd(dir, sizeof(dir));
  strcat(dir,"/calib");
  for (int i=FirstBoard; i<=LastBoard; i++) {
    (drs->GetBoard(i))->SetCalibrationDirectory(dir);
    PrintMessage("Reading response calibration file for board %d from: \"%s\"\n",i,dir);
    for (int Chip=0; Chip<kNumberOfChips; Chip++)
      if (drs->GetBoard(i)->GetResponseCalibration()->ReadCalibration(Chip)==false) return false;
  }
  return true;
}

// Check if calibration file has been read
bool DAQReadout::IsCalibrationRead() {

  for (int i=FirstBoard; i<=LastBoard; i++) {
    for (int Chip=0; Chip<kNumberOfChips; Chip++) 
      if (!(drs->GetBoard(i)->GetResponseCalibration()->IsRead(Chip))) {
	PrintMessage("Warning: Response calibration of board %d chip %d not yet read!\n",i,Chip);
	return false;
      }
  }
  return true;
}

// Stop DAQ
void DAQReadout::StopRun() {

  if(daq_state != active) PrintMessage("DAQ is not active.\n");
  else {
    Stop = true;
    PrintMessage("DAQ will stop.\n");
  }
}

// Set DOMINO mode 
void DAQReadout::SetDOMINOMode(int mode) {
 
  if (NumCMCBoards) 
    for (int i=FirstBoard; i<=LastBoard; i++) {
      (drs->GetBoard(i))->SetDominoMode(mode==1 ? 1:0);
      PrintMessage("Domino mode of board %d switched to %s.\n",i,mode==1 ? "continuous":"single shot");
    } 
  else PrintMessage("No DRS boards available\n");
}

// Set DOMINO readout mode 
void DAQReadout::SetDOMINOReadMode(int mode) {

  if (NumCMCBoards) 
    for (int i=FirstBoard; i<=LastBoard; i++) {
      (drs->GetBoard(i))->SetReadoutMode(mode==1 ? 1:0);
      PrintMessage("Start readout of board %d from %s.\n",i,mode==1 ? "first bin":"stop position");
    } 
  else PrintMessage("No DRS boards available\n");
}

// Set DOMINO wave mode 
void DAQReadout::SetDOMINOWaveMode(int mode) {

  if (NumCMCBoards) 
    for (int i=FirstBoard; i<=LastBoard; i++) {
      (drs->GetBoard(i))->SetDominoActive(mode==1 ? 1:0);
      PrintMessage("Domino wave of board %d is %s during readout\n",i,mode==1 ? "running":"stopped");
    } 
  else PrintMessage("No DRS boards available\n");
}

// Delayed start on/off 
void DAQReadout::SetDelayedStart(int mode) {

  if (NumCMCBoards) 
    for (int i=FirstBoard; i<=LastBoard; i++) {
      (drs->GetBoard(i))->SetDelayedStart(mode==1 ? 1:0);
       PrintMessage("Delayed start of board %d is %s\n",i,mode==1 ? "on":"off");
    } 
  else PrintMessage("No DRS boards available\n");
}

// Enable hardware trigger of all boards 
void DAQReadout::HWTrigger(int mode) {

  if (NumCMCBoards) 
    for (int i=FirstBoard; i<=LastBoard; i++) {
      drs->GetBoard(i)->EnableTrigger(mode==1 ? 1:0);
      PrintMessage("Hardware trigger of board %d %s\n",i,mode==1 ? "enabled":"disabled");
    }
  else PrintMessage("No DRS boards available\n");
}

// Read the frequency of all boards 
double DAQReadout::ReadDRSFrequency() {

  double freq = 0;
  
  if (NumCMCBoards) 
    for (int i=FirstBoard; i<=LastBoard; i++) {
      (drs->GetBoard(i))->ReadFrequency(0, &freq); 
       PrintMessage("Domino wave of board %d is running at %1.3lf GHz\n",i,freq);
    } 
  else PrintMessage("No DRS boards available\n");

  return freq;
}

// Set DRS sampling frequency 
void DAQReadout::SetDRSFrequency(double freq) {

  double currentfreq;

  if (NumCMCBoards) {
    PrintMessage("Setting frequency without regulation:\n");
    
    for (int i=FirstBoard; i<=LastBoard; i++) { 
      drs->GetBoard(i)->SetDebug(1);
      
      if (drs->GetBoard(i)->SetFrequency(freq)) {
 	drs->GetBoard(i)->ReadFrequency(0, &currentfreq); 
	DRSFreq[i] = freq;
	PrintMessage("Domino wave of board %d is running at %1.3lf GHz\n",i,currentfreq);
      } else {
	DRSFreq[i] = 0;
	PrintMessage("Warning: domino wave of board %d has changed but not reached the requested value\n",i);
      }
    }
  }  
  else PrintMessage("No DRS boards available\n");
}

// Regulate DRS sampling frequency 
void DAQReadout::SetRegulatedDRSFrequency(double freq) {

  double currentfreq;

  if (NumCMCBoards) {
    PrintMessage("Setting frequency with regulation:\n");

    for (int i=FirstBoard; i<=LastBoard; i++) {
      drs->GetBoard(i)->SetDebug(1);
      
      if (drs->GetBoard(i)->RegulateFrequency(freq)) {
	
	  drs->GetBoard(i)->ReadFrequency(0, &currentfreq); 
	  PrintMessage("Domino wave of board %d is running at %1.3lf GHz\n",i,currentfreq);
	  DRSFreq[i] = freq;;
      }
      else DRSFreq[i] = 0;
    }
  }
  else PrintMessage("No DRS boards available\n");
}

// Do internal calibration
void DAQReadout::CalibrateDRS(char *dir, double trigfreq, double calibfreq) {

  int i,j;
  char str[MAX_COM_SIZE];
  DIR *pdir;
      
  if (NumCMCBoards) {
    if(dir!=NULL) {
      if ((pdir=opendir(str))==0){
	PrintMessage("Error: target directory \"%s\" does not exist!\n",str);
        return;
      }
      closedir(pdir);
      sprintf(str,"%s",dir);
      PrintMessage("Target: \"%s\"\n",str);
    }
    else {
      getcwd(str, sizeof(str));
      strcat(str,"/calib");
      PrintMessage("Taking default target: \"%s/\"\n",str);
    }
        
    for (i=FirstBoard; i<=LastBoard; i++) {
      drs->GetBoard(i)->Init();
      drs->GetBoard(i)->SetFrequency(calibfreq);
      drs->GetBoard(i)->SoftTrigger();
            
      PrintMessage("Creating calibration of board %d\n", drs->GetBoard(i)->GetCMCSerialNumber());

      drs->GetBoard(i)->EnableTcal(1);
      PrintMessage("Tcal enabled");

      if (drs->GetBoard(i)->GetChipVersion() == 3)
	drs->GetBoard(i)->GetResponseCalibration()->SetCalibrationParameters(1,21,0,20,0,0,0,0,0);
      else
	drs->GetBoard(i)->GetResponseCalibration()->SetCalibrationParameters(1,36,110,20,19,40,15,trigfreq,0);
            
      drs->GetBoard(i)->SetCalibrationDirectory(str);
      PrintMessage("Storage directory \"%s\"\n",str);

      for (j=0;j<2;j++) {
	drs->GetBoard(i)->GetResponseCalibration()->ResetCalibration();
	PrintMessage("Calibration reset done.\n");

	while (!drs->GetBoard(i)->GetResponseCalibration()->RecordCalibrationPoints(j)) {}
	PrintMessage("Record calibration points done.\n");
	while (!drs->GetBoard(i)->GetResponseCalibration()->FitCalibrationPoints(j)) {}
	PrintMessage("Calibration points fitted.\n");
	while (!drs->GetBoard(i)->GetResponseCalibration()->OffsetCalibration(j)) {}
	PrintMessage("Offset calibration done.\n");

	if (!drs->GetBoard(i)->GetResponseCalibration()->WriteCalibration(j))
	  break;
      }        
      drs->GetBoard(i)->Init(); // Reset linear range -0.2 ... 0.8 V
    } // Loop over boards   
  }
  else PrintMessage("No DRS boards available\n");
}

// Check if DAQ is busy
bool DAQReadout::IsDAQBusy() {

  if (daq_state == active) {
    PrintMessage("DAQ is busy\n");
    return true;
  }
  else return false;
}

// Check if DRS is sampling
bool DAQReadout::IsDRSBusy() {

  for (int i=FirstBoard; i<=LastBoard; i++)     
    if ((drs->GetBoard(i))->IsBusy()) return true;
  return false;
}

// Check if DRS frequency is set
bool DAQReadout::IsDRSFreqSet() {

  for (int i=FirstBoard;i<=LastBoard;i++)
    if (DRSFreq[i]==0) {
      PrintMessage("DRS sampling frequency of board %d not set!\n",i);
      return false;
    }
  return true;
}

// Open new raw data file
bool DAQReadout::OpenRawFile(int Part) {

  time_t rawtime;
  struct tm *timeinfo;
  char RunDate[MAX_COM_SIZE], Buffer[MAX_COM_SIZE], SystemCommand[MAX_COM_SIZE];
  int TempDescriptor;
  
  // Write run date to status structure
  time(&rawtime);
  timeinfo = localtime(&rawtime);
  sprintf(RunDate,"%d%02d%02d",timeinfo->tm_year + 1900,timeinfo->tm_mon + 1,timeinfo->tm_mday);

  // Create direcory if not existing (ignore error if already existing) and change to it
  sprintf(Buffer, "%s/%s", fRawDataPath, RunDate);
  if(mkdir(Buffer, S_IRWXU|S_IRWXG)==-1 && errno!=EEXIST) {
    PrintMessage("\rError: Could not create direcory \"%s\" (%s)\n", Buffer, strerror(errno));
    return false;
  }
  
  // Determine new run number in directory (only if first file in series) by finding the
  // last file in alphabetical order and assuming that run number starts at position 9
  if(Part==0) {
    char *TmpName = tmpnam(NULL); 
    sprintf(SystemCommand, "ls -1 %s/*.raw 2>/dev/null|tail -n-1 >%s", Buffer, TmpName);
    system(SystemCommand);
    if ((TempDescriptor=open(TmpName,O_RDONLY)) == -1) {
      PrintMessage("Error: Could not determine last run number (%s)\n",strerror(errno));
      return false;
    }
    memset(Buffer,0,sizeof(Buffer));
    read(TempDescriptor, Buffer, sizeof(Buffer));
    close(TempDescriptor);
    remove(TmpName);
    if(sscanf(Buffer, "%*28c%u", &RunNumber) == 1) RunNumber++;
    else RunNumber = 0;
  }

  // Generate filename
  sprintf(FileName,"%s/%s/%s_%.8d_%s_%c_%d.raw", fRawDataPath, RunDate,
    RunDate,RunNumber,Source,daq_runtype_str[daq_runtype][0],Part);
 
  //  Open file with rwx right for owner and group, never overwrite file
  TempDescriptor = open(FileName,O_WRONLY|O_CREAT|O_EXCL, S_IRWXU|S_IRWXG);
  if(TempDescriptor==-1) {
    PrintMessage("\rError: Could not open file \"%s\"\n",FileName);
    perror("Error");
    return false;
  }
  Rawfile = fdopen(TempDescriptor,"w"); 
  return true;
}

// Write run header and board structures
void DAQReadout::WriteRunHeader() {

  time_t time_now_secs;
  struct tm *time_now;

  RHeader->MagicNum = MAGICNUM_FILE_OPEN;
  RHeader->DataFormat 	= DATA_FORMAT;
  strcpy(RHeader->DAQVersion,   __DATE__);
  strcpy(RHeader->Source,       Source);
  RHeader->Type = daq_runtype_str[daq_runtype][0];
  RHeader->RunNumber  = RunNumber;

  time(&time_now_secs);
  time_now = localtime(&time_now_secs);
  
  RHeader->StartYear   = 1900 + time_now->tm_year;
  RHeader->StartMonth  = 1 + time_now->tm_mon;
  RHeader->StartDay    = time_now->tm_mday;
  RHeader->StartHour   = time_now->tm_hour;
  RHeader->StartMinute = time_now->tm_min;
  RHeader->StartSecond = time_now->tm_sec;
  
  RHeader->SourceRA     = -1;    
  RHeader->SourceDEC    = -1;   
  RHeader->TelescopeRA  = -1; 
  RHeader->TelescopeDEC = -1;

  RHeader->NCMCBoards = NumCMCBoards==0 && daq_runtype==test ? 1 : (LastBoard - FirstBoard) + 1;  
  RHeader->NChips       = kNumberOfChips;
  RHeader->NChannels    = kNumberOfChannels;

  RHeader->Offset       = fFirstSample;
  RHeader->Samples      = fLastSample - fFirstSample + 1;
  
  if(fwrite(RHeader, sizeof(RunHeader), 1, Rawfile) != 1) {
    PrintMessage("Error: Could not write run header, terminating run (%s)\n", strerror(errno));
    Stop = true;
  }

  for (int i=FirstBoard; i<=LastBoard; i++) {
    BStruct[i].Index       = i;	  
    BStruct[i].SerialNo    = drs->GetBoard(i)->GetCMCSerialNumber();	  
    BStruct[i].BoardTemp   = drs->GetBoard(i)->GetTemperature();
    BStruct[i].NomFreq     = DRSFreq[i];
    BStruct[i].ScaleFactor = drs->GetBoard(i)->GetPrecision();
  }

  // In case no boards are available, dummy data is written for one board structure   
  if (NumCMCBoards == 0) {
    LastBoard=0;
    BStruct[0].NomFreq     = 1;
    BStruct[0].ScaleFactor = 0.1;
  }    

  if(fwrite(BStruct, sizeof(BoardStructure), LastBoard-FirstBoard+1, Rawfile) != (unsigned int) (LastBoard-FirstBoard+1)) {
    PrintMessage("Error: Could not write (all) board structures, terminating run (%s)\n", strerror(errno));
    Stop = true;
  }
  if (NumCMCBoards == 0) LastBoard=-1;
}

// Update the run header
void DAQReadout::UpdateRunHeader(unsigned int Events) {

  time_t time_now_secs;
  struct tm *time_now;
  
  RHeader->MagicNum = MAGICNUM_FILE_CLOSED;
  RHeader->Events     = Events;

  time(&time_now_secs);
  time_now = localtime(&time_now_secs);
  
  RHeader->EndYear     = 1900 + time_now->tm_year;
  RHeader->EndMonth    = 1 + time_now->tm_mon;
  RHeader->EndDay      = time_now->tm_mday;
  RHeader->EndHour     = time_now->tm_hour;
  RHeader->EndMinute   = time_now->tm_min;
  RHeader->EndSecond   = time_now->tm_sec;

  rewind(Rawfile);
  if(fwrite(RHeader, sizeof(RunHeader), 1, Rawfile) != 1) {
    PrintMessage("Error: Could not write updated run header, terminating run (%s)\n", strerror(errno));
    Stop = true;
  }
}

// Write event header
void DAQReadout::WriteEventHeader() {

  time_t time_now_secs;
  struct tm *time_now;
  struct timezone tz;
  struct timeval actual_time;

  strcpy(EHeader->Name,"EVTH");
 
  EHeader->EventNumber = NumEvents;
  EHeader->TriggerType = 0XFFFF;

  time(&time_now_secs);
  time_now = localtime(&time_now_secs);
  
  gettimeofday(&actual_time, &tz);
  EHeader->TimeSec = time_now->tm_sec + actual_time.tv_usec/1000000.;

  if(fwrite(EHeader, sizeof(EventHeader), 1, Rawfile) != 1) {
    PrintMessage("Error: Could not write event header, terminating run (%s)\n", strerror(errno));
    Stop = true;
  }
}

// Print usage text for command
void DAQReadout::PrintUsage() {
  PrintMessage("Usage: %s %s\n", CommandList[CmdNumber].Name, CommandList[CmdNumber].Parameters);
}
	 
// Print message to selected target
void DAQReadout::PrintMessage(int Target, char *Format, ...) {
  va_list ArgumentPointer;
  va_start(ArgumentPointer, Format); 
  PrintMessage(Target, Format, ArgumentPointer);
  va_end(ArgumentPointer);
}

// Print message to screen, log file and socket
void DAQReadout::PrintMessage(char *Format, ...) {
  va_list ArgumentPointer;
  va_start(ArgumentPointer, Format); 
  PrintMessage(MsgToConsole|MsgToLog|MsgToSocket, Format, ArgumentPointer);
  va_end(ArgumentPointer);
}

// Function doing the actual printing work
void DAQReadout::PrintMessage(int Target, char *Format, va_list ArgumentPointer) {

  char Textbuffer[MAX_COM_SIZE];

  memset(Textbuffer, 0, sizeof(Textbuffer));  
  vsprintf(Textbuffer, Format, ArgumentPointer);
  
  // Print to console and generate new prompt
  if(Target & MsgToConsole) {
    if(strlen(Textbuffer)>0 && Textbuffer[strlen(Textbuffer)-1]=='\n') printf("\r%s", Textbuffer);
    else printf("%s", Textbuffer);

    // New prompt only after newline
    if(Textbuffer[strlen(Textbuffer)-1]=='\n' || strlen(Textbuffer)==0) {
      if (NumCMCBoards == 0) printf("\rDAQ> "); 
      else if (FirstBoard == LastBoard) printf("\rDAQ|B%d> ",FirstBoard); 
      else printf("\rDAQ|B%d-%d> ",FirstBoard,LastBoard); 
      fflush(stdout);
    }
  }

  // Print to log file and socket only if length not zero (then only prompt)
  if (strlen(Textbuffer)>0) {
    if((Target & MsgToLog) && Logfile!=NULL) {
      fprintf(Logfile, "%s", Textbuffer);
      fflush(Logfile);
    }
    if((Target & MsgToSocket) && Socket!=-1) write(Socket, Textbuffer, strlen(Textbuffer));
  }
}


// ---------------------------------------
// *****  Various utility functions  *****
// ---------------------------------------

// Check if two strings match (min 1 character must match)
int Match(char *str, char *cmd) {
  return strncasecmp(str,cmd,strlen(str)==0 ? 1:strlen(str)) ? 0:1;
}

// Return current available storage space in given directory
int CheckDisk(char *Directory) {
  struct statfs FileSystemStats;

  statfs(Directory, &FileSystemStats);
  return FileSystemStats.f_bavail / 1024 * (FileSystemStats.f_bsize / 1024);
}

// Parse command line for white space and double-quote separated tokens 
int ParseInput(char* Command, char *Param[]) {
  int Count=0;
   
  while(Count<MAX_NUM_TOKEN) {
    while (isspace(*Command)) Command++; // Ignore initial white spaces
    if(*Command=='\0') break;
    if (*Command == '\"') {
      Param[Count] = ++Command;
      while(*Command!='\"' && *Command!='\0') Command++;
    }
    else {
      Param[Count] = Command;
      while(!isspace(*Command) && *Command!='\0') Command++;
    }
    *Command++ = '\0';
    Count++;
  };
  return Count;
}

// ReadCard function by F. Goebel
int ReadCard(char *card_flag, void *store, char type, FILE *fptr) {
  
  char *card_name, *card_val, line[160];

  rewind(fptr);

  while (fgets(line, 160, fptr) != NULL) {    // Read line by line
    card_name = strtok(line," \t\n");
    card_val  = strtok(NULL," \t\n");
    
    if (   card_name    != NULL  && card_val     != NULL   // Comment or empty line?
        && card_name[0] != '*'   && card_name[0] != '#') {
      
      if (strcmp(card_name, card_flag)!=0) {   // Is this the card name we are looking for?
	continue;  
      }

      switch (type) {
      case 'I':	*((int *) store)   = (int) strtol(card_val, (char**)NULL, 10);
	      	break;
      case 'i':	*((short *) store) = (short) strtol(card_val, (char**)NULL, 10);
	      	break;
      case 'U':	*((unsigned int *) store) = (unsigned int) strtoul(card_val, (char**)NULL, 10);
	      	break;
      case 'u':	*((unsigned short *) store) = (unsigned short) strtoul(card_val, (char**)NULL, 10);
	      	break;
      case 'f': *((float *) store) = atof(card_val);
	      	break;
      case 'd':	*((double *) store) = atof(card_val);
	      	break;
      case 's':	sprintf((char *) store,"%s",card_val);
	      	break;
      case 'c':	*((char *) store) = card_val[0];
	      	break;
      default:	fprintf(stderr,"WARNING: ReadCard: unknown type: %c\n", type);
	      	return -2;
      }
      return 0; // Found card name
    }
  }
  fprintf(stderr,"WARNING: ReadCard: card: %s not found\n", card_flag);
  return -1;
}


/********************************************************************\

  DAQ Thread

  This thread takes data until the requested number of events is reached,
  until no more disk space is available or until data taking is stopped.
  No mutex mechanism is used since variables will never be written 
  simultaneoously by two threads.
  
\********************************************************************/

void DAQ(DAQReadout *m) {

  struct timeval StartTime, StopTime;
  int Filepart;
  unsigned int EventsInFile;
  unsigned long long RunSize;
   
  Filepart = 0;   RunSize = 0;
  m->HVFB->ClearAverages();    
  m->NumEvents = 0;
  gettimeofday(&StartTime, NULL);

  m->PrintMessage("\rStarting run #%d (%s) on \"%s\" with %u event(s)\n",m->RunNumber,daq_runtype_str[m->daq_runtype],m->Source,m->NumEventsRequested);
  do {
    // Check if enough disk space is left
    if (CheckDisk(m->fRawDataPath) <= m->fMinDiskSpaceMB+m->fMaxFileSizeMB) {	  
      m->PrintMessage("\rError: Disk space after next file (max. %d MByte) below %d MByte\n",m->fMaxFileSizeMB,m->fMinDiskSpaceMB);
      break;
    }

    // Init run header, open raw file, write run header
    if (!m->OpenRawFile(Filepart)) break;
    m->PrintMessage("\rData file \"%s\" opened.\n",m->FileName);
    EventsInFile = 0;
    m->WriteRunHeader();
    
    if (m->daq_runtype != test) m->StartDRS(); 

    // Take data until finished, stopped or file too large   
    while (m->NumEvents<m->NumEventsRequested && !m->Stop && 
      ftell (m->Rawfile)/1024/1024<m->fMaxFileSizeMB) {

      if (m->daq_runtype == data) while (m->IsDRSBusy());  // Wait for hardware trigger (if DAQ stopped, DRS will not be busy anymore)
      else if (m->daq_runtype == pedestal) m->StopDRS();      // Wait for software trigger

      EventsInFile++;
      m->NumEvents++;	
      m->WriteEventHeader();

      // Read event data via VME or generate test data (for one board if no boards available)
      if (m->daq_runtype != test) {
	m->ReadCalibratedDRSData();
        m->StartDRS();  // Restart here: writing data is in parallel to waiting for next trigger
      }
      else {
	double Period = ((double) rand())/RAND_MAX*20;
	for (long int i=0; i<(m->NumCMCBoards>0 ?  m->NumCMCBoards : 1)*kNumberOfChips*kNumberOfChannels*kNumberOfBins; i++)
	  *((short *) m->WaveForm+i) = (short) (sin(i/Period)*1000);
      }
      // Write data to disk
      for (int i=m->FirstBoard; i<=m->LastBoard + (m->NumCMCBoards==0); i++) {
        for (unsigned int k=0; k<m->RHeader->NChips*m->RHeader->NChannels; k++) 
    	  if(fwrite((short *) m->WaveForm[i] + m->RHeader->Offset + k*kNumberOfBins, sizeof(short), m->RHeader->Samples, m->Rawfile) != m->RHeader->Samples) {
            m->PrintMessage("Error: Could not write event data, terminating run ()\n", strerror(errno));
            m->Stop = true;
    	  }
        }
      // Call feedback to process event
      m->HVFB->ProcessEvent();
    }

    // Write updated run header, close file
    RunSize += ftell (m->Rawfile);
    m->UpdateRunHeader(EventsInFile);
    fclose(m->Rawfile);  
    m->PrintMessage("Data file closed.\n");

    Filepart += 1; 
  } while(m->NumEvents < m->NumEventsRequested && !m->Stop);

  m->StopDRS();

  m->PrintMessage("\r%s run #%d %s (%d event(s))\n",daq_runtype_str[m->daq_runtype],m->RunNumber,(m->NumEvents == m->NumEventsRequested) ? "completed":"stopped",m->NumEvents);
  if (m->NumEvents>0) {
      gettimeofday(&StopTime, NULL);
      float RunTime = StopTime.tv_sec-StartTime.tv_sec + (StopTime.tv_usec-StartTime.tv_usec)*1e-6;
      m->PrintMessage("Time for run %.2f seconds, trigger rate %.2f Hz.\n", RunTime, m->NumEvents/RunTime);
      m->PrintMessage("Run size %llu MByte, data rate %.1f MByte/s.\n", RunSize/1024/1024, RunSize/1024.0/1024/RunTime);
  }
  m->daq_state = stopped;
}
