RSC GDS services

Note: The execution of RSC services can take some time. For this reason, avoid direct calls from ESM tasks that are scheduled for real-time execution.

IDataAccessService

During runtime, the function extensions can use the IDataAccessService interface to have read and write access to the GDS data. The service enables asynchronous reading and writing of one or more ports or even internal variables. For this access, you need the name of the port that is to be read out. The data read can be written to a database, for example.

You need the following header to use the IDataAccessService service. If necessary, include it via the #include command: 

#include Arp/Plc/Gds/Services/IDataAccessService.hpp

Note: From firmware 2021.6, values of data types WSTRING and StaticWString<n> are mapped to the data type Arp::System::RscServices::RscString<512> and the encoding is mapped to UF8. During reading or writing values, the firmware converts between UTF8 and UTF16, which is the encoding for WSTRING and StaticWString<n>.

Service functions for direct data access:

  • Read(): This function is used to read the values of the variable addresses passed on.
  • ReadSingle(): This function is used to read the value of the variable address passed on. Only simple variables are supported (no arrays or structures).
  • Write(): This function is used to write the values passed on to the variables of the variable address passed on.
  • WriteSingle(): This function is used to write the value passed on to the variables of the variable address passed on. Only simple variables are supported (no arrays or structures).

 

These examples show how the above functions can be implemented:

ReadItem     ReadSingle(const RscString<512>& portName)
             Read(ReadPortNamesDelegate portNamesDelegate, 
                  ReadResultDelegate resultDelegate)
DataAccessError   WriteSingle(const WriteItem& data)
                  Write(WriteDataDelegate dataDelegate, 
                        WriteResultDelegate resultDelegate)

Each port has a unique name within the GDS that is made up as described in Global Data Space configuration. You need the complete name (URI) of a port to address it. For example, the following port addresses are valid:

  • ComponentName-1/ProgramName-1.Variable_Name
  • ComponentName-1/Global_Variable_Name
  • ComponentName-1/ProgramName-1.Array_Variable_Name
  • ComponentName-1/ProgramName-1.Array_Variable_Name[index]
  • ComponentName-1/ProgramName-1.Array_Variable_Name[startIndex:endIndex]
  • ComponentName-1/ProgramName-1.Struct_Variable_Name.Element1.Leaf
  • ComponentName-1/ProgramName-1.Struct_Variable_Name.Element1.LeafArray
  • ComponentName-1/ProgramName-1.Struct_Variable_Name.Element1.LeafArray[index]

The following variable types can be read and written:

  • Primitive
  • DateTime
  • StaticString
  • IecString
  • Enum– The values of an enum are only transmitted as BaseType.
  • Struct – You can address the entire structure or each element of a structure, even if it is not an END element. Information on the layout of the structure can be obtained from the data that is transmitted. This is an example for a Struct:
    In C#, you receive an array from object[] containing values of a and b: In C++, you receive three individual elements, each in an RscVariant.
    The first Struct type RscVariant contains information, e.g., that it is a Struct type variable with two elements.
    Next, the Int type RscVariant with the associated value and the Bool type RscVariant with the associated value are read.
  • Array – You can address each individual element or the entire array. You also have the option to address a specific range of an array. Enter the start index and the end index separated with a colon for this, e.g. ComponentName-1/ProgramName-1.Array_Variable_Name[20:30].

When writing values to variables/ports, the data structure WriteItem is used:

struct WriteItem ...
{
    RscString<512>  PortName;
    RscVariant<512> Value;
    ...
};

Note: Data type mismatch possible!

The data type assigned to the Value member of WriteItem must match the data type of the port addressed by the PortName member. Make sure that your application meets this requirement. You can set the type explicitly by using the RscVariant::SetType(RscType rscType) method.

If the data types do not match then the handling depends on the firmware release:

  • With firmware 2020.0 LTS and 2020.3, this mismatch is logged in the /opt/plcnext/logs/Output.log file as a warning:
    TypeMismatch: Given value with type '{dataTypeGivenByValue}' doesn't match the type '{dataTypeOfPort}' from '{nameOfPort}'!
    The firmware always copies as much bytes from the value to the port as the port's data type provides. It is therefore possible that the value is cut to fit into the port. This behavior was chosen to ensure compatibility of existing customer applications. For this reason, it is highly recommended to check the Output.log file to ensure your application will continue to run with current and future firmware releases.
  • With firmware 2020.6 to 2021.3, the value is converted to the matching variable data type by applying the same rules as for a GDS connection. If this process fails, the error code DataAccessError::TypeMismatch will be returned.
  • From firmware 2021.6 to current releases, this handling of string data has added:
    If the value is larger than the capacity of the variable/port of data type STRING, StaticString<> or WSTRING, then the service method returns DataAccessError::StringLengthExceeds, no bytes are copied, and no warning message is emitted.

ISubscriptionService

ISubscriptionService offers an alternative to the read functions of IDataAccessService. The variables of which the values are to be read are only registered once and can then be read continuously. The data can be read more rapidly, and also recorded consistently with the task cycle. All subscribed variables instantiated in the same ESM task are recorded in the same cycle (with the exception of the DirectRead subscription type). In addition, ISubscriptionService provides time stamps that can be used to assign a clear recording time to each value.

To use the ISubscriptionService class, which was declared in the Arp::Plc::Gds::Services namespace, you require the following header file:

#include "Arp/Plc/Gds/Services/ISubscriptionService.hpp"

Note: From firmware 2021.6, values of data types WSTRING and StaticWString<n> are mapped to the data type Arp::System::RscServices::RscString<512> and the encoding is mapped to UF8. During reading or writing values, the firmware converts between UTF8 and UTF16, which is the encoding for WSTRING and StaticWString<n>.

Here's how to create, start and read a subscription.

Creating a subscription

To create a subscription, use the following function of ISubscriptionService:

uint32    CreateSubscription(SubscriptionKind kind)

CreateSubscription(SubscritionKind::Recording) creates a recording subscription whose ring buffer can hold 10 records. Otherwise you can call CreateRecordingSubscription and pass as parameter the number of segments of the ring buffer (recordCount):

uint32    CreateRecordingSubscription(uint16 recordCount)

The type of the subscription is always Recording and therefore used in the method name.

Subscription types (SubscriptionKind)

Note: With the subscription kinds HighPerformance, RealTime, and Recording the values are recorded within the context of the ESM task each respective variable is assigned to. Resource global variables are recorded within the context of a cyclic ESM task that matches as much as possible to the sampleRate. Therefore the values of all variables assigned to the same ESM task are from the same execution cycle of that ESM task

For the DirectRead kind the data is read in the context of the thread which calls ReadValues() and because this thread is typically of lower priority than the ESM tasks, in which the values are calculated, the read data can be from different task cycles.

Select one of the following four subscription types:

Type Description
DirectRead The DirectRead subscription records the values directly when the ReadValues() function is called within the context of the calling thread whereas the HighPerformance, RealTime, and Recording subscription types collect the values within the context of the ESM task the respective variable is assigned to. As the data is read in the context of the thread which calls ReadValues() and because this thread is typically of lower priority than the ESM tasks, in which the values are calculated, the read data can be from different task cycles.
This subscription can deliver the best performance with the lowest impact on the real time.
Possible use: Asynchronous data acquisition of non-time-critical data.
HighPerformance The HighPerformance subscription uses a double buffer which contains the last written data of a variable. The buffer enables almost simultaneous writing and reading of data.
The HighPerformance type is consistent with the task cycle. It uses the least memory and shows the least impact on the real time (compared to the RealTime and Recording subscription types).
Possible use: Standard type for acquiring data.
RealTime The RealTime subscription uses a quad buffer which contains the last written data of a variable. The buffer minimizes access times during reading and writing.
The RealTime type is consistent with the task cycle. It guarantees fast data access, but requires four times the amount of memory.
Possible use: The subscription is suitable for variables running in very fast tasks and if fast access to the variable's values is required.
Recording The Recording subscription uses a ring buffer that can store records of a variable. This type is consistent with the task cycle and has low impact on the real time. However, depending on the ring size, it requires a lot of memory. By default, the ring buffer size is set to 10. It can be configured if the CreateRecordingSubscription() function is used instead of CreateSubscription().
Possible use: The subscription is suitable for variables running in tasks that are faster than the task of the user but the user still needs all values.
This SubscriptionKind is also used by the Real-time DataLogger.

 

Note:  With a large amount of variables, the subscription types <SubscriptionKind> HighPerformance, RealTime and Recording record the values within the context of ESM tasks which increases the execution duration of these ESM tasks. The task watchdog may be triggered.
Remedy: Configure the subscription as DirectRead to avoid this behavior. Read further information on Accessing variables via OPC UA subscriptions.

Adding variables

Add the desired variables to the subscription by calling one of these functions:

DataAccessError     AddVariable(uint32 subscriptionId, 
                      const RscString<512>& variableName)
void    AddVariables(uint32 subscriptionId, 
        AddVariablesVariableNamesDelegate variableNamesDelegate, 
        AddVariablesResultDelegate resultDelegate);

Both functions can be called repeatedly for the same subscription. As with IDataAccessService, the variables are addressed via their complete name.

Examples for variable addressing

Variable addressing Description
ComponentName-1/ProgramName-1.Variable_Name Program variable
ComponentName-1/Global_Variable_Name Component variable (global variable)
ComponentName-1/ProgramName-1:Array_Variable_Name[index] Array element
ComponentName-1/ProgramName-1:Struct_Variable_Name.Element1.Leaf Structure element (leaf)
ComponentName-1/ProgramName-1:Struct_Variable_Name.Element1.LeafArray[index] Array element from a structure
ComponentName-1/ProgramName-1:Array_Variable_Name[startIndex:endIndex] Extract of array elements

Return values in case of an error

Once a variable was added successfully to the subscription, the DataAccessError::None value is returned.

In case of an error, the following error codes might be returned:

Return Value Description
None No error
NotExists The variable does not exist in the system.
NotAuthorized The user does not have sufficient authorization.
TypeMismatch During writing, the value type is not suitable for the respective port.
PortNameSyntaxError The port address is syntactically incorrect.
PortNameSemanticError The port address is semantically incorrect.
IndexOutOfRange The address contains an array index that is outside the array.
NotImplemented The variable or service function has not yet been implemented.
NotSupported The variable is not supported.
CurrentlyUnavailable The service is currently unavailable.
UnvalidSubscription The specified subscription was not found or is invalid.

Supported data types 

The following types are currently supported:

  • Primitive
  • DateTime
  • String, currently, only these types are supported:
    • StaticString
    • IecString
  • Enum
  • Struct
  • Array

Subscribe/unsubscribe

Once you created a subscription and configured it with variables, you can activate it with Subscribe.

To activate the subscription, call the following service function:

DataAccessError     Subscribe(uint32 subscriptionId, uint64 sampleRate);

By calling the function, copying of the variable starts (exception: for the DirectRead type, data is not being recorded automatically).

Recording distinguishes between global variables and instance variables.

  • In the case of instance variables, the values are recorded in the context of the ESM task to which the corresponding program instance is assigned. If the sampleRate is greater than the interval time of the ESM task, the values are not recorded in each cycle of the ESM task but in a multiple of the interval time that is less than the sampleRate.
  • If the sampleRate is specified as 0 then the values of global variables are recorded with the Globals task. Its default interval time is 50 ms.
  • If the sampleRate is specified as a value greater than 0 then the global variables are recorded in an ESM task whose interval time corresponds to the sampleRate. If no match is found, recording takes place with the ESM task with the shortest interval time. In this case, the recording does not take place in every cycle of the ESM task but in a multiple of the interval time that is less than the sampleRate.

Use the sampleRate parameter to indicate in which time grid the values are to be recorded. The sampleRate can only be a multiple of the interval time of a cyclic ESM task. If a sampleRate that does not correspond to this interval time is passed on, the SubscriptionService rounds the value to the next faster value.

Example:
Variables from task A and task B are to be recorded:

  • Interval time for task A: 10 ms
  • Interval time for task B: 8 ms

If you indicate 50 ms for sampleRate, the following is actually recorded:

  • Variables from task A at 50 ms (each fifth cycle)
  • Variables from task B at 48 ms (each sixth cycle)
  • Global variables are recorded with task B at 8 ms (each sixth cycle)

If you indicate value 0 for sampleRate, all data is recorded in the interval of the respective task.

  • Variables from task A at 10 ms
  • Variables from task B at 8 ms
  • Global variables are recorded with Globals task at 50 ms (default interval)

When a subscription was started, you can pause it via Unsubscribe. For this, call the following service function:

DataAccessError     Unsubscribe(uint subscriptionId);

If a subscription pauses, no new data is recorded. Existing data is available in the subscription. To restart recording, call Subscribe again.

GetVariableInfos

This service function shows which variables are currently recorded. Therefore, the function only returns information once Subscribe is called.

The function only returns information on variable sorting. This information is decisive for reading the data. Each subscription internally sorts the variables, e.g., by assignment to the ESM task. The data of added variables is therefore not read in the order in which the variables were added. The Read functions only provide the raw values of the variables but do not give information to which variable the value is assigned to. At this point, service function information is the only option to assign the values to the respective variables. Variable information is returned in the same order as data for the Read functions. Therefore, variable information has to be read before the Read functions of a subscription are called for the first time.

A matching Info function is available for each Read function.

To query all currently recorded variables, call the following function:

DataAccessError     GetVariableInfos(uint32 subscriptionId, 
                    GetVariableInfosVariableInfoDelegate variableInfoDelegate);

To query the data, call the associated Read function:

DataAccessError     ReadValues(uint32 subscriptionId, 
                    ReadTimeStampedValuesValuesDelegate valuesDelegate)

If, in addition to the variable values, you also require the time stamps of the value acquisition, call the following function:

DataAccessError     GetTimeStampedVariableInfos(uint32 subscriptionId, 
                    GetTimeStampedVariableInfosVariableInfoDelegate variableInfoDelegate)

Information on time stamps is returned in addition to information on the variables. A variable with the name timestamp and of the Arp.Plc:DataType.Int64 data type is always returned as the first element of an ESM task. This is followed by all information of the variable that is associated with the ESM task and can be assigned to the time stamp. If there are variables of several ESM tasks in the subscription, an additional time stamp is returned for each task, which is followed by the associated information.

ReadValues

Once you have started the recording of a subscription, you can query the acquired data. Different Read functions are available for this. The Read functions only return the variable values. The values are not assigned to the respective variable. For assigning the values to the respective variables, you have to call the corresponding Info function once (see GetVariableInfos).

The following Read service functions are available:

DataAccessError     ReadValues(uint32 subscriptionId, 
                    ReadTimeStampedValuesValuesDelegate valuesDelegate)

This function returns all values in a static order. The GetVariableInfos() function is used for assigning the variables.

Example:

  • Added variables from task A: a1, a2
  • Added variables from task B: b1

ReadValues:

Object[]

a2
 a1
 b1

The following function returns all values in a static order, including the associated time stamps:

DataAccessError     ReadTimeStampedValues(uint32 subscriptionId, 
                    ReadTimeStampedValuesValuesDelegate valuesDelegate);

The time stamps are always located before the associated variable values. The number of time stamps always corresponds to the number of ESM tasks the variables originate from. By means of the DateTime class, which is defined in the Arp namespace and in the Arp/System/Core/DateTime.hpp header file, the value of the timestamp variable can be converted into a time stamp. The GetTimeStampedVariableInfos() function is used for assigning the variables.

Example:

  • Added variables from task A: a1, a2
  • Added variables from task B: b1

ReadValues:

Object[]

Timestamp task A
a2
a1
Timestamp task B
b1

The following function returns all values packed in records:

DataAccessError     ReadRecords(uint32 subscriptionId, uint16 count, 
                    ReadRecordsRecordsDelegate recordsDelegate);

A record, also called data record, contains only the variable data from an ESM task and the associated time stamp. The time stamps are always located before the associated variable values. The variable order is always static and does not change during operation. An ESM task record is created for each ESM task. It contains all corresponding data records of the respective ESM task. Depending on the subscription configuration, an ESM task record can contain several data records.

Example:
A subscription of the Recording type with a task interval of 100 ms and a capacity of 10 returns 10 data records after one second. The time stamps are 100 ms apart. However, a subscription of the HighPerformance, RealTime, or DirectRead type only always returns one data record. By means of the Read function, you can read all subscription data of the Recording type at once.

In addition to the static order of variables in the data records, the order of the ESM task records is static, too. By means of the GetTimeStampedVariableInfos() function, each value can be assigned a variable. The variable information describes exactly the first ESM task record and all data records contained therein, from the first time stamp to the final variable information associated with this time stamp.

By means of the DateTime class, which is defined in the Arp namespace and in the Arp/System/Core/DateTime.hpp header file, the value of the timestamp variable in the respective data records can be converted into a time stamp.

Example:

  • Added variables from task A: a1, a2
  • Added variables from task B: b1
  • Task A recorded 2 records.
  • Task B recorded 1 record.

ReadValues:

Object[]

Timestamp task A
a2
a1
Timestamp task B
b1

 

ReadRecords:

Object[] (ESM task records)

Objects[] (ESM task record A)

Objects[](data record cycle 1)

Timestamp
a2
a1

Objects[](data record cycle 2)

Timestamp
a2
a1

Objects[] (ESM task record B)

Objects[](data record cycle 1)

Timestamp
b1

Changing a subscription

You can modify two things of an existing subscription:

  • The sampling interval used by the subscription to record data.
  • The variables that are to be recorded.

The subscription type can only be changed if you delete the subscription and create it again.

To change the sample rate, proceed as follows:

  • Stop the subscription by calling Unsubscribe.
  • Execute the subscription by calling Subscribe and entering the new sample rate as the parameter.

Several functions are available for changing the variables to be recorded:

  • To delete a variable from a subscription, call the following function:
    DataAccessError     RemoveVariable(uint32 subscriptionId, 
                        const RscString<512>& variableName),
  • To add a variable to a subscription, call the following function:
    DataAccessError     AddVariable(uint32 subscriptionId, 
                        const RscString<512>& variableName)
  • To add several variables to a subscription, call the following function:
    void                AddVariables(uint32 subscriptionId, 
                        AddVariablesVariableNamesDelegate variableNamesDelegate, 
                        AddVariablesResultDelegate resultDelegate);

The changes can be made during operation and also to a subscription that is currently recording. To apply the changes, execute Resubscribe.

  • To execute Resubscribe, call the following service function:
    DataAccessError     Resubscribe(uint32 subscriptionId, uint64 sampleRate);

Once Resubscribe was called, the changes are applied to the subscription. As the internal memories are rebuilt, data loss might occur.

Deleting a subscription

If a subscription is no longer required, you have to delete it. If you do not delete the subscription, it will be retained until the next restart of the PLC application (warm or cold restart).

  • To delete a subscription, call the following function:
    DataAccessError     DeleteSubscription(uint32 subscriptionId)

The internally reserved memory is released and the subscription ID becomes invalid.

 

 


• Published/reviewed: 2024-10-30   ☀  Revision 074 •