Skip to content
Snippets Groups Projects
Commit cba96457 authored by Christoph Kampmeyer's avatar Christoph Kampmeyer
Browse files

Add StatusAggregator (#14):

    - Basic structure of test application
    - Added modules
    - Refactoring of StatusMonitor to provide a non-templated base class
    - Needs follow-up, for status see issue #14 and the linked follow-up
      ticket
parent b4fdd9f0
No related branches found
No related tags found
No related merge requests found
#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
......@@ -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();
}
}
......
#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()*/
}
#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();
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment