Figure 13 shows the structure of a RequestSPDU which originates at the SafetyConsumer and contains a SafetyConsumerID, a MonitoringNumber (MNR), and one byte of (non-safety-related) flags (Flags).
NOTE: The RequestSPDU does not contain a CRC-checksum.
Figure 14 shows the structure of a ResponseSPDU which originates at the SafetyProvider and contains the safety data (1 – 1500 Byte) and additional 25 Byte safety code (STrailer) as described in the subsequent sections, and the non-safety related data.
NOTE: In order to avoid spurious trips, the ResponseSPDU is transmitted in an atomic (consistent) way from the OPC UA platform interface of the SafetyProvider to the OPC UA platform interface of the SafetyConsumer. This is the task of the respective OPC UA mapper, see Figure 2.
Identifier of the SafetyConsumer instance, for diagnostic purposes, see Clause 11.1.2.
The SafetyConsumer uses the MNR to detect mis-timed SPDUs, e.g. such SPDUs which are continuously repeated by an erroneous network storing element. A different MNR is used in every RequestSPDU of a given SafetyConsumer, and a ResponseSPDU will only be accepted, if its MNR is identical to its matching RequestSPDU.
The checking for correctness of the MNR is performed by the SafetyConsumer, only.
[RQ8.1] The flags of the Safety Consumer (RequestSPDU.Flags) shall be used as shown in Table 18.
Table 18 – Structure of RequestSPDU.Flags
Bit nr. |
Identifier |
Description |
LSB = Bit 0 |
CommunicationError |
0: No error 1: An error was detected in the previous ResponseSPDU. |
Bit 1 |
OperatorAckRequested |
Used to inform the SafetyProvider that operator acknowledgment is requested. |
Bit 2 |
FSV_Activated |
Is used for conformance test of SafetyConsumer.SAPI.FSV_Activated |
Bit 3......7 |
Reserved for future use |
Always set to zero, not evaluated. |
NOTE: CommunicationError can be used as a trigger, e.g. for a communication analysis tool.
Flags reserved for future use shall be set to zero by the SafetyConsumer and shall not be evaluated by the SafetyProvider.
[RQ8.2] SafetyData shall contain the safety-related application data transmitted from the SafetyProvider to the SafetyConsumer. It may comprise multiple basic OPC UA variables (see Clause 6.4). For the sake of reducing distinctions of cases, SafetyData shall always be a structure, even if it contains a single basic OPC UA variable, only.
For the calculation of the CRC Signature, the order in which this data is processed by the calculation is important. SafetyProvider and SafetyConsumer must agree upon the number, type and order of application data transmitted in SafetyData. The sequence of SafetyData is fixed.
NOTE SafetyData may contain qualifier bits for a fine-grained activation of fail-safe substitute values. For a valid process value, the respective qualifier is set to 1 (good), whereas the value 0 (bad) is used for invalid values. Invalid process values are replaced by a fail-safe substitute value in the consumer’s safety application. See Clause 5.3.
[RQ8.3] The flags of the SafetyProvider (ResponseSPDU.Flags) shall be used as shown in Table 19.
Table 19 – Structure of ResponseSPDU.Flags
Bit nr. |
Name |
Description |
LSB = Bit 0 |
Operator acknowledgment at the provider, hereby forwarded to the SafetyConsumer, see OperatorAckProvider in the SAPI of the SafetyProvider, Clause 7.3.1. |
|
Bit 1 |
Activation of fail-safe values by the safety application at the SafetyProvider, hereby forwarded to the SafetyConsumer, see ActivateFSV in the SAPI of the SafetyProvider, Clause 7.3.1. |
|
Bit 2 |
Enabling and disabling of test mode in the SafetyProvider, hereby forwarded to the SafetyConsumer, see EnableTestMode in the SAPI of the SafetyProvider, Clause 7.3.1. |
|
Bit 3 ...... 7 |
Reserved for future use |
Always set to zero, not evaluated. |
[RQ8.4] Flags reserved for future use shall be set to zero by the SafetyProvider and shall not be evaluated by the SafetyConsumer.
This field is used by the SafetyConsumer to check whether the ResponseSPDU is coming from the correct SafetyProvider. For details, see Clause 8.1.3.1.
[RQ8.5] The SafetyConsumerID in the ResponseSPDU shall be a copy of the SafetyConsumerID received in the corresponding RequestSPDU. See Clause 8.1.3.1.
[RQ8.6] The MonitoringNumber in the ResponseSPDU shall be a copy of the MonitoringNumber received in the corresponding RequestSPDU. See Clause 8.1.3.1.
The SafetyConsumer uses the ResponseSPDU.MonitoringNumber to detect mis-timed SPDUs, e.g. such SPDUs which are continuously repeated by an erroneous network storing element. A different MonitoringNumber is used in every RequestSPDU of a given SafetyConsumer, and a ResponseSPDU will only be accepted, if its MonitoringNumber is identical to its matching RequestSPDU.
[RQ8.7] This CRC-checksum shall be used to detect data corruption. See Clause 8.1.3.5 on how it is calculated in the SafetyProvider and how it is checked in the SafetyConsumer.
[RQ8.8] This structure shall be used to transmit non-safety data values (e.g. diagnostic information) together with safe data consistently. Non-safety data is not CRC-protected and may stem from an unsafe source. [RQ8.9] When presented to the safety application (e.g. at an output of the SafetyConsumer), non-safety values shall clearly be indicated as “non-safety”, by an appropriate vendor-specific mechanism (e.g. by using a different color).
To avoid possible problems with empty structures, the dummy structure NonSafetyDataPlaceholder shall be used when no non-safety data is used.
The two SCL-services “SafetyProvider” and “SafetyConsumer” are specified using state diagrams.
Figure 15 shows the sequence of request and response with SafetyData and the timeouts for OPC UA Safety.
NOTE: Transmission errors are handled within the OPC UA stack (e.g. when using client/server over TCP) and do not have to be corrected or re-transmitted by OPC UA Safety.
Figure 15 – Sequence diagram for OPC UA Safety
The SafetyConsumerTimeout is the watchdog time checked in the SafetyConsumer. The watchdog is restarted whenever a new RequestSPDU is generated (transitions T14 and T26 of the SafetyConsumer). If an appropriate ResponseSPDU is received in time, and the checks for data integrity, authenticity, and timeliness are all valid, the timer will not expire before it is restarted.
Otherwise, the watchdog timer expires, and the SafetyConsumer triggers a safe reaction. To duly check its timer, the SafetyConsumer is executed cyclically, with period ConsumerCycleTime. ConsumerCycleTime is expected to be smaller than SafetyConsumerTimeout.
The ConsumerCycleTime is the maximum time for the cyclic update of the SafetyConsumer. It is the timeframe from one call of the SafetyConsumer to the next call of the SafetyConsumer. The implementation and error reaction of ConsumerCycleTime is not part of OPC UA Safety; it is vendor specific.
[RQ8.10] Figure 16 shows a simplified representation of the state diagram of the SafetyProvider. The exact behavior is described in Table 21, Table 22, and Table 23. The SafetyProvider shall implement that behavior. It is not required to literally follow the entries given in the tables, if the behavior does not change.
Figure 16 – Simplified representation of the state diagram for the SafetyProvider
Graphical representation |
Type |
Description |
|
Activity State |
Within these interruptible "activity" states the SafetyProvider waits for new inputs. |
|
Action State |
Within these non-interruptible "action" states events like new request is deferred until the next "activity" state is reached, see [1]. |
The transitions are fired in case of an event, for example receiving a SPDU. In case of several possible transitions, so-called guard conditions (refer to […] in UML diagrams) define which transition to fire
The diagram consists of activity and action states. Activity states are surrounded by bold lines, action states are surrounded by thin lines. While activity states may be interruptible by new events, action states are not. External events occurring while the state machine is in an action state, are deferred until the next activity state is reached.
Table 20 – Symbols used for state machines.
Table 21 – SafetyProvider instance internal items
INTERNAL ITEMS |
TYPE |
DEFINITION |
RequestSPDU_i |
Variable |
Local Memory for RequestSPDU (required to react on changes). |
<Get RequestSPDU> |
Macro |
Instruction to take the whole RequestSPDU from the OPC UA Mapper. |
<Set ResponseSPDU> |
Macro |
Instruction to transfer the whole ResponseSPDU to the OPC UA Mapper |
<build ResponseSPDU> |
Macro |
Take the MNR and the SafetyConsumerID of the received RequestSPDU. Add the SPDU_ID_1, SPDU_ID_2, SPDU_ID_3, Flags, and SafetyData, as well as the calculated CRC. See Clause 8.1.3.1 |
Table 22 – States of SafetyProvider instance
STATE NAME |
STATE DESCRIPTION |
Initialization |
// Initial state SAPI.SafetyData:= 0SAPI.MonitoringNumber:= 0SAPI.SafetyConsumerID:= 0 RequestSPDU_i:= 0 |
S1_WaitForRequest |
// waiting on next RequestSPDU from SafetyConsumer <Get RequestSPDU> |
S2_PrepareSPDU |
ResponseSPDU.Flags.ActivateFSV := SAPI.ActivateFSVResponseSPDU.Flags.OperatorAckProvider := SAPI.OperatorAckProvider Response.Flags.TestModeActivated := SAPI.EnableTestMode <build ResponseSPDU> // see Clause 8.1.3.1 |
Table 23 – SafetyProvider driver transitions
TRAN-SITION |
SOURCE STATE |
TARGET STATE |
GUARD CONDITION |
ACTIVITY |
T1 |
Init |
1 |
- |
|
T2
|
1
|
2 |
// RequestSPDU received <Get RequestSPDU> When: [RequestSPDU_i<> RequestSPDU] |
// Process Request RequestSPDU_i:= RequestSPDU SAPI.MonitoringNumber:= RequestSPDU.MonitoringNumber SAPI.SafetyConsumerID := RequestSPDU.SafetyConsumerID |
T3 |
2 |
1 |
// SPDU is prepared - |
<Set ResponseSPDU> |
[RQ8.11] Figure 17 shows a simplified representation of the state diagram of the SafetyConsumer. The exact behavior is described in Table 24, Table 25, and Table 26. The SafetyConsumer shall implement that behavior. It is not required to literally follow the entries given in the tables, if the behavior does not change.
Figure 17 – Principle state diagram for SafetyConsumer
Table 24 – SafetyConsumer driver internal items
INTERNAL ITEMS |
TYPE |
DEFINITION |
Constants |
|
|
MNR_min := 0x100 |
UInt32 |
// 0x100 is the start value for MNR, also used after wrap-around. // The values 0…0xFF are reserved for future use. |
Variables |
|
|
FaultReqOA_i |
Boolean |
Local memory for errors which request operator acknowledgment. |
MNR_i |
UInt32 |
Local Monitoring Number (MNR). |
prevMNR_i |
UInt32 |
Local memory for previous MNR |
SafetyProviderID_i |
UInt32 |
Local memory for SafetyProviderID in use. |
CRCCheck_i |
Boolean |
Local variable used to store the result of the CRC-check. |
SPDUCheck_i |
Boolean |
Local variable used to store the result of the additional SPDU-checks. |
SPDU_ID_1_i |
UInt32 |
Local variable to store the expected SPDU_ID_1 |
SPDU_ID_2_i |
UInt32 |
Local variable to store the expected SPDU_ID_2 |
SPDU_ID_3_i |
UInt32 |
Local variable to store the expected SPDU_ID_3 |
Timers |
|
|
ConsumerTimer |
Timer |
This timer is used to check whether the next valid ResponseSPDU has arrived on time. It is initialized using the parameter SPI.SafetyConsumerTimeOut. |
ErrorIntervalTimer |
Timer |
This timer is initialized using the parameter SPI.SafetyErrorIntervalLimit. See Table 17, Clause 7.4.2, and Clause 11.4 for more information.
|
Macros <...><...> |
|
|
<risingEdge x> |
Macro |
// detection of a rising edge: If x==true && tmp==false Then result:= trueElse result := falseEndif tmp := x |
<Get ResponseSPDU> |
Macro |
Instruction to take the whole ResponseSPDU from the OPC UA Mapper. |
<Use FSV> |
Macro |
SafetyData is set to binary 0 SAPI.FSV_Activated := 1 RequestSPDU.Flags.FSV_Activated := 1
NOTE: If a safety application prefers different fail-safe values than binary 0, this can be implemented in the safety application by querying SAPI.FSV_Activated. |
<Use SafetyData> |
Macro |
SAPI.SafetyData is set to ResponseSPDU. SafetyData SAPI.FSV_Activated := 0 RequestSPDU.Flags.FSV_Activated := 0 RequestSPDU.Flags.CommunicationError:=0 |
<Set RequestSPDU> |
Macro |
Instruction to transfer the whole RequestSPDU to the OPC UA Mapper |
<(Re)Start ConsumerTimer> |
Macro |
Restarts the consumer timer. |
<(Re)Start ErrorIntervalTimer> |
Macro |
Restarts the error interval timer. |
<ConsumerTimer expired?> |
Macro |
Yields “true” if the timer is running longer than SPI.SafetyConsumerTimeOut since last restart, “false” otherwise. |
<ErrorIntervalTimer expired?> |
Macro |
Yields “true” if the timer is running longer than SPI.SafetyErrorIntervalLimit since last restart, “false” otherwise. |
<Build RequestSPDU> |
Macro |
RequestSPDU.SafetyConsumerID := SPI.SafetyConsumerID RequestSPDU.MonitoringNumber := MNR_i
|
<Calc SPDU_ID_i> |
Macro |
uint128 BaseID uint32 ProviderID const uint32 SafetyProviderLevel_ID := … // see Clause 8.1.3.3 If(SAPI.SafetyBaseID == 0) thenBaseID := SPI.SafetyBaseIDElse BaseID := SAPI.SafetyBaseID Endif If(SAPI.SafetyProviderID == 0) thenProviderID := SPI.SafetyProviderIDElse ProviderID := SAPI.SafetyProviderID Endif SPDU_ID_1_i := BaseID (bytes 0…3) XOR SafetyProviderLevel_ID SPDU_ID_2_i := BaseID (bytes 4…7) XOR SPI.SafetyStructureSignature SPDU_ID_3_i := BaseID (bytes 8…11) XOR BaseID (bytes 12…15) XOR ProviderID // see Clause 8.1.3.2 for clarification |
<Set Diag(ID, Boolean permanent)> |
Macro |
// ID is the identifier for the type of diagnostic output, see Table 29// permanent is used to indicate a permanent error. // Only one diagnostic message is created for multiple permanent // errors in sequence If(RequestSPDU.Flags.CommunicationError == 0) Then <do vendor-specific function for diagnostic output using ID>Else //do nothingEndif
RequestSPDU.Flags.CommunicationError:= permanent // Note: See for possible values for “ID” and their codes. |
External Event |
|
|
Restart Cycle |
Event |
The external call of SafetyConsumer can be interpreted as event “Restart Cycle” |
Note: A macro is a shorthand representation for operations described in the according definition.
Table 25 – SafetyConsumer driver states
STATE NAME |
STATE DESCRIPTION |
Initialization |
// Initial state of the SafetyConsumer driver instance. <Use FSV> SAPI.OperatorAckRequested := 0RequestSPDU.Flags.OperatorAckRequested :=0SAPI.OperatorAckProvider := 0FaultReqOA_i :=0SAPI.TestModeActivated := 0RequestSPDU.Flags.CommunicationError:= 0 |
S11_Wait for (Re)Start |
// Safety Layer is waiting (Re)Start |
S12_initialize MNR |
// Use previous MNR if known// or random MNR within the allowed range (e.g. after cold start), see Clause 11.2. MNR_i := (previous MNR_i if known) or (random MNR) MNR_i := max(MNR_i, MNR_min) |
S13_PrepareRequest |
// Build RequestSPDU and send (done in T16) |
S14_WaitForChangedSPDU |
// Safety Layer is waiting on next ResponseSPDU from SafetyProvider |
S15_CRCCheckSPDU |
// Check CRC
uint32 CRC_calcCRCCheck_i := (CRC_calc == ResponseSPDU.CRC) // see Clause 8.1.3.5 on how to calculate CRC_calc |
S16_CheckResponseSPDU |
// Check SafetyConsumerID and SPDU_ID and MNR (see T22, T23, T24)
SPDUCheck_i := ResponseSPDU.SPDU_ID_1== SPDU_ID_1_i && ResponseSPDU.SPDU_ID_2== SPDU_ID_2_i && ResponseSPDU.SPDU_ID_3== SPDU_ID_3_i && ResponseSPDU.SafetyConsumerID== SPI.SafetyConsumerID && ResponseSPDU.MNR==MNR_i |
S17_Error |
SAPI.TestModeActivated := 0 |
S18_ProvideSafetyData |
// Provide SafetyData to the application program |
Table 26 – SafetyConsumer driver transitions
TRANSITION |
SOURCE STATE |
TARGET STATE |
GUARD CONDITION |
ACTIVITY |
T12 |
Init |
S11 |
- |
|
T13 |
S11 |
S12
|
//Start [SAPI.Enable==1] |
<(Re)Start ErrorIntervalTimer><calc SPDU_ID>// see Clause 8.1.3.2 for clarification |
T14 |
S12 |
S13 |
// MNR initialized |
<(Re)Start ConsumerTimer> |
T15 |
S18 |
S11 |
// Termination [SAPI.Enable==0] |
<Use FSV> |
T16 |
S13 |
S14 |
// Build Request SPDU and send
|
prevMNR_i := MNR_i, If MNR_i== 0xFFFFFFFFFThen MNR_i := MNR_min,Else MNR_i := MNR_i + 1Endif <Build RequestSPDU> <Set RequestSPDU> |
T17 |
S14 |
S15 |
// New ResponseSPDU received <Get ResponseSPDU> [ResponseSPDU.MNR<>prevMNR_i] |
- |
T18 |
S14 |
S17 |
// WDTimeout [<ConsumerTimer expired?>] |
<Set Diag(CommErrTO,1)><use FSV> If SPI.SafetyOperatorAckNecessary == 1Then FaultReqOA_i := 1Else // do nothingEndif |
T19 |
S15 |
S13 |
// When CRC err and SafetyErrorIntervalTimer expired[(crcCheck_i == 0 ) && <ErrorIntervalTimer expired?>] |
<(Re)Start ErrorIntervalTimer><Set Diag(CRCerrIgn,0)> |
T20 |
S15 |
S17 |
// When CRC err and SafetyErrorIntervalTimer not expired [(crcCheck_i == 0 ) && not <ErrorIntervalTimer expired?>] |
<(Re)Start ErrorIntervalTimer><Set Diag(CRCerrOA,1)><use FSV> FaultReqOA_i:= 1
|
T21 |
S15 |
S16 |
// When CRCCheckOK [crcCheck_i == 1 ] |
- |
T22 |
S16 |
S18 |
// SPDU OK [SPDUCheck_i==1] |
// For clarification, refer to Figure 18
// indicate OA from provider SAPI.OperatorAckProvider := ResponseSPDU.Flags.OperatorAckProvider
// OA requested due to edge at ActivateFSV? If (<risingEdge ResponseSPDU.Flags.ActivateFSV>&& SPI.SafetyOperatorAckNecessary == 1)Then FaultReqOA_i:=1;<Set Diag(FSV_Requested,1)> Else // do nothing Endif // Set Flags if OA requested: If FaultReqOA_i==1 Then SAPI.OperatorAckRequested:= 1, RequestSPDU.Flags.OperatorAckRequested:=1, FaultReqOA_i:= 0Else //do nothingEndif // Reset flags after OA: If (<risingEdge SAPI.OperatorAckConsumer >) Then SAPI.OperatorAckRequested:=0, RequestSPDU.Flags.OperatorAckRequested:=0Else // do nothingEndif If SAPI.OperatorAckRequested==1 || ResponseSPDU.ActivateFSV==1Then <use FSV> Else <use SafetyData>Endif
// Notify safety application that SafetyProvider is in test mode: SAPI.TestModeActivated:= ResponseSPDU.Flags.TestModeActivated |
T23 |
S16 |
S13 |
// SPDU NOK and SafetyErrorIntervalTimer expired[SPDUCheck_i == 0 && <ErrorIntervalTimer expired?>] |
<(Re)Start ErrorIntervalTimer>,// Send diagnostic message according the// detected error: If ResponseSPDU.SafetyConsumerID<> SPI.SafetyConsumerIDThen <Set Diag(CoIDerrIgn,0)>ElseIf ResponseSPDU.MNR<>MNR_iThen <Set Diag(MNRerrIgn,0)> Else //do nothing EndIf If ResponseSPDU.SPDU_ID_1<> SPDU_ID_1_i || ResponseSPDU.SPDU_ID_2<> SPDU_ID_2_i || ResponseSPDU.SPDU_ID_3<> SPDU_ID_3_iThen <Set Diag(SD_IDerrIgn,0)> Else // do nothing Endif Endif
|
T24 |
S16 |
S17 |
// SPDU NOK and SafetyErrorIntervalTimer not expired [SPDUCheck_i == 0 && not <ErrorIntervalTimer expired?>] |
<(Re)Start ErrorIntervalTimer>// Send diagnostic message according the // detected error: If ResponseSPDU.SafetyConsumerID<> SPI.SafetyConsumerIDThen <Set Diag(CoIDerrIgn,1)>Else If ResponseSPDU.MNR<>MNR_iThen <Set Diag(MNRerrIgn,1)> Else //do nothing Endif If ResponseSPDU.SPDU_ID_1<> SPDU_ID_1_i || ResponseSPDU.SPDU_ID_2<> SPDU_ID_2_i || ResponseSPDU.SPDU_ID_3<> SPDU_ID_3_iThen <Set Diag(SD_IDerrIgn,1)> <use FSV> Else //do nothing Endif Endif FaultReqOA_i:= 1 |
T25 |
S17 |
S18 |
// SPDU NOK - |
|
T26 |
S18 |
S13 |
// Restart Cycle [SAPI.Enable==1] |
<(Re)Start ConsumerTimer> |
Figure 18 shows the sequence after a second ResponseSPDU error was detected before the timer SafetyErrorIntervalTimer stops.
Figure 18 – Sequence diagram for OA
After the error is gone the sequence follows the logic of T22 in Table 26.
[RQ8.12] ResponseSPDU shall be built by the SafetyProvider by copying RequestSPDU.MonitoringNumber and the RequestSPDU.SafetyConsumerID into the ResponseSPDU. After this, SPDU_ID, Flags, and the SafetyData shall be updated. Finally, ResponseSPDU.CRC shall be calculated and appended.
Figure 19 – Overview of task for SafetyProvider
For the ResponseSPDU.Flags, see Clause 8.1.1.5. For the calculation of the SPDU_ID, see Clause 8.1.3.2. For the calculation of CRC, see Clause 8.1.3.5.
[RQ8.13] The SPDU_ID_1-3 shall be calculated according to Figure 20 and Table 27.
Figure 20 – Calculation of the SPDU_ID
Table 27 – Presentation of the SPDU_ID
SPDU_ID_1 := SafetyBaseID (bytes 0…3) XOR SafetyProviderLevel_ID |
SPDU_ID_2 := SafetyBaseID (bytes 4…7) XOR SafetyStructureSignature |
SPDU_ID_3 := SafetyBaseID (bytes 8…11) XOR SafetyBaseID (bytes 12…15) XOR SafetyProviderID |
NOTE: In case of a mismatch between expected SPDU_ID and actual SPDU_ID, the following rules can be used for diagnostic purposes:
- If all of SPDU_ID1, SPDU_ID2, and SPDU_ID3 differ, there is a mismatching SafetyBaseID.
- If SPDU_ID3 differs, but SPDU_ID1 and SPDU_ID2 do not, there is a mismatching SafetyProviderID.
- If SPDU_ID2 differs, but SPDU_ID1 and SPDU_ID3 do not, the structure or identifier of the safety data do not match.
- If SPDU_ID3 differs, but SPDU_ID1 and SPDU_ID2 do not, the SafetyProviderLevel does not match.
By using these rules, there is a very low probability (<10-9) that a mismatching SafetyBaseID will be misinterpreted. From a practical view, this probability can be ignored.
Table 28 – Coding for the SafetyProviderLevel_ID
SafetyProviderLevel |
Value of SafetyProviderLevel_ID |
Up to SIL1Up to SIL2Up to SIL3Up to SIL4 |
0x119128810x647C46540xDEAA9DEE0xAB47F33B |
[RQ8.14] Exactly one of the values provided in Table 28 shall be used as constant code value for SafetyProviderLevel_ID. They were chosen in such a way that the hamming distance becomes maximal (hamming distance of 21).
[RQ8.15] Measures shall be taken to avoid that a SafetyProvider is erroneously using a code-value belonging to a SIL that is higher than the SIL it is capable of. For instance, a SafetyProvider capable of SIL1-3 should not be able to accidently use the value 0xAB47F33B used for SIL4. One way to achieve this is to avoid that this constant appears in the source code of the SafetyProvider at all.
The SafetyProviderLevel is independent to the SIL capability of the provided SafetyData, see Clause 7.3.3.
SafetyStructureSignature is used to check the number, data types and order of application data transmitted in SafetyData. If the SafetyConsumer is expecting anything different than what the SafetyProvider actually provides, SafetyStructureSignature will differ, allowing the SafetyConsumer to enable fail-safe substitute values.
In addition, also the identifier of the structure type (StructureIdentifier) is taken into account when calculating SafetyStructureSignature. This ensures that the SafetyProvider and SafetyConsumer are using the same identifier for the structure type, effectively avoiding any confusion.
For instance, if a SafetyProvider defines a structure with identifier “vec3D_m” comprising three floats containing a three-dimensional vector in the metric system, this structure could not be used by a SafetyConsumer expecting a structure of type “vec3D_in” where the vector components are given in inch, or even at a SafetyConsumer expecting a structure of type “orientation”, containing three floats to define an orientation using Euler angles.
[RQ8.16] StructureSignature shall be calculated as CRC32-signature (polynomial: 0x F4ACFB13, see Annex B.1) over StructureIdentifier (encoding: UTF-8), StructureSignatureVersion and the sequence of the DataType IDs. After each datatype ID, a 16-bit zero-value (0x0000) shall be inserted.
The terminating zero of StructureIdentifier shall not be considered when calculating the CRC.
[RQ8.17] StructureIdentifier shall be visible in the OPC UA information model for diagnostic purposes, but shall not be evaluated by the SafetyConsumer during runtime.
[RQ8.18] For version V1.0 of the specification, the value for StructureSignatureVersion shall be 0x0001.
Example:
StructureIdentifier, e.g. “foo” = 0x66,0x6f,0x6f
StructureSignatureVersion:= 0x0001
1. DataType Int16: (Id = 0x0004), // see Clause 6.4
2. DataType Boolean: (Id = 0x0001),
3. DataType Float32: (Id =0x000A)
StructureSignature := CRC32(0x66,0x6f,0x6f, 0x00,0x01,0x00,0x00, 0x00,0x04, 0x00,0x00, 0x00,0x01, 0x00,0x00, 0x00,0x0A)
NOTE: The insertion of 0x0000 values before the DataType ID, allows for introducing arrays in later version of OPC UA Safety.
The DataType ID can be found at the DataType or at the derived DataType.
The OPC UA Information model supports not only built-in DataTypes, but also allows for DataTypes derived from built-in DataTypes. In case of derived DataTypes, the Data Structure CRC uses the ID of a built-in DataType (which is found at the end of the tree).
Example: the base type "enumeration" is derived from the DataType Int32 (ID=6); therefore, an ID of 6 is used whenever the DataType “enumeration” is used in SafetyData.
In this version of the specification, arrays are not supported. Instead, multiple variables of the same type are used.
The SafetyProvider calculates the CRC signature (ResponseSPDU.CRC) and sends it to the SafetyConsumer as part of SPDU. This enables the SafetyConsumer to check the correctness of the SPDU including the SafetyData, Flags, MNR, SafetyConsumerID and SPDU_ID by recalculating the CRC signature (CRC_calc).
[RQ8.19] The generator polynomial 0x F4ACFB13 shall be used for the 32-Bit CRC signature.
[RQ8.20] If SafetyData is longer than one byte (e.g. UInt16, Int16, Float32), it shall be decoded and encoded using big-endian order in which the least significant byte appears last in the incremental memory address stream.
[RQ8.21] The calculation sequence shall begin with the highest memory address (n) of the SafetyData counting back to the lowest memory address (0) and then include also the STrailer beginning with the highest memory address.
Figure 21 shows the calculation sequence of the CRC_SPDU using an example SafetyData with the following fields:
Boolean var1
UInt16var2
Int16 var3
UInt32var4
Int32 var5
The STrailer and SafetyData have a total length of 34 bytes. The calculation of ResponseSPDU.CRC (SafetyProvider) or CRC_calc (SafetyConsumer) is done in reverse order, i.e. starts at byte 33 and ends at byte 0.
Figure 21 – Calculation of the CRCr
For devices where the SafetyData remains at the same value for a longer period of time, it is a viable optimization to store the calculated CRC over the SafetyData and take – in case the SafetyData hasn’t changed, this stored CRC as start value for the CRC calculation of the STrailer.
Note: On the SafetyConsumer, CRC_calc is calculated using data received in the ResponseSPDU, and not from expected values.