-
Martin Christoph Hierholzer authoredMartin Christoph Hierholzer authored
Application.cc 23.55 KiB
/*
* Application.cc
*
* Created on: Jun 10, 2016
* Author: Martin Hierholzer
*/
#include <string>
#include <thread>
#include <exception>
#include <boost/fusion/container/map.hpp>
#include <libxml++/libxml++.h>
#include <mtca4u/BackendFactory.h>
#include "Application.h"
#include "ApplicationModule.h"
#include "Accessor.h"
#include "DeviceAccessor.h"
#include "FanOut.h"
#include "FeedingFanOut.h"
#include "VariableNetworkNode.h"
#include "ScalarAccessor.h"
#include "ArrayAccessor.h"
#include "ConstantAccessor.h"
using namespace ChimeraTK;
/*********************************************************************************************************************/
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() {
for(auto &module : overallModuleList) {
for(auto &accessor : module->getAccessorList()) {
if(!accessor->getNode().hasOwner()) {
std::cerr << "*** Warning: Variable '" << accessor->getName() << "' is not connected. "
"Reading will always result in 0, writing will be ignored." << std::endl;
networkList.emplace_back();
networkList.back().addNode(*accessor);
bool makeFeeder = !(networkList.back().hasFeedingNode());
size_t length = accessor->getNumberOfElements();
if(accessor->getNode().getValueType() == typeid(int8_t)) {
constantList.emplace_back(VariableNetworkNode::makeConstant<int8_t>(makeFeeder, 0, length));
}
else if(accessor->getNode().getValueType() == typeid(uint8_t)) {
constantList.emplace_back(VariableNetworkNode::makeConstant<uint8_t>(makeFeeder, 0, length));
}
else if(accessor->getNode().getValueType() == typeid(int16_t)) {
constantList.emplace_back(VariableNetworkNode::makeConstant<int16_t>(makeFeeder, 0, length));
}
else if(accessor->getNode().getValueType() == typeid(uint16_t)) {
constantList.emplace_back(VariableNetworkNode::makeConstant<uint16_t>(makeFeeder, 0, length));
}
else if(accessor->getNode().getValueType() == typeid(int32_t)) {
constantList.emplace_back(VariableNetworkNode::makeConstant<int32_t>(makeFeeder, 0, length));
}
else if(accessor->getNode().getValueType() == typeid(uint32_t)) {
constantList.emplace_back(VariableNetworkNode::makeConstant<uint32_t>(makeFeeder, 0, length));
}
else if(accessor->getNode().getValueType() == typeid(float)) {
constantList.emplace_back(VariableNetworkNode::makeConstant<float>(makeFeeder, 0, length));
}
else if(accessor->getNode().getValueType() == typeid(double)) {
constantList.emplace_back(VariableNetworkNode::makeConstant<double>(makeFeeder, 0, length));
}
else {
throw std::invalid_argument("Unknown value type.");
}
networkList.back().addNode(constantList.back());
}
}
}
}
/*********************************************************************************************************************/
void Application::checkConnections() {
// check all networks for validity
for(auto &network : networkList) {
network.check();
}
// check if all accessors are connected
for(auto &module : overallModuleList) {
for(auto &accessor : module->getAccessorList()) {
if(!accessor->getNode().hasOwner()) {
throw std::invalid_argument("The accessor '"+accessor->getName()+"' of the module '"+module->getName()+
"' was not connected!");
}
}
}
}
/*********************************************************************************************************************/
void Application::run() {
// check if the application name has been set
if(applicationName == "") {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>(
"Error: An instance of Application must have its applicationName set.");
}
// start the necessary threads for the FanOuts etc.
for(auto &internalModule : internalModuleList) {
internalModule->activate();
}
// read all input variables once, to set the startup value e.g. coming from the config file
// (without triggering an action inside the application)
for(auto &module : overallModuleList) {
for(auto &variable : module->getAccessorList()) {
if(variable->getDirection() == VariableDirection::consuming) {
variable->readNonBlocking();
}
}
}
// start the threads for the modules
for(auto &module : overallModuleList) {
module->run();
}
}
/*********************************************************************************************************************/
void Application::shutdown() {
// 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();
}
// next deactivate the modules, as they have running threads inside as well
for(auto &module : overallModuleList) {
module->terminate();
}
ApplicationBase::shutdown();
}
/*********************************************************************************************************************/
void Application::generateXML() {
// define the connections
defineConnections();
// also search for unconnected nodes - this is here only executed to print the warnings
processUnconnectedNodes();
// check if the application name has been set
if(applicationName == "") {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>(
"Error: An instance of Application must have its applicationName set.");
}
// create XML document with root node
xmlpp::Document doc;
xmlpp::Element *rootElement = doc.create_root_node("application", "https://github.com/ChimeraTK/ApplicationCore");
rootElement->set_attribute("name",applicationName);
for(auto &network : networkList) {
// perform checks
network.check();
// create xml code for the feeder (if it is a control system node)
auto feeder = network.getFeedingNode();
feeder.createXML(rootElement);
// create xml code for the consumers
for(auto &consumer : network.getConsumingNodes()) {
consumer.createXML(rootElement);
}
}
doc.write_to_file_formatted(applicationName+".xml");
}
/*********************************************************************************************************************/
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
// 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!");
}
// if both nodes already have an owner, we are done
if(a.hasOwner() && b.hasOwner()) {
assert( &(a.getOwner()) == &(b.getOwner()) ); /// @todo TODO merge networks?
}
// add b to the existing network of a
else if(a.hasOwner()) {
a.getOwner().addNode(b);
}
// add a to the existing network of b
else if(b.hasOwner()) {
b.getOwner().addNode(a);
}
// create new network
else {
networkList.emplace_back();
networkList.back().addNode(a);
networkList.back().addNode(b);
}
return a.getOwner();
}
/*********************************************************************************************************************/
template<typename UserType>
boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> Application::createDeviceVariable(const std::string &deviceAlias,
const std::string ®isterName, VariableDirection direction, UpdateMode mode, size_t nElements) {
// open device if needed
if(deviceMap.count(deviceAlias) == 0) {
deviceMap[deviceAlias] = mtca4u::BackendFactory::getInstance().createBackend(deviceAlias);
deviceMap[deviceAlias]->open();
}
// use wait_for_new_data mode if push update mode was requested
mtca4u::AccessModeFlags flags{};
if(mode == UpdateMode::push && direction == VariableDirection::consuming) flags = {AccessMode::wait_for_new_data};
// create DeviceAccessor for the proper UserType
boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> impl;
auto regacc = deviceMap[deviceAlias]->getRegisterAccessor<UserType>(registerName, nElements, 0, flags);
impl.reset(new DeviceAccessor<UserType>(regacc, direction, mode));
return impl;
}
/*********************************************************************************************************************/
template<typename UserType>
boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> Application::createProcessVariable(VariableNetworkNode const &node) {
// determine the SynchronizationDirection
SynchronizationDirection dir;
if(node.getDirection() == VariableDirection::feeding) {
dir = SynchronizationDirection::controlSystemToDevice;
}
else {
dir = SynchronizationDirection::deviceToControlSystem;
}
// create the ProcessScalar for the proper UserType
return _processVariableManager->createProcessArray<UserType>(dir, node.getPublicName(), node.getNumberOfElements(),
node.getOwner().getUnit(), node.getOwner().getDescription());
}
/*********************************************************************************************************************/
template<typename UserType>
std::pair< boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>>, boost::shared_ptr<mtca4u::NDRegisterAccessor<UserType>> >
Application::createApplicationVariable(size_t nElements, const std::string &name) {
// create the ProcessScalar for the proper UserType
return createSynchronizedProcessArray<UserType>(nElements, name);
}
/*********************************************************************************************************************/
void Application::makeConnections() {
// run checks first
checkConnections();
// make the connections for all networks
for(auto &network : networkList) {
makeConnectionsForNetwork(network);
}
}
/*********************************************************************************************************************/
void Application::dumpConnections() {
std::cout << "==== List of all variable connections of the current Application ====" << std::endl;
for(auto &network : networkList) {
network.dump();
}
std::cout << "=====================================================================" << std::endl;
}
/*********************************************************************************************************************/
void Application::makeConnectionsForNetwork(VariableNetwork &network) {
// if the network has been created already, do nothing
if(network.isCreated()) return;
// if the trigger type is external, create the trigger first
if(network.getFeedingNode().hasExternalTrigger()) {
VariableNetwork &dependency = network.getFeedingNode().getExternalTrigger().getOwner();
if(!dependency.isCreated()) makeConnectionsForNetwork(dependency);
}
// 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);
}
// mark the network as created
network.markCreated();
}
/*********************************************************************************************************************/
template<typename UserType>
void Application::typedMakeConnection(VariableNetwork &network) {
bool connectionMade = false; // to check the logic...
size_t nNodes = network.countConsumingNodes()+1;
auto feeder = network.getFeedingNode();
auto consumers = network.getConsumingNodes();
bool useExternalTrigger = network.getTriggerType() == VariableNetwork::TriggerType::external;
bool useFeederTrigger = network.getTriggerType() == VariableNetwork::TriggerType::feeder;
// 1st case: the feeder requires a fixed implementation
if(feeder.hasImplementation()) {
// 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;
if(feeder.getType() == NodeType::Device) {
feedingImpl = createDeviceVariable<UserType>(feeder.getDeviceAlias(), feeder.getRegisterName(),
VariableDirection::consuming, feeder.getMode(), feeder.getNumberOfElements());
}
else if(feeder.getType() == NodeType::ControlSystem) {
feedingImpl = createProcessVariable<UserType>(feeder);
}
else if(feeder.getType() == NodeType::Constant) {
feedingImpl = boost::dynamic_pointer_cast<mtca4u::NDRegisterAccessor<UserType>>(feeder.getConstAccessor());
assert(feedingImpl != nullptr);
}
else {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
// if we just have two nodes, directly connect them
if(nNodes == 2 && !useExternalTrigger) {
auto consumer = consumers.front();
if(consumer.getType() == NodeType::Application) {
consumer.getAppAccessor().useProcessVariable(feedingImpl);
connectionMade = true;
}
else if(consumer.getType() == NodeType::Device) {
auto consumingImpl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
VariableDirection::feeding, consumer.getMode(), consumer.getNumberOfElements());
// connect the Device with e.g. a ControlSystem node via a threaded FanOut
auto fanOut = boost::make_shared<FanOut<UserType>>(feedingImpl);
fanOut->addSlave(consumingImpl);
internalModuleList.push_back(fanOut);
connectionMade = true;
}
else if(consumer.getType() == NodeType::ControlSystem) {
auto consumingImpl = createProcessVariable<UserType>(consumer);
// connect the ControlSystem with e.g. a Device node via an threaded FanOut
auto fanOut = boost::make_shared<FanOut<UserType>>(feedingImpl);
fanOut->addSlave(consumingImpl);
internalModuleList.push_back(fanOut);
connectionMade = true;
}
else if(consumer.getType() == NodeType::TriggerReceiver) {
consumer.getTriggerReceiver().getOwner().setExternalTriggerImpl(feedingImpl);
connectionMade = true;
}
else {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
}
else { /* !(nNodes == 2 && !useExternalTrigger) */
// flag needed when the ConsumerFanOut is used
bool mayUseAsImplementation = false;
// create the right FanOut type
boost::shared_ptr<FanOut<UserType>> fanOut;
if(useExternalTrigger) {
// if external trigger is enabled, use externally triggered threaded FanOut
fanOut = boost::make_shared<FanOut<UserType>>(feedingImpl);
fanOut->addExternalTrigger(network.getExternalTriggerImpl());
internalModuleList.push_back(fanOut);
}
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.
fanOut = boost::make_shared<FanOut<UserType>>(feedingImpl);
internalModuleList.push_back(fanOut);
}
else {
if(!network.hasApplicationConsumer()) {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("No application node in the network but no trigger!");
}
mayUseAsImplementation = true;
fanOut = boost::make_shared<FanOut<UserType>>(feedingImpl);
}
// add all consumers to the FanOut
for(auto &consumer : consumers) {
if(consumer.getType() == NodeType::Application) {
if(mayUseAsImplementation) {
consumer.getAppAccessor().useProcessVariable(fanOut);
mayUseAsImplementation = false;
}
else {
auto impls = createApplicationVariable<UserType>(consumer.getNumberOfElements(), consumer.getAppAccessor().getName());
fanOut->addSlave(impls.first);
consumer.getAppAccessor().useProcessVariable(impls.second);
}
}
else if(consumer.getType() == NodeType::ControlSystem) {
auto impl = createProcessVariable<UserType>(consumer);
fanOut->addSlave(impl);
}
else if(consumer.getType() == NodeType::Device) {
auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
VariableDirection::feeding, consumer.getMode(), consumer.getNumberOfElements());
fanOut->addSlave(impl);
}
else if(consumer.getType() == NodeType::TriggerReceiver) {
auto impls = createApplicationVariable<UserType>(consumer.getNumberOfElements(), consumer.getAppAccessor().getName());
fanOut->addSlave(impls.first);
consumer.getTriggerReceiver().getOwner().setExternalTriggerImpl(impls.second);
}
else {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
}
connectionMade = true;
}
}
// 2nd case: the feeder does not require a fixed implementation
else { /* !feeder.hasImplementation() */
// we should be left with an application feeder node
if(feeder.getType() != NodeType::Application) {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
assert(!useExternalTrigger);
// if we just have two nodes, directly connect them
if(nNodes == 2) {
auto consumer = consumers.front();
if(consumer.getType() == NodeType::Application) {
auto impls = createApplicationVariable<UserType>(consumer.getNumberOfElements(), feeder.getAppAccessor().getName());
feeder.getAppAccessor().useProcessVariable(impls.first);
consumer.getAppAccessor().useProcessVariable(impls.second);
connectionMade = true;
}
else if(consumer.getType() == NodeType::ControlSystem) {
auto impl = createProcessVariable<UserType>(consumer);
feeder.getAppAccessor().useProcessVariable(impl);
connectionMade = true;
}
else if(consumer.getType() == NodeType::Device) {
auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
VariableDirection::feeding, consumer.getMode(), consumer.getNumberOfElements());
feeder.getAppAccessor().useProcessVariable(impl);
connectionMade = true;
}
else if(consumer.getType() == NodeType::TriggerReceiver) {
auto impls = createApplicationVariable<UserType>(consumer.getNumberOfElements(), feeder.getAppAccessor().getName());
feeder.getAppAccessor().useProcessVariable(impls.first);
consumer.getTriggerReceiver().getOwner().setExternalTriggerImpl(impls.second);
connectionMade = true;
}
else if(consumer.getType() == NodeType::Constant) {
auto impl = consumer.getConstAccessor();
feeder.getAppAccessor().useProcessVariable(impl);
connectionMade = true;
}
else {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
}
else {
// create FanOut and use it as the feeder implementation
auto fanOut = boost::make_shared<FeedingFanOut<UserType>>();
feeder.getAppAccessor().useProcessVariable(fanOut);
for(auto &consumer : consumers) {
if(consumer.getType() == NodeType::Application) {
auto impls = createApplicationVariable<UserType>(consumer.getNumberOfElements(), feeder.getAppAccessor().getName());
fanOut->addSlave(impls.first);
consumer.getAppAccessor().useProcessVariable(impls.second);
}
else if(consumer.getType() == NodeType::ControlSystem) {
auto impl = createProcessVariable<UserType>(consumer);
fanOut->addSlave(impl);
}
else if(consumer.getType() == NodeType::Device) {
auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(),
VariableDirection::feeding, consumer.getMode(), consumer.getNumberOfElements());
fanOut->addSlave(impl);
}
else if(consumer.getType() == NodeType::TriggerReceiver) {
auto impls = createApplicationVariable<UserType>(consumer.getNumberOfElements(), feeder.getAppAccessor().getName());
fanOut->addSlave(impls.first);
consumer.getTriggerReceiver().getOwner().setExternalTriggerImpl(impls.second);
}
else {
throw ApplicationExceptionWithID<ApplicationExceptionID::illegalParameter>("Unexpected node type!");
}
}
connectionMade = true;
}
}
if(!connectionMade) {
throw ApplicationExceptionWithID<ApplicationExceptionID::notYetImplemented>(
"The variable network cannot be handled. Implementation missing!");
}
}
/*********************************************************************************************************************/
VariableNetwork& Application::createNetwork() {
networkList.emplace_back();
return networkList.back();
}
/*********************************************************************************************************************/
Application& Application::getInstance() {
return dynamic_cast<Application&>(ApplicationBase::getInstance());
}