Newer
Older
Martin Christoph Hierholzer
committed
/*
* Application.cc
*
* Created on: Jun 10, 2016
* Author: Martin Hierholzer
*/
#include <string>
#include <thread>
Martin Christoph Hierholzer
committed
#include <exception>
Martin Christoph Hierholzer
committed
#include <boost/fusion/container/map.hpp>
Martin Christoph Hierholzer
committed
#include <libxml++/libxml++.h>
Martin Christoph Hierholzer
committed
#include <mtca4u/BackendFactory.h>
Martin Christoph Hierholzer
committed
#include "Application.h"
#include "ApplicationModule.h"
Martin Christoph Hierholzer
committed
#include "ThreadedFanOut.h"
#include "ConsumingFanOut.h"
#include "FeedingFanOut.h"
#include "TriggerFanOut.h"
Martin Christoph Hierholzer
committed
#include "VariableNetworkNode.h"
Martin Christoph Hierholzer
committed
#include "ScalarAccessor.h"
#include "ArrayAccessor.h"
#include "ConstantAccessor.h"
Martin Christoph Hierholzer
committed
#include "TestDecoratorRegisterAccessor.h"
Martin Christoph Hierholzer
committed
using namespace ChimeraTK;
Martin Christoph Hierholzer
committed
std::mutex Application::testableMode_mutex;
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::initialise() {
// call the user-defined defineConnections() function which describes the structure of the application
defineConnections();
// connect any unconnected accessors with constant values
processUnconnectedNodes();
// realise the connections between variable accessors as described in the initialise() function
makeConnections();
}
/*********************************************************************************************************************/
void Application::processUnconnectedNodes() {
Martin Christoph Hierholzer
committed
for(auto &module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
for(auto &accessor : module->getAccessorList()) {
Martin Christoph Hierholzer
committed
if(!accessor.hasOwner()) {
Martin Christoph Hierholzer
committed
if(enableUnconnectedVariablesWarning) {
std::cerr << "*** Warning: Variable '" << accessor.getName() << "' is not connected. "
"Reading will always result in 0, writing will be ignored." << std::endl;
}
Martin Christoph Hierholzer
committed
networkList.emplace_back();
Martin Christoph Hierholzer
committed
networkList.back().addNode(accessor);
Martin Christoph Hierholzer
committed
bool makeFeeder = !(networkList.back().hasFeedingNode());
Martin Christoph Hierholzer
committed
size_t length = accessor.getNumberOfElements();
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
if(accessor.getValueType() == typeid(int8_t)) {
Martin Christoph Hierholzer
committed
constantList.emplace_back(VariableNetworkNode::makeConstant<int8_t>(makeFeeder, 0, length));
}
Martin Christoph Hierholzer
committed
else if(accessor.getValueType() == typeid(uint8_t)) {
Martin Christoph Hierholzer
committed
constantList.emplace_back(VariableNetworkNode::makeConstant<uint8_t>(makeFeeder, 0, length));
}
Martin Christoph Hierholzer
committed
else if(accessor.getValueType() == typeid(int16_t)) {
Martin Christoph Hierholzer
committed
constantList.emplace_back(VariableNetworkNode::makeConstant<int16_t>(makeFeeder, 0, length));
}
Martin Christoph Hierholzer
committed
else if(accessor.getValueType() == typeid(uint16_t)) {
Martin Christoph Hierholzer
committed
constantList.emplace_back(VariableNetworkNode::makeConstant<uint16_t>(makeFeeder, 0, length));
}
Martin Christoph Hierholzer
committed
else if(accessor.getValueType() == typeid(int32_t)) {
Martin Christoph Hierholzer
committed
constantList.emplace_back(VariableNetworkNode::makeConstant<int32_t>(makeFeeder, 0, length));
}
Martin Christoph Hierholzer
committed
else if(accessor.getValueType() == typeid(uint32_t)) {
Martin Christoph Hierholzer
committed
constantList.emplace_back(VariableNetworkNode::makeConstant<uint32_t>(makeFeeder, 0, length));
}
Martin Christoph Hierholzer
committed
else if(accessor.getValueType() == typeid(float)) {
Martin Christoph Hierholzer
committed
constantList.emplace_back(VariableNetworkNode::makeConstant<float>(makeFeeder, 0, length));
}
Martin Christoph Hierholzer
committed
else if(accessor.getValueType() == typeid(double)) {
Martin Christoph Hierholzer
committed
constantList.emplace_back(VariableNetworkNode::makeConstant<double>(makeFeeder, 0, length));
}
Martin Christoph Hierholzer
committed
else if(accessor.getValueType() == typeid(std::string)) {
constantList.emplace_back(VariableNetworkNode::makeConstant<std::string>(makeFeeder, 0, length));
}
Martin Christoph Hierholzer
committed
else {
throw std::invalid_argument("Unknown value type.");
}
networkList.back().addNode(constantList.back());
}
}
}
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
void Application::checkConnections() {
// check all networks for validity
for(auto &network : networkList) {
network.check();
}
// check if all accessors are connected
Martin Christoph Hierholzer
committed
for(auto &module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
for(auto &accessor : module->getAccessorList()) {
Martin Christoph Hierholzer
committed
if(!accessor.hasOwner()) {
throw std::invalid_argument("The accessor '"+accessor.getName()+"' of the module '"+module->getName()+
Martin Christoph Hierholzer
committed
"' was not connected!");
}
}
}
}
/*********************************************************************************************************************/
void Application::run() {
Martin Christoph Hierholzer
committed
// check if the application name has been set
if(applicationName == "") {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>(
"Error: An instance of Application must have its applicationName set.");
}
Martin Christoph Hierholzer
committed
// start the necessary threads for the FanOuts etc.
for(auto &internalModule : internalModuleList) {
internalModule->activate();
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// read all input variables once, to set the startup value e.g. coming from the config file
// (without triggering an action inside the application)
Martin Christoph Hierholzer
committed
for(auto &module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
for(auto &variable : module->getAccessorList()) {
Martin Christoph Hierholzer
committed
if(variable.getDirection() == VariableDirection::consuming) {
variable.getAppAccessorNoType().readNonBlocking();
Martin Christoph Hierholzer
committed
}
}
}
Martin Christoph Hierholzer
committed
// start the threads for the modules
Martin Christoph Hierholzer
committed
for(auto &module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
module->run();
}
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::shutdown() {
Martin Christoph Hierholzer
committed
// first allow to run the application threads again, if we are in testable mode
Martin Christoph Hierholzer
committed
if(testableMode && testableModeTestLock()) {
testableModeUnlock("shutdown");
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// deactivate the FanOuts first, since they have running threads inside accessing the modules etc.
// (note: the modules are members of the Application implementation and thus get destroyed after this destructor)
for(auto &internalModule : internalModuleList) {
internalModule->deactivate();
Martin Christoph Hierholzer
committed
}
// next deactivate the modules, as they have running threads inside as well
Martin Christoph Hierholzer
committed
for(auto &module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
module->terminate();
}
ApplicationBase::shutdown();
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::generateXML() {
Martin Christoph Hierholzer
committed
// define the connections
defineConnections();
// also search for unconnected nodes - this is here only executed to print the warnings
processUnconnectedNodes();
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// check if the application name has been set
if(applicationName == "") {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>(
"Error: An instance of Application must have its applicationName set.");
}
Martin Christoph Hierholzer
committed
// create XML document with root node
xmlpp::Document doc;
Martin Christoph Hierholzer
committed
xmlpp::Element *rootElement = doc.create_root_node("application", "https://github.com/ChimeraTK/ApplicationCore");
rootElement->set_attribute("name",applicationName);
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
for(auto &network : networkList) {
Martin Christoph Hierholzer
committed
// perform checks
network.check();
Martin Christoph Hierholzer
committed
// create xml code for the feeder (if it is a control system node)
Martin Christoph Hierholzer
committed
auto feeder = network.getFeedingNode();
Martin Christoph Hierholzer
committed
feeder.createXML(rootElement);
// create xml code for the consumers
for(auto &consumer : network.getConsumingNodes()) {
consumer.createXML(rootElement);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
doc.write_to_file_formatted(applicationName+".xml");
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
VariableNetwork& Application::connect(VariableNetworkNode a, VariableNetworkNode b) {
// if one of the nodes has the value type AnyType, set it to the type of the other
Martin Christoph Hierholzer
committed
// if both are AnyType, nothing changes.
if(a.getValueType() == typeid(AnyType)) {
a.setValueType(b.getValueType());
}
else if(b.getValueType() == typeid(AnyType)) {
b.setValueType(a.getValueType());
}
// if one of the nodes has not yet a defined number of elements, set it to the number of elements of the other.
// if both are undefined, nothing changes.
if(a.getNumberOfElements() == 0) {
a.setNumberOfElements(b.getNumberOfElements());
}
else if(b.getNumberOfElements() == 0) {
b.setNumberOfElements(a.getNumberOfElements());
}
if(a.getNumberOfElements() != b.getNumberOfElements()) {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>(
"Error: Cannot connect array variables with difference number of elements!");
}
Martin Christoph Hierholzer
committed
// if both nodes already have an owner, we are done
if(a.hasOwner() && b.hasOwner()) {
assert( &(a.getOwner()) == &(b.getOwner()) ); /// @todo TODO merge networks?
}
Martin Christoph Hierholzer
committed
// add b to the existing network of a
Martin Christoph Hierholzer
committed
else if(a.hasOwner()) {
a.getOwner().addNode(b);
}
Martin Christoph Hierholzer
committed
// add a to the existing network of b
Martin Christoph Hierholzer
committed
else if(b.hasOwner()) {
b.getOwner().addNode(a);
}
Martin Christoph Hierholzer
committed
// create new network
Martin Christoph Hierholzer
committed
else {
networkList.emplace_back();
networkList.back().addNode(a);
networkList.back().addNode(b);
}
Martin Christoph Hierholzer
committed
return a.getOwner();
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
template<typename UserType>
boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> Application::createDeviceVariable(const std::string &deviceAlias,
Martin Christoph Hierholzer
committed
const std::string ®isterName, VariableDirection direction, UpdateMode mode, size_t nElements) {
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// open device if needed
if(deviceMap.count(deviceAlias) == 0) {
deviceMap[deviceAlias] = mtca4u::BackendFactory::getInstance().createBackend(deviceAlias);
if(!deviceMap[deviceAlias]->isOpen()) deviceMap[deviceAlias]->open();
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// use wait_for_new_data mode if push update mode was requested
Martin Christoph Hierholzer
committed
mtca4u::AccessModeFlags flags{};
Martin Christoph Hierholzer
committed
if(mode == UpdateMode::push && direction == VariableDirection::consuming) flags = {AccessMode::wait_for_new_data};
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// return the register accessor from the device
return deviceMap[deviceAlias]->getRegisterAccessor<UserType>(registerName, nElements, 0, flags);
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
template<typename UserType>
Martin Christoph Hierholzer
committed
boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> Application::createProcessVariable(VariableNetworkNode const &node) {
Martin Christoph Hierholzer
committed
// determine the SynchronizationDirection
SynchronizationDirection dir;
Martin Christoph Hierholzer
committed
if(node.getDirection() == VariableDirection::feeding) {
Martin Christoph Hierholzer
committed
dir = SynchronizationDirection::controlSystemToDevice;
Martin Christoph Hierholzer
committed
}
else {
Martin Christoph Hierholzer
committed
dir = SynchronizationDirection::deviceToControlSystem;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// create the ProcessArray for the proper UserType
Martin Christoph Hierholzer
committed
auto pvar = _processVariableManager->createProcessArray<UserType>(dir, node.getPublicName(), node.getNumberOfElements(),
node.getOwner().getUnit(), node.getOwner().getDescription());
Martin Christoph Hierholzer
committed
assert(pvar->getName() != "");
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// decorate the process variable if testable mode is enabled and this is the receiving end of the variable
Martin Christoph Hierholzer
committed
// don't decorate if the feeding side is a constant!
// Also don't decorate, if the mode is polling. Instead flag the variable to be polling, so the TestFacility is aware of this.
Martin Christoph Hierholzer
committed
if(testableMode && node.getDirection() == VariableDirection::feeding && node.getType() != NodeType::Constant) {
// The transfer mode of this process variable is considered to be polling, if only one consumer exists and this
// consumer is polling. Reason: mulitple consumers will result in the use of a FanOut, so the communication up to
// the FanOut will be push-type, even if all consumers are poll-type.
/// @todo Check if this is true!
auto mode = UpdateMode::push;
if(node.getOwner().countConsumingNodes() == 1) {
if(node.getOwner().getConsumingNodes().front().getMode() == UpdateMode::poll) mode = UpdateMode::poll;
}
if(mode != UpdateMode::poll) {
auto pvarDec = boost::make_shared<TestDecoratorRegisterAccessor<UserType>>(pvar);
testableMode_names[pvarDec->getUniqueId()] = "ControlSystem:"+node.getPublicName();
return pvarDec;
}
else {
testableMode_isPollMode[pvar->getUniqueId()] = true;
}
Martin Christoph Hierholzer
committed
}
// return the process variable
return pvar;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
template<typename UserType>
std::pair< boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>>, boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> >
Martin Christoph Hierholzer
committed
Application::createApplicationVariable(VariableNetworkNode const &node) {
// obtain the meta data
size_t nElements = node.getNumberOfElements();
std::string name = node.getName();
Martin Christoph Hierholzer
committed
assert(name != "");
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// create the ProcessArray for the proper UserType
Martin Christoph Hierholzer
committed
auto pvarPair = createSynchronizedProcessArray<UserType>(nElements, name);
Martin Christoph Hierholzer
committed
assert(pvarPair.first->getName() != "");
assert(pvarPair.second->getName() != "");
Martin Christoph Hierholzer
committed
// decorate the process variable if testable mode is enabled and mode is push-type
if(testableMode && node.getMode() == UpdateMode::push) {
Martin Christoph Hierholzer
committed
std::pair< boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>>,
boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> > pvarPairDec;
pvarPairDec.first = boost::make_shared<TestDecoratorRegisterAccessor<UserType>>(pvarPair.first);
pvarPairDec.second = boost::make_shared<TestDecoratorRegisterAccessor<UserType>>(pvarPair.second);
Martin Christoph Hierholzer
committed
// put the decorators into the list
testableMode_names[pvarPair.first->getUniqueId()] = "Internal:"+pvarPair.first->getName()+"->"+pvarPair.second->getName();
Martin Christoph Hierholzer
committed
return pvarPairDec;
Martin Christoph Hierholzer
committed
}
// return the pair
return pvarPair;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
void Application::makeConnections() {
Martin Christoph Hierholzer
committed
// run checks first
checkConnections();
// apply optimisations
optimiseConnections();
// run checks again to make sure the optimisations didn't create corrupted networks
checkConnections();
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// make the connections for all networks
for(auto &network : networkList) {
makeConnectionsForNetwork(network);
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
void Application::optimiseConnections() {
// list of iterators of networks to be removed from the networkList after the merge operation
std::list<VariableNetwork*> deleteNetworks;
// search for networks with the same feeder
for(auto it1 = networkList.begin(); it1 != networkList.end(); ++it1) {
for(auto it2 = it1; it2 != networkList.end(); ++it2) {
if(it1 == it2) continue;
auto feeder1 = it1->getFeedingNode();
auto feeder2 = it2->getFeedingNode();
// this optimisation is only necessary for device-type nodes, since application and control-system nodes will
// automatically create merged networks when having the same feeder
/// @todo check if this assumtion is true! control-system nodes can be created with different types, too!
if(feeder1.getType() != NodeType::Device || feeder2.getType() != NodeType::Device) continue;
// check if referrring to same register
if(feeder1.getDeviceAlias() != feeder2.getDeviceAlias()) continue;
if(feeder1.getRegisterName() != feeder2.getRegisterName()) continue;
// check if directions are the same
if(feeder1.getDirection() != feeder2.getDirection()) continue;
// check if value types and number of elements are compatible
if(feeder1.getValueType() != feeder2.getValueType()) continue;
if(feeder1.getNumberOfElements() != feeder2.getNumberOfElements()) continue;
// check if transfer mode is the same
if(feeder1.getMode() != feeder2.getMode()) continue;
// check if triggers are compatible, if present
if(feeder1.hasExternalTrigger() != feeder2.hasExternalTrigger()) continue;
if(feeder1.hasExternalTrigger()) {
if(feeder1.getExternalTrigger() != feeder2.getExternalTrigger()) continue;
}
// everything should be compatible at this point: merge the networks. We will merge the network of the outer
// loop into the network of the inner loop, since the network of the outer loop will not be found a second time
// in the inner loop.
for(auto consumer : it1->getConsumingNodes()) {
consumer.clearOwner();
it2->addNode(consumer);
}
Martin Christoph Hierholzer
committed
// if trigger present, remove corresponding trigger receiver node from the trigger network
for(auto &itTrig : networkList) {
if(itTrig.getFeedingNode() != feeder1.getExternalTrigger()) continue;
itTrig.removeNodeToTrigger(it1->getFeedingNode());
}
// schedule the outer loop network for deletion and stop processing it
deleteNetworks.push_back(&(*it1));
break;
}
}
// remove networks from the network list
for(auto net : deleteNetworks) {
networkList.remove(*net);
}
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
void Application::dumpConnections() {
std::cout << "==== List of all variable connections of the current Application ====" << std::endl;
Martin Christoph Hierholzer
committed
for(auto &network : networkList) {
network.dump();
}
std::cout << "=====================================================================" << std::endl;
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::makeConnectionsForNetwork(VariableNetwork &network) {
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// if the network has been created already, do nothing
if(network.isCreated()) return;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// if the trigger type is external, create the trigger first
if(network.getFeedingNode().hasExternalTrigger()) {
VariableNetwork &dependency = network.getFeedingNode().getExternalTrigger().getOwner();
Martin Christoph Hierholzer
committed
if(!dependency.isCreated()) makeConnectionsForNetwork(dependency);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
// defer actual network creation to templated function
// @todo TODO replace with boost::mpl::for_each loop!
if(network.getValueType() == typeid(int8_t)) {
typedMakeConnection<int8_t>(network);
}
else if(network.getValueType() == typeid(uint8_t)) {
typedMakeConnection<uint8_t>(network);
}
else if(network.getValueType() == typeid(int16_t)) {
typedMakeConnection<int16_t>(network);
}
else if(network.getValueType() == typeid(uint16_t)) {
typedMakeConnection<uint16_t>(network);
}
else if(network.getValueType() == typeid(int32_t)) {
typedMakeConnection<int32_t>(network);
}
else if(network.getValueType() == typeid(uint32_t)) {
typedMakeConnection<uint32_t>(network);
}
else if(network.getValueType() == typeid(float)) {
typedMakeConnection<float>(network);
}
else if(network.getValueType() == typeid(double)) {
typedMakeConnection<double>(network);
}
Martin Christoph Hierholzer
committed
else if(network.getValueType() == typeid(std::string)) {
typedMakeConnection<std::string>(network);
}
else {
throw std::invalid_argument("Unknown value type.");
}
Martin Christoph Hierholzer
committed
// mark the network as created
network.markCreated();
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
template<typename UserType>
void Application::typedMakeConnection(VariableNetwork &network) {
bool connectionMade = false; // to check the logic...
size_t nNodes = network.countConsumingNodes()+1;
Martin Christoph Hierholzer
committed
auto feeder = network.getFeedingNode();
Martin Christoph Hierholzer
committed
auto consumers = network.getConsumingNodes();
Martin Christoph Hierholzer
committed
bool useExternalTrigger = network.getTriggerType() == VariableNetwork::TriggerType::external;
Martin Christoph Hierholzer
committed
bool useFeederTrigger = network.getTriggerType() == VariableNetwork::TriggerType::feeder;
Martin Christoph Hierholzer
committed
bool constantFeeder = feeder.getType() == NodeType::Constant;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// 1st case: the feeder requires a fixed implementation
Martin Christoph Hierholzer
committed
if(feeder.hasImplementation() && !constantFeeder) {
Martin Christoph Hierholzer
committed
// Create feeding implementation. Note: though the implementation is derived from the feeder, it will be used as
// the implementation of the (or one of the) consumer. Logically, implementations are always pairs of
// implementations (sender and receiver), but in this case the feeder already has a fixed implementation pair.
// So our feedingImpl will contain the consumer-end of the implementation pair. This is the reason why the
// functions createProcessScalar() and createDeviceAccessor() get the VariableDirection::consuming.
boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> feedingImpl;
Martin Christoph Hierholzer
committed
if(feeder.getType() == NodeType::Device) {
feedingImpl = createDeviceVariable<UserType>(feeder.getDeviceAlias(), feeder.getRegisterName(),
Martin Christoph Hierholzer
committed
VariableDirection::consuming, feeder.getMode(), feeder.getNumberOfElements());
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(feeder.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
feedingImpl = createProcessVariable<UserType>(feeder);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
Martin Christoph Hierholzer
committed
// if we just have two nodes, directly connect them
Martin Christoph Hierholzer
committed
if(nNodes == 2 && !useExternalTrigger) {
Martin Christoph Hierholzer
committed
auto consumer = consumers.front();
Martin Christoph Hierholzer
committed
if(consumer.getType() == NodeType::Application) {
Martin Christoph Hierholzer
committed
consumer.getAppAccessor<UserType>().replace(feedingImpl);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::Device) {
auto consumingImpl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
Martin Christoph Hierholzer
committed
VariableDirection::feeding, consumer.getMode(), consumer.getNumberOfElements());
Martin Christoph Hierholzer
committed
// connect the Device with e.g. a ControlSystem node via a ThreadedFanOut
auto fanOut = boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl);
Martin Christoph Hierholzer
committed
fanOut->addSlave(consumingImpl);
internalModuleList.push_back(fanOut);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
auto consumingImpl = createProcessVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
// connect the ControlSystem with e.g. a Device node via an ThreadedFanOut
auto fanOut = boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl);
Martin Christoph Hierholzer
committed
fanOut->addSlave(consumingImpl);
internalModuleList.push_back(fanOut);
Martin Christoph Hierholzer
committed
connectionMade = true;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::TriggerReceiver) {
Martin Christoph Hierholzer
committed
consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(feedingImpl);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
}
Martin Christoph Hierholzer
committed
else { /* !(nNodes == 2 && !useExternalTrigger) */
Martin Christoph Hierholzer
committed
// create the right FanOut type
boost::shared_ptr<FanOut<UserType>> fanOut;
Martin Christoph Hierholzer
committed
boost::shared_ptr<ConsumingFanOut<UserType>> consumingFanOut;
Martin Christoph Hierholzer
committed
if(useExternalTrigger) {
// if external trigger is enabled, use externally triggered threaded FanOut
Martin Christoph Hierholzer
committed
auto triggerNode = feeder.getExternalTrigger();
auto triggerFanOut = triggerMap[triggerNode.getUniqueId()];
if(!triggerFanOut) {
Martin Christoph Hierholzer
committed
triggerFanOut = boost::make_shared<TriggerFanOut>(network.getExternalTriggerImpl());
triggerMap[triggerNode.getUniqueId()] = triggerFanOut;
internalModuleList.push_back(triggerFanOut);
}
fanOut = triggerFanOut->addNetwork(feedingImpl);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(useFeederTrigger) {
// if the trigger is provided by the pushing feeder, use the treaded version of the FanOut to distribute
// new values immediately to all consumers.
Martin Christoph Hierholzer
committed
auto threadedFanOut = boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl);
internalModuleList.push_back(threadedFanOut);
fanOut = threadedFanOut;
Martin Christoph Hierholzer
committed
}
else {
if(!network.hasApplicationConsumer()) {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("No application node in the network but no trigger!");
}
Martin Christoph Hierholzer
committed
consumingFanOut = boost::make_shared<ConsumingFanOut<UserType>>(feedingImpl);
fanOut = consumingFanOut;
Martin Christoph Hierholzer
committed
// In case we have one or more trigger receivers among our consumers, we produce exactly one application variable
// for it. We never need more, since the distribution is done with a TriggerFanOut.
bool usedTriggerReceiver{false}; // flag if we already have a trigger receiver
auto triggerConnection = createApplicationVariable<UserType>(feeder); // will get destroyed if not used
// add all consumers to the FanOut
Martin Christoph Hierholzer
committed
for(auto &consumer : consumers) {
Martin Christoph Hierholzer
committed
if(consumer.getType() == NodeType::Application) {
Martin Christoph Hierholzer
committed
if(consumingFanOut) {
Martin Christoph Hierholzer
committed
consumer.getAppAccessor<UserType>().replace(consumingFanOut);
Martin Christoph Hierholzer
committed
consumingFanOut.reset();
Martin Christoph Hierholzer
committed
}
else {
Martin Christoph Hierholzer
committed
auto impls = createApplicationVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
fanOut->addSlave(impls.first);
Martin Christoph Hierholzer
committed
consumer.getAppAccessor<UserType>().replace(impls.second);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
auto impl = createProcessVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
fanOut->addSlave(impl);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::Device) {
auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
Martin Christoph Hierholzer
committed
VariableDirection::feeding, consumer.getMode(), consumer.getNumberOfElements());
Martin Christoph Hierholzer
committed
fanOut->addSlave(impl);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::TriggerReceiver) {
Martin Christoph Hierholzer
committed
if(!usedTriggerReceiver) fanOut->addSlave(triggerConnection.first);
usedTriggerReceiver = true;
Martin Christoph Hierholzer
committed
consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(triggerConnection.second);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
connectionMade = true;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
// 2nd case: the feeder does not require a fixed implementation
Martin Christoph Hierholzer
committed
else if(!constantFeeder) { /* !feeder.hasImplementation() */
Martin Christoph Hierholzer
committed
// we should be left with an application feeder node
Martin Christoph Hierholzer
committed
if(feeder.getType() != NodeType::Application) {
Martin Christoph Hierholzer
committed
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
Martin Christoph Hierholzer
committed
assert(!useExternalTrigger);
Martin Christoph Hierholzer
committed
// if we just have two nodes, directly connect them
if(nNodes == 2) {
Martin Christoph Hierholzer
committed
auto consumer = consumers.front();
Martin Christoph Hierholzer
committed
if(consumer.getType() == NodeType::Application) {
Martin Christoph Hierholzer
committed
auto impls = createApplicationVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
feeder.getAppAccessor<UserType>().replace(impls.first);
consumer.getAppAccessor<UserType>().replace(impls.second);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
auto impl = createProcessVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
feeder.getAppAccessor<UserType>().replace(impl);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::Device) {
auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
Martin Christoph Hierholzer
committed
VariableDirection::feeding, consumer.getMode(), consumer.getNumberOfElements());
Martin Christoph Hierholzer
committed
feeder.getAppAccessor<UserType>().replace(impl);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::TriggerReceiver) {
Martin Christoph Hierholzer
committed
consumer.dump();
Martin Christoph Hierholzer
committed
auto impls = createApplicationVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
feeder.getAppAccessor<UserType>().replace(impls.first);
Martin Christoph Hierholzer
committed
consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(impls.second);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::Constant) {
Martin Christoph Hierholzer
committed
auto impl = consumer.getConstAccessor<UserType>();
feeder.getAppAccessor<UserType>().replace(impl);
Martin Christoph Hierholzer
committed
connectionMade = true;
}
Martin Christoph Hierholzer
committed
else {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
}
else {
// create FanOut and use it as the feeder implementation
Martin Christoph Hierholzer
committed
auto fanOut = boost::make_shared<FeedingFanOut<UserType>>(feeder.getName(), feeder.getUnit(),
Martin Christoph Hierholzer
committed
feeder.getDescription(), feeder.getNumberOfElements());
Martin Christoph Hierholzer
committed
feeder.getAppAccessor<UserType>().replace(fanOut);
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// In case we have one or more trigger receivers among our consumers, we produce exactly one application variable
// for it. We never need more, since the distribution is done with a TriggerFanOut.
bool usedTriggerReceiver{false}; // flag if we already have a trigger receiver
auto triggerConnection = createApplicationVariable<UserType>(feeder); // will get destroyed if not used
Martin Christoph Hierholzer
committed
for(auto &consumer : consumers) {
Martin Christoph Hierholzer
committed
if(consumer.getType() == NodeType::Application) {
Martin Christoph Hierholzer
committed
auto impls = createApplicationVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
fanOut->addSlave(impls.first);
Martin Christoph Hierholzer
committed
consumer.getAppAccessor<UserType>().replace(impls.second);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
auto impl = createProcessVariable<UserType>(consumer);
Martin Christoph Hierholzer
committed
fanOut->addSlave(impl);
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::Device) {
auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
Martin Christoph Hierholzer
committed
VariableDirection::feeding, consumer.getMode(), consumer.getNumberOfElements());
Martin Christoph Hierholzer
committed
fanOut->addSlave(impl);
}
Martin Christoph Hierholzer
committed
else if(consumer.getType() == NodeType::TriggerReceiver) {
Martin Christoph Hierholzer
committed
if(!usedTriggerReceiver) fanOut->addSlave(triggerConnection.first);
usedTriggerReceiver = true;
Martin Christoph Hierholzer
committed
consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(triggerConnection.second);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
else {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
}
connectionMade = true;
Martin Christoph Hierholzer
committed
}
}
Martin Christoph Hierholzer
committed
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
else { /* constantFeeder */
assert(feeder.getType() == NodeType::Constant);
auto feedingImpl = feeder.getConstAccessor<UserType>();
assert(feedingImpl != nullptr);
for(auto &consumer : consumers) {
if(consumer.getType() == NodeType::Application) {
consumer.getAppAccessor<UserType>().replace(feedingImpl);
}
else if(consumer.getType() == NodeType::ControlSystem) {
auto impl = createProcessVariable<UserType>(consumer);
impl->accessChannel(0) = feedingImpl->accessChannel(0);
impl->write();
}
else if(consumer.getType() == NodeType::Device) {
auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
VariableDirection::feeding, consumer.getMode(), consumer.getNumberOfElements());
impl->accessChannel(0) = feedingImpl->accessChannel(0);
impl->write();
}
else if(consumer.getType() == NodeType::TriggerReceiver) {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Using constants as triggers is not supported!");
}
else {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
}
connectionMade = true;
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
if(!connectionMade) {
throw ApplicationExceptionWithID<ApplicationExceptionID::notYetImplemented>(
Martin Christoph Hierholzer
committed
"The variable network cannot be handled. Implementation missing!");
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
VariableNetwork& Application::createNetwork() {
networkList.emplace_back();
return networkList.back();
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Application& Application::getInstance() {
return dynamic_cast<Application&>(ApplicationBase::getInstance());
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
void Application::stepApplication() {
// testableMode_counter must be non-zero, otherwise there is no input for the application to process
if(testableMode_counter == 0) {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>(
"Application::stepApplication() called despite no input was provided to the application to process!");
}
// let the application run until it has processed all data (i.e. the semaphore counter is 0)
Martin Christoph Hierholzer
committed
size_t oldCounter = 0;
Martin Christoph Hierholzer
committed
while(testableMode_counter > 0) {
Martin Christoph Hierholzer
committed
if(enableDebugTestableMode && ( oldCounter != testableMode_counter) ) {
std::cout << "Application::stepApplication(): testableMode_counter = " << testableMode_counter << std::endl;
oldCounter = testableMode_counter;
}
Martin Christoph Hierholzer
committed
testableModeUnlock("stepApplication");
Martin Christoph Hierholzer
committed
boost::this_thread::yield();
Martin Christoph Hierholzer
committed
testableModeLock("stepApplication");
Martin Christoph Hierholzer
committed
}
}
/*********************************************************************************************************************/
boost::shared_ptr<TransferElement> Application::readAny(std::list<std::reference_wrapper<TransferElement>> elementsToRead) {
Martin Christoph Hierholzer
committed
if(!Application::getInstance().testableMode) {
Martin Christoph Hierholzer
committed
return mtca4u::TransferElement::readAny(elementsToRead);
}
else {
try {
Martin Christoph Hierholzer
committed
testableModeUnlock("readAny");
Martin Christoph Hierholzer
committed
}
catch(std::system_error &e) { // ignore operation not permitted errors, since they happen the first time (lock not yet owned)
if(e.code() != std::errc::operation_not_permitted) throw;
Martin Christoph Hierholzer
committed
}
auto ret = mtca4u::TransferElement::readAny(elementsToRead);
Martin Christoph Hierholzer
committed
assert(testableModeTestLock()); // lock is acquired inside readAny(), since TestDecoratorTransferFuture::wait() is called there.
Martin Christoph Hierholzer
committed
return ret;
}
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
void Application::testableModeLock(const std::string& name) {
Martin Christoph Hierholzer
committed
// don't do anything if testable mode is not enabled
Martin Christoph Hierholzer
committed
if(!getInstance().testableMode) return;
Martin Christoph Hierholzer
committed
// debug output if enabled (also prevent spamming the same message)
Martin Christoph Hierholzer
committed
if(getInstance().enableDebugTestableMode && getInstance().testableMode_repeatingMutexOwner == 0) {
Martin Christoph Hierholzer
committed
std::cout << "Application::testableModeLock(): Thread " << threadName()
Martin Christoph Hierholzer
committed
<< " tries to obtain lock for " << name << std::endl;
}
Martin Christoph Hierholzer
committed
// if last lock was obtained repeatedly by the same thread, sleep a short time before obtaining the lock to give the
// other threads a chance to get the lock first
if(getInstance().testableMode_repeatingMutexOwner > 0) usleep(1000);
// obtain the lock
Martin Christoph Hierholzer
committed
getTestableModeLockObject().lock();
Martin Christoph Hierholzer
committed
// check if the last owner of the mutex was this thread, which may be a hint that no other thread is waiting for the
// lock
Martin Christoph Hierholzer
committed
if(getInstance().testableMode_lastMutexOwner == std::this_thread::get_id()) {
Martin Christoph Hierholzer
committed
// debug output if enabled
if(getInstance().enableDebugTestableMode && getInstance().testableMode_repeatingMutexOwner == 0) {
Martin Christoph Hierholzer
committed
std::cout << "Application::testableModeLock(): Thread " << threadName()
Martin Christoph Hierholzer
committed
<< " repeatedly obtained lock successfully for " << name << ". Further messages will be suppressed." << std::endl;
}
Martin Christoph Hierholzer
committed
// increase counter for stall detection
Martin Christoph Hierholzer
committed
getInstance().testableMode_repeatingMutexOwner++;
Martin Christoph Hierholzer
committed
// detect stall: if the same thread got the mutex with no other thread obtaining it in between for one second, we
// assume no other thread is able to process data at this time. The test should fail in this case
Martin Christoph Hierholzer
committed
if(getInstance().testableMode_repeatingMutexOwner > 1000) {
Martin Christoph Hierholzer
committed
// print an informative message first, which lists also all variables currently containing unread data.
Martin Christoph Hierholzer
committed
std::cout << "*** Tests are stalled due to data which has been sent but not received." << std::endl;
Martin Christoph Hierholzer
committed
std::cout << " The following variables still contain unread values or had data loss due to a queue overflow:" << std::endl;
for(auto &pair : Application::getInstance().testableMode_perVarCounter) {
if(pair.second > 0) {
Martin Christoph Hierholzer
committed
std::cout << " - " << Application::getInstance().testableMode_names[pair.first];
// check if process variable still has data in the queue
try {
if(getInstance().testableMode_processVars[pair.first]->readNonBlocking()) {
std::cout << " (unread data in queue)";
}
else {
std::cout << " (data loss)";
}
Martin Christoph Hierholzer
committed
}
catch(std::logic_error &e) {
// if we receive a logic_error in readNonBlocking() it just means another thread is waiting on a
// TransferFuture of this variable, and we actually were not allowed to read...
Martin Christoph Hierholzer
committed
std::cout << " (data loss)";
}
std::cout << std::endl;
Martin Christoph Hierholzer
committed
}
}
Martin Christoph Hierholzer
committed
// throw an exception which cannot be caught (other than with catch all). This makes sure that the tests fail
// properly
Martin Christoph Hierholzer
committed
class TestsStalled {};
throw TestsStalled();
}
}
else {
Martin Christoph Hierholzer
committed
// last owner of the mutex was different: reset the counter and store the thread id
Martin Christoph Hierholzer
committed
getInstance().testableMode_repeatingMutexOwner = 0;
Martin Christoph Hierholzer
committed
getInstance().testableMode_lastMutexOwner = std::this_thread::get_id();
// debug output if enabled
if(getInstance().enableDebugTestableMode) {
Martin Christoph Hierholzer
committed
std::cout << "Application::testableModeLock(): Thread " << threadName()
Martin Christoph Hierholzer
committed
<< " obtained lock successfully for " << name << std::endl;
}
Martin Christoph Hierholzer
committed
}
}
/*********************************************************************************************************************/
void Application::testableModeUnlock(const std::string& name) {
if(!getInstance().testableMode) return;
if(getInstance().enableDebugTestableMode && (!getInstance().testableMode_repeatingMutexOwner
|| getInstance().testableMode_lastMutexOwner != std::this_thread::get_id())) {
Martin Christoph Hierholzer
committed
std::cout << "Application::testableModeUnlock(): Thread " << threadName()
Martin Christoph Hierholzer
committed
<< " releases lock for " << name << std::endl;
}
getTestableModeLockObject().unlock();
}