ChimeraTK-ApplicationCore  04.01.00
Example: Basic structure of an Application

This example shows the basic structure of an Application written with ApplicationCore. The full example project can be found in the "example" subdirectory. Please refer to the Conceptual overview for detail explanation.

The directory structure looks like this:

  • CMakeLists.txt - cmake project configuration
  • include/... - Header files for the ApplicationModule and the Application implementations
  • src/... - Source files for the ApplicationModule and the Application implementations
  • src_factory/... - Source file for creating the instance of the ApplicationFactory
  • config/... - Configuration files needed for testing and demo execution
  • cmake/... - These files are coming from the project template and must not be altered
  • All other files in project root - These files are coming from the project template and must not be altered

include

include/SetpointRamp.h

// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
/*
* This example is explained as part of the \ref conceptualOverview. Please refere there for step-by-step explanations.
* Reading the full example might not be a good starting point for learning ApplicationCore as it can be overwelming
* and lacks important background information.
*
* Please ignore all comments of the format "//! [some name]", those are used for Doxygen to include code snippets in
* the documentation pages.
*/
#include <ChimeraTK/ApplicationCore/ApplicationCore.h>
namespace ctk = ChimeraTK;
ctk::ScalarPollInput<float> operatorSetpoint{this, "operatorSetpoint", "degC", "..."};
struct ControllerInterface : ctk::VariableGroup {
ctk::ScalarOutput<float> actualSetpoint{this, "temperatureSetpoint", "degC", "..."};
};
ControllerInterface ctrl{this, "/ControlUnit/Controller", ""};
ctk::ScalarPushInput<uint64_t> trigger{this, "/Timer/tick", "", "..."};
void mainLoop() override;
};

include/AverageCurrent.h

// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
/*
* This example is explained as part of the \ref conceptualOverview. Please refere there for step-by-step explanations.
* Reading the full example might not be a good starting point for learning ApplicationCore as it can be overwelming
* and lacks important background information.
*
* Please ignore all comments of the format "//! [some name]", those are used for Doxygen to include code snippets in
* the documentation pages.
*/
#include <ChimeraTK/ApplicationCore/ApplicationCore.h>
namespace ctk = ChimeraTK;
// Take the heaterCurrent from the Controller module as an input
ctk::ScalarPushInput<float> current{this, "../Controller/heatingCurrent", "mA", "Actuator output of the controller"};
ctk::ScalarOutput<float> currentAveraged{this, "heatingCurrentAveraged", "mA", "Averaged heating current"};
void mainLoop() override;
};

include/Controller.h

// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
/*
* This example is explained as part of the \ref conceptualOverview. Please refere there for step-by-step explanations.
* Reading the full example might not be a good starting point for learning ApplicationCore as it can be overwelming
* and lacks important background information.
*
* Please ignore all comments of the format "//! [some name]", those are used for Doxygen to include code snippets in
* the documentation pages.
*/
#include <ChimeraTK/ApplicationCore/ApplicationCore.h>
namespace ctk = ChimeraTK;
ctk::ScalarPollInput<float> temperatureSetpoint{
this, "temperatureSetpoint", "degC", "Setpoint for the temperature controller"};
ctk::ScalarPushInput<float> temperatureReadback{
this, "temperatureReadback", "degC", "Actual temperature used as controller input"};
ctk::ScalarOutput<float> heatingCurrent{this, "heatingCurrent", "mA", "Actuator output of the controller"};
void mainLoop() override;
};

include/ExampleApp.h

// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
/*
* This example is explained as part of the \ref conceptualOverview. Please refere there for step-by-step explanations.
* Reading the full example might not be a good starting point for learning ApplicationCore as it can be overwelming
* and lacks important background information.
*
* Please ignore all comments of the format "//! [some name]", those are used for Doxygen to include code snippets in
* the documentation pages.
*/
#include "AverageCurrent.h"
#include "Controller.h"
#include "SetpointRamp.h"
#include <ChimeraTK/ApplicationCore/ApplicationCore.h>
#include <ChimeraTK/ApplicationCore/ConfigReader.h>
#include <ChimeraTK/ApplicationCore/PeriodicTrigger.h>
#include <ChimeraTK/ApplicationCore/ScriptedInitialisationHandler.h>
#include <ChimeraTK/ApplicationCore/VersionInfoProvider.h>
namespace ctk = ChimeraTK;
class ExampleApp : public ctk::Application {
public:
~ExampleApp() override;
private:
// Set the name of the DMAP file to define the devices. Must be done before instantiating any DeviceModule.
// Using the application name as a base helps for automated testing against different config files.
ctk::SetDMapFilePath dmapPath{getName() + ".dmap"};
// Provide version information from the `CMakeLists.txt` as process variables
// Apart from the line below and the inclusion of the
// `#include <ChimeraTK/ApplicationCore/VersionInfoProvider.h>` line,
// The server is also expected to have a module named "Application"
// with a variable named "configPatchVersion" of type "int32" in its "config.xml" file.
// Periodic trigger used to readout data from the device periodically.
ctk::PeriodicTrigger timer{this, "Timer", "Periodic timer for the controller"};
// Publish the content of the device "oven" defined in the DMAP file to the control system and to the application
// modules. The "tick" output of the PeriodicTimer "Timer" defined above is used as a readout trigger (for all
// poll-typed device registers).
ctk::DeviceModule oven{this, "oven", "/Timer/tick"};
// Initialisation handler: execute Python script to initialise oven device
ctk::ScriptedInitHandler ovenInit{this, "ovenInit", "Initialisation of oven device", "./ovenInit.py", oven};
struct ControlUnit : ctk::ModuleGroup {
// Instantiate the temperature controller module
Controller controller{this, "Controller", "The temperature controller"};
// Instantiate the heater current averaging module
AverageCurrent averageCurrent{this, "AverageCurrent", "Provide averaged heater current"};
};
ControlUnit controlUnit{this, "ControlUnit", "Unit for controlling the oven temperature"};
// Optionally instantiate the automated setpoint ramping module
SetpointRamp ramp{getConfigReader().get<ChimeraTK::Boolean>("Configuration/enableSetpointRamping") ?
SetpointRamp(this, "SetpointRamp", "Slow ramping of temperator setpoint") :
};

src

src/AutomSetpointRampation.cc

// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
/*
* This example is explained as part of the \ref conceptualOverview. Please refere there for step-by-step explanations.
* Reading the full example might not be a good starting point for learning ApplicationCore as it can be overwelming
* and lacks important background information.
*
* Please ignore all comments of the format "//! [some name]", those are used for Doxygen to include code snippets in
* the documentation pages.
*/
#include "SetpointRamp.h"
/*********************************************************************************************************************/
void SetpointRamp::mainLoop() {
const float maxStep = 0.1F;
while(true) {
readAll(); // waits until trigger received, then read operatorSetpoint
ctrl.actualSetpoint += std::max(std::min(operatorSetpoint - ctrl.actualSetpoint, maxStep), -maxStep);
}
}
/*********************************************************************************************************************/

src/AverageCurrent.cc

// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "AverageCurrent.h"
/*
* This example is explained as part of the \ref conceptualOverview. Please refere there for step-by-step explanations.
* Reading the full example might not be a good starting point for learning ApplicationCore as it can be overwelming
* and lacks important background information.
*
* Please ignore all comments of the format "//! [some name]", those are used for Doxygen to include code snippets in
* the documentation pages.
*/
/*********************************************************************************************************************/
void AverageCurrent::mainLoop() {
const float coeff = 0.1;
currentAveraged.setAndWrite(current); // initialise currentAveraged with initial value
while(true) {
current.read();
// Often, it can be considered a good practise to only write values if they have actually changed. This will
// prevent subsequent computations from running unneccessarily. On the other hand, it may prevent receivers from
// getting a consistent "snapshot" for each trigger. This has to be decided case by case.
currentAveraged.writeIfDifferent((1 - coeff) * currentAveraged + coeff * current);
}
}
/*********************************************************************************************************************/

src/AutomatiControlleron.cc

// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "Controller.h"
/*
* This example is explained as part of the \ref conceptualOverview. Please refere there for step-by-step explanations.
* Reading the full example might not be a good starting point for learning ApplicationCore as it can be overwelming
* and lacks important background information.
*
* Please ignore all comments of the format "//! [some name]", those are used for Doxygen to include code snippets in
* the documentation pages.
*/
/*********************************************************************************************************************/
void Controller::mainLoop() {
const float gain = 100.0F;
while(true) {
heatingCurrent = gain * (temperatureSetpoint - temperatureReadback);
writeAll(); // writes any outputs
readAll(); // waits until temperatureReadback updated, then reads temperatureSetpoint
}
}
/*********************************************************************************************************************/

src/ExampleApp.cc

// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
/*
* This example is explained as part of the \ref conceptualOverview. Please refere there for step-by-step explanations.
* Reading the full example might not be a good starting point for learning ApplicationCore as it can be overwelming
* and lacks important background information.
*
* Please ignore all comments of the format "//! [some name]", those are used for Doxygen to include code snippets in
* the documentation pages.
*/
#include "ExampleApp.h"
/*********************************************************************************************************************/
}
/*********************************************************************************************************************/

src_factory

// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
/*
* This example is explained as part of the \ref conceptualOverview. Please refere there for step-by-step explanations.
* Reading the full example might not be a good starting point for learning ApplicationCore as it can be overwelming
* and lacks important background information.
*
* Please ignore all comments of the format "//! [some name]", those are used for Doxygen to include code snippets in
* the documentation pages.
*/
#include "ExampleApp.h"
#include <ChimeraTK/ApplicationCore/EnableXMLGenerator.h>
/*********************************************************************************************************************/
static ctk::ApplicationFactory<ExampleApp> appFactory("demo_example");
/*********************************************************************************************************************/

config

<configuration>
<module name="Configuration">
<variable name="enableSetpointRamping" type="boolean" value="false"/>
<variable name="heaterMode" type="uint8" value="2"/>
</module>
<!-- this defines user-defined python application modules to load at server start.
List all your python modules as <module>. In our case, userAppModules.py defines a drop-in replacement for SetpointRamp -->
<module name="PythonModules">
<module name="UserAppModules">
<variable name="path" type="string" value="userAppModules" />
</module>
</module>
<module name="Information">
<variable name="ovenName" type="string" value="Cookie Oven 42"/>
</module>
<module name="Timer">
<variable name="period" type="uint32" value="500"/>
</module>
<module name="Application">
<!--
The value of configPatchVersion is used by the VersionInfoProvider module.
67 is only used as an example and should be changed when used as a template.
-->
<variable name="configPatchVersion" type="int32" value="67"/>
</module>
</configuration>

<?xml version="1.0" encoding="UTF-8"?>
<device_server xmlns="https://github.com/ChimeraTK/ControlSystemAdapter-DoocsAdapter">
<location name="DEMO">
<import>/</import>
</location>
</device_server>

config/demo_example.conf

# Conf file created at 17:08.38 13. Jun. 2016
# eq_fct_type's are defined in eq_fct_code.h
eq_conf:
oper_uid: -1
oper_gid: 422
xpert_uid: 0
xpert_gid: 0
ring_buffer: 10000
memory_buffer: 500
eq_fct_name: "NODENAME._SVR"
eq_fct_type: 1
{
SVR.NAME: "NODENAME._SVR"
STS: 0x1c
STS.ERROR: 0
STS.NEWERROR: 1
STS.ERRORMASK: 1
STS.ONLINE: 1
ERROR.STR: 0 0 0 1078761493 ""
SYS_MASK: 1
FCT_CODE: 1
FCT_PANEL: ""
X_POS: 0
Z_POS: 0
Z_POS.STRING: ""
DEVICE.INFO: 235672 0 0 0 "Device OK"
MESSAGE: ""
LAST_UPDATE: 1112798768 350 0 0
LAST_USR1: 0 0 0 0
SVR.ALIAS: 0
SVR.ARCFLUSH: 0
SVR.ARCFLUSH_B: 0
SVR.UPDATE: 1465830518
SVR.RATE: 1 0 0 0
SVR.RESIZE: 200
SVR.FILE: "demoApp.conf"
SVR.DESC: ""
SVR.PROGRAMMER: "mhier"
SVR.XMLFILE: ""
SVR.ERRORLOG: "/doocs/nodename/server/InstaCoSADevExample_server/InstaCoSADevExample_server.log"
SVR.STORE.RATE: 10
SVR.STORE.AUTO: 4
SVR.HOST_NAME: "mskpcx19821"
SVR.PROCESSNAME: "demoApp"
SVR.RPC_NUMBER: 610498009
SVR.STARTTIME: 1465830430
SVR.LIBINFO: "18.10.6"
SVR.LIBDATE: "Jun 2 13:38"
SVR.WDADDR: ""
SVR.CONTR: 0x0
SVR.MUST_RUN: 0
SVR.STOP_SVR: 0
SVR.START_CMD: ""
SVR.RPC_CALL_TIME.COMMENT: "Time per Call"
SVR.RPC_CALL_TIME.EGU: 1 1 100000 0 "rate"
SVR.RPC_CALL_TIME.XEGU: 0 0 100 0 "ms"
SVR.UPDATE_TIME.COMMENT: "Time per Update"
SVR.UPDATE_TIME.EGU: 1 1 100000 0 "rate"
SVR.UPDATE_TIME.XEGU: 0 0 100 0 "ms"
SVR.USR1_TIME.COMMENT: "run time of SIGUSR1"
SVR.USR1_TIME.EGU: 1 1 1e+06 1427728880 "counts"
SVR.USR1_TIME.XEGU: 0 0 100 1427728880 "ms"
SVR.USR1_PERIOD.COMMENT: "time between SIGUSR1"
SVR.USR1_PERIOD.EGU: 1 1 1e+06 1427728880 "counts"
SVR.USR1_PERIOD.XEGU: 0 0 500 1427728880 "ms"
SVR.ARCH_GET_TIME.COMMENT: "time per archiver get"
SVR.ARCH_GET_TIME.EGU: 1 1 1e+06 1459338630 "counts"
SVR.ARCH_GET_TIME.XEGU: 0 0 100 1459338630 "ms"
SVR.LAFL: 0
SVR.ERROR_COUNT: 0
DEVICE.ONLINE: 1
DEVICE.OFFLINE: 0
SVR.DEVMAX: 0
SVR.TINERUN: 0
SVR.TINEVERS: "4.05.0009"
SVR.TINEPREF: ""
SVR.TINESUFF: ""
SVR.TINE_DBG: 0
SVR.TINE_LOG: 0
SVR.TINE_FEC: 0 0 0 0 ""
SVR.TINE_PORT: 0
SVR.TINE_MTU: 1472
SVR.TINE_CTSZ: 32
SVR.TINE_MCTTL: 16
SVR.TINE_BLIM: 1000
SVR.TINE_CDLY: 20
SVR.TINE_GROUP: 0
SVR.FACILITY: "TEST.DOOCS"
SVR.DEVICE: "InstaCoSADevExample"
T_ZERO: 700
SVR.BPN: 0
SVR.SPR: 0
}
#No need to put application locations if you don't need variables content as persistency layer.
#They will be auto-generated.

config/demo_example.dmap

device (sharedMemoryDummy:0?map=DemoDummy.map)
oven (logicalNameMap?map=oven.xlmap)

config/DemoDummy.map

# name nr of elements address size bar width fracbits signed R/W
HEATER.MODE 1 0 4 0 2 0 0 RW
HEATER.CURRENT_SET 1 4 4 0 32 16 0 RW
HEATER.CURRENT_READBACK 1 8 4 0 32 16 0 RO
SENSORS.TEMPERATURE1 1 16 4 0 32 16 0 RO
SENSORS.TEMPERATURE2 1 20 4 0 32 16 0 RO
SENSORS.TEMPERATURE3 1 24 4 0 32 16 0 RO
SENSORS.TEMPERATURE4 1 28 4 0 32 16 0 RO
BOARD.RESET_N 1 32 4 0 1 0 0 RW
BOARD.GPIO_OUT0 1 36 4 0 1 0 0 RW

config/oven.xlmap

<logicalNameMap>
<module name="Configuration">
<redirectedRegister name="heaterMode">
<targetDevice>device</targetDevice>
<targetRegister>HEATER.MODE</targetRegister>
</redirectedRegister>
<redirectedRegister name="lightOn">
<targetDevice>device</targetDevice>
<targetRegister>BOARD.GPIO_OUT0</targetRegister>
</redirectedRegister>
</module>
<module name="ControlUnit">
<module name="Controller">
<redirectedRegister name="heatingCurrent">
<targetDevice>device</targetDevice>
<targetRegister>HEATER.CURRENT_SET</targetRegister>
</redirectedRegister>
<redirectedRegister name="temperatureReadback">
<targetDevice>device</targetDevice>
<targetRegister>SENSORS.TEMPERATURE1</targetRegister>
</redirectedRegister>
</module>
</module>
<module name="Monitoring">
<redirectedRegister name="heatingCurrent">
<targetDevice>device</targetDevice>
<targetRegister>HEATER.CURRENT_READBACK</targetRegister>
</redirectedRegister>
<redirectedRegister name="temperatureOvenTop">
<targetDevice>device</targetDevice>
<targetRegister>SENSORS.TEMPERATURE2</targetRegister>
</redirectedRegister>
<redirectedRegister name="temperatureOvenBottom">
<targetDevice>device</targetDevice>
<targetRegister>SENSORS.TEMPERATURE3</targetRegister>
</redirectedRegister>
<redirectedRegister name="temperatureOutside">
<targetDevice>device</targetDevice>
<targetRegister>SENSORS.TEMPERATURE4</targetRegister>
</redirectedRegister>
</module>
</logicalNameMap>
ChimeraTK::VersionInfoProvider
This module can be added to applications to provide version information from the CMakeLists....
Definition: VersionInfoProvider.h:23
ChimeraTK::ConfigReader::get
const T & get(const std::string &variableName) const
Get value for given configuration variable.
Definition: ConfigReader.h:248
AverageCurrent
[Snippet: Class Definition]
Definition: AverageCurrent.h:19
ChimeraTK::Application::shutdown
void shutdown() override
This will remove the global pointer to the instance and allows creating another instance afterwards.
Definition: Application.cc:207
ExampleApp::~ExampleApp
~ExampleApp() override
[Snippet: Destructor]
Definition: ExampleApp.cc:18
ChimeraTK::ScalarPushInput< uint64_t >
ChimeraTK::ScriptedInitHandler
Initialisation handler which calls an external application (usually a script), captures its output (b...
Definition: ScriptedInitialisationHandler.h:34
ExampleApp
[Snippet: Class Definition Start]
Definition: ExampleApp.h:27
ChimeraTK::ModuleGroup
Definition: ModuleGroup.h:16
Controller.h
ChimeraTK::ModuleGroup::ModuleGroup
ModuleGroup()=default
Default constructor to allow late initialisation of module groups.
ChimeraTK::DeviceModule
Definition: DeviceModule.h:20
ChimeraTK::VariableGroup::VariableGroup
VariableGroup()=default
Default constructor: Allows late initialisation of VariableGroups (e.g.
ExampleApp.h
ChimeraTK::Application::getConfigReader
ConfigReader & getConfigReader()
Definition: Application.h:189
Controller
[Snippet: Class Definition]
Definition: Controller.h:19
ChimeraTK::ApplicationModule
Definition: ApplicationModule.h:24
ChimeraTK::SetDMapFilePath
Helper class to set the DMAP file path.
Definition: DeviceModule.h:94
ChimeraTK::VariableGroup
Definition: VariableGroup.h:22
AverageCurrent.h
ChimeraTK::ScalarAccessor::writeIfDifferent
void writeIfDifferent(UserType newValue, VersionNumber versionNumber, DataValidity validity)=delete
ovenInit
Definition: ovenInit.py:1
ChimeraTK::ScalarAccessor::setAndWrite
void setAndWrite(UserType newValue, VersionNumber versionNumber)=delete
ChimeraTK::Module::readAll
void readAll(bool includeReturnChannels=false)
Read all readable variables in the group.
Definition: Module.cc:72
SetpointRamp.h
ChimeraTK::ModuleGroup::Application
friend class Application
Definition: ModuleGroup.h:47
ChimeraTK::ScalarPollInput< float >
ChimeraTK::Module::writeAll
void writeAll(bool includeReturnChannels=false)
Just call write() on all writable variables in the group.
Definition: Module.cc:157
SetpointRamp
Definition: SetpointRamp.h:18
ChimeraTK::ApplicationModule::ApplicationModule
ApplicationModule()=default
Default constructor: Allows late initialisation of modules (e.g.
ChimeraTK
InvalidityTracer application module.
Definition: spec_dataValidityPropagation.dox:2
ChimeraTK::EntityOwner::getName
const std::string & getName() const
Get the name of the module instance.
Definition: EntityOwner.h:59
ChimeraTK::ScalarOutput< float >
ChimeraTK::Application
Definition: Application.h:48
ChimeraTK::PeriodicTrigger
Simple periodic trigger that fires a variable once per second.
Definition: PeriodicTrigger.h:16