diff --git a/Modules/include/StatusMonitor.h b/Modules/include/StatusMonitor.h index dde8511f4bc0b7c7054660d50a72f7ecc328093b..e9548fc80add29668c88facde9684139a1b4946f 100644 --- a/Modules/include/StatusMonitor.h +++ b/Modules/include/StatusMonitor.h @@ -13,7 +13,6 @@ * - MinMonitor to monitor a value depending upon two MIN thresholds for warning and fault. * - RangeMonitor to monitor a value depending upon two ranges of thresholds for warning and fault. * - ExactMonitor to monitor a value which should be exactly same as required value. - * - StateMonitor to monitor On/Off state. * Depending upon the value and condition on of the four states are reported. * - OFF, OK, WARNING, FAULT. * @@ -34,144 +33,105 @@ For more info see \ref statusmonitordoc #include "HierarchyModifyingGroup.h" #include "ScalarAccessor.h" #include "StatusAccessor.h" -#include "VariableGroup.h" namespace ChimeraTK { - /** 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. - * - * FIXME This distinction between StatusMonitorImpl and StatusMonitor is no longer required. Merge the classes! - */ - 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) {} + /*******************************************************************************************************************/ + /* Declaration of MonitorBase **************************************************************************************/ + /*******************************************************************************************************************/ + struct MonitorBase : ApplicationModule { + // make constructors protected not to allow of instantiancion of this object - this is just a base class for other monitors + protected: + MonitorBase(EntityOwner* owner, const std::string& description, const std::string& outputPath, + const std::string& disablePath, const std::unordered_set<std::string>& outputTags = {}, + const std::unordered_set<std::string>& parameterTags = {}); + + MonitorBase() = default; + + public: + /** Disable/enable the entire status monitor */ + ModifyHierarchy<ScalarPushInput<ChimeraTK::Boolean>> disable; + /** Result of the monitor */ + ModifyHierarchy<StatusOutput> status; - StatusMonitor(StatusMonitor&&) = default; + protected: + DataValidity lastStatusValidity = DataValidity::ok; + void setStatus(StatusOutput::Status newStatus); + }; - ~StatusMonitor() override {} - void prepare() override {} + /*******************************************************************************************************************/ + /* Declaration of MaxMonitor ***************************************************************************************/ + /*******************************************************************************************************************/ - /**Tags for parameters. This makes it easier to connect them to e.g, to control system*/ - std::unordered_set<std::string> _parameterTags; + /** Module for status monitoring depending on a maximum threshold value*/ + template<typename T> + struct MaxMonitor : MonitorBase { + MaxMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& parameterPath, const std::string& description, + const std::unordered_set<std::string>& outputTags = {}, + const std::unordered_set<std::string>& parameterTags = {}); - const std::string _input; + MaxMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& warningThresholdPath, const std::string& faultThresholdPath, const std::string& disablePath, + const std::string& description, const std::unordered_set<std::string>& outputTags = {}, + const std::unordered_set<std::string>& parameterTags = {}); - /** Status to be reported */ - StatusOutput status; + MaxMonitor() = default; + + /** Variable to monitor */ + ModifyHierarchy<ScalarPushInput<T>> watch; + + /** WARNING state to be reported if threshold is reached or exceeded*/ + ModifyHierarchy<ScalarPushInput<T>> warningThreshold; + + /** FAULT state to be reported if threshold is reached or exceeded*/ + ModifyHierarchy<ScalarPushInput<T>> faultThreshold; /** 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"}; + + /** This is where state evaluation is done */ + void mainLoop(); }; - /** 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. - */ + /*******************************************************************************************************************/ + /* Declaration of MinMonitor ***************************************************************************************/ + /*******************************************************************************************************************/ + + /** Module for status monitoring depending on a minimum threshold value*/ 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, + struct MinMonitor : MonitorBase { + MinMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& parameterPath, const std::string& description, 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(StatusMonitorImpl&&) = default; - - ~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; - }; + const std::unordered_set<std::string>& parameterTags = {}); + + MinMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& warningThresholdPath, const std::string& faultThresholdPath, const std::string& disablePath, + const std::string& description, const std::unordered_set<std::string>& outputTags = {}, + const std::unordered_set<std::string>& parameterTags = {}); + + MinMonitor() = default; + + /** Variable to monitor */ + ModifyHierarchy<ScalarPushInput<T>> watch; - /** Module for status monitoring depending on a maximum threshold value*/ - template<typename T> - 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::_parameterTags}; + ModifyHierarchy<ScalarPushInput<T>> warningThreshold; + /** FAULT state to be reported if threshold is reached or exceeded*/ - ScalarPushInput<T> fault{this, "upperFaultThreshold", "", "", 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{StatusMonitorImpl<T>::oneUp.watch, StatusMonitorImpl<T>::disable, warning, fault}; - 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 = StatusOutput::Status::OFF; - } - else if(StatusMonitorImpl<T>::oneUp.watch >= fault) { - StatusMonitorImpl<T>::status = StatusOutput::Status::FAULT; - } - else if(StatusMonitorImpl<T>::oneUp.watch >= warning) { - StatusMonitorImpl<T>::status = StatusOutput::Status::WARNING; - } - else { - StatusMonitorImpl<T>::status = StatusOutput::Status::OK; - } - StatusMonitorImpl<T>::status.write(); - group.readAny(); - } - } - }; + ModifyHierarchy<ScalarPushInput<T>> faultThreshold; - /** Module for status monitoring depending on a minimum threshold value*/ - template<typename T> - struct MinMonitor : public StatusMonitorImpl<T> { - using StatusMonitorImpl<T>::StatusMonitorImpl; - - /** WARNING state to be reported if threshold is crossed*/ - ScalarPushInput<T> warning{this, "lowerWarningThreshold", "", "", StatusMonitor::_parameterTags}; - /** FAULT state to be reported if threshold is crossed*/ - ScalarPushInput<T> fault{this, "lowerFaultThreshold", "", "", 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{StatusMonitorImpl<T>::oneUp.watch, StatusMonitorImpl<T>::disable, warning, fault}; - while(true) { - if(StatusMonitorImpl<T>::disable != 0) { - StatusMonitorImpl<T>::status = StatusOutput::Status::OFF; - } - else if(StatusMonitorImpl<T>::oneUp.watch <= fault) { - StatusMonitorImpl<T>::status = StatusOutput::Status::FAULT; - } - else if(StatusMonitorImpl<T>::oneUp.watch <= warning) { - StatusMonitorImpl<T>::status = StatusOutput::Status::WARNING; - } - else { - StatusMonitorImpl<T>::status = StatusOutput::Status::OK; - } - StatusMonitorImpl<T>::status.write(); - group.readAny(); - } - } + /** This is where state evaluation is done */ + void mainLoop(); }; + /*******************************************************************************************************************/ + /* Declaration of RangeMonitor *************************************************************************************/ + /*******************************************************************************************************************/ + /** Module for status monitoring depending on range of threshold values. * As long as a monitored value is in the range defined by user it goes * to fault or warning state. If the monitored value exceeds the upper limmit @@ -180,48 +140,44 @@ namespace ChimeraTK { * set the ranges correctly to issue warning or fault. */ template<typename T> - struct RangeMonitor : public StatusMonitorImpl<T> { - using StatusMonitorImpl<T>::StatusMonitorImpl; + struct RangeMonitor : MonitorBase { + RangeMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& parameterPath, const std::string& description, + const std::unordered_set<std::string>& outputTags = {}, + const std::unordered_set<std::string>& parameterTags = {}); + + RangeMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& warningLowerThresholdPath, const std::string& warningUpperThresholdPath, + const std::string& faultLowerThresholdPath, const std::string& faultUpperThresholdPath, + const std::string& disablePath, const std::string& description, + const std::unordered_set<std::string>& outputTags = {}, + const std::unordered_set<std::string>& parameterTags = {}); + + RangeMonitor() = default; + + /** Variable to monitor */ + ModifyHierarchy<ScalarPushInput<T>> watch; /** 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::_parameterTags}; - ScalarPushInput<T> warningLowerThreshold{this, "lowerWarningThreshold", "", "", StatusMonitor::_parameterTags}; + ModifyHierarchy<ScalarPushInput<T>> warningLowerThreshold; + ModifyHierarchy<ScalarPushInput<T>> warningUpperThreshold; + /** FAULT state to be reported if value is in between the upper and * lower threshold including the start and end of thresholds. */ - ScalarPushInput<T> faultUpperThreshold{this, "upperFaultThreshold", "", "", StatusMonitor::_parameterTags}; - ScalarPushInput<T> faultLowerThreshold{this, "lowerFaultThreshold", "", "", 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{StatusMonitorImpl<T>::oneUp.watch, StatusMonitorImpl<T>::disable, warningUpperThreshold, - warningLowerThreshold, faultUpperThreshold, faultLowerThreshold}; - while(true) { - if(StatusMonitorImpl<T>::disable != 0) { - 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 = StatusOutput::Status::FAULT; - } - else if(StatusMonitorImpl<T>::oneUp.watch <= warningLowerThreshold || - StatusMonitorImpl<T>::oneUp.watch >= warningUpperThreshold) { - StatusMonitorImpl<T>::status = StatusOutput::Status::WARNING; - } - else { - StatusMonitorImpl<T>::status = StatusOutput::Status::OK; - } - StatusMonitorImpl<T>::status.write(); - group.readAny(); - } - } + ModifyHierarchy<ScalarPushInput<T>> faultLowerThreshold; + ModifyHierarchy<ScalarPushInput<T>> faultUpperThreshold; + + /** This is where state evaluation is done */ + void mainLoop(); }; + /*******************************************************************************************************************/ + /* Declaration of ExactMonitor *************************************************************************************/ + /*******************************************************************************************************************/ + /** * Module for status monitoring of an exact value. * @@ -233,7 +189,7 @@ namespace ChimeraTK { * data types should never be compared with exact equality. */ template<typename T> - struct ExactMonitor : ApplicationModule { + struct ExactMonitor : MonitorBase { /** * Constructor for exact monitoring module. * @@ -247,9 +203,7 @@ namespace ChimeraTK { ExactMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, const std::string& parameterPath, const std::string& description, const std::unordered_set<std::string>& outputTags = {}, - const std::unordered_set<std::string>& parameterTags = {}) - : ExactMonitor(owner, inputPath, outputPath, parameterPath + "/requiredValue", parameterPath + "/disable", - description, outputTags, parameterTags) {} + const std::unordered_set<std::string>& parameterTags = {}); /** * Constructor for exact monitoring module. @@ -265,12 +219,7 @@ namespace ChimeraTK { ExactMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, const std::string& requiredValuePath, const std::string& disablePath, const std::string& description, const std::unordered_set<std::string>& outputTags = {}, - const std::unordered_set<std::string>& parameterTags = {}) - : ApplicationModule(owner, "hidden", description, HierarchyModifier::hideThis), - watch(this, inputPath, "", "Value to monitor"), - requiredValue(this, requiredValuePath, "", "Value to compare with", parameterTags), - disable(this, disablePath, "", "Disable the status monitor", parameterTags), - status(this, outputPath, "Resulting status", outputTags) {} + const std::unordered_set<std::string>& parameterTags = {}); ExactMonitor() = default; @@ -280,41 +229,212 @@ namespace ChimeraTK { /** The required value to compare with */ ModifyHierarchy<ScalarPushInput<T>> requiredValue; - /** Disable/enable the entire status monitor */ - ModifyHierarchy<ScalarPushInput<int>> disable; + /** This is where state evaluation is done */ + void mainLoop(); + }; - /** Result of the monitor */ - ModifyHierarchy<StatusOutput> status; + /*******************************************************************************************************************/ + /* Implementation starts here **************************************************************************************/ + /*******************************************************************************************************************/ + + /*******************************************************************************************************************/ + /* Implementation of MonitorBase ***********************************************************************************/ + /*******************************************************************************************************************/ + MonitorBase::MonitorBase(EntityOwner* owner, const std::string& description, const std::string& outputPath, + const std::string& disablePath, const std::unordered_set<std::string>& outputTags, + const std::unordered_set<std::string>& parameterTags) + : ApplicationModule(owner, "hidden", description, HierarchyModifier::hideThis), + disable(this, disablePath, "", "Disable the status monitor", parameterTags), + status(this, outputPath, "Resulting status", outputTags) {} + + /*******************************************************************************************************************/ + void MonitorBase::setStatus(StatusOutput::Status newStatus) { + // update only if status has changed, but always in case of initial value + if(status.value != newStatus || getDataValidity() != lastStatusValidity || + status.value.getVersionNumber() == VersionNumber{nullptr}) { + status.value = newStatus; + status.value.write(); + lastStatusValidity = getDataValidity(); + } + } - /** 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{watch.value, disable.value, requiredValue.value}; - - DataValidity lastStatusValidity = DataValidity::ok; - - while(true) { - StatusOutput::Status newStatus; - if(disable.value != 0) { - newStatus = StatusOutput::Status::OFF; - } - else if(watch.value != requiredValue.value) { - newStatus = StatusOutput::Status::FAULT; - } - else { - newStatus = StatusOutput::Status::OK; - } - - // update only if status has changed, but always in case of initial value - if(status.value != newStatus || getDataValidity() != lastStatusValidity || - status.value.getVersionNumber() == VersionNumber{nullptr}) { - status.value = newStatus; - status.value.write(); - lastStatusValidity = getDataValidity(); - } - group.readAny(); + /*******************************************************************************************************************/ + /* Implementation of MaxMonitor ************************************************************************************/ + /*******************************************************************************************************************/ + template<typename T> + MaxMonitor<T>::MaxMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& parameterPath, const std::string& description, + const std::unordered_set<std::string>& outputTags, const std::unordered_set<std::string>& parameterTags) + : MaxMonitor(owner, inputPath, outputPath, parameterPath + "/upperWarningThreshold", + parameterPath + "/upperFaultThreshold", parameterPath + "/disable", description, outputTags, parameterTags) {} + + /*******************************************************************************************************************/ + template<typename T> + MaxMonitor<T>::MaxMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& warningThresholdPath, const std::string& faultThresholdPath, const std::string& disablePath, + const std::string& description, const std::unordered_set<std::string>& outputTags, + const std::unordered_set<std::string>& parameterTags) + : MonitorBase(owner, description, outputPath, disablePath, outputTags, parameterTags), + watch(this, inputPath, "", "Value to monitor"), + warningThreshold(this, warningThresholdPath, "", "Warning threshold to compare with", parameterTags), + faultThreshold(this, faultThresholdPath, "", "Fault threshold to compare with", parameterTags) {} + + /*******************************************************************************************************************/ + template<typename T> + void MaxMonitor<T>::mainLoop() { + // If there is a change either in value monitored or in requiredValue, the status is re-evaluated + ReadAnyGroup group{watch.value, disable.value, warningThreshold.value, faultThreshold.value}; + + while(true) { + if(disable.value) { + setStatus(StatusOutput::Status::OFF); + } + else if(watch.value >= faultThreshold.value) { + setStatus(StatusOutput::Status::FAULT); + } + else if(watch.value >= warningThreshold.value) { + setStatus(StatusOutput::Status::WARNING); } + else { + setStatus(StatusOutput::Status::OK); + } + group.readAny(); } - }; + } + + /*******************************************************************************************************************/ + /* Implementation of MinMonitor ************************************************************************************/ + /*******************************************************************************************************************/ + template<typename T> + MinMonitor<T>::MinMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& parameterPath, const std::string& description, + const std::unordered_set<std::string>& outputTags, const std::unordered_set<std::string>& parameterTags) + : MinMonitor(owner, inputPath, outputPath, parameterPath + "/lowerWarningThreshold", + parameterPath + "/lowerFaultThreshold", parameterPath + "/disable", description, outputTags, parameterTags) {} + + /*******************************************************************************************************************/ + template<typename T> + MinMonitor<T>::MinMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& warningThresholdPath, const std::string& faultThresholdPath, const std::string& disablePath, + const std::string& description, const std::unordered_set<std::string>& outputTags, + const std::unordered_set<std::string>& parameterTags) + : MonitorBase(owner, description, outputPath, disablePath, outputTags, parameterTags), + watch(this, inputPath, "", "Value to monitor"), + warningThreshold(this, warningThresholdPath, "", "Warning threshold to compare with", parameterTags), + faultThreshold(this, faultThresholdPath, "", "Fault threshold to compare with", parameterTags) {} + + /*******************************************************************************************************************/ + template<typename T> + void MinMonitor<T>::mainLoop() { + // If there is a change either in value monitored or in requiredValue, the status is re-evaluated + ReadAnyGroup group{watch.value, disable.value, warningThreshold.value, faultThreshold.value}; + + while(true) { + if(disable.value) { + setStatus(StatusOutput::Status::OFF); + } + else if(watch.value <= faultThreshold.value) { + setStatus(StatusOutput::Status::FAULT); + } + else if(watch.value <= warningThreshold.value) { + setStatus(StatusOutput::Status::WARNING); + } + else { + setStatus(StatusOutput::Status::OK); + } + group.readAny(); + } + } + + /*******************************************************************************************************************/ + /* Implementation of RangeMonitor **********************************************************************************/ + /*******************************************************************************************************************/ + template<typename T> + RangeMonitor<T>::RangeMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& parameterPath, const std::string& description, + const std::unordered_set<std::string>& outputTags, const std::unordered_set<std::string>& parameterTags) + : RangeMonitor(owner, inputPath, outputPath, parameterPath + "/lowerWarningThreshold", + parameterPath + "/upperWarningThreshold", parameterPath + "/lowerFaultThreshold", + parameterPath + "/upperFaultThreshold", parameterPath + "/disable", description, outputTags, parameterTags) {} + + /*******************************************************************************************************************/ + template<typename T> + RangeMonitor<T>::RangeMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& warningLowerThresholdPath, const std::string& warningUpperThresholdPath, + const std::string& faultLowerThresholdPath, const std::string& faultUpperThresholdPath, + const std::string& disablePath, const std::string& description, const std::unordered_set<std::string>& outputTags, + const std::unordered_set<std::string>& parameterTags) + : MonitorBase(owner, description, outputPath, disablePath, outputTags, parameterTags), + watch(this, inputPath, "", "Value to monitor"), warningLowerThreshold(this, warningLowerThresholdPath, "", + "Lower warning threshold to compare with", parameterTags), + warningUpperThreshold( + this, warningUpperThresholdPath, "", "Upper warning threshold to compare with", parameterTags), + faultLowerThreshold(this, faultLowerThresholdPath, "", "Lower fault threshold to compare with", parameterTags), + faultUpperThreshold(this, faultUpperThresholdPath, "", "Upper fault threshold to compare with", parameterTags) {} + + /*******************************************************************************************************************/ + template<typename T> + void RangeMonitor<T>::mainLoop() { + // If there is a change either in value monitored or in requiredValue, the status is re-evaluated + ReadAnyGroup group{watch.value, disable.value, warningLowerThreshold.value, warningUpperThreshold.value, + faultLowerThreshold.value, faultUpperThreshold.value}; + + while(true) { + if(disable.value) { + setStatus(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(watch.value <= faultLowerThreshold.value || watch.value >= faultUpperThreshold.value) { + setStatus(StatusOutput::Status::FAULT); + } + else if(watch.value <= warningLowerThreshold.value || watch.value >= warningUpperThreshold.value) { + setStatus(StatusOutput::Status::WARNING); + } + else { + setStatus(StatusOutput::Status::OK); + } + group.readAny(); + } + } + + /*******************************************************************************************************************/ + /* Implementation of ExactMonitor **********************************************************************************/ + /*******************************************************************************************************************/ + template<typename T> + ExactMonitor<T>::ExactMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& parameterPath, const std::string& description, + const std::unordered_set<std::string>& outputTags, const std::unordered_set<std::string>& parameterTags) + : ExactMonitor(owner, inputPath, outputPath, parameterPath + "/requiredValue", parameterPath + "/disable", + description, outputTags, parameterTags) {} + + /*******************************************************************************************************************/ + template<typename T> + ExactMonitor<T>::ExactMonitor(EntityOwner* owner, const std::string& inputPath, const std::string& outputPath, + const std::string& requiredValuePath, const std::string& disablePath, const std::string& description, + const std::unordered_set<std::string>& outputTags, const std::unordered_set<std::string>& parameterTags) + : MonitorBase(owner, description, outputPath, disablePath, outputTags, parameterTags), + watch(this, inputPath, "", "Value to monitor"), + requiredValue(this, requiredValuePath, "", "Value to compare with", parameterTags) {} + + /*******************************************************************************************************************/ + template<typename T> + void ExactMonitor<T>::mainLoop() { + // If there is a change either in value monitored or in requiredValue, the status is re-evaluated + ReadAnyGroup group{watch.value, disable.value, requiredValue.value}; + + while(true) { + if(disable.value) { + setStatus(StatusOutput::Status::OFF); + } + else if(watch.value != requiredValue.value) { + setStatus(StatusOutput::Status::FAULT); + } + else { + setStatus(StatusOutput::Status::OK); + } + group.readAny(); + } + } } // namespace ChimeraTK diff --git a/example_status_monitor/demoStatusMonitor.cc b/example_status_monitor/demoStatusMonitor.cc index 5473a7f4e0a74326b410bec9257bbd3e9de1051e..ab86a7f70d3b9d0c2146970541e5dbb80ddd0286 100644 --- a/example_status_monitor/demoStatusMonitor.cc +++ b/example_status_monitor/demoStatusMonitor.cc @@ -63,8 +63,13 @@ struct ExampleApp : public ctk::Application { // the name of the output variable. The monitor automatically connects to the input variable that is in the same // hierarchy level. We add output and parameter tags (STATUS and CONFIG, respectively) for easier connetion of the // variables. - ctk::RangeMonitor<double> temperatureMonitor{this, "TemperatureMonitor", "monitor for the simulated temperature", - "temperature", "temperatureStatus", ctk::HierarchyModifier::none, {"STATUS"}, {"CONFIG"}, {}}; + // ctk::RangeMonitor<double> temperatureMonitor{this, "TemperatureMonitor", "monitor for the simulated temperature", + // "temperature", "temperatureStatus", ctk::HierarchyModifier::none, {"STATUS"}, {"CONFIG"}, {}}; + + ctk::RangeMonitor<double> temperatureMonitor{this, "/TemperatureMonitor/temperature", + "/TemperatureMonitor/temperatureStatus", "/TemperatureMonitor", "monitor for the simulated temperature", + ctk::TAGS{"MON_OUTPUT"}, ctk::TAGS{"MON_PARAMS"}}; + } simulationGroup{this, "Simulation", ""}; ctk::ConfigReader config{this, "Config", "demoStatusMonitor_config.xml"}; diff --git a/include/HierarchyModifyingGroup.h b/include/HierarchyModifyingGroup.h index 16e6a1936061be58dd4c8f0f06efbcb2f6e8e7a0..1c8f70e3aa249166d0d49dabfb94bf4ade2aa3da 100644 --- a/include/HierarchyModifyingGroup.h +++ b/include/HierarchyModifyingGroup.h @@ -68,6 +68,7 @@ namespace ChimeraTK { : HierarchyModifyingGroup(owner, HierarchyModifyingGroup::getPathName(qualifiedName), ""), value(this, HierarchyModifyingGroup::getUnqualifiedName(qualifiedName), args...) {} + ModifyHierarchy() = default; ACCESSOR value; }; diff --git a/tests/executables_src/testPropagateDataFaultFlag.cc b/tests/executables_src/testPropagateDataFaultFlag.cc index f202b930477f4169c5ddcd0be882b40c2e035ca8..5dcda699e3b0d6dc48eecd849d5829a17977432a 100644 --- a/tests/executables_src/testPropagateDataFaultFlag.cc +++ b/tests/executables_src/testPropagateDataFaultFlag.cc @@ -9,7 +9,6 @@ #include "ArrayAccessor.h" #include "check_timeout.h" #include "ControlSystemModule.h" -#include "ModuleGroup.h" #include "ScalarAccessor.h" #include "TestFacility.h" diff --git a/tests/executables_src/testStatusAggregator.cc b/tests/executables_src/testStatusAggregator.cc index 9ca62120b9388569216d2cd26ca89a1bd5c66e58..bf5370d818b98a99347fec164ed93feebe6637e7 100644 --- a/tests/executables_src/testStatusAggregator.cc +++ b/tests/executables_src/testStatusAggregator.cc @@ -4,7 +4,6 @@ #include "Application.h" #include "ModuleGroup.h" #include "StatusAggregator.h" -#include "StatusMonitor.h" #include "TestFacility.h" #define BOOST_NO_EXCEPTIONS diff --git a/tests/executables_src/testStatusModule.cc b/tests/executables_src/testStatusModule.cc deleted file mode 100644 index a38ddd507001f54e8bcf099e8f238497cf7aa543..0000000000000000000000000000000000000000 --- a/tests/executables_src/testStatusModule.cc +++ /dev/null @@ -1,669 +0,0 @@ -// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de> -// SPDX-License-Identifier: LGPL-3.0-or-later -#include <future> - -#define BOOST_TEST_MODULE testExceptionHandling - -#include "Application.h" -#include "ApplicationModule.h" -#include "ControlSystemModule.h" -#include "ScalarAccessor.h" -#include "StatusMonitor.h" -#include "TestFacility.h" - -#include <boost/mpl/list.hpp> -#include <boost/test/included/unit_test.hpp> - -using namespace boost::unit_test_framework; -namespace ctk = ChimeraTK; - -/* dummy application */ -template<typename T> -struct TestApplication : public ctk::Application { - TestApplication() : Application("testSuite") {} - ~TestApplication() override { shutdown(); } - - void defineConnections() override { - findTag(".*").connectTo(cs); // publish everything to CS - findTag("MY_MONITOR").connectTo(cs["MyNiceMonitorCopy"]); // cable again, checking that the tag is applied correctly - findTag("MON_PARAMS") - .connectTo(cs["MonitorParameters"]); // cable the parameters in addition (checking that tags are set correctly) - findTag("MON_OUTPUT") - .connectTo(cs["MonitorOutput"]); // cable the parameters in addition (checking that tags are set correctly) - } - ctk::ControlSystemModule cs; - T monitor{this, "Monitor", "Now this is a nice monitor...", "watch", "status", ChimeraTK::HierarchyModifier::none, - {"MON_OUTPUT"}, {"MON_PARAMS"}, {"MY_MONITOR"}}; -}; - -/* dummy application - for new StatusMonitor interface */ -template<typename T> -struct TestApplicationNewInterface : public ctk::Application { - TestApplicationNewInterface() : Application("testSuite") {} - ~TestApplicationNewInterface() override { shutdown(); } - - void defineConnections() override { - findTag(".*").connectTo(cs); // publish everything to CS - findTag("MON_PARAMS") - .connectTo(cs["MonitorParameters"]); // cable the parameters in addition (checking that tags are set correctly) - findTag("MON_OUTPUT") - .connectTo(cs["MonitorOutput"]); // cable the parameters in addition (checking that tags are set correctly) - } - ctk::ControlSystemModule cs; - T monitor{this, "/input/path", "/output/path", "/parameters", "Now this is a nice monitor...", - ctk::TAGS{"MON_OUTPUT"}, ctk::TAGS{"MON_PARAMS"}}; -}; - -/*********************************************************************************************************************/ - -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(); - - auto warning = test.getScalar<double_t>(std::string("/Monitor/upperWarningThreshold")); - warning = 50.0; - warning.write(); - test.stepApplication(); - - auto fault = test.getScalar<double_t>(std::string("/Monitor/upperFaultThreshold")); - fault = 60.0; - fault.write(); - test.stepApplication(); - - auto watch = test.getScalar<double_t>(std::string("/watch")); - watch = 40.0; - watch.write(); - test.stepApplication(); - - auto status = test.getScalar<int32_t>(std::string("/Monitor/status")); - status.readLatest(); - - // should be in OK state. - 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, static_cast<int>(ChimeraTK::StatusOutput::Status::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, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); - - disable = 0; - disable.write(); - test.stepApplication(); - status.readLatest(); - 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, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); - - // drop in a disable test. - disable = 1; - disable.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); - - disable = 0; - disable.write(); - test.stepApplication(); - status.readLatest(); - 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, 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, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); - - // drop in a disable test. - disable = 1; - disable.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); - - disable = 0; - disable.write(); - test.stepApplication(); - status.readLatest(); - 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, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); - - // now check that changing the status is updated correctly if we change the limits - - // increase fault value greater than watch - fault = 68; - fault.write(); - test.stepApplication(); - status.readLatest(); - // should be in WARNING state. - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); - - // increase warning value greater than watch - warning = 66; - warning.write(); - test.stepApplication(); - status.readLatest(); - // should be in OK state. - 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. - fault = 60; - fault.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); - - // check that the tags are applied correctly - 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")); -} - -/*********************************************************************************************************************/ -BOOST_AUTO_TEST_CASE(testMinMonitor) { - std::cout << "testMinMonitor" << std::endl; - - 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(); - - auto warning = test.getScalar<uint>(std::string("/Monitor/lowerWarningThreshold")); - warning = 40; - warning.write(); - test.stepApplication(); - - auto fault = test.getScalar<uint>(std::string("/Monitor/lowerFaultThreshold")); - fault = 30; - fault.write(); - test.stepApplication(); - - auto watch = test.getScalar<uint>(std::string("/watch")); - watch = 45; - watch.write(); - test.stepApplication(); - - auto status = test.getScalar<int32_t>(std::string("/Monitor/status")); - status.readLatest(); - - // should be in OK state. - 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, static_cast<int>(ChimeraTK::StatusOutput::Status::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, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); - - disable = 0; - disable.write(); - test.stepApplication(); - status.readLatest(); - 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, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); - - // drop in a disable test. - disable = 1; - disable.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); - - disable = 0; - disable.write(); - test.stepApplication(); - status.readLatest(); - 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, 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, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); - - // drop in a disable test. - disable = 1; - disable.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); - - disable = 0; - disable.write(); - test.stepApplication(); - status.readLatest(); - 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, 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, static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); - - // change upper warning limit - warning = 42; - warning.write(); - test.stepApplication(); - status.readLatest(); - 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, 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. - fault = 44; - fault.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); - - // check that the tags are applied correctly - 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")); -} - -/*********************************************************************************************************************/ - -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(); - - auto warningUpperLimit = test.getScalar<int>(std::string("/Monitor/upperWarningThreshold")); - warningUpperLimit = 50; - warningUpperLimit.write(); - test.stepApplication(); - - auto warningLowerLimit = test.getScalar<int>(std::string("/Monitor/lowerWarningThreshold")); - warningLowerLimit = 40; - warningLowerLimit.write(); - test.stepApplication(); - - auto faultUpperLimit = test.getScalar<int>(std::string("/Monitor/upperFaultThreshold")); - faultUpperLimit = 60; - faultUpperLimit.write(); - test.stepApplication(); - - auto faultLowerLimit = test.getScalar<int>(std::string("/Monitor/lowerFaultThreshold")); - faultLowerLimit = 30; - faultLowerLimit.write(); - test.stepApplication(); - - // start with a good value - auto watch = test.getScalar<int>(std::string("/watch")); - watch = 45; - watch.write(); - test.stepApplication(); - - auto status = test.getScalar<int32_t>(std::string("/Monitor/status")); - status.readLatest(); - - 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, static_cast<int>(ChimeraTK::StatusOutput::Status::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, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); - - disable = 0; - disable.write(); - test.stepApplication(); - status.readLatest(); - 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, static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); - - // drop in a disable test. - disable = 1; - disable.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); - - disable = 0; - disable.write(); - test.stepApplication(); - status.readLatest(); - 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, 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, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); - - // drop in a disable test. - disable = 1; - disable.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); - - disable = 0; - disable.write(); - test.stepApplication(); - status.readLatest(); - 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, 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, 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, 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, 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, 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, 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, static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); - - // change upper warning limit - warningUpperLimit = 48; - warningUpperLimit.write(); - test.stepApplication(); - status.readLatest(); - 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, 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. - faultUpperLimit = 46; - faultUpperLimit.write(); - test.stepApplication(); - status.readLatest(); - 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, static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); - - // change upper warning limit - warningLowerLimit = 42; - warningLowerLimit.write(); - test.stepApplication(); - status.readLatest(); - 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, 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. - faultLowerLimit = 44; - faultLowerLimit.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); - - // check that the tags are applied correctly - 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")); - BOOST_CHECK_EQUAL(faultUpperLimit, test.readScalar<int>("/MonitorParameters/Monitor/upperFaultThreshold")); - BOOST_CHECK_EQUAL(warningUpperLimit, test.readScalar<int>("/MonitorParameters/Monitor/upperWarningThreshold")); -} - -/*********************************************************************************************************************/ - -BOOST_AUTO_TEST_CASE(testExactMonitor) { - std::cout << "testExactMonitor" << std::endl; - TestApplicationNewInterface<ctk::ExactMonitor<int64_t>> app; - - // check that the reserved StatusOutput tag is present at the output, required for StatusAggregator integration - auto tags = ctk::VariableNetworkNode(app.monitor.status.value).getTags(); - BOOST_CHECK(tags.find(ctk::StatusOutput::tagStatusOutput) != tags.end()); - - ctk::TestFacility test; - test.runApplication(); - // app.dumpConnections(); - - auto requiredValue = test.getScalar<int64_t>(std::string("/parameters/requiredValue")); - requiredValue = 409; - requiredValue.write(); - test.stepApplication(); - - auto watch = test.getScalar<int64_t>(std::string("/input/path")); - watch = 409; - watch.write(); - test.stepApplication(); - - auto status = test.getScalar<int32_t>(std::string("/output/path")); - status.readLatest(); - - // should be in OK state. - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); - - // drop in a disable test. - auto disable = test.getScalar<int>("/parameters/disable"); - disable = 1; - disable.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); - - disable = 0; - disable.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); - - // set watch value different than required value - watch = 414; - watch.write(); - test.stepApplication(); - status.readLatest(); - // should be in FAULT state. - 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, static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); - - disable = 0; - disable.write(); - test.stepApplication(); - status.readLatest(); - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); - - watch = 409; - watch.write(); - test.stepApplication(); - status.readLatest(); - // should be in OK state. - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); - - // set requiredValue value different than watch value - requiredValue = 413; - requiredValue.write(); - test.stepApplication(); - status.readLatest(); - // should be in WARNING state. - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); - - // set requiredValue value equals to watch value - requiredValue = 409; - requiredValue.write(); - test.stepApplication(); - status.readLatest(); - // should be in WARNING state. - BOOST_CHECK_EQUAL(status, static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); - - // check that the tags are applied correctly - BOOST_CHECK_EQUAL(status, test.readScalar<int32_t>("/MonitorOutput/output/path")); - BOOST_CHECK_EQUAL(requiredValue, test.readScalar<int64_t>("/MonitorParameters/parameters/requiredValue")); - disable = 1; - disable.write(); - test.stepApplication(); - BOOST_CHECK_EQUAL(disable, test.readScalar<int>("/MonitorParameters/parameters/disable")); -} - -/*********************************************************************************************************************/ diff --git a/tests/executables_src/testStatusMonitor.cc b/tests/executables_src/testStatusMonitor.cc new file mode 100644 index 0000000000000000000000000000000000000000..5d13ea1c1b16baa178c366e6d500e24f1d08d88d --- /dev/null +++ b/tests/executables_src/testStatusMonitor.cc @@ -0,0 +1,952 @@ +// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de> +// SPDX-License-Identifier: LGPL-3.0-or-later +#include <future> + +#define BOOST_TEST_MODULE testStatusMonitor + +#include "Application.h" +#include "ControlSystemModule.h" +#include "ScalarAccessor.h" +#include "StatusMonitor.h" +#include "TestFacility.h" + +#include <boost/mpl/list.hpp> +#include <boost/test/included/unit_test.hpp> + +using namespace boost::unit_test_framework; +namespace ctk = ChimeraTK; + +/*********************************************************************************************************************/ +/* Test dummy application for the Monitors ************************************************************************/ +/*********************************************************************************************************************/ + +template<typename T> +struct TestApplication : public ctk::Application { + TestApplication() : Application("testSuite") {} + ~TestApplication() override { shutdown(); } + + ctk::ControlSystemModule cs; + T monitor{this, "/input/path", "/output/path", "/parameters", "Now this is a nice monitor...", + ctk::TAGS{"MON_OUTPUT"}, ctk::TAGS{"MON_PARAMS"}}; +}; + +/*********************************************************************************************************************/ +/* Test generic functionality of the Monitors ************************************************************************/ +/*********************************************************************************************************************/ + +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.value).getTags(); + BOOST_CHECK(tags.find(ctk::StatusOutput::tagStatusOutput) != tags.end()); + + ctk::TestFacility test; + test.runApplication(); + // app.cs.dump(); + + auto warning = test.getScalar<double_t>(std::string("/parameters/upperWarningThreshold")); + warning = 50.0; + warning.write(); + test.stepApplication(); + + auto fault = test.getScalar<double_t>(std::string("/parameters/upperFaultThreshold")); + fault = 60.0; + fault.write(); + test.stepApplication(); + + auto watch = test.getScalar<double_t>(std::string("/input/path")); + watch = 40.0; + watch.write(); + test.stepApplication(); + + auto status = test.getScalar<int32_t>(std::string("/output/path")); + status.readLatest(); + + // should be in OK state. + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // //just below the warning level + watch = 49.99; + watch.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // drop in a disable test. + auto disable = test.getScalar<ChimeraTK::Boolean>("/parameters/disable"); + disable = 1; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + + disable = 0; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(status == static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); + + // drop in a disable test. + disable = 1; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + + disable = 0; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(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(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + // drop in a disable test. + disable = 1; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + + disable = 0; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + // now check that changing the status is updated correctly if we change the limits + + // increase fault value greater than watch + fault = 68; + fault.write(); + test.stepApplication(); + status.readLatest(); + // should be in WARNING state. + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); + + // increase warning value greater than watch + warning = 66; + warning.write(); + test.stepApplication(); + status.readLatest(); + // should be in OK state. + BOOST_CHECK(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. + fault = 60; + fault.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + // check that the tags are applied correctly + BOOST_CHECK(app.findTag("MON_PARAMS")["parameters"]("upperFaultThreshold") == app.monitor.faultThreshold.value); + BOOST_CHECK(app.findTag("MON_PARAMS")["parameters"]("upperWarningThreshold") == app.monitor.warningThreshold.value); + BOOST_CHECK(app.findTag("MON_PARAMS")["parameters"]("disable") == app.monitor.disable.value); + BOOST_CHECK(app.findTag("MON_OUTPUT")["output"]("path") == app.monitor.status.value); +} + +/*********************************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(testMinMonitor) { + std::cout << "testMinMonitor" << std::endl; + + 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.value).getTags(); + BOOST_CHECK(tags.find(ctk::StatusOutput::tagStatusOutput) != tags.end()); + + ctk::TestFacility test; + test.runApplication(); + // app.dumpConnections(); + + auto warning = test.getScalar<uint>(std::string("/parameters/lowerWarningThreshold")); + warning = 40; + warning.write(); + test.stepApplication(); + + auto fault = test.getScalar<uint>(std::string("/parameters/lowerFaultThreshold")); + fault = 30; + fault.write(); + test.stepApplication(); + + auto watch = test.getScalar<uint>(std::string("/input/path")); + watch = 45; + watch.write(); + test.stepApplication(); + + auto status = test.getScalar<int32_t>(std::string("/output/path")); + status.readLatest(); + + // should be in OK state. + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // just abow the lower warning limit + watch = 41; + watch.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // drop in a disable test. + auto disable = test.getScalar<ChimeraTK::Boolean>("/parameters/disable"); + disable = 1; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + + disable = 0; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // exactly at the lower warning limit + watch = 40; + watch.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); + + // drop in a disable test. + disable = 1; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + + disable = 0; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); + + // just above the lower fault limit + watch = 31; + watch.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + // drop in a disable test. + disable = 1; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + + disable = 0; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + // way bellow the lower fault limit + watch = 12; + watch.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // change upper warning limit + warning = 42; + warning.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(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. + fault = 44; + fault.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + // check that the tags are applied correctly + BOOST_CHECK(app.findTag("MON_PARAMS")["parameters"]("lowerFaultThreshold") == app.monitor.faultThreshold.value); + BOOST_CHECK(app.findTag("MON_PARAMS")["parameters"]("lowerWarningThreshold") == app.monitor.warningThreshold.value); + BOOST_CHECK(app.findTag("MON_PARAMS")["parameters"]("disable") == app.monitor.disable.value); + BOOST_CHECK(app.findTag("MON_OUTPUT")["output"]("path") == app.monitor.status.value); +} + +/*********************************************************************************************************************/ + +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.value).getTags(); + BOOST_CHECK(tags.find(ctk::StatusOutput::tagStatusOutput) != tags.end()); + + ctk::TestFacility test; + test.runApplication(); + // app.dumpConnections(); + + auto warningUpperLimit = test.getScalar<int>(std::string("/parameters/upperWarningThreshold")); + warningUpperLimit = 50; + warningUpperLimit.write(); + test.stepApplication(); + + auto warningLowerLimit = test.getScalar<int>(std::string("/parameters/lowerWarningThreshold")); + warningLowerLimit = 40; + warningLowerLimit.write(); + test.stepApplication(); + + auto faultUpperLimit = test.getScalar<int>(std::string("/parameters/upperFaultThreshold")); + faultUpperLimit = 60; + faultUpperLimit.write(); + test.stepApplication(); + + auto faultLowerLimit = test.getScalar<int>(std::string("/parameters/lowerFaultThreshold")); + faultLowerLimit = 30; + faultLowerLimit.write(); + test.stepApplication(); + + // start with a good value + auto watch = test.getScalar<int>(std::string("/input/path")); + watch = 45; + watch.write(); + test.stepApplication(); + + auto status = test.getScalar<int32_t>(std::string("/output/path")); + status.readLatest(); + + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // just below the warning level + watch = 49; + watch.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // drop in a disable test. + auto disable = test.getScalar<ChimeraTK::Boolean>("/parameters/disable"); + disable = 1; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + + disable = 0; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(status == static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); + + // drop in a disable test. + disable = 1; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + + disable = 0; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(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(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + // drop in a disable test. + disable = 1; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + + disable = 0; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(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(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // exactly at the lower warning limit + watch = 40; + watch.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); + + // just above the lower fault limit + watch = 31; + watch.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + // way bellow the lower fault limit + watch = 12; + watch.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // change upper warning limit + warningUpperLimit = 48; + warningUpperLimit.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(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. + faultUpperLimit = 46; + faultUpperLimit.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // change upper warning limit + warningLowerLimit = 42; + warningLowerLimit.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(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(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. + faultLowerLimit = 44; + faultLowerLimit.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + // check that the tags are applied correctly + BOOST_CHECK(app.findTag("MON_PARAMS")["parameters"]("lowerFaultThreshold") == app.monitor.faultLowerThreshold.value); + BOOST_CHECK( + app.findTag("MON_PARAMS")["parameters"]("lowerWarningThreshold") == app.monitor.warningLowerThreshold.value); + BOOST_CHECK(app.findTag("MON_PARAMS")["parameters"]("upperFaultThreshold") == app.monitor.faultUpperThreshold.value); + BOOST_CHECK( + app.findTag("MON_PARAMS")["parameters"]("upperWarningThreshold") == app.monitor.warningUpperThreshold.value); + BOOST_CHECK(app.findTag("MON_PARAMS")["parameters"]("disable") == app.monitor.disable.value); + BOOST_CHECK(app.findTag("MON_OUTPUT")["output"]("path") == app.monitor.status.value); +} + +/*********************************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(testExactMonitor) { + std::cout << "testExactMonitor" << std::endl; + TestApplication<ctk::ExactMonitor<int64_t>> app; + + // check that the reserved StatusOutput tag is present at the output, required for StatusAggregator integration + auto tags = ctk::VariableNetworkNode(app.monitor.status.value).getTags(); + BOOST_CHECK(tags.find(ctk::StatusOutput::tagStatusOutput) != tags.end()); + + ctk::TestFacility test; + test.runApplication(); + // app.dumpConnections(); + + auto requiredValue = test.getScalar<int64_t>(std::string("/parameters/requiredValue")); + requiredValue = 409; + requiredValue.write(); + test.stepApplication(); + + auto watch = test.getScalar<int64_t>(std::string("/input/path")); + watch = 409; + watch.write(); + test.stepApplication(); + + auto status = test.getScalar<int32_t>(std::string("/output/path")); + status.readLatest(); + + // should be in OK state. + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // drop in a disable test. + auto disable = test.getScalar<ChimeraTK::Boolean>("/parameters/disable"); + disable = 1; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + + disable = 0; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // set watch value different than required value + watch = 414; + watch.write(); + test.stepApplication(); + status.readLatest(); + // should be in FAULT state. + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + // drop in a disable test. + disable = 1; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + + disable = 0; + disable.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + watch = 409; + watch.write(); + test.stepApplication(); + status.readLatest(); + // should be in OK state. + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // set requiredValue value different than watch value + requiredValue = 413; + requiredValue.write(); + test.stepApplication(); + status.readLatest(); + // should be in WARNING state. + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + + // set requiredValue value equals to watch value + requiredValue = 409; + requiredValue.write(); + test.stepApplication(); + status.readLatest(); + // should be in WARNING state. + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + + // check that the tags are applied correctly + BOOST_CHECK(app.findTag("MON_PARAMS")["parameters"]("requiredValue") == app.monitor.requiredValue.value); + BOOST_CHECK(app.findTag("MON_PARAMS")["parameters"]("disable") == app.monitor.disable.value); + BOOST_CHECK(app.findTag("MON_OUTPUT")["output"]("path") == app.monitor.status.value); +} + +/*********************************************************************************************************************/ +/* Test initial value propagation for the Monitors *******************************************************************/ +/*********************************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(testMaxMonitorInitialValuePropagation) { + std::cout << "testMaxMonitorInitialValuePropagation" << std::endl; + + { + TestApplication<ctk::MaxMonitor<float_t>> app; + ctk::TestFacility test; + test.setScalarDefault<float_t>("/parameters/upperFaultThreshold", 60.0); + test.setScalarDefault<float_t>("/parameters/upperWarningThreshold", 50.0); + test.setScalarDefault<float_t>("/input/path", 45.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + } + { + TestApplication<ctk::MaxMonitor<float_t>> app; + ctk::TestFacility test; + test.setScalarDefault<float_t>("/parameters/upperFaultThreshold", 60.0); + test.setScalarDefault<float_t>("/parameters/upperWarningThreshold", 50.0); + test.setScalarDefault<float_t>("/input/path", 55.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); + } + { + TestApplication<ctk::MaxMonitor<float_t>> app; + ctk::TestFacility test; + test.setScalarDefault<float_t>("/parameters/upperFaultThreshold", 60.0); + test.setScalarDefault<float_t>("/parameters/upperWarningThreshold", 50.0); + test.setScalarDefault<float_t>("/input/path", 55.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", true); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + } + { + TestApplication<ctk::MaxMonitor<double_t>> app; + ctk::TestFacility test; + test.setScalarDefault<double_t>("/parameters/upperFaultThreshold", 60.0); + test.setScalarDefault<double_t>("/parameters/upperWarningThreshold", 50.0); + test.setScalarDefault<double_t>("/input/path", 65.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + } +} + +/*********************************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(testMinMonitorInitialValuePropagation) { + std::cout << "testMinMonitorInitialValuePropagation" << std::endl; + + { + TestApplication<ctk::MinMonitor<double_t>> app; + ctk::TestFacility test; + test.setScalarDefault<double_t>("/parameters/lowerFaultThreshold", 50.0); + test.setScalarDefault<double_t>("/parameters/lowerWarningThreshold", 60.0); + test.setScalarDefault<double_t>("/input/path", 65.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + } + { + TestApplication<ctk::MinMonitor<float_t>> app; + ctk::TestFacility test; + test.setScalarDefault<float_t>("/parameters/lowerFaultThreshold", 50.0); + test.setScalarDefault<float_t>("/parameters/lowerWarningThreshold", 60.0); + test.setScalarDefault<float_t>("/input/path", 55.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); + } + { + TestApplication<ctk::MinMonitor<float_t>> app; + ctk::TestFacility test; + test.setScalarDefault<float_t>("/parameters/lowerFaultThreshold", 50.0); + test.setScalarDefault<float_t>("/parameters/lowerWarningThreshold", 60.0); + test.setScalarDefault<float_t>("/input/path", 55.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", true); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + } + { + TestApplication<ctk::MinMonitor<int32_t>> app; + ctk::TestFacility test; + test.setScalarDefault<int32_t>("/parameters/lowerFaultThreshold", 50); + test.setScalarDefault<int32_t>("/parameters/lowerWarningThreshold", 60); + test.setScalarDefault<int32_t>("/input/path", 45); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + } +} + +/*********************************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(testRangeMonitorInitialValuePropagation) { + std::cout << "testRangeMonitorInitialValuePropagation" << std::endl; + // each {} defines new application and test facility + { + TestApplication<ctk::RangeMonitor<double_t>> app; + ctk::TestFacility test; + test.setScalarDefault<double_t>("/parameters/upperFaultThreshold", 80.0); + test.setScalarDefault<double_t>("/parameters/upperWarningThreshold", 70.0); + test.setScalarDefault<double_t>("/parameters/lowerWarningThreshold", 60.0); + test.setScalarDefault<double_t>("/parameters/lowerFaultThreshold", 50.0); + test.setScalarDefault<double_t>("/input/path", 65.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + } + { + TestApplication<ctk::RangeMonitor<float_t>> app; + ctk::TestFacility test; + test.setScalarDefault<float_t>("/parameters/upperFaultThreshold", 80.0); + test.setScalarDefault<float_t>("/parameters/upperWarningThreshold", 70.0); + test.setScalarDefault<float_t>("/parameters/lowerWarningThreshold", 60.0); + test.setScalarDefault<float_t>("/parameters/lowerFaultThreshold", 50.0); + test.setScalarDefault<float_t>("/input/path", 75.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); + } + { + TestApplication<ctk::RangeMonitor<float_t>> app; + ctk::TestFacility test; + test.setScalarDefault<float_t>("/parameters/upperFaultThreshold", 80.0); + test.setScalarDefault<float_t>("/parameters/upperWarningThreshold", 70.0); + test.setScalarDefault<float_t>("/parameters/lowerWarningThreshold", 60.0); + test.setScalarDefault<float_t>("/parameters/lowerFaultThreshold", 50.0); + test.setScalarDefault<float_t>("/input/path", 55.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); + } + { + TestApplication<ctk::RangeMonitor<float_t>> app; + ctk::TestFacility test; + test.setScalarDefault<float_t>("/parameters/upperFaultThreshold", 80.0); + test.setScalarDefault<float_t>("/parameters/upperWarningThreshold", 70.0); + test.setScalarDefault<float_t>("/parameters/lowerWarningThreshold", 60.0); + test.setScalarDefault<float_t>("/parameters/lowerFaultThreshold", 50.0); + test.setScalarDefault<float_t>("/input/path", 55.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", true); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + } + { + TestApplication<ctk::RangeMonitor<int32_t>> app; + ctk::TestFacility test; + test.setScalarDefault<int32_t>("/parameters/upperFaultThreshold", 80); + test.setScalarDefault<int32_t>("/parameters/upperWarningThreshold", 70); + test.setScalarDefault<int32_t>("/parameters/lowerWarningThreshold", 60); + test.setScalarDefault<int32_t>("/parameters/lowerFaultThreshold", 50); + test.setScalarDefault<int32_t>("/input/path", 85); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + } + { + TestApplication<ctk::RangeMonitor<int32_t>> app; + ctk::TestFacility test; + test.setScalarDefault<int32_t>("/parameters/upperFaultThreshold", 80); + test.setScalarDefault<int32_t>("/parameters/upperWarningThreshold", 70); + test.setScalarDefault<int32_t>("/parameters/lowerWarningThreshold", 60); + test.setScalarDefault<int32_t>("/parameters/lowerFaultThreshold", 50); + test.setScalarDefault<int32_t>("/input/path", 45); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + } +} + +/*********************************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(testExactMonitorInitialValuePropagation) { + std::cout << "testExactMonitorInitialValuePropagation" << std::endl; + + { + TestApplication<ctk::ExactMonitor<double_t>> app; + ctk::TestFacility test; + test.setScalarDefault<double_t>("/parameters/requiredValue", 60.0); + test.setScalarDefault<double_t>("/input/path", 60.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + } + { + TestApplication<ctk::ExactMonitor<float_t>> app; + ctk::TestFacility test; + test.setScalarDefault<float_t>("/parameters/requiredValue", 60.0); + test.setScalarDefault<float_t>("/input/path", 55.0); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", true); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::OFF)); + } + { + TestApplication<ctk::ExactMonitor<int32_t>> app; + ctk::TestFacility test; + test.setScalarDefault<int32_t>("/parameters/requiredValue", 60); + test.setScalarDefault<int32_t>("/input/path", 45); + test.setScalarDefault<ChimeraTK::Boolean>("/parameters/disable", false); + + test.runApplication(); + + BOOST_TEST(test.readScalar<int32_t>("/output/path") == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + } +} + +/*********************************************************************************************************************/ +/* Test data validity propagation for the Monitors *******************************************************************/ +/*********************************************************************************************************************/ + +/* + * Data validity is checked in base class of all monitors (MonitorBase) in method MonitorBase::setStatus only + * There is no need to test all types of monitors so we gonna use MaxMonitor + * */ +BOOST_AUTO_TEST_CASE(testMonitorDataValidityPropagation) { + std::cout << "testMonitorDataValidityPropagation" << std::endl; + + TestApplication<ctk::MaxMonitor<double_t>> app; + ctk::TestFacility test; + + test.runApplication(); + + auto fault = test.getScalar<double_t>("/parameters/upperFaultThreshold"); + auto warning = test.getScalar<double_t>("/parameters/upperWarningThreshold"); + auto watch = test.getScalar<double_t>("/input/path"); + auto status = test.getScalar<int32_t>("/output/path"); + + fault = 60.0; + fault.write(); + warning = 50.0; + warning.write(); + watch = 40.0; + watch.write(); + test.stepApplication(); + status.readLatest(); + // status is OK and data validity also + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + BOOST_CHECK(status.dataValidity() == ctk::DataValidity::ok); + + watch.setDataValidity(ctk::DataValidity::faulty); + watch.write(); + test.stepApplication(); + status.readLatest(); + // status is not changed as watch is the same, data validity is changed -> test condition: getDataValidity() != + // lastStatusValidity + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::OK)); + BOOST_CHECK(status.dataValidity() == ctk::DataValidity::faulty); + + watch = 55.0; + watch.write(); + test.stepApplication(); + status.readLatest(); + // status is changed, data validity is not changed -> test condition: status.value != newStatus + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::WARNING)); + BOOST_CHECK(status.dataValidity() == ctk::DataValidity::faulty); + + watch = 70.0; + watch.setDataValidity(ctk::DataValidity::ok); + watch.write(); + test.stepApplication(); + status.readLatest(); + // status is changed, data validity is changed -> test condition: status.value != newStatus || status.value != newStatus + BOOST_CHECK(status == static_cast<int>(ChimeraTK::StatusOutput::Status::FAULT)); + BOOST_CHECK(status.dataValidity() == ctk::DataValidity::ok); + + watch = 75.0; + watch.setDataValidity(ctk::DataValidity::ok); + watch.write(); + test.stepApplication(); + // status is not changed, data validity is not changed -> test that there is no new value of status + BOOST_CHECK(status.readLatest() == false); +}