diff --git a/Modules/include/StatusAggregator.h b/Modules/include/StatusAggregator.h new file mode 100644 index 0000000000000000000000000000000000000000..abef317792baa87b16b4864777e7045ca5e051c6 --- /dev/null +++ b/Modules/include/StatusAggregator.h @@ -0,0 +1,64 @@ +#ifndef CHIMERATK_STATUS_AGGREGATOR_H +#define CHIMERATK_STATUS_AGGREGATOR_H + +#include "ApplicationModule.h" +#include "ModuleGroup.h" +#include "StatusMonitor.h" + +#include <list> +#include <vector> +#include <string> + +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. + * + * 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(); + } + + ~StatusAggregator() override {} + + protected: + + void mainLoop() override { + + std::cout << "Entered StatusAggregator::mainLoop()" << std::endl; + +// while(true){ +// // Status collection goes here +// } + } + + /// Recursivly search for StatusMonitors and other StatusAggregators + void populateStatusInput(); + + /**One of four possible states to be reported*/ + ScalarOutput<uint16_t> status; + + /**Vector of status inputs */ + std::vector<ScalarPushInput<uint16_t>> statusInput; + + //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"}; + + }; + +} // namespace ChimeraTK +#endif // CHIMERATK_STATUS_AGGREGATOR_H diff --git a/Modules/include/StatusMonitor.h b/Modules/include/StatusMonitor.h index 164341bff527801b227ec2ce6478d5a26c3a421c..030f5cc41e93da50e8bb6b38f12556150b45fee3 100644 --- a/Modules/include/StatusMonitor.h +++ b/Modules/include/StatusMonitor.h @@ -36,31 +36,29 @@ namespace ChimeraTK { /** There are four states that can be reported*/ enum States { OFF, OK, WARNING, ERROR }; - template<typename T> + + /** Common base for StatusMonitors + * + * This holds common process variables that are not dependant on the + * type of the variable to be monitored. A non-template base class + * facilitates checking for the type in the StatusAggregator, which + * needs to identify any StatusMonitor. + */ struct StatusMonitor : public ApplicationModule { - /** Number of convience constructors for ease of use. - * The input and output variable names can be given by user which - * should be mapped with the variables of module to be watched. -*/ + 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), oneUp(this, input), + : ApplicationModule(owner, name, description, modifier, tags), _parameterTags(parameterTags), _input(input), status(this, output, "", "", outputTags) {} - StatusMonitor() { throw logic_error("Default constructor unusable. Just exists to work around gcc bug."); } - ~StatusMonitor() override {} void prepare() override {} /**Tags for parameters. This makes it easier to connect them to e.g, to control system*/ std::unordered_set<std::string> _parameterTags; - /**Input value that should be monitored. It is moved one level up, so it's parallel to this monitor object.*/ - struct OneUp : public VariableGroup { - OneUp(EntityOwner* owner, const std::string& watchName) - : VariableGroup(owner, "hidden", "", HierarchyModifier::oneUpAndHide), watch(this, watchName, "", "") {} - ScalarPushInput<T> watch; - } oneUp; + + const std::string _input; /**One of four possible states to be reported*/ ScalarOutput<uint16_t> status; @@ -71,34 +69,66 @@ namespace ChimeraTK { ScalarPushInput<int> disable{this, "disable", "", "Disable the status monitor"}; }; + + /** Common template base class for StatusMonitors + * + * This provides a ScalarPushInput for the variable to be monitored, which + * can be specified by the input parameter of the constructor. + */ + template<typename T> + struct StatusMonitorImpl : public StatusMonitor { + + /** Number of convience constructors for ease of use. + * The input and output variable names can be given by user which + * should be mapped with the variables of module to be watched. + */ + StatusMonitorImpl(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 = {}) + : StatusMonitor(owner, name, description, input, output, modifier, outputTags, parameterTags, tags), + oneUp(this, input){} + + StatusMonitorImpl() { throw logic_error("Default constructor unusable. Just exists to work around gcc bug."); } + + ~StatusMonitorImpl() override {} + void prepare() override {} + + /**Input value that should be monitored. It is moved one level up, so it's parallel to this monitor object.*/ + struct OneUp : public VariableGroup { + OneUp(EntityOwner* owner, const std::string& watchName) + : VariableGroup(owner, "hidden", "", HierarchyModifier::oneUpAndHide), watch(this, watchName, "", "") {} + ScalarPushInput<T> watch; + } oneUp; + }; + /** Module for status monitoring depending on a maximum threshold value*/ template<typename T> - struct MaxMonitor : public StatusMonitor<T> { - using StatusMonitor<T>::StatusMonitor; + struct MaxMonitor : public StatusMonitorImpl<T> { + using StatusMonitorImpl<T>::StatusMonitorImpl; /** WARNING state to be reported if threshold is reached or exceeded*/ - ScalarPushInput<T> warning{this, "upperWarningThreshold", "", "", StatusMonitor<T>::_parameterTags}; + ScalarPushInput<T> warning{this, "upperWarningThreshold", "", "", StatusMonitor::_parameterTags}; /** ERROR state to be reported if threshold is reached or exceeded*/ - ScalarPushInput<T> error{this, "upperErrorThreshold", "", "", StatusMonitor<T>::_parameterTags}; + ScalarPushInput<T> error{this, "upperErrorThreshold", "", "", StatusMonitor::_parameterTags}; /**This is where state evaluation is done*/ void mainLoop() { /** If there is a change either in value monitored or in thershold values, the status is re-evaluated*/ - ReadAnyGroup group{StatusMonitor<T>::oneUp.watch, StatusMonitor<T>::disable, warning, error}; + ReadAnyGroup group{StatusMonitorImpl<T>::oneUp.watch, StatusMonitorImpl<T>::disable, warning, error}; while(true) { // evaluate and publish first, then read and wait. This takes care of the publishing the initial variables - if(StatusMonitor<T>::disable != 0) { - StatusMonitor<T>::status = OFF; + if(StatusMonitorImpl<T>::disable != 0) { + StatusMonitorImpl<T>::status = OFF; } - else if(StatusMonitor<T>::oneUp.watch >= error) { - StatusMonitor<T>::status = ERROR; + else if(StatusMonitorImpl<T>::oneUp.watch >= error) { + StatusMonitorImpl<T>::status = ERROR; } - else if(StatusMonitor<T>::oneUp.watch >= warning) { - StatusMonitor<T>::status = WARNING; + else if(StatusMonitorImpl<T>::oneUp.watch >= warning) { + StatusMonitorImpl<T>::status = WARNING; } else { - StatusMonitor<T>::status = OK; + StatusMonitorImpl<T>::status = OK; } - StatusMonitor<T>::status.write(); + StatusMonitorImpl<T>::status.write(); group.readAny(); } } @@ -106,32 +136,32 @@ namespace ChimeraTK { /** Module for status monitoring depending on a minimum threshold value*/ template<typename T> - struct MinMonitor : public StatusMonitor<T> { - using StatusMonitor<T>::StatusMonitor; + struct MinMonitor : public StatusMonitorImpl<T> { + using StatusMonitorImpl<T>::StatusMonitorImpl; /** WARNING state to be reported if threshold is crossed*/ - ScalarPushInput<T> warning{this, "lowerWarningThreshold", "", "", StatusMonitor<T>::_parameterTags}; + ScalarPushInput<T> warning{this, "lowerWarningThreshold", "", "", StatusMonitor::_parameterTags}; /** ERROR state to be reported if threshold is crossed*/ - ScalarPushInput<T> error{this, "lowerErrorThreshold", "", "", StatusMonitor<T>::_parameterTags}; + ScalarPushInput<T> error{this, "lowerErrorThreshold", "", "", StatusMonitor::_parameterTags}; /**This is where state evaluation is done*/ void mainLoop() { /** If there is a change either in value monitored or in thershold values, the status is re-evaluated*/ - ReadAnyGroup group{StatusMonitor<T>::oneUp.watch, StatusMonitor<T>::disable, warning, error}; + ReadAnyGroup group{StatusMonitorImpl<T>::oneUp.watch, StatusMonitorImpl<T>::disable, warning, error}; while(true) { - if(StatusMonitor<T>::disable != 0) { - StatusMonitor<T>::status = OFF; + if(StatusMonitorImpl<T>::disable != 0) { + StatusMonitorImpl<T>::status = OFF; } - else if(StatusMonitor<T>::oneUp.watch <= error) { - StatusMonitor<T>::status = ERROR; + else if(StatusMonitorImpl<T>::oneUp.watch <= error) { + StatusMonitorImpl<T>::status = ERROR; } - else if(StatusMonitor<T>::oneUp.watch <= warning) { - StatusMonitor<T>::status = WARNING; + else if(StatusMonitorImpl<T>::oneUp.watch <= warning) { + StatusMonitorImpl<T>::status = WARNING; } else { - StatusMonitor<T>::status = OK; + StatusMonitorImpl<T>::status = OK; } - StatusMonitor<T>::status.write(); + StatusMonitorImpl<T>::status.write(); group.readAny(); } } @@ -145,43 +175,43 @@ namespace ChimeraTK { * set the ranges correctly to issue warning or error. */ template<typename T> - struct RangeMonitor : public StatusMonitor<T> { - using StatusMonitor<T>::StatusMonitor; + struct RangeMonitor : public StatusMonitorImpl<T> { + using StatusMonitorImpl<T>::StatusMonitorImpl; /** WARNING state to be reported if value is in between the upper and * lower threshold including the start and end of thresholds. */ - ScalarPushInput<T> warningUpperThreshold{this, "upperWarningThreshold", "", "", StatusMonitor<T>::_parameterTags}; - ScalarPushInput<T> warningLowerThreshold{this, "lowerWarningThreshold", "", "", StatusMonitor<T>::_parameterTags}; + ScalarPushInput<T> warningUpperThreshold{this, "upperWarningThreshold", "", "", StatusMonitor::_parameterTags}; + ScalarPushInput<T> warningLowerThreshold{this, "lowerWarningThreshold", "", "", StatusMonitor::_parameterTags}; /** ERROR state to be reported if value is in between the upper and * lower threshold including the start and end of thresholds. */ - ScalarPushInput<T> errorUpperThreshold{this, "upperErrorThreshold", "", "", StatusMonitor<T>::_parameterTags}; - ScalarPushInput<T> errorLowerThreshold{this, "lowerErrorThreshold", "", "", StatusMonitor<T>::_parameterTags}; + ScalarPushInput<T> errorUpperThreshold{this, "upperErrorThreshold", "", "", StatusMonitor::_parameterTags}; + ScalarPushInput<T> errorLowerThreshold{this, "lowerErrorThreshold", "", "", StatusMonitor::_parameterTags}; /**This is where state evaluation is done*/ void mainLoop() { /** If there is a change either in value monitored or in thershold values, the status is re-evaluated*/ - ReadAnyGroup group{StatusMonitor<T>::oneUp.watch, StatusMonitor<T>::disable, warningUpperThreshold, warningLowerThreshold, + ReadAnyGroup group{StatusMonitorImpl<T>::oneUp.watch, StatusMonitorImpl<T>::disable, warningUpperThreshold, warningLowerThreshold, errorUpperThreshold, errorLowerThreshold}; while(true) { - if(StatusMonitor<T>::disable != 0) { - StatusMonitor<T>::status = OFF; + if(StatusMonitorImpl<T>::disable != 0) { + StatusMonitorImpl<T>::status = OFF; } // Check for error limits first. Like this they supersede the warning, // even if they are stricter then the warning limits (mis-configuration) - else if(StatusMonitor<T>::oneUp.watch <= errorLowerThreshold || - StatusMonitor<T>::oneUp.watch >= errorUpperThreshold) { - StatusMonitor<T>::status = ERROR; + else if(StatusMonitorImpl<T>::oneUp.watch <= errorLowerThreshold || + StatusMonitorImpl<T>::oneUp.watch >= errorUpperThreshold) { + StatusMonitorImpl<T>::status = ERROR; } - else if(StatusMonitor<T>::oneUp.watch <= warningLowerThreshold || - StatusMonitor<T>::oneUp.watch >= warningUpperThreshold) { - StatusMonitor<T>::status = WARNING; + else if(StatusMonitorImpl<T>::oneUp.watch <= warningLowerThreshold || + StatusMonitorImpl<T>::oneUp.watch >= warningUpperThreshold) { + StatusMonitorImpl<T>::status = WARNING; } else { - StatusMonitor<T>::status = OK; + StatusMonitorImpl<T>::status = OK; } - StatusMonitor<T>::status.write(); + StatusMonitorImpl<T>::status.write(); group.readAny(); } } @@ -191,26 +221,26 @@ namespace ChimeraTK { * If value monitored is not exactly the same. an error state will be * reported.*/ template<typename T> - struct ExactMonitor : public StatusMonitor<T> { - using StatusMonitor<T>::StatusMonitor; + struct ExactMonitor : public StatusMonitorImpl<T> { + using StatusMonitorImpl<T>::StatusMonitorImpl; /**ERROR state if value is not equal to requiredValue*/ - ScalarPushInput<T> requiredValue{this, "requiredValue", "", "", StatusMonitor<T>::_parameterTags}; + ScalarPushInput<T> requiredValue{this, "requiredValue", "", "", StatusMonitor::_parameterTags}; /**This is where state evaluation is done*/ void mainLoop() { /** If there is a change either in value monitored or in requiredValue, the status is re-evaluated*/ - ReadAnyGroup group{StatusMonitor<T>::oneUp.watch, StatusMonitor<T>::disable, requiredValue}; + ReadAnyGroup group{StatusMonitorImpl<T>::oneUp.watch, StatusMonitorImpl<T>::disable, requiredValue}; while(true) { - if(StatusMonitor<T>::disable != 0) { - StatusMonitor<T>::status = OFF; + if(StatusMonitorImpl<T>::disable != 0) { + StatusMonitorImpl<T>::status = OFF; } - else if(StatusMonitor<T>::oneUp.watch != requiredValue) { - StatusMonitor<T>::status = ERROR; + else if(StatusMonitorImpl<T>::oneUp.watch != requiredValue) { + StatusMonitorImpl<T>::status = ERROR; } else { - StatusMonitor<T>::status = OK; + StatusMonitorImpl<T>::status = OK; } - StatusMonitor<T>::status.write(); + StatusMonitorImpl<T>::status.write(); group.readAny(); } } @@ -221,31 +251,31 @@ namespace ChimeraTK { * will be reported, otherwise OFF(0) or OK(1) depending on state. */ template<typename T> - struct StateMonitor : public StatusMonitor<T> { - using StatusMonitor<T>::StatusMonitor; + struct StateMonitor : public StatusMonitorImpl<T> { + using StatusMonitorImpl<T>::StatusMonitorImpl; /// The state that we are supposed to have - ScalarPushInput<T> nominalState{this, "nominalState", "", "", StatusMonitor<T>::_parameterTags}; + 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{StatusMonitor<T>::oneUp.watch, StatusMonitor<T>::disable, nominalState}; + ReadAnyGroup group{StatusMonitorImpl<T>::oneUp.watch, StatusMonitorImpl<T>::disable, nominalState}; while(true) { - if(StatusMonitor<T>::disable != 0) { - StatusMonitor<T>::status = OFF; + if(StatusMonitorImpl<T>::disable != 0) { + StatusMonitorImpl<T>::status = OFF; } - else if(StatusMonitor<T>::oneUp.watch != nominalState) { - StatusMonitor<T>::status = ERROR; + else if(StatusMonitorImpl<T>::oneUp.watch != nominalState) { + StatusMonitorImpl<T>::status = ERROR; } else if(nominalState == OK || nominalState == OFF) { - StatusMonitor<T>::status = nominalState; + StatusMonitorImpl<T>::status = nominalState; } else { //no correct value - StatusMonitor<T>::status = ERROR; + StatusMonitorImpl<T>::status = ERROR; } - StatusMonitor<T>::status.write(); + StatusMonitorImpl<T>::status.write(); group.readAny(); } } diff --git a/Modules/src/StatusAggregator.cc b/Modules/src/StatusAggregator.cc new file mode 100644 index 0000000000000000000000000000000000000000..f35af076617d6f851cf7c893d29cde9df6171519 --- /dev/null +++ b/Modules/src/StatusAggregator.cc @@ -0,0 +1,59 @@ +#include "StatusAggregator.h" + +#include <list> + +namespace ChimeraTK{ + + void StatusAggregator::populateStatusInput(){ + + + // This does not work, we need to operate on the virtual hierarchy + //std::list<Module*> subModuleList{getOwner()->getSubmoduleList()}; + + // This approach crashes +// auto subModuleList = getOwner()->findTag(".*").getSubmoduleList(); + +// for(auto module : subModuleList){ + +// std::cout << "Testing Module " << module->getName() << std::endl; +// auto accList{module->getAccessorList()}; +// } + + + // Works, as long as we do not use HIerarchyModifiers other than "none" in the test app + std::cout << "Populating aggregator " << getName() << std::endl; + + auto allAccessors{getOwner()->findTag(".*").getAccessorListRecursive()}; + + std::cout << " Size of allAccessors: " << allAccessors.size() << std::endl; + + for(auto acc : allAccessors){ + + if(acc.getDirection().dir == VariableDirection::feeding){ + std::cout << " -- Accessor: " << acc.getName() + << " of module: " << acc.getOwningModule()->getName() + << std::endl; + } + + } + + + +// if(dynamic_cast<StatusMonitor*>(module)){ +// std::cout << " Found Monitor " << module->getName() << std::endl; +// } +// else if(dynamic_cast<StatusAggregator*>(module)){ +// std::string moduleName = module->getName(); + +// std::cout << "Found Aggregator " << moduleName << std::endl; + +// statusInput.emplace_back(this, moduleName, "", ""); +// } +// else if(dynamic_cast<ModuleGroup*>(module)){ +// std::cout << "Found ModuleGroup " << module->getName() << std::endl; +// } +// else{} +// } + std::cout << std::endl << std::endl; + } // poplateStatusInput()*/ +} diff --git a/tests/executables_src/testStatusAggregator.cc b/tests/executables_src/testStatusAggregator.cc new file mode 100644 index 0000000000000000000000000000000000000000..2b36d1a186930b529dafffb06eaf9d96d08ca961 --- /dev/null +++ b/tests/executables_src/testStatusAggregator.cc @@ -0,0 +1,70 @@ + +#define BOOST_TEST_MODULE testStatusAggregator +#include <boost/test/included/unit_test.hpp> + +#include "StatusAggregator.h" + +#include "Application.h" +#include "ModuleGroup.h" +#include "TestFacility.h" + + +using namespace boost::unit_test_framework; + +namespace ctk = ChimeraTK; + + +struct OuterGroup : public ctk::ModuleGroup { + using ctk::ModuleGroup::ModuleGroup; + virtual ~OuterGroup(){} + + ctk::MinMonitor<double_t> outerMinMonitor{this, "outerMinMonitor", "", "watch", "status", ctk::HierarchyModifier::none, + {"OUTER_MON_OUTPUT"}, {"OUTER_MON_PARAMS"},{"OUTER_MON_INPUT"}}; + + //ctk::StatusAggregator outerStatusAggregator{this, "outerStatusAggregator", "", ctk::HierarchyModifier::none, {"STATUS"}}; + + struct InnerGroup : public ctk::ModuleGroup { + using ctk::ModuleGroup::ModuleGroup; + + ctk::MinMonitor<double_t> innerMinMonitor{this, "innerMinMonitor", "", "minWatch", "minStatus", ctk::HierarchyModifier::none, + {"INNER_MON_OUTPUT"}, {"INNER_MON_PARAMS"},{"INNER_MON_INPUT"}}; + ctk::StateMonitor<uint8_t> innerStateMonitor{this, "innerStateMonitor", "", "stateWatch", "stateStatus", ctk::HierarchyModifier::none, + {"INNER_MON_OUTPUT"}, {"INNER_MON_PARAMS"},{"INNER_MON_INPUT"}}; + + } innerGroup{this, "innerModuleGroup", ""}; + +}; + + +struct TestApplication : public ctk::Application { + TestApplication() : Application("testApp"){} + ~TestApplication(){ shutdown(); } + + OuterGroup outerModuleGroup1{this, "outerModuleGroup1", ""}; + OuterGroup outerModuleGroup2{this, "outerModuleGroup2", ""}; + + ctk::StateMonitor<uint8_t> globalStateMonitor{this, "globalStateMonitor", "", "stateWatch", "stateStatus", ctk::HierarchyModifier::none, + {"GLOBAL_MON_OUTPUT"}, {"GLOBAL_MON_PARAMS"},{"GLOBAL_MON_INPUT"}}; + + ctk::ControlSystemModule cs; + + ctk::StatusAggregator globalStatusAggregator{this, "globalStatusAggregator", "Global StatusAggregator of testApp", + "globalStatus", ctk::HierarchyModifier::none, {"STATUS"}}; + + void defineConnections(){ + findTag(".*").connectTo(cs); + } +}; + + + +BOOST_AUTO_TEST_CASE(testStatusAggregator){ + std::cout << "testStatusAggregator" << std::endl; + + TestApplication app; + + ctk::TestFacility test; + test.runApplication(); + //app.dump(); + +}