Skip to content
Snippets Groups Projects
ServerHistory.h 9.36 KiB
Newer Older
// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once

/*!
 * \author Klaus Zenker (HZDR)
 * \date 10.08.2018
 * \page historydoc Server based history module
 * \section historyintro Server based history
 *  Some control systems offer a variable history but some do not. In this case
 * the \c ServerHistory can be used to create a history ring buffer on the
 * server. If only a local history is needed consider to use the \c MicroDAQ
 * module. In order to do so you connect the variable that should have a history
 * on the server to the \c ServerHistory module. The history length is set
 * during module construction and fixed per module. Every time one of the
 * variable handled by the history module is updated it will be filled into the
 * history buffer. The buffer length (history length) can not be changed during
 * runtime. Finally, one can create an addition buffer for each history
 * buffer that includes the time stamps of each data point in the history buffer.
 * This is useful if not all history buffers are filled with the same rate or the
 * rate is not known.
 *  Output variables created by the \c ServerHistory module are named like their
 * feeding process variables with a prefixed name that is set when the process
 * variables is added to the history module. In case of Array type feeding
 * process variables n history buffers are created (where n is the Array size)
 * and the element index i is appended to the feeding process variable name. In
 * consequence an input array of length i will result in i output history
 * arrays. The following tags are added to the history output variable:
 *  - name of the history module
zenker's avatar
zenker committed
 * The connection of variables with the 'history' tag to the ServerHistory module is
 * done automatically.
 * \attention Only variables of modules defined before constructing the ServerHistory
 * module are considered.
 *
 * It is also possible to connect a DeviceModule to the ServerHistory module.
 * Variables of Devices have no tags and therefor they will not be automatically connected
 * to the SereverHistory module. One has to call addSource().
 * In addition a trigger in case the variables are not push type. It is given as optional
 * parameter to the \c addSource method.
 * If the device variables are writable they are of push type. In this case
Klaus Zenker (HZDR)'s avatar
Klaus Zenker (HZDR) committed
 * the trigger will not be added. One has to use the LogicalNameMapping backend to
 * force the device variables to be read only by using the \c forceReadOnly plugin.
 * Using the LogicalNameMapping backend also allows to select individual device
 * process variables to be connected to the \c ServerHistory.
 *
 *  The following example shows how to integrate the \c ServerHistory module.
 *  \code
 *  sruct TestModule: public ChimeraTK::ApplicationModule{
 *  chimeraTK::ScalarOutput<float> measurement{this, "measurement", "" ,
zenker's avatar
zenker committed
 * "measurement variable", {"history"}};
 *  ...
 *  };
 *  struct myApp : public ChimeraTK::Application{
 *
 *  history::ServerHistory<float> history{this, "ServerHistory", "History for
 * certain scalara float variables", 20}; // History buffer length is 20
 *
 *  ChimeraTK::ControlSystemModule cs;
 *
zenker's avatar
zenker committed
 *  ChimeraTK::ConnectingDeviceModule dev{this, "Dummy", "Trigger/tick"};
Klaus Zenker (HZDR)'s avatar
Klaus Zenker (HZDR) committed
 *
 *  ChimeraTK::PeriodicTrigger trigger{this, "Trigger", "Trigger used for other modules"};
 *
 *
 *  TestModule test{ this, "test", "" };
 *  ...
 *  };
 *
 *
zenker's avatar
zenker committed
 *  void myAPP::intitialise(){
 *  // The variable of the TestModule will show up in the control system as history/test/measurement automatically
 * (identified by the tag).
 *  // Add a device. Updating of the history buffer is trigger external by the given trigger
 *  history.addSource(&dev,"device_history",trigger.tick);
 *  ChimeraTK::Application::initialise();
 *
 *  \remark Before starting the main loop of the server history module \c readAnyGroup() is called.
 *  This seems to block until all connected variables are written once. So if the history buffers
 *  are not filled make sure all variables are written. If they are not written in the module main loop,
 *  write them once before the main loop of the module containing the history variables.
#include "ApplicationModule.h"
#include "ArrayAccessor.h"
#include "DeviceModule.h"
#include "VariableGroup.h"
#include <unordered_set>

#include <ChimeraTK/SupportedUserTypes.h>
zenker's avatar
zenker committed
#include <string>
#include <vector>
namespace ChimeraTK { namespace history {

  struct AccessorAttacher;

  template<typename UserType>
  struct HistoryEntry {
    HistoryEntry(bool enableHistory)
    : data(std::vector<ArrayOutput<UserType>>{}), timeStamp(std::vector<ArrayOutput<uint64_t>>{}),
      withTimeStamps(enableHistory) {}
    std::vector<ArrayOutput<UserType>> data;
    std::vector<ArrayOutput<uint64_t>> timeStamp;
zenker's avatar
zenker committed
  class ServerHistory : public ApplicationModule {
   public:
     * Addition parameters to a normal application module constructor:
     * \param owner Module owner passed to ApplicationModule constructor.
     * \param name Module name passed to ApplicationModule constructor.
     * \param description Module description passed to ApplicationModule constructor.
     * \param historyLength Length of the history buffers.
zenker's avatar
zenker committed
     * \param enableTimeStamps An additional
     * ring buffer per variable will be added that holds the time stamps corresponding to the data ring buffer entries.
     * \param hierarchyModifier Flag passed to ApplicationModule constructor.
     * \param tags Module tags passed to ApplicationModule constructor.
    ServerHistory(EntityOwner* owner, const std::string& name, const std::string& description,
zenker's avatar
zenker committed
        size_t historyLength = 1200, bool enableTimeStamps = false,
        HierarchyModifier hierarchyModifier = HierarchyModifier::none,
        const std::unordered_set<std::string>& tags = {});

    /** Default constructor, creates a non-working module. Can be used for late
     * initialisation. */
    ServerHistory() : _historyLength(1200), _enbaleTimeStamps(false) {}
zenker's avatar
zenker committed
     * Ad variables of a device to the ServerHistory. Calls virtualiseFromCatalog to get access to the internal variables.
zenker's avatar
zenker committed
     * \param source For all variables of this module ring buffers are created.
     *               Use the LogicalNameMapping to create a virtual device module that holds all variables that should be
     *               passed to the history module.
     * \param namePrefix This prefix is added to variable names added to the root directory in the process variable
zenker's avatar
zenker committed
     *                   tree. E.g. a prefix \c history for a variable names data will appear as history/dummy/data
     *                   if dummy is the name of the source module.
     * \param submodule If only a submodule should be added give the name.
     *                  It does not work do create a submodule of the DeviceModule itself!
     * \param trigger This trigger is used for all poll type variable found in the source module.
zenker's avatar
zenker committed
    void addSource(const DeviceModule& source, const RegisterPath& namePrefix, const std::string& submodule = "",
        const VariableNetworkNode& trigger = {});
zenker's avatar
zenker committed
     * Just gets the device module from the ConnectingDeviceModule before calling the DeviceModule version of addSource.
zenker's avatar
zenker committed
    void addSource(ConnectingDeviceModule* source, const RegisterPath& namePrefix, const std::string& submodule = "",
        const VariableNetworkNode& trigger = {});
    void prepare() override;
    void mainLoop() override;

zenker's avatar
zenker committed
    void findTagAndAppendToModule(VirtualModule& virtualParent, const std::string& tag, bool eliminateAllHierarchies,
        bool eliminateFirstHierarchy, bool negate, VirtualModule& root) const override;

   private:
    void prepareHierarchy(const RegisterPath& namePrefix);

    /**
     * See public method for documentation.
     */
    void addSource(const Module& source, const RegisterPath& namePrefix, const VariableNetworkNode& trigger = {});

    template<typename UserType>
    VariableNetworkNode getAccessor(const std::string& variableName, const size_t& nElements);

    /** Map of VariableGroups required to build the hierarchies. The key it the
     * full path name. */
    std::map<std::string, VariableGroup> groupMap;

    /** boost::fusion::map of UserTypes to std::lists containing the
     * ArrayPushInput and ArrayOutput accessors. These accessors are dynamically
     * created by the AccessorAttacher. */
    template<typename UserType>
    using AccessorList = std::list<std::pair<ArrayPushInput<UserType>, HistoryEntry<UserType>>>;
    TemplateUserTypeMapNoVoid<AccessorList> _accessorListMap;

    /** boost::fusion::map of UserTypes to std::lists containing the names of the
     * accessors. Technically there would be no need to use TemplateUserTypeMap
     * for this (as type does not depend on the UserType), but since these lists
     * must be filled consistently with the accessorListMap, the same construction
     * is used here. */
    template<typename UserType>
    using NameList = std::list<std::string>;
    TemplateUserTypeMapNoVoid<NameList> _nameListMap;

    /** Overall variable name list, used to detect name collisions */
    std::list<std::string> _overallVariableList;

    size_t _historyLength;
    bool _enbaleTimeStamps;

    friend struct AccessorAttacher;
  };
}} // namespace ChimeraTK::history