From 49481410e678478cc386c14592c6168abafdf016 Mon Sep 17 00:00:00 2001
From: Martin Hierholzer <martin.hierholzer@desy.de>
Date: Fri, 17 Aug 2018 11:27:47 +0200
Subject: [PATCH] Improvements for MicroDAQ module:

- Make data decimation configurable
- Addded some documentation
- Some code cleaning (eliminate warnings, move internal classes into detail namespace)
---
 Modules/include/MicroDAQ.h |  41 ++-
 Modules/src/MicroDAQ.cc    | 524 +++++++++++++++++++------------------
 2 files changed, 306 insertions(+), 259 deletions(-)

diff --git a/Modules/include/MicroDAQ.h b/Modules/include/MicroDAQ.h
index 982398e5..2822592f 100644
--- a/Modules/include/MicroDAQ.h
+++ b/Modules/include/MicroDAQ.h
@@ -10,8 +10,33 @@
 
 namespace ChimeraTK {
 
+  namespace detail {
+    struct AccessorAttacher;
+    struct H5storage;
+    struct DataSpaceCreator;
+    struct DataWriter;
+  }
+
+  /**
+   *  MicroDAQ module for logging data to HDF5 files. This can be usefull in enviromenents where no sufficient logging
+   *  of data is possible through the control system. Any ChimeraTK::Module can act as a data source. Which variables
+   *  should be logged can be selected through EntityOwner::findTag().
+   */
   struct MicroDAQ : public ApplicationModule {
-      using ApplicationModule::ApplicationModule;
+
+      /**
+       *  Constructor. decimationFactor and decimationThreshold are configuration constants which determine how the
+       *  data reduction is working. Arrays with a size bigger than decimationThreshold will be decimated by
+       *  decimationFactor before writing to the HDF5 file.
+       */
+      MicroDAQ(EntityOwner *owner, const std::string &name, const std::string &description, uint32_t decimationFactor=10,
+               uint32_t decimationThreshold=1000, bool eliminateHierarchy=false,
+               const std::unordered_set<std::string> &tags={})
+      : ApplicationModule(owner, name, description, eliminateHierarchy, tags),
+        decimationFactor_(decimationFactor), decimationThreshold_(decimationThreshold) {}
+
+      /** Default constructor, creates a non-working module. Can be used for late initialisation. */
+      MicroDAQ() : decimationFactor_(0), decimationThreshold_(0) {}
 
       ScalarPushInput<int> trigger{this, "trigger", "", "When written, the MicroDAQ write snapshot of all variables "
                 "to the file", {"MicroDAQ.CONFIG"}};
@@ -26,11 +51,13 @@ namespace ChimeraTK {
       ScalarOutput<uint32_t> currentFile{this, "currentFile", "", "File number currently written to.",
                 {"MicroDAQ.CONFIG"}};
 
-      void mainLoop() override;
-
       /** Add a Module as a source to this DAQ. */
       void addSource(const Module &source, const std::string &namePrefix="");
 
+    protected:
+
+      void mainLoop() override;
+
       template<typename UserType>
       VariableNetworkNode getAccessor(const std::string &variableName);
 
@@ -50,6 +77,14 @@ namespace ChimeraTK {
       /** Overall variable name list, used to detect name collisions */
       std::list<std::string> overallVariableList;
 
+      /** Parameters for the data decimation */
+      uint32_t decimationFactor_, decimationThreshold_;
+
+      friend struct detail::AccessorAttacher;
+      friend struct detail::H5storage;
+      friend struct detail::DataSpaceCreator;
+      friend struct detail::DataWriter;
+
   };
 
 } // namespace ChimeraTK
diff --git a/Modules/src/MicroDAQ.cc b/Modules/src/MicroDAQ.cc
index ef8b3155..9c0bf4b5 100644
--- a/Modules/src/MicroDAQ.cc
+++ b/Modules/src/MicroDAQ.cc
@@ -9,46 +9,50 @@ namespace ChimeraTK {
 
   /*********************************************************************************************************************/
 
-  /** Callable class for use with  boost::fusion::for_each: Attach the given accessor to the MicroDAQ with proper
-    *  handling of the UserType. */
-  struct AccessorAttacher {
-    AccessorAttacher(VariableNetworkNode& feeder, MicroDAQ *owner, const std::string &name)
-    : _feeder(feeder), _owner(owner), _name(name) {}
-
-    template<typename PAIR>
-    void operator()(PAIR&) const {
-      
-      // only continue if the call is for the right type
-      if(typeid(typename PAIR::first_type) != _feeder.getValueType()) return;
-      
-      // register connection
-      _feeder >> _owner->template getAccessor<typename PAIR::first_type>(_name);
+  namespace detail {
 
-    }
+    /** Callable class for use with  boost::fusion::for_each: Attach the given accessor to the MicroDAQ with proper
+      *  handling of the UserType. */
+    struct AccessorAttacher {
+      AccessorAttacher(VariableNetworkNode& feeder, MicroDAQ *owner, const std::string &name)
+      : _feeder(feeder), _owner(owner), _name(name) {}
+
+      template<typename PAIR>
+      void operator()(PAIR&) const {
+
+        // only continue if the call is for the right type
+        if(typeid(typename PAIR::first_type) != _feeder.getValueType()) return;
+
+        // register connection
+        _feeder >> _owner->template getAccessor<typename PAIR::first_type>(_name);
 
-    VariableNetworkNode &_feeder;
-    MicroDAQ *_owner;
-    const std::string &_name;
-  };
+      }
+
+      VariableNetworkNode &_feeder;
+      MicroDAQ *_owner;
+      const std::string &_name;
+    };
+
+  }
 
   /*********************************************************************************************************************/
 
   void MicroDAQ::addSource(const Module &source, const std::string &namePrefix) {
-    
+
     // for simplification, first create a VirtualModule containing the correct hierarchy structure (obeying eliminate
     // hierarchy etc.)
     auto dynamicModel = source.findTag(".*");     /// @todo use virtualise() instead
-    
+
     // add all accessors on this hierarchy level
     for(auto &acc : dynamicModel.getAccessorList()) {
-      boost::fusion::for_each(accessorListMap.table, AccessorAttacher(acc, this, namePrefix+"/"+acc.getName()));
+      boost::fusion::for_each(accessorListMap.table, detail::AccessorAttacher(acc, this, namePrefix+"/"+acc.getName()));
     }
-    
+
     // recurse into submodules
     for(auto mod : dynamicModel.getSubmoduleList()) {
       addSource(*mod, namePrefix+"/"+mod->getName());
     }
-    
+
   }
 
   /*********************************************************************************************************************/
@@ -77,76 +81,81 @@ namespace ChimeraTK {
 
   /*********************************************************************************************************************/
 
-  struct H5storage {
-      H5storage(MicroDAQ *owner) : _owner(owner) {}
-    
-      H5::H5File outFile;
-      std::string currentGroupName;
-      
-      /** Unique list of groups, used to create the groups in the file */
-      std::list <std::string> groupList;
-      
-      /** boost::fusion::map of UserTypes to std::lists containing the H5::DataSpace objects. */
-      template<typename UserType>
-      using dataSpaceList = std::list<H5::DataSpace>;
-      TemplateUserTypeMap<dataSpaceList> dataSpaceListMap;
-      
-      /** boost::fusion::map of UserTypes to std::lists containing decimation factors. */
-      template<typename UserType>
-      using decimationFactorList = std::list<size_t>;
-      TemplateUserTypeMap<decimationFactorList> decimationFactorListMap;
-
-      uint32_t currentBuffer{0};
-      uint32_t nFillsInBuffer{0};
-      bool isOpened{false};
-      bool firstTrigger{true};
-      
-      void processTrigger();
-      void writeData();
-
-      MicroDAQ *_owner;
-  };
-
-  /*********************************************************************************************************************/
-
-  struct DataSpaceCreator {
-      DataSpaceCreator(H5storage &storage) : _storage(storage) {}
-      
-      template<typename PAIR>
-      void operator()(PAIR &pair) const {
-        typedef typename PAIR::first_type UserType;
-        
-        // get the lists for the UserType
-        auto &accessorList = pair.second;
-        auto &decimationFactorList = boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table);
-        auto &dataSpaceList = boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table);
-        auto &nameList = boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table);
-        
-        // iterate through all accessors for this UserType
-        auto name = nameList.begin();
-        for(auto accessor = accessorList.begin() ; accessor != accessorList.end() ; ++accessor , ++name) {
-          // determine decimation factor
-          /// @todo make it configurable!
-          int factor = 1;
-          if(accessor->getNElements() > 1000) factor = 10;
-          decimationFactorList.push_back(factor);
-
-          // define data space
-          hsize_t dimsf[1];  // dataset dimensions
-          dimsf[0] = accessor->getNElements()/factor;
-          dataSpaceList.push_back( H5::DataSpace(1, dimsf) );
-          
-          // put all group names in list (each hierarchy level separately)
-          size_t idx = 0;
-          while( (idx = name->find('/', idx+1)) != std::string::npos ) {
-            std::string groupName = name->substr(0,idx);
-            _storage.groupList.push_back(groupName);
+  namespace detail {
+
+    struct H5storage {
+        H5storage(MicroDAQ *owner) : _owner(owner) {}
+
+        H5::H5File outFile;
+        std::string currentGroupName;
+
+        /** Unique list of groups, used to create the groups in the file */
+        std::list <std::string> groupList;
+
+        /** boost::fusion::map of UserTypes to std::lists containing the H5::DataSpace objects. */
+        template<typename UserType>
+        using dataSpaceList = std::list<H5::DataSpace>;
+        TemplateUserTypeMap<dataSpaceList> dataSpaceListMap;
+
+        /** boost::fusion::map of UserTypes to std::lists containing decimation factors. */
+        template<typename UserType>
+        using decimationFactorList = std::list<size_t>;
+        TemplateUserTypeMap<decimationFactorList> decimationFactorListMap;
+
+        uint32_t currentBuffer{0};
+        uint32_t nFillsInBuffer{0};
+        bool isOpened{false};
+        bool firstTrigger{true};
+
+        void processTrigger();
+        void writeData();
+
+        MicroDAQ *_owner;
+    };
+
+    /*********************************************************************************************************************/
+
+    struct DataSpaceCreator {
+        DataSpaceCreator(H5storage &storage) : _storage(storage) {}
+
+        template<typename PAIR>
+        void operator()(PAIR &pair) const {
+          typedef typename PAIR::first_type UserType;
+
+          // get the lists for the UserType
+          auto &accessorList = pair.second;
+          auto &decimationFactorList = boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table);
+          auto &dataSpaceList = boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table);
+          auto &nameList = boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table);
+
+          // iterate through all accessors for this UserType
+          auto name = nameList.begin();
+          for(auto accessor = accessorList.begin() ; accessor != accessorList.end() ; ++accessor , ++name) {
+            // determine decimation factor
+            int factor = 1;
+            if(accessor->getNElements() > _storage._owner->decimationThreshold_) {
+              factor = _storage._owner->decimationFactor_;
+            }
+            decimationFactorList.push_back(factor);
+
+            // define data space
+            hsize_t dimsf[1];  // dataset dimensions
+            dimsf[0] = accessor->getNElements()/factor;
+            dataSpaceList.push_back( H5::DataSpace(1, dimsf) );
+
+            // put all group names in list (each hierarchy level separately)
+            size_t idx = 0;
+            while( (idx = name->find('/', idx+1)) != std::string::npos ) {
+              std::string groupName = name->substr(0,idx);
+              _storage.groupList.push_back(groupName);
+            }
           }
         }
-      }
 
-      H5storage &_storage;
-  };
+        H5storage &_storage;
+    };
+
+  }
 
   /*********************************************************************************************************************/
 
@@ -154,15 +163,15 @@ namespace ChimeraTK {
       std::cout << "Initialising MicroDAQ system...";
 
       // storage object
-      H5storage storage(this);
+      detail::H5storage storage(this);
 
       // create the data spaces
-      boost::fusion::for_each(accessorListMap.table, DataSpaceCreator(storage));
-      
+      boost::fusion::for_each(accessorListMap.table, detail::DataSpaceCreator(storage));
+
       // sort group list and make unique to make sure lower levels get created first
       storage.groupList.sort();
       storage.groupList.unique();
-      
+
       // loop: process incoming triggers
       while(true) {
         trigger.read();
@@ -174,190 +183,193 @@ namespace ChimeraTK {
 
   /*********************************************************************************************************************/
 
-  void H5storage::processTrigger() {
-
-      // update configuration variables
-      _owner->enable.readLatest();
-      _owner->nMaxFiles.readLatest();
-      _owner->nTriggersPerFile.readLatest();
-
-      // need to open or close file?
-      if(!isOpened && _owner->enable != 0) {
-        std::fstream bufferNumber;
-        
-        // some things to be done only on first trigger
-        if(firstTrigger) {
-          // create sub-directory
-          boost::filesystem::create_directory("uDAQ");
-
-          // determine current buffer number
-          bufferNumber.open("uDAQ/currentBuffer", std::ofstream::in);
-          bufferNumber.seekg(0);
-          if(!bufferNumber.eof()) {
-            bufferNumber >> currentBuffer;
-            char filename[64];
-            std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer);
-            if(boost::filesystem::exists(filename) && boost::filesystem::file_size(filename) > 1000) currentBuffer++;
-            if(currentBuffer >= _owner->nMaxFiles) currentBuffer = 0;
-          }
-          else {
-            currentBuffer = 0;
+  namespace detail {
+
+    void H5storage::processTrigger() {
+
+        // update configuration variables
+        _owner->enable.readLatest();
+        _owner->nMaxFiles.readLatest();
+        _owner->nTriggersPerFile.readLatest();
+
+        // need to open or close file?
+        if(!isOpened && _owner->enable != 0) {
+          std::fstream bufferNumber;
+
+          // some things to be done only on first trigger
+          if(firstTrigger) {
+            // create sub-directory
+            boost::filesystem::create_directory("uDAQ");
+
+            // determine current buffer number
+            bufferNumber.open("uDAQ/currentBuffer", std::ofstream::in);
+            bufferNumber.seekg(0);
+            if(!bufferNumber.eof()) {
+              bufferNumber >> currentBuffer;
+              char filename[64];
+              std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer);
+              if(boost::filesystem::exists(filename) && boost::filesystem::file_size(filename) > 1000) currentBuffer++;
+              if(currentBuffer >= _owner->nMaxFiles) currentBuffer = 0;
+            }
+            else {
+              currentBuffer = 0;
+            }
+            bufferNumber.close();
           }
+
+          // store current buffer number to disk
+          char filename[64];
+          std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer);
+          std::cout << "uDAQ: Starting with file: " << filename << std::endl;
+          bufferNumber.open("uDAQ/currentBuffer", std::ofstream::out);
+          bufferNumber << currentBuffer << std::endl;
           bufferNumber.close();
-        }
 
-        // store current buffer number to disk
-        char filename[64];
-        std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer);
-        std::cout << "uDAQ: Starting with file: " << filename << std::endl;
-        bufferNumber.open("uDAQ/currentBuffer", std::ofstream::out);
-        bufferNumber << currentBuffer << std::endl;
-        bufferNumber.close();
-        
-        // update file number process variables
-        _owner->currentFile = currentBuffer;
-        _owner->currentFile.write();
-
-        // open file
-        try {
-          outFile = H5::H5File(filename, H5F_ACC_TRUNC);
-        }
-        catch(H5::FileIException &e) {
-          return;
+          // update file number process variables
+          _owner->currentFile = currentBuffer;
+          _owner->currentFile.write();
+
+          // open file
+          try {
+            outFile = H5::H5File(filename, H5F_ACC_TRUNC);
+          }
+          catch(H5::FileIException &) {
+            return;
+          }
+          isOpened = true;
+
         }
-        isOpened = true;
-        
-      }
-      else if(isOpened && _owner->enable == 0) {
-        outFile.close();
-        isOpened = false;
-      }
-      
-      // if file is opened, this trigger should be included in the DAQ
-      if(isOpened) {
-
-        // write data
-        writeData();
-        
-        // increment counter, after 100 triggers written to the same file, switch the file
-        /// @todo make all this configurable!
-        nFillsInBuffer++;
-        if(nFillsInBuffer > _owner->nTriggersPerFile) {
-
-          // increment file number. use at most 1000 files, overwrite old files
-          currentBuffer++;
-          if(currentBuffer >= _owner->nMaxFiles) currentBuffer = 0;
-          nFillsInBuffer = 0;
-          
-          // just close the file here, will re-open on next trigger
+        else if(isOpened && _owner->enable == 0) {
           outFile.close();
           isOpened = false;
         }
-      }
-  }
 
-  /*********************************************************************************************************************/
+        // if file is opened, this trigger should be included in the DAQ
+        if(isOpened) {
 
-  struct DataWriter {
-      DataWriter(H5storage &storage) : _storage(storage) {}
-      
-      template<typename PAIR>
-      void operator()(PAIR &pair) const {
-        typedef typename PAIR::first_type UserType;
-        
-        // get the lists for the UserType
-        auto &accessorList = pair.second;
-        auto &decimationFactorList = boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table);
-        auto &dataSpaceList = boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table);
-        auto &nameList = boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table);
-        
-        // iterate through all accessors for this UserType
-        auto decimationFactor = decimationFactorList.begin();
-        auto dataSpace = dataSpaceList.begin();
-        auto name = nameList.begin();
-        for(auto accessor = accessorList.begin() ; accessor != accessorList.end() ; ++accessor , ++decimationFactor, ++dataSpace, ++name) {
-          
-          // form full path name of data set
-          std::string dataSetName = _storage.currentGroupName+"/"+*name;
-
-          // write to file (this is mainly a function call to allow template specialisations at this point)
-          try {
-            write2hdf<UserType>(*accessor, dataSetName, *decimationFactor, *dataSpace);
+          // write data
+          writeData();
+
+          // increment counter, after nTriggersPerFile triggers written to the same file, switch the file
+          nFillsInBuffer++;
+          if(nFillsInBuffer > _owner->nTriggersPerFile) {
+
+            // increment file number. use at most nMaxFiles files, overwrite old files
+            currentBuffer++;
+            if(currentBuffer >= _owner->nMaxFiles) currentBuffer = 0;
+            nFillsInBuffer = 0;
+
+            // just close the file here, will re-open on next trigger
+            outFile.close();
+            isOpened = false;
           }
-          catch(H5::FileIException&) {
-            std::cout << "MicroDAQ: ERROR writing data set " << dataSetName << std::endl;
-            throw;
+        }
+    }
+
+    /*********************************************************************************************************************/
+
+    struct DataWriter {
+        DataWriter(detail::H5storage &storage) : _storage(storage) {}
+
+        template<typename PAIR>
+        void operator()(PAIR &pair) const {
+          typedef typename PAIR::first_type UserType;
+
+          // get the lists for the UserType
+          auto &accessorList = pair.second;
+          auto &decimationFactorList = boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table);
+          auto &dataSpaceList = boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table);
+          auto &nameList = boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table);
+
+          // iterate through all accessors for this UserType
+          auto decimationFactor = decimationFactorList.begin();
+          auto dataSpace = dataSpaceList.begin();
+          auto name = nameList.begin();
+          for(auto accessor = accessorList.begin() ; accessor != accessorList.end() ; ++accessor , ++decimationFactor, ++dataSpace, ++name) {
+
+            // form full path name of data set
+            std::string dataSetName = _storage.currentGroupName+"/"+*name;
+
+            // write to file (this is mainly a function call to allow template specialisations at this point)
+            try {
+              write2hdf<UserType>(*accessor, dataSetName, *decimationFactor, *dataSpace);
+            }
+            catch(H5::FileIException&) {
+              std::cout << "MicroDAQ: ERROR writing data set " << dataSetName << std::endl;
+              throw;
+            }
           }
         }
+
+        template<typename UserType>
+        void write2hdf(ArrayPollInput<UserType> &accessor, std::string &name, size_t decimationFactor, H5::DataSpace &dataSpace) const;
+
+        H5storage &_storage;
+    };
+
+    /*********************************************************************************************************************/
+
+    template<typename UserType>
+    void DataWriter::write2hdf(ArrayPollInput<UserType> &accessor, std::string &dataSetName, size_t decimationFactor, H5::DataSpace &dataSpace) const {
+
+      // prepare decimated buffer
+      size_t n = accessor.getNElements()/decimationFactor;
+      std::vector<float> buffer(n);
+      for(size_t i=0; i<n; ++i) {
+        buffer[i] = accessor[i*decimationFactor];
       }
-      
-      template<typename UserType>
-      void write2hdf(ArrayPollInput<UserType> &accessor, std::string &name, size_t decimationFactor, H5::DataSpace &dataSpace) const;
 
-      H5storage &_storage;
-  };
+      // write data from internal buffer to data set in HDF5 file
+      H5::DataSet dataset = _storage.outFile.createDataSet(dataSetName, H5::PredType::NATIVE_FLOAT, dataSpace);
+      dataset.write(buffer.data(), H5::PredType::NATIVE_FLOAT);
+    }
 
-  /*********************************************************************************************************************/
+    /*********************************************************************************************************************/
 
-  template<typename UserType>
-  void DataWriter::write2hdf(ArrayPollInput<UserType> &accessor, std::string &dataSetName, size_t decimationFactor, H5::DataSpace &dataSpace) const {
+    template<>
+    void DataWriter::write2hdf<std::string>(ArrayPollInput<std::string> &accessor, std::string &dataSetName, size_t, H5::DataSpace &dataSpace) const {
 
-    // prepare decimated buffer
-    size_t n = accessor.getNElements()/decimationFactor;
-    std::vector<float> buffer(n);
-    for(size_t i=0; i<n; ++i) {
-      buffer[i] = accessor[i*decimationFactor];
+      // write data from internal buffer to data set in HDF5 file
+      H5::DataSet dataset = _storage.outFile.createDataSet(dataSetName, H5::PredType::C_S1, dataSpace);
+      dataset.write(accessor[0].c_str(), H5::PredType::NATIVE_FLOAT);
     }
-    
-    // write data from internal buffer to data set in HDF5 file
-    H5::DataSet dataset = _storage.outFile.createDataSet(dataSetName, H5::PredType::NATIVE_FLOAT, dataSpace);
-    dataset.write(buffer.data(), H5::PredType::NATIVE_FLOAT);
-  }
 
-  /*********************************************************************************************************************/
+    /*********************************************************************************************************************/
 
-  template<>
-  void DataWriter::write2hdf<std::string>(ArrayPollInput<std::string> &accessor, std::string &dataSetName, size_t, H5::DataSpace &dataSpace) const {
-        
-    // write data from internal buffer to data set in HDF5 file
-    H5::DataSet dataset = _storage.outFile.createDataSet(dataSetName, H5::PredType::C_S1, dataSpace);
-    dataset.write(accessor[0].c_str(), H5::PredType::NATIVE_FLOAT);
-  }
+    void H5storage::writeData() {
 
-  /*********************************************************************************************************************/
+        // format current time
+        struct timeval tv;
+        gettimeofday(&tv, nullptr);
+        time_t t = tv.tv_sec;
+        if(t == 0) t = time(nullptr);
+        struct tm *tmp = localtime(&t);
+        char timeString[64];
+        std::sprintf(timeString, "%04d-%02d-%02d %02d:%02d:%02d.%03d", 1900+tmp->tm_year, tmp->tm_mon+1, tmp->tm_mday,
+            tmp->tm_hour, tmp->tm_min, tmp->tm_sec, static_cast<int>(tv.tv_usec / 1000));
 
-  void H5storage::writeData() {
-
-      // format current time
-      struct timeval tv;
-      gettimeofday(&tv, NULL);
-      time_t t = tv.tv_sec;
-      if(t == 0) t = time(NULL);
-      struct tm *tmp = localtime(&t);
-      char timeString[64];
-      std::sprintf(timeString, "%04d-%02d-%02d %02d:%02d:%02d.%03d", 1900+tmp->tm_year, tmp->tm_mon+1, tmp->tm_mday,
-          tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (int)(tv.tv_usec / 1000));
-
-      // create groups
-      currentGroupName = std::string("/")+std::string(timeString);
-      try {
-        outFile.createGroup(currentGroupName);
-        for(auto &group : groupList) outFile.createGroup(currentGroupName+"/"+group);
-      }
-      catch(H5::FileIException &e) {
-        outFile.close();
-        isOpened = false;    // will re-open file on next trigger
-        return;
-      }
-      
-      // read all input data
-      _owner->readAllLatest();
+        // create groups
+        currentGroupName = std::string("/")+std::string(timeString);
+        try {
+          outFile.createGroup(currentGroupName);
+          for(auto &group : groupList) outFile.createGroup(currentGroupName+"/"+group);
+        }
+        catch(H5::FileIException &) {
+          outFile.close();
+          isOpened = false;    // will re-open file on next trigger
+          return;
+        }
 
-      // write all data to file
-      boost::fusion::for_each(_owner->accessorListMap.table, DataWriter(*this));
-    
-  }
+        // read all input data
+        _owner->readAllLatest();
 
-  /*********************************************************************************************************************/
+        // write all data to file
+        boost::fusion::for_each(_owner->accessorListMap.table, DataWriter(*this));
+
+    }
+
+    /*********************************************************************************************************************/
+
+  } // namespace detail
 
 } // namespace ChimeraTK
-- 
GitLab