diff --git a/Modules/include/StatusAccessor.h b/Modules/include/StatusAccessor.h
new file mode 100644
index 0000000000000000000000000000000000000000..1b9bbdc1b884e8ed664479966313d7bbd5aaff28
--- /dev/null
+++ b/Modules/include/StatusAccessor.h
@@ -0,0 +1,97 @@
+#pragma once
+
+/*!
+ * \author Martin Hierholzer (DESY)
+ * \date 01.06.2021
+ *
+ * Provide an aggregateable StatusOutputs which can have one of the four states: OFF, OK, WARNING, FAULT.
+ * 
+ * Multiple StatusOutputs can be aggregated using the StatusAggregator. StatusOutputs are typically provided by
+ * StatusMonitors, but also custom ApplicationModules can provide them.
+ * 
+ * For convenience, also StatusPushInputs and StatusPollInput are provided for use in custom ApplicationModules.
+ */
+
+#include "ScalarAccessor.h"
+#include "Module.h"
+
+namespace ChimeraTK {
+
+  /********************************************************************************************************************/
+
+  /** Base class - used to avoid code duplication in StatusOutput, StatusPushInput and StatusPollInput. */
+  struct StatusAccessorBase {
+    /**
+     *  These are the states which can be reported.
+     *  
+     *  Note: The values are exposed to the control system and hence are part of the public interface!
+     */
+    enum class Status : int32_t { OFF = 0, OK = 1, WARNING = 2, FAULT = 3 };
+  };
+
+  /** Special StatusAccessor - used to avoid code duplication in StatusOutput, StatusPushInput and StatusPollInput. */
+  template<typename ACCESSOR>
+  struct StatusAccessor : ACCESSOR, StatusAccessorBase {
+    /** Note: In contrast to normal ScalarInput accessors, this constructor omits the unit argument. */
+    StatusAccessor(Module* owner, const std::string& name, const std::string& description,
+        const std::unordered_set<std::string>& tags = {})
+    : ACCESSOR(owner, name, "", description, tags) {}
+    StatusAccessor() {}
+
+    using ACCESSOR::ACCESSOR;
+
+    /** Reserved tag which is used to mark status outputs */
+    constexpr static auto tagStatusOutput = "_ChimeraTK_StatusOutput_statusOutput";
+
+    /** Implicit type conversion to user type T to access the value. */
+    operator Status&() { return *reinterpret_cast<Status*>(&ACCESSOR::get()->accessData(0, 0)); }
+
+    /** Implicit type conversion to user type T to access the const value. */
+    operator const Status&() const { return *reinterpret_cast<Status*>(&ACCESSOR::get()->accessData(0, 0)); }
+
+    /** Assignment operator, assigns the first element. */
+    StatusAccessor& operator=(Status rightHandSide) {
+      ACCESSOR::get()->accessData(0, 0) = static_cast<int32_t>(rightHandSide);
+      return *this;
+    }
+
+    /* Delete increment/decrement operators since they do not make much sense with a Status */
+    void operator++() = delete;
+    void operator++(int) = delete;
+    void operator--() = delete;
+    void operator--(int) = delete;
+  };
+
+  /********************************************************************************************************************/
+
+  /** Special ScalarOutput which represents a status which can be aggregated by the StatusAggregator. */
+  struct StatusOutput : StatusAccessor<ScalarOutput<int32_t>> {
+    /** Note: In contrast to normal ScalarOutput accessors, this constructor omits the unit argument. */
+    StatusOutput(Module* owner, const std::string& name, const std::string& description,
+        const std::unordered_set<std::string>& tags = {})
+    : StatusAccessor<ScalarOutput<int32_t>>(owner, name, "", description, tags) {
+      addTag(tagStatusOutput);
+    }
+    StatusOutput() {}
+    using StatusAccessor<ScalarOutput<int32_t>>::operator=;
+  };
+
+  /********************************************************************************************************************/
+
+  /** Special StatusPushInput which reads from a StatusOutput and also handles the type conversion */
+  struct StatusPushInput : StatusAccessor<ScalarPushInput<int32_t>> {
+    using StatusAccessor<ScalarPushInput<int32_t>>::StatusAccessor;
+    using StatusAccessor<ScalarPushInput<int32_t>>::operator=;
+  };
+
+  /********************************************************************************************************************/
+
+  /** Special StatusPollInput which reads from a StatusOutput and also handles the type conversion */
+  struct StatusPollInput : StatusAccessor<ScalarPollInput<int32_t>> {
+    using StatusAccessor<ScalarPollInput<int32_t>>::StatusAccessor;
+    using StatusAccessor<ScalarPollInput<int32_t>>::operator=;
+  };
+
+  /********************************************************************************************************************/
+
+} // namespace ChimeraTK
diff --git a/Modules/include/StatusAggregator.h b/Modules/include/StatusAggregator.h
index 854cb3259dae16d96f6190d86b5a981e6ee5f90c..44f27c9ccb16df3064da33e4bd380de329054249 100644
--- a/Modules/include/StatusAggregator.h
+++ b/Modules/include/StatusAggregator.h
@@ -3,7 +3,8 @@
 
 #include "ApplicationModule.h"
 #include "ModuleGroup.h"
-#include "StatusMonitor.h"
+#include "HierarchyModifyingGroup.h"
+#include "StatusAccessor.h"
 
 #include <list>
 #include <vector>
@@ -11,54 +12,112 @@
 
 namespace ChimeraTK {
 
+  /********************************************************************************************************************/
+
   /**
-   * The StatusAggregator collects results of multiple StatusMonitor instances
-   * and aggregates them into a single status, which can take the same values
-   * as the result of the individual monitors.
+   * The StatusAggregator collects results of multiple StatusMonitor instances and aggregates them into a single status,
+   * which can take the same values as the result of the individual monitors.
+   * 
+   * It will search for all StatusOutputs from its point in hierarchy downwards, matching the tagsToAggregate passed to
+   * the constructor. If a StatusOutputs beloging to another StatusAggregator is found (also matching the
+   * tagsToAggregate) the search is not recursing further down at that branch, since the StatusAggregator already
+   * represents the complete status of the branch below it. StatusAggregators created on the same hierarchy level (i.e.
+   * sharing the owner) never aggregate each other.
    *
-   * Note: The aggregated instances are collected on construction. Hence, the
-   * StatusAggregator has to be declared after all instances that shall to be
-   * included in the scope (ModuleGroup, Application, ...) of interest.
+   * Note: The aggregated instances are collected on construction. Hence, the StatusAggregator has to be declared after
+   * all instances that shall to be included in the scope (ModuleGroup, Application, ...) of interest.
    */
-  class StatusAggregator : public ApplicationModule {
-   public:
-    StatusAggregator(EntityOwner* owner, const std::string& name, const std::string& description,
-        const std::string& output, HierarchyModifier modifier, const std::unordered_set<std::string>& tags = {})
-    : ApplicationModule(owner, name, description, modifier, tags), status(this, output, "", "", {}) {
-      populateStatusInput();
-    }
+  struct StatusAggregator : ApplicationModule {
+    /**
+     *  Possible status priority modes used during aggregation of unequal Status values. The output Status value of the
+     *  StatusAggregator will be equal to the current input Status value with the highest priority.
+     *  
+     *  The priorities are listed with the possible values, highest priority first.
+     *  
+     *  Hint for remembering the value names: f = fault, w = warning, o = off, k = ok
+     */
+    enum class PriorityMode {
+      fwok,          ///< fault - warning - off - ok
+      fwko,          ///< fault - warning - ok - off
+      fw_warn_mixed, ///< fault - warning - ok or off, mixed state of ok or off results in warning
+      ofwk           ///< off - fault - warning - ok
+    };
 
-    StatusAggregator() = default;
-    StatusAggregator(StatusAggregator&&) = default;
+    /**
+     *  Construct StatusAggregator object.
+     * 
+     *  The StatusAggregator is a module with a single output, the aggregated status. For convenience, the module itself
+     *  is always hidden, and the outputName is interpreted as a qualified variable name, which can be relative or
+     *  absolute. See the class description of the HierarchyModifyingGroup for more details.
+     *  
+     *  The mode governs how multiple unequal input status values are aggregated into a single status. See the
+     *  PriorityMode class description for details.
+     *  
+     *  The tagsToAggregate are the tags which are required to be present at the aggregated StatusOutputs. StatusOutputs
+     *  which do not have the specified tags are ignored. If no tag is specified, all StatusOutputs are aggregated. At
+     *  the moment, at maximum only one tag may be specified.
+     *  
+     *  outputTags is the list of tags which is attached to the aggregated output. This tag has no influence on the
+     *  aggregation. Other StatusAggregators will aggregate the output based on the tagsToAggregate, not based on the
+     *  outputTags. Any number of tags can be specified here. Typically no tag is specified (even if tagsToAggregate
+     *  contains a tag), unless the output needs special treatment somewhere else (e.g. if it is included in the
+     *  MicroDAQ system searching for a particular tag).
+     *  
+     *  Note: The constructor will search for StatusOutputs to be aggregated. It can only find what has been constructed
+     *  already. Make sure all StatusOutputs to be aggregated are constructed before this aggregator.
+     */
+    StatusAggregator(EntityOwner* owner, const std::string& outputName, const std::string& description,
+        PriorityMode mode = PriorityMode::fwok, const std::unordered_set<std::string>& tagsToAggregate = {},
+        const std::unordered_set<std::string>& outputTags = {});
 
-    ~StatusAggregator() override {}
+    StatusAggregator(StatusAggregator&& other) = default;
+    StatusAggregator() = default;
+    StatusAggregator& operator=(StatusAggregator&& other) = default;
 
-   protected:
-    void mainLoop() override {
-      std::cout << "Entered StatusAggregator::mainLoop()" << std::endl;
+    void mainLoop() override;
 
-      //      while(true){
-      //      // Status collection goes here
-      //      }
-    }
+    void findTagAndAppendToModule(VirtualModule& virtualParent, const std::string& tag, bool eliminateAllHierarchies,
+        bool eliminateFirstHierarchy, bool negate, VirtualModule& root) const override;
 
+   protected:
     /// Recursivly search for StatusMonitors and other StatusAggregators
     void populateStatusInput();
+
     /// Helper for populateStatusInput
-    void scanAndPopulateFromHierarchyLevel(std::list<VariableNetworkNode> nodes);
+    void scanAndPopulateFromHierarchyLevel(EntityOwner& module, const std::string& namePrefix);
 
-    /**One of four possible states to be reported*/
-    ScalarOutput<uint16_t> status;
+    /** Reserved tag which is used to mark aggregated status outputs (need to stop searching further down the
+     *  hierarchy) */
+    constexpr static auto tagAggregatedStatus = "_ChimeraTK_StatusAggregator_aggregatedStatus";
 
-    /**Vector of status inputs */
-    std::vector<ScalarPushInput<uint16_t>> statusInput;
+    /** Reserved tag which is used to mark internal variables which should not be visible in the virtual hierachy. */
+    constexpr static auto tagInternalVars = "_ChimeraTK_StatusAggregator_internalVars";
 
-    //TODO Also provide this for the aggregator?
-    //    /** Disable the monitor. The status will always be OFF. You don't have to connect this input.
-    //     *  When there is no feeder, ApplicationCore will connect it to a constant feeder with value 0, hence the monitor is always enabled.
-    //     */
-    //    ScalarPushInput<int> disable{this, "disable", "", "Disable the status monitor"};
+    /// The aggregated status output
+    struct StatusOutputGroup : HierarchyModifyingGroup {
+      StatusOutputGroup(EntityOwner* owner, std::string qualifiedVariableName)
+      : HierarchyModifyingGroup(owner, HierarchyModifyingGroup::getPathName(qualifiedVariableName), ""),
+        status(this, HierarchyModifyingGroup::getUnqualifiedName(qualifiedVariableName), "") {}
+      StatusOutputGroup() = default;
+      StatusOutput status;
+    } _output;
+
+    /// All status inputs to be aggregated
+    std::vector<StatusPushInput> _inputs;
+
+    /// Priority mode used in aggregation
+    PriorityMode _mode;
+
+    /// List of tags to aggregate
+    std::unordered_set<std::string> _tagsToAggregate;
+
+    /// Convert Status value into a priority (high integer value = high priority), depending on chosen PriorityMode
+    /// Return value of -1 has the special meaning that the input Status's must be all equal, otherwise it must result
+    /// in a warning Status.
+    int getPriority(StatusOutput::Status status);
   };
 
+  /********************************************************************************************************************/
+
 } // namespace ChimeraTK
 #endif // CHIMERATK_STATUS_AGGREGATOR_H
diff --git a/Modules/include/StatusMonitor.h b/Modules/include/StatusMonitor.h
index 991cde77367f65422f0dda11aef35040e0c461c9..bdf2640684d7b071c88caec115a289db83a58c96 100644
--- a/Modules/include/StatusMonitor.h
+++ b/Modules/include/StatusMonitor.h
@@ -32,10 +32,9 @@ For more info see \ref statusmonitordoc
  * conditions reports four different states.
 */
 #include "ApplicationCore.h"
-namespace ChimeraTK {
+#include "StatusAccessor.h"
 
-  /** There are four states that can be reported*/
-  enum States { OFF, OK, WARNING, FAULT };
+namespace ChimeraTK {
 
   /** Common base for StatusMonitors
    *
@@ -44,12 +43,12 @@ namespace ChimeraTK {
    *  facilitates checking for the type in the StatusAggregator, which
    *  needs to identify any StatusMonitor.
    */
-  struct StatusMonitor : public ApplicationModule {
+  struct StatusMonitor : ApplicationModule {
     StatusMonitor(EntityOwner* owner, const std::string& name, const std::string& description, const std::string& input,
         const std::string& output, HierarchyModifier modifier, const std::unordered_set<std::string>& outputTags = {},
         const std::unordered_set<std::string>& parameterTags = {}, const std::unordered_set<std::string>& tags = {})
     : ApplicationModule(owner, name, description, modifier, tags), _parameterTags(parameterTags), _input(input),
-      status(this, output, "", "", outputTags) {}
+      status(this, output, "", outputTags) {}
 
     StatusMonitor(StatusMonitor&&) = default;
 
@@ -61,8 +60,8 @@ namespace ChimeraTK {
 
     const std::string _input;
 
-    /**One of four possible states to be reported*/
-    ScalarOutput<uint16_t> status;
+    /** Status to be reported */
+    StatusOutput status;
 
     /** Disable the monitor. The status will always be OFF. You don't have to connect this input.
      *  When there is no feeder, ApplicationCore will connect it to a constant feeder with value 0, hence the monitor is always enabled.
@@ -118,16 +117,16 @@ namespace ChimeraTK {
       while(true) {
         // evaluate and publish first, then read and wait. This takes care of the publishing the initial variables
         if(StatusMonitorImpl<T>::disable != 0) {
-          StatusMonitorImpl<T>::status = OFF;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::OFF;
         }
         else if(StatusMonitorImpl<T>::oneUp.watch >= fault) {
-          StatusMonitorImpl<T>::status = FAULT;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::FAULT;
         }
         else if(StatusMonitorImpl<T>::oneUp.watch >= warning) {
-          StatusMonitorImpl<T>::status = WARNING;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::WARNING;
         }
         else {
-          StatusMonitorImpl<T>::status = OK;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::OK;
         }
         StatusMonitorImpl<T>::status.write();
         group.readAny();
@@ -151,16 +150,16 @@ namespace ChimeraTK {
       ReadAnyGroup group{StatusMonitorImpl<T>::oneUp.watch, StatusMonitorImpl<T>::disable, warning, fault};
       while(true) {
         if(StatusMonitorImpl<T>::disable != 0) {
-          StatusMonitorImpl<T>::status = OFF;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::OFF;
         }
         else if(StatusMonitorImpl<T>::oneUp.watch <= fault) {
-          StatusMonitorImpl<T>::status = FAULT;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::FAULT;
         }
         else if(StatusMonitorImpl<T>::oneUp.watch <= warning) {
-          StatusMonitorImpl<T>::status = WARNING;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::WARNING;
         }
         else {
-          StatusMonitorImpl<T>::status = OK;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::OK;
         }
         StatusMonitorImpl<T>::status.write();
         group.readAny();
@@ -197,20 +196,20 @@ namespace ChimeraTK {
           warningLowerThreshold, faultUpperThreshold, faultLowerThreshold};
       while(true) {
         if(StatusMonitorImpl<T>::disable != 0) {
-          StatusMonitorImpl<T>::status = OFF;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::OFF;
         }
         // Check for fault limits first. Like this they supersede the warning,
         // even if they are stricter then the warning limits (mis-configuration)
         else if(StatusMonitorImpl<T>::oneUp.watch <= faultLowerThreshold ||
             StatusMonitorImpl<T>::oneUp.watch >= faultUpperThreshold) {
-          StatusMonitorImpl<T>::status = FAULT;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::FAULT;
         }
         else if(StatusMonitorImpl<T>::oneUp.watch <= warningLowerThreshold ||
             StatusMonitorImpl<T>::oneUp.watch >= warningUpperThreshold) {
-          StatusMonitorImpl<T>::status = WARNING;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::WARNING;
         }
         else {
-          StatusMonitorImpl<T>::status = OK;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::OK;
         }
         StatusMonitorImpl<T>::status.write();
         group.readAny();
@@ -233,13 +232,13 @@ namespace ChimeraTK {
       ReadAnyGroup group{StatusMonitorImpl<T>::oneUp.watch, StatusMonitorImpl<T>::disable, requiredValue};
       while(true) {
         if(StatusMonitorImpl<T>::disable != 0) {
-          StatusMonitorImpl<T>::status = OFF;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::OFF;
         }
         else if(StatusMonitorImpl<T>::oneUp.watch != requiredValue) {
-          StatusMonitorImpl<T>::status = FAULT;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::FAULT;
         }
         else {
-          StatusMonitorImpl<T>::status = OK;
+          StatusMonitorImpl<T>::status = StatusOutput::Status::OK;
         }
         StatusMonitorImpl<T>::status.write();
         group.readAny();
@@ -247,40 +246,6 @@ namespace ChimeraTK {
     }
   };
 
-  /** Module for On/off status monitoring.
- * If value monitored is different then desired state (on/off) a fault
- * will be reported, otherwise OFF(0) or OK(1) depending on state.
- */
-  template<typename T>
-  struct StateMonitor : public StatusMonitorImpl<T> {
-    using StatusMonitorImpl<T>::StatusMonitorImpl;
-
-    /// The state that we are supposed to have
-    ScalarPushInput<T> nominalState{this, "nominalState", "", "", StatusMonitor::_parameterTags};
-
-    /**This is where state evaluation is done*/
-    void mainLoop() {
-      /** If there is a change either in value monitored or in state, the status is re-evaluated*/
-      ReadAnyGroup group{StatusMonitorImpl<T>::oneUp.watch, StatusMonitorImpl<T>::disable, nominalState};
-      while(true) {
-        if(StatusMonitorImpl<T>::disable != 0) {
-          StatusMonitorImpl<T>::status = OFF;
-        }
-        else if(StatusMonitorImpl<T>::oneUp.watch != nominalState) {
-          StatusMonitorImpl<T>::status = FAULT;
-        }
-        else if(nominalState == OK || nominalState == OFF) {
-          StatusMonitorImpl<T>::status = nominalState;
-        }
-        else {
-          //no correct value
-          StatusMonitorImpl<T>::status = FAULT;
-        }
-        StatusMonitorImpl<T>::status.write();
-        group.readAny();
-      }
-    }
-  };
 } // namespace ChimeraTK
 
 #endif // CHIMERATK_STATUS_MONITOR_H
diff --git a/Modules/src/StatusAggregator.cc b/Modules/src/StatusAggregator.cc
index e3cb5221d923358df0096b9f954965a644c37d3b..77c24f514fcc4adb1906dd2932a09756d4714de0 100644
--- a/Modules/src/StatusAggregator.cc
+++ b/Modules/src/StatusAggregator.cc
@@ -1,114 +1,203 @@
 #include "StatusAggregator.h"
+#include "ControlSystemModule.h"
 
 #include <list>
 #include <regex>
 
 namespace ChimeraTK {
 
-  void StatusAggregator::populateStatusInput() {
-    std::cout << "Populating aggregator " << getName() << ", fully qualified name is: " << getQualifiedName()
-              << std::endl;
-
-    // Another try, virtualise the entire Application
-    auto virtualisedApplication = Application::getInstance().findTag(".*");
-    auto virtualPathToThis = getVirtualQualifiedName();
-
-    // Remove name of leaf node from the path
-    auto pathEnd = virtualPathToThis.find_last_of("/") - 1;
-    auto pathWithoutLeafNode{virtualPathToThis.substr(0, pathEnd + 1)};
-
-    std::list<VariableNetworkNode> allAccessors;
-    std::list<Module*> allSubmodules;
-    if(pathWithoutLeafNode == "") {
-      // Path to this module, the parent is the Application
-      //FIXME Not using getAccessorListRecursive here, yet, because it crashes
-      allAccessors = virtualisedApplication.getAccessorList();
-      allSubmodules = virtualisedApplication.getSubmoduleList();
-    }
-    else {
-      Module& virtualParent = virtualisedApplication.submodule(pathWithoutLeafNode);
+  /********************************************************************************************************************/
 
-      virtualParent.dump();
+  StatusAggregator::StatusAggregator(EntityOwner* owner, const std::string& name, const std::string& description,
+      PriorityMode mode, const std::unordered_set<std::string>& tagsToAggregate,
+      const std::unordered_set<std::string>& outputTags)
+  : ApplicationModule(owner, "aggregator", description, HierarchyModifier::hideThis, outputTags), _output(this, name),
+    _mode(mode), _tagsToAggregate(tagsToAggregate) {
+    // add reserved tag tagAggregatedStatus to the status output, so it can be detected by other StatusAggregators
+    _output.status.addTag(tagAggregatedStatus);
 
-      allAccessors = virtualParent.getAccessorListRecursive();
-      allSubmodules = virtualParent.getSubmoduleList();
-    }
+    // search the variable tree for StatusOutputs and create the matching inputs
+    populateStatusInput();
 
-    std::cout << "  Size of allAccessors: " << allAccessors.size() << std::endl;
-    std::cout << "  Size of allSubmodules: " << allSubmodules.size() << std::endl;
-
-    // Approaches to get the modules of interest to feed to scanAndPopulateFromHierarchyLeve:
-    // 1. directly call getSubmoduleList on each level: gives VirtualModules and the dynamic_casts below fail
-    // 2.use getAccessorList and call getOwningModule() on each Accessor (as done below): Does not find the status outputs right now
-    //   because  Also, this means more effort to detect and recurse into ModuleGroups
-    // 3. Use getAccessorListRecursive to get all underlying accessors and call getOwningModule() on those (see commented code below).
-    //    Works, but makes level-per-level processing harder, e.g, where to stop if we found another aggregator on a branch below
-    //    for(auto acc : virtualisedApplication.getAccessorListRecursive()) {
-    //      if(acc.getDirection().dir == VariableDirection::feeding) {
-    //        std::cout << "      -- Accessor: " << acc.getName() << " of module: " << acc.getOwningModule()->getName()
-    //                  << std::endl;
-    //      }
-    //    }
-    // 4. Combine 1. and 2. to get the non-virtual modules per virtual level, see below:
-    //    for(auto module : allSubmodules) {
-    //      auto accessors = module->getAccessorList();
-    //      for(auto acc : accessors)
-    //        if(acc.getDirection().dir == VariableDirection::feeding) {
-    //          std::cout << "      -- Accessor: " << acc.getName() << " of module: " << acc.getOwningModule()->getName()
-    //                    << std::endl;
-    //        }
-    //    }
-
-    scanAndPopulateFromHierarchyLevel(allAccessors);
-
-    std::cout << std::endl << std::endl;
-  } // poplateStatusInput()*/
-
-  void StatusAggregator::scanAndPopulateFromHierarchyLevel(std::list<VariableNetworkNode> nodes) {
-    if(nodes.empty()) {
-      return;
+    // check maximum size of tagsToAggregate
+    if(tagsToAggregate.size() > 1) {
+      throw ChimeraTK::logic_error("StatusAggregator: List of tagsToAggregate must contain at most one tag.");
     }
+  }
 
-    bool statusAggregatorFound{false};
-    std::list<Module*> instancesToBeAggregated;
+  /********************************************************************************************************************/
 
-    // This does loops per level:
-    // 1. Find all StatusMonitors and StatusAggregators on this level, if there
-    //    is an aggregator, we can discard the Monitors on this level
-    // 2. Iterate over instancesToBeAggregated and add to statusInput
-    for(auto node : nodes) {
-      // Only check feeding nodes to test each Module only once on the status output
-      if(node.getDirection().dir != VariableDirection::feeding) {
+  void StatusAggregator::populateStatusInput() {
+    scanAndPopulateFromHierarchyLevel(*getOwner(), ".");
+
+    // Check if no inputs are present (nothing found to aggregate)
+    if(_inputs.empty()) {
+      throw ChimeraTK::logic_error("StatusAggregator " + VariableNetworkNode(_output.status).getQualifiedName() +
+          " has not found anything to aggregate.");
+    }
+  }
+
+  /********************************************************************************************************************/
+
+  void StatusAggregator::scanAndPopulateFromHierarchyLevel(EntityOwner& module, const std::string& namePrefix) {
+    // Search for StatusOutputs to aggregate
+    for(auto& node : module.getAccessorList()) {
+      // Filter required tags
+      // Note: findTag() cannot be used instead, since the search must be done on the original C++ hierarchy. Otherwise
+      // it is not possible to identify which StatusOutputs are aggregated by other StatusAggregators.
+      const auto& tags = node.getTags();
+      if(tags.find(StatusOutput::tagStatusOutput) == tags.end()) {
+        // StatusOutput's reserved tag not present: not a StatusOutput
         continue;
       }
-      auto module{node.getOwningModule()};
-      std::cout << "Scanning Module " << module->getName() << std::endl;
+      bool skip = false;
+      for(const auto& tag : _tagsToAggregate) {
+        // Each tag attached to this StatusAggregator must be present at all StatusOutputs to be aggregated
+        if(tags.find(tag) == tags.end()) {
+          skip = true;
+          break;
+        }
+      }
+      if(skip) continue;
 
-      if(dynamic_cast<StatusMonitor*>(module)) {
-        std::cout << "  Found Monitor " << module->getName() << std::endl;
+      // All variables with the StatusOutput::tagStatusOutput need to be feeding, otherwise someone has used the tag
+      // wrongly
+      if(node.getDirection().dir != VariableDirection::feeding) {
+        throw ChimeraTK::logic_error("BUG DETECTED: StatusOutput's reserved tag has been found on a consumer.");
       }
-      else if(dynamic_cast<StatusAggregator*>(module)) {
-        std::string moduleName = module->getName();
-        if(statusAggregatorFound) {
-          throw ChimeraTK::logic_error("StatusAggregator: A second instance was found on the same hierarchy level.");
+
+      // Create matching input for the found StatusOutput of the other StatusAggregator
+      _inputs.emplace_back(this, node.getName(), "", std::unordered_set<std::string>{tagInternalVars});
+      node >> _inputs.back();
+    }
+
+    // Search for StatusAggregators among submodules
+    const auto& ml = module.getSubmoduleList();
+    for(const auto& submodule : ml) {
+      // do nothing, if it is *this
+      if(submodule == this) continue;
+
+      auto* aggregator = dynamic_cast<StatusAggregator*>(submodule);
+      if(aggregator != nullptr) {
+        // never aggregate on the same level
+        if(aggregator->getOwner() == getOwner()) {
+          continue;
         }
-        statusAggregatorFound = true;
 
-        std::cout << "Found Aggregator " << moduleName << std::endl;
+        // if another StatusAggregator has been found, check tags
+        bool skip = false;
+        const auto& tags = aggregator->_tagsToAggregate;
+        for(const auto& tag : _tagsToAggregate) {
+          // Each tag attached to this StatusAggregator must be present at all StatusOutputs to be aggregated
+          if(tags.find(tag) == tags.end()) {
+            skip = true;
+            break;
+          }
+        }
+        if(skip) continue;
 
-        statusInput.emplace_back(this, moduleName, "", "");
+        // aggregate the StatusAggregator's result
+        auto node = VariableNetworkNode(aggregator->_output.status);
+        _inputs.emplace_back(this, node.getName(), "", std::unordered_set<std::string>{tagInternalVars});
+        node >> _inputs.back();
+        return;
       }
-      else if(dynamic_cast<ModuleGroup*>(module)) {
-        std::cout << "Found ModuleGroup " << module->getName() << std::endl;
-        // Process level below
-        scanAndPopulateFromHierarchyLevel(module->getAccessorList());
+    }
+
+    // If no (matching) StatusAggregator is found, recurse into sub-modules
+    for(const auto& submodule : ml) {
+      if(dynamic_cast<StatusAggregator*>(submodule) != nullptr) continue; // StatusAggregators are already handled
+      scanAndPopulateFromHierarchyLevel(*submodule, namePrefix + "/" + submodule->getName());
+    }
+  }
+
+  /********************************************************************************************************************/
+
+  int StatusAggregator::getPriority(StatusOutput::Status status) {
+    auto is = int32_t(status);
+
+    // Note, without alteration of the value, we have the PriorityMode::fwko
+    if(_mode == PriorityMode::fwok) {
+      // swap 0 <-> 1
+      if(is == 0) {
+        is = 1;
       }
-      else {
-        //FIXME (wip) Some warning if a module could not be properly classified
-        std::cout << "StatusAggregator WARNING: Module" << module->getName() << " was not detected." << std::endl;
+      else if(is == 1) {
+        is = 0;
       }
     }
+    else if(_mode == PriorityMode::ofwk) {
+      // subtract 1, then change -1 into 3
+      --is;
+      if(is == -1) is = 3;
+    }
+    else if(_mode == PriorityMode::fw_warn_mixed) {
+      // change 0,1 into -1 (cf. special meaning of -1)
+      if(is <= 1) is = -1;
+    }
 
-    // 2. TODO Add status inputs
+    return is;
   }
+
+  /********************************************************************************************************************/
+
+  void StatusAggregator::mainLoop() {
+    auto rag = readAnyGroup();
+    bool initialValue = true;
+    while(true) {
+      // find highest priority status of all inputs
+      StatusOutput::Status status;
+      bool statusSet = false; // flag whether status has been set from an input already
+      for(auto& input : _inputs) {
+        auto prio = getPriority(input);
+        if(!statusSet || prio > getPriority(status)) {
+          status = input;
+          statusSet = true;
+        }
+        else if(prio == -1) { //  -1 means, we need to warn about mixed values
+          if(getPriority(status) == -1 && input != status) {
+            status = StatusOutput::Status::WARNING;
+          }
+        }
+      }
+      assert(statusSet);
+
+      // write status only if changed, but always write initial value out
+      if(status != _output.status || initialValue) {
+        _output.status = status;
+        _output.status.write();
+        initialValue = false;
+      }
+
+      // wait for changed inputs
+      rag.readAny();
+    }
+  }
+
+  /********************************************************************************************************************/
+
+  void StatusAggregator::findTagAndAppendToModule(VirtualModule& virtualParent, const std::string& tag,
+      bool eliminateAllHierarchies, bool eliminateFirstHierarchy, bool negate, VirtualModule& root) const {
+    // Change behaviour to exclude the auto-generated inputs which are connected to the data sources. Otherwise those
+    // variables might get published twice to the control system, if findTag(".*") is used to connect the entire
+    // application to the control system.
+    // This is a temporary solution. In future, instead the inputs should be generated at the same place in the
+    // hierarchy as the source variable, and the connetion should not be made by the module itself. This currently would
+    // be complicated to implement, since it is difficult to find the correct virtual name for the variables.
+
+    struct MyVirtualModule : VirtualModule {
+      using VirtualModule::VirtualModule;
+      using VirtualModule::findTagAndAppendToModule;
+    };
+
+    MyVirtualModule tempParent("tempRoot", "", ModuleType::ApplicationModule);
+    MyVirtualModule tempRoot("tempRoot", "", ModuleType::ApplicationModule);
+    EntityOwner::findTagAndAppendToModule(
+        tempParent, tagInternalVars, eliminateAllHierarchies, eliminateFirstHierarchy, true, tempRoot);
+    tempParent.findTagAndAppendToModule(virtualParent, tag, false, true, negate, root);
+    tempRoot.findTagAndAppendToModule(root, tag, false, true, negate, root);
+  }
+
+  /********************************************************************************************************************/
+
 } // namespace ChimeraTK
diff --git a/src/Module.cc b/src/Module.cc
index 094b2694b1d2f3b04657794c2727f163cd0b0423..41285e0f4167a3c94be0cc28577ef57443d33f60 100644
--- a/src/Module.cc
+++ b/src/Module.cc
@@ -38,8 +38,7 @@ namespace ChimeraTK {
     EntityOwner::operator=(std::move(other));
     _owner = other._owner;
     if(_owner != nullptr) _owner->registerModule(this, false);
-    // note: the other module unregisters itself in its destructor - will will be
-    // called next after any move operation
+    // note: the other module unregisters itself in its destructor - which will be called next after any move operation
     return *this;
   }
   /*********************************************************************************************************************/
diff --git a/tests/executables_src/testStatusAggregator.cc b/tests/executables_src/testStatusAggregator.cc
index 17d7f3460afb9ecd43f56ab57c603d6d2d8743ea..aa7a3ea83fbb6784c1c44397e6ec6c8050c0b687 100644
--- a/tests/executables_src/testStatusAggregator.cc
+++ b/tests/executables_src/testStatusAggregator.cc
@@ -1,66 +1,359 @@
 
-#define BOOST_TEST_MODULE testStatusAggregator
-#include <boost/test/included/unit_test.hpp>
-
+#include "StatusMonitor.h"
 #include "StatusAggregator.h"
 
 #include "Application.h"
 #include "ModuleGroup.h"
 #include "TestFacility.h"
 
+#define BOOST_NO_EXCEPTIONS
+#define BOOST_TEST_MODULE testStatusAggregator
+#include <boost/test/included/unit_test.hpp>
+#undef BOOST_NO_EXCEPTIONS
+
 using namespace boost::unit_test_framework;
 
 namespace ctk = ChimeraTK;
 
-struct OuterGroup : public ctk::ModuleGroup {
-  using ctk::ModuleGroup::ModuleGroup;
-  virtual ~OuterGroup() {}
+/**********************************************************************************************************************/
+
+struct StatusGenerator : ctk::ApplicationModule {
+  using ctk::ApplicationModule::ApplicationModule;
+  ctk::StatusOutput status{this, getName(), ""};
+  void mainLoop() override {}
+};
 
-  ctk::StateMonitor<uint8_t> outerStateMonitor{this, "outerStateMonitor", "", "watch", "status",
-      ctk::HierarchyModifier::none, {"OUTER_MON_OUTPUT"}, {"OUTER_MON_PARAMS"}, {"OUTER_MON_INPUT"}};
+/**********************************************************************************************************************/
 
-  struct InnerGroup : public ctk::ModuleGroup {
+struct TestApplication : ctk::Application {
+  TestApplication() : Application("testApp") {}
+  ~TestApplication() override { shutdown(); }
+
+  StatusGenerator s{this, "s", "Status", ctk::HierarchyModifier::hideThis};
+
+  struct OuterGroup : ctk::ModuleGroup {
     using ctk::ModuleGroup::ModuleGroup;
 
-    ctk::StateMonitor<uint8_t> innerStateMonitorNone{this, "innerMinMonitorNone", "", "watch", "status",
-        ctk::HierarchyModifier::none, {"INNER_MON_OUTPUT"}, {"INNER_MON_PARAMS"}, {"INNER_MON_INPUT"}};
+    StatusGenerator s1{this, "s1", "Status 1", ctk::HierarchyModifier::hideThis};
+    StatusGenerator s2{this, "s2", "Status 2", ctk::HierarchyModifier::hideThis};
+
+    struct InnerGroup : ctk::ModuleGroup {
+      using ctk::ModuleGroup::ModuleGroup;
+      StatusGenerator s{this, "s", "Status", ctk::HierarchyModifier::hideThis};
+      StatusGenerator deep{this, "deep", "Status"};
+    };
+    InnerGroup innerGroup1{this, "InnerGroup1", ""};
+    InnerGroup innerGroup2{this, "InnerGroup2", ""};
 
-    ctk::StateMonitor<uint8_t> innerStateMonitorHideThis{this, "innerStateMonitorHideThis", "", "watch", "status",
-        ctk::HierarchyModifier::hideThis, {"INNER_MON_OUTPUT"}, {"INNER_MON_PARAMS"}, {"INNER_MON_INPUT"}};
+  } outerGroup{this, "OuterGroup", ""};
+
+  ctk::StatusAggregator aggregator{
+      this, "Aggregated/status", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko};
+};
 
-    ctk::StateMonitor<uint8_t> innerStateMonitorOneUp{this, "innerStateMonitorOneUp", "", "watch", "status",
-        ctk::HierarchyModifier::oneLevelUp, {"INNER_MON_OUTPUT"}, {"INNER_MON_PARAMS"}, {"INNER_MON_INPUT"}};
+/**********************************************************************************************************************/
+
+BOOST_AUTO_TEST_CASE(testSingleNoTags) {
+  std::cout << "testSingleNoTags" << std::endl;
+
+  TestApplication app;
+  ctk::TestFacility test;
 
-  } innerGroup{this, "innerModuleGroup", "", ctk::HierarchyModifier::none};
+  auto status = test.getScalar<int>("/Aggregated/status");
 
-  ctk::StatusAggregator outerStatusAggregator{this, "outerStatusAggregator", "StatusAggregator of OuterGroup",
-      "groupStatus", ctk::HierarchyModifier::none, {"STATUS"}};
+  // write initial values (all in Status::OFF = 0)
+  app.s.status.write();
+  app.outerGroup.s1.status.write();
+  app.outerGroup.s2.status.write();
+  app.outerGroup.innerGroup1.s.status.write();
+  app.outerGroup.innerGroup1.deep.status.write();
+  app.outerGroup.innerGroup2.s.status.write();
+  app.outerGroup.innerGroup2.deep.status.write();
+
+  test.runApplication();
+
+  // check that statuses on different levels are correctly aggregated
+  auto check = [&](auto& var) {
+    var = ctk::StatusOutput::Status::OK;
+    var.write();
+    test.stepApplication();
+    BOOST_CHECK(status.readNonBlocking() == true);
+    BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OK));
+    var = ctk::StatusOutput::Status::OFF;
+    var.write();
+    test.stepApplication();
+    BOOST_CHECK(status.readNonBlocking() == true);
+    BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF));
+  };
+  check(app.s.status);
+  check(app.outerGroup.s1.status);
+  check(app.outerGroup.s2.status);
+  check(app.outerGroup.innerGroup1.s.status);
+  check(app.outerGroup.innerGroup1.deep.status);
+  check(app.outerGroup.innerGroup2.s.status);
+  check(app.outerGroup.innerGroup2.deep.status);
+}
+
+/**********************************************************************************************************************/
+
+struct TestPrioApplication : ctk::Application {
+  TestPrioApplication() : Application("testApp") {}
+  ~TestPrioApplication() override { shutdown(); }
+
+  StatusGenerator s1{this, "s1", "Status 1", ctk::HierarchyModifier::hideThis};
+  StatusGenerator s2{this, "s2", "Status 2", ctk::HierarchyModifier::hideThis};
+
+  ctk::StatusAggregator aggregator;
 };
 
-struct TestApplication : public ctk::Application {
-  TestApplication() : Application("testApp") {}
-  ~TestApplication() override { shutdown(); }
+/**********************************************************************************************************************/
+
+BOOST_AUTO_TEST_CASE(testPriorities) {
+  std::cout << "testPriorities" << std::endl;
+
+  // Define repeated check for a given priority mode
+  auto check = [&](ctk::StatusAggregator::PriorityMode mode, auto prio0, auto prio1, auto prio2, auto prio3,
+                   bool warnMixed01 = false) {
+    TestPrioApplication app;
+    app.aggregator = ctk::StatusAggregator{&app, "Aggregated/status", "aggregated status description", mode};
+
+    ctk::TestFacility test;
+
+    auto status = test.getScalar<int>("/Aggregated/status");
+
+    // write initial values (all in lowest prio value)
+    app.s1.status = prio0;
+    app.s1.status.write();
+    app.s2.status = prio0;
+    app.s2.status.write();
+
+    test.runApplication();
+
+    // check initial value
+    status.readNonBlocking(); // do not check return value, as it will only be written when changed
+    BOOST_CHECK_EQUAL(int(status), int(prio0));
+
+    // define repeated check to test all combinations of two given values with different priority
+    auto subcheck = [&](auto lower, auto higher, bool warnMixed = false) {
+      std::cout << int(lower) << " vs. " << int(higher) << std::endl;
+      app.s1.status = lower;
+      app.s1.status.write();
+      app.s2.status = lower;
+      app.s2.status.write();
+      test.stepApplication();
+      status.readLatest();
+      BOOST_CHECK_EQUAL(int(status), int(lower));
+      app.s1.status = lower;
+      app.s1.status.write();
+      app.s2.status = higher;
+      app.s2.status.write();
+      test.stepApplication();
+      status.readLatest();
+      if(!warnMixed) {
+        BOOST_CHECK_EQUAL(int(status), int(higher));
+      }
+      else {
+        BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::WARNING));
+      }
+      app.s1.status = higher;
+      app.s1.status.write();
+      app.s2.status = lower;
+      app.s2.status.write();
+      test.stepApplication();
+      status.readLatest();
+      if(!warnMixed) {
+        BOOST_CHECK_EQUAL(int(status), int(higher));
+      }
+      else {
+        BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::WARNING));
+      }
+      app.s1.status = higher;
+      app.s1.status.write();
+      app.s2.status = higher;
+      app.s2.status.write();
+      test.stepApplication();
+      status.readLatest();
+      BOOST_CHECK_EQUAL(int(status), int(higher));
+    };
 
-  OuterGroup outerModuleGroup1{this, "outerModuleGroup1", "", ctk::HierarchyModifier::none};
-  //OuterGroup outerModuleGroup2{this, "outerModuleGroup2", "", ctk::HierarchyModifier::hideThis};
+    // all prios against each other
+    subcheck(prio0, prio1, warnMixed01);
+    subcheck(prio0, prio2);
+    subcheck(prio0, prio3);
+    subcheck(prio1, prio2);
+    subcheck(prio1, prio3);
+    subcheck(prio2, prio3);
+  };
 
-  ctk::StateMonitor<uint8_t> globalStateMonitor{this, "globalStateMonitor", "", "stateWatch", "stateStatus",
-      ctk::HierarchyModifier::none, {"GLOBAL_MON_OUTPUT"}, {"GLOBAL_MON_PARAMS"}, {"GLOBAL_MON_INPUT"}};
+  // check all priority modes
+  std::cout << "PriorityMode::fwko" << std::endl;
+  check(ctk::StatusAggregator::PriorityMode::fwko, ctk::StatusOutput::Status::OFF, ctk::StatusOutput::Status::OK,
+      ctk::StatusOutput::Status::WARNING, ctk::StatusOutput::Status::FAULT);
+  std::cout << "PriorityMode::fwok" << std::endl;
+  check(ctk::StatusAggregator::PriorityMode::fwok, ctk::StatusOutput::Status::OK, ctk::StatusOutput::Status::OFF,
+      ctk::StatusOutput::Status::WARNING, ctk::StatusOutput::Status::FAULT);
+  std::cout << "PriorityMode::ofwk" << std::endl;
+  check(ctk::StatusAggregator::PriorityMode::ofwk, ctk::StatusOutput::Status::OK, ctk::StatusOutput::Status::WARNING,
+      ctk::StatusOutput::Status::FAULT, ctk::StatusOutput::Status::OFF);
+  std::cout << "PriorityMode::fw_warn_mixed" << std::endl;
+  check(ctk::StatusAggregator::PriorityMode::fw_warn_mixed, ctk::StatusOutput::Status::OFF,
+      ctk::StatusOutput::Status::OK, ctk::StatusOutput::Status::WARNING, ctk::StatusOutput::Status::FAULT, true);
+}
+
+/**********************************************************************************************************************/
+
+struct TestApplication2Levels : ctk::Application {
+  TestApplication2Levels() : Application("testApp") {}
+  ~TestApplication2Levels() override { shutdown(); }
+
+  StatusGenerator s{this, "s", "Status", ctk::HierarchyModifier::hideThis};
+
+  struct OuterGroup : ctk::ModuleGroup {
+    using ctk::ModuleGroup::ModuleGroup;
+
+    StatusGenerator s1{this, "s1", "Status 1", ctk::HierarchyModifier::hideThis};
+    StatusGenerator s2{this, "s2", "Status 2", ctk::HierarchyModifier::hideThis};
 
-  ctk::ControlSystemModule cs;
+    ctk::StatusAggregator extraAggregator{
+        this, "/Aggregated/extraStatus", "aggregated status description", ctk::StatusAggregator::PriorityMode::ofwk};
 
-  ctk::StatusAggregator globalStatusAggregator{this, "globalStatusAggregator", "Global StatusAggregator of testApp",
-      "globalStatus", ctk::HierarchyModifier::none, {"STATUS"}};
+  } outerGroup{this, "OuterGroup", ""};
 
-  void defineConnections() { findTag(".*").connectTo(cs); }
+  ctk::StatusAggregator aggregator{
+      this, "Aggregated/status", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko};
 };
 
-BOOST_AUTO_TEST_CASE(testStatusAggregator) {
-  std::cout << "testStatusAggregator" << std::endl;
+/**********************************************************************************************************************/
 
-  TestApplication app;
+BOOST_AUTO_TEST_CASE(testTwoLevels) {
+  std::cout << "testTwoLevels" << std::endl;
+  TestApplication2Levels app;
 
   ctk::TestFacility test;
+
+  auto status = test.getScalar<int>("/Aggregated/status");
+  auto extraStatus = test.getScalar<int>("/Aggregated/extraStatus");
+
+  // write initial values [default/0 value is OFF]
+  app.s.status.write();
+  app.outerGroup.s2.status.write();
+  // Set one of the inputs for the extraAggregator to fault, which has no effect, since one other is OFF which is
+  // prioritised. If the top-level aggregator would wrongly aggregate this input directly, it would go to FAULT.
+  app.outerGroup.s1.status = ctk::StatusOutput::Status::FAULT;
+  app.outerGroup.s1.status.write();
+
   test.runApplication();
-  //app.cs.dump();
+
+  // check the initial values
+  extraStatus.readLatest();
+  BOOST_CHECK_EQUAL(int(extraStatus), int(ctk::StatusOutput::Status::OFF));
+  status.readLatest();
+  BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF));
+
+  // change status which goes directly into the upper aggregator
+  app.s.status = ctk::StatusOutput::Status::OK;
+  app.s.status.write();
+  test.stepApplication();
+  status.readLatest();
+  BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OK));
+  app.s.status = ctk::StatusOutput::Status::OFF;
+  app.s.status.write();
+  test.stepApplication();
+  status.readLatest();
+  BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF));
+
+  // change status which goes into the lower aggregator (then the FAULT of s1 will win)
+  app.outerGroup.s2.status = ctk::StatusOutput::Status::OK;
+  app.outerGroup.s2.status.write();
+  test.stepApplication();
+  status.readLatest();
+  BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::FAULT));
+  app.outerGroup.s2.status = ctk::StatusOutput::Status::OFF;
+  app.outerGroup.s2.status.write();
+  test.stepApplication();
+  status.readLatest();
+  BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF));
 }
+
+/**********************************************************************************************************************/
+
+struct TestApplicationTags : ctk::Application {
+  TestApplicationTags() : Application("testApp") {}
+  ~TestApplicationTags() override { shutdown(); }
+
+  struct OuterGroup : ctk::ModuleGroup {
+    using ctk::ModuleGroup::ModuleGroup;
+
+    StatusGenerator sA{this, "sA", "Status 1", ctk::HierarchyModifier::hideThis, {"A"}};
+    StatusGenerator sAB{this, "sAB", "Status 2", ctk::HierarchyModifier::hideThis, {"A", "B"}};
+
+    ctk::StatusAggregator aggregateA{
+        this, "aggregateA", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko, {"A"}};
+    ctk::StatusAggregator aggregateB{this, "aggregateB", "aggregated status description",
+        ctk::StatusAggregator::PriorityMode::fwko, {"B"}, {"A"}}; // the "A" tag should be ignored by other aggregators
+
+  } group{this, "Group", ""};
+
+  // Use other priority mode here to make sure only the aggregators are aggregated, not the generators
+  ctk::StatusAggregator aggregateA{
+      this, "aggregateA", "aggregated status description", ctk::StatusAggregator::PriorityMode::ofwk, {"A"}};
+  ctk::StatusAggregator aggregateB{
+      this, "aggregateB", "aggregated status description", ctk::StatusAggregator::PriorityMode::ofwk, {"B"}};
+  ctk::StatusAggregator aggregateAll{
+      this, "aggregateAll", "aggregated status description", ctk::StatusAggregator::PriorityMode::fw_warn_mixed};
+};
+
+/**********************************************************************************************************************/
+
+BOOST_AUTO_TEST_CASE(testTags) {
+  std::cout << "testTags" << std::endl;
+
+  TestApplicationTags app;
+  ctk::TestFacility test;
+
+  auto aggregateA = test.getScalar<int>("/aggregateA");
+  auto aggregateB = test.getScalar<int>("/aggregateB");
+  auto aggregateAll = test.getScalar<int>("/aggregateAll");
+  auto Group_aggregateA = test.getScalar<int>("/Group/aggregateA");
+  auto Group_aggregateB = test.getScalar<int>("/Group/aggregateB");
+
+  // write initial values
+  app.group.sA.status = ctk::StatusOutput::Status::WARNING;
+  app.group.sA.status.write();
+  app.group.sAB.status = ctk::StatusOutput::Status::OFF;
+  app.group.sAB.status.write();
+
+  test.runApplication();
+
+  // check initial values
+  aggregateA.readLatest();
+  aggregateB.readLatest();
+  aggregateAll.readLatest();
+  Group_aggregateA.readLatest();
+  Group_aggregateB.readLatest();
+  BOOST_CHECK_EQUAL(int(aggregateA), int(ctk::StatusOutput::Status::WARNING));
+  BOOST_CHECK_EQUAL(int(aggregateB), int(ctk::StatusOutput::Status::OFF));
+  BOOST_CHECK_EQUAL(int(aggregateAll), int(ctk::StatusOutput::Status::WARNING));
+  BOOST_CHECK_EQUAL(int(Group_aggregateA), int(ctk::StatusOutput::Status::WARNING));
+  BOOST_CHECK_EQUAL(int(Group_aggregateB), int(ctk::StatusOutput::Status::OFF));
+
+  app.group.sAB.status = ctk::StatusOutput::Status::FAULT;
+  app.group.sAB.status.write();
+
+  test.stepApplication();
+
+  // change value tagged with 'A' and 'B', affecting all aggregators to highest priority value, so it is visible
+  // everywhere
+  aggregateA.readLatest();
+  aggregateB.readLatest();
+  aggregateAll.readLatest();
+  Group_aggregateA.readLatest();
+  Group_aggregateB.readLatest();
+  BOOST_CHECK_EQUAL(int(aggregateA), int(ctk::StatusOutput::Status::FAULT));
+  BOOST_CHECK_EQUAL(int(aggregateB), int(ctk::StatusOutput::Status::FAULT));
+  BOOST_CHECK_EQUAL(int(aggregateAll), int(ctk::StatusOutput::Status::FAULT));
+  BOOST_CHECK_EQUAL(int(Group_aggregateA), int(ctk::StatusOutput::Status::FAULT));
+  BOOST_CHECK_EQUAL(int(Group_aggregateB), int(ctk::StatusOutput::Status::FAULT));
+}
+
+/**********************************************************************************************************************/
diff --git a/tests/executables_src/testStatusModule.cc b/tests/executables_src/testStatusModule.cc
index 4f0f1b2729cc4a4b3b0ab43123a8cd140059fb92..578c07b2e0c2d1e1d77cbe8b936a226b9a5155df 100644
--- a/tests/executables_src/testStatusModule.cc
+++ b/tests/executables_src/testStatusModule.cc
@@ -40,6 +40,10 @@ BOOST_AUTO_TEST_CASE(testMaxMonitor) {
   std::cout << "testMaxMonitor" << std::endl;
   TestApplication<ctk::MaxMonitor<double_t>> app;
 
+  // check that the reserved StatusOutput tag is present at the output, required for StatusAggregator integration
+  auto tags = ctk::VariableNetworkNode(app.monitor.status).getTags();
+  BOOST_CHECK(tags.find(ctk::StatusOutput::tagStatusOutput) != tags.end());
+
   ctk::TestFacility test;
   test.runApplication();
   //app.cs.dump();
@@ -59,18 +63,18 @@ BOOST_AUTO_TEST_CASE(testMaxMonitor) {
   watch.write();
   test.stepApplication();
 
-  auto status = test.getScalar<uint16_t>(std::string("/Monitor/status"));
+  auto status = test.getScalar<int32_t>(std::string("/Monitor/status"));
   status.readLatest();
 
   //should be in OK state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   //   //just below the warning level
   watch = 49.99;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // drop in a disable test.
   auto disable = test.getScalar<int>("/Monitor/disable");
@@ -78,67 +82,67 @@ BOOST_AUTO_TEST_CASE(testMaxMonitor) {
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF));
 
   disable = 0;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // slightly above at the upper warning threshold (exact is not good due to rounding errors in floats/doubles)
   watch = 50.01;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   // drop in a disable test.
   disable = 1;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF));
 
   disable = 0;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   //just below the fault threshold,. still warning
   watch = 59.99;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   // slightly above at the upper fault threshold (exact is not good due to rounding errors in floats/doubles)
   watch = 60.01;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   // drop in a disable test.
   disable = 1;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF));
 
   disable = 0;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   //increase well above the upper fault level
   watch = 65;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   // now check that changing the status is updated correctly if we change the limits
 
@@ -148,7 +152,7 @@ BOOST_AUTO_TEST_CASE(testMaxMonitor) {
   test.stepApplication();
   status.readLatest();
   //should be in WARNING state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   //increase warning value greater than watch
   warning = 66;
@@ -156,7 +160,7 @@ BOOST_AUTO_TEST_CASE(testMaxMonitor) {
   test.stepApplication();
   status.readLatest();
   //should be in OK state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // Set the upper fault limit below the upper warning limit and below the current temperature. The warning is not active, but the fault.
   // Although this is not a reasonable configuration the fault limit must superseed the warning and the status has to be fault.
@@ -164,11 +168,11 @@ BOOST_AUTO_TEST_CASE(testMaxMonitor) {
   fault.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   // check that the tags are applied correctly
-  BOOST_CHECK_EQUAL(status, test.readScalar<uint16_t>("/MyNiceMonitorCopy/Monitor/status"));
-  BOOST_CHECK_EQUAL(status, test.readScalar<uint16_t>("/MonitorOutput/Monitor/status"));
+  BOOST_CHECK_EQUAL(status, test.readScalar<int32_t>("/MyNiceMonitorCopy/Monitor/status"));
+  BOOST_CHECK_EQUAL(status, test.readScalar<int32_t>("/MonitorOutput/Monitor/status"));
   BOOST_CHECK_EQUAL(watch, test.readScalar<double>("/MyNiceMonitorCopy/watch"));
   BOOST_CHECK_EQUAL(fault, test.readScalar<double>("/MonitorParameters/Monitor/upperFaultThreshold"));
   BOOST_CHECK_EQUAL(warning, test.readScalar<double>("/MonitorParameters/Monitor/upperWarningThreshold"));
@@ -180,6 +184,10 @@ BOOST_AUTO_TEST_CASE(testMinMonitor) {
 
   TestApplication<ctk::MinMonitor<uint>> app;
 
+  // check that the reserved StatusOutput tag is present at the output, required for StatusAggregator integration
+  auto tags = ctk::VariableNetworkNode(app.monitor.status).getTags();
+  BOOST_CHECK(tags.find(ctk::StatusOutput::tagStatusOutput) != tags.end());
+
   ctk::TestFacility test;
   test.runApplication();
   //app.dumpConnections();
@@ -199,18 +207,18 @@ BOOST_AUTO_TEST_CASE(testMinMonitor) {
   watch.write();
   test.stepApplication();
 
-  auto status = test.getScalar<uint16_t>(std::string("/Monitor/status"));
+  auto status = test.getScalar<int32_t>(std::string("/Monitor/status"));
   status.readLatest();
 
   //should be in OK state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // just abow the lower warning limit
   watch = 41;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // drop in a disable test.
   auto disable = test.getScalar<int>("/Monitor/disable");
@@ -218,88 +226,88 @@ BOOST_AUTO_TEST_CASE(testMinMonitor) {
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF));
 
   disable = 0;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   //exactly at the lower warning limit
   watch = 40;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   // drop in a disable test.
   disable = 1;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF));
 
   disable = 0;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   //just above the lower fault limit
   watch = 31;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   //exactly at the lower fault limit (only well defined for int)
   watch = 30;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   // drop in a disable test.
   disable = 1;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF));
 
   disable = 0;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   //way bellow the lower fault limit
   watch = 12;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   // move the temperature back to the good range and check that the status updates correctly when changing the limits
   watch = 41;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // change upper warning limit
   warning = 42;
   warning.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   // rise the temperature above the lower warning limit
   watch = 43;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // Set the lower fault limit above the lower warning limit. The warning is not active, but the fault.
   // Although this is not a reasonable configuration the fault limit must superseed the warning and the status has to be fault.
@@ -307,11 +315,11 @@ BOOST_AUTO_TEST_CASE(testMinMonitor) {
   fault.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   // check that the tags are applied correctly
-  BOOST_CHECK_EQUAL(status, test.readScalar<uint16_t>("/MyNiceMonitorCopy/Monitor/status"));
-  BOOST_CHECK_EQUAL(status, test.readScalar<uint16_t>("/MonitorOutput/Monitor/status"));
+  BOOST_CHECK_EQUAL(status, test.readScalar<int32_t>("/MyNiceMonitorCopy/Monitor/status"));
+  BOOST_CHECK_EQUAL(status, test.readScalar<int32_t>("/MonitorOutput/Monitor/status"));
   BOOST_CHECK_EQUAL(watch, test.readScalar<uint>("/MyNiceMonitorCopy/watch"));
   BOOST_CHECK_EQUAL(fault, test.readScalar<uint>("/MonitorParameters/Monitor/lowerFaultThreshold"));
   BOOST_CHECK_EQUAL(warning, test.readScalar<uint>("/MonitorParameters/Monitor/lowerWarningThreshold"));
@@ -323,6 +331,10 @@ BOOST_AUTO_TEST_CASE(testRangeMonitor) {
   std::cout << "testRangeMonitor" << std::endl;
   TestApplication<ctk::RangeMonitor<int>> app;
 
+  // check that the reserved StatusOutput tag is present at the output, required for StatusAggregator integration
+  auto tags = ctk::VariableNetworkNode(app.monitor.status).getTags();
+  BOOST_CHECK(tags.find(ctk::StatusOutput::tagStatusOutput) != tags.end());
+
   ctk::TestFacility test;
   test.runApplication();
   //app.dumpConnections();
@@ -353,17 +365,17 @@ BOOST_AUTO_TEST_CASE(testRangeMonitor) {
   watch.write();
   test.stepApplication();
 
-  auto status = test.getScalar<uint16_t>(std::string("/Monitor/status"));
+  auto status = test.getScalar<int32_t>(std::string("/Monitor/status"));
   status.readLatest();
 
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   //just below the warning level
   watch = 49;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // drop in a disable test.
   auto disable = test.getScalar<int>("/Monitor/disable");
@@ -371,123 +383,123 @@ BOOST_AUTO_TEST_CASE(testRangeMonitor) {
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF));
 
   disable = 0;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   //exactly at the upper warning threshold (only well defined for int)
   watch = 50;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   // drop in a disable test.
   disable = 1;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF));
 
   disable = 0;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   //just below the fault threshold,. still warning
   watch = 59;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   //exactly at the upper warning threshold (only well defined for int)
   watch = 60;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   // drop in a disable test.
   disable = 1;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF));
 
   disable = 0;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   //increase well above the upper fault level
   watch = 65;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   //back to ok, just abow the lower warning limit
   watch = 41;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   //exactly at the lower warning limit
   watch = 40;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   //just above the lower fault limit
   watch = 31;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   //exactly at the lower fault limit (only well defined for int)
   watch = 30;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   //way bellow the lower fault limit
   watch = 12;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   // Put the value back to the good range, then check that chaning the threshold also updated the status
   watch = 49;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // change upper warning limit
   warningUpperLimit = 48;
   warningUpperLimit.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   // lower the temperature below the upper warning limit
   watch = 47;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // Set the upper fault limit below the upper warning limit. The warning is not active, but the fault.
   // Although this is not a reasonable configuration the fault limit must superseed the warning and the status has to be fault.
@@ -495,28 +507,28 @@ BOOST_AUTO_TEST_CASE(testRangeMonitor) {
   faultUpperLimit.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   // move the temperature back to the good range and repeat for the lower limits
   watch = 41;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // change upper warning limit
   warningLowerLimit = 42;
   warningLowerLimit.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::WARNING);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING));
 
   // rise the temperature above the lower warning limit
   watch = 43;
   watch.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // Set the lower fault limit above the lower warning limit. The warning is not active, but the fault.
   // Although this is not a reasonable configuration the fault limit must superseed the warning and the status has to be fault.
@@ -524,11 +536,11 @@ BOOST_AUTO_TEST_CASE(testRangeMonitor) {
   faultLowerLimit.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   // check that the tags are applied correctly
-  BOOST_CHECK_EQUAL(status, test.readScalar<uint16_t>("/MyNiceMonitorCopy/Monitor/status"));
-  BOOST_CHECK_EQUAL(status, test.readScalar<uint16_t>("/MonitorOutput/Monitor/status"));
+  BOOST_CHECK_EQUAL(status, test.readScalar<int32_t>("/MyNiceMonitorCopy/Monitor/status"));
+  BOOST_CHECK_EQUAL(status, test.readScalar<int32_t>("/MonitorOutput/Monitor/status"));
   BOOST_CHECK_EQUAL(watch, test.readScalar<int>("/MyNiceMonitorCopy/watch"));
   BOOST_CHECK_EQUAL(faultLowerLimit, test.readScalar<int>("/MonitorParameters/Monitor/lowerFaultThreshold"));
   BOOST_CHECK_EQUAL(warningLowerLimit, test.readScalar<int>("/MonitorParameters/Monitor/lowerWarningThreshold"));
@@ -542,6 +554,10 @@ BOOST_AUTO_TEST_CASE(testExactMonitor) {
   std::cout << "testExactMonitor" << std::endl;
   TestApplication<ctk::ExactMonitor<float>> app;
 
+  // check that the reserved StatusOutput tag is present at the output, required for StatusAggregator integration
+  auto tags = ctk::VariableNetworkNode(app.monitor.status).getTags();
+  BOOST_CHECK(tags.find(ctk::StatusOutput::tagStatusOutput) != tags.end());
+
   ctk::TestFacility test;
   test.runApplication();
   //app.dumpConnections();
@@ -556,11 +572,11 @@ BOOST_AUTO_TEST_CASE(testExactMonitor) {
   watch.write();
   test.stepApplication();
 
-  auto status = test.getScalar<uint16_t>(std::string("/Monitor/status"));
+  auto status = test.getScalar<int32_t>(std::string("/Monitor/status"));
   status.readLatest();
 
   //should be in OK state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // drop in a disable test.
   auto disable = test.getScalar<int>("/Monitor/disable");
@@ -568,13 +584,13 @@ BOOST_AUTO_TEST_CASE(testExactMonitor) {
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF));
 
   disable = 0;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   //set watch value different than required value
   watch = 41.4;
@@ -582,27 +598,27 @@ BOOST_AUTO_TEST_CASE(testExactMonitor) {
   test.stepApplication();
   status.readLatest();
   //should be in FAULT state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   // drop in a disable test.
   disable = 1;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF));
 
   disable = 0;
   disable.write();
   test.stepApplication();
   status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   watch = 40.9;
   watch.write();
   test.stepApplication();
   status.readLatest();
   //should be in OK state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   //set requiredValue value different than watch value
   requiredValue = 41.3;
@@ -610,7 +626,7 @@ BOOST_AUTO_TEST_CASE(testExactMonitor) {
   test.stepApplication();
   status.readLatest();
   //should be in WARNING state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT));
 
   //set requiredValue value equals to watch value
   requiredValue = 40.9;
@@ -618,106 +634,13 @@ BOOST_AUTO_TEST_CASE(testExactMonitor) {
   test.stepApplication();
   status.readLatest();
   //should be in WARNING state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
+  BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK));
 
   // check that the tags are applied correctly
-  BOOST_CHECK_EQUAL(status, test.readScalar<uint16_t>("/MyNiceMonitorCopy/Monitor/status"));
-  BOOST_CHECK_EQUAL(status, test.readScalar<uint16_t>("/MonitorOutput/Monitor/status"));
+  BOOST_CHECK_EQUAL(status, test.readScalar<int32_t>("/MyNiceMonitorCopy/Monitor/status"));
+  BOOST_CHECK_EQUAL(status, test.readScalar<int32_t>("/MonitorOutput/Monitor/status"));
   BOOST_CHECK_EQUAL(watch, test.readScalar<float>("/MyNiceMonitorCopy/watch"));
   BOOST_CHECK_EQUAL(requiredValue, test.readScalar<float>("/MonitorParameters/Monitor/requiredValue"));
 }
 
 /*********************************************************************************************************************/
-
-BOOST_AUTO_TEST_CASE(testStateMonitor) {
-  std::cout << "testStateMonitor" << std::endl;
-  TestApplication<ctk::StateMonitor<uint8_t>> app;
-
-  ctk::TestFacility test;
-  test.runApplication();
-  //app.dumpConnections();
-
-  auto stateValue = test.getScalar<uint8_t>(std::string("/Monitor/nominalState"));
-  stateValue = 1;
-  stateValue.write();
-  test.stepApplication();
-
-  auto watch = test.getScalar<uint8_t>(std::string("/watch"));
-  watch = 1;
-  watch.write();
-  test.stepApplication();
-
-  auto status = test.getScalar<uint16_t>(std::string("/Monitor/status"));
-  status.readLatest();
-  //should be in OK state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
-
-  // drop in a disable test.
-  auto disable = test.getScalar<int>("/Monitor/disable");
-  disable = 1;
-  disable.write();
-  test.stepApplication();
-  status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
-
-  disable = 0;
-  disable.write();
-  test.stepApplication();
-  status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OK);
-
-  watch = 0;
-  watch.write();
-  test.stepApplication();
-  status.readLatest();
-  //should be in FAULT state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
-
-  // drop in a disable test.
-  disable = 1;
-  disable.write();
-  test.stepApplication();
-  status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
-
-  disable = 0;
-  disable.write();
-  test.stepApplication();
-  status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
-
-  stateValue = 0;
-  stateValue.write();
-  test.stepApplication();
-  status.readLatest();
-  //should be in OFF state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
-
-  // State change while disabled is detected.
-  disable = 1;
-  disable.write();
-  test.stepApplication();
-  status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
-
-  // it is still disabled
-  stateValue = 1; // should be OK, but watch is still 0  => result would be FAULT if it was not disabled
-  stateValue.write();
-  test.stepApplication();
-  status.readLatest();
-  //should be in OFF state.
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::OFF);
-
-  // after enabling the FAULT becomes visible
-  disable = 0;
-  disable.write();
-  test.stepApplication();
-  status.readLatest();
-  BOOST_CHECK_EQUAL(status, ChimeraTK::States::FAULT);
-
-  // check that the tags are applied correctly
-  BOOST_CHECK_EQUAL(status, test.readScalar<uint16_t>("/MyNiceMonitorCopy/Monitor/status"));
-  BOOST_CHECK_EQUAL(status, test.readScalar<uint16_t>("/MonitorOutput/Monitor/status"));
-  BOOST_CHECK_EQUAL(watch, test.readScalar<uint8_t>("/MyNiceMonitorCopy/watch"));
-  BOOST_CHECK_EQUAL(stateValue, test.readScalar<uint8_t>("/MonitorParameters/Monitor/nominalState"));
-}