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