xentara-plugin v2.0.4
The Xentara Plugin Framework
|
Xentara skill elements can publish certain data as attributes, which can then be read and written as described in Reading and Writing Attributes.
In order to publish an attribute, an element must do the following things:
For readable attributes, the element must do the following additional things:
For writable attributes, the element must do the following additional things:
Some attributes of I/O skill elements are used directly to perform the associated I/O operations, and to report the status and result of the I/O. These include the following standard attributes:
Each of these attributes is marked as an I/O attribute for the relevant data directions (input and/or output). If a data point uses a particular skill data point as input and/or output, then it inherits the relevant I/O attributes from the skill data point. A data point that uses an input that publishes the sourceTime attribute, for example, will automatically gain a sourceTime attribute of its own, that contains the same value as that of the input skill data point.
You can mark attributes you define yourself as input and/or output attributes by passing the relevant data directions to the constructor of xentara::model::Attribute. The Xentara OPC UA driver, for example, publishes the OPC UA serverTimestamp parameter asttribute named serverTime. This attribute is marked as an I/O attribute for the input direction, and thus inherited by data points that use an OPC UA skill data point as an input.
The values of attributes must be stored in a special memory resource. Currently, Xentara provides two memory resources:
In order to store data in a memory resource, a memory block must be allocated. Xentara can allocate three different types of memory blocks:
All memory blocks are accessed using the template class xentara::memory::MemoryBlock. This template class takes two parameters, one is the type of memory resource to use, and the second is the structure object. The structure object determines what exactly the memory block will hold.
Memory block objects inherit from xentara::utils::tools::Unique, so they can be move constructed and move assigned, but not copy constructed or copy assigned. xentara::memory::MemoryBlock behaves like a smart pointer and automatically deallocates the memory block when necessary (e.g. in the descructor).
Raw memory blocks are managed using a xentara::memory::MemoryBlock with the dummy class xentara::memory::RawMemory as structure. xentara::memory::RawMemory is the default parameter for the structure object, so that a raw memory block can simple be written as xentara::memory::MemoryBlock.
A raw memory block can be allocated in a memory resource using create(), and deallocated using destroy().
Object memory blocks are managed using a xentara::memory::ObjectBlock, which is a xentara::memory::MemoryBlock with the dummy template class xentara::memory::Object as structure. xentara::memory::ObjectBlock and xentara::memory::Object both take the C++ type of the contained object as template parameter.
An object memory block containing an object can be allocated in a memory resource using create(), and deallocated using destroy().
Array memory blocks are managed using a xentara::memory::ArrayBlock, which is a xentara::memory::MemoryBlock with xentara::memory::Array as structure. Contrary to xentara::memory::RawMemory and xentara::memory::Object, xentara::memory::Array is not a dummy class. You must provide an object of type xentara::memory::Array, and use it to define the internal structure of the array.
An array memory block takes the array that describes its structure as a parameter in its constructor. The array object need not contain the correct structure yet, the constructor merely binds the array object to the memory block. To construct a class containing an array memory block, you would usually proceed as follows:
To define the internal structure of the memory block, you must add elements to the xentara::memory::Array object using appendElement(), appendObject(), or appendObjects(). Each of these functions will return a handle to the newly added element or elements, that can then be used when reading or writing the data.
You can define the structure at any time before allocating the array. For memory blocks that hold value or status attributes, the callback realize() is a good place to define the structure of the array. In realize(), the entire model has already been loaded and the configuration of all objects is known, but no attribute read handles have been resolved yet. (Read handles are usually resolved in prepare(), which is called after realize().)
Here is an example of an I/O transaction that allocates storage for all its data points:
An array memory block containing all the element added to the structure object can be allocated in a memory resource using create(), and deallocated using destroy().
In order to write to a memory block, you need to have access to the block object.
The data in a memory block is always written atomically using swap-in: A new memory region is allocated for the new data, and then swapped in atomically to replace the old data on commit. The new data can either be default initialized, or copied from the old data. If the data is default initialized, all old values of all objects contained in the block will be lost, and the entire block must be rewritten from scratch. If the old data is copied, only the changed data need be written.
Writing to a memory block is done using a write sentinel. A write sentinel is an object of a template instantiation of xentara::model::WriteSentinel. The write sentinel object takes the memory resource type and the memory block type as template parameters.
To write to a block, four things need to be done:
Steps 2 and 3 can be combined into a single C++ statement.
The data in a raw data block is accessed using a void *. To write a raw data block, proceed as follows:
Or, simplified:
Replace the std::memcpy call with whatever is needed to write the data.
Please note that the new values are left uninitialized, so every byte of the new data must be written before calling commit().
You can access the old values using oldData().
The value in an object block is accessed using a reference to the C++ object. To write an object block, proceed as follows:
Or, simplified:
In this example, the new value is default initialized. If the value has multiple members, you must set all the members to the correct values, since no data is copied from the original value.
You can access the old value using oldValue().
You can also opt to copy the old value into the new value when creating the write sentinel. To accomplish this, pass the dummy object xentara::memory::kCopyOldValues to the [constructor](xentara::memory::workaround::ObjectWriteSentinel<Type>::WriteSentinel(const xentara::memory::ObjectBlock<Type> &, xentara::memory::CopyOldValuesTag). This will initialize the new value with the old one:
Or, simplified:
The elements in an array block are accessed using the handles returned by xentara::memory::array::appendElement(), xentara::memory::array::appendObject(), and xentara::memory::array::appendObjects() when registering the elements with the structure object. To write an array data block, proceed as follows:
Or, simplified:
You can intermix the calls to operator[]() and the modification of the elements as you please. It is not necessary to first resolve all the references and then write all the values, any order is ok.
In this example, the values of the new elements are default initialized. You must set all the members of all elements to the correct values, since no data is copied from the original elements.
You can access the old elements using oldValues().
You can also opt to copy the old values of the elements into the new elements when creating the write sentinel. To accomplish this, pass the dummy object xentara::memory::kCopyOldValues to the constructor. This will initialize the new elements with the old ones:
Or, simplified:
In order to read from a memory block, you need to have access to the block object.
Reading from a memory block is done using a read sentinel. A read sentinel is an object of a template instantiation of xentara::model::ReadSentinel. The read sentinel object takes the memory resource type and the memory block type as template parameters.
To read to a block, three things need to be done:
The last two steps can be combined into a single C++ statement.
The data in a raw data block is accessed using a const void *. To read a raw data block, proceed as follows:
Or, simplified:
Replace the std::memcpy call with whatever is needed to read the data.
The value in an object block is accessed using a reference to the C++ object. To read an object block, proceed as follows:
Or, simplified:
You do not have to copy the entire value, of course. You can access individual members, call const member functions, pass the object on to another function, or do anything you can normally do with a C++ reference. Here is an example of reading a single member of a class object:
Or, simplified:
The elements in an array block are accessed using the handles returned by xentara::memory::array::appendElement(), xentara::memory::array::appendObject(), and xentara::memory::array::appendObjects() when registering the elements with the structure object. To read an array data block, proceed as follows:
Or, simplified:
You can intermix the calls to operator[]() and the modification of the elements as you please. It is not necessary to first resolve all the references and then read all the values, any order is ok.