Skip to content
Snippets Groups Projects
Unverified Commit 34583a5c authored by Martin Killenberg's avatar Martin Killenberg Committed by GitHub
Browse files

Merge pull request #209 from ChimeraTK/wip-mhier-7982-status-aggregator

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