FESA C++ Code Snippets
- Working with devices -
Working with devices in the server-part
// Since server actions are only executed for a specific device,
// we directly can use the argument "pDev", which is passed to the execute-method
pDev->myField.set(someValue,pEvt->getMultiplexingContext());
// In Server-Actions the global-device can be accessed by using the service-locator
this->MyClassServiceLocator_->getGlobalDevice()->myField.set(someValue,pEvt->getMultiplexingContext());
For the complete API of the class "device" and it's relatives, check the
doxygen documentation.
Working with devices in the RT-part
// In a RT-Action we can iterate over the whole device-collection like that:
std::vector<Device*> devCol = this->MyClassServiceLocator_->getDeviceCollection();
std::vector<Device*>::iterator device;
for(device=devCol.begin();device!=devCol.end();++device)
{
// perform some action with the device
(*device)->myField.set(someValue,pEvt->getMultiplexingContext());
}
// In RT-Action global-fields can be accessed by using the service-locator
this->MyClassServiceLocator_->getGlobalDevice()->myField.set(someValue,pEvt->getMultiplexingContext());
For the complete API of the class "device" and it's relatives, check the
doxygen documentation.
Working with devices in the specificInit methods
#include <fesa-core/Synchronization/NoneContext.h>
// Since an empty context is needed to access any field, you can make use of the internal FESA type "NoneContext"
fesa::NoneContext context;
// Like in the RT-Action in the method "specificInit" of the RT/Server-DeviceClass the service-locator can be used to access the different devices
std::vector<Device*> devCol = this->MyClassServiceLocator_->getDeviceCollection();
std::vector<Device*>::iterator device;
for(device=devCol.begin();device!=devCol.end();++device)
{
// perform some action with the device
std::cout << "DeviceName: " << (*device)->getName() << std::endl;
(*device)->myField.set(someValue,&context);
}
// Accordingly the global-device can be accessed
this->MyClassServiceLocator_->getGlobalDevice()->myField.set(someValue,&context);
Working with devices of a composed class
In this code-snipped the current class defines the class "Slave" as composed class in relationship/composition. E.g. the field "slaveField" can be accessed like that:
std::vector<Slave::Device*> slaveDevCol = this->SlaveServiceLocator_->getDeviceCollection();
std::vector<Slave::Device*>::iterator slaveDevice;
for(slaveDevice=slaveDevCol.begin();slaveDevice!=slaveDevCol.end();++slaveDevice)
{
(*slaveDevice)->slaveField.set(someValue,pEvt->getMultiplexingContext());
}
Accordingly the global-device of "Slave" can be accessed:
Slave::GlobalDevice* slaveGlobalDevice = this->SlaveServiceLocator_->getGlobalDevice();
slaveGlobalDevice->slaveGlobalField.set(someValue,&context);
- Working with fields -
Working with FESA scalar-fields
int32_t myData = (*device)->myField.get(pEvt->getMultiplexingContext());
myData ++;
(*device)->myField.set(myData,pEvt->getMultiplexingContext());
For the complete API of scalar-fields and its relatives, check the
doxygen documentation.
Working with FESA array-fields
uint32_t index = 1;
//Get a cell-value
const int32_t myData = (*device)->myArrayField.getCell(index,pEvt->getMultiplexingContext());
//Set a cell-value
int32_t myNewData= 123;
(*device)->myArrayField.setCell(myNewData,index,pEvt->getMultiplexingContext());
// Set data to a array-field:
uint32_t sizeOfmyData = 4;
int32_t myData[sizeOfmyData];
myData[1] = 15;
(*device)->myArrayField.set(myData,sizeOfmyData,pEvt->getMultiplexingContext());
// Get data from a array-field:
uint32_t dim;
const int32_t* myData = (*device)->myArrayField.get(dim,pEvt->getMultiplexingContext());
// Get the size of the current array:
uint32_t size = (*device)->myArrayField.getSize(pEvt->getMultiplexingContext());
// Get the maximum possible size of the array:
uint32_t maxSize = (*device)->myArrayField.getMaxSize();
// Set the size of the current array ( Needed when a smaller array is copied to the field! The maximum size cannot be exceeded.)
std::size_t newSize = 123;
(*device)->myArrayField.setSize(newSize,pEvt->getMultiplexingContext());
For the complete API of array-fields and it's relatives, check the
doxygen documentation.
Working with FESA 2D-array-fields
uint32_t index1 = 1;
uint32_t index2 = 1;
uint32_t dim1;
uint32_t dim2;
// Get a cell-value
const int32_t myData = (*device)->my2DArray.getCell(index1,index2,pEvt->getMultiplexingContext());
// Set a cell-value
int32_t myNewData
(*device)->my2DArray.setCell(myNewData,index1,index2,pEvt->getMultiplexingContext());
// Get a row
const int32_t* myData = (*device)->my2DArray.getRow(index1,dim1,pEvt->getMultiplexingContext());
// Get a column
uint32_t tempArraySize = dim2;
double* column = new double[tempArraySize];
(*device)->myField.getColumn(index2, dim2, column, tempArraySize, pEvt->getMultiplexingContext());
// Set a row/column
uint32_t index = 1;
int32_t myDataColumn[dim2];
int32_t myDataRow[dim1];
(*device)->my2DArray.setColumn(myDataColumn,index,dim2,pEvt->getMultiplexingContext());
(*device)->my2DArray.setRow(myDataRow,index,dim1,pEvt->getMultiplexingContext());
// Get the complete array2D ( use the correct type as template argument )
fesa::ImmutableArray2D<double> data = (*device)->myField.get(pEvt->getMultiplexingContext());
std::cout << " data[2][2]: "<< data[2][2] << std::endl;
// Get the current sizes of the array
(*device)->myField.getSize(dim1, dim2, pEvt->getMultiplexingContext());
// Get the maximum sizes of the array
(*device)->myField.getMaxSize(dim1, dim2);
// Set the sizes of the current array ( Needed when a smaller array is copied to the field! The maximum sizes cannot be exceeded.)
std::size_t newSize1 = 12;
std::size_t newSize2 = 34;
(*device)->myField.setSize(newSize1,newSize2,pEvt->getMultiplexingContext());
For the complete API of 2D-array-fields and it's relatives, check the
doxygen documentation.
Working with FESA char-array-fields (strings)
std::string myString;
(*device)->myCharArray.set(myString.c_str(),pEvt->getMultiplexingContext());
const char* myCharArrayPointer = (*device)->myCharArray.get(pEvt->getMultiplexingContext());
For the complete API of string-fields and it's relatives, check the
doxygen documentation.
Working with FESA 2D-char-array-fields (string-arrays)
uint32_t index = 0;
std::string myString = "MyString";
(*device)-->myCharArray2D.setString(myString.c_str(), index,pEvt->getMultiplexingContext());
const char* myCharArrayPointer = (*device)->myCharArray2D.getString(index,pEvt->getMultiplexingContext());
uint32_t size;
const char** my2DCharArrayPointer = (*device)->myCharArray2D.get(size,pEvt->getMultiplexingContext());
std::cout << "Number of strings: " << size << std::endl;
std::cout << "First String: " << my2DCharArrayPointer[0] << std::endl;
For the complete API of string-array-fields and it's relatives, check the
doxygen documentation.
Working with variable-sized 2D char arrays
// In the design document you can optionally specify the minimum / maximum values, which can be used by the devices
<value-item name="aVarV" direction="OUT">
<array2D type="char">
<variable-dim1>
<min>0</min>
<max>4000</max>
</variable-dim1>
<custom-constant-dim2 constant-name-ref="MAX_STRING_LENGTH" />
</array2D>
</value-item>
// C++ code:
string aVarTemp[MAX_SIZE]; // declaration of array
// setting part of the array
for(int i=0; i < some_size; i++) // some_size <= MAX_SIZE
aVarTemp[i] = "some string";
// filling variable-sized string array
fesa::NoneContext context;
const std::vector<Device*> devCol =
DeviceFactory::getInstance()->getDeviceCollection();
std::vector<Device*>::const_iterator device; for(device= devCol.begin();device!=devCol.end();++device) {
(*device)->aVarV.set(reinterpret_cast<const char**>(&aVarTemp), some_size, &context);
}
Working with variable-sized arrays
In the design document you can optionally specify the minimum / maximum dim-values, which can be used by the devices
<field multiplexed="false" name="aVarF">
<array type="int16_t">
<variable-dim>
<min>0</min>
<max>20</max>
</variable-dim>
</array>
</field>
In the instance document the concrete array-size is specified.
<device-instance name="TestDevice0">
<configuration>
<description value=""/>
<accelerator value="NONE"/>
<timingDomain value="NONE"/>
<mainMuxCriterion value="NONE"/>
...
</configuration>
<acquisition>
<aVarF>
<dim value="19"/>
</aVarF>
</acquisition>
...
Now the variable-sized array may be filled, e.g.
fesa::NoneContext context;
const std::vector<Device*> devCol = DeviceFactory::getInstance()->getDeviceCollection();
GlobalDevice* globalDev = DeviceFactory::getInstance()->getGlobalDevice();
std::vector<Device*>::const_iterator device;
for(device= devCol.begin();device!=devCol.end();++device)
{
std::cout << "max size = " << (*device)->aVarF.getMaxSize() << std::endl; // this is the value as defined in the instance document
for(int i(0); i<20; i++) {
std::cout << "set var: " << i << std::endl;
(*device)->aVarF.setCell(i, i, &context);
}
}
Working with bit-enum-fields
// generated code in TypeDefinition.h (just an example):
namespace MyBitEnum
{
enum MyBitEnum
{
isActive=1,
isValid=2,
hasPower=4
}; // 3 bits
}
// Access the field in Server/RT-Actions:
int32_t value = MyBitEnum::isActive | MyBitEnum::hasPower;
(*device)->myBitEnumField.set( value, pEvt->getMultiplexingContext() );
int32_t newValue = (*device)->myBitEnumField.get(pEvt->getMultiplexingContext());
Working with hw-address fields
// The concrete values of a hw-address can be set in the instantiation-file for each device
// for a "logical-address"
// The name of the logical address is defined in the class.
// The logicalUnitNumber, the channel and the moduleType can be filled in the instance-file for each device. (channel and moduleType are optional)
int32_t logicalUnitNumber = (*device)->myLogicalHardwareAddress.get()->logicalUnitNumber;
int32_t channel = (*device)->myLogicalHardwareAddress.get()->channel;
const char* moduleType = (*device)->myLogicalHardwareAddress.get()->moduleType;
// for a "host"
// The (alias)name of a required host can be defined in the class
// The concrete name and port usually are device-specific and can be defined per device in the instance-file
int32_t port = (*device)->myRemoteHost.get()->port;
const char* host = (*device)->myRemoteHost.get()->hostName;
Fill the GSI-acquisition-context-field
// easy method (cyclestamp, cylcename and acquisitionstamp are obtained from the MultiplexingContext, if available)
// This method will only work when using the TimingEventSource, since it is the only source which provides interrupt-stamps which can be used as acquisition-stamp
(*device)->acquisitionContext.insert(pEvt->getMultiplexingContext());
// Advanced method ( usage of self-defined acquisition-stamp )
// Stamp must be given in Nanoseconds - UTC
// E.g. if your hardware provides a precise stamp, you can make use of it here.
// You can as well make use of the system-stamp. The following fesa-method already returns the right format:
#include <fesa-core/Synchronization/Timing.h>
....
int64_t stamp = fesa::getSystemTime();
(*device)->acquisitionContext.insert( pEvt->getMultiplexingContext(), stamp );
Report an error by using the GSI-error_collection-field
std::string errorString= "Put in your error-text here";
int32_t error_code= 4711;
(*device)->error_collection.addError(error_code,errorString,pEvt->getMultiplexingContext(),*device);
For the complete API of the class "GSIErrorCollectionField" and it's relatives, check the
doxygen documentation.
Usage of the GSI-detailed-status-field
// You can either directly use an index
(*device)->detailedStatus.lower(0,pEvt->getMultiplexingContext());
(*device)->detailedStatus.raise(1,pEvt->getMultiplexingContext());
// Or make use of the labels which you defined in the configuration field 'detailedStatus_labels'
(*device)->detailedStatus.lower("MyLabelBit4",pEvt->getMultiplexingContext());
(*device)->detailedStatus.raise("MyLabelBit5",pEvt->getMultiplexingContext());
The field directly inherits from the
Array-Field, so you can as well can use all the methods from the baseclass !
Usage of the GSI-module-status-field
// make use of the labels which you defined in the configuration field 'moduleStatus_labels'
// Available values are defined in the custom-type 'MODULE_STATUS'
(*device)->moduleStatus.set("myModule1", MODULE_STATUS::NOT_AVAILABLE,context);
(*device)->moduleStatus.setCell( MODULE_STATUS::OK ,1,context);
The field directly inherits from the
Array-Field, so you can as well can use all the methods from the baseclass !
Trigger ther persistence of setting-fields by hand
// The following command will write the values of all setting-fields to a file which have the flag "persistent = true".
// "this->" is not needed here, it just helps to trigger the eclipse-auto-completion
this->serviceLocator_->triggerPersistency();
- Misc -
Usage of class-specific custom-types
// Take a look into "generated/cpp/MyClassName/GeneratedCode/TypeDefinition.h" to see all custom-types, defined by your class
(*device)->control.set(DEVICE_CONTROL::LOCAL,pEvt->getMultiplexingContext());
Usage of client-data in server-actions
// In a Set-Server-Acttion you may want to retrieve the data and the filter, which was send by the client:
bool myData = data.getMyValueItemName();
bool myData = filter.getMyFilterItemName();
// In a Get-Server-Action you may want to fill the data-container which will be send to the client.
int32_t myData = 1234;
data.setMyValueItemName(myData );
// For 2D arrays you can make use of the FESA Container-class "ImmutableArray2D":
fesa::ImmutableArray2D<double> myData = data.getMyDoubleArray2D();
std::cout << "myData[2][2]: " << myData[2][2] << std::endl;
Usage of external headers and libraries
# Modify the file "Makefile.specific" of your class / deploy-unit according to your requirements
SOME_LIBRARY_HOME= /path/to/the/library
COMPILER_FLAGS += -I$(SOME_LIBRARY_HOME)/include
LINKER_FLAGS += -L$(SOME_LIBRARY_HOME)/lib/$(CPU) -lsome-library
std::ostringstream message;
message << "Enter your log message here";
LOG_ERROR_IF(logger, message.str());
// Depending on how important your log message is, you should use one of the following macros:
// LOG_TRACE_IF(logger, message.str()); // In FESA classes you should use the diagnostic-logging instead
// LOG_DEBUG_IF(logger, message.str()); // In FESA classes you should use the diagnostic-logging instead
// LOG_INFO_IF(logger, message.str()); // In FESA classes you should use the diagnostic-logging instead
// LOG_WARNING_IF(logger, message.str());
// LOG_ERROR_IF(logger, message.str());
// Diagnostic-Information can be logged like this:
// You can define specific log-traces and enable/disable them during runtime
// Use the global "diagnostic-property" in the FESA Explorer to view and control the diagnostic logging (see the links below)
LOG_DIAG_IF("MyTopic",message.str());
LOG_DIAG_DEVICE_IF("MyTopic",message.str(),(*device));
- Use the application arguments -v and -vv to control the talkativeness of your FESA software
- You as well can re-configure the logging configuration by providing a foreign configuration file (application argument -cfglog required during launch of FESA software)
- To view the log output, login with your ACC account on https://logstash.acc.gsi.de. A usage manual is found here.
- For detailed information on how to work with logging in FESA, please check the the related wiki page !
How to trigger an on-demand-event manually
// In the FESA class design you have to choose "@automatic = false" on your action/triggered-event-source.
// In the custom-server-/rt- action you can now do the following:
triggerOnDemandEventSource(MyOnDemandEventSourceName,pEvt->getMultiplexingContext());
If required some payload information can be added to the event. Check the
payload-section for more infrmation.
For the complete API of the class "AbstractAction" and it's relatives, check the
doxygen documentation.
How to trigger a property-notification manually
// The same code can be used in Server- and RT-actions
// Make sure that the names of the property/device you want to notify are correct
// For inherited or composed classes, dont use the className::propertyName format! It is sufficient to only put the property-name.
std::string notifiedProperty = "MyProperty";
std::string notifiedDevice = "MyDevice";
// You can add any number of property/device-combinations by using the following method:
registerManualNotification(notifiedProperty,notifiedDevice);
//If you are finished with adding properties/devices, send the notification by using the method below
sendManualNotification(pEvt->getMultiplexingContext());
Throwing Exceptions to clients
Prefably the type "FesaException" should be used here. ( However as well any type of RDAException will do. )
Either an object of this type can be created directly, or the class developer can inherit from the class, in order to define a class-specific exception-type.
std::string errorMessage = "Something went wrong here!";
//Currently at GSI there are no standards regarding the error-codes
std::string errorCode = "4711";
// errorCategory should be "FESACLASS_" as prefix an the classname.
std::string errorCategory = "FESACLASS_MyFesaClass";
throw fesa::FesaException(errorMessage,__FILE__,__LINE__,errorCode,errorCategory);
Enable/Disable logical events and event-sources
// By the use of the service-locator, events and sources can be enabled/disabled everywhere in the user-code
// "this->" is not needed here, it just helps to trigger the eclipse-auto-completion
this->serviceLocator_->enableEventSource("MySource");
this->serviceLocator_->disableEventSource("MySource");
this->serviceLocator_->enableRTEvent("MyLogicalEvent");
this->serviceLocator_->disableRTEvent("MyLogicalEvent")
Working with Event-Payload
OnDemand Payload
// If you choose "triggered-event-source/@automatic = false" for the action,
// you can add your own payload to the event. The data-format is a char-array
std::string payload = "myPayload";
triggerOnDemandEventSource(MyODEventSourceName,pEvt->getMultiplexingContext(),payload.c_str(),payload.size() + 1);
// Please note that for "triggered-event-source/@automatic = true" in server-actions,
// the device-name for which the server-action was triggered is automatically added as payload !
// In the RT-Action you can read-out the defined payload like that:
#include <fesa-core/RealTime/RTEventPayload.h>
....
const char* payload = pEvt->getPayload()->getValue<char>();
// or, if you prefer using a std::string:
std::string payload(pEvt->getPayload()->getValue<char>());
Timing Payload for White Rabbit based timing
// Include this header in the RT-Action
#include <fesa-core-gsi/Synchronization/TimingContextWR.h>
...
// Cast the MultiplexingContext to the right type
const fesaGSI::TimingContextWR* contextWR = dynamic_cast<const fesaGSI::TimingContextWR*>(pEvt->getMultiplexingContext());
std::cout << "FormatID: " << contextWR->getFormatID() << std::endl;
std::cout << "GroupID: " << ccontextWR->getGroupID() << std::endl;
std::cout << "EventNumber: " << contextWR->getEventNumber() << std::endl;
std::cout << "SequenceIndex: " << contextWR->getSequenceIndex() << std::endl;
std::cout << "ProcessIndex: " << contextWR->getProcessIndex() << std::endl;
std::cout << "SequenceCounter: " << contextWR->getSequenceCounter() << std::endl;
std::cout << "BeamProductionChainIndex: " << contextWR->getBeamProductionChainIndex() << std::endl;
std::cout << "EventExecutionTimeStamp: " << contextWR->getEventExecutionTimeStamp() << std::endl;
std::cout << "AcquisitionTimestamp: " << contextWR->getAcquisitionTimestamp() << std::endl;
std::cout << "ProcessTimeStamp: " << contextWR->getProcessTimeStamp() << std::endl;
std::cout << "SequenceTimeStamp: " << contextWR->getSequenceTimeStamp() << std::endl;
std::cout << "BeamProductionChainTimestamp: " << contextWR->getBeamProductionChainTimestamp() << std::endl;
//You as well can get the whole payload at once:
fesaGSI::TimingContextWRPayload payload = const_cast<fesaGSI::TimingContextWR*>(contextWR)->getPayload();
More information about White Rabbit based timing can be found
here.
OnSubscription Payload
- First of all, make sure, that the FESA-device you want to subscribe to, is listed in the FESA database. More info about this can be found here .
- Lets assume the FESA-class you want to subscribe to is "ClassB" and the class which establishes the subscription is "ClassA".
- Furter lets assume you want to subscribe to the property "PropertyB" which has the value-items: "ValueItemB1" and "ValueItemB2" which are scalar.
- In order to use this code snippet, you need to replace all occurences of "ClassA", "ClassB", "PropertyB", "ValueItemB1" and "ValueItemB2" with your concrete names.
// In the RTAction of ClassA which receives the onSubscriptionEvent, add these line to the includes:
#include <ClassB/GeneratedCode/PropertyData.h>
#include <fesa-core/RealTime/OnSubscriptionRTEventPayload.h>
...
// Use the following code in the execute-method
// extract the payload from the RTEvent
const OnSubscriptionRTEventPayload* payload = dynamic_cast<const OnSubscriptionRTEventPayload*> (pEvt->getPayload().get());
std::auto_ptr<const ClassB::PropertyBPropertyData> propData;
try
{
// extract the right data-format from the payload
propData = fesa::OnSubscriptionRTEventPayload::extract<ClassB::PropertyBPropertyData>(*pEvt);
}
catch (const std::exception& ex)
{
throw FesaException(__FILE__, __LINE__, "Payload extraction to type PropertyBPropertyData failed");
}
// probably this check is not needed, however it will not hurt either
if (payload == NULL)
{
throw FesaException(__FILE__, __LINE__, "Payload is not of type OnSubscriptionRTEventPayload");
}
// This snippet only couvers the UpdateType::NORMAL
// Other possible update-types are: UpdateType::FIRST(first update of a new subscription) and UpdateType::IMMEDIATE
if (payload->getData().getUpdateType() != UpdateType::NORMAL)
{
std::ostringstream error;
error << "Notification was not of type NORMAL but " << payload->getData().getUpdateType();
throw FesaException(__FILE__, __LINE__, error.str());
}
// Perform different operations on the data-container "propData". E.g. get the data of the received value-items
// Depending on the type of the value item, the methods of propData look different.
// Take a look at "ClassB/GeneratedCode/PropertyData.h" for details of the API.
std::cout << "Event received. Event-data: " << std::endl;
std::cout << "ValueItemB1: " << propData->getValueItemB1() << std::endl;
std::cout << "ValueItemB1: " << propData->getValueItemB2() << std::endl;
// Lets assume you write code for class "ClassA". And you want to do a "Get" of the property "PropB " of the device "DevB" of class "ClassB"
// PropB has the value items "ItemB1" and "ItemB2"
// First you need to include the following fesa-core header
#include <fesa-core/Proxy/ProxyInterface.h>
// Second you need to include PropertyData.h of ClassB
#include <ClassB/GeneratedCode/PropertyData.h>
...
// If you need to do the "Get" more than once, it may make sense to store this object as class-member
fesa::ProxyInterface rda;
std::string deviceName = "DevB";
std::string cycleSelector = ""; // For multiplexed devices you have to enter the concrete cycle-selector here
std::auto_ptr<const ClassB::PropBPropertyData> ;
// Probably you want to wrap a try / catch around this block later and catch for std::exception
propData = rda.getProperty<const ClassB::PropBPropertyData>(deviceName,cycleSelector);
std::cout << "ItemB1: " << propData->getItemB1() << std::endl;
std::cout << "ItemB2: " << propData->getItemB2() << std::endl;
// Lets assume you write code for class "ClassA". And you want to do a "Set" of the property "PropB " of the device "DevB" of class "ClassB"
// PropB has the value items "ItemB1" and "ItemB2"
// First you need to include the following fesa-core header
#include <fesa-core/Proxy/ProxyInterface.h>
// Second you need to include PropertyData.h of ClassB
#include <ClassB/GeneratedCode/PropertyData.h>
...
// If you need to do the "Get" more than once, it may make sense to store this object as class-member
fesa::ProxyInterface rda;
std::string deviceName = "DevB";
std::string cycleSelector = ""; // For multiplexed devices you have to enter the concrete cycle-selector here
ClassB::PropBPropertyData propData;
double myValue = 42;
propData.setItemB1(myValue);
propData.setItemB2(myValue);
// Probably you want to wrap a try / catch around this block later and catch for std::exception
rda.setProperty<const ClassB::PropBPropertyData>(deviceName,cycleSelector,propData);
// E.g. the TimingDomain defined in the instance-file can be accessed like that:
// ( How to obtain a device-pointer is explained on top )
std::string timingDomain = (*device)->getInstantiationData()->getTimingDomain();
The complete API of the class 'DeviceInstantiationData' can be found
here
--
SolveighMatthies - 17 Aug 2016