Skip to content
Snippets Groups Projects
Commit 6a391c5d authored by Tomasz Kozak's avatar Tomasz Kozak Committed by Martin Christoph Hierholzer
Browse files

Rework StatusAggregator

parent c42d1606
No related branches found
No related tags found
No related merge requests found
......@@ -87,7 +87,7 @@ namespace ChimeraTK {
void populateStatusInput();
/// Helper for populateStatusInput
void scanAndPopulateFromHierarchyLevel(EntityOwner& module, const std::string& namePrefix);
// void scanAndPopulateFromHierarchyLevel(EntityOwner& module, const std::string& namePrefix);
/** Reserved tag which is used to mark aggregated status outputs (need to stop searching further down the
* hierarchy) */
......
......@@ -4,6 +4,7 @@
#include "ScalarAccessor.h"
#include "StatusAccessor.h"
#include "Utilities.h"
#include "VariableGroup.h"
#include <ChimeraTK/ControlSystemAdapter/StatusWithMessageReader.h>
......@@ -51,17 +52,10 @@ namespace ChimeraTK {
ScalarPushInput<std::string> _message; // left uninitialized, if no message source provided
/// Construct StatusWithMessageInput which reads only status, not message
StatusWithMessageInput(ApplicationModule* owner, std::string name, const std::string& description,
StatusWithMessageInput(ApplicationModule* owner, const std::string& qualifiedName, const std::string& description,
const std::unordered_set<std::string>& tags = {})
: VariableGroup(owner, name, "", tags), _status(this, name, description) {
hasMessageSource = false;
_statusNameLong = description;
}
[[deprecated]] StatusWithMessageInput(ApplicationModule* owner, std::string name, const std::string& description,
HierarchyModifier hierarchyModifier = HierarchyModifier::none, const std::unordered_set<std::string>& tags = {})
: VariableGroup(owner, name, "", tags), _status(this, name, description) {
applyHierarchyModifierToName(hierarchyModifier);
: VariableGroup(owner, Utilities::getPathName(qualifiedName), "", tags),
_status(this, Utilities::getUnqualifiedName(qualifiedName), description) {
hasMessageSource = false;
_statusNameLong = description;
}
......@@ -70,7 +64,9 @@ namespace ChimeraTK {
/// If not given, it is selected automatically by the naming convention
void setMessageSource(std::string msgInputName = "") {
// at the time this function is called, TransferElement impl is not yet set, so don't look there for name
if(msgInputName == "") msgInputName = ((VariableNetworkNode)_status).getName() + "_message";
if(msgInputName.empty()) {
msgInputName = ((VariableNetworkNode)_status).getName() + "_message";
}
// late initialization of _message
_message = ScalarPushInput<std::string>(this, msgInputName, "", "");
hasMessageSource = true;
......
......@@ -15,13 +15,11 @@ namespace ChimeraTK {
: ApplicationModule(owner, ".", description, outputTags), _output(this, name), _mode(mode),
_tagsToAggregate(tagsToAggregate) {
// check maximum size of tagsToAggregate
if(tagsToAggregate.size() > 1) {
if(_tagsToAggregate.size() > 1) {
throw ChimeraTK::logic_error("StatusAggregator: List of tagsToAggregate must contain at most one tag.");
}
// add reserved tag tagAggregatedStatus to the status output, so it can be detected by other StatusAggregators
_output._status.addTag(tagAggregatedStatus);
// search the variable tree for StatusOutputs and create the matching inputs
populateStatusInput();
}
......@@ -29,120 +27,75 @@ namespace ChimeraTK {
/********************************************************************************************************************/
void StatusAggregator::populateStatusInput() {
throw ChimeraTK::logic_error("THIS FUNCTION NEEDS TO BE REWRITTEN TO USE THE MODEL.");
/* scanAndPopulateFromHierarchyLevel(*getOwner(), ".");
// Special treatment for DeviceModules, because they are formally not owned by the Application: If we are
// aggregating all tags at the top-level of the Application, include the status outputs of all DeviceModules.
if(getOwner() == &Application::getInstance() && _tagsToAggregate.empty()) {
for(auto& p : Application::getInstance().deviceModuleMap) {
scanAndPopulateFromHierarchyLevel(p.second->deviceError, ".");
auto model = dynamic_cast<ModuleGroup*>(_owner)->getModel();
// set of potential inputs for this StatusAggregator instance
std::set<std::string> inputPathsSet;
// set of inputs for other, already created, StatusAggregator instances
std::set<std::string> anotherStatusAgregatorInputSet;
// map which assigns fully qualified path of StatusAggregator output to the ully qualified path StatusAggregator output message
std::map<std::string, std::string> statusToMessagePathsMap;
auto scanModel = [&](auto proxy) {
if constexpr(ChimeraTK::Model::isApplicationModule(proxy)) {
StatusAggregator* staAggPtr = dynamic_cast<StatusAggregator*>(&proxy.getApplicationModule());
if(staAggPtr != nullptr) {
if(staAggPtr == this) return;
if(_tagsToAggregate == staAggPtr->_tagsToAggregate) {
inputPathsSet.insert(staAggPtr->_output._status.getModel().getFullyQualifiedPath());
statusToMessagePathsMap[staAggPtr->_output._status.getModel().getFullyQualifiedPath()] =
staAggPtr->_output._message.getModel().getFullyQualifiedPath();
for(auto& anotherStatusAgregatorInput : staAggPtr->_inputs) {
anotherStatusAgregatorInputSet.insert(
anotherStatusAgregatorInput._status.getModel().getFullyQualifiedPath());
}
}
}
// 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) {
// a map used just for optimization of node lookup by name, which happens in inner loop below
std::map<std::string, VariableNetworkNode> nodesByName;
for(auto& node : module.getAccessorList()) {
nodesByName[node.getName()] = node;
}
// 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;
}
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;
// 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.");
}
// Create matching input for the found StatusOutput of the other StatusAggregator
// Unfortunately, the qualified name of the newly-created node is useless,
// so we save the original one as description for indication in a message
_inputs.emplace_back(this, node.getName(), node.getQualifiedName(), HierarchyModifier::hideThis,
std::unordered_set<std::string>{tagInternalVars});
// node >> _inputs.back()._status;
// look for matching status message output node
auto result = nodesByName.find(node.getName() + "_message");
if(result != nodesByName.end()) {
// tell the StatusWithMessageInput that it should consider the message source, and connect it
auto statusMsgNode = result->second;
_inputs.back().setMessageSource();
// statusMsgNode >> _inputs.back()._message;
}
}
// 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;
if constexpr(ChimeraTK::Model::isVariable(proxy)) {
// check whether its not output of this (current) StatusAggregator. 'Current' StatusAggregator output is also
// visible in the scanned model and should be ignored
if(proxy.getFullyQualifiedPath().compare(_output._status.getModel().getFullyQualifiedPath()) == 0) {
return;
}
// 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;
auto tags = proxy.getTags();
// find another aggregator output - this is already covered by checking if given module is a StatusAggregator
if(tags.find(StatusAggregator::tagAggregatedStatus) != tags.end()) {
return;
}
// find status output - this is potential candidate to be aggregated
if(tags.find(StatusOutput::tagStatusOutput) != tags.end()) {
for(const auto& tagToAgregate : _tagsToAggregate) {
// Each tag attached to this StatusAggregator must be present at all StatusOutputs to be aggregated
if(tags.find(tagToAgregate) == tags.end()) {
return;
}
}
inputPathsSet.insert(proxy.getFullyQualifiedPath());
}
if(skip) continue;
// aggregate the StatusAggregator's result
auto statusNode = VariableNetworkNode(aggregator->_output._status);
_inputs.emplace_back(this, statusNode.getName(), "", HierarchyModifier::hideThis,
std::unordered_set<std::string>{tagInternalVars});
// statusNode >> _inputs.back()._status;
auto msgNode = VariableNetworkNode(aggregator->_output._message);
_inputs.back().setMessageSource();
// msgNode >> _inputs.back()._message;
return;
}
};
model.visit(scanModel, ChimeraTK::Model::keepApplicationModules || ChimeraTK::Model::keepProcessVariables,
ChimeraTK::Model::breadthFirstSearch, ChimeraTK::Model::keepOwnership);
for(auto& pathToBeRemoved : anotherStatusAgregatorInputSet) {
inputPathsSet.erase(pathToBeRemoved);
}
// 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());
for(auto& pathToBeAggregated : inputPathsSet) {
_inputs.emplace_back(
this, pathToBeAggregated, pathToBeAggregated, std::unordered_set<std::string>{tagInternalVars});
if(!statusToMessagePathsMap[pathToBeAggregated].empty())
_inputs.back().setMessageSource(statusToMessagePathsMap[pathToBeAggregated]);
}
}
/********************************************************************************************************************/
int StatusAggregator::getPriority(StatusOutput::Status status) const {
using Status = StatusOutput::Status;
......
......@@ -19,6 +19,12 @@ namespace ctk = ChimeraTK;
struct StatusGenerator : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
StatusGenerator(ctk::ModuleGroup* owner, const std::string& name, const std::string& description,
const std::unordered_set<std::string>& tags = {})
: ApplicationModule(owner, name, description, tags) {
// std::cout << "The name: " << getName() << std::endl;
}
ctk::StatusOutput status{this, getName(), ""};
void mainLoop() override {}
};
......@@ -29,17 +35,17 @@ struct TestApplication : ctk::Application {
TestApplication() : Application("testApp") {}
~TestApplication() override { shutdown(); }
StatusGenerator s{this, ".", "Status"};
StatusGenerator s{this, "s", "Status"};
struct OuterGroup : ctk::ModuleGroup {
using ctk::ModuleGroup::ModuleGroup;
StatusGenerator s1{this, ".", "Status 1"};
StatusGenerator s2{this, ".", "Status 2"};
StatusGenerator s1{this, "s1", "Status 1"};
StatusGenerator s2{this, "s2", "Status 2"};
struct InnerGroup : ctk::ModuleGroup {
using ctk::ModuleGroup::ModuleGroup;
StatusGenerator s{this, ".", "Status"};
StatusGenerator s{this, "s", "Status"};
StatusGenerator deep{this, "deep", "Status"};
};
InnerGroup innerGroup1{this, "InnerGroup1", ""};
......@@ -51,7 +57,7 @@ struct TestApplication : ctk::Application {
this, "Aggregated/status", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko};
};
/**********************************************************************************************************************/
///**********************************************************************************************************************/
BOOST_AUTO_TEST_CASE(testSingleNoTags) {
std::cout << "testSingleNoTags" << std::endl;
......@@ -107,8 +113,8 @@ struct TestPrioApplication : ctk::Application {
TestPrioApplication() : Application("testApp") {}
~TestPrioApplication() override { shutdown(); }
StatusGenerator s1{this, ".", "Status 1"};
StatusGenerator s2{this, ".", "Status 2"};
StatusGenerator s1{this, "sg1/internal", "Status 1"};
StatusGenerator s2{this, "sg2/external", "Status 2"};
ctk::StatusAggregator aggregator;
};
......@@ -213,13 +219,13 @@ struct TestApplication2Levels : ctk::Application {
TestApplication2Levels() : Application("testApp") {}
~TestApplication2Levels() override { shutdown(); }
StatusGenerator s{this, ".", "Status"};
StatusGenerator s{this, "s", "Status"};
struct OuterGroup : ctk::ModuleGroup {
using ctk::ModuleGroup::ModuleGroup;
StatusGenerator s1{this, ".", "Status 1"};
StatusGenerator s2{this, ".", "Status 2"};
StatusGenerator s1{this, "s1", "Status 1"};
StatusGenerator s2{this, "s2", "Status 2"};
ctk::StatusAggregator extraAggregator{
this, "/Aggregated/extraStatus", "aggregated status description", ctk::StatusAggregator::PriorityMode::ofwk};
......@@ -230,10 +236,10 @@ struct TestApplication2Levels : ctk::Application {
this, "Aggregated/status", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko};
};
/**********************************************************************************************************************/
///**********************************************************************************************************************/
BOOST_AUTO_TEST_CASE(testTwoLevels) {
std::cout << "testTwoLevels" << std::endl;
std::cout << "testTwoLevels" << std::endl << std::endl << std::endl;
TestApplication2Levels app;
ctk::TestFacility test(app);
......@@ -293,8 +299,8 @@ struct TestApplicationTags : ctk::Application {
struct OuterGroup : ctk::ModuleGroup {
using ctk::ModuleGroup::ModuleGroup;
StatusGenerator sA{this, ".", "Status 1", ctk::TAGS{"A"}};
StatusGenerator sAB{this, ".", "Status 2", {"A", "B"}};
StatusGenerator sA{this, "sA", "Status 1", ctk::TAGS{"A"}};
StatusGenerator sAB{this, "sAB", "Status 2", {"A", "B"}};
ctk::StatusAggregator aggregateA{
this, "aggregateA", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko, {"A"}};
......@@ -408,7 +414,7 @@ BOOST_AUTO_TEST_CASE(testStatusMessage) {
innerStatus.readLatest();
innerStatusMessage.readLatest();
BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::FAULT));
const char* faultString = "/testApp/OuterGroup/s2/s2 switched to FAULT";
const char* faultString = "/OuterGroup/s2/s2 switched to FAULT";
BOOST_CHECK_EQUAL(std::string(statusMessage), faultString);
BOOST_CHECK_EQUAL(int(innerStatus), int(ctk::StatusOutput::Status::FAULT));
BOOST_CHECK_EQUAL(std::string(innerStatusMessage), faultString);
......
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