SILECS C++ Code Snippets
- General -
Dependency in Makefile.specific
In order to build and links against the silecs library and snap7 you will need to add some line to the FESA makefile
Makefile.specific. Replace the concrete version with the one you want to use.
For fesa classes:
...
SILECS_VERSION ?= 2.3.0
SILECS_BASE ?= /common/usr/cscofe/silecs/$(SILECS_VERSION)
SILECS_COM ?= $(SILECS_BASE)/silecs-communication-cpp
COMPILER_FLAGS += -I$(SILECS_COM)/include
...
For fesa deploy units:
...
SILECS_VERSION ?= 2.3.0
SILECS_BASE ?= /common/usr/cscofe/silecs/$(SILECS_VERSION)
SILECS_COM ?= $(SILECS_BASE)/silecs-communication-cpp
SILECS_SNAP7 ?= /common/usr/cscofe/silecs/snap7/1.4
LINKER_FLAGS += -L$(SILECS_COM)/lib/$(CPU) -lsilecs-comm
LINKER_FLAGS += -L$(SILECS_SNAP7)/$(CPU)-linux -lsnap7
#add default search path for dynamic snap7 library
LINKER_FLAGS += -Wl,-rpath,$(SILECS_SNAP7)/bin/$(CPU)-linux,-rpath,/usr/lib
...
Specific Init
In order to make the generated silecs-code work, you need to initialize it in the specifc-init of your class (usually in RealTime/ RTDeviceClass.cpp)
// Replace "MyClass" with the name of your class
// include the silecs header
#include <MyClass/Common/MyClass.h>
...
RTDeviceClass::~RTDeviceClass()
{
MyClass::cleanup();
}
void RTDeviceClass::specificInit()
{
try
{
if ( !MyClass::isInitialized() )
MyClass::setup(this->MyClassServiceLocator_);
}
catch(std::exception& ex)
{
// required to trace failures on logstash.acc.gsi.de
LOG_ERROR_IF(logger, ex.what());
throw;
}
catch(...)
{
LOG_ERROR_IF(logger, "Unexpected error. Please notify support");
throw;
}
}
- Access of PLC-blocks -
Working with Acquisition blocks
Acquisition blocks are filled by the PLC. The client(FESA) only can read them out.
Best practise is to read the PLC-values periodically in the RT-Action. If possible, only notify FESA-client if a value changed.
// include the silecs header
#include <MyClass/Common/MyClass.h>
...
// Replace "MyClass" with the name of your class
// Replace "MyBlock" with the name of the block you want to read
std::vector<Device*> devCol = this->MyClassServiceLocator_->getDeviceCollection();
std::vector<Device*>::iterator device;
for(device=devCol.begin();device!=devCol.end();++device)
{
try
{
MyClass::MyBlock.getOneDevice((*device),true,pEvt->getMultiplexingContext());
// When using the GSI Template, you at least may want to set the acquisitionContext
// (otherwise you will get Exceptions during client get/subscription)
(*device)->acquisitionContext.insert(pEvt->getMultiplexingContext());
}
catch(std::exception& ex)
{
// required to trace failures on logstash.acc.gsi.de
// Use error_collection.addError over LOG_ERROR_IF whenever possible, since it adds device-info and the error as well can be read out by fex
std::string message = ex.what();
(*device)->error_collection.addError(4711,message, pEvt->getMultiplexingContext(), *device);
throw;
}
catch(...)
{
// Use error_collection.addError over LOG_ERROR_IF whenever possible, since it adds device-info and the error as well can be read out by fex
std::string message = "Unexpected error. Please notify support";
(*device)->error_collection.addError(4711, message, pEvt->getMultiplexingContext(), *device);
throw;
}
}
If the Block is connected to a FESA-Property, you can as well read the PLC value in the FESA Get-Server-Action of that Property instead. In order to do so, set the FESA field-attribute "shared" and "persistent" to "false". Please note that in this case, the FESA-field will not be available on the FESA RT-side.
<field name="myRegister" multiplexed="false" persistent="false" shared="false">
<scalar type="int16_t" />
</field>
#include <MyClass/Common/MyClass.h>
...
void getMyBlock::execute(fesa::RequestEvent* pEvt, Device* pDev, myBlockPropertyData& data)
{
...
// Surround this statement with try/catch and make use of LOG_ERROR_IF in order to profit from logstash.acc.gsi.de
MyClass::MyBlock.getOneDevice(pDev,true,pEvt->getMultiplexingContext());
...
}
Working with Command blocks
Command Blocks should only be modified by the client(FESA), not by the PLC itself ! A command-block is sent to the PLC by the client, however it is not possible to read-back the value of the block's registers from the PLC. A Command block generates a FESA-command property
The plc-value of Command blocks can be written for all devices in a RT-Action:
// include the silecs header
#include <MyClass/Common/MyClass.h>
...
try { ... // Surround the code with try/catch and make use of LOG_ERROR_IF in order to profit from logstash.acc.gsi.de
// Replace "MyClass" with the name of your class
// Replace "MyBlock" with the name of the block you want to write
std::vector<Device*> devCol = this->MyClassServiceLocator_->getDeviceCollection();
std::vector<Device*>::iterator device;
for(device=devCol.begin();device!=devCol.end();++device)
{
MyClass::MyBlock.setOneDevice((*device),true,pEvt->getMultiplexingContext());
}
Or for a specific device in a Set-Server-Action:
#include <MyClass/Common/MyClass.h>
...
void setMyBlock::execute(fesa::RequestEvent* pEvt, Device* pDev, myBlockPropertyData& data)
{
...
// Surround the code with try/catch and make use of LOG_ERROR_IF in order to profit from logstash.acc.gsi.de
pDev->myRegister.set(data.getMyRegisterSet(),pEvt->getMultiplexingContext()); // first update the FESA-field with the incoming values
MyClass::MyBlock.setOneDevice(pDev,true,pEvt->getMultiplexingContext());
...
}
Working with Setting blocks
Setting Blocks should only be modified by the client(FESA), not by the PLC itself ! The client is able to read back the value which was set.
For Setting-Blocks Usually it makes sense to get/set the PLC-Blocks only in the FESA-Server-Actions.
If you want to read the value of a Setting Block in the RT-Action, dont read the whole block via e.g. "getOneDevice(...)"! Instead read only the specific register you are interested in. (Otherwise you will receive a FESA-Exception for writing to a Setting field in a RT-Action)
For reading a Setting block, please refer to the
the documentation of Acquisition blocks
For writing a Setting block, please refer to the
the documentation of Command blocks
Reading out the current PLC run-state
try { ... // Surround the code with try/catch and make use of LOG_ERROR_IF in order to profit from logstash.acc.gsi.de
// Replace "MyClass" with the name of your class
std::vector<Device*> devCol = this->MyClassServiceLocator_->getDeviceCollection();
std::vector<Device*>::iterator device;
for(device=devCol.begin();device!=devCol.end();++device)
{
Silecs::PLC* pPLC = MyClass::getPLC(*device);
std::cout << " IsRunning: " << pPLC->isRunning() << std::endl;
}
...
Usage of the class SilecsPLC
The silecs internal class "SilecsPLC" provides many options which can be used. Here just one example.
To see all provided methods, check:
https://sourceforge.net/p/silecs/git/ci/gsi/tree/silecs-communication-cpp/src/silecs-communication/interface/equipment/SilecsPLC.h
try { ... // Surround the code with try/catch and make use of LOG_ERROR_IF in order to profit from logstash.acc.gsi.de
// Replace "MyClass" with the name of your class
for(device=devCol.begin();device!=devCol.end();++device)
{
Silecs::PLC* pPLC = MyClass::getPLC(*device);
std::string model = pPLC->getModel();
pPLC->sendPlcStop();
pPLC->sendColdRestart();
...
}
...
Get/Set a specific PLC-register
The silecs internal class "SilecsRegister" provides many options which can be used. Here just one example.
To see all provided methods, check:
https://sourceforge.net/p/silecs/git/ci/gsi/tree/silecs-communication-cpp/src/silecs-communication/interface/equipment/SilecsRegister.h
try { ... // Surround the code with try/catch and make use of LOG_ERROR_IF in order to profit from logstash.acc.gsi.de
// Replace "MyClass" with the name of your class
// Replace "MyClass" with the name of your class
// Replace "myRegisterName" with the name of your register
// Replace "MySettingBlock" with the name of your block
for(device=devCol.begin();device!=devCol.end();++device)
{
Silecs::PLC* pPLC = MyClass::getPLC(*device);
Silecs::Device* plcDevice = pPLC->getDevice((*deviceMaster)->plcDeviceLabel.get());
Silecs::Register* plcRegister = plcDevice->getRegister("myRegisterName");
// BYTE values internally are stored as uchar ... check the conversion List on SilecsRegister.h to see what is the correct data-type to use here
plcDevice->recv("MySettingBlock");//update data from PLC for a specific block
int value = (int)plcRegister->getValUChar();
std::cout << " old value: " << (int)value << std::endl;
plcRegister->setValUChar((unsigned char)(value + 1)); // set new value in silecs
plcDevice->send("MySettingBlock");//send data to PLC for a specific block
plcDevice->recv("MySettingBlock");//update data from PLC for a specific block
std::cout << " new value: " << (int)plcRegister->getValUChar() << std::endl;
//oder auch:
plcRegister->printVal();
}