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:
...
# 'latest' is a moving target. You as well can use a concrete version, if preferred
SILECS_VERSION ?= latest
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:
...
# 'latest' is a moving target. You as well can use a concrete version, if preferred
SILECS_VERSION ?= latest
SILECS_BASE ?= /common/usr/cscofe/silecs/$(SILECS_VERSION)
SILECS_COM ?= $(SILECS_BASE)/silecs-communication-cpp
SILECS_SNAP7 ?= /common/usr/cscofe/silecs/snap7/latest

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)/$(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();
}
Topic revision: r3 - 13 Dec 2022, AlexanderSchwinn
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback